Example #1
0
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            SqlExpression modifier = null;

            if (_addMilliseconds.Equals(method))
            {
                modifier = _sqlExpressionFactory.Add(
                    _sqlExpressionFactory.Convert(
                        _sqlExpressionFactory.Divide(
                            arguments[0],
                            _sqlExpressionFactory.Constant(1000.0)),
                        typeof(string)),
                    _sqlExpressionFactory.Constant(" seconds"));
            }
            else if (_addTicks.Equals(method))
            {
                modifier = _sqlExpressionFactory.Add(
                    _sqlExpressionFactory.Convert(
                        _sqlExpressionFactory.Divide(
                            arguments[0],
                            _sqlExpressionFactory.Constant((double)TimeSpan.TicksPerDay)),
                        typeof(string)),
                    _sqlExpressionFactory.Constant(" seconds"));
            }
            else if (_methodInfoToUnitSuffix.TryGetValue(method, out var unitSuffix))
            {
                modifier = _sqlExpressionFactory.Add(
                    _sqlExpressionFactory.Convert(arguments[0], typeof(string)),
                    _sqlExpressionFactory.Constant(unitSuffix));
            }

            if (modifier != null)
            {
                return(_sqlExpressionFactory.Function(
                           "rtrim",
                           new SqlExpression[]
                {
                    _sqlExpressionFactory.Function(
                        "rtrim",
                        new SqlExpression[]
                    {
                        SqliteExpression.Strftime(
                            _sqlExpressionFactory,
                            method.ReturnType,
                            "%Y-%m-%d %H:%M:%f",
                            instance,
                            new[] { modifier }),
                        _sqlExpressionFactory.Constant("0")
                    },
                        method.ReturnType),
                    _sqlExpressionFactory.Constant(".")
                },
                           method.ReturnType));
            }

            return(null);
        }
        public virtual SqlExpression TranslateAverage([NotNull] Expression expression)
        {
            Check.NotNull(expression, nameof(expression));

            if (!(expression is SqlExpression sqlExpression))
            {
                sqlExpression = Translate(expression);
            }

            if (sqlExpression == null)
            {
                throw new InvalidOperationException(CoreStrings.TranslationFailed(expression.Print()));
            }

            var inputType = sqlExpression.Type.UnwrapNullableType();

            if (inputType == typeof(int) ||
                inputType == typeof(long))
            {
                sqlExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(
                    _sqlExpressionFactory.Convert(sqlExpression, typeof(double)));
            }

            return(inputType == typeof(float)
                ? _sqlExpressionFactory.Convert(
                       _sqlExpressionFactory.Function(
                           "AVG", new[] { sqlExpression }, typeof(double)),
                       sqlExpression.Type,
                       sqlExpression.TypeMapping)
                : (SqlExpression)_sqlExpressionFactory.Function(
                       "AVG", new[] { sqlExpression }, sqlExpression.Type, sqlExpression.TypeMapping));
        }
        public virtual SqlExpression TranslateAverage(Expression expression)
        {
            if (!(expression is SqlExpression sqlExpression))
            {
                sqlExpression = Translate(expression);
            }

            var inputType = sqlExpression.Type.UnwrapNullableType();

            if (inputType == typeof(int) ||
                inputType == typeof(long))
            {
                sqlExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(
                    _sqlExpressionFactory.Convert(sqlExpression, typeof(double)));
            }

            return(inputType == typeof(float)
                ? _sqlExpressionFactory.Convert(
                       _sqlExpressionFactory.Function(
                           "AVG", new[] { sqlExpression }, typeof(double), null),
                       sqlExpression.Type,
                       sqlExpression.TypeMapping)
                : (SqlExpression)_sqlExpressionFactory.Function(
                       "AVG", new[] { sqlExpression }, sqlExpression.Type, sqlExpression.TypeMapping));
        }
        SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
        {
            var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);

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

            if (pattern is SqlConstantExpression constantExpression)
            {
                // The pattern is constant. Aside from null, we escape all special characters (%, _, \)
                // in C# and send a simple LIKE
                return(constantExpression.Value is string constantPattern
                    ? _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(
                               startsWith
                                ? EscapeLikePattern(constantPattern) + '%'
                                : '%' + EscapeLikePattern(constantPattern)))
                    : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant(null, stringTypeMapping)));
            }

            // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare.
            // For StartsWith we also first run a LIKE to quickly filter out most non-matching results (sargable, but imprecise
            // because of wildchars).
            SqlExpression leftRight = _sqlExpressionFactory.Function(
                startsWith ? "LEFT" : "RIGHT",
                new[]
            {
                instance,
                _sqlExpressionFactory.Function("LENGTH", new[] { pattern }, typeof(int))
            },
                typeof(string),
                stringTypeMapping);

            // LEFT/RIGHT of a citext return a text, so for non-default text mappings we apply an explicit cast.
            if (instance.TypeMapping != _textTypeMapping)
            {
                leftRight = _sqlExpressionFactory.Convert(leftRight, typeof(string), instance.TypeMapping);
            }

            // Also add an explicit cast on the pattern; this is only required because of
            // The following is only needed because of https://github.com/aspnet/EntityFrameworkCore/issues/19120
            var castPattern = pattern.TypeMapping == _textTypeMapping
                ? pattern
                : _sqlExpressionFactory.Convert(pattern, typeof(string), pattern.TypeMapping);

            return(startsWith
                ? _sqlExpressionFactory.AndAlso(
                       _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Add(
                               pattern,
                               _sqlExpressionFactory.Constant("%"))),
                       _sqlExpressionFactory.Equal(leftRight, castPattern))
                : _sqlExpressionFactory.Equal(leftRight, castPattern));
        }
Example #5
0
        /// <inheritdoc />
        public virtual SqlExpression Translate(
            SqlExpression instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            if (!MethodInfoDatePartMapping.TryGetValue(method, out var datePart))
            {
                return(null);
            }

            var interval = arguments[0];

            if (instance is null || interval is null)
            {
                return(null);
            }

            // Note: ideally we'd simply generate a PostgreSQL interval expression, but the .NET mapping of that is TimeSpan,
            // which does not work for months, years, etc. So we generate special fragments instead.
            if (interval is SqlConstantExpression constantExpression)
            {
                // We generate constant intervals as INTERVAL '1 days'
                if (constantExpression.Type == typeof(double) &&
                    ((double)constantExpression.Value >= int.MaxValue ||
                     (double)constantExpression.Value <= int.MinValue))
                {
                    return(null);
                }

                interval = _sqlExpressionFactory.Fragment(FormattableString.Invariant($"INTERVAL '{constantExpression.Value} {datePart}'"));
            }
            else
            {
                // For non-constants, we can't parameterize INTERVAL '1 days'. Instead, we use CAST($1 || ' days' AS interval).
                // Note that a make_interval() function also exists, but accepts only int (for all fields except for
                // seconds), so we don't use it.
                // Note: we instantiate SqlBinaryExpression manually rather than via sqlExpressionFactory because
                // of the non-standard Add expression (concatenate int with text)
                interval = _sqlExpressionFactory.Convert(
                    new SqlBinaryExpression(
                        ExpressionType.Add,
                        _sqlExpressionFactory.Convert(interval, typeof(string), _textMapping),
                        _sqlExpressionFactory.Constant(' ' + datePart, _textMapping),
                        typeof(string),
                        _textMapping),
                    typeof(TimeSpan),
                    _intervalMapping);
            }

            return(_sqlExpressionFactory.Add(instance, interval, instance.TypeMapping));
        }
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            if (method == IPAddressParse)
            {
                return(_sqlExpressionFactory.Convert(arguments[0], typeof(IPAddress), _sqlExpressionFactory.FindMapping(typeof(IPAddress))));
            }

            if (method == PhysicalAddressParse)
            {
                return(_sqlExpressionFactory.Convert(arguments[0], typeof(PhysicalAddress), _sqlExpressionFactory.FindMapping(typeof(PhysicalAddress))));
            }

            if (method.DeclaringType != typeof(NpgsqlNetworkExtensions))
            {
                return(null);
            }

            return(method.Name switch
            {
                nameof(NpgsqlNetworkExtensions.LessThan) => _sqlExpressionFactory.LessThan(arguments[1], arguments[2]),
                nameof(NpgsqlNetworkExtensions.LessThanOrEqual) => _sqlExpressionFactory.LessThanOrEqual(arguments[1], arguments[2]),
                nameof(NpgsqlNetworkExtensions.GreaterThanOrEqual) => _sqlExpressionFactory.GreaterThanOrEqual(arguments[1], arguments[2]),
                nameof(NpgsqlNetworkExtensions.GreaterThan) => _sqlExpressionFactory.GreaterThan(arguments[1], arguments[2]),

                nameof(NpgsqlNetworkExtensions.ContainedBy) => BoolReturningOnTwoNetworkTypes("<<"),
                nameof(NpgsqlNetworkExtensions.ContainedByOrEqual) => BoolReturningOnTwoNetworkTypes("<<="),
                nameof(NpgsqlNetworkExtensions.Contains) => BoolReturningOnTwoNetworkTypes(">>"),
                nameof(NpgsqlNetworkExtensions.ContainsOrEqual) => BoolReturningOnTwoNetworkTypes(">>="),
                nameof(NpgsqlNetworkExtensions.ContainsOrContainedBy) => BoolReturningOnTwoNetworkTypes("&&"),

                // TODO: Hack, see #1118
                nameof(NpgsqlNetworkExtensions.BitwiseNot) => new SqlUnaryExpression(ExpressionType.Negate,
                                                                                     arguments[1],
                                                                                     arguments[1].Type,
                                                                                     arguments[1].TypeMapping),

                nameof(NpgsqlNetworkExtensions.BitwiseAnd) => _sqlExpressionFactory.And(arguments[1], arguments[2]),
                nameof(NpgsqlNetworkExtensions.BitwiseOr) => _sqlExpressionFactory.Or(arguments[1], arguments[2]),

                // Add/Subtract accept inet + int, so we can't use the default type mapping inference logic which assumes
                // same-typed operands
                nameof(NpgsqlNetworkExtensions.Add)
                => new SqlBinaryExpression(
                    ExpressionType.Add,
                    _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]),
                    _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]),
                    arguments[1].Type,
                    arguments[1].TypeMapping),

                nameof(NpgsqlNetworkExtensions.Subtract) when arguments[2].Type == typeof(int)
    private SqlExpression TranslateIndexOf(
        SqlExpression instance,
        MethodInfo method,
        SqlExpression searchExpression,
        SqlExpression?startIndex)
    {
        var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, searchExpression) !;

        searchExpression = _sqlExpressionFactory.ApplyTypeMapping(searchExpression, stringTypeMapping);
        instance         = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);

        var charIndexArguments = new List <SqlExpression> {
            searchExpression, instance
        };

        if (startIndex is not null)
        {
            charIndexArguments.Add(_sqlExpressionFactory.Add(startIndex, _sqlExpressionFactory.Constant(1)));
        }

        var argumentsPropagateNullability = Enumerable.Repeat(true, charIndexArguments.Count);

        SqlExpression charIndexExpression;
        var           storeType = stringTypeMapping.StoreType;

        if (string.Equals(storeType, "nvarchar(max)", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(storeType, "varchar(max)", StringComparison.OrdinalIgnoreCase))
        {
            charIndexExpression = _sqlExpressionFactory.Function(
                "CHARINDEX",
                charIndexArguments,
                nullable: true,
                argumentsPropagateNullability,
                typeof(long));

            charIndexExpression = _sqlExpressionFactory.Convert(charIndexExpression, typeof(int));
        }
        else
        {
            charIndexExpression = _sqlExpressionFactory.Function(
                "CHARINDEX",
                charIndexArguments,
                nullable: true,
                argumentsPropagateNullability,
                method.ReturnType);
        }

        charIndexExpression = _sqlExpressionFactory.Subtract(charIndexExpression, _sqlExpressionFactory.Constant(1));

        return(_sqlExpressionFactory.Case(
                   new[]
        {
            new CaseWhenClause(
                _sqlExpressionFactory.Equal(
                    searchExpression,
                    _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                _sqlExpressionFactory.Constant(0))
        },
                   charIndexExpression));
    }
        /// <summary>
        ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
        ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
        ///     any release. You should only use it directly in your code with extreme caution and knowing that
        ///     doing so can result in application failures when updating to a new Entity Framework Core release.
        /// </summary>
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            if (_methodInfoDataLengthMapping.Contains(method))
            {
                var argument = arguments[1];
                if (argument.TypeMapping == null)
                {
                    argument = _sqlExpressionFactory.ApplyDefaultTypeMapping(argument);
                }

                if (_longReturningTypes.Contains(argument.TypeMapping.StoreType))
                {
                    var result = _sqlExpressionFactory.Function(
                        "DATALENGTH",
                        arguments.Skip(1),
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        typeof(long));

                    return(_sqlExpressionFactory.Convert(result, method.ReturnType.UnwrapNullableType()));
                }

                return(_sqlExpressionFactory.Function(
                           "DATALENGTH",
                           arguments.Skip(1),
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           method.ReturnType.UnwrapNullableType()));
            }

            return(null);
        }
Example #9
0
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            if (_methodInfoDatePartMapping.TryGetValue(method, out var datePart))
            {
                // DateAdd does not accept number argument outside of int range
                // AddYears/AddMonths take int argument so no need to check for range
                return(!datePart.Equals("year") &&
                       !datePart.Equals("month") &&
                       arguments[0] is SqlConstantExpression sqlConstant &&
                       ((double)sqlConstant.Value >= int.MaxValue ||
                        (double)sqlConstant.Value <= int.MinValue)
                        ? null
                        : _sqlExpressionFactory.Function(
                           "DATEADD",
                           new[]
                {
                    _sqlExpressionFactory.Fragment(datePart),
                    _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
                    instance
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { false, true, true },
                           instance.Type,
                           instance.TypeMapping));
            }

            return(null);
        }
Example #10
0
        /// <summary>
        ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
        ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
        ///     any release. You should only use it directly in your code with extreme caution and knowing that
        ///     doing so can result in application failures when updating to a new Entity Framework Core release.
        /// </summary>
        public virtual SqlExpression?Translate(
            SqlExpression?instance,
            MemberInfo member,
            Type returnType,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            Check.NotNull(member, nameof(member));
            Check.NotNull(returnType, nameof(returnType));
            Check.NotNull(logger, nameof(logger));

            if (member.Name == nameof(string.Length) &&
                instance?.Type == typeof(string))
            {
                return(_sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.Function(
                               "LEN",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               typeof(long)),
                           returnType));
            }

            return(null);
        }
Example #11
0
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            if (_methodInfoDatePartMapping.TryGetValue(method, out var datePart))
            {
                return(!datePart.Equals("year") &&
                       !datePart.Equals("month") &&
                       arguments[0] is SqlConstantExpression sqlConstant &&
                       ((double)sqlConstant.Value >= int.MaxValue ||
                        (double)sqlConstant.Value <= int.MinValue)
                        ? null
                        : _sqlExpressionFactory.Function(
                           "DATEADD",
                           new[]
                {
                    _sqlExpressionFactory.Fragment(datePart),
                    _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
                    instance
                },
                           instance.Type,
                           instance.TypeMapping));
            }

            return(null);
        }
Example #12
0
        protected override Expression VisitUnary(UnaryExpression unaryExpression)
        {
            var operand = Visit(unaryExpression.Operand);

            if (TranslationFailed(unaryExpression.Operand, operand))
            {
                return(null);
            }

            var sqlOperand = (SqlExpression)operand;

            switch (unaryExpression.NodeType)
            {
            case ExpressionType.Not:
                return(_sqlExpressionFactory.Not(sqlOperand));

            case ExpressionType.Negate:
                return(_sqlExpressionFactory.Negate(sqlOperand));

            case ExpressionType.Convert:
                // Object convert needs to be converted to explicit cast when mismatching types
                if (operand.Type.IsInterface &&
                    unaryExpression.Type.GetInterfaces().Any(e => e == operand.Type) ||
                    unaryExpression.Type.UnwrapNullableType() == operand.Type ||
                    unaryExpression.Type.UnwrapNullableType() == typeof(Enum))
                {
                    return(sqlOperand);
                }
                sqlOperand = _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand);

                return(_sqlExpressionFactory.Convert(sqlOperand, unaryExpression.Type));
            }

            return(null);
        }
        /// <summary>
        ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
        ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
        ///     any release. You should only use it directly in your code with extreme caution and knowing that
        ///     doing so can result in application failures when updating to a new Entity Framework Core release.
        /// </summary>
        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(EnumerableMethods.Contains) &&
                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.Function(
                               "CHARINDEX",
                               new[] { value, source },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true },
                               typeof(int)),
                           _sqlExpressionFactory.Constant(0)));
            }

            return(null);
        }
 public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments, IDiagnosticsLogger<DbLoggerCategory.Query> logger)
 {
     return _supportedMethods.Contains(method)
       ? _sqlExpressionFactory.Convert(
       arguments[0],
       method.ReturnType)
       : null;
 }
Example #15
0
 public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
 {
     return(_supportedMethods.Contains(method)
   ? _sqlExpressionFactory.Convert(
                arguments[0],
                method.ReturnType)
   : null);
 }
Example #16
0
        internal static SqlExpression Strftime(this ISqlExpressionFactory factory, Type returnType, string format, SqlExpression timestring, IEnumerable <SqlExpression>?modifiers = null)
        {
            var result = factory.DateFunction("strftime", GetArguments(timestring, modifiers).Insert(0, factory.Constant(format)), timestring.Type);

            if (timestring.Type != returnType)
            {
                result = factory.Convert(result, returnType);
            }
            return(result);
        }
        protected override ShapedQueryExpression TranslateAverage(ShapedQueryExpression source, LambdaExpression selector, Type resultType)
        {
            var selectExpression = (SelectExpression)source.QueryExpression;

            if (selectExpression.Limit != null ||
                selectExpression.Offset != null)
            {
                selectExpression.PushdownIntoSubQuery();
            }

            if (selector != null)
            {
                source = TranslateSelect(source, selector);
            }

            var projection = (SqlExpression)selectExpression.GetProjectionExpression(new ProjectionMember());

            var inputType = projection.Type.UnwrapNullableType();

            if (inputType == typeof(int) ||
                inputType == typeof(long))
            {
                projection = _sqlExpressionFactory.ApplyDefaultTypeMapping(
                    _sqlExpressionFactory.Convert(projection, typeof(double)));
            }

            if (inputType == typeof(float))
            {
                projection = _sqlExpressionFactory.Convert(
                    _sqlExpressionFactory.Function(
                        "AVG", new[] { projection }, typeof(double), null),
                    projection.Type,
                    projection.TypeMapping);
            }
            else
            {
                projection = _sqlExpressionFactory.Function(
                    "AVG", new[] { projection }, projection.Type, projection.TypeMapping);
            }

            return(AggregateResultShaper(source, projection, throwOnNullResult: true, resultType));
        }
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            var password = _expressionFactory.Constant(Password);

            if (method == _nonTranslatableEncryptMethod || method == __nonTranslatableDecryptMethod)
            {
                throw new Exception("Dont use this this will not translate to SQL. Use Decrypt() or Encrypt()");
            }
            if (method == _encryptStringMethod)
            {
                var value = arguments[0];

                var aesToHexExpression = AESEncryptionBuild(instance, password, value);

                return(aesToHexExpression);
            }
            if (method == _encryptMethod)
            {
                var value = arguments[1];

                var aesToHexExpression = AESEncryptionBuild(instance, password, value);

                return(aesToHexExpression);
            }

            if (method == _decryptMethod)
            {
                var value = arguments[1];

                var aesDecryptExpression = AesDecryptExpression(instance, password, value);
                return(_expressionFactory.Convert(aesDecryptExpression, typeof(string)));
            }
            if (method == _decryptStringMethod)
            {
                var value = arguments[0];

                var aesDecryptExpression = AesDecryptExpression(instance, password, value);
                return(_expressionFactory.Convert(aesDecryptExpression, typeof(string)));
            }
            return(null);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType)
        {
            if (member.Name == nameof(string.Length) &&
                instance?.Type == typeof(string))
            {
                return(_sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.Function("LEN", new[] { instance }, typeof(long)),
                           returnType));
            }

            return(null);
        }
Example #20
0
 public virtual SqlExpression Translate(
     SqlExpression instance,
     MethodInfo method,
     IReadOnlyList <SqlExpression> arguments,
     IDiagnosticsLogger <DbLoggerCategory.Query> logger)
 {
     // Translates parameterless Object.ToString() calls.
     return(method.Name == nameof(ToString) &&
            arguments.Count == 0 &&
            instance != null &&
            _supportedTypes.Contains(instance.Type.UnwrapNullableType())
         ? _sqlExpressionFactory.Convert(instance, typeof(string))
         : null);
 }
 /// <summary>
 ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
 ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
 ///     any release. You should only use it directly in your code with extreme caution and knowing that
 ///     doing so can result in application failures when updating to a new Entity Framework Core release.
 /// </summary>
 public virtual SqlExpression?Translate(
     SqlExpression?instance,
     MethodInfo method,
     IReadOnlyList <SqlExpression> arguments,
     IDiagnosticsLogger <DbLoggerCategory.Query> logger)
 => _methodInfo.Equals(method)
         ? _sqlExpressionFactory.Convert(
     _sqlExpressionFactory.Function(
         "ISDATE",
         new[] { arguments[1] },
         nullable: true,
         argumentsPropagateNullability: new[] { true },
         _methodInfo.ReturnType),
     _methodInfo.ReturnType)
         : null;
Example #22
0
        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 (_methodInfoDatePartMapping.TryGetValue(method, out var datePart) &&
                instance != null)
            {
                // DateAdd does not accept number argument outside of int range
                // AddYears/AddMonths take int argument so no need to check for range
                if (datePart != "year" &&
                    datePart != "month" &&
                    arguments[0] is SqlConstantExpression sqlConstant &&
                    sqlConstant.Value is double doubleValue &&
                    (doubleValue >= int.MaxValue ||
                     doubleValue <= int.MinValue))
                {
                    return(null);
                }

                if (instance is SqlConstantExpression instanceConstant)
                {
                    instance = instanceConstant.ApplyTypeMapping(_typeMappingSource.FindMapping(typeof(DateTime), "datetime"));
                }

                return(_sqlExpressionFactory.Function(
                           "DATEADD",
                           new[]
                {
                    _sqlExpressionFactory.Fragment(datePart),
                    _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
                    instance
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { false, true, true },
                           instance.Type,
                           instance.TypeMapping));
            }

            return(null);
        }
        public virtual SqlExpression Translate(
            SqlExpression instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            if (instance == null ||
                method.Name != nameof(ToString) ||
                arguments.Count != 0)
            {
                return(null);
            }

            if (instance.Type == typeof(bool))
            {
                return(instance is ColumnExpression columnExpression &&
                       columnExpression.IsNullable
                    ? _sqlExpressionFactory.Case(
                           new[]
                {
                    new CaseWhenClause(
                        _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)),
                        _sqlExpressionFactory.Constant(false.ToString())),
                    new CaseWhenClause(
                        _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)),
                        _sqlExpressionFactory.Constant(true.ToString()))
                },
                           _sqlExpressionFactory.Constant(null))
                    : _sqlExpressionFactory.Case(
                           new[]
                {
                    new CaseWhenClause(
                        _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)),
                        _sqlExpressionFactory.Constant(false.ToString()))
                },
                           _sqlExpressionFactory.Constant(true.ToString())));
            }

            // Translates parameterless Object.ToString() calls.
            return(_supportedTypes.Contains(instance.Type.UnwrapNullableType())
                ? _sqlExpressionFactory.Convert(instance, typeof(string))
                : null);
        }
        //protected override Expression VisitNew(NewExpression newExpression)
        //{
        //    if (newExpression.Members == null
        //        || newExpression.Arguments.Count == 0)
        //    {
        //        return null;
        //    }

        //    var bindings = new Expression[newExpression.Arguments.Count];

        //    for (var i = 0; i < bindings.Length; i++)
        //    {
        //        var translation = Visit(newExpression.Arguments[i]);

        //        if (translation == null)
        //        {
        //            return null;
        //        }

        //        bindings[i] = translation;
        //    }

        //    return Expression.Constant(bindings);
        //}

        protected override Expression VisitUnary(UnaryExpression unaryExpression)
        {
            var operand = Visit(unaryExpression.Operand);

            if (TranslationFailed(unaryExpression.Operand, operand))
            {
                return(null);
            }

            // In certain cases EF.Property would have convert node around the source.
            if (operand is EntityShaperExpression &&
                unaryExpression.Type == typeof(object) &&
                unaryExpression.NodeType == ExpressionType.Convert)
            {
                return(operand);
            }

            var sqlOperand = (SqlExpression)operand;

            if (unaryExpression.NodeType == ExpressionType.Convert)
            {
                // Object convert needs to be converted to explicit cast when mismatching types
                if (operand.Type.IsInterface &&
                    unaryExpression.Type.GetInterfaces().Any(e => e == operand.Type) ||
                    unaryExpression.Type.UnwrapNullableType() == operand.Type ||
                    unaryExpression.Type.UnwrapNullableType() == typeof(Enum))
                {
                    return(sqlOperand);
                }

                sqlOperand = _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand);

                return(_sqlExpressionFactory.Convert(sqlOperand, unaryExpression.Type));
            }

            if (unaryExpression.NodeType == ExpressionType.Not)
            {
                return(_sqlExpressionFactory.Not(sqlOperand));
            }

            return(null);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            if (method.IsGenericMethod &&
                method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains) &&
                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.Function("CHARINDEX", new[] { value, source }, typeof(int)),
                           _sqlExpressionFactory.Constant(0)));
            }

            return(null);
        }
Example #26
0
        public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType)
        {
            Check.NotNull(member, nameof(member));
            Check.NotNull(returnType, nameof(returnType));

            if (member.Name == nameof(string.Length) &&
                instance?.Type == typeof(string))
            {
                return(_sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.Function(
                               "LEN",
                               new[] { instance },
                               nullResultAllowed: true,
                               argumentsPropagateNullability: new[] { true },
                               typeof(long)),
                           returnType));
            }

            return(null);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType, IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            member.ThrowIfNull(nameof(member));
            returnType.ThrowIfNull(nameof(returnType));
            logger.ThrowIfNull(nameof(logger));

            if (member.Name == nameof(string.Length) &&
                instance?.Type == typeof(string))
            {
                return(_sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.Function(
                               "LEN",
                               new[] { instance },
                               true,
                               new[] { true },
                               typeof(int)),
                           returnType));
            }

            return(null);
        }
        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 (instance != null)
            {
                if (_indexOfMethodInfo.Equals(method))
                {
                    var argument          = arguments[0];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument) !;
                    argument = _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping);

                    SqlExpression charIndexExpression;
                    var           storeType = stringTypeMapping.StoreType;
                    if (string.Equals(storeType, "nvarchar(max)", StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(storeType, "varchar(max)", StringComparison.OrdinalIgnoreCase))
                    {
                        charIndexExpression = _sqlExpressionFactory.Function(
                            "CHARINDEX",
                            new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true, true },
                            typeof(long));

                        charIndexExpression = _sqlExpressionFactory.Convert(charIndexExpression, typeof(int));
                    }
                    else
                    {
                        charIndexExpression = _sqlExpressionFactory.Function(
                            "CHARINDEX",
                            new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true, true },
                            method.ReturnType);
                    }

                    charIndexExpression = _sqlExpressionFactory.Subtract(charIndexExpression, _sqlExpressionFactory.Constant(1));

                    return(_sqlExpressionFactory.Case(
                               new[]
                    {
                        new CaseWhenClause(
                            _sqlExpressionFactory.Equal(
                                argument,
                                _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                            _sqlExpressionFactory.Constant(0))
                    },
                               charIndexExpression));
                }

                if (_replaceMethodInfo.Equals(method))
                {
                    var firstArgument     = arguments[0];
                    var secondArgument    = arguments[1];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);

                    instance       = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                    firstArgument  = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping);
                    secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping);

                    return(_sqlExpressionFactory.Function(
                               "REPLACE",
                               new[] { instance, firstArgument, secondArgument },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true, true },
                               method.ReturnType,
                               stringTypeMapping));
                }

                if (_toLowerMethodInfo.Equals(method) ||
                    _toUpperMethodInfo.Equals(method))
                {
                    return(_sqlExpressionFactory.Function(
                               _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               method.ReturnType,
                               instance.TypeMapping));
                }

                if (_substringMethodInfo.Equals(method))
                {
                    return(_sqlExpressionFactory.Function(
                               "SUBSTRING",
                               new[]
                    {
                        instance,
                        _sqlExpressionFactory.Add(
                            arguments[0],
                            _sqlExpressionFactory.Constant(1)),
                        arguments[1]
                    },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true, true },
                               method.ReturnType,
                               instance.TypeMapping));
                }

                if (_trimStartMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimStartMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer LTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "LTRIM",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_trimEndMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimEndMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer RTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "RTRIM",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_trimMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer LTRIM/RTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "LTRIM",
                               new[]
                    {
                        _sqlExpressionFactory.Function(
                            "RTRIM",
                            new[] { instance },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true },
                            instance.Type,
                            instance.TypeMapping)
                    },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_containsMethodInfo.Equals(method))
                {
                    var pattern           = arguments[0];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
                    instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                    pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

                    if (pattern is SqlConstantExpression constantPattern)
                    {
                        if (!(constantPattern.Value is string patternValue))
                        {
                            return(_sqlExpressionFactory.Like(
                                       instance,
                                       _sqlExpressionFactory.Constant(null, stringTypeMapping)));
                        }

                        if (patternValue.Length == 0)
                        {
                            return(_sqlExpressionFactory.Constant(true));
                        }

                        return(patternValue.Any(IsLikeWildChar)
                            ? _sqlExpressionFactory.Like(
                                   instance,
                                   _sqlExpressionFactory.Constant($"%{EscapeLikePattern(patternValue)}%"),
                                   _sqlExpressionFactory.Constant(LikeEscapeString))
                            : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant($"%{patternValue}%")));
                    }

                    return(_sqlExpressionFactory.OrElse(
                               _sqlExpressionFactory.Like(
                                   pattern,
                                   _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                               _sqlExpressionFactory.GreaterThan(
                                   _sqlExpressionFactory.Function(
                                       "CHARINDEX",
                                       new[] { pattern, instance },
                                       nullable: true,
                                       argumentsPropagateNullability: new[] { true, true },
                                       typeof(int)),
                                   _sqlExpressionFactory.Constant(0))));
                }

                if (_startsWithMethodInfo.Equals(method))
                {
                    return(TranslateStartsEndsWith(instance, arguments[0], true));
                }

                if (_endsWithMethodInfo.Equals(method))
                {
                    return(TranslateStartsEndsWith(instance, arguments[0], false));
                }
            }

            if (_isNullOrEmptyMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Like(
                               argument,
                               _sqlExpressionFactory.Constant(string.Empty))));
            }

            if (_isNullOrWhiteSpaceMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Equal(
                               argument,
                               _sqlExpressionFactory.Constant(string.Empty, argument.TypeMapping))));
            }

            if (_firstOrDefaultMethodInfoWithoutArgs.Equals(method))
            {
                var argument = arguments[0];
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[] { argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1) },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType));
            }

            if (_lastOrDefaultMethodInfoWithoutArgs.Equals(method))
            {
                var argument = arguments[0];
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[]
                {
                    argument,
                    _sqlExpressionFactory.Function(
                        "LEN",
                        new[] { argument },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        typeof(int)),
                    _sqlExpressionFactory.Constant(1)
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType));
            }

            return(null);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            if (_indexOfMethodInfo.Equals(method))
            {
                var argument          = arguments[0];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument);
                argument = _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping);

                SqlExpression charIndexExpression;
                var           storeType = stringTypeMapping.StoreType;
                if (string.Equals(storeType, "nvarchar(max)", StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(storeType, "varchar(max)", StringComparison.OrdinalIgnoreCase))
                {
                    charIndexExpression = _sqlExpressionFactory.Function(
                        "CHARINDEX",
                        new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                        nullable: true,
                        argumentsPropagateNullability: new [] { true, true },
                        typeof(long));

                    charIndexExpression = _sqlExpressionFactory.Convert(charIndexExpression, typeof(int));
                }
                else
                {
                    charIndexExpression = _sqlExpressionFactory.Function(
                        "CHARINDEX",
                        new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true, true },
                        method.ReturnType);
                }

                charIndexExpression = _sqlExpressionFactory.Subtract(charIndexExpression, _sqlExpressionFactory.Constant(1));

                return(_sqlExpressionFactory.Case(
                           new[]
                {
                    new CaseWhenClause(
                        _sqlExpressionFactory.Equal(
                            argument,
                            _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                        _sqlExpressionFactory.Constant(0))
                },
                           charIndexExpression));
            }

            if (_replaceMethodInfo.Equals(method))
            {
                var firstArgument     = arguments[0];
                var secondArgument    = arguments[1];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);

                instance       = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                firstArgument  = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping);
                secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping);

                return(_sqlExpressionFactory.Function(
                           "REPLACE",
                           new[] { instance, firstArgument, secondArgument },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType,
                           stringTypeMapping));
            }

            if (_toLowerMethodInfo.Equals(method) ||
                _toUpperMethodInfo.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (_substringMethodInfo.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[]
                {
                    instance,
                    _sqlExpressionFactory.Add(
                        arguments[0],
                        _sqlExpressionFactory.Constant(1)),
                    arguments[1]
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (_isNullOrWhiteSpaceMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Equal(
                               _sqlExpressionFactory.Function(
                                   "LTRIM",
                                   new[]
                {
                    _sqlExpressionFactory.Function(
                        "RTRIM",
                        new[] { argument },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        argument.Type,
                        argument.TypeMapping)
                },
                                   nullable: true,
                                   argumentsPropagateNullability: new[] { true },
                                   argument.Type,
                                   argument.TypeMapping),
                               _sqlExpressionFactory.Constant(string.Empty, argument.TypeMapping))));
            }

            if (_trimStartMethodInfoWithoutArgs?.Equals(method) == true ||
                (_trimStartMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer LTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "LTRIM",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (_trimEndMethodInfoWithoutArgs?.Equals(method) == true ||
                (_trimEndMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer RTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "RTRIM",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (_trimMethodInfoWithoutArgs?.Equals(method) == true ||
                (_trimMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer LTRIM/RTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "LTRIM",
                           new[]
                {
                    _sqlExpressionFactory.Function(
                        "RTRIM",
                        new[] { instance },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        instance.Type,
                        instance.TypeMapping)
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (_containsMethodInfo.Equals(method))
            {
                var pattern           = arguments[0];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
                instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

                if (pattern is SqlConstantExpression constantPattern)
                {
                    // Intentionally string.Empty since we don't want to match nulls here.
#pragma warning disable CA1820 // Test for empty strings using string length
                    if ((string)constantPattern.Value == string.Empty)
#pragma warning restore CA1820 // Test for empty strings using string length
                    {
                        return(_sqlExpressionFactory.Constant(true));
                    }

                    return(_sqlExpressionFactory.GreaterThan(
                               _sqlExpressionFactory.Function(
                                   "CHARINDEX",
                                   new[] { pattern, instance },
                                   nullable: true,
                                   argumentsPropagateNullability: new[] { true, true },
                                   typeof(int)),
                               _sqlExpressionFactory.Constant(0)));
                }

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.Equal(
                               pattern,
                               _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                           _sqlExpressionFactory.GreaterThan(
                               _sqlExpressionFactory.Function(
                                   "CHARINDEX",
                                   new[] { pattern, instance },
                                   nullable: true,
                                   argumentsPropagateNullability: new[] { true, true },
                                   typeof(int)),
                               _sqlExpressionFactory.Constant(0))));
            }

            if (_startsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], true));
            }

            if (_endsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], false));
            }

            return(null);
        }
        public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType)
        {
            Check.NotNull(member, nameof(member));
            Check.NotNull(returnType, nameof(returnType));

            if (member.DeclaringType == typeof(DateTime))
            {
                var memberName = member.Name;

                if (_datePartMapping.TryGetValue(memberName, out var datePart))
                {
                    return(_sqlExpressionFactory.Convert(
                               SqliteExpression.Strftime(
                                   _sqlExpressionFactory,
                                   typeof(string),
                                   datePart,
                                   instance),
                               returnType));
                }

                if (string.Equals(memberName, nameof(DateTime.Ticks)))
                {
                    return(_sqlExpressionFactory.Convert(
                               _sqlExpressionFactory.Multiply(
                                   _sqlExpressionFactory.Subtract(
                                       _sqlExpressionFactory.Function(
                                           "julianday",
                                           new[] { instance },
                                           nullable: true,
                                           argumentsPropagateNullability: new[] { true },
                                           typeof(double)),
                                       _sqlExpressionFactory.Constant(1721425.5)), // NB: Result of julianday('0001-01-01 00:00:00')
                                   _sqlExpressionFactory.Constant(TimeSpan.TicksPerDay)),
                               typeof(long)));
                }

                if (string.Equals(memberName, nameof(DateTime.Millisecond)))
                {
                    return(_sqlExpressionFactory.Modulo(
                               _sqlExpressionFactory.Multiply(
                                   _sqlExpressionFactory.Convert(
                                       SqliteExpression.Strftime(
                                           _sqlExpressionFactory,
                                           typeof(string),
                                           "%f",
                                           instance),
                                       typeof(double)),
                                   _sqlExpressionFactory.Constant(1000)),
                               _sqlExpressionFactory.Constant(1000)));
                }

                var           format = "%Y-%m-%d %H:%M:%f";
                SqlExpression timestring;
                var           modifiers = new List <SqlExpression>();

                switch (memberName)
                {
                case nameof(DateTime.Now):
                    timestring = _sqlExpressionFactory.Constant("now");
                    modifiers.Add(_sqlExpressionFactory.Constant("localtime"));
                    break;

                case nameof(DateTime.UtcNow):
                    timestring = _sqlExpressionFactory.Constant("now");
                    break;

                case nameof(DateTime.Date):
                    timestring = instance;
                    modifiers.Add(_sqlExpressionFactory.Constant("start of day"));
                    break;

                case nameof(DateTime.Today):
                    timestring = _sqlExpressionFactory.Constant("now");
                    modifiers.Add(_sqlExpressionFactory.Constant("localtime"));
                    modifiers.Add(_sqlExpressionFactory.Constant("start of day"));
                    break;

                case nameof(DateTime.TimeOfDay):
                    format     = "%H:%M:%f";
                    timestring = instance;
                    break;

                default:
                    return(null);
                }

                Check.DebugAssert(timestring != null, "timestring is null");

                return(_sqlExpressionFactory.Function(
                           "rtrim",
                           new SqlExpression[]
                {
                    _sqlExpressionFactory.Function(
                        "rtrim",
                        new SqlExpression[]
                    {
                        SqliteExpression.Strftime(
                            _sqlExpressionFactory,
                            returnType,
                            format,
                            timestring,
                            modifiers),
                        _sqlExpressionFactory.Constant("0")
                    },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true, false },
                        returnType),
                    _sqlExpressionFactory.Constant(".")
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, false },
                           returnType));
            }

            return(null);
        }