diff --git a/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslator.cs b/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslator.cs index 11171d6..f4d6fa6 100644 --- a/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslator.cs +++ b/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslator.cs @@ -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, + }; } /// diff --git a/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs b/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs index d1728f8..ab05a46 100644 --- a/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs +++ b/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs @@ -15,6 +15,7 @@ public class WindowFunctionsNpgsqlEvaluatableExpressionFilter : NpgsqlEvaluatabl /// Service dependencies. /// Relational service dependencies. /// NpgSql Singleton Options. + [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")] public WindowFunctionsNpgsqlEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies, INpgsqlSingletonOptions npgsqlSingletonOptions) : base(dependencies, relationalDependencies, npgsqlSingletonOptions) { diff --git a/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs b/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs index d2c2c4f..fe4ccb8 100644 --- a/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs +++ b/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs @@ -15,6 +15,7 @@ public class WindowFunctionsSqlServerParameterBasedSqlProcessorFactory : SqlServ /// /// Service dependencies. /// The singleton option. + [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")] public WindowFunctionsSqlServerParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies, ISqlServerSingletonOptions sqlServerSingletonOptions) : base(dependencies, sqlServerSingletonOptions) { diff --git a/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerSqlNullabilityProcessor.cs b/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerSqlNullabilityProcessor.cs index d980b5e..22a2c07 100644 --- a/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerSqlNullabilityProcessor.cs +++ b/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerSqlNullabilityProcessor.cs @@ -15,6 +15,7 @@ public class WindowFunctionsSqlServerSqlNullabilityProcessor : SqlServerSqlNulla /// Relational Parameter Based Sql Processor Dependencies. /// Relational parameters. /// The singleton option. + [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Conditional compilation")] public WindowFunctionsSqlServerSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters, ISqlServerSingletonOptions sqlServerSingletonOptions) : base(dependencies, parameters, sqlServerSingletonOptions) { diff --git a/src/Zomp.EFCore.WindowFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs b/src/Zomp.EFCore.WindowFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs index 8e9a93b..f8dae89 100644 --- a/src/Zomp.EFCore.WindowFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs +++ b/src/Zomp.EFCore.WindowFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs @@ -28,7 +28,7 @@ private static SqliteDbContextOptionsBuilder AddOrUpdateExtension( IRelationalParameterBasedSqlProcessorFactory, WindowFunctionsSqliteParameterBasedSqlProcessorFactory >() - .ReplaceService() + .ReplaceService() .ReplaceService() .ReplaceService() .ReplaceService() diff --git a/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGenerator.cs b/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGenerator.cs new file mode 100644 index 0000000..585f586 --- /dev/null +++ b/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGenerator.cs @@ -0,0 +1,25 @@ +namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal; + +/// +/// A query SQL generator for window functions to get for given . +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")] +public class WindowFunctionsSqliteQuerySqlGenerator : SqliteQuerySqlGenerator +{ + /// + /// Initializes a new instance of the class. + /// + /// Service dependencies. + public WindowFunctionsSqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) + : base(dependencies) + { + } + + /// + protected override Expression VisitExtension(Expression extensionExpression) + => extensionExpression switch + { + WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression), + _ => base.VisitExtension(extensionExpression), + }; +} diff --git a/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGeneratorFactory.cs b/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGeneratorFactory.cs new file mode 100644 index 0000000..d7441ac --- /dev/null +++ b/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQuerySqlGeneratorFactory.cs @@ -0,0 +1,18 @@ +namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal; + +/// +/// A factory for creating instances. +/// +/// +/// Initializes a new instance of the class. +/// +/// Query Sql Generator Dependencies. +public class WindowFunctionsSqliteQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies) + : SqliteQuerySqlGeneratorFactory(dependencies) +{ + private readonly QuerySqlGeneratorDependencies dependencies = dependencies; + + /// + public override QuerySqlGenerator Create() + => new WindowFunctionsSqliteQuerySqlGenerator(dependencies); +} diff --git a/src/Zomp.EFCore.WindowFunctions/Query/Internal/ExpressionVisitorExtensions.cs b/src/Zomp.EFCore.WindowFunctions/Query/Internal/ExpressionVisitorExtensions.cs index b759130..27d3316 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/Internal/ExpressionVisitorExtensions.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/Internal/ExpressionVisitorExtensions.cs @@ -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)); @@ -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) { diff --git a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionInsideWhereDetector.cs b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionInsideWhereDetector.cs index ddde0ef..8615c0a 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionInsideWhereDetector.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionInsideWhereDetector.cs @@ -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); @@ -302,7 +302,7 @@ private static Type CreateNewType(IEnumerable 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 parameterGenerator) diff --git a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsSqlNullabilityProcessorHelper.cs b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsSqlNullabilityProcessorHelper.cs index 291c578..efd6f1d 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsSqlNullabilityProcessorHelper.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsSqlNullabilityProcessorHelper.cs @@ -33,10 +33,7 @@ public static WindowFunctionExpression VisitWindowFunction( } } - if (arguments is not null) - { - arguments[i] = visitedArgument; - } + arguments?[i] = visitedArgument; } SqlExpression[]? partitions = null; @@ -54,10 +51,7 @@ public static WindowFunctionExpression VisitWindowFunction( } } - if (partitions is not null) - { - partitions[i] = visitedPartition!; - } + partitions?[i] = visitedPartition!; } OrderingExpression[]? orderings = null; @@ -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 diff --git a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslator.cs b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslator.cs index cef4fe8..e4b0c4a 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslator.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslator.cs @@ -14,46 +14,43 @@ public class WindowFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactor /// public SqlExpression? Translate(SqlExpression? instance, MethodInfo method, IReadOnlyList arguments, IDiagnosticsLogger 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, + }; } /// diff --git a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/ChainedSqlExpression.cs b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/ChainedSqlExpression.cs index 2229bd6..1abda0e 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/ChainedSqlExpression.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/ChainedSqlExpression.cs @@ -3,9 +3,11 @@ internal abstract class ChainedSqlExpression(T first) : SqlExpression(typeof(ChainedSqlExpression), null) where T : Expression { - public IReadOnlyList List { get; } = new List([first]); + private readonly List list = [first]; - public void Add(T item) => ((List)List).Add(item); + public IReadOnlyList List => list; + + public void Add(T item) => list.Add(item); protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.VisitCollection(List); } \ No newline at end of file diff --git a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/RowOrRangeExpression.cs b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/RowOrRangeExpression.cs index 371fcfd..6ccd61d 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/RowOrRangeExpression.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/RowOrRangeExpression.cs @@ -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) { diff --git a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/WindowFunctionExpression.cs b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/WindowFunctionExpression.cs index 3e087c5..858b9a3 100644 --- a/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/WindowFunctionExpression.cs +++ b/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/WindowFunctionExpression.cs @@ -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); diff --git a/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs b/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs index 38ecb1f..eb91abd 100644 --- a/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs +++ b/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs @@ -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 diff --git a/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs b/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs index 016e518..dc7c79d 100644 --- a/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs +++ b/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs @@ -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 diff --git a/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs b/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs index 445e140..877400b 100644 --- a/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs +++ b/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs @@ -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 diff --git a/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteSpecificTests.cs b/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteSpecificTests.cs new file mode 100644 index 0000000..c77e3f7 --- /dev/null +++ b/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteSpecificTests.cs @@ -0,0 +1,22 @@ +namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests; + +[Collection(nameof(SqliteCollection))] +public class SqliteSpecificTests(ITestOutputHelper output) : TestBase(output) +{ + /// + /// Ensures paging is generated with SQLite's LIMIT rather than ANSI OFFSET ... FETCH. + /// + /// + /// 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. + /// + [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); + } +} diff --git a/tests/Zomp.EFCore.WindowFunctions.Testing/PagingTests.cs b/tests/Zomp.EFCore.WindowFunctions.Testing/PagingTests.cs new file mode 100644 index 0000000..bd760fe --- /dev/null +++ b/tests/Zomp.EFCore.WindowFunctions.Testing/PagingTests.cs @@ -0,0 +1,55 @@ +namespace Zomp.EFCore.WindowFunctions.Testing; + +/// +/// Verifies that enabling window functions leaves provider specific paging SQL intact. +/// +/// +/// Regression tests for https://github.com/zompinc/efcore-extensions/issues/23, where the SQLite +/// provider fell back to the generic query SQL generator and emitted ANSI +/// OFFSET ... FETCH NEXT instead of SQLite's LIMIT. +/// +public partial class PagingTests +{ + [Fact] + public void First() + { + var result = DbContext.TestRows.OrderBy(r => r.Id).First(); + + var expected = TestRows.OrderBy(r => r.Id).First(); + + Assert.Equal(expected, result, TestRowEqualityComparer.Default); + } + + [Fact] + public void Take() + { + var result = DbContext.TestRows.OrderBy(r => r.Id).Take(3).ToList(); + + var expected = TestRows.OrderBy(r => r.Id).Take(3); + + Assert.Equal(expected, result, TestRowEqualityComparer.Default); + } + + [Fact] + public void SkipAndTake() + { + var result = DbContext.TestRows.OrderBy(r => r.Id).Skip(2).Take(3).ToList(); + + var expected = TestRows.OrderBy(r => r.Id).Skip(2).Take(3); + + Assert.Equal(expected, result, TestRowEqualityComparer.Default); + } + + [Fact] + public void TakeWithWindowFunction() + { + var query = DbContext.TestRows + .OrderBy(r => r.Id) + .Select(r => EF.Functions.RowNumber(EF.Functions.Over().OrderBy(r.Id))) + .Take(3); + + var result = query.ToList(); + + Assert.Equal([1L, 2L, 3L], result); + } +} diff --git a/tests/Zomp.EFCore.WindowFunctions.Testing/RankTests.cs b/tests/Zomp.EFCore.WindowFunctions.Testing/RankTests.cs index 156c045..8513d27 100644 --- a/tests/Zomp.EFCore.WindowFunctions.Testing/RankTests.cs +++ b/tests/Zomp.EFCore.WindowFunctions.Testing/RankTests.cs @@ -1,4 +1,4 @@ -namespace Zomp.EFCore.WindowFunctions.Testing; +namespace Zomp.EFCore.WindowFunctions.Testing; public partial class RankTests { @@ -46,8 +46,7 @@ public void RankBasic() .Select(r => r.Id / 10) .Select(v => (long)groups .Where(g => g.Key < v) - .Select(g => g.Count()) - .Sum() + 1); + .Sum(g => g.Count()) + 1); Assert.Equal(expectedSequence, result); } @@ -86,8 +85,7 @@ public void PercentRankBasic() .OrderBy(x => x, comparer) .Select(v => groups .Where(g => comparer.Compare(g.Key, v) < 0) - .Select(g => g.Count()) - .Sum() / (double)(TestRows.Length - 1)); + .Sum(g => g.Count()) / (double)(TestRows.Length - 1)); Assert.Equal(expectedSequence, result.Select(r => r)); } diff --git a/tests/Zomp.EFCore.WindowFunctions.Testing/SubQueryTests.cs b/tests/Zomp.EFCore.WindowFunctions.Testing/SubQueryTests.cs index 58012eb..ea74781 100644 --- a/tests/Zomp.EFCore.WindowFunctions.Testing/SubQueryTests.cs +++ b/tests/Zomp.EFCore.WindowFunctions.Testing/SubQueryTests.cs @@ -15,11 +15,9 @@ public void RowNumberWithWhere() Assert.Equal(expected, result.Single(), TestRowEqualityComparer.Default); } - [SkippableFact] + [Fact] public void RowNumberWithSingle() { - // Fixme: investigate why this fails. - Skip.If(DbContext.IsSqlite); var result = DbContext.TestRows .Single(t => EF.Functions.RowNumber(EF.Functions.Over().OrderBy(t.Id)) == 1); diff --git a/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.projitems b/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.projitems index edcbf2c..332f3c2 100644 --- a/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.projitems +++ b/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.projitems @@ -17,6 +17,7 @@ +