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 @@ -402,6 +402,44 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
_ => throw new UnreachableException()
});
}

// We translate EF.Functions.JsonPathExists here and not in a method translator since we need to support JsonPathExists over
// complex and owned JSON properties, which requires special handling.
case nameof(RelationalDbFunctionsExtensions.JsonPathExists)
when declaringType == typeof(RelationalDbFunctionsExtensions)
&& @object is null
&& arguments is [_, var json, var path]:
{
if (Translate(path) is not SqlExpression translatedPath)
{
return QueryCompilationContext.NotTranslatedExpression;
}

#pragma warning disable EF1001 // TranslateProjection() is pubternal
var translatedJson = TranslateProjection(json) switch
{
// The JSON argument is a scalar string property
SqlExpression scalar => scalar,

// The JSON argument is a complex or owned JSON property
RelationalStructuralTypeShaperExpression { ValueBufferExpression: JsonQueryExpression { JsonColumn: var c } } => c,

_ => null
};
#pragma warning restore EF1001

return translatedJson is null
? QueryCompilationContext.NotTranslatedExpression
: _sqlExpressionFactory.Function(
"JSON_EXISTS",
[translatedJson, translatedPath],
nullable: true,
// Note that JSON_EXISTS() does propagate nullability; however, our query pipeline assumes that if
// arguments propagate nullability, that's the *only* reason for the function to return null; this means that
// if the arguments are non-nullable, the IS NOT NULL wrapping check can be optimized away.
argumentsPropagateNullability: [false, false],
typeof(bool));
}
}

return QueryCompilationContext.NotTranslatedExpression;
Expand Down
3 changes: 1 addition & 2 deletions test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,7 @@ public void JsonExists()
{
using var ctx = CreateContext();
var count = ctx.JsonbEntities.Count(
e =>
NpgsqlJsonDbFunctionsExtensions.JsonExists(EF.Functions, e.CustomerElement.GetProperty("Statistics"), "Visits"));
e => EF.Functions.JsonExists(e.CustomerElement.GetProperty("Statistics"), "Visits"));

Assert.Equal(2, count);
AssertSql(
Expand Down
3 changes: 1 addition & 2 deletions test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,7 @@ public void JsonExists()
{
using var ctx = CreateContext();
var count = ctx.JsonbEntities.Count(
e =>
NpgsqlJsonDbFunctionsExtensions.JsonExists(EF.Functions, e.Customer.Statistics, "Visits"));
e => EF.Functions.JsonExists(e.Customer.Statistics, "Visits"));

Assert.Equal(2, count);
AssertSql(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public void JsonExists()
{
using var ctx = CreateContext();

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

Assert.Equal(2, count);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,43 @@ public JsonTranslationsNpgsqlTest(JsonTranslationsQueryNpgsqlFixture fixture, IT
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

[MinimumPostgresVersion(17, 0)]
public override async Task JsonPathExists_on_scalar_string_column()
{
// TODO: #3733
await AssertTranslationFailed(base.JsonPathExists_on_scalar_string_column);
await base.JsonPathExists_on_scalar_string_column();

AssertSql();
AssertSql(
"""
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
FROM "JsonEntities" AS j
WHERE JSON_EXISTS(j."JsonString", '$.OptionalInt')
""");
}

[MinimumPostgresVersion(17, 0)]
public override async Task JsonPathExists_on_complex_property()
{
// TODO: #3733
await AssertTranslationFailed(base.JsonPathExists_on_complex_property);
await base.JsonPathExists_on_complex_property();

AssertSql();
AssertSql(
"""
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
FROM "JsonEntities" AS j
WHERE JSON_EXISTS(j."JsonComplexType", '$.OptionalInt')
""");
}

[MinimumPostgresVersion(17, 0)]
public override async Task JsonPathExists_on_owned_entity()
{
// TODO: #3733
await AssertTranslationFailed(base.JsonPathExists_on_owned_entity);
await base.JsonPathExists_on_owned_entity();

AssertSql();
AssertSql(
"""
SELECT j."Id", j."JsonString", j."JsonComplexType", j."JsonOwnedType"
FROM "JsonEntities" AS j
WHERE JSON_EXISTS(j."JsonOwnedType", '$.OptionalInt')
""");
}

public class JsonTranslationsQueryNpgsqlFixture : JsonTranslationsQueryFixtureBase, ITestSqlLoggerFactory
Expand Down