public virtual SqlExpression Translate(
            SqlExpression instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));
            Check.NotNull(logger, nameof(logger));

            if (method.IsGenericMethod &&
                method.GetGenericMethodDefinition().Equals(_containsMethod) &&
                arguments[0].Type == typeof(byte[]))
            {
                var source            = arguments[0];
                var sourceTypeMapping = source.TypeMapping;

                var value = arguments[1] is SqlConstantExpression constantValue
                    ? (SqlExpression)_sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value }, sourceTypeMapping)
                    : _sqlExpressionFactory.Convert(arguments[1], typeof(byte[]), sourceTypeMapping);

                return(_sqlExpressionFactory.GreaterThan(
                           _sqlExpressionFactory.NullableFunction(
                               "LOCATE",
                               new[] { value, source },
                               typeof(int)),
                           _sqlExpressionFactory.Constant(0)));
            }

            return(null);
        }
Пример #2
0
        private SqlExpression MakeContainsExpressionImpl(
            SqlExpression target,
            [NotNull] Func <SqlExpression, SqlExpression> targetTransform,
            SqlExpression pattern,
            [NotNull] Func <SqlExpression, SqlExpression> patternTransform)
        {
            var stringTypeMapping = ExpressionExtensions.InferTypeMapping(target, pattern);

            target  = _sqlExpressionFactory.ApplyTypeMapping(target, stringTypeMapping);
            pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

            if (pattern is SqlConstantExpression constantPatternExpression)
            {
                // The prefix is constant. Aside from null or empty, we escape all special characters (%, _, \)
                // in C# and send a simple LIKE.
                if (constantPatternExpression.Value is string constantPatternString)
                {
                    // TRUE (pattern == "")
                    // something LIKE '%foo%' (pattern != "")
                    return(constantPatternString == string.Empty
                        ? (SqlExpression)_sqlExpressionFactory.Constant(true)
                        : _sqlExpressionFactory.Like(
                               targetTransform(target),
                               patternTransform(_sqlExpressionFactory.Constant('%' + EscapeLikePattern(constantPatternString) + '%'))));
                }

                // TODO: EF Core 5
                // https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/996#issuecomment-607876040
                // Can return NULL in .NET 5 after https://github.com/dotnet/efcore/issues/20498 has been fixed.
                // `something LIKE NULL` always returns `NULL`. We will return `false`, to indicate, that no match
                // could be found, because returning a constant of `NULL` will throw later in EF Core when used as
                // a predicate.
                // return _sqlExpressionFactory.Constant(null, RelationalTypeMapping.NullMapping);
                // This results in NULL anyway, but works around EF Core's inability to handle predicates that are
                // constant null values.
                return(_sqlExpressionFactory.Like(target, _sqlExpressionFactory.Constant(null, stringTypeMapping)));
            }

            // 'foo' LIKE '' OR LOCATE('foo', 'barfoobar') > 0
            // This cannot be "'   ' = '' OR ..", because '   ' would be trimmed to '' when using equals, but not when using LIKE.
            // Using an empty pattern `LOCATE('', 'barfoobar')` returns 1.
            return(_sqlExpressionFactory.OrElse(
                       _sqlExpressionFactory.Like(
                           pattern,
                           _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                       _sqlExpressionFactory.GreaterThan(
                           _sqlExpressionFactory.NullableFunction(
                               "LOCATE",
                               new[] { patternTransform(pattern), targetTransform(target) },
                               typeof(int)),
                           _sqlExpressionFactory.Constant(0))));
        }
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            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)));
            }

            return(null);
        }
        private SqlExpression MakeContainsExpressionImpl(
            [NotNull] SqlExpression target,
            [NotNull] SqlExpression search,
            [NotNull] SqlExpression originalSearch)
        {
            var containsExpression = _sqlExpressionFactory.GreaterThan(
                _sqlExpressionFactory.Function("LOCATE", new[] { search, target }, typeof(int), null),
                _sqlExpressionFactory.Constant(0)
                );

            if (originalSearch is SqlConstantExpression constantSuffix)
            {
                return((string)constantSuffix.Value == string.Empty
                    ? _sqlExpressionFactory.Constant(true)
                    : (SqlExpression)containsExpression);
            }
            else
            {
                return(_sqlExpressionFactory.OrElse(
                           containsExpression,
                           _sqlExpressionFactory.Equal(originalSearch, _sqlExpressionFactory.Constant(string.Empty))));
            }
        }
        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);
        }
Пример #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);
        }