Skip to content

Commit

Permalink
Make respect / ignore nulls more user friendly
Browse files Browse the repository at this point in the history
  • Loading branch information
virzak committed Jan 2, 2024
1 parent f6db188 commit 5fff5af
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ Starting with SQL Server 2022 (16.x) it is possible to use LAG with IGNORE NULLS
Use the following expression:

```cs
Expression<Func<TestRow, int?>> lastNonNullExpr = r => EF.Functions.Lag(r.Col1, 0, Clauses.RespectOrIgnoreNulls.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id)
Expression<Func<TestRow, int?>> lastNonNullExpr = r => EF.Functions.Lag(r.Col1, 0, NullHandling.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id)
```

More SQL Server related information on the LAG function here available [here](https://learn.microsoft.com/en-us/sql/t-sql/functions/lead-transact-sql?view=sql-server-ver16#-ignore-nulls--respect-nulls-).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace Zomp.EFCore.WindowFunctions.Clauses;
namespace Zomp.EFCore.WindowFunctions;

/// <summary>
/// Respect Or Ignore Nulls.
/// Specifies whether null should be respected or ignored.
/// </summary>
public enum RespectOrIgnoreNulls
public enum NullHandling
{
/// <summary>
/// Respect nulls.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public static Expression VisitWindowFunction(this ExpressionVisitor expressionVi
GenerateList(relationalCommandBuilder, windowFunctionExpression.Arguments, e => expressionVisitor.Visit(e));
relationalCommandBuilder.Append(") ");

if (windowFunctionExpression.RespectOrIgnoreNulls is { } respectOrIgnoreNulls)
if (windowFunctionExpression.NullHandling is { } nullHandling)
{
relationalCommandBuilder.Append(respectOrIgnoreNulls == RespectOrIgnoreNulls.RespectNulls
relationalCommandBuilder.Append(nullHandling == NullHandling.RespectNulls
? "RESPECT NULLS " : "IGNORE NULLS ");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected virtual SqlExpression Parse(IReadOnlyList<SqlExpression> arguments, st
var directArgs = new List<SqlExpression>();

OverExpression? over = null;
RespectOrIgnoreNulls? respectOrIgnoreNulls = null;
NullHandling? nullHandling = null;

for (var i = 1; i < arguments.Count; ++i)
{
Expand All @@ -83,16 +83,16 @@ protected virtual SqlExpression Parse(IReadOnlyList<SqlExpression> arguments, st
break;
}

if (argument is SqlConstantExpression sce && sce.Type == typeof(RespectOrIgnoreNulls))
if (argument is SqlConstantExpression sce && sce.Type == typeof(NullHandling))
{
respectOrIgnoreNulls = (RespectOrIgnoreNulls?)sce.Value;
nullHandling = (NullHandling?)sce.Value;
continue;
}

directArgs.Add(sqlExpressionFactory.ApplyDefaultTypeMapping(argument));
}

return new WindowFunctionExpression(functionName, directArgs, respectOrIgnoreNulls, over?.PartitionByExpression?.List, over?.OrderingExpression?.List, over?.OrderingExpression?.RowOrRangeClause, RelationalTypeMapping.NullMapping);
return new WindowFunctionExpression(functionName, directArgs, nullHandling, over?.PartitionByExpression?.List, over?.OrderingExpression?.List, over?.OrderingExpression?.RowOrRangeClause, RelationalTypeMapping.NullMapping);
}

private static OverExpression GetOrderingSqlExpression(IReadOnlyList<SqlExpression> arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// </summary>
/// <param name="function">Function (MIN, MAX).</param>
/// <param name="arguments">A list of argument expressions of the Window function.</param>
/// <param name="respectOrIgnoreNulls">Respect or ignore nulls.</param>
/// <param name="nullHandling">Respect or ignore nulls.</param>
/// <param name="partitions">A list of expressions to partition by.</param>
/// <param name="orderings">A list of ordering expressions to order by.</param>
/// <param name="rowOrRange">Row or range clause.</param>
Expand All @@ -23,7 +23,7 @@
public class WindowFunctionExpression(
string function,
IReadOnlyList<SqlExpression> arguments,
RespectOrIgnoreNulls? respectOrIgnoreNulls,
NullHandling? nullHandling,
IReadOnlyList<SqlExpression>? partitions,
IReadOnlyList<OrderingExpression>? orderings,
RowOrRangeExpression? rowOrRange,
Expand All @@ -37,7 +37,7 @@ public class WindowFunctionExpression(
/// <summary>
/// Gets the respect nulls or ignore nulls parameter.
/// </summary>
public RespectOrIgnoreNulls? RespectOrIgnoreNulls { get; } = respectOrIgnoreNulls;
public NullHandling? NullHandling { get; } = nullHandling;

/// <summary>
/// Gets the list of expressions used in partitioning.
Expand Down Expand Up @@ -74,7 +74,7 @@ public WindowFunctionExpression Update(IReadOnlyList<SqlExpression> arguments, I
&& (ReferenceEquals(partitions, Partitions) || partitions.SequenceEqual(Partitions))
&& (ReferenceEquals(orderings, Orderings) || orderings.SequenceEqual(Orderings))
? this
: new(Function, arguments, RespectOrIgnoreNulls, partitions, orderings, RowOrRange, TypeMapping);
: new(Function, arguments, NullHandling, partitions, orderings, RowOrRange, TypeMapping);

/// <inheritdoc />
public override bool Equals(object? obj)
Expand Down Expand Up @@ -135,7 +135,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
}

return changed
? new WindowFunctionExpression(Function, arguments, RespectOrIgnoreNulls, partitions, orderings, RowOrRange, TypeMapping)
? new WindowFunctionExpression(Function, arguments, NullHandling, partitions, orderings, RowOrRange, TypeMapping)
: this;
}

Expand All @@ -147,9 +147,9 @@ protected override void Print(ExpressionPrinter expressionPrinter)
expressionPrinter.VisitCollection(Arguments);
expressionPrinter.Append(") ");

if (RespectOrIgnoreNulls is { } respectOrIgnoreNulls)
if (NullHandling is { } nullHandling)
{
expressionPrinter.Append(respectOrIgnoreNulls == Clauses.RespectOrIgnoreNulls.RespectNulls
expressionPrinter.Append(nullHandling == WindowFunctions.NullHandling.RespectNulls
? "RESPECT NULLS " : "IGNORE NULLS ");
}

Expand Down
4 changes: 2 additions & 2 deletions src/Zomp.EFCore.WindowFunctions/Templates/Includes.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public record Configuration(FunctionType FunctionType, ExpressionType Expression
string AlwaysNullableTypeDefinition { get; } = GetAlwaysNullableTypeDefinition(ExpressionType);

private static readonly Argument Offset = new("long", "offset", "The offset.");
private static readonly Argument RespectOrIgnoreNulls = new("RespectOrIgnoreNulls?", "respectOrIgnoreNulls", "Respect nulls or ignore nulls.");
private static readonly Argument NullHandling = new("NullHandling?", "nullHandling", "Respect nulls or ignore nulls. If omitted or <see langword=\"null\" /> is specified, provider's default is used which is to respect nulls.");
public bool IsGeneric => ExpressionType is ExpressionType.NonNullableStruct or ExpressionType.NullableStruct or ExpressionType.Generic;
public bool IsGenericWithQualifier => ExpressionType is ExpressionType.NonNullableStruct or ExpressionType.NullableStruct;
public string ReturnType => CustomReturnType ? "TResult" : !string.IsNullOrEmpty(SpecificReturnType) ? SpecificReturnType : IsGeneric ? "T" : TypeDefinition;
Expand All @@ -71,7 +71,7 @@ public record Configuration(FunctionType FunctionType, ExpressionType Expression
list = list[..^NumberOfOptionalArguments];
if (RespectIgnoreNullsOption)
{
list.Add(RespectOrIgnoreNulls);
list.Add(NullHandling);
}
return list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void LeadBasic()
}

[SkippableFact]
public void LeadNullForRespectOrIgnoreNulls()
public void LeadNullForNullHandling()
{
var query = DbContext.TestRows
.Select(r => EF.Functions.Lead(r.Id, Offset, Default, null, EF.Functions.Over().OrderBy(r.Id)));
Expand All @@ -35,7 +35,7 @@ public void LeadRespectNulls()
Skip.If(DbContext.IsSqlite || DbContext.IsPostgreSQL);

var query = DbContext.TestRows
.Select(r => EF.Functions.Lead(r.Id, Offset, Default, Clauses.RespectOrIgnoreNulls.RespectNulls, EF.Functions.Over().OrderBy(r.Id)));
.Select(r => EF.Functions.Lead(r.Id, Offset, Default, NullHandling.RespectNulls, EF.Functions.Over().OrderBy(r.Id)));

var result = query.ToList();

Expand All @@ -51,7 +51,7 @@ public void LeadIgnoreNulls()
var query = DbContext.TestRows
.Select(r => new
{
Lead = EF.Functions.Lead(r.Id, Offset, Default, Clauses.RespectOrIgnoreNulls.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id)),
Lead = EF.Functions.Lead(r.Id, Offset, Default, NullHandling.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id)),
Original = r,
})
.OrderBy(r => r.Original)
Expand Down Expand Up @@ -95,8 +95,8 @@ public void LagLastNonNull(bool withDefault)
Skip.If(DbContext.IsSqlite || DbContext.IsPostgreSQL);

Expression<Func<TestRow, int?>> lastNonNullExpr = withDefault
? r => EF.Functions.Lag(r.Col1, 0, null, Clauses.RespectOrIgnoreNulls.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id))
: r => EF.Functions.Lag(r.Col1, 0, Clauses.RespectOrIgnoreNulls.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id));
? r => EF.Functions.Lag(r.Col1, 0, null, NullHandling.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id))
: r => EF.Functions.Lag(r.Col1, 0, NullHandling.IgnoreNulls, EF.Functions.Over().OrderBy(r.Id));

var query = DbContext.TestRows.Select(lastNonNullExpr);

Expand Down

0 comments on commit 5fff5af

Please sign in to comment.