Beispiel #1
0
        public virtual SqlExpression?Translate(
            SqlExpression?instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            if (instance?.Type.IsGenericList() == true && !IsMappedToNonArray(instance))
            {
                // Translate list[i]. Note that array[i] is translated by NpgsqlSqlTranslatingExpressionVisitor.VisitBinary (ArrayIndex)
                if (method.Name == "get_Item" && arguments.Count == 1)
                {
                    return
                        // Try translating indexing inside json column
                        (_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType) ??
                         // Other types should be subscriptable - but PostgreSQL arrays are 1-based, so adjust the index.
                         _sqlExpressionFactory.ArrayIndex(instance, GenerateOneBasedIndexExpression(arguments[0])));
                }

                return(TranslateCommon(instance, arguments));
            }

            if (instance is null && arguments.Count > 0 && arguments[0].Type.IsArrayOrGenericList() && !IsMappedToNonArray(arguments[0]))
            {
                // Extension method over an array or list
                if (method.IsClosedFormOf(SequenceEqual) && arguments[1].Type.IsArray)
                {
                    return(_sqlExpressionFactory.Equal(arguments[0], arguments[1]));
                }

                return(TranslateCommon(arguments[0], arguments.Slice(1)));
            }

            // Not an array/list
            return(null);
Beispiel #2
0
        public virtual SqlExpression Translate(
            SqlExpression instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            if (instance != null && instance.Type.IsGenericList() && method.Name == "get_Item" && arguments.Count == 1)
            {
                return
                    // Try translating indexing inside json column
                    (_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType) ??
                     // Other types should be subscriptable - but PostgreSQL arrays are 1-based, so adjust the index.
                     _sqlExpressionFactory.ArrayIndex(instance, GenerateOneBasedIndexExpression(arguments[0])));
            }

            if (arguments.Count == 0)
            {
                return(null);
            }

            var array = arguments[0];

            if (!array.Type.TryGetElementType(out var elementType))
            {
                return(null); // Not an array/list
            }
            // The array/list CLR type may be mapped to a non-array database type (e.g. byte[] to bytea, or just
            // value converters). Make sure we're dealing with an array
            // Regardless of CLR type, we may be dealing with a non-array database type (e.g. via value converters).
            if (array.TypeMapping is RelationalTypeMapping typeMapping &&
                !(typeMapping is NpgsqlArrayTypeMapping) && !(typeMapping is NpgsqlJsonTypeMapping))
            {
                return(null);
            }

            if (method.IsClosedFormOf(SequenceEqual) && arguments[1].Type.IsArray)
            {
                return(_sqlExpressionFactory.Equal(array, arguments[1]));
            }

            // Predicate-less Any - translate to a simple length check.
            if (method.IsClosedFormOf(EnumerableAnyWithoutPredicate))
            {
                return(_sqlExpressionFactory.GreaterThan(
                           _jsonPocoTranslator.TranslateArrayLength(array) ??
                           _sqlExpressionFactory.Function(
                               "cardinality",
                               arguments,
                               nullable: true,
                               argumentsPropagateNullability: TrueArrays[1],
                               typeof(int)),
                           _sqlExpressionFactory.Constant(0)));
            }

            // Note that .Where(e => new[] { "a", "b", "c" }.Any(p => e.SomeText == p)))
            // is pattern-matched in AllAnyToContainsRewritingExpressionVisitor, which transforms it to
            // new[] { "a", "b", "c" }.Contains(e.Some Text).

            if (method.IsClosedFormOf(Contains) &&
                (
                    // Handle either parameters (no mapping but supported CLR type), or array columns. We specifically
                    // don't want to translate if the type mapping is bytea (CLR type is array, but not an array in
                    // the database).
                    array.TypeMapping == null && _typeMappingSource.FindMapping(array.Type) != null ||
                    array.TypeMapping is NpgsqlArrayTypeMapping
                ) &&
                // Exclude arrays/lists over Nullable<T> since the ADO layer doesn't handle them (but will in 5.0)
                Nullable.GetUnderlyingType(elementType) == null)
            {
                var item = arguments[1];

                switch (array)
                {
                // When the array is a column, we translate to array @> ARRAY[item]. GIN indexes
                // on array are used, but null semantics is impossible without preventing index use.
                case ColumnExpression _:
                    if (item is SqlConstantExpression constant && constant.Value is null)
                    {
                        // We special-case null constant item and use array_position instead, since it does
                        // nulls correctly (but doesn't use indexes)
                        // TODO: once lambda-based caching is implemented, move this to NpgsqlSqlNullabilityProcessor
                        // (https://github.com/dotnet/efcore/issues/17598) and do for parameters as well.
                        return(_sqlExpressionFactory.IsNotNull(
                                   _sqlExpressionFactory.Function(
                                       "array_position",
                                       new[] { array, item },
                                       nullable: true,
                                       argumentsPropagateNullability: FalseArrays[2],
                                       typeof(int))));
                    }

                    return(_sqlExpressionFactory.Contains(array,
                                                          _sqlExpressionFactory.NewArrayOrConstant(new[] { item }, array.Type)));

                // Don't do anything PG-specific for constant arrays since the general EF Core mechanism is fine
                // for that case: item IN (1, 2, 3).
                // After https://github.com/aspnet/EntityFrameworkCore/issues/16375 is done we may not need the
                // check any more.
                case SqlConstantExpression _:
                    return(null);

                // For ParameterExpression, and for all other cases - e.g. array returned from some function -
                // translate to e.SomeText = ANY (@p). This is superior to the general solution which will expand
                // parameters to constants, since non-PG SQL does not support arrays.
                // Note that this will allow indexes on the item to be used.
                default:
                    return(_sqlExpressionFactory.Any(item, array, PostgresAnyOperatorType.Equal));
                }
            }

            // Note: we also translate .Where(e => new[] { "a", "b", "c" }.Any(p => EF.Functions.Like(e.SomeText, p)))
            // to LIKE ANY (...). See NpgsqlSqlTranslatingExpressionVisitor.VisitArrayMethodCall.

            return(null);
        }