Пример #1
0
        /// <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"));
Пример #4
0
        /// <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);
        }