/// <inheritdoc /> protected override Expression VisitUnary(UnaryExpression unaryExpression) { if (unaryExpression.NodeType == ExpressionType.ArrayLength) { return(TranslationFailed(unaryExpression.Operand, Visit(unaryExpression.Operand), out var sqlOperand) ? null : _jsonPocoTranslator?.TranslateArrayLength(sqlOperand)); } // Make explicit casts implicit if they are applied to a JSON traversal object. // It is pretty common for Newtonsoft.Json objects to be cast to other types (e.g. casting from // JToken to JArray to check an arrays length via the JContainer.Count property). if (unaryExpression.NodeType == ExpressionType.Convert || unaryExpression.NodeType == ExpressionType.ConvertChecked) { var visitedOperand = Visit(unaryExpression.Operand); if (visitedOperand is MySqlJsonTraversalExpression traversal) { return(unaryExpression.Type == typeof(object) ? visitedOperand : traversal.Clone( traversal.ReturnsText, unaryExpression.Type, _sqlExpressionFactory.FindMapping(unaryExpression.Type))); } } return(base.VisitUnary(unaryExpression)); }
public virtual SqlExpression Translate( SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments, IDiagnosticsLogger <DbLoggerCategory.Query> logger) { if (method.DeclaringType != typeof(MySqlJsonDbFunctionsExtensions)) { return(null); } var args = arguments // Skip useless DbFunctions instance .Skip(1) // JSON extensions accept object parameters for JSON, since they must be able to handle POCOs, strings or DOM types. // This means they come wrapped in a convert node, which we need to remove. // Convert nodes may also come from wrapping JsonTraversalExpressions generated through POCO traversal. .Select(RemoveConvert) // CHECK: Either just not doing this at all is fine, or not applying it to JsonQuote and JsonUnquote // (as already implemented below) is needed. An alternative would be to move the check into the local // json() function. // // If a function is invoked over a JSON traversal expression, that expression may come with // returnText: true (i.e. operator ->> and not ->). Since the functions below require a json object and // not text, we transform it. // .Select( // a => a is MySqlJsonTraversalExpression traversal && // method.Name != nameof(MySqlJsonDbFunctionsExtensions.JsonQuote) && // method.Name != nameof(MySqlJsonDbFunctionsExtensions.JsonUnquote) // ? withReturnsText(traversal, false) // : a) .ToArray(); var result = method.Name switch { nameof(MySqlJsonDbFunctionsExtensions.AsJson) => _sqlExpressionFactory.ApplyTypeMapping( args[0], _sqlExpressionFactory.FindMapping(method.ReturnType, "json")), nameof(MySqlJsonDbFunctionsExtensions.JsonType) => _sqlExpressionFactory.NullableFunction( "JSON_TYPE", new[] { Json(args[0]) }, typeof(string)), nameof(MySqlJsonDbFunctionsExtensions.JsonQuote) => _sqlExpressionFactory.NullableFunction( "JSON_QUOTE", new[] { args[0] }, method.ReturnType), nameof(MySqlJsonDbFunctionsExtensions.JsonUnquote) => _sqlExpressionFactory.NullableFunction( "JSON_UNQUOTE", new[] { args[0] }, method.ReturnType), nameof(MySqlJsonDbFunctionsExtensions.JsonExtract) => _sqlExpressionFactory.NullableFunction( "JSON_EXTRACT", Array.Empty <SqlExpression>() .Append(Json(args[0])) .Concat(DeconstructParamsArray(args[1])), method.ReturnType, _sqlExpressionFactory.FindMapping(method.ReturnType, "json"), false), nameof(MySqlJsonDbFunctionsExtensions.JsonContains) => _sqlExpressionFactory.NullableFunction( "JSON_CONTAINS", args.Length >= 3 ? new[] { Json(args[0]), args[1], args[2] } : new[] { Json(args[0]), args[1] }, typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPath) => _sqlExpressionFactory.NullableFunction( "JSON_CONTAINS_PATH", new[] { Json(args[0]), _sqlExpressionFactory.Constant("one"), args[1] }, typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPathAny) => _sqlExpressionFactory.NullableFunction( "JSON_CONTAINS_PATH", Array.Empty <SqlExpression>() .Append(Json(args[0])) .Append(_sqlExpressionFactory.Constant("one")) .Concat(DeconstructParamsArray(args[1])), typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPathAll) => _sqlExpressionFactory.NullableFunction( "JSON_CONTAINS_PATH", Array.Empty <SqlExpression>() .Append(Json(args[0])) .Append(_sqlExpressionFactory.Constant("all")) .Concat(DeconstructParamsArray(args[1])), typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonSearchAny) => _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.NullableFunction( "JSON_SEARCH", Array.Empty <SqlExpression>() .Append(Json(args[0])) .Append(_sqlExpressionFactory.Constant("one")) .Append(args[1]) .AppendIfTrue( args.Length >= 3, () => args.Length >= 4 ? args[3] : _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping)) .AppendIfTrue(args.Length >= 3, () => args[2]), typeof(bool), null, false)), // JSON_SEARCH can return null even if all arguments are not null _ => null }; return(result); SqlExpression Json(SqlExpression e) => _sqlExpressionFactory.ApplyTypeMapping(EnsureJson(e), _sqlExpressionFactory.FindMapping(e.Type, "json"));
public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments) { if (method.DeclaringType != typeof(MySqlJsonDbFunctionsExtensions)) { return(null); } var args = arguments // Skip useless DbFunctions instance .Skip(1) // JSON extensions accept object parameters for JSON, since they must be able to handle POCOs, strings or DOM types. // This means they come wrapped in a convert node, which we need to remove. // Convert nodes may also come from wrapping JsonTraversalExpressions generated through POCO traversal. .Select(removeConvert) // If a function is invoked over a JSON traversal expression, that expression may come with // returnText: true (i.e. operator ->> and not ->). Since the functions below require a json object and // not text, we transform it. .Select(a => a is MySqlJsonTraversalExpression traversal ? withReturnsText(traversal, false) : a) .ToArray(); if (!args.Any(a => a.TypeMapping is MySqlJsonTypeMapping || a is MySqlJsonTraversalExpression)) { throw new InvalidOperationException("The EF JSON methods require a JSON parameter but none was found."); } var result = method.Name switch { nameof(MySqlJsonDbFunctionsExtensions.JsonType) => _sqlExpressionFactory.Function( "JSON_TYPE", new[] { args[0] }, typeof(string)), nameof(MySqlJsonDbFunctionsExtensions.JsonContains) => _sqlExpressionFactory.Function( "JSON_CONTAINS", args.Length >= 3 ? new[] { json(args[0]), args[1], args[2] } : new[] { json(args[0]), args[1] }, typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPath) => _sqlExpressionFactory.Function( "JSON_CONTAINS_PATH", new[] { json(args[0]), _sqlExpressionFactory.Constant("one"), args[1] }, typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPathAny) => _sqlExpressionFactory.Function( "JSON_CONTAINS_PATH", Array.Empty <SqlExpression>() .Append(json(args[0])) .Append(_sqlExpressionFactory.Constant("one")) .Concat(deconstructParamsArray(args[1])), typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonContainsPathAll) => _sqlExpressionFactory.Function( "JSON_CONTAINS_PATH", Array.Empty <SqlExpression>() .Append(json(args[0])) .Append(_sqlExpressionFactory.Constant("all")) .Concat(deconstructParamsArray(args[1])), typeof(bool)), nameof(MySqlJsonDbFunctionsExtensions.JsonSearchAny) => _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "JSON_SEARCH", Array.Empty <SqlExpression>() .Append(json(args[0])) .Append(_sqlExpressionFactory.Constant("one")) .Append(args[1]) .AppendIfTrue(args.Length >= 3, () => args.Length >= 4 ? args[3] : _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping)) .AppendIfTrue(args.Length >= 3, () => args[2]), typeof(bool))), nameof(MySqlJsonDbFunctionsExtensions.JsonSearchAll) => _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "JSON_SEARCH", Array.Empty <SqlExpression>() .Append(json(args[0])) .Append(_sqlExpressionFactory.Constant("all")) .Append(args[1]) .AppendIfTrue(args.Length >= 3, () => args.Length >= 4 ? args[3] : _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping)) .AppendIfTrue(args.Length >= 3, () => args[2]), typeof(bool))) as SqlExpression, _ => null }; return(result); SqlExpression json(SqlExpression e) => _sqlExpressionFactory.ApplyTypeMapping(e, _sqlExpressionFactory.FindMapping(e.Type, "json"));
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments) { if (Equals(method, _spatialDistancePlanarMethodInfo)) { // MySQL 8 uses the Andoyer algorithm by default for `ST_Distance()`, if an SRID of 4326 has been // associated with the geometry. // Since this call explicitly asked for a planar distance calculation, we need to ensure that // MySQL actually does that. // MariaDB ignores SRIDs and always calculates the planar distance. // CHECK: It could be faster to just manually apply the Pythagoras Theorem instead of changing the // SRID in the case where ST_SRID() does not support a second parameter yet (see // SetSrid()). if (_options.ServerVersion.SupportsSpatialSupportFunctionAdditions && _options.ServerVersion.SupportsSpatialDistanceFunctionImplementsAndoyer) { return(_sqlExpressionFactory.Case( new[] { new CaseWhenClause( _sqlExpressionFactory.Equal( _sqlExpressionFactory.Function( "ST_SRID", new[] { arguments[1] }, typeof(int)), _sqlExpressionFactory.Constant(0)), GetStDistanceFunctionCall( arguments[1], arguments[2], method.ReturnType, _sqlExpressionFactory.FindMapping(method.ReturnType), _sqlExpressionFactory)) }, GetStDistanceFunctionCall( SetSrid(arguments[1], 0, _sqlExpressionFactory, _options), SetSrid(arguments[2], 0, _sqlExpressionFactory, _options), method.ReturnType, _sqlExpressionFactory.FindMapping(method.ReturnType), _sqlExpressionFactory))); } return(GetStDistanceFunctionCall( arguments[1], arguments[2], method.ReturnType, _sqlExpressionFactory.FindMapping(method.ReturnType), _sqlExpressionFactory)); } if (Equals(method, _spatialDistanceSphere)) { if (!(arguments[3] is SqlConstantExpression algorithm)) { throw new InvalidOperationException("The 'algorithm' parameter must be supplied as a constant."); } return(GetStDistanceSphereFunctionCall( arguments[1], arguments[2], (SpatialDistanceAlgorithm)algorithm.Value, method.ReturnType, _sqlExpressionFactory.FindMapping(method.ReturnType), _sqlExpressionFactory, _options)); } return(null); }