Skip to content

Commit

Permalink
SQLite: Use infix GLOB and REGEXP operators
Browse files Browse the repository at this point in the history
Mostly just to be pretentious
  • Loading branch information
bricelam committed Nov 30, 2022
1 parent f93b25c commit 99a642b
Show file tree
Hide file tree
Showing 12 changed files with 518 additions and 33 deletions.
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);
}
33 changes: 33 additions & 0 deletions src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs
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

0 comments on commit 99a642b

Please sign in to comment.