diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs index ba117031b92..3bc77f951c2 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs @@ -124,6 +124,7 @@ public static IServiceCollection AddEntityFrameworkSqlite(this IServiceCollectio : new SqliteUpdateSqlGenerator(dependencies); }) .TryAdd() + .TryAdd() .TryAddProviderSpecificServices( b => b.TryAddScoped()); diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteGlobMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteGlobMethodTranslator.cs index 2de04affc5c..83efe4ca8a2 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteGlobMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteGlobMethodTranslator.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -17,7 +16,7 @@ public class SqliteGlobMethodTranslator : IMethodCallTranslator private static readonly MethodInfo MethodInfo = typeof(SqliteDbFunctionsExtensions) .GetMethod(nameof(SqliteDbFunctionsExtensions.Glob), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; - private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly SqliteSqlExpressionFactory _sqlExpressionFactory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -25,7 +24,7 @@ public class SqliteGlobMethodTranslator : IMethodCallTranslator /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public SqliteGlobMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) + public SqliteGlobMethodTranslator(SqliteSqlExpressionFactory sqlExpressionFactory) { _sqlExpressionFactory = sqlExpressionFactory; } @@ -46,18 +45,8 @@ public SqliteGlobMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) { var matchExpression = arguments[1]; var pattern = arguments[2]; - var stringTypeMapping = ExpressionExtensions.InferTypeMapping(matchExpression, pattern); - return _sqlExpressionFactory.Function( - "glob", - new[] - { - _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping), - _sqlExpressionFactory.ApplyTypeMapping(matchExpression, stringTypeMapping) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(bool)); + return _sqlExpressionFactory.Glob(matchExpression, pattern); } return null; diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs new file mode 100644 index 00000000000..7b58edc0a0e --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessor.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqliteParameterBasedSqlProcessor : RelationalParameterBasedSqlProcessor +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqliteParameterBasedSqlProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + bool useRelationalNulls) + : base(dependencies, useRelationalNulls) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + /// + protected override Expression ProcessSqlNullability( + Expression queryExpression, + IReadOnlyDictionary parametersValues, + out bool canCache) + => new SqliteSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache); +} diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessorFactory.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessorFactory.cs new file mode 100644 index 00000000000..9ea85519924 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteParameterBasedSqlProcessorFactory.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqliteParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory +{ + private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqliteParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies) + => _dependencies = dependencies; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls) + => new SqliteParameterBasedSqlProcessor(_dependencies, useRelationalNulls); +} diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs index 4a38d76c811..766076182cb 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -24,6 +25,20 @@ public SqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) { } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override Expression VisitExtension(Expression extensionExpression) + => extensionExpression switch + { + GlobExpression globExpression => VisitGlob(globExpression), + RegexpExpression regexpExpression => VisitRegexp(regexpExpression), + _ => base.VisitExtension(extensionExpression) + }; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -72,4 +87,22 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression) protected override void GenerateSetOperationOperand(SetOperationBase setOperation, SelectExpression operand) // Sqlite doesn't support parentheses around set operation operands => Visit(operand); + + private Expression VisitGlob(GlobExpression globExpression) + { + Visit(globExpression.Match); + Sql.Append(" GLOB "); + Visit(globExpression.Pattern); + + return globExpression; + } + + private Expression VisitRegexp(RegexpExpression regexpExpression) + { + Visit(regexpExpression.Match); + Sql.Append(" REGEXP "); + Visit(regexpExpression.Pattern); + + return regexpExpression; + } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteRegexMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteRegexMethodTranslator.cs index 5e1cccd098b..a1f61089731 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteRegexMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteRegexMethodTranslator.cs @@ -3,7 +3,6 @@ using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -18,7 +17,7 @@ public class SqliteRegexMethodTranslator : IMethodCallTranslator private static readonly MethodInfo RegexIsMatchMethodInfo = typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), new[] { typeof(string), typeof(string) })!; - private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly SqliteSqlExpressionFactory _sqlExpressionFactory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -26,7 +25,7 @@ private static readonly MethodInfo RegexIsMatchMethodInfo /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public SqliteRegexMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) + public SqliteRegexMethodTranslator(SqliteSqlExpressionFactory sqlExpressionFactory) { _sqlExpressionFactory = sqlExpressionFactory; } @@ -47,18 +46,8 @@ public SqliteRegexMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) { var input = arguments[0]; var pattern = arguments[1]; - var stringTypeMapping = ExpressionExtensions.InferTypeMapping(input, pattern); - return _sqlExpressionFactory.Function( - "regexp", - new[] - { - _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping), - _sqlExpressionFactory.ApplyTypeMapping(input, stringTypeMapping) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(bool)); + return _sqlExpressionFactory.Regexp(input, pattern); } return null; diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs index 0675a166cbf..4e8721bd858 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -13,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; /// public class SqliteSqlExpressionFactory : SqlExpressionFactory { + private readonly RelationalTypeMapping _boolTypeMapping; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -21,8 +26,7 @@ public class SqliteSqlExpressionFactory : SqlExpressionFactory /// public SqliteSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) : base(dependencies) - { - } + => _boolTypeMapping = dependencies.TypeMappingSource.FindMapping(typeof(bool), dependencies.Model)!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -106,4 +110,65 @@ public virtual SqlFunctionExpression Date( returnType, typeMapping); } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual GlobExpression Glob( + SqlExpression match, + SqlExpression pattern) + => (GlobExpression)ApplyDefaultTypeMapping(new GlobExpression(match, pattern, _boolTypeMapping)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RegexpExpression Regexp( + SqlExpression match, + SqlExpression pattern) + => (RegexpExpression)ApplyDefaultTypeMapping(new RegexpExpression(match, pattern, _boolTypeMapping)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [return: NotNullIfNotNull("sqlExpression")] + public override SqlExpression? ApplyTypeMapping(SqlExpression? sqlExpression, RelationalTypeMapping? typeMapping) + => sqlExpression == null || sqlExpression.TypeMapping != null + ? sqlExpression + : sqlExpression switch + { + GlobExpression globExpression => ApplyTypeMappingOnGlob(globExpression), + RegexpExpression regexpExpression => ApplyTypeMappingOnRegexp(regexpExpression), + _ => base.ApplyTypeMapping(sqlExpression, typeMapping) + }; + + private SqlExpression ApplyTypeMappingOnGlob(GlobExpression globExpression) + { + var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(globExpression.Match, globExpression.Pattern) + ?? Dependencies.TypeMappingSource.FindMapping(globExpression.Match.Type, Dependencies.Model); + + return new GlobExpression( + ApplyTypeMapping(globExpression.Match, inferredTypeMapping), + ApplyTypeMapping(globExpression.Pattern, inferredTypeMapping), + globExpression.TypeMapping); + } + + private SqlExpression? ApplyTypeMappingOnRegexp(RegexpExpression regexpExpression) + { + var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(regexpExpression.Match, regexpExpression.Pattern) + ?? Dependencies.TypeMappingSource.FindMapping(regexpExpression.Match.Type, Dependencies.Model); + + return new RegexpExpression( + ApplyTypeMapping(regexpExpression.Match, inferredTypeMapping), + ApplyTypeMapping(regexpExpression.Pattern, inferredTypeMapping), + regexpExpression.TypeMapping); + } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlNullabilityProcessor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlNullabilityProcessor.cs new file mode 100644 index 00000000000..9385ab82464 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlNullabilityProcessor.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqliteSqlNullabilityProcessor : SqlNullabilityProcessor +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqliteSqlNullabilityProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + bool useRelationalNulls) + : base(dependencies, useRelationalNulls) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override SqlExpression VisitCustomSqlExpression( + SqlExpression sqlExpression, + bool allowOptimizedExpansion, + out bool nullable) + => sqlExpression switch + { + GlobExpression globExpression => VisitGlob(globExpression, allowOptimizedExpansion, out nullable), + RegexpExpression regexpExpression => VisitRegexp(regexpExpression, allowOptimizedExpansion, out nullable), + _ => base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable) + }; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual SqlExpression VisitGlob( + GlobExpression globExpression, + bool allowOptimizedExpansion, + out bool nullable) + { + var match = Visit(globExpression.Match, out var matchNullable); + var pattern = Visit(globExpression.Pattern, out var patternNullable); + + nullable = matchNullable || patternNullable; + + return globExpression.Update(match, pattern); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual SqlExpression VisitRegexp( + RegexpExpression regexpExpression, + bool allowOptimizedExpansion, + out bool nullable) + { + Check.NotNull(regexpExpression, nameof(regexpExpression)); + + var match = Visit(regexpExpression.Match, out var matchNullable); + var pattern = Visit(regexpExpression.Pattern, out var patternNullable); + + nullable = matchNullable || patternNullable; + + return regexpExpression.Update(match, pattern); + } +} diff --git a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs new file mode 100644 index 00000000000..0363d186f93 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class GlobExpression : SqlExpression +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public GlobExpression(SqlExpression match, SqlExpression pattern, RelationalTypeMapping typeMapping) + : base(typeof(bool), typeMapping) + { + + Match = match; + Pattern = pattern; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SqlExpression Match { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SqlExpression Pattern { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override RelationalTypeMapping TypeMapping + => base.TypeMapping!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var match = (SqlExpression)visitor.Visit(Match); + var pattern = (SqlExpression)visitor.Visit(Pattern); + + return Update(match, pattern); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual GlobExpression Update(SqlExpression match, SqlExpression pattern) + => match != Match || pattern != Pattern + ? new GlobExpression(match, pattern, TypeMapping) + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Visit(Match); + expressionPrinter.Append(" GLOB "); + expressionPrinter.Visit(Pattern); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool Equals(object? obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is GlobExpression globExpression + && Equals(globExpression)); + + private bool Equals(GlobExpression globExpression) + => base.Equals(globExpression) + && Match.Equals(globExpression.Match) + && Pattern.Equals(globExpression.Pattern); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override int GetHashCode() + => HashCode.Combine(base.GetHashCode(), Match, Pattern); +} diff --git a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs new file mode 100644 index 00000000000..1a4b3918547 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RegexpExpression : SqlExpression +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public RegexpExpression(SqlExpression match, SqlExpression pattern, RelationalTypeMapping typeMapping) + : base(typeof(bool), typeMapping) + { + Match = match; + Pattern = pattern; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SqlExpression Match { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual SqlExpression Pattern { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override RelationalTypeMapping TypeMapping + => base.TypeMapping!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var match = (SqlExpression)visitor.Visit(Match); + var pattern = (SqlExpression)visitor.Visit(Pattern); + + return Update(match, pattern); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RegexpExpression Update(SqlExpression match, SqlExpression pattern) + => match != Match || pattern != Pattern + ? new RegexpExpression(match, pattern, TypeMapping) + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Visit(Match); + expressionPrinter.Append(" REGEXP "); + expressionPrinter.Visit(Pattern); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool Equals(object? obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is RegexpExpression regexpExpression + && Equals(regexpExpression)); + + private bool Equals(RegexpExpression regexpExpression) + => base.Equals(regexpExpression) + && Match.Equals(regexpExpression.Match) + && Pattern.Equals(regexpExpression.Pattern); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override int GetHashCode() + => HashCode.Combine(base.GetHashCode(), Match, Pattern); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindDbFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindDbFunctionsQuerySqliteTest.cs index ddb845968bb..ffa450ad67f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindDbFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindDbFunctionsQuerySqliteTest.cs @@ -31,7 +31,7 @@ await AssertCount( """ SELECT COUNT(*) FROM "Customers" AS "c" -WHERE glob('*M*', "c"."ContactName") +WHERE '*M*', "c"."ContactName" GLOB '*M*' """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs index 3c80c16b9f9..7cc2a1d474d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs @@ -812,7 +812,7 @@ public override async Task Regex_IsMatch_MethodCall(bool async) """ SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" FROM "Customers" AS "c" -WHERE regexp('^T', "c"."CustomerID") +WHERE "c"."CustomerID" REGEXP '^T' """); } @@ -824,7 +824,7 @@ public override async Task Regex_IsMatch_MethodCall_constant_input(bool async) """ SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" FROM "Customers" AS "c" -WHERE regexp("c"."CustomerID", 'ALFKI') +WHERE 'ALFKI' REGEXP "c"."CustomerID" """); }