From 48692bae48366770fde32e2b8b472116f72a47bb Mon Sep 17 00:00:00 2001 From: Mohamed Seada Date: Fri, 27 Dec 2024 21:04:29 +0200 Subject: [PATCH] Add JsonExists DbFunction - Define SqlServer translation - Define Sqllite translation Fixes dotnet#31136 --- .../RelationalDbFunctionsExtensions.cs | 28 ++++++++ .../SqlServerMethodCallTranslatorProvider.cs | 1 + .../SqlServerJsonFunctionsTranslator.cs | 64 +++++++++++++++++ .../SqliteMethodCallTranslatorProvider.cs | 1 + .../SqliteJsonFunctionsTranslator.cs | 68 +++++++++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 src/EFCore.SqlServer/Query/Internal/Translators/SqlServerJsonFunctionsTranslator.cs create mode 100644 src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteJsonFunctionsTranslator.cs diff --git a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs index f0f6a0e1ea8..1906fac8f8e 100644 --- a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs @@ -56,4 +56,32 @@ public static T Greatest( this DbFunctions _, [NotParameterized] params T[] values) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Greatest))); + + /// + /// Checks whether a specified JSON path exists within a JSON string. + /// Typically corresponds to a database function or SQL expression. + /// + /// + /// + /// This method is translated to a database-specific function or expression. + /// The support for this function depends on the database and provider being used. + /// Refer to your database provider's documentation for detailed support information. + /// + /// + /// For more details, see EF Core database providers. + /// + /// + /// The instance. + /// The JSON string or column containing JSON text. + /// The JSON path to check for existence. + /// The type of the JSON expression. + /// + /// A nullable boolean value, if the JSON path exists, if not, and + /// when the JSON string is null. + /// + public static bool? JsonExists( + this DbFunctions _, + T expression, + string path) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(JsonExists))); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs index 79a99b8c437..394de2a054e 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs @@ -38,6 +38,7 @@ public SqlServerMethodCallTranslatorProvider( new SqlServerFullTextSearchFunctionsTranslator(sqlExpressionFactory), new SqlServerIsDateFunctionTranslator(sqlExpressionFactory), new SqlServerIsNumericFunctionTranslator(sqlExpressionFactory), + new SqlServerJsonFunctionsTranslator(sqlExpressionFactory, sqlServerSingletonOptions), new SqlServerMathTranslator(sqlExpressionFactory), new SqlServerNewGuidTranslator(sqlExpressionFactory), new SqlServerObjectToStringTranslator(sqlExpressionFactory, typeMappingSource), diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerJsonFunctionsTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerJsonFunctionsTranslator.cs new file mode 100644 index 00000000000..6568ab2a094 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerJsonFunctionsTranslator.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +/// +/// 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. +/// +public class SqlServerJsonFunctionsTranslator : IMethodCallTranslator +{ + private static readonly MethodInfo JsonExistsMethodInfo = typeof(RelationalDbFunctionsExtensions) + .GetRuntimeMethod(nameof(RelationalDbFunctionsExtensions.JsonExists), [typeof(DbFunctions), typeof(object), typeof(string)])!; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; + + /// + /// 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. + /// + public SqlServerJsonFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactory, ISqlServerSingletonOptions sqlServerSingletonOptions) + { + _sqlExpressionFactory = sqlExpressionFactory; + _sqlServerSingletonOptions = sqlServerSingletonOptions; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + { + if (JsonExistsMethodInfo.Equals(method) + && arguments[0].TypeMapping is SqlServerOwnedJsonTypeMapping or StringTypeMapping + && _sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer + && _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160) + { + return _sqlExpressionFactory.Function( + "JSON_PATH_EXISTS", + arguments, + nullable: true, + argumentsPropagateNullability: Statics.TrueArrays[2], + method.ReturnType); + } + + return null; + } +} diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs index 970873af563..1fab485f481 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs @@ -30,6 +30,7 @@ public SqliteMethodCallTranslatorProvider(RelationalMethodCallTranslatorProvider new SqliteDateTimeMethodTranslator(sqlExpressionFactory), new SqliteGlobMethodTranslator(sqlExpressionFactory), new SqliteHexMethodTranslator(sqlExpressionFactory), + new SqliteJsonFunctionsTranslator(sqlExpressionFactory), new SqliteMathTranslator(sqlExpressionFactory), new SqliteObjectToStringTranslator(sqlExpressionFactory), new SqliteRandomTranslator(sqlExpressionFactory), diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteJsonFunctionsTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteJsonFunctionsTranslator.cs new file mode 100644 index 00000000000..712814e4d85 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteJsonFunctionsTranslator.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +/// +/// 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. +/// +public class SqliteJsonFunctionsTranslator : IMethodCallTranslator +{ + private static readonly MethodInfo JsonExistsMethodInfo = typeof(RelationalDbFunctionsExtensions) + .GetRuntimeMethod(nameof(RelationalDbFunctionsExtensions.JsonExists), [typeof(DbFunctions), typeof(object), typeof(string)])!; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public SqliteJsonFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + { + if (JsonExistsMethodInfo.Equals(method) + && arguments[0].TypeMapping is SqliteJsonTypeMapping or StringTypeMapping) + { + // IIF(arguments_0 IS NULL, NULL, JSON_TYPE(arguments_0, arguments_1) IS NOT NULL) + return _sqlExpressionFactory.Function("IFF", + [ + _sqlExpressionFactory.IsNull(arguments[0]), + _sqlExpressionFactory.Fragment("NULL", method.ReturnType), + _sqlExpressionFactory.IsNotNull( + _sqlExpressionFactory.Function("JSON_TYPE", + arguments, + nullable: true, + argumentsPropagateNullability: Statics.TrueArrays[2], + returnType: typeof(string))) + ], + nullable: true, + argumentsPropagateNullability: Statics.TrueArrays[3], + method.ReturnType); + } + + return null; + } +}