Skip to content
Merged
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
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