Skip to content

Commit 12a1aa8

Browse files
7645reroji
authored andcommitted
Fix alias cloning for json recordset table-valued functions (#3773)
Fixes #3790 (cherry picked from commit fd26075)
1 parent cbb4bd7 commit 12a1aa8

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni
8989
arguments[i] = (SqlExpression)cloningExpressionVisitor.Visit(Arguments[i]);
9090
}
9191

92-
return new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality);
92+
// Without ColumnInfos (e.g. unnest), PostgreSQL ties the output column name to the table alias, so preserve it.
93+
// With explicit ColumnInfos (e.g. jsonb_to_recordset), apply the clone alias to keep FROM references consistent.
94+
return new PgTableValuedFunctionExpression(ColumnInfos is null ? Alias : alias!, Name, arguments, ColumnInfos, WithOrdinality);
9395
}
9496

9597
/// <inheritdoc />

test/EFCore.PG.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonCollectionNpgsqlTest.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,60 @@ GROUP BY a0."Key"
238238
""");
239239
}
240240

241+
[ConditionalFact]
242+
public virtual async Task GroupBy_with_json_collection_predicate_and_projecting_group_elements()
243+
{
244+
await using var context = Fixture.CreateContext();
245+
246+
var result = await context.Set<RootEntity>()
247+
.Where(root => root.AssociateCollection.Any(element => element.Int > 0))
248+
.GroupBy(root => root.Name)
249+
.Select(group => new { Elements = group.OrderBy(root => root.Id).Take(1) })
250+
.ToListAsync();
251+
252+
Assert.NotEmpty(result);
253+
Assert.All(result, grouping => Assert.NotNull(grouping.Elements));
254+
255+
AssertSql(
256+
"""
257+
SELECT r1."Name", r3."Id", r3."Name", r3.c, r3.c0, r3.c1
258+
FROM (
259+
SELECT r."Name"
260+
FROM "RootEntity" AS r
261+
WHERE EXISTS (
262+
SELECT 1
263+
FROM ROWS FROM (jsonb_to_recordset(r."AssociateCollection") AS (
264+
"Id" integer,
265+
"Int" integer,
266+
"Ints" jsonb,
267+
"Name" text,
268+
"String" text
269+
)) WITH ORDINALITY AS a
270+
WHERE a."Int" > 0)
271+
GROUP BY r."Name"
272+
) AS r1
273+
LEFT JOIN (
274+
SELECT r2."Id", r2."Name", r2.c, r2.c0, r2.c1
275+
FROM (
276+
SELECT r0."Id", r0."Name", r0."AssociateCollection" AS c, r0."OptionalAssociate" AS c0, r0."RequiredAssociate" AS c1, ROW_NUMBER() OVER(PARTITION BY r0."Name" ORDER BY r0."Id" NULLS FIRST) AS row
277+
FROM "RootEntity" AS r0
278+
WHERE EXISTS (
279+
SELECT 1
280+
FROM ROWS FROM (jsonb_to_recordset(r0."AssociateCollection") AS (
281+
"Id" integer,
282+
"Int" integer,
283+
"Ints" jsonb,
284+
"Name" text,
285+
"String" text
286+
)) WITH ORDINALITY AS a0
287+
WHERE a0."Int" > 0)
288+
) AS r2
289+
WHERE r2.row <= 1
290+
) AS r3 ON r1."Name" = r3."Name"
291+
ORDER BY r1."Name" NULLS FIRST, r3."Name" NULLS FIRST, r3."Id" NULLS FIRST
292+
""");
293+
}
294+
241295
#endregion GroupBy
242296

243297
public override async Task Select_within_Select_within_Select_with_aggregates()

0 commit comments

Comments
 (0)