/// <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 (_indexOfMethodInfo.Equals(method))
            {
                var argument          = arguments[0];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument);

                return(_sqlExpressionFactory.Subtract(
                           _sqlExpressionFactory.Function(
                               "instr",
                               new[]
                {
                    _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping),
                    _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping)
                },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true },
                               method.ReturnType),
                           _sqlExpressionFactory.Constant(1)));
            }

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

                return(_sqlExpressionFactory.Function(
                           "replace",
                           new[]
                {
                    _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping),
                    _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping),
                    _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping)
                },
                           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(
                           "substr",
                           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(
                                   "trim",
                                   new[] { argument },
                                   nullable: true,
                                   argumentsPropagateNullability: new[] { true },
                                   argument.Type,
                                   argument.TypeMapping),
                               _sqlExpressionFactory.Constant(string.Empty))));
            }

            if (_trimStartMethodInfoWithoutArgs?.Equals(method) == true ||
                _trimStartMethodInfoWithCharArg?.Equals(method) == true ||
                _trimStartMethodInfoWithCharArrayArg.Equals(method))
            {
                return(ProcessTrimMethod(instance, arguments, "ltrim"));
            }

            if (_trimEndMethodInfoWithoutArgs?.Equals(method) == true ||
                _trimEndMethodInfoWithCharArg?.Equals(method) == true ||
                _trimEndMethodInfoWithCharArrayArg.Equals(method))
            {
                return(ProcessTrimMethod(instance, arguments, "rtrim"));
            }

            if (_trimMethodInfoWithoutArgs?.Equals(method) == true ||
                _trimMethodInfoWithCharArg?.Equals(method) == true ||
                _trimMethodInfoWithCharArrayArg.Equals(method))
            {
                return(ProcessTrimMethod(instance, arguments, "trim"));
            }

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

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

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

            if (_firstOrDefaultMethodInfoWithoutArgs.Equals(method))
            {
                var argument = arguments[0];
                return(_sqlExpressionFactory.Function(
                           "substr",
                           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(
                           "substr",
                           new[] { argument,
                                   _sqlExpressionFactory.Function(
                                       "length",
                                       new[] { argument },
                                       nullable: true,
                                       argumentsPropagateNullability: new[] { true },
                                       typeof(int)),
                                   _sqlExpressionFactory.Constant(1) },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType));
            }

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

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

            return(null);
        }
        protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression)
        {
            Check.NotNull(sqlBinaryExpression, nameof(sqlBinaryExpression));

            _isNullable = false;
            var canOptimize = _canOptimize;

            // for SqlServer we could also allow optimize on children of ExpressionType.Equal
            // because they get converted to CASE blocks anyway, but for other providers it's incorrect
            // once/if null semantics optimizations are provider-specific we can enable it
            _canOptimize = _canOptimize && (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso ||
                                            sqlBinaryExpression.OperatorType == ExpressionType.OrElse);

            var nonNullableColumns = new List <ColumnExpression>();

            if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso)
            {
                nonNullableColumns = FindNonNullableColumns(sqlBinaryExpression.Left);
            }

            var newLeft      = (SqlExpression)Visit(sqlBinaryExpression.Left);
            var leftNullable = _isNullable;

            _isNullable = false;
            if (nonNullableColumns.Count > 0)
            {
                _nonNullableColumns.AddRange(nonNullableColumns);
            }

            var newRight      = (SqlExpression)Visit(sqlBinaryExpression.Right);
            var rightNullable = _isNullable;

            foreach (var nonNullableColumn in nonNullableColumns)
            {
                _nonNullableColumns.Remove(nonNullableColumn);
            }

            if (sqlBinaryExpression.OperatorType == ExpressionType.Coalesce)
            {
                _isNullable  = leftNullable && rightNullable;
                _canOptimize = canOptimize;

                return(sqlBinaryExpression.Update(newLeft, newRight));
            }

            if (sqlBinaryExpression.OperatorType == ExpressionType.Add &&
                sqlBinaryExpression.Type == typeof(string))
            {
                if (leftNullable)
                {
                    newLeft = newLeft is SqlConstantExpression
                        ? _sqlExpressionFactory.Constant(string.Empty)
                        : newLeft is ColumnExpression || newLeft is SqlParameterExpression
                            ? _sqlExpressionFactory.Coalesce(newLeft, _sqlExpressionFactory.Constant(string.Empty))
                            : newLeft;
                }

                if (rightNullable)
                {
                    newRight = newRight is SqlConstantExpression
                        ? _sqlExpressionFactory.Constant(string.Empty)
                        : newRight is ColumnExpression || newRight is SqlParameterExpression
                            ? _sqlExpressionFactory.Coalesce(newRight, _sqlExpressionFactory.Constant(string.Empty))
                            : newRight;
                }

                return(sqlBinaryExpression.Update(newLeft, newRight));
            }

            if (sqlBinaryExpression.OperatorType == ExpressionType.Equal ||
                sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)
            {
                var leftConstantNull  = newLeft is SqlConstantExpression leftConstant && leftConstant.Value == null;
                var rightConstantNull = newRight is SqlConstantExpression rightConstant && rightConstant.Value == null;

                // a == null -> a IS NULL
                // a != null -> a IS NOT NULL
                if (rightConstantNull)
                {
                    _isNullable  = false;
                    _canOptimize = canOptimize;

                    return(sqlBinaryExpression.OperatorType == ExpressionType.Equal
                        ? _sqlExpressionFactory.IsNull(newLeft)
                        : _sqlExpressionFactory.IsNotNull(newLeft));
                }

                // null == a -> a IS NULL
                // null != a -> a IS NOT NULL
                if (leftConstantNull)
                {
                    _isNullable  = false;
                    _canOptimize = canOptimize;

                    return(sqlBinaryExpression.OperatorType == ExpressionType.Equal
                        ? _sqlExpressionFactory.IsNull(newRight)
                        : _sqlExpressionFactory.IsNotNull(newRight));
                }

                var leftUnary  = newLeft as SqlUnaryExpression;
                var rightUnary = newRight as SqlUnaryExpression;

                var leftNegated  = leftUnary?.IsLogicalNot() == true;
                var rightNegated = rightUnary?.IsLogicalNot() == true;

                if (leftNegated)
                {
                    newLeft = leftUnary.Operand;
                }

                if (rightNegated)
                {
                    newRight = rightUnary.Operand;
                }

                var leftIsNull  = _sqlExpressionFactory.IsNull(newLeft);
                var rightIsNull = _sqlExpressionFactory.IsNull(newRight);

                // optimized expansion which doesn't distinguish between null and false
                if (canOptimize &&
                    sqlBinaryExpression.OperatorType == ExpressionType.Equal &&
                    !leftNegated &&
                    !rightNegated)
                {
                    // when we use optimized form, the result can still be nullable
                    if (leftNullable && rightNullable)
                    {
                        _isNullable  = true;
                        _canOptimize = canOptimize;

                        return(_sqlExpressionFactory.OrElse(
                                   _sqlExpressionFactory.Equal(newLeft, newRight),
                                   _sqlExpressionFactory.AndAlso(leftIsNull, rightIsNull)));
                    }

                    if ((leftNullable && !rightNullable) ||
                        (!leftNullable && rightNullable))
                    {
                        _isNullable  = true;
                        _canOptimize = canOptimize;

                        return(_sqlExpressionFactory.Equal(newLeft, newRight));
                    }
                }

                // doing a full null semantics rewrite - removing all nulls from truth table
                // this will NOT be correct once we introduce simplified null semantics
                _isNullable  = false;
                _canOptimize = canOptimize;

                if (sqlBinaryExpression.OperatorType == ExpressionType.Equal)
                {
                    if (!leftNullable &&
                        !rightNullable)
                    {
                        // a == b <=> !a == !b -> a == b
                        // !a == b <=> a == !b -> a != b
                        return(leftNegated == rightNegated
                            ? _sqlExpressionFactory.Equal(newLeft, newRight)
                            : _sqlExpressionFactory.NotEqual(newLeft, newRight));
                    }

                    if (leftNullable && rightNullable)
                    {
                        // ?a == ?b <=> !(?a) == !(?b) -> [(a == b) && (a != null && b != null)] || (a == null && b == null))
                        // !(?a) == ?b <=> ?a == !(?b) -> [(a != b) && (a != null && b != null)] || (a == null && b == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNullable(newLeft, newRight, leftIsNull, rightIsNull)
                            : ExpandNegatedNullableEqualNullable(newLeft, newRight, leftIsNull, rightIsNull));
                    }

                    if (leftNullable && !rightNullable)
                    {
                        // ?a == b <=> !(?a) == !b -> (a == b) && (a != null)
                        // !(?a) == b <=> ?a == !b -> (a != b) && (a != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNonNullable(newLeft, newRight, leftIsNull)
                            : ExpandNegatedNullableEqualNonNullable(newLeft, newRight, leftIsNull));
                    }

                    if (rightNullable && !leftNullable)
                    {
                        // a == ?b <=> !a == !(?b) -> (a == b) && (b != null)
                        // !a == ?b <=> a == !(?b) -> (a != b) && (b != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNonNullable(newLeft, newRight, rightIsNull)
                            : ExpandNegatedNullableEqualNonNullable(newLeft, newRight, rightIsNull));
                    }
                }

                if (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)
                {
                    if (!leftNullable &&
                        !rightNullable)
                    {
                        // a != b <=> !a != !b -> a != b
                        // !a != b <=> a != !b -> a == b
                        return(leftNegated == rightNegated
                            ? _sqlExpressionFactory.NotEqual(newLeft, newRight)
                            : _sqlExpressionFactory.Equal(newLeft, newRight));
                    }

                    if (leftNullable && rightNullable)
                    {
                        // ?a != ?b <=> !(?a) != !(?b) -> [(a != b) || (a == null || b == null)] && (a != null || b != null)
                        // !(?a) != ?b <=> ?a != !(?b) -> [(a == b) || (a == null || b == null)] && (a != null || b != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNullable(newLeft, newRight, leftIsNull, rightIsNull)
                            : ExpandNegatedNullableNotEqualNullable(newLeft, newRight, leftIsNull, rightIsNull));
                    }

                    if (leftNullable)
                    {
                        // ?a != b <=> !(?a) != !b -> (a != b) || (a == null)
                        // !(?a) != b <=> ?a != !b -> (a == b) || (a == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNonNullable(newLeft, newRight, leftIsNull)
                            : ExpandNegatedNullableNotEqualNonNullable(newLeft, newRight, leftIsNull));
                    }

                    if (rightNullable)
                    {
                        // a != ?b <=> !a != !(?b) -> (a != b) || (b == null)
                        // !a != ?b <=> a != !(?b) -> (a == b) || (b == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNonNullable(newLeft, newRight, rightIsNull)
                            : ExpandNegatedNullableNotEqualNonNullable(newLeft, newRight, rightIsNull));
                    }
                }
            }

            _isNullable  = leftNullable || rightNullable;
            _canOptimize = canOptimize;

            return(sqlBinaryExpression.Update(newLeft, newRight));
        }
Example #3
0
            public override Expression Visit(Expression expression)
            {
                if (expression is InExpression inExpression)
                {
                    var             inValues     = new List <object>();
                    var             hasNullValue = false;
                    CoreTypeMapping typeMapping  = null;

                    switch (inExpression.Values)
                    {
                    case SqlConstantExpression sqlConstant:
                    {
                        typeMapping = sqlConstant.TypeMapping;
                        var values = (IEnumerable)sqlConstant.Value;
                        foreach (var value in values)
                        {
                            if (value == null)
                            {
                                hasNullValue = true;
                                continue;
                            }

                            inValues.Add(value);
                        }
                    }
                    break;

                    case SqlParameterExpression sqlParameter:
                    {
                        typeMapping = sqlParameter.TypeMapping;
                        var values = (IEnumerable)_parametersValues[sqlParameter.Name];
                        foreach (var value in values)
                        {
                            if (value == null)
                            {
                                hasNullValue = true;
                                continue;
                            }

                            inValues.Add(value);
                        }
                    }
                    break;
                    }

                    var updatedInExpression = inValues.Count > 0
                        ? _sqlExpressionFactory.In(
                        (SqlExpression)Visit(inExpression.Item),
                        _sqlExpressionFactory.Constant(inValues, typeMapping),
                        inExpression.IsNegated)
                        : null;

                    var nullCheckExpression = hasNullValue
                        ? _sqlExpressionFactory.IsNull(inExpression.Item)
                        : null;

                    if (updatedInExpression != null &&
                        nullCheckExpression != null)
                    {
                        return(_sqlExpressionFactory.OrElse(updatedInExpression, nullCheckExpression));
                    }

                    if (updatedInExpression == null &&
                        nullCheckExpression == null)
                    {
                        return(_sqlExpressionFactory.Equal(_sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(false)));
                    }

                    return((SqlExpression)updatedInExpression ?? nullCheckExpression);
                }

                return(base.Visit(expression));
            }
 private SqlExpression BuildCompareToExpression(SqlExpression sqlExpression)
 {
     return(_sqlExpressionFactory.Equal(sqlExpression, _sqlExpressionFactory.Constant(true)));
 }
        private SqlBinaryExpression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpression)
        {
            _isNullable = false;

            var nonNullableColumns = new List <ColumnExpression>();

            if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso)
            {
                nonNullableColumns = FindNonNullableColumns(sqlBinaryExpression.Left);
            }

            var newLeft      = (SqlExpression)Visit(sqlBinaryExpression.Left);
            var leftNullable = _isNullable;

            _isNullable = false;
            if (nonNullableColumns.Count > 0)
            {
                _nonNullableColumns.AddRange(nonNullableColumns);
            }

            var newRight      = (SqlExpression)Visit(sqlBinaryExpression.Right);
            var rightNullable = _isNullable;

            foreach (var nonNullableColumn in nonNullableColumns)
            {
                _nonNullableColumns.Remove(nonNullableColumn);
            }

            if (sqlBinaryExpression.OperatorType == ExpressionType.Coalesce)
            {
                _isNullable = leftNullable && rightNullable;

                return(sqlBinaryExpression.Update(newLeft, newRight));
            }

            if (sqlBinaryExpression.OperatorType == ExpressionType.Equal ||
                sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)
            {
                var leftUnary  = newLeft as SqlUnaryExpression;
                var rightUnary = newRight as SqlUnaryExpression;

                var leftNegated  = leftUnary?.OperatorType == ExpressionType.Not;
                var rightNegated = rightUnary?.OperatorType == ExpressionType.Not;

                if (leftNegated)
                {
                    newLeft = leftUnary.Operand;
                }

                if (rightNegated)
                {
                    newRight = rightUnary.Operand;
                }

                // TODO: optimize this by looking at subcomponents, e.g. f(a, b) == null <=> a == null || b == null
                var leftIsNull  = _sqlExpressionFactory.IsNull(newLeft);
                var rightIsNull = _sqlExpressionFactory.IsNull(newRight);

                // doing a full null semantics rewrite - removing all nulls from truth table
                _isNullable = false;

                if (sqlBinaryExpression.OperatorType == ExpressionType.Equal)
                {
                    if (!leftNullable &&
                        !rightNullable)
                    {
                        // a == b <=> !a == !b -> a == b
                        // !a == b <=> a == !b -> a != b
                        return(leftNegated == rightNegated
                            ? _sqlExpressionFactory.Equal(newLeft, newRight)
                            : _sqlExpressionFactory.NotEqual(newLeft, newRight));
                    }

                    if (leftNullable && rightNullable)
                    {
                        // ?a == ?b <=> !(?a) == !(?b) -> [(a == b) && (a != null && b != null)] || (a == null && b == null))
                        // !(?a) == ?b <=> ?a == !(?b) -> [(a != b) && (a != null && b != null)] || (a == null && b == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNullable(newLeft, newRight, leftIsNull, rightIsNull)
                            : ExpandNegatedNullableEqualNullable(newLeft, newRight, leftIsNull, rightIsNull));
                    }

                    if (leftNullable && !rightNullable)
                    {
                        // ?a == b <=> !(?a) == !b -> (a == b) && (a != null)
                        // !(?a) == b <=> ?a == !b -> (a != b) && (a != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNonNullable(newLeft, newRight, leftIsNull)
                            : ExpandNegatedNullableEqualNonNullable(newLeft, newRight, leftIsNull));
                    }

                    if (rightNullable && !leftNullable)
                    {
                        // a == ?b <=> !a == !(?b) -> (a == b) && (b != null)
                        // !a == ?b <=> a == !(?b) -> (a != b) && (b != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableEqualNonNullable(newLeft, newRight, rightIsNull)
                            : ExpandNegatedNullableEqualNonNullable(newLeft, newRight, rightIsNull));
                    }
                }

                if (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)
                {
                    if (!leftNullable &&
                        !rightNullable)
                    {
                        // a != b <=> !a != !b -> a != b
                        // !a != b <=> a != !b -> a == b
                        return(leftNegated == rightNegated
                            ? _sqlExpressionFactory.NotEqual(newLeft, newRight)
                            : _sqlExpressionFactory.Equal(newLeft, newRight));
                    }

                    if (leftNullable && rightNullable)
                    {
                        // ?a != ?b <=> !(?a) != !(?b) -> [(a != b) || (a == null || b == null)] && (a != null || b != null)
                        // !(?a) != ?b <=> ?a != !(?b) -> [(a == b) || (a == null || b == null)] && (a != null || b != null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNullable(newLeft, newRight, leftIsNull, rightIsNull)
                            : ExpandNegatedNullableNotEqualNullable(newLeft, newRight, leftIsNull, rightIsNull));
                    }

                    if (leftNullable)
                    {
                        // ?a != b <=> !(?a) != !b -> (a != b) || (a == null)
                        // !(?a) != b <=> ?a != !b -> (a == b) || (a == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNonNullable(newLeft, newRight, leftIsNull)
                            : ExpandNegatedNullableNotEqualNonNullable(newLeft, newRight, leftIsNull));
                    }

                    if (rightNullable)
                    {
                        // a != ?b <=> !a != !(?b) -> (a != b) || (b == null)
                        // !a != ?b <=> a != !(?b) -> (a == b) || (b == null)
                        return(leftNegated == rightNegated
                            ? ExpandNullableNotEqualNonNullable(newLeft, newRight, rightIsNull)
                            : ExpandNegatedNullableNotEqualNonNullable(newLeft, newRight, rightIsNull));
                    }
                }
            }

            _isNullable = leftNullable || rightNullable;

            return(sqlBinaryExpression.Update(newLeft, newRight));
        }
        /// <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 (_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 (_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));
            }

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

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

            return(null);
        }
Example #7
0
        private static SqlExpression GetAndoyerDistance(
            SqlExpression left,
            SqlExpression right,
            Type resultType,
            ISqlExpressionFactory sqlExpressionFactory)
        {
            SqlExpression toDegrees(SqlExpression coord)
            => sqlExpressionFactory.Divide(
                sqlExpressionFactory.Multiply(
                    coord,
                    sqlExpressionFactory.Function(
                        "PI",
                        Array.Empty <SqlExpression>(),
                        resultType)),
                sqlExpressionFactory.Constant(180.0));

            SqlExpression xCoord(SqlExpression point)
            => sqlExpressionFactory.Function(
                "ST_X",
                new[] { point },
                resultType);

            SqlExpression yCoord(SqlExpression point)
            => sqlExpressionFactory.Function(
                "ST_Y",
                new[] { point },
                resultType);

            var c0    = sqlExpressionFactory.Constant(0.0);
            var c1    = sqlExpressionFactory.Constant(1.0);
            var c2    = sqlExpressionFactory.Constant(2.0);
            var c3    = sqlExpressionFactory.Constant(3.0);
            var c2Int = sqlExpressionFactory.Constant(2);

            var lon1 = toDegrees(xCoord(left));
            var lat1 = toDegrees(yCoord(left));
            var lon2 = toDegrees(xCoord(right));
            var lat2 = toDegrees(yCoord(right));

            var g = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Subtract(
                    lat1,
                    lat2),
                c2);
            var lambda = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Subtract(
                    lon1,
                    lon2),
                c2);

            var f = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Add(
                    lat1,
                    lat2),
                c2);

            var sinG2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "SIN",
                    new[] { g },
                    resultType),
                c2Int
            },
                resultType);
            var cosG2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "COS",
                    new[] { g },
                    resultType),
                c2Int
            },
                resultType);
            var sinF2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "SIN",
                    new[] { f },
                    resultType),
                c2Int
            },
                resultType);
            var cosF2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "COS",
                    new[] { f },
                    resultType),
                c2Int
            },
                resultType);
            var sinL2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "SIN",
                    new[] { lambda },
                    resultType),
                c2Int
            },
                resultType);
            var cosL2 = sqlExpressionFactory.Function(
                "POWER",
                new SqlExpression[]
            {
                sqlExpressionFactory.Function(
                    "COS",
                    new[] { lambda },
                    resultType),
                c2Int
            },
                resultType);

            var s = sqlExpressionFactory.Add(
                sqlExpressionFactory.Multiply(sinG2, cosL2),
                sqlExpressionFactory.Multiply(cosF2, sinL2));
            var c = sqlExpressionFactory.Add(
                sqlExpressionFactory.Multiply(cosG2, cosL2),
                sqlExpressionFactory.Multiply(sinF2, sinL2));

            var radiusA    = sqlExpressionFactory.Constant(6378137.0);
            var radiusB    = sqlExpressionFactory.Constant(6356752.3142451793);
            var flattening = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Subtract(radiusA, radiusB),
                radiusA);

            var omega = sqlExpressionFactory.Function(
                "ATAN",
                new[]
            {
                sqlExpressionFactory.Function(
                    "SQRT",
                    new[] { sqlExpressionFactory.Divide(s, c) },
                    resultType)
            },
                resultType);
            var r3 = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Multiply(
                    c3,
                    sqlExpressionFactory.Function(
                        "SQRT",
                        new[] { sqlExpressionFactory.Multiply(s, c) },
                        resultType)),
                omega);
            var d = sqlExpressionFactory.Multiply(
                sqlExpressionFactory.Multiply(c2, omega),
                radiusA);
            var h1 = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Subtract(r3, c1),
                sqlExpressionFactory.Multiply(c2, c));
            var h2 = sqlExpressionFactory.Divide(
                sqlExpressionFactory.Add(r3, c1),
                sqlExpressionFactory.Multiply(c2, s));

            var andoyer = sqlExpressionFactory.Multiply(
                d,
                sqlExpressionFactory.Add(
                    c1,
                    sqlExpressionFactory.Multiply(
                        flattening,
                        sqlExpressionFactory.Subtract(
                            sqlExpressionFactory.Multiply(
                                sqlExpressionFactory.Multiply(
                                    h1,
                                    sinF2),
                                cosG2),
                            sqlExpressionFactory.Multiply(
                                sqlExpressionFactory.Multiply(
                                    h2,
                                    cosF2),
                                sinG2)))));

            return(sqlExpressionFactory.Case(
                       new[]
            {
                new CaseWhenClause(
                    sqlExpressionFactory.OrElse(
                        sqlExpressionFactory.OrElse(
                            sqlExpressionFactory.AndAlso(
                                sqlExpressionFactory.Equal(lambda, c0),
                                sqlExpressionFactory.Equal(g, c0)),
                            sqlExpressionFactory.Equal(s, c0)),
                        sqlExpressionFactory.Equal(c, c0)),
                    c0),
            },
                       andoyer));
        }
Example #8
0
        public static SqlExpression GetStDistanceSphereFunctionCall(
            SqlExpression left,
            SqlExpression right,
            SpatialDistanceAlgorithm algorithm,
            Type resultType,
            RelationalTypeMapping resultTypeMapping,
            ISqlExpressionFactory sqlExpressionFactory,
            IMySqlOptions options)
        {
            if (options.ServerVersion.SupportsSpatialDistanceSphereFunction)
            {
                if (algorithm == SpatialDistanceAlgorithm.Native)
                {
                    return(sqlExpressionFactory.Function(
                               "ST_Distance_Sphere",
                               new[] { left, right },
                               resultType,
                               resultTypeMapping));
                }

                if (algorithm == SpatialDistanceAlgorithm.Andoyer &&
                    options.ServerVersion.SupportsSpatialDistanceFunctionImplementsAndoyer)
                {
                    // The `ST_Distance()` in MySQL already uses the Andoyer algorithm, when SRID 4326 is associated
                    // with the geometry.
                    // CHECK: It might be faster to just run the custom implementation, if `ST_SRID()` does not support
                    //        a second parameter yet (see SetSrid()).
                    return(sqlExpressionFactory.Case(
                               new[]
                    {
                        new CaseWhenClause(
                            sqlExpressionFactory.Equal(
                                sqlExpressionFactory.Function(
                                    "ST_SRID",
                                    new[] { left },
                                    typeof(int)),
                                sqlExpressionFactory.Constant(4326)),
                            GetStDistanceFunctionCall(
                                left,
                                right,
                                resultType,
                                resultTypeMapping,
                                sqlExpressionFactory))
                    },
                               GetStDistanceFunctionCall(
                                   SetSrid(left, 4326, sqlExpressionFactory, options),
                                   SetSrid(right, 4326, sqlExpressionFactory, options),
                                   resultType,
                                   resultTypeMapping,
                                   sqlExpressionFactory)));
                }

                if (algorithm == SpatialDistanceAlgorithm.Haversine)
                {
                    // The Haversine algorithm assumes planar coordinates.
                    return(sqlExpressionFactory.Case(
                               new[]
                    {
                        new CaseWhenClause(
                            sqlExpressionFactory.Equal(
                                sqlExpressionFactory.Function(
                                    "ST_SRID",
                                    new[] { left },
                                    typeof(int)),
                                sqlExpressionFactory.Constant(0)),
                            GetHaversineDistance(
                                left,
                                right,
                                resultType,
                                sqlExpressionFactory))
                    },
                               GetHaversineDistance(
                                   SetSrid(left, 0, sqlExpressionFactory, options),
                                   SetSrid(right, 0, sqlExpressionFactory, options),
                                   resultType,
                                   sqlExpressionFactory)));
                }
            }

            if (algorithm == SpatialDistanceAlgorithm.Haversine)
            {
                return(GetHaversineDistance(left, right, resultType, sqlExpressionFactory));
            }

            return(GetAndoyerDistance(left, right, resultType, sqlExpressionFactory));
        }