Skip to content

Commit b16af1b

Browse files
authored
Simplify SQL when copying JSON scalar properties across documents (#3627)
Continues #3608
1 parent 4405acb commit b16af1b

21 files changed

+48
-23
lines changed

src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,8 +1092,10 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp
10921092
GenerateJsonPath(jsonScalarExpression.Json, returnsText: false, path);
10931093
break;
10941094

1095-
// Scalar collection mapped to JSON (either because it's nested within a JSON document, or because the user explicitly
1096-
// opted for this rather than the default PG array mapping).
1095+
// A scalar to be extracted out as JSON (and not as e.g. text/int).
1096+
// This happens for scalar collection mapped to JSON (either because it's nested within a JSON document,
1097+
// or because the user explicitly opted for this rather than the default PG array mapping), or when we copy a scalar JSON
1098+
// property from one document to another via ExecuteUpdate.
10971099
case NpgsqlJsonTypeMapping typeMapping:
10981100
Check.DebugAssert(typeMapping.ElementTypeMapping is not null);
10991101
GenerateJsonPath(jsonScalarExpression.Json, returnsText: false, path);

src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,27 @@ protected override bool TrySerializeScalarToJson(
13011301
break;
13021302
}
13031303

1304+
// We now have a scalar value expression that needs to be passed to jsonb_set(), but jsonb_set() requires a json/jsonb
1305+
// argument, not e.g. text or int. So we need to wrap the argument in to_jsonb/to_json.
1306+
// Note that for structural types we always already get a jsonb/json value and have already exited above (no need for
1307+
// to_jsonb/to_json).
1308+
1309+
// One exception is if the value expression happens to be a JsonScalarExpression (e.g. copy scalar property from within
1310+
// one JSON document into another). For that case, rather than do to_jsonb(x.JsonbDoc ->> 'SomeProperty') - which extracts
1311+
// a jsonb property as text only to reconvert it back to jsonb - we just change the type mapping on the JsonScalarExpression
1312+
// to json/jsonb, in order to generate x.JsonbDoc -> 'SomeProperty' (no text extraction).
1313+
if (value is JsonScalarExpression jsonScalarValue
1314+
&& jsonScalarValue.Json.TypeMapping?.StoreType == jsonTypeMapping.StoreType)
1315+
{
1316+
jsonValue = new JsonScalarExpression(
1317+
jsonScalarValue.Json,
1318+
jsonScalarValue.Path,
1319+
jsonScalarValue.Type,
1320+
jsonTypeMapping,
1321+
jsonScalarValue.IsNullable);
1322+
return true;
1323+
}
1324+
13041325
jsonValue = _sqlExpressionFactory.Function(
13051326
jsonTypeMapping.StoreType switch
13061327
{

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlJsonTypeMapping.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
66

77
/// <summary>
8-
/// Supports the older Npgsql-specific JSON mapping, allowing mapping json/jsonb to text, to e.g.
8+
/// Represents scalars within a JSON document that maintain their json/jsonb type (rather than being extracted out as e.g. text/int).
9+
/// Also supports the older Npgsql-specific JSON mapping, allowing mapping json/jsonb to text, to e.g.
910
/// <see cref="JsonElement" /> (weakly-typed mapping) or to arbitrary POCOs (but without them being modeled).
10-
/// For the standard EF JSON support, which relies on owned entity modeling, see <see cref="NpgsqlStructuralJsonTypeMapping" />.
11+
/// Note that for structural types mapped via the standard EF complex/owned mapping, we use
12+
/// <see cref="NpgsqlStructuralJsonTypeMapping" />.
1113
/// </summary>
1214
public class NpgsqlJsonTypeMapping : NpgsqlTypeMapping
1315
{

test/EFCore.PG.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonBulkUpdateNpgsqlTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public override async Task Update_multiple_properties_inside_associations_and_on
364364
365365
UPDATE "RootEntity" AS r
366366
SET "Name" = r."Name" || 'Modified',
367-
"RequiredRelated" = jsonb_set(r."RequiredRelated", '{String}', to_jsonb(r."OptionalRelated" ->> 'String')),
367+
"RequiredRelated" = jsonb_set(r."RequiredRelated", '{String}', r."OptionalRelated" -> 'String'),
368368
"OptionalRelated" = jsonb_set(r."OptionalRelated", '{RequiredNested,String}', to_jsonb(@p))
369369
WHERE (r."OptionalRelated") IS NOT NULL
370370
""");
@@ -379,7 +379,7 @@ public override async Task Update_multiple_projected_associations_via_anonymous_
379379
@p='?'
380380
381381
UPDATE "RootEntity" AS r
382-
SET "RequiredRelated" = jsonb_set(r."RequiredRelated", '{String}', to_jsonb(r."OptionalRelated" ->> 'String')),
382+
SET "RequiredRelated" = jsonb_set(r."RequiredRelated", '{String}', r."OptionalRelated" -> 'String'),
383383
"OptionalRelated" = jsonb_set(r."OptionalRelated", '{String}', to_jsonb(@p))
384384
WHERE (r."OptionalRelated") IS NOT NULL
385385
""");

test/EFCore.PG.FunctionalTests/Types/Miscellaneous/NpgsqlBoolTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8080
AssertSql(
8181
"""
8282
UPDATE "JsonTypeEntity" AS j
83-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(CAST(j."JsonContainer" ->> 'OtherValue' AS boolean)))
83+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8484
""");
8585
}
8686

test/EFCore.PG.FunctionalTests/Types/Miscellaneous/NpgsqlGuidTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8080
AssertSql(
8181
"""
8282
UPDATE "JsonTypeEntity" AS j
83-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(CAST(j."JsonContainer" ->> 'OtherValue' AS uuid)))
83+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8484
""");
8585
}
8686

test/EFCore.PG.FunctionalTests/Types/Miscellaneous/NpgsqlStringTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8080
AssertSql(
8181
"""
8282
UPDATE "JsonTypeEntity" AS j
83-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(j."JsonContainer" ->> 'OtherValue'))
83+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8484
""");
8585
}
8686

test/EFCore.PG.FunctionalTests/Types/Networking/NpgsqlInetTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8282
AssertSql(
8383
"""
8484
UPDATE "JsonTypeEntity" AS j
85-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(CAST(j."JsonContainer" ->> 'OtherValue' AS inet)))
85+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8686
""");
8787
}
8888

test/EFCore.PG.FunctionalTests/Types/Networking/NpgsqlMacaddrTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8282
AssertSql(
8383
"""
8484
UPDATE "JsonTypeEntity" AS j
85-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(CAST(j."JsonContainer" ->> 'OtherValue' AS macaddr)))
85+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8686
""");
8787
}
8888

test/EFCore.PG.FunctionalTests/Types/Numeric/NpgsqlDecimalTypeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public override async Task ExecuteUpdate_within_json_to_another_json_property()
8080
AssertSql(
8181
"""
8282
UPDATE "JsonTypeEntity" AS j
83-
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', to_jsonb(CAST(j."JsonContainer" ->> 'OtherValue' AS numeric)))
83+
SET "JsonContainer" = jsonb_set(j."JsonContainer", '{Value}', j."JsonContainer" -> 'OtherValue')
8484
""");
8585
}
8686

0 commit comments

Comments
 (0)