Skip to content

Commit

Permalink
Redo NpgsqlSetOperationTypingInjector (#3291)
Browse files Browse the repository at this point in the history
Fixes #3283
  • Loading branch information
roji authored Sep 21, 2024
1 parent 8a0fca5 commit a894bdc
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public override Expression Process(Expression query)
var result = base.Process(query);

result = new NpgsqlUnnestPostprocessor().Visit(result);
result = new NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor().Visit(result);
result = new NpgsqlSetOperationTypingInjector().Visit(result);

return result;
}
Expand Down

This file was deleted.

80 changes: 80 additions & 0 deletions src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypingInjector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;

/// <summary>
/// A visitor that injects explicit typing on null projections in set operations, to ensure PostgreSQL gets the typing right.
/// </summary>
/// <remarks>
/// <para>
/// See the <see href="https://www.postgresql.org/docs/current/typeconv-union-case.html">
/// PostgreSQL docs on type conversion and set operations</see>.
/// </para>
/// <para>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </para>
/// </remarks>
public class NpgsqlSetOperationTypingInjector : ExpressionVisitor
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression switch
{
ShapedQueryExpression shapedQueryExpression
=> shapedQueryExpression.Update(
Visit(shapedQueryExpression.QueryExpression),
Visit(shapedQueryExpression.ShaperExpression)),

SetOperationBase setOperationExpression => VisitSetOperation(setOperationExpression),

_ => base.VisitExtension(extensionExpression)
};

private Expression VisitSetOperation(SetOperationBase setOperation)
{
var select1 = (SelectExpression)Visit(setOperation.Source1);
var select2 = (SelectExpression)Visit(setOperation.Source2);

List<ProjectionExpression>? rewrittenProjections = null;

for (var i = 0; i < select1.Projection.Count; i++)
{
var projection = select1.Projection[i];
var visitedProjection = projection.Expression is SqlConstantExpression { Value : null }
&& select2.Projection[i].Expression is SqlConstantExpression { Value : null }
? projection.Update(
new SqlUnaryExpression(
ExpressionType.Convert, projection.Expression, projection.Expression.Type, projection.Expression.TypeMapping))
: (ProjectionExpression)Visit(projection);

if (visitedProjection != projection && rewrittenProjections is null)
{
rewrittenProjections = new List<ProjectionExpression>(select1.Projection.Count);
rewrittenProjections.AddRange(select1.Projection.Take(i));
}

rewrittenProjections?.Add(visitedProjection);
}

if (rewrittenProjections is not null)
{
select1 = select1.Update(
select1.Tables,
select1.Predicate,
select1.GroupBy,
select1.Having,
rewrittenProjections,
select1.Orderings,
select1.Offset,
select1.Limit);
}

return setOperation.Update(select1, select2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_with_tabl
"""
SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", l0."Id", l0."OwnedReference_Id", l0."OwnedReference_OwnedIntValue1", l0."OwnedReference_OwnedIntValue2", o0."OwnedIntValue3", o."OwnedIntValue4", l0."OwnedReference_OwnedStringValue1", l0."OwnedReference_OwnedStringValue2", o0."OwnedStringValue3", o."OwnedStringValue4"
FROM (
SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
FROM "BaseEntity" AS b
UNION ALL
SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator"
Expand Down Expand Up @@ -578,7 +578,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_base_without_t
"""
SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."BaseEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4"
FROM (
SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
FROM "BaseEntity" AS b
UNION ALL
SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator"
Expand Down Expand Up @@ -620,7 +620,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_middle_without
"""
SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."MiddleEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4"
FROM (
SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
FROM "BaseEntity" AS b
UNION ALL
SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator"
Expand Down Expand Up @@ -686,7 +686,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_base(bool asy
"""
SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."BaseEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4"
FROM (
SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
FROM "BaseEntity" AS b
UNION ALL
SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator"
Expand Down Expand Up @@ -732,7 +732,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_middle(bool a
"""
SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."MiddleEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4"
FROM (
SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator"
FROM "BaseEntity" AS b
UNION ALL
SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator"
Expand Down

0 comments on commit a894bdc

Please sign in to comment.