Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,17 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor

// Find any unnest table which does not have any references to its ordinality column in the projection or orderings
// (this is where they may appear); if found, remove the ordinality column from the unnest call.
// Note that if the ordinality column is the first ordering, we can still remove it, since unnest already returns
// ordered results.
// Note that if the ordinality column is the first ordering and it is ascending, we can still remove it, since
// unnest already returns ordered results in ascending ordinality order. Descending ordinality ordering must be
// kept (along with WITH ORDINALITY), otherwise Last()/LastOrDefault() incorrectly returns the first element
// (see https://github.com/npgsql/efcore.pg/issues/3909).
if (unwrappedTable is PgUnnestExpression unnest
&& !selectExpression.Orderings.Skip(1).Select(o => o.Expression)
.Concat(selectExpression.Projection.Select(p => p.Expression))
.Any(IsOrdinalityColumn))
.Any(IsOrdinalityColumn)
&& (orderings.Count == 0
|| !IsOrdinalityColumn(orderings[0].Expression)
|| orderings[0].IsAscending))
{
if (newTables is null)
{
Expand All @@ -75,6 +80,10 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor
orderings = orderings.Skip(1).ToList();
}
}
else if (newTables is not null)
{
newTables[i] = table;
}

bool IsOrdinalityColumn(SqlExpression expression)
=> expression is ColumnExpression { Name: "ordinality" } ordinalityColumn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,25 @@ FROM unnest(p."Ints") AS i(value)
""");
}

[ConditionalFact]
public virtual async Task Column_collection_Last()
{
await AssertQuery(
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => c.Ints.Last() == 10),
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => (c.Ints.Length >= 1 ? c.Ints.Last() : -1) == 10));

AssertSql(
"""
SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."NullableWrappedId", p."NullableWrappedIdWithNullableComparer", p."String", p."Strings", p."WrappedId"
FROM "PrimitiveCollectionsEntity" AS p
WHERE (
SELECT i.value
FROM unnest(p."Ints") WITH ORDINALITY AS i(value)
ORDER BY i.ordinality DESC NULLS LAST
LIMIT 1) = 10
""");
}

public override async Task Column_collection_FirstOrDefault()
{
await base.Column_collection_FirstOrDefault();
Expand All @@ -1504,6 +1523,24 @@ FROM unnest(p."Ints") AS i(value)
""");
}

[ConditionalFact]
public virtual async Task Column_collection_LastOrDefault()
{
await AssertQuery(
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => c.Ints.LastOrDefault() == 10));

AssertSql(
"""
SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."NullableWrappedId", p."NullableWrappedIdWithNullableComparer", p."String", p."Strings", p."WrappedId"
FROM "PrimitiveCollectionsEntity" AS p
WHERE COALESCE((
SELECT i.value
FROM unnest(p."Ints") WITH ORDINALITY AS i(value)
ORDER BY i.ordinality DESC NULLS LAST
LIMIT 1), 0) = 10
""");
}

public override async Task Column_collection_Single()
{
await base.Column_collection_Single();
Expand Down