Skip to content
Open
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
27 changes: 12 additions & 15 deletions src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,18 @@ public class BinaryTranslator(ISqlExpressionFactory sqlExpressionFactory, IRelat
{
ArgumentNullException.ThrowIfNull(method);

if (method.DeclaringType != typeof(DbFunctionsExtensions))
{
return null;
}

return method.Name switch
{
nameof(DbFunctionsExtensions.GetBytes) => GetBytes(arguments[1]),
nameof(DbFunctionsExtensions.Concat) => Concat(arguments),
nameof(DbFunctionsExtensions.Substring) => Substring(arguments[1], arguments[2], arguments[3]),
nameof(DbFunctionsExtensions.ToValue) when arguments.Count > 2 => ToValue(arguments[1], arguments[2], method.GetGenericArguments()[0]),
nameof(DbFunctionsExtensions.ToValue) => ToValue(arguments[1], method.GetGenericArguments()[0]),
nameof(DbFunctionsExtensions.BinaryCast) => BinaryCast(arguments[1], method.GetGenericArguments()[1]),
_ => null,
};
return method.DeclaringType != typeof(DbFunctionsExtensions)
? null
: method.Name switch
{
nameof(DbFunctionsExtensions.GetBytes) => GetBytes(arguments[1]),
nameof(DbFunctionsExtensions.Concat) => Concat(arguments),
nameof(DbFunctionsExtensions.Substring) => Substring(arguments[1], arguments[2], arguments[3]),
nameof(DbFunctionsExtensions.ToValue) when arguments.Count > 2 => ToValue(arguments[1], arguments[2], method.GetGenericArguments()[0]),
nameof(DbFunctionsExtensions.ToValue) => ToValue(arguments[1], method.GetGenericArguments()[0]),
nameof(DbFunctionsExtensions.BinaryCast) => BinaryCast(arguments[1], method.GetGenericArguments()[1]),
_ => null,
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class WindowFunctionsNpgsqlEvaluatableExpressionFilter : NpgsqlEvaluatabl
/// <param name="dependencies">Service dependencies.</param>
/// <param name="relationalDependencies">Relational service dependencies.</param>
/// <param name="npgsqlSingletonOptions">NpgSql Singleton Options.</param>
[SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")]
public WindowFunctionsNpgsqlEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies, INpgsqlSingletonOptions npgsqlSingletonOptions)
: base(dependencies, relationalDependencies, npgsqlSingletonOptions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class WindowFunctionsSqlServerParameterBasedSqlProcessorFactory : SqlServ
/// </summary>
/// <param name="dependencies">Service dependencies.</param>
/// <param name="sqlServerSingletonOptions">The singleton option.</param>
[SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")]
public WindowFunctionsSqlServerParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies, ISqlServerSingletonOptions sqlServerSingletonOptions)
: base(dependencies, sqlServerSingletonOptions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class WindowFunctionsSqlServerSqlNullabilityProcessor : SqlServerSqlNulla
/// <param name="dependencies">Relational Parameter Based Sql Processor Dependencies.</param>
/// <param name="parameters">Relational parameters.</param>
/// <param name="sqlServerSingletonOptions">The singleton option.</param>
[SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")]
public WindowFunctionsSqlServerSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters, ISqlServerSingletonOptions sqlServerSingletonOptions)
: base(dependencies, parameters, sqlServerSingletonOptions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private static SqliteDbContextOptionsBuilder AddOrUpdateExtension(
IRelationalParameterBasedSqlProcessorFactory,
WindowFunctionsSqliteParameterBasedSqlProcessorFactory
>()
.ReplaceService<IQuerySqlGeneratorFactory, WindowQuerySqlGeneratorFactory>()
.ReplaceService<IQuerySqlGeneratorFactory, WindowFunctionsSqliteQuerySqlGeneratorFactory>()
.ReplaceService<IWindowFunctionsTranslatorPluginFactory, SqliteWindowFunctionsTranslatorPluginFactory>()
.ReplaceService<IEvaluatableExpressionFilter, SqliteWindowFunctionsEvaluatableExpressionFilter>()
.ReplaceService<IQueryableMethodTranslatingExpressionVisitorFactory, WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitorFactory>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;

/// <summary>
/// A query SQL generator for window functions to get <see cref="IRelationalCommand" /> for given <see cref="SelectExpression" />.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
public class WindowFunctionsSqliteQuerySqlGenerator : SqliteQuerySqlGenerator
{
/// <summary>
/// Initializes a new instance of the <see cref="WindowFunctionsSqliteQuerySqlGenerator"/> class.
/// </summary>
/// <param name="dependencies">Service dependencies.</param>
public WindowFunctionsSqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies)
: base(dependencies)
{
}

/// <inheritdoc/>
protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression switch
{
WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression),
_ => base.VisitExtension(extensionExpression),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;

/// <summary>
/// A factory for creating <see cref="WindowFunctionsSqliteQuerySqlGenerator" /> instances.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="WindowFunctionsSqliteQuerySqlGeneratorFactory"/> class.
/// </remarks>
/// <param name="dependencies">Query Sql Generator Dependencies.</param>
public class WindowFunctionsSqliteQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies)
: SqliteQuerySqlGeneratorFactory(dependencies)
{
private readonly QuerySqlGeneratorDependencies dependencies = dependencies;

/// <inheritdoc/>
public override QuerySqlGenerator Create()
=> new WindowFunctionsSqliteQuerySqlGenerator(dependencies);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ public static Expression VisitWindowFunction(this ExpressionVisitor expressionVi
}

_ = relationalCommandBuilder.Append("OVER(");
if (windowFunctionExpression.Partitions.Any())
if (windowFunctionExpression.Partitions.Count > 0)
{
_ = relationalCommandBuilder.Append("PARTITION BY ");
GenerateList(relationalCommandBuilder, windowFunctionExpression.Partitions, e => expressionVisitor.Visit(e));
_ = relationalCommandBuilder.Append(" ");
}

if (windowFunctionExpression.Orderings.Any())
if (windowFunctionExpression.Orderings.Count > 0)
{
_ = relationalCommandBuilder.Append("ORDER BY ");
GenerateList(relationalCommandBuilder, windowFunctionExpression.Orderings, e => expressionVisitor.Visit(e));
Expand Down Expand Up @@ -92,7 +92,7 @@ private static void ProcessRowOrRange(WindowFunctionExpression windowFunctionExp

private static void ProcessWindowFrame(IRelationalCommandBuilder relationalCommandBuilder, WindowFrame windowFrame, bool isStart)
{
_ = relationalCommandBuilder.Append(windowFrame.ToString()!);
_ = relationalCommandBuilder.Append(windowFrame.ToString());

if (windowFrame.IsDirectional)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private static LambdaExpression BuildSubqueries(
}

var replacing = windowFunctions[level];
subqueryList = replacing.Select(z => (MethodCallExpression)wfr.Visit(z)).ToList();
subqueryList = [.. replacing.Select(z => (MethodCallExpression)wfr.Visit(z))];
}

var newBody = wfr.Visit(lambda.Body);
Expand Down Expand Up @@ -302,7 +302,7 @@ private static Type CreateNewType(IEnumerable<Name_Type_And_Replacement> info)
cil.Emit(OpCodes.Ret);

// Return the type to the caller
return dynamicAnonymousType.CreateType()!;
return dynamicAnonymousType.CreateType();
}

private static MethodInfo GetMethod(string name, int genericParameterCount, Func<Type[], Type[]> parameterGenerator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ public static WindowFunctionExpression VisitWindowFunction(
}
}

if (arguments is not null)
{
arguments[i] = visitedArgument;
}
arguments?[i] = visitedArgument;
}

SqlExpression[]? partitions = null;
Expand All @@ -54,10 +51,7 @@ public static WindowFunctionExpression VisitWindowFunction(
}
}

if (partitions is not null)
{
partitions[i] = visitedPartition!;
}
partitions?[i] = visitedPartition!;
}

OrderingExpression[]? orderings = null;
Expand All @@ -75,10 +69,7 @@ public static WindowFunctionExpression VisitWindowFunction(
}
}

if (orderings is not null)
{
orderings[i] = visitedOrdering;
}
orderings?[i] = visitedOrdering;
}

return arguments is not null || orderings is not null || partitions is not null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,43 @@ public class WindowFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactor
/// <inheritdoc/>
public SqlExpression? Translate(SqlExpression? instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments, IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.DeclaringType != typeof(DbFunctionsExtensions))
{
return null;
}

return method.Name switch
{
nameof(DbFunctionsExtensions.Min) => Parse(arguments, "MIN"),
nameof(DbFunctionsExtensions.Max) => Parse(arguments, "MAX"),
nameof(DbFunctionsExtensions.Lead) => Parse(arguments, "LEAD"),
nameof(DbFunctionsExtensions.Lag) => Parse(arguments, "LAG"),
nameof(DbFunctionsExtensions.Sum) => Parse(arguments, "SUM"),
nameof(DbFunctionsExtensions.Avg) => Parse(arguments, "AVG"),
nameof(DbFunctionsExtensions.Count) => Parse(arguments, "COUNT"),
nameof(DbFunctionsExtensions.RowNumber) => Parse(arguments, "ROW_NUMBER"),
nameof(DbFunctionsExtensions.Rank) => Parse(arguments, "RANK"),
nameof(DbFunctionsExtensions.DenseRank) => Parse(arguments, "DENSE_RANK"),
nameof(DbFunctionsExtensions.PercentRank) => Parse(arguments, "PERCENT_RANK"),

nameof(DbFunctionsExtensions.OrderBy) => OrderBy(arguments, true),
nameof(DbFunctionsExtensions.OrderByDescending) => OrderBy(arguments, false),
nameof(DbFunctionsExtensions.PartitionBy) => PartitionBy(arguments),
nameof(DbFunctionsExtensions.ThenBy) => ThenBy(arguments, true),
nameof(DbFunctionsExtensions.ThenByDescending) => ThenBy(arguments, false),

nameof(DbFunctionsExtensions.Rows) => RowsOrRange(arguments, true),
nameof(DbFunctionsExtensions.Range) => RowsOrRange(arguments, false),

nameof(DbFunctionsExtensions.FromPreceding) => From(arguments, false),
nameof(DbFunctionsExtensions.FromFollowing) => From(arguments, true),
nameof(DbFunctionsExtensions.FromCurrentRow) => FromWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.CurrentRow),
nameof(DbFunctionsExtensions.FromUnbounded) => FromWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.Unbounded),

nameof(DbFunctionsExtensions.ToFollowing) => To(arguments, true),
nameof(DbFunctionsExtensions.ToCurrentRow) => ToWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.CurrentRow),
nameof(DbFunctionsExtensions.ToUnbounded) => ToWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.Unbounded),
nameof(DbFunctionsExtensions.ToPreceding) => To(arguments, false),

_ => null,
};
return method.DeclaringType != typeof(DbFunctionsExtensions)
? null
: method.Name switch
{
nameof(DbFunctionsExtensions.Min) => Parse(arguments, "MIN"),
nameof(DbFunctionsExtensions.Max) => Parse(arguments, "MAX"),
nameof(DbFunctionsExtensions.Lead) => Parse(arguments, "LEAD"),
nameof(DbFunctionsExtensions.Lag) => Parse(arguments, "LAG"),
nameof(DbFunctionsExtensions.Sum) => Parse(arguments, "SUM"),
nameof(DbFunctionsExtensions.Avg) => Parse(arguments, "AVG"),
nameof(DbFunctionsExtensions.Count) => Parse(arguments, "COUNT"),
nameof(DbFunctionsExtensions.RowNumber) => Parse(arguments, "ROW_NUMBER"),
nameof(DbFunctionsExtensions.Rank) => Parse(arguments, "RANK"),
nameof(DbFunctionsExtensions.DenseRank) => Parse(arguments, "DENSE_RANK"),
nameof(DbFunctionsExtensions.PercentRank) => Parse(arguments, "PERCENT_RANK"),

nameof(DbFunctionsExtensions.OrderBy) => OrderBy(arguments, true),
nameof(DbFunctionsExtensions.OrderByDescending) => OrderBy(arguments, false),
nameof(DbFunctionsExtensions.PartitionBy) => PartitionBy(arguments),
nameof(DbFunctionsExtensions.ThenBy) => ThenBy(arguments, true),
nameof(DbFunctionsExtensions.ThenByDescending) => ThenBy(arguments, false),

nameof(DbFunctionsExtensions.Rows) => RowsOrRange(arguments, true),
nameof(DbFunctionsExtensions.Range) => RowsOrRange(arguments, false),

nameof(DbFunctionsExtensions.FromPreceding) => From(arguments, false),
nameof(DbFunctionsExtensions.FromFollowing) => From(arguments, true),
nameof(DbFunctionsExtensions.FromCurrentRow) => FromWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.CurrentRow),
nameof(DbFunctionsExtensions.FromUnbounded) => FromWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.Unbounded),

nameof(DbFunctionsExtensions.ToFollowing) => To(arguments, true),
nameof(DbFunctionsExtensions.ToCurrentRow) => ToWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.CurrentRow),
nameof(DbFunctionsExtensions.ToUnbounded) => ToWindowFrame(GetOrderingSqlExpression(arguments), WindowFrame.Unbounded),
nameof(DbFunctionsExtensions.ToPreceding) => To(arguments, false),

_ => null,
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
internal abstract class ChainedSqlExpression<T>(T first) : SqlExpression(typeof(ChainedSqlExpression<T>), null)
where T : Expression
{
public IReadOnlyList<T> List { get; } = new List<T>([first]);
private readonly List<T> list = [first];

public void Add(T item) => ((List<T>)List).Add(item);
public IReadOnlyList<T> List => list;

public void Add(T item) => list.Add(item);

protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.VisitCollection(List);
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override void Print(ExpressionPrinter expressionPrinter)

private static void ProcessWindowFrame(ExpressionPrinter expressionPrinter, WindowFrame windowFrame, bool isStart)
{
_ = expressionPrinter.Append(windowFrame.ToString()!);
_ = expressionPrinter.Append(windowFrame.ToString());

if (windowFrame.IsDirectional)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,14 @@ protected override void Print(ExpressionPrinter expressionPrinter)

_ = expressionPrinter.Append("OVER(");

if (Partitions.Any())
if (Partitions.Count > 0)
{
_ = expressionPrinter.Append("PARTITION BY ");
expressionPrinter.VisitCollection(Partitions);
_ = expressionPrinter.Append(" ");
}

if (Orderings.Any())
if (Orderings.Count > 0)
{
_ = expressionPrinter.Append("ORDER BY ");
expressionPrinter.VisitCollection(Orderings);
Expand Down
3 changes: 3 additions & 0 deletions tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output)

[Collection(nameof(NpgsqlCollection))]
public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }

[Collection(nameof(NpgsqlCollection))]
public partial class PagingTests(ITestOutputHelper output) : TestBase(output) { }
#pragma warning restore SA1402 // File may only contain a single type
3 changes: 3 additions & 0 deletions tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output)

[Collection(nameof(SqlServerCollection))]
public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }

[Collection(nameof(SqlServerCollection))]
public partial class PagingTests(ITestOutputHelper output) : TestBase(output) { }
#pragma warning restore SA1402 // File may only contain a single type
3 changes: 3 additions & 0 deletions tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output)

[Collection(nameof(SqliteCollection))]
public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }

[Collection(nameof(SqliteCollection))]
public partial class PagingTests(ITestOutputHelper output) : TestBase(output) { }
#pragma warning restore SA1402 // File may only contain a single type
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests;

[Collection(nameof(SqliteCollection))]
public class SqliteSpecificTests(ITestOutputHelper output) : TestBase(output)
{
/// <summary>
/// Ensures paging is generated with SQLite's <c>LIMIT</c> rather than ANSI <c>OFFSET ... FETCH</c>.
/// </summary>
/// <remarks>
/// https://github.com/zompinc/efcore-extensions/issues/23. Enabling window functions used to
/// replace the SQLite query SQL generator with the provider agnostic one, which broke every
/// query using Take / Skip / First, whether or not it contained a window function.
/// </remarks>
[Fact]
public void Issue23PagingUsesLimit()
{
var sql = DbContext.TestRows.OrderBy(r => r.Id).Take(1).ToQueryString();

Assert.Contains("LIMIT", sql, StringComparison.Ordinal);
Assert.DoesNotContain("FETCH", sql, StringComparison.Ordinal);
}
}
Loading
Loading