Пример #1
0
        protected override Expression VisitBinary(BinaryExpression binaryExpression)
        {
            if (binaryExpression.NodeType == ExpressionType.ArrayIndex)
            {
                if (TranslationFailed(binaryExpression.Left, Visit(TryRemoveImplicitConvert(binaryExpression.Left)), out var sqlLeft) ||
                    TranslationFailed(binaryExpression.Right, Visit(TryRemoveImplicitConvert(binaryExpression.Right)), out var sqlRight))
                {
                    return(null);
                }

                // Try translating ArrayIndex inside json column
                var expression = _jsonPocoTranslator?.TranslateMemberAccess(
                    sqlLeft,
                    _sqlExpressionFactory.JsonArrayIndex(sqlRight),
                    binaryExpression.Type);

                if (expression != null)
                {
                    return(expression);
                }
            }

            var visitedExpression = (SqlExpression)base.VisitBinary(binaryExpression);

            if (visitedExpression == null)
            {
                return(null);
            }

            if (visitedExpression is SqlBinaryExpression visitedBinaryExpression)
            {
                // Returning null forces client projection.
                // CHECK: Is this still true in .NET Core 3.0?
                switch (visitedBinaryExpression.OperatorType)
                {
                case ExpressionType.Add:
                case ExpressionType.Subtract:
                case ExpressionType.Multiply:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                    return(IsDateTimeBasedOperation(visitedBinaryExpression)
                            ? null
                            : visitedBinaryExpression);
                }
            }

            return(visitedExpression);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            if (method.DeclaringType != typeof(JsonElement) ||
                !(instance.TypeMapping is MySqlJsonTypeMapping mapping))
            {
                return(null);
            }

            // The root of the JSON expression is a ColumnExpression. We wrap that with an empty traversal
            // expression (col->'$'); subsequent traversals will gradually append the path into that.
            // Note that it's possible to call methods such as GetString() directly on the root, and the
            // empty traversal is necessary to properly convert it to a text.
            instance = instance is ColumnExpression columnExpression
                ? _sqlExpressionFactory.JsonTraversal(
                columnExpression, returnsText : false, typeof(string), mapping)
                : instance;

            if (method == _getProperty)
            {
                return(instance is MySqlJsonTraversalExpression prevPathTraversal
                    ? prevPathTraversal.Append(
                           ApplyPathLocationTypeMapping(arguments[0]),
                           typeof(JsonElement),
                           _typeMappingSource.FindMapping(typeof(JsonElement)))
                    : null);
            }

            if (method == _arrayIndexer)
            {
                return(instance is MySqlJsonTraversalExpression prevPathTraversal
                    ? prevPathTraversal.Append(
                           _sqlExpressionFactory.JsonArrayIndex(ApplyPathLocationTypeMapping(arguments[0])),
                           typeof(JsonElement),
                           _typeMappingSource.FindMapping(typeof(JsonElement)))
                    : null);
            }

            if (_getMethods.Contains(method.Name) &&
                arguments.Count == 0 &&
                instance is MySqlJsonTraversalExpression traversal)
            {
                return(ConvertFromJsonExtract(
                           traversal.Clone(
                               method.Name == nameof(JsonElement.GetString),
                               method.ReturnType,
                               _typeMappingSource.FindMapping(method.ReturnType)
                               ),
                           method.ReturnType));
            }

            if (method == _getArrayLength)
            {
                return(_sqlExpressionFactory.Function(
                           "JSON_LENGTH",
                           new[] { instance },
                           typeof(int)));
            }

            if (method.Name.StartsWith("TryGet") && arguments.Count == 0)
            {
                throw new InvalidOperationException($"The TryGet* methods on {nameof(JsonElement)} aren't translated yet, use Get* instead.'");
            }

            return(null);
        }
Пример #3
0
        protected override Expression VisitBinary(BinaryExpression binaryExpression)
        {
            if (binaryExpression.NodeType == ExpressionType.ArrayIndex)
            {
                if (TranslationFailed(binaryExpression.Left, Visit(TryRemoveImplicitConvert(binaryExpression.Left)), out var sqlLeft) ||
                    TranslationFailed(binaryExpression.Right, Visit(TryRemoveImplicitConvert(binaryExpression.Right)), out var sqlRight))
                {
                    return(QueryCompilationContext.NotTranslatedExpression);
                }

                if (binaryExpression.Left.Type == typeof(byte[]))
                {
                    var left  = Visit(binaryExpression.Left);
                    var right = Visit(binaryExpression.Right);

                    if (left is SqlExpression leftSql &&
                        right is SqlExpression rightSql)
                    {
                        return(_sqlExpressionFactory.NullableFunction(
                                   "ASCII",
                                   new[]
                        {
                            _sqlExpressionFactory.NullableFunction(
                                "SUBSTRING",
                                new[]
                            {
                                leftSql, Dependencies.SqlExpressionFactory.Add(
                                    Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql),
                                    Dependencies.SqlExpressionFactory.Constant(1)),
                                Dependencies.SqlExpressionFactory.Constant(1)
                            },
                                typeof(byte[]))
                        },
                                   typeof(byte)));
                    }
                }

                // Try translating ArrayIndex inside json column
                var expression = _jsonPocoTranslator?.TranslateMemberAccess(
                    sqlLeft,
                    _sqlExpressionFactory.JsonArrayIndex(sqlRight),
                    binaryExpression.Type);

                if (expression is not null)
                {
                    return(expression);
                }
            }

            var visitedExpression = base.VisitBinary(binaryExpression);

            if (visitedExpression is SqlBinaryExpression visitedBinaryExpression)
            {
                // TODO: Is this still true in .NET Core 3.0?
                switch (visitedBinaryExpression.OperatorType)
                {
                case ExpressionType.Add:
                case ExpressionType.Subtract:
                case ExpressionType.Multiply:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                    return(IsDateTimeBasedOperation(visitedBinaryExpression)
                            ? QueryCompilationContext.NotTranslatedExpression
                            : visitedBinaryExpression);
                }
            }

            return(visitedExpression);
        }
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments, IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            if (instance != null && instance.Type.IsGenericList() && method.Name == "get_Item" && arguments.Count == 1)
            {
                // Try translating indexing inside json column
                return(_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType));
            }

            // Predicate-less Any - translate to a simple length check.
            if (method.IsClosedFormOf(_enumerableAnyWithoutPredicate) &&
                arguments.Count == 1 &&
                arguments[0].Type.TryGetElementType(out _) &&
                arguments[0].TypeMapping is MySqlJsonTypeMapping)
            {
                return(_sqlExpressionFactory.GreaterThan(
                           _jsonPocoTranslator.TranslateArrayLength(arguments[0]),
                           _sqlExpressionFactory.Constant(0)));
            }

            if (method.DeclaringType != typeof(JsonElement) ||
                !(instance.TypeMapping is MySqlJsonTypeMapping mapping))
            {
                return(null);
            }

            // The root of the JSON expression is a ColumnExpression. We wrap that with an empty traversal
            // expression (col->'$'); subsequent traversals will gradually append the path into that.
            // Note that it's possible to call methods such as GetString() directly on the root, and the
            // empty traversal is necessary to properly convert it to a text.
            instance = instance is ColumnExpression columnExpression
                ? _sqlExpressionFactory.JsonTraversal(
                columnExpression, returnsText : false, typeof(string), mapping)
                : instance;

            if (method == _getProperty)
            {
                return(instance is MySqlJsonTraversalExpression prevPathTraversal
                    ? prevPathTraversal.Append(
                           ApplyPathLocationTypeMapping(arguments[0]),
                           typeof(JsonElement),
                           _typeMappingSource.FindMapping(typeof(JsonElement)))
                    : null);
            }

            if (method == _arrayIndexer)
            {
                return(instance is MySqlJsonTraversalExpression prevPathTraversal
                    ? prevPathTraversal.Append(
                           _sqlExpressionFactory.JsonArrayIndex(ApplyPathLocationTypeMapping(arguments[0])),
                           typeof(JsonElement),
                           _typeMappingSource.FindMapping(typeof(JsonElement)))
                    : null);
            }

            if (_getMethods.Contains(method.Name) &&
                arguments.Count == 0 &&
                instance is MySqlJsonTraversalExpression traversal)
            {
                return(ConvertFromJsonExtract(
                           traversal.Clone(
                               method.Name == nameof(JsonElement.GetString),
                               method.ReturnType,
                               _typeMappingSource.FindMapping(method.ReturnType)
                               ),
                           method.ReturnType));
            }

            if (method == _getArrayLength)
            {
                // Could return NULL if the path is not found, but we would be alright to throw then.
                return(_sqlExpressionFactory.NullableFunction(
                           "JSON_LENGTH",
                           new[] { instance },
                           typeof(int),
                           false));
            }

            if (method.Name.StartsWith("TryGet") && arguments.Count == 0)
            {
                throw new InvalidOperationException($"The TryGet* methods on {nameof(JsonElement)} aren't translated yet, use Get* instead.'");
            }

            return(null);
        }
        protected override Expression VisitBinary(BinaryExpression binaryExpression)
        {
            if (binaryExpression.NodeType == ExpressionType.ArrayIndex)
            {
                if (TranslationFailed(binaryExpression.Left, Visit(TryRemoveImplicitConvert(binaryExpression.Left)), out var sqlLeft) ||
                    TranslationFailed(binaryExpression.Right, Visit(TryRemoveImplicitConvert(binaryExpression.Right)), out var sqlRight))
                {
                    return(null);
                }

                // Try translating ArrayIndex inside json column
                return(_jsonPocoTranslator.TranslateMemberAccess(
                           sqlLeft,
                           _sqlExpressionFactory.JsonArrayIndex(sqlRight),
                           binaryExpression.Type));
            }

            var visitedExpression = (SqlExpression)base.VisitBinary(binaryExpression);

            if (visitedExpression == null)
            {
                return(null);
            }

            if (visitedExpression is SqlBinaryExpression visitedBinaryExpression)
            {
                // Returning null forces client projection.
                // CHECK: Is this still true in .NET Core 3.0?
                switch (visitedBinaryExpression.OperatorType)
                {
                case ExpressionType.Add:
                case ExpressionType.Subtract:
                case ExpressionType.Multiply:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                    return(IsDateTimeBasedOperation(visitedBinaryExpression)
                            ? null
                            : visitedBinaryExpression);
                }

                // The following section might not be needed. Lets keep it for a bit, until we are sure.

                /*
                 * // When comparing a JSON value to some string value or when assigning a string value
                 * // to a JSON column, convert the string value to JSON first.
                 * // Also explicitly convert parameter instances to JSON.
                 * if (visitedBinaryExpression.Left.TypeMapping?.StoreType == "json" &&
                 *  visitedBinaryExpression.Right.TypeMapping?.StoreType != "json" &&
                 *  visitedBinaryExpression.Right.TypeMapping?.ClrType == typeof(string))
                 * {
                 *  visitedExpression = _sqlExpressionFactory.MakeBinary(
                 *      visitedBinaryExpression.OperatorType,
                 *      visitedBinaryExpression.Left,
                 *      _sqlExpressionFactory.Convert(
                 *          visitedBinaryExpression.Right,
                 *          visitedBinaryExpression.Right.Type,
                 *          _jsonTypeMapping),
                 *      _jsonTypeMapping);
                 * }
                 * else if (visitedBinaryExpression.Right.TypeMapping?.StoreType == "json" &&
                 *       visitedBinaryExpression.Left.TypeMapping?.StoreType != "json" &&
                 *       visitedBinaryExpression.Left.TypeMapping?.ClrType == typeof(string) &&
                 *       visitedBinaryExpression.OperatorType != ExpressionType.Assign)
                 * {
                 *  visitedExpression = _sqlExpressionFactory.MakeBinary(
                 *      visitedBinaryExpression.OperatorType,
                 *      _sqlExpressionFactory.Convert(
                 *          visitedBinaryExpression.Left,
                 *          visitedBinaryExpression.Left.Type,
                 *          _jsonTypeMapping),
                 *      visitedBinaryExpression.Right,
                 *      _jsonTypeMapping);
                 * }
                 */
            }

            return(visitedExpression);
        }
Пример #6
0
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            var traversal = GetTraversalExpression(instance, arguments);

            if (traversal == null)
            {
                return(null);
            }

            if (typeof(JToken).IsAssignableFrom(method.DeclaringType) &&
                method.Name == "get_Item" &&
                arguments.Count == 1)
            {
                var indexExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[0]);

                if (method.DeclaringType == typeof(JArray) ||
                    indexExpression.Type == typeof(int))
                {
                    // Try translating indexing inside json column.
                    return(_jsonPocoTranslator.TranslateMemberAccess(
                               traversal,
                               _sqlExpressionFactory.JsonArrayIndex(indexExpression),
                               method.ReturnType));
                }

                return(traversal.Append(
                           ApplyPathLocationTypeMapping(arguments[0]),
                           method.DeclaringType,
                           _typeMappingSource.FindMapping(method.DeclaringType)));
            }

            // Support for .Value<T>() and .Value<U, T>():
            if (instance == null &&
                method.Name == nameof(global::Newtonsoft.Json.Linq.Extensions.Value) &&
                method.DeclaringType == typeof(global::Newtonsoft.Json.Linq.Extensions) &&
                method.IsGenericMethod &&
                method.GetParameters().Length == 1 &&
                arguments.Count == 1)
            {
                return(ConvertFromJsonExtract(
                           traversal.Clone(
                               method.ReturnType == typeof(string),
                               method.ReturnType,
                               _typeMappingSource.FindMapping(method.ReturnType)
                               ),
                           method.ReturnType));
            }

            // Support for Count()
            if (instance == null &&
                method.Name == nameof(Enumerable.Count) &&
                method.DeclaringType == typeof(Enumerable) &&
                method.IsGenericMethod &&
                method.GetParameters().Length == 1 &&
                arguments.Count == 1)
            {
                return(_jsonPocoTranslator.TranslateArrayLength(traversal));
            }

            // Predicate-less Any - translate to a simple length check.
            if (method.IsClosedFormOf(_enumerableAnyWithoutPredicate) &&
                arguments.Count == 1 &&
                arguments[0].Type.TryGetElementType(out _) &&
                arguments[0].TypeMapping is MySqlJsonTypeMapping)
            {
                return(_sqlExpressionFactory.GreaterThan(
                           _jsonPocoTranslator.TranslateArrayLength(arguments[0]),
                           _sqlExpressionFactory.Constant(0)));
            }

            return(null);
        }