Skip to content

Commit 716994b

Browse files
authored
Implement EF.Function.JsonPathExists (#3770)
Closes #3733
1 parent 76e84e1 commit 716994b

File tree

5 files changed

+65
-14
lines changed

5 files changed

+65
-14
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,44 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
402402
_ => throw new UnreachableException()
403403
});
404404
}
405+
406+
// We translate EF.Functions.JsonPathExists here and not in a method translator since we need to support JsonPathExists over
407+
// complex and owned JSON properties, which requires special handling.
408+
case nameof(RelationalDbFunctionsExtensions.JsonPathExists)
409+
when declaringType == typeof(RelationalDbFunctionsExtensions)
410+
&& @object is null
411+
&& arguments is [_, var json, var path]:
412+
{
413+
if (Translate(path) is not SqlExpression translatedPath)
414+
{
415+
return QueryCompilationContext.NotTranslatedExpression;
416+
}
417+
418+
#pragma warning disable EF1001 // TranslateProjection() is pubternal
419+
var translatedJson = TranslateProjection(json) switch
420+
{
421+
// The JSON argument is a scalar string property
422+
SqlExpression scalar => scalar,
423+
424+
// The JSON argument is a complex or owned JSON property
425+
RelationalStructuralTypeShaperExpression { ValueBufferExpression: JsonQueryExpression { JsonColumn: var c } } => c,
426+
427+
_ => null
428+
};
429+
#pragma warning restore EF1001
430+
431+
return translatedJson is null
432+
? QueryCompilationContext.NotTranslatedExpression
433+
: _sqlExpressionFactory.Function(
434+
"JSON_EXISTS",
435+
[translatedJson, translatedPath],
436+
nullable: true,
437+
// Note that JSON_EXISTS() does propagate nullability; however, our query pipeline assumes that if
438+
// arguments propagate nullability, that's the *only* reason for the function to return null; this means that
439+
// if the arguments are non-nullable, the IS NOT NULL wrapping check can be optimized away.
440+
argumentsPropagateNullability: [false, false],
441+
typeof(bool));
442+
}
405443
}
406444

407445
return QueryCompilationContext.NotTranslatedExpression;

test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,7 @@ public void JsonExists()
504504
{
505505
using var ctx = CreateContext();
506506
var count = ctx.JsonbEntities.Count(
507-
e =>
508-
NpgsqlJsonDbFunctionsExtensions.JsonExists(EF.Functions, e.CustomerElement.GetProperty("Statistics"), "Visits"));
507+
e => EF.Functions.JsonExists(e.CustomerElement.GetProperty("Statistics"), "Visits"));
509508

510509
Assert.Equal(2, count);
511510
AssertSql(

test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,7 @@ public void JsonExists()
613613
{
614614
using var ctx = CreateContext();
615615
var count = ctx.JsonbEntities.Count(
616-
e =>
617-
NpgsqlJsonDbFunctionsExtensions.JsonExists(EF.Functions, e.Customer.Statistics, "Visits"));
616+
e => EF.Functions.JsonExists(e.Customer.Statistics, "Visits"));
618617

619618
Assert.Equal(2, count);
620619
AssertSql(

test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public void JsonExists()
194194
{
195195
using var ctx = CreateContext();
196196

197-
var count = ctx.JsonEntities.Count(e => NpgsqlJsonDbFunctionsExtensions.JsonExists(EF.Functions, e.CustomerJsonb, "Age"));
197+
var count = ctx.JsonEntities.Count(e => EF.Functions.JsonExists(e.CustomerJsonb, "Age"));
198198

199199
Assert.Equal(2, count);
200200

test/EFCore.PG.FunctionalTests/Query/Translations/JsonTranslationsNpgsqlTest.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,43 @@ public JsonTranslationsNpgsqlTest(JsonTranslationsQueryNpgsqlFixture fixture, IT
99
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
1010
}
1111

12+
[MinimumPostgresVersion(17, 0)]
1213
public override async Task JsonPathExists_on_scalar_string_column()
1314
{
14-
// TODO: #3733
15-
await AssertTranslationFailed(base.JsonPathExists_on_scalar_string_column);
15+
await base.JsonPathExists_on_scalar_string_column();
1616

17-
AssertSql();
17+
AssertSql(
18+
"""
19+
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
20+
FROM "JsonEntities" AS j
21+
WHERE JSON_EXISTS(j."JsonString", '$.OptionalInt')
22+
""");
1823
}
1924

25+
[MinimumPostgresVersion(17, 0)]
2026
public override async Task JsonPathExists_on_complex_property()
2127
{
22-
// TODO: #3733
23-
await AssertTranslationFailed(base.JsonPathExists_on_complex_property);
28+
await base.JsonPathExists_on_complex_property();
2429

25-
AssertSql();
30+
AssertSql(
31+
"""
32+
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
33+
FROM "JsonEntities" AS j
34+
WHERE JSON_EXISTS(j."JsonComplexType", '$.OptionalInt')
35+
""");
2636
}
2737

38+
[MinimumPostgresVersion(17, 0)]
2839
public override async Task JsonPathExists_on_owned_entity()
2940
{
30-
// TODO: #3733
31-
await AssertTranslationFailed(base.JsonPathExists_on_owned_entity);
41+
await base.JsonPathExists_on_owned_entity();
3242

33-
AssertSql();
43+
AssertSql(
44+
"""
45+
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
46+
FROM "JsonEntities" AS j
47+
WHERE JSON_EXISTS(j."JsonOwnedType", '$.OptionalInt')
48+
""");
3449
}
3550

3651
public class JsonTranslationsQueryNpgsqlFixture : JsonTranslationsQueryFixtureBase, ITestSqlLoggerFactory

0 commit comments

Comments
 (0)