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); }
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); }
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); }