Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite: Use infix GLOB and REGEXP operators #23193

Merged
merged 1 commit into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public static IServiceCollection AddEntityFrameworkSqlite(this IServiceCollectio
: new SqliteUpdateSqlGenerator(dependencies);
})
.TryAdd<ISqlExpressionFactory, SqliteSqlExpressionFactory>()
.TryAdd<IRelationalParameterBasedSqlProcessorFactory, SqliteParameterBasedSqlProcessorFactory>()
.TryAddProviderSpecificServices(
b => b.TryAddScoped<ISqliteRelationalConnection, SqliteRelationalConnection>());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,15 +16,15 @@ 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;

/// <summary>
/// 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.
/// </summary>
public SqliteGlobMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
public SqliteGlobMethodTranslator(SqliteSqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
public class SqliteParameterBasedSqlProcessor : RelationalParameterBasedSqlProcessor
{
/// <summary>
/// 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.
/// </summary>
public SqliteParameterBasedSqlProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
bool useRelationalNulls)
: base(dependencies, useRelationalNulls)
{
}

/// <summary>
/// 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.
/// </summary>
///
protected override Expression ProcessSqlNullability(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parametersValues,
out bool canCache)
=> new SqliteSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache);
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
public class SqliteParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory
{
private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies;

/// <summary>
/// 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.
/// </summary>
public SqliteParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
=> _dependencies = dependencies;

/// <summary>
/// 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.
/// </summary>
public virtual RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
=> new SqliteParameterBasedSqlProcessor(_dependencies, useRelationalNulls);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -24,6 +25,20 @@ public SqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies)
{
}

/// <summary>
/// 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.
/// </summary>
protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression switch
{
GlobExpression globExpression => VisitGlob(globExpression),
RegexpExpression regexpExpression => VisitRegexp(regexpExpression),
_ => base.VisitExtension(extensionExpression)
};

/// <summary>
/// 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
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,15 +17,15 @@ 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;

/// <summary>
/// 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.
/// </summary>
public SqliteRegexMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
public SqliteRegexMethodTranslator(SqliteSqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -13,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
/// </summary>
public class SqliteSqlExpressionFactory : SqlExpressionFactory
{
private readonly RelationalTypeMapping _boolTypeMapping;

/// <summary>
/// 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
Expand All @@ -21,8 +26,7 @@ public class SqliteSqlExpressionFactory : SqlExpressionFactory
/// </summary>
public SqliteSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
: base(dependencies)
{
}
=> _boolTypeMapping = dependencies.TypeMappingSource.FindMapping(typeof(bool), dependencies.Model)!;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -106,4 +110,81 @@ public virtual SqlFunctionExpression Date(
returnType,
typeMapping);
}

/// <summary>
/// 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.
/// </summary>
public virtual GlobExpression Glob(SqlExpression match, SqlExpression pattern)
{
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(match, pattern)
?? Dependencies.TypeMappingSource.FindMapping(match.Type, Dependencies.Model);

match = ApplyTypeMapping(match, inferredTypeMapping);
pattern = ApplyTypeMapping(pattern, inferredTypeMapping);

return new GlobExpression(match, pattern, _boolTypeMapping);
}

/// <summary>
/// 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.
/// </summary>
public virtual RegexpExpression Regexp(SqlExpression match, SqlExpression pattern)
{
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(match, pattern)
?? Dependencies.TypeMappingSource.FindMapping(match.Type, Dependencies.Model);

match = ApplyTypeMapping(match, inferredTypeMapping);
pattern = ApplyTypeMapping(pattern, inferredTypeMapping);

return new RegexpExpression(match, pattern, _boolTypeMapping);
}

/// <summary>
/// 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.
/// </summary>
[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);

var match = ApplyTypeMapping(globExpression.Match, inferredTypeMapping);
var pattern = ApplyTypeMapping(globExpression.Pattern, inferredTypeMapping);

return match != globExpression.Match || pattern != globExpression.Pattern || globExpression.TypeMapping != _boolTypeMapping
? new GlobExpression(match, pattern, _boolTypeMapping)
: globExpression;
}

private SqlExpression? ApplyTypeMappingOnRegexp(RegexpExpression regexpExpression)
{
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(regexpExpression.Match, regexpExpression.Pattern)
?? Dependencies.TypeMappingSource.FindMapping(regexpExpression.Match.Type, Dependencies.Model);

var match = ApplyTypeMapping(regexpExpression.Match, inferredTypeMapping);
var pattern = ApplyTypeMapping(regexpExpression.Pattern, inferredTypeMapping);

return match != regexpExpression.Match || pattern != regexpExpression.Pattern || regexpExpression.TypeMapping != _boolTypeMapping
? new RegexpExpression(match, pattern, _boolTypeMapping)
: regexpExpression;
}
}
Loading