diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index a2d56ec8e2d..f8e8406b72e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -59,6 +59,7 @@ + @@ -87,6 +88,7 @@ + @@ -115,6 +117,7 @@ + @@ -142,6 +145,7 @@ + diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs index b12b008e8a7..ab637e81812 100644 --- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs +++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs @@ -15,6 +15,14 @@ internal static class ExpressionHelpers .GetMethod(nameof(CreateAndConvertParameter), BindingFlags.NonPublic | BindingFlags.Static)!; private static readonly ConcurrentDictionary> s_cachedConverters = new(); + private static readonly NullabilityInfoContext s_nullabilityInfoContext = new(); +#if NET9_0_OR_GREATER + private static readonly Lock s_nullabilityInfoContextLock = new(); +#else + private static readonly object s_nullabilityInfoContextLock = new(); +#endif + private static readonly Expression s_false = Expression.Constant(false); + private static readonly Expression s_zero = Expression.Constant(0); /// /// Builds a where expression that can be used to slice a dataset. @@ -28,6 +36,9 @@ internal static class ExpressionHelpers /// /// Defines how the dataset is sorted. /// + /// + /// Defines the null ordering to be used. + /// /// /// The entity type. /// @@ -43,7 +54,8 @@ internal static class ExpressionHelpers public static (Expression> WhereExpression, int Offset) BuildWhereExpression( ReadOnlySpan keys, Cursor cursor, - bool forward) + bool forward, + NullOrdering nullOrdering) { if (keys.Length == 0) { @@ -58,14 +70,16 @@ public static (Expression> WhereExpression, int Offset) BuildWhere var cursorExpr = new Expression[cursor.Values.Length]; for (var i = 0; i < cursor.Values.Length; i++) { - cursorExpr[i] = CreateParameter(cursor.Values[i], keys[i].Expression.ReturnType); + var parameterType = Nullable.GetUnderlyingType(keys[i].Expression.ReturnType) + ?? keys[i].Expression.ReturnType; + + cursorExpr[i] = CreateParameter(cursor.Values[i], parameterType); } var handled = new List(); Expression? expression = null; var parameter = Expression.Parameter(typeof(T), "t"); - var zero = Expression.Constant(0); for (var i = 0; i < keys.Length; i++) { @@ -76,25 +90,53 @@ public static (Expression> WhereExpression, int Offset) BuildWhere for (var j = 0; j < handled.Count; j++) { var handledKey = handled[j]; + var handledKeyIsNullable = IsNullable(handledKey.Expression); - keyExpr = Expression.Equal( - Expression.Call(ReplaceParameter(handledKey.Expression, parameter), handledKey.CompareMethod, - cursorExpr[j]), zero); + keyExpr = BuildEqualToKeyExpr( + handledKey, + parameter, + cursor.Values[j], + handledKeyIsNullable, + cursorExpr[j]); current = current is null ? keyExpr : Expression.AndAlso(current, keyExpr); } + var keyIsNullable = IsNullable(key.Expression); + + if (keyIsNullable && nullOrdering == NullOrdering.Unspecified) + { + throw new InvalidOperationException( + "The NullOrdering option must be specified in the paging options or " + + "arguments when using nullable keys."); + } + var greaterThan = forward ? key.Direction == CursorKeyDirection.Ascending : key.Direction == CursorKeyDirection.Descending; - keyExpr = greaterThan - ? Expression.GreaterThan( - Expression.Call(ReplaceParameter(key.Expression, parameter), key.CompareMethod, cursorExpr[i]), - zero) - : Expression.LessThan( - Expression.Call(ReplaceParameter(key.Expression, parameter), key.CompareMethod, cursorExpr[i]), - zero); + if (greaterThan) + { + keyExpr = + BuildGreaterThanKeyExpr( + key, + parameter, + cursor.Values[i], + keyIsNullable, + nullOrdering, + cursorExpr[i]); + } + else + { + keyExpr = + BuildLessThanKeyExpr( + key, + parameter, + cursor.Values[i], + keyIsNullable, + nullOrdering, + cursorExpr[i]); + } current = current is null ? keyExpr : Expression.AndAlso(current, keyExpr); expression = expression is null ? current : Expression.OrElse(expression, current); @@ -104,6 +146,238 @@ public static (Expression> WhereExpression, int Offset) BuildWhere return (Expression.Lambda>(expression!, parameter), cursor.Offset ?? 0); } + private static Expression BuildEqualToKeyExpr( + CursorKey cursorKey, + ParameterExpression parameter, + object? cursorValue, + bool keyIsNullable, + Expression cursorExpr) + { + var keyExpr = ReplaceParameter(cursorKey.Expression, parameter); + + // Access the value of the key if it is a nullable value type. + var keyValueExpr = cursorKey.Expression.ReturnType.IsValueType && keyIsNullable + ? Expression.Property(keyExpr, "Value") + : keyExpr; + + if (keyIsNullable) + { + // Null constant must be typed to match keyExpr.Type so that expression + // construction works for both reference types and Nullable value types. + var nullConst = Expression.Constant(null, keyExpr.Type); + + if (cursorValue is null) + { + // SQL: WHERE key IS NULL. + keyExpr = Expression.Equal(keyExpr, nullConst); + } + else + { + // SQL: WHERE key IS NOT NULL AND key = cursorValue. + keyExpr = Expression.AndAlso( + Expression.NotEqual(keyExpr, nullConst), + Expression.Equal( + Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr), + s_zero)); + } + } + else + { + // SQL: WHERE key = cursorValue. + keyExpr = Expression.Equal( + Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr), + s_zero); + } + + return keyExpr; + } + + private static Expression BuildGreaterThanKeyExpr( + CursorKey cursorKey, + ParameterExpression parameter, + object? cursorValue, + bool keyIsNullable, + NullOrdering nullOrdering, + Expression cursorExpr) + { + var keyExpr = ReplaceParameter(cursorKey.Expression, parameter); + + // Access the value of the key if it is a nullable value type. + var keyValueExpr = + cursorKey.Expression.ReturnType.IsValueType && keyIsNullable + ? Expression.Property(keyExpr, "Value") + : keyExpr; + + if (keyIsNullable) + { + // Null constant must be typed to match keyExpr.Type so that expression + // construction works for both reference types and Nullable value types. + var nullConst = Expression.Constant(null, keyExpr.Type); + + if (cursorValue is null) + { + keyExpr = nullOrdering == NullOrdering.NativeNullsFirst + // With nulls first, any non-null value is greater than null. + // SQL: WHERE key IS NOT NULL. + ? Expression.NotEqual(keyExpr, nullConst) + // With nulls last, no value is greater than null. + // SQL: WHERE false. + : s_false; + } + else + { + if (nullOrdering == NullOrdering.NativeNullsFirst) + { + // SQL: WHERE key > cursorValue. + keyExpr = Expression.GreaterThan( + Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr), + s_zero); + } + else + { + // When nulls are last, null is greater than any non-null value. + // SQL: WHERE key IS NULL OR key > cursorValue. + keyExpr = Expression.OrElse( + Expression.Equal(keyExpr, nullConst), + Expression.GreaterThan( + Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr), + s_zero)); + } + } + } + else + { + // SQL: WHERE key > cursorValue. + keyExpr = Expression.GreaterThan( + Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr), + s_zero); + } + + return keyExpr; + } + + private static Expression BuildLessThanKeyExpr( + CursorKey cursorKey, + ParameterExpression parameter, + object? cursorValue, + bool keyIsNullable, + NullOrdering nullOrdering, + Expression cursorExpr) + { + var keyExpr = ReplaceParameter(cursorKey.Expression, parameter); + + // Access the value of the key if it is a nullable value type. + var keyValueExpr = + cursorKey.Expression.ReturnType.IsValueType && keyIsNullable + ? Expression.Property(keyExpr, "Value") + : keyExpr; + + if (keyIsNullable) + { + // Null constant must be typed to match keyExpr.Type so that expression + // construction works for both reference types and Nullable value types. + var nullConst = Expression.Constant(null, keyExpr.Type); + + if (cursorValue is null) + { + keyExpr = nullOrdering == NullOrdering.NativeNullsFirst + // With nulls first, no value is less than null. + // SQL: WHERE false. + ? s_false + // With nulls last, any non-null value is less than null. + // SQL: WHERE key IS NOT NULL. + : Expression.NotEqual(keyExpr, nullConst); + } + else + { + if (nullOrdering == NullOrdering.NativeNullsFirst) + { + // With nulls first, null is less than any non-null value. + // SQL: WHERE key IS NULL OR key < cursorValue. + keyExpr = Expression.OrElse( + Expression.Equal(keyExpr, nullConst), + Expression.LessThan( + Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr), + s_zero)); + } + else + { + // SQL: WHERE key < cursorValue. + keyExpr = Expression.LessThan( + Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr), + s_zero); + } + } + } + else + { + // SQL: WHERE key < cursorValue. + keyExpr = Expression.LessThan( + Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr), + s_zero); + } + + return keyExpr; + } + + private static bool IsNullable(LambdaExpression expression) + { + if (expression.ReturnType.IsValueType) + { + return Nullable.GetUnderlyingType(expression.ReturnType) is not null; + } + + var member = expression.Body switch + { + MemberExpression { Member: PropertyInfo or FieldInfo } m => m.Member, + BinaryExpression + { + NodeType: ExpressionType.Coalesce, + Right: MemberExpression { Member: PropertyInfo or FieldInfo } m + } => m.Member, + _ => null + }; + + if (member is not null) + { + var state = member switch + { + PropertyInfo p => GetNullabilityInfoState(p), + FieldInfo f => GetNullabilityInfoState(f), + _ => throw new InvalidOperationException() + }; + + return state switch + { + NullabilityState.Nullable => true, + // Unknown means the assembly was compiled without NRT annotations; + // treat as non-nullable (safe default). + _ => false + }; + } + + // For computed key expressions (method calls, concatenation, etc.) we cannot inspect + // NRT annotations at runtime. Treat as non-nullable — the safe default that avoids + // injecting spurious null-handling into the generated WHERE clause. + return false; + } + + private static NullabilityState GetNullabilityInfoState(PropertyInfo propertyInfo) + { + lock (s_nullabilityInfoContextLock) + { + return s_nullabilityInfoContext.Create(propertyInfo).ReadState; + } + } + + private static NullabilityState GetNullabilityInfoState(FieldInfo fieldInfo) + { + lock (s_nullabilityInfoContextLock) + { + return s_nullabilityInfoContext.Create(fieldInfo).ReadState; + } + } + /// /// Build the select expression for a batch paging expression that uses grouping. /// @@ -200,7 +474,11 @@ public static BatchExpression BuildBatchExpression( if (arguments.After is not null) { cursor = CursorParser.Parse(arguments.After, keys); - var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, forward: true); + var (whereExpr, cursorOffset) = BuildWhereExpression( + keys, + cursor, + forward: true, + arguments.NullOrdering); source = Expression.Call(typeof(Enumerable), "Where", [typeof(TV)], source, whereExpr); offset = cursorOffset; @@ -220,7 +498,12 @@ public static BatchExpression BuildBatchExpression( } cursor = CursorParser.Parse(arguments.Before, keys); - var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, forward: false); + var (whereExpr, cursorOffset) = BuildWhereExpression( + keys, + cursor, + forward: + false, + arguments.NullOrdering); source = Expression.Call(typeof(Enumerable), "Where", [typeof(TV)], source, whereExpr); offset = cursorOffset; } @@ -298,8 +581,8 @@ public static BatchExpression BuildBatchExpression( var groupType = typeof(Group); var bindings = new MemberBinding[] { - Expression.Bind(groupType.GetProperty(nameof(Group.Key))!, groupKey), - Expression.Bind(groupType.GetProperty(nameof(Group.Items))!, source) + Expression.Bind(groupType.GetProperty(nameof(Group<,>.Key))!, groupKey), + Expression.Bind(groupType.GetProperty(nameof(Group<,>.Items))!, source) }; var createGroup = Expression.MemberInit(Expression.New(groupType), bindings); @@ -328,9 +611,6 @@ static MethodInfo GetEnumerableMethod(string methodName, Type elementType, Lambd /// /// Extracts and removes the orderBy and thenBy expressions from the given expression tree. /// - /// - /// - /// public static OrderRewriterResult ExtractAndRemoveOrder(Expression expression) { ArgumentNullException.ThrowIfNull(expression); @@ -432,15 +712,13 @@ protected override Expression VisitMethodCall(MethodCallExpression node) && (node.Method.Name == nameof(Queryable.OrderBy) || node.Method.Name == nameof(Queryable.OrderByDescending) || node.Method.Name == nameof(Queryable.ThenBy) - || node.Method.Name == nameof(Queryable.ThenByDescending))) + || node.Method.Name == nameof(Queryable.ThenByDescending)) + && !_insideSelectProjection) { - if (!_insideSelectProjection) - { - var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); - _orderExpressions.Add(lambda); - _orderMethods.Add(node.Method.Name); - return Visit(node.Arguments[0]); - } + var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]); + _orderExpressions.Add(lambda); + _orderMethods.Add(node.Method.Name); + return Visit(node.Arguments[0]); } return base.VisitMethodCall(node); diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs index 61593a89dba..ad08cc0f1a0 100644 --- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs +++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs @@ -132,7 +132,11 @@ public static async ValueTask> ToPageAsync( if (arguments.After is not null) { cursor = CursorParser.Parse(arguments.After, keys); - var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, true); + var (whereExpr, cursorOffset) = BuildWhereExpression( + keys, + cursor, + true, + arguments.NullOrdering); source = source.Where(whereExpr); offset = cursorOffset; @@ -157,7 +161,11 @@ public static async ValueTask> ToPageAsync( } cursor = CursorParser.Parse(arguments.Before, keys); - var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, false); + var (whereExpr, cursorOffset) = BuildWhereExpression( + keys, + cursor, + false, + arguments.NullOrdering); source = source.Where(whereExpr); offset = cursorOffset; diff --git a/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs b/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs new file mode 100644 index 00000000000..8a086dea64e --- /dev/null +++ b/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs @@ -0,0 +1,19 @@ +namespace GreenDonut.Data; + +public enum NullOrdering +{ + /// The null ordering is not specified. + Unspecified = 0, + + /// + /// The database orders null values first (i.e., null is considered less than + /// non-null values). + /// + NativeNullsFirst = 1, + + /// + /// The database orders null values last (i.e., null is considered greater than + /// non-null values). + /// + NativeNullsLast = 2 +} diff --git a/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs b/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs index e150e81e6fa..bf3168cfd21 100644 --- a/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs +++ b/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs @@ -67,6 +67,9 @@ public PagingArguments( /// public bool EnableRelativeCursors { get; init; } + /// Defines the null ordering to be used. + public NullOrdering NullOrdering { get; init; } + /// /// Deconstructs the paging arguments into its components. /// @@ -85,17 +88,22 @@ public PagingArguments( /// /// Defines if the total count of items in the dataset shall be included in the result. /// + /// + /// Defines the null ordering to be used. + /// public void Deconstruct( out int? first, out string? after, out int? last, out string? before, - out bool includeTotalCount) + out bool includeTotalCount, + out NullOrdering nullOrdering) { first = First; after = After; last = Last; before = Before; includeTotalCount = IncludeTotalCount; + nullOrdering = NullOrdering; } } diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs index d7dfda0b1a2..f8161ab62eb 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class BoolCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(bool); + => type == typeof(bool) || type == typeof(bool?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs index 0f60548f71e..6698333a3cd 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs @@ -12,7 +12,7 @@ internal sealed class DateOnlyCursorKeySerializer : ICursorKeySerializer private const string DateFormat = "yyyyMMdd"; public bool IsSupported(Type type) - => type == typeof(DateOnly); + => type == typeof(DateOnly) || type == typeof(DateOnly?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs index 96e0b1bf929..775017058c8 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs @@ -13,7 +13,7 @@ internal sealed class DateTimeCursorKeySerializer : ICursorKeySerializer private const string DateTimeFormat = "yyyyMMddHHmmssfffffff"; public bool IsSupported(Type type) - => type == typeof(DateTime); + => type == typeof(DateTime) || type == typeof(DateTime?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs index 7f58f5a8611..8b9900067ce 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs @@ -14,7 +14,7 @@ internal sealed class DateTimeOffsetCursorKeySerializer : ICursorKeySerializer private const string OffsetFormat = "hhmm"; public bool IsSupported(Type type) - => type == typeof(DateTimeOffset); + => type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs index 8a6988c30d7..2db8b7358f8 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class DecimalCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(decimal); + => type == typeof(decimal) || type == typeof(decimal?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs index f2bbba889b6..822e7ea1481 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class DoubleCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(double); + => type == typeof(double) || type == typeof(double?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs index c55f988d7a6..fd6467e12ea 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class FloatCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(float); + => type == typeof(float) || type == typeof(float?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs index 9174ee67b3d..9840c4cdc5a 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class GuidCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(Guid); + => type == typeof(Guid) || type == typeof(Guid?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs index d33eee4b2a2..73bef74b1bd 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class IntCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(int); + => type == typeof(int) || type == typeof(int?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs index c798c778fd6..260baef755d 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class LongCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(long); + => type == typeof(long) || type == typeof(long?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs index e5e4208de6a..c9b308a146a 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class ShortCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(short); + => type == typeof(short) || type == typeof(short?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs index b3057c6c234..9779e7a9473 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs @@ -12,7 +12,7 @@ internal sealed class TimeOnlyCursorKeySerializer : ICursorKeySerializer private const string TimeFormat = "HHmmssfffffff"; public bool IsSupported(Type type) - => type == typeof(TimeOnly); + => type == typeof(TimeOnly) || type == typeof(TimeOnly?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs index 668874d013e..3a1b461fc9d 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class UIntCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(uint); + => type == typeof(uint) || type == typeof(uint?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs index 4dd8f560246..efba41b5b92 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class ULongCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(ulong); + => type == typeof(ulong) || type == typeof(ulong?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs index b7db336fa36..0ab2949b0c3 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs @@ -8,7 +8,7 @@ internal sealed class UShortCursorKeySerializer : ICursorKeySerializer private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod(); public bool IsSupported(Type type) - => type == typeof(ushort); + => type == typeof(ushort) || type == typeof(ushort?); public MethodInfo GetCompareToMethod(Type type) => s_compareTo; diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj index d4b1212772a..131b0c3a3d7 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj @@ -13,8 +13,10 @@ + + diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs new file mode 100644 index 00000000000..42894406b4b --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs @@ -0,0 +1,362 @@ +using GreenDonut.Data.TestContext; +using Squadron; +using Record = GreenDonut.Data.TestContext.Record; + +namespace GreenDonut.Data; + +[Collection(PostgresCacheCollectionFixture.DefinitionName)] +public class PagingHelperPostgreSqlNullableTests(PostgreSqlResource resource) +{ + private string CreateConnectionString() + => resource.GetConnectionString($"db_{Guid.NewGuid():N}"); + + [Fact] + public async Task Paging_Nullable_Ascending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Descending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot( + postFix: TestEnvironment.TargetFramework == "NET10_0" + ? TestEnvironment.TargetFramework + : null); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Ascending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Descending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot( + postFix: TestEnvironment.TargetFramework == "NET10_0" + ? TestEnvironment.TargetFramework + : null); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Ascending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Descending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot( + postFix: TestEnvironment.TargetFramework == "NET10_0" + ? TestEnvironment.TargetFramework + : null); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Ascending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Descending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast }; + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot( + postFix: TestEnvironment.TargetFramework == "NET10_0" + ? TestEnvironment.TargetFramework + : null); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Throws_When_NullOrdering_Not_Specified() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + async Task Act() + { + var arguments = new PagingArguments(2); + await using var context + = new NullableTestsContext(Provider.PostgreSql, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + } + + // Assert + Assert.Equal( + "The NullOrdering option must be specified in the paging options or arguments when " + + "using nullable keys.", + (await Assert.ThrowsAsync(Act)).Message); + } + + private static async Task SeedAsync(string connectionString) + { + await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString); + await context.Database.EnsureCreatedAsync(); + + // https://stackoverflow.com/questions/68971695/cursor-pagination-prev-next-with-null-values + // ... with the addition of a String property to test nullable reference types. + context.Records.AddRange( + new Record + { + Id = Guid.Parse("68a5c7c2-1234-4def-bc01-9f1a23456789"), + Date = new DateOnly(2017, 10, 28), + Time = new TimeOnly(22, 00, 00), + String = "22:00:00" + }, + new Record + { + Id = Guid.Parse("d3b7e9f1-4567-4abc-a102-8c2b34567890"), + Date = new DateOnly(2017, 11, 03), + Time = null, + String = null + }, + new Record + { + Id = Guid.Parse("dd8f3a21-89ab-4cde-a203-7d3c45678901"), + Date = new DateOnly(2017, 11, 03), + Time = new TimeOnly(21, 45, 00), + String = "21:45:00" + }, + new Record + { + Id = Guid.Parse("62ce9d54-2345-4f01-b304-6e4d56789012"), + Date = new DateOnly(2017, 11, 04), + Time = new TimeOnly(14, 00, 00), + String = "14:00:00" + }, + new Record + { + Id = Guid.Parse("a1d5b763-6789-4f23-c405-5f5e67890123"), + Date = new DateOnly(2017, 11, 04), + Time = new TimeOnly(19, 40, 00), + String = "19:40:00" + }); + + await context.SaveChangesAsync(); + } +} diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs new file mode 100644 index 00000000000..84eaf510b00 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs @@ -0,0 +1,315 @@ +using GreenDonut.Data.TestContext; +using Squadron; +using Record = GreenDonut.Data.TestContext.Record; + +namespace GreenDonut.Data; + +[Collection(SqlServerCacheCollectionFixture.DefinitionName)] +public class PagingHelperSqlServerNullableTests(SqlServerResource resource) +{ + private string CreateConnectionString() + => resource.CreateConnectionString($"db_{Guid.NewGuid():N}"); + + [Fact] + public async Task Paging_Nullable_Ascending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Descending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Ascending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.Time) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_Nullable_Descending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(4) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.Time) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Ascending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Descending_Cursor_Value_NonNull() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Ascending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderBy(t => t.Date) + .ThenBy(t => t.String) + .ThenBy(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task Paging_NullableReference_Descending_Cursor_Value_Null() + { + // Arrange + using var interceptor = new CapturePagingQueryInterceptor(); + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + // Act + var arguments = new PagingArguments(4) { NullOrdering = NullOrdering.NativeNullsFirst }; + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + var page1 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + arguments = arguments with { After = page1.CreateCursor(page1.Last!) }; + var page2 = await context.Records + .OrderByDescending(t => t.Date) + .ThenByDescending(t => t.String) + .ThenByDescending(t => t.Id) + .ToPageAsync(arguments); + + // Assert + var snapshot = new Snapshot(TestEnvironment.TargetFramework); + snapshot.Add(page1); + snapshot.Add(page2); + snapshot.AddSql(interceptor); + snapshot.MatchMarkdownSnapshot(); + } + + private static async Task SeedAsync(string connectionString) + { + await using var context = new NullableTestsContext(Provider.SqlServer, connectionString); + await context.Database.EnsureCreatedAsync(); + + // https://stackoverflow.com/questions/68971695/cursor-pagination-prev-next-with-null-values + // ... with the addition of a String property to test nullable reference types. + context.Records.AddRange( + new Record + { + Id = Guid.Parse("68a5c7c2-1234-4def-bc01-9f1a23456789"), + Date = new DateOnly(2017, 10, 28), + Time = new TimeOnly(22, 00, 00), + String = "22:00:00" + }, + new Record + { + Id = Guid.Parse("d3b7e9f1-4567-4abc-a102-8c2b34567890"), + Date = new DateOnly(2017, 11, 03), + Time = null, + String = null + }, + new Record + { + Id = Guid.Parse("dd8f3a21-89ab-4cde-a203-7d3c45678901"), + Date = new DateOnly(2017, 11, 03), + Time = new TimeOnly(21, 45, 00), + String = "21:45:00" + }, + new Record + { + Id = Guid.Parse("62ce9d54-2345-4f01-b304-6e4d56789012"), + Date = new DateOnly(2017, 11, 04), + Time = new TimeOnly(14, 00, 00), + String = "14:00:00" + }, + new Record + { + Id = Guid.Parse("a1d5b763-6789-4f23-c405-5f5e67890123"), + Date = new DateOnly(2017, 11, 04), + Time = new TimeOnly(19, 40, 00), + String = "19:40:00" + }); + + await context.SaveChangesAsync(); + } +} diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs new file mode 100644 index 00000000000..53337da749b --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs @@ -0,0 +1,9 @@ +using Squadron; + +namespace GreenDonut.Data; + +[CollectionDefinition(DefinitionName)] +public class SqlServerCacheCollectionFixture : ICollectionFixture +{ + internal const string DefinitionName = "SqlServerResource"; +} diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs new file mode 100644 index 00000000000..67c10249666 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; + +namespace GreenDonut.Data.TestContext; + +public class NullableTestsContext(Provider provider, string connectionString) : DbContext +{ + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + switch (provider) + { + case Provider.PostgreSql: + optionsBuilder.UseNpgsql(connectionString); + break; + case Provider.SqlServer: + optionsBuilder.UseSqlServer(connectionString); + break; + default: + throw new InvalidOperationException(); + } + } + + public DbSet Records { get; set; } = null!; +} + +public class Record +{ + public Guid Id { get; set; } + public DateOnly? Date { get; set; } + public TimeOnly? Time { get; set; } + public string? String { get; set; } +} + +public enum Provider +{ + PostgreSql, + SqlServer +} diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md index adba1b2b006..d3e440c35d5 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md index 017f339abfd..dcc9055a17d 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md index 017f339abfd..dcc9055a17d 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md index 50e11481562..40da3598267 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) < 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md index d3dc3984660..ef3c0174609 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md index d3dc3984660..ef3c0174609 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6) ``` ## Result 3 diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..869546d1b7a --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,63 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value1='21:45:00' +-- @value4='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND (r."String" IS NULL OR r."String" > @value1)) OR (r."Date" = @value AND r."String" IS NOT NULL AND r."String" = @value1 AND r."Id" > @value4) +ORDER BY r."Date", r."String", r."Id" +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..0a313f9d9b4 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,63 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='21:45:00' +-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."String" IS NULL OR r."String" > @__value_1)) OR (r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" > @__value_2) +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..0a313f9d9b4 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,63 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='21:45:00' +-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."String" IS NULL OR r."String" > @__value_1)) OR (r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" > @__value_2) +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..07b41caf032 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND r."String" IS NULL AND r."Id" > @value2) +ORDER BY r."Date", r."String", r."Id" +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..23643db5da9 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" > @__value_1) +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..23643db5da9 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" > @__value_1) +ORDER BY r."Date", r."String", r."Id" +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md new file mode 100644 index 00000000000..84e4e3e85ba --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md @@ -0,0 +1,63 @@ +# Paging_NullableReference_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/04/2017' (DbType = Date) +-- @__value_1='14:00:00' +-- @__value_2='62ce9d54-2345-4f01-b304-6e4d56789012' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" < @__value_1) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" < @__value_2) +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..507cabd8ac5 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,63 @@ +# Paging_NullableReference_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/04/2017' (DbType = Date) +-- @value1='14:00:00' +-- @value4='62ce9d54-2345-4f01-b304-6e4d56789012' +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" < @value1) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NOT NULL AND r."String" = @value1 AND r."Id" < @value4) +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md new file mode 100644 index 00000000000..5f38117a332 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" < @__value_1) +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..4de1c82032c --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NULL AND r."Id" < @value2) +ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..0c5877dbe0a --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,63 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value1='21:45' (DbType = Time) +-- @value4='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND (r."Time" IS NULL OR r."Time" > @value1)) OR (r."Date" = @value AND r."Time" IS NOT NULL AND r."Time" = @value1 AND r."Id" > @value4) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..f13bac10ef0 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,63 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='21:45' (DbType = Time) +-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."Time" IS NULL OR r."Time" > @__value_1)) OR (r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" > @__value_2) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..f13bac10ef0 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,63 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='21:45' (DbType = Time) +-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."Time" IS NULL OR r."Time" > @__value_1)) OR (r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" > @__value_2) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..9494ad16ea2 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND r."Time" IS NULL AND r."Id" > @value2) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..ab8d617e957 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" > @__value_1) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..ab8d617e957 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" > @__value_1) +ORDER BY r."Date", r."Time", r."Id" +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md new file mode 100644 index 00000000000..4ce9d101b10 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md @@ -0,0 +1,63 @@ +# Paging_Nullable_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/04/2017' (DbType = Date) +-- @__value_1='14:00' (DbType = Time) +-- @__value_2='62ce9d54-2345-4f01-b304-6e4d56789012' +-- @__p_3='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" < @__value_1) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" < @__value_2) +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @__p_3 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..56a5251f432 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,63 @@ +# Paging_Nullable_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/04/2017' (DbType = Date) +-- @value1='14:00' (DbType = Time) +-- @value4='62ce9d54-2345-4f01-b304-6e4d56789012' +-- @p='3' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" < @value1) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NOT NULL AND r."Time" = @value1 AND r."Id" < @value4) +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md new file mode 100644 index 00000000000..bcdaa85a7b6 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @__p_0='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @__p_0 +``` + +## SQL 1 + +```sql +-- @__value_0='11/03/2017' (DbType = Date) +-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @__p_2='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" < @__value_1) +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @__p_2 +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..1abf3942fda --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @p +``` + +## SQL 1 + +```sql +-- @value='11/03/2017' (DbType = Date) +-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890' +-- @p='4' +SELECT r."Id", r."Date", r."String", r."Time" +FROM "Records" AS r +WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NULL AND r."Id" < @value2) +ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC +LIMIT @p +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..dddf0508c08 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 4; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @p int = 4; +DECLARE @value date = '2017-11-03'; +DECLARE @value1 nvarchar(4000) = N'21:45:00'; +DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] > @value1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NOT NULL AND [r].[String] = @value1 AND [r].[Id] > @value4) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..003c5eadfe2 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 nvarchar(4000) = N'21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] > @__value_2) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..003c5eadfe2 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 nvarchar(4000) = N'21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] > @__value_2) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..501b0ea2b36 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,62 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 3; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @p int = 3; +DECLARE @value date = '2017-11-03'; +DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NULL AND [r].[Id] > @value2) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..5c5e2be2206 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,62 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 3; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 3; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] > @__value_1) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..5c5e2be2206 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,62 @@ +# Paging_NullableReference_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 3; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 3; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] > @__value_1) +ORDER BY [r].[Date], [r].[String], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..21bc2f28e37 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 4; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @p int = 4; +DECLARE @value date = '2017-11-03'; +DECLARE @value1 nvarchar(4000) = N'21:45:00'; +DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND ([r].[String] IS NULL OR [r].[String] < @value1)) OR ([r].[Date] = @value AND [r].[String] IS NOT NULL AND [r].[String] = @value1 AND [r].[Id] < @value4) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..cecfe34715f --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 nvarchar(4000) = N'21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[String] IS NULL OR [r].[String] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] < @__value_2) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..cecfe34715f --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,69 @@ +# Paging_NullableReference_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 nvarchar(4000) = N'21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[String] IS NULL OR [r].[String] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] < @__value_2) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..f3652d06de0 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 5; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @p int = 5; +DECLARE @value date = '2017-11-03'; +DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND [r].[String] IS NULL AND [r].[Id] < @value2) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..13c61301192 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 5; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 5; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] < @__value_1) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..13c61301192 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,68 @@ +# Paging_NullableReference_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 5; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 5; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] < @__value_1) +ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..e29721a2b54 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 4; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @p int = 4; +DECLARE @value date = '2017-11-03'; +DECLARE @value1 time = '21:45:00'; +DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] > @value1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NOT NULL AND [r].[Time] = @value1 AND [r].[Id] > @value4) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..3bfcdb86fa0 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 time = '21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] > @__value_2) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..3bfcdb86fa0 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Ascending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 time = '21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] > @__value_2) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..f1b99c1fb85 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,62 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 3; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @p int = 3; +DECLARE @value date = '2017-11-03'; +DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NULL AND [r].[Id] > @value2) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..ae57828835d --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,62 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 3; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 3; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] > @__value_1) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..ae57828835d --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,62 @@ +# Paging_Nullable_Ascending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 3; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 3; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] > @__value_1) +ORDER BY [r].[Date], [r].[Time], [r].[Id] +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md new file mode 100644 index 00000000000..b7f456b498b --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 4; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @p int = 4; +DECLARE @value date = '2017-11-03'; +DECLARE @value1 time = '21:45:00'; +DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND ([r].[Time] IS NULL OR [r].[Time] < @value1)) OR ([r].[Date] = @value AND [r].[Time] IS NOT NULL AND [r].[Time] = @value1 AND [r].[Id] < @value4) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md new file mode 100644 index 00000000000..edbffb5d9d9 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 time = '21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[Time] IS NULL OR [r].[Time] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] < @__value_2) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md new file mode 100644 index 00000000000..edbffb5d9d9 --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md @@ -0,0 +1,69 @@ +# Paging_Nullable_Descending_Cursor_Value_NonNull + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + }, + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 4; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_3 int = 4; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 time = '21:45:00'; +DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901'; + +SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[Time] IS NULL OR [r].[Time] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] < @__value_2) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md new file mode 100644 index 00000000000..10c56ce09fd --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @p int = 5; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @p int = 5; +DECLARE @value date = '2017-11-03'; +DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND [r].[Time] IS NULL AND [r].[Id] < @value2) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md new file mode 100644 index 00000000000..5329d5e817c --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 5; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 5; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] < @__value_1) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md new file mode 100644 index 00000000000..5329d5e817c --- /dev/null +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md @@ -0,0 +1,68 @@ +# Paging_Nullable_Descending_Cursor_Value_Null + +## Result 1 + +```json +[ + { + "Id": "a1d5b763-6789-4f23-c405-5f5e67890123", + "Date": "2017-11-04", + "Time": "19:40:00", + "String": "19:40:00" + }, + { + "Id": "62ce9d54-2345-4f01-b304-6e4d56789012", + "Date": "2017-11-04", + "Time": "14:00:00", + "String": "14:00:00" + }, + { + "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901", + "Date": "2017-11-03", + "Time": "21:45:00", + "String": "21:45:00" + }, + { + "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890", + "Date": "2017-11-03", + "Time": null, + "String": null + } +] +``` + +## Result 2 + +```json +[ + { + "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789", + "Date": "2017-10-28", + "Time": "22:00:00", + "String": "22:00:00" + } +] +``` + +## SQL 0 + +```sql +DECLARE @__p_0 int = 5; + +SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` + +## SQL 1 + +```sql +DECLARE @__p_2 int = 5; +DECLARE @__value_0 date = '2017-11-03'; +DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890'; + +SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time] +FROM [Records] AS [r] +WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] < @__value_1) +ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC +``` diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs index 8472f49bce2..70cae7fb7c9 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs @@ -1292,9 +1292,12 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet using (Writer.IncreaseIndent()) { Writer.WriteIndentedLine( - "EnableRelativeCursors = args{0}_flags.HasFlag(global::{1}.RelativeCursor)", + "EnableRelativeCursors = args{0}_flags.HasFlag(global::{1}.RelativeCursor),", i, WellKnownTypes.ConnectionFlags); + Writer.WriteIndentedLine( + "NullOrdering = args{0}_options.NullOrdering", + i); } Writer.WriteIndentedLine("};"); diff --git a/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj b/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj index 40c34203c94..b397fe324cb 100644 --- a/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj +++ b/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj @@ -59,6 +59,7 @@ + diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs index d6143ba8aa2..951afb464ad 100644 --- a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using GreenDonut.Data; namespace HotChocolate.Types.Pagination; @@ -64,6 +65,9 @@ public class PagingOptions /// public bool? EnableRelativeCursors { get; set; } + /// Defines the null ordering to be used. + public NullOrdering NullOrdering { get; set; } + /// /// Gets or sets the fields that represent relative cursors. /// @@ -103,6 +107,10 @@ internal void Merge(PagingOptions other) ProviderName ??= other.ProviderName; IncludeNodesField ??= other.IncludeNodesField; EnableRelativeCursors ??= other.EnableRelativeCursors; + if (NullOrdering == NullOrdering.Unspecified) + { + NullOrdering = other.NullOrdering; + } RelativeCursorFields = RelativeCursorFields.Union(other.RelativeCursorFields); PageInfoFields = PageInfoFields.Union(other.PageInfoFields); } @@ -123,6 +131,7 @@ internal PagingOptions Copy() ProviderName = ProviderName, IncludeNodesField = IncludeNodesField, EnableRelativeCursors = EnableRelativeCursors, + NullOrdering = NullOrdering, RelativeCursorFields = RelativeCursorFields, PageInfoFields = PageInfoFields }; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs new file mode 100644 index 00000000000..af23b5725d8 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs @@ -0,0 +1,40 @@ +using GreenDonut.Data; +using HotChocolate.Execution; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Types; + +public class IntegrationTests +{ + [Fact] + public async Task Schema_Snapshot() + { + await new ServiceCollection() + .AddGraphQLServer() + .AddIntegrationTestTypes() + .AddPagingArguments() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task Maps_NullOrdering_From_PagingOptions_To_PagingArguments() + { + // arrange + var executor = await new ServiceCollection() + .AddGraphQLServer() + .AddIntegrationTestTypes() + .AddPagingArguments() + .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsFirst) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync("{ ints { nodes } }"); + var operationResult = result.ExpectOperationResult(); + + // assert + Assert.Empty(operationResult.Errors); + Assert.Equal(NullOrdering.NativeNullsFirst, Query.PagingArguments.NullOrdering); + } +} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs index 582518b51ae..3ae7c2431a4 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs @@ -1,21 +1,10 @@ using HotChocolate.Execution; -using HotChocolate.Tests; using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Types; public class InterfaceTests { - [Fact] - public async Task Schema_Snapshot() - { - await new ServiceCollection() - .AddGraphQLServer() - .AddIntegrationTestTypes() - .BuildSchemaAsync() - .MatchSnapshotAsync(); - } - [Fact] public async Task Ensure_Interface_Resolvers_Are_ParallelExecutable() { @@ -23,6 +12,7 @@ public async Task Ensure_Interface_Resolvers_Are_ParallelExecutable() await new ServiceCollection() .AddGraphQLServer() .AddIntegrationTestTypes() + .AddPagingArguments() .BuildSchemaAsync(); Assert.True( diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs index 14e174ce973..129e5cd8750 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Text.Json; +using GreenDonut.Data; using HotChocolate.Features; using HotChocolate.Language; using HotChocolate.Text.Json; @@ -43,6 +44,9 @@ public static string NullableArgumentWithExplicitType( [QueryType] public static partial class Query { + [GraphQLIgnore] + public static PagingArguments PagingArguments { get; private set; } + /// /// Gets the product. /// @@ -50,6 +54,13 @@ public static partial class Query public static Product GetProduct() => new Book { Id = "1", Title = "GraphQL in Action" }; + [UsePaging] + public static IEnumerable GetInts(PagingArguments pagingArguments) + { + PagingArguments = pagingArguments; + return []; + } + [UsePaging] [RewriteAfterToVersion] public static IQueryable GetProducts([GraphQLType>] long after) diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap similarity index 84% rename from src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap rename to src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap index 8b3cccf4882..ee5e95bd61a 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap +++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap @@ -18,6 +18,24 @@ type Book implements Product { kind: String! } +"A connection to a list of items." +type IntsConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [IntsEdge!] + "A flattened list of the nodes." + nodes: [Int!] +} + +"An edge in a connection." +type IntsEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Int! +} + type Issue8057Entity implements Node { id: ID! } @@ -61,6 +79,7 @@ type Query { The only product. """ product: Product! + ints("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): IntsConnection @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) products("Returns the elements in the list that come after the specified cursor." after: Version2 "Returns the first _n_ elements from the list." first: Int "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): ProductsConnection @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10") argumentWithExplicitType(arg: Version2): String! nullableArgumentWithExplicitType(arg: Version2): String! diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md index 7e207f817e2..cd1f98ffec3 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md @@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = global::HotChocolate.Types.Pagination.ConnectionFlagsHelper.GetConnectionFlags(context); var args2 = context.RequestAborted; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md index 85a8a33ddb5..4a75957736d 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md @@ -101,7 +101,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.BookPage.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md index 9aed6db3349..555b1dec77b 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md @@ -369,7 +369,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md index 85cbace87f1..14ff4747d76 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md @@ -363,7 +363,8 @@ namespace TestNamespace.Types.Nodes args1_before, args1_includeTotalCount) { - EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args1_options.NullOrdering }; var args2 = context.RequestAborted; var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2); @@ -500,7 +501,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); @@ -543,7 +545,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md index 4c658412e44..da094afa7e9 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md @@ -120,7 +120,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md index 455ad32b079..60e86f0ebec 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md @@ -387,7 +387,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md index 01357211e5f..2dc7ffebf7c 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md @@ -370,7 +370,8 @@ namespace TestNamespace.Types.Nodes args1_before, args1_includeTotalCount) { - EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args1_options.NullOrdering }; var args2 = context.RequestAborted; var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2); @@ -509,7 +510,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); @@ -552,7 +554,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md index b03e2071a4e..8cea2bebc1f 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md @@ -370,7 +370,8 @@ namespace TestNamespace.Types.Nodes args1_before, args1_includeTotalCount) { - EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args1_options.NullOrdering }; var args2 = context.RequestAborted; var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2); @@ -527,7 +528,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); @@ -570,7 +572,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md index 4924720173e..0bfcd520a41 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md @@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md index 7f5cfdeb60d..2efc930c4a2 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md @@ -371,7 +371,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md index 5a6c5889c8d..f3563e68e02 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md @@ -378,7 +378,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md index 6cd7beca197..4a617e9553f 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md @@ -406,7 +406,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md index 14dacdc1f2d..75d7fd07ea9 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md @@ -371,7 +371,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md index ac5c2eb6c73..83a368eddef 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md @@ -373,7 +373,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md index 86a6d102186..664dd0620dd 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md @@ -370,7 +370,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md index 18dd6ec42c6..7a809ed967c 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md @@ -371,7 +371,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md index 030b10e605d..39ce816d419 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md @@ -371,7 +371,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md index 396050729e1..b435569087a 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md @@ -370,7 +370,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md index 340e7dfe2cf..b5fb8b04e72 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md @@ -410,7 +410,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md index 7007a6b2030..5a64df96469 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md @@ -413,7 +413,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md index 6d6cfd514c3..2bc49bebe0d 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md @@ -366,7 +366,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md index ad975f570d6..fcda35edae1 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md @@ -373,7 +373,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md index 07a9914b255..119b37ed851 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md @@ -370,7 +370,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md index c281ad185a1..8ad2e03d79d 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md @@ -366,7 +366,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md index e41c109c83e..2c1aea0bf70 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md @@ -371,7 +371,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md index c154ec5918a..4502f046821 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md @@ -370,7 +370,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md index 0a4c876600e..e4bd624515a 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md @@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md index b2ba274ce67..e42d279063d 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md @@ -413,7 +413,8 @@ namespace TestNamespace.Types.Root args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = context.RequestAborted; var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md index 946e88f0048..c89059f3681 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md @@ -440,7 +440,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md index 130dccd6bdb..70c97406cde 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md @@ -157,7 +157,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md index 81a6c7a8bc8..650a5a06f33 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md @@ -440,7 +440,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1 = _binding_GetProductsAsync_productService.Execute(context); var args2 = context.RequestAborted; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md index 56453c60c8e..64352dda65e 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md @@ -157,7 +157,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md index 23c5c1f70d8..2330e8f5d7c 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md @@ -157,7 +157,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md index 5b19b90aea1..98bcd87eb85 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md @@ -173,7 +173,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md index 5cd1ecc24e8..8f65f343169 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md @@ -440,7 +440,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md index 6b3a96cc7b0..ac21c2147c6 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md @@ -452,7 +452,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md index 8d9d459fd36..11ebaf345e0 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md @@ -452,7 +452,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md index e5c7057aca5..53baad9f8b4 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md @@ -179,7 +179,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md index 6d8e5613468..451c712ff03 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md @@ -179,7 +179,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md index 73389a78b8e..dacd6bf8e85 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md @@ -178,7 +178,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md index b418b28ebc8..6b6b3f00931 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md @@ -178,7 +178,8 @@ namespace TestNamespace args0_before, args0_includeTotalCount) { - EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor) + EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor), + NullOrdering = args0_options.NullOrdering }; var args1_selection = context.Selection; var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context); diff --git a/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs b/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs index a9ad284273b..a24b73911ec 100644 --- a/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs +++ b/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs @@ -28,6 +28,7 @@ public T Execute(IResolverContext context) private static PagingArguments MapArguments(IResolverContext context) { + var pagingOptions = PagingHelper.GetPagingOptions(context.Schema, context.Selection.Field); var pagingArguments = context.GetLocalState(WellKnownContextData.PagingArguments); var includeTotalCount = IncludeTotalCount(context.Selection); @@ -36,11 +37,20 @@ private static PagingArguments MapArguments(IResolverContext context) includeTotalCount = context.IsSelected("totalCount"); } - return MapArguments(pagingArguments, includeTotalCount); + return MapArguments(pagingArguments, includeTotalCount, pagingOptions.NullOrdering); } - private static PagingArguments MapArguments(CursorPagingArguments arguments, bool includeTotalCount) - => new(arguments.First, arguments.After, arguments.Last, arguments.Before, includeTotalCount); + private static PagingArguments MapArguments( + CursorPagingArguments arguments, + bool includeTotalCount, + NullOrdering nullOrdering) + => new( + arguments.First, + arguments.After, + arguments.Last, + arguments.Before, + includeTotalCount) + { NullOrdering = nullOrdering }; private static bool IncludeTotalCount(Selection selection) => selection.Field.Features.Get()?.IncludeTotalCount is true; diff --git a/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs b/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs index e6a1e24ed4f..cc58b291e9c 100644 --- a/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs +++ b/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs @@ -1,12 +1,13 @@ using System.Collections.Immutable; +using System.Reflection; +using GreenDonut.Data; using GreenDonut.Data.Cursors; using GreenDonut.Data.Expressions; using HotChocolate.Resolvers; using HotChocolate.Types.Pagination; using HotChocolate.Types.Pagination.Utilities; -#if DEBUG using Microsoft.EntityFrameworkCore; -#endif +using Microsoft.EntityFrameworkCore.Infrastructure; using static HotChocolate.Data.Properties.EntityFrameworkResources; namespace HotChocolate.Data.Pagination; @@ -14,6 +15,8 @@ namespace HotChocolate.Data.Pagination; internal sealed class EfQueryableCursorPagingHandler(PagingOptions options) : CursorPagingHandler(options) { + private readonly NullOrdering _nullOrdering = options.NullOrdering; + protected override ValueTask SliceAsync( IResolverContext context, object source, @@ -26,6 +29,7 @@ private async ValueTask SliceAsync( CursorPagingArguments arguments) { var query = executable.Source; + var nullOrdering = ResolveNullOrdering(context, query, executable.IsInMemory, _nullOrdering); var keys = ParseDataSetKeys(query); var forward = arguments.Last is null; var requestedCount = int.MaxValue; @@ -48,14 +52,24 @@ private async ValueTask SliceAsync( if (arguments.After is not null) { var cursor = CursorParser.Parse(arguments.After, keys); - var (whereExpr, _) = ExpressionHelpers.BuildWhereExpression(keys, cursor, true); + var (whereExpr, _) = + ExpressionHelpers.BuildWhereExpression( + keys, + cursor, + true, + nullOrdering); query = query.Where(whereExpr); } if (arguments.Before is not null) { var cursor = CursorParser.Parse(arguments.Before, keys); - var (whereExpr, _) = ExpressionHelpers.BuildWhereExpression(keys, cursor, false); + var (whereExpr, _) = + ExpressionHelpers.BuildWhereExpression( + keys, + cursor, + false, + nullOrdering); query = query.Where(whereExpr); } @@ -218,4 +232,98 @@ private static CursorKey[] ParseDataSetKeys(IQueryable source) parser.Visit(source.Expression); return parser.Keys.ToArray(); } + + private static NullOrdering ResolveNullOrdering( + IResolverContext context, + IQueryable query, + bool isInMemory, + NullOrdering configured) + { + if (configured is not NullOrdering.Unspecified) + { + return configured; + } + + // LINQ-to-Objects sorts null values first in ascending order. + if (isInMemory) + { + return NullOrdering.NativeNullsFirst; + } + + var providerName = TryGetProviderName(query) + ?? TryGetProviderName(context, query.Provider); + + if (providerName is not null) + { + if (providerName.Contains("Npgsql", StringComparison.OrdinalIgnoreCase)) + { + return NullOrdering.NativeNullsLast; + } + + if (providerName.Contains("SqlServer", StringComparison.OrdinalIgnoreCase) + || providerName.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) + || providerName.Contains("MySql", StringComparison.OrdinalIgnoreCase)) + { + return NullOrdering.NativeNullsFirst; + } + + // When the provider is not in our supported list we return Unspecified so that + // BuildWhereExpression throws a clear error if nullable cursor keys are present. + return NullOrdering.Unspecified; + } + + // The provider could not be identified. Return Unspecified so that + // BuildWhereExpression throws a clear error if nullable cursor keys are + // encountered, prompting the user to set PagingOptions.NullOrdering explicitly. + return NullOrdering.Unspecified; + } + + private static string? TryGetProviderName(IQueryable query) + { + if (TryGetProviderName(query as IInfrastructure) is { } providerName) + { + return providerName; + } + + if (TryGetProviderName(query.Provider as IInfrastructure) is { } providerNameFromProvider) + { + return providerNameFromProvider; + } + + return null; + } + + private static string? TryGetProviderName(IInfrastructure? infrastructure) + => infrastructure? + .Instance + .GetService(typeof(ICurrentDbContext)) + is ICurrentDbContext currentDbContext + ? currentDbContext.Context.Database.ProviderName + : null; + + private static string? TryGetProviderName(IResolverContext context, IQueryProvider provider) + { + if (TryGetDbContextType(provider) is { } dbContextType + && context.Services.GetService(dbContextType) is DbContext dbContext) + { + return dbContext.Database.ProviderName; + } + + return null; + } + + private static Type? TryGetDbContextType(IQueryProvider provider) + { + const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; + + if (TryGetFieldValue(provider, "_queryCompiler", flags) is not object queryCompiler) + { + return null; + } + + return TryGetFieldValue(queryCompiler, "_contextType", flags) as Type; + } + + private static object? TryGetFieldValue(object instance, string fieldName, BindingFlags flags) + => instance.GetType().GetField(fieldName, flags)?.GetValue(instance); } diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs index d9a2c1900ef..b3576ee3f5e 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs @@ -1,5 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json; +using GreenDonut.Data; using HotChocolate.Data.Sorting; using HotChocolate.Data.TestContext; using HotChocolate.Execution; @@ -413,6 +415,135 @@ public async Task Paging_Fetch_Last_2_Items_Between() : null); } + [Fact] + public async Task Paging_Next_2_With_Nullable_Key_And_Configured_NullOrdering() + { + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + var executor = await new ServiceCollection() + .AddScoped(_ => new CatalogContext(connectionString)) + .AddGraphQLServer() + .AddQueryType() + .AddSorting() + .AddDbContextCursorPagingProvider() + .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast) + .BuildRequestExecutorAsync(); + + var firstResult = await executor.ExecuteAsync( + """ + { + brandsNullable(first: 2) { + pageInfo { + endCursor + } + } + } + """); + + var firstOperationResult = firstResult.ExpectOperationResult(); + var firstGraphQLError = firstOperationResult.Errors?.FirstOrDefault(); + var firstError = firstGraphQLError?.Exception?.ToString(); + var firstExtensions = firstGraphQLError?.Extensions is null + ? null + : JsonSerializer.Serialize(firstGraphQLError.Extensions); + Assert.True( + firstOperationResult.Errors is null or { Count: 0 }, + $"{firstError}\n{firstExtensions}\n{firstResult.ToJson()}"); + + using var firstDocument = JsonDocument.Parse(firstResult.ToJson()); + var afterCursor = firstDocument.RootElement + .GetProperty("data") + .GetProperty("brandsNullable") + .GetProperty("pageInfo") + .GetProperty("endCursor") + .GetString(); + + Assert.False(string.IsNullOrEmpty(afterCursor)); + + var secondResult = await executor.ExecuteAsync( + $$""" + { + brandsNullable(first: 2, after: "{{afterCursor}}") { + nodes { + name + } + pageInfo { + hasNextPage + hasPreviousPage + } + } + } + """); + + var secondOperationResult = secondResult.ExpectOperationResult(); + var secondGraphQLError = secondOperationResult.Errors?.FirstOrDefault(); + var secondError = secondGraphQLError?.Exception?.ToString(); + var secondExtensions = secondGraphQLError?.Extensions is null + ? null + : JsonSerializer.Serialize(secondGraphQLError.Extensions); + Assert.True( + secondOperationResult.Errors is null or { Count: 0 }, + $"{secondError}\n{secondExtensions}\n{secondResult.ToJson()}"); + } + + [Fact] + public async Task Paging_Next_2_With_Nullable_Key_And_Inferred_NullOrdering() + { + var connectionString = CreateConnectionString(); + await SeedAsync(connectionString); + + var executor = await new ServiceCollection() + .AddScoped(_ => new CatalogContext(connectionString)) + .AddGraphQLServer() + .AddQueryType() + .AddSorting() + .AddDbContextCursorPagingProvider() + .BuildRequestExecutorAsync(); + + var firstResult = await executor.ExecuteAsync( + """ + { + brandsNullable(first: 2) { + pageInfo { + endCursor + } + } + } + """); + + var firstOperationResult = firstResult.ExpectOperationResult(); + Assert.True(firstOperationResult.Errors is null or { Count: 0 }, firstResult.ToJson()); + + using var firstDocument = JsonDocument.Parse(firstResult.ToJson()); + var afterCursor = firstDocument.RootElement + .GetProperty("data") + .GetProperty("brandsNullable") + .GetProperty("pageInfo") + .GetProperty("endCursor") + .GetString(); + + Assert.False(string.IsNullOrEmpty(afterCursor)); + + var secondResult = await executor.ExecuteAsync( + $$""" + { + brandsNullable(first: 2, after: "{{afterCursor}}") { + nodes { + name + } + pageInfo { + hasNextPage + hasPreviousPage + } + } + } + """); + + var secondOperationResult = secondResult.ExpectOperationResult(); + Assert.True(secondOperationResult.Errors is null or { Count: 0 }, secondResult.ToJson()); + } + public class Query { [UsePaging] @@ -453,6 +584,13 @@ public IQueryable GetProducts(CatalogContext context, ISortingContext s return context.Products; } + + [UsePaging] + public IQueryable GetBrandsNullable(CatalogContext context) + => context.Brands + .OrderBy(t => t.Name) + .ThenBy(t => t.AlwaysNull) + .ThenBy(t => t.Id); } [ExtendObjectType("ProductConnection")] diff --git a/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs b/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs new file mode 100644 index 00000000000..fdbba155672 --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs @@ -0,0 +1,42 @@ +using GreenDonut.Data; +using HotChocolate.Execution; +using HotChocolate.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Data.Pagination; + +public class PagingArgumentsParameterExpressionBuilderTests +{ + [Fact] + public async Task Maps_NullOrdering_From_PagingOptions_To_PagingArguments() + { + // arrange + var executor = await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddPagingArguments() + .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsFirst) + .BuildRequestExecutorAsync(); + + // act + var result = await executor.ExecuteAsync("{ ints { nodes } }"); + var operationResult = result.ExpectOperationResult(); + + // assert + Assert.Empty(operationResult.Errors); + Assert.Equal(NullOrdering.NativeNullsFirst, Query.PagingArguments.NullOrdering); + } + + public class Query + { + public static PagingArguments PagingArguments { get; private set; } + + [UsePaging] + public IEnumerable GetInts(PagingArguments pagingArguments) + { + PagingArguments = pagingArguments; + + return []; + } + } +} diff --git a/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs b/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs index fafd08dd6e9..1bc5ad329e7 100644 --- a/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs @@ -233,6 +233,7 @@ public async Task GetDefaultPage_With_Nullable_SecondPage() .AddGraphQL() .AddQueryType() .AddPagingArguments() + .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast) .ExecuteRequestAsync( OperationRequestBuilder.New() .SetDocument( @@ -345,6 +346,7 @@ public async Task GetDefaultPage_With_Nullable_Fallback_SecondPage() .AddGraphQL() .AddQueryType() .AddPagingArguments() + .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast) .ExecuteRequestAsync( OperationRequestBuilder.New() .SetDocument( diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md index 9b2e6a0a1ea..5f918abe82a 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md index 55781a45184..856690862c0 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md index b8092b48bec..8b67613feef 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md index 8340bf5fe79..839e1510dd1 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md index a74b0183df3..6eee21c65e6 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md @@ -4,19 +4,19 @@ ```sql -- @__value_0='Brand10' --- @__value_2='11' --- @__p_3='3' +-- @__value_1='11' +-- @__p_2='3' SELECT b."Id", b."AlwaysNull", b."DisplayName", b."Name", b."BrandDetails_Country_Name" FROM "Brands" AS b -WHERE b."Name" > @__value_0 OR (b."Name" = @__value_0 AND b."AlwaysNull" > NULL) OR (b."Name" = @__value_0 AND b."AlwaysNull" IS NULL AND b."Id" > @__value_2) +WHERE b."Name" > @__value_0 OR (b."Name" = @__value_0 AND b."AlwaysNull" IS NULL AND b."Id" > @__value_1) ORDER BY b."Name", b."AlwaysNull", b."Id" -LIMIT @__p_3 +LIMIT @__p_2 ``` ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0))) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso False)) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull == null)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md index 4387aababf2..c57e326fa8e 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md @@ -4,11 +4,11 @@ ```sql -- @value='Brand10' --- @value4='11' +-- @value2='11' -- @p='3' SELECT b."Id", b."AlwaysNull", b."DisplayName", b."Name", b."BrandDetails_Country_Name" FROM "Brands" AS b -WHERE b."Name" > @value OR (b."Name" = @value AND b."AlwaysNull" > NULL) OR (b."Name" = @value AND b."AlwaysNull" IS NULL AND b."Id" > @value4) +WHERE b."Name" > @value OR (b."Name" = @value AND b."AlwaysNull" IS NULL AND b."Id" > @value2) ORDER BY b."Name", b."AlwaysNull", b."Id" LIMIT @p ``` @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0))) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso False)) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull == null)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md index 916311626ca..6bef425e8f7 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md index 80b865ca900..14d7b63f828 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3) ``` ## Result diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md index 017f339abfd..dcc9055a17d 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6) ``` ## Result 3 diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md index adba1b2b006..d3e440c35d5 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(6) ``` ## Result 3 diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md index d3dc3984660..ef3c0174609 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md @@ -16,7 +16,7 @@ LIMIT @__p_2 ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6) ``` ## Result 3 diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md index 50e11481562..40da3598267 100644 --- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md +++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md @@ -16,7 +16,7 @@ LIMIT @p ## Expression 0 ```text -[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6) +[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) < 0)))).Take(6) ``` ## Result 3 diff --git a/website/src/docs/hotchocolate/v15/fetching-data/pagination.md b/website/src/docs/hotchocolate/v15/fetching-data/pagination.md index 30266fb6036..68540d973d1 100644 --- a/website/src/docs/hotchocolate/v15/fetching-data/pagination.md +++ b/website/src/docs/hotchocolate/v15/fetching-data/pagination.md @@ -820,15 +820,18 @@ If no paging providers have been registered, a default paging provider capable o The following options can be configured. -| Property | Default | Description | -| ------------------------------ | ------- | ----------------------------------------------------------------------------------- | -| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. | -| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. | -| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. | -| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. | -| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. | -| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. | -| `ProviderName` | `null` | The name of the pagination provider to use. | +| Property | Default | Description | +| ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------ | +| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. | +| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. | +| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. | +| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. | +| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. | +| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. | +| `ProviderName` | `null` | The name of the pagination provider to use. | +| `NullOrdering` | `Unspecified` | The database null sort order for nullable cursor keys (`NativeNullsFirst` or `NativeNullsLast`). | + +When paging over nullable cursor keys, configure `NullOrdering` to match your database's native null sorting behavior. # Pagination defaults @@ -837,7 +840,11 @@ If we want to enforce consistent pagination defaults throughout our app, we can ```csharp builder.Services .AddGraphQLServer() - .ModifyPagingOptions(opt => opt.MaxPageSize = 100); + .ModifyPagingOptions(opt => + { + opt.MaxPageSize = 100; + opt.NullOrdering = NullOrdering.NativeNullsLast; + }); ``` [Learn more about possible PagingOptions](#pagingoptions) diff --git a/website/src/docs/hotchocolate/v16/fetching-data/pagination.md b/website/src/docs/hotchocolate/v16/fetching-data/pagination.md index 1a684b46434..bd9a77916cd 100644 --- a/website/src/docs/hotchocolate/v16/fetching-data/pagination.md +++ b/website/src/docs/hotchocolate/v16/fetching-data/pagination.md @@ -820,15 +820,36 @@ If no paging providers have been registered, a default paging provider capable o The following options can be configured. -| Property | Default | Description | -| ------------------------------ | ------- | ----------------------------------------------------------------------------------- | -| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. | -| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. | -| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. | -| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. | -| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. | -| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. | -| `ProviderName` | `null` | The name of the pagination provider to use. | +| Property | Default | Description | +| ------------------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. | +| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. | +| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. | +| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. | +| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. | +| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. | +| `ProviderName` | `null` | The name of the pagination provider to use. | +| `NullOrdering` | `Unspecified` | Controls how `null` values are ordered relative to non-null values when a nullable field is used as a cursor key. See [Nullable cursor keys](#nullable-cursor-keys) below. | + +# Nullable cursor keys + +When a cursor key field can be `null`, you must tell Hot Chocolate how the database orders `null` values so that cursor-based pagination produces correct results across pages. + +Set `NullOrdering` on `PagingOptions` to match your database's native behavior: + +| Value | When to use | +| ------------------ | --------------------------------------------------------------------------------------------------- | +| `Unspecified` | Default. The EF Core paging handler auto-detects the ordering for known providers (see note below). | +| `NativeNullsFirst` | `null` is ordered **before** all non-null values (e.g. SQL Server, SQLite, in-memory LINQ). | +| `NativeNullsLast` | `null` is ordered **after** all non-null values (e.g. PostgreSQL default behavior). | + +```csharp +builder.Services + .AddGraphQLServer() + .ModifyPagingOptions(opt => opt.NullOrdering = NullOrdering.NativeNullsLast); +``` + +> **Auto-detection:** When `NullOrdering` is `Unspecified` and the EF Core paging handler is used, the correct ordering is detected automatically for all supported providers: PostgreSQL (`NativeNullsLast`), and SQL Server, SQLite, and in-memory (`NativeNullsFirst`). For unrecognized providers, an error is thrown when nullable cursor keys are present — set `PagingOptions.NullOrdering` explicitly to resolve it. # Pagination defaults