diff --git a/Directory.Packages.props b/Directory.Packages.props index ab4c1b0dfc..57993167b3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,8 @@ - 11.0.0-preview.5.26227.124 - 11.0.0-preview.5.26227.124 - 11.0.0-preview.5.26227.124 + 11.0.0-preview.5.26251.112 + 11.0.0-preview.5.26251.112 + 11.0.0-preview.5.26251.112 10.0.0 diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index 50f44ff61f..8b3423af2f 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -193,7 +193,8 @@ SqlExpression Upper() => _sqlExpressionFactory.Convert( _sqlExpressionFactory.Subtract( Upper(), - _sqlExpressionFactory.Constant(Period.FromDays(1), _periodTypeMapping)), typeof(LocalDate), + _sqlExpressionFactory.Constant(Period.FromDays(1), _periodTypeMapping), + _typeMappingSource.FindMapping(typeof(LocalDateTime))), typeof(LocalDate), _typeMappingSource.FindMapping(typeof(LocalDate))), nameof(DateInterval.Length) => _sqlExpressionFactory.Subtract(Upper(), Lower()), diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 8b9f0587b3..9a82c8eedc 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -889,6 +889,14 @@ protected virtual Expression VisitArrayAny(PgAnyExpression expression) Visit(expression.Array); + // When the array operand is a scalar subquery, PostgreSQL interprets = ANY(subquery) as a subquery comparison + // (comparing against each row), not as an array comparison. We need to add an explicit cast to force + // PostgreSQL to treat it as an array expression (see #1803). + if (expression.Array is ScalarSubqueryExpression { TypeMapping.StoreType: var storeType }) + { + Sql.Append("::").Append(storeType); + } + Sql.Append(")"); return expression; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 9f0806a540..5bed034320 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -719,14 +719,14 @@ SqlConstantExpression or PgNewArrayExpression // Similar to ParameterExpression below, but when a bare subquery is present inside ANY(), PostgreSQL just compares // against each of its resulting rows (just like IN). To "extract" the array result of the scalar subquery, we need - // to add an explicit cast (see #1803). + // to add an explicit cast (see #1803). This is handled in the SQL generator (VisitArrayAny) rather than via + // SqlUnaryExpression(Convert), since it's a same-type cast that would be removed by EF's simplifier. ScalarSubqueryExpression subqueryExpression => BuildSimplifiedShapedQuery( source, _sqlExpressionFactory.Any( translatedItem, - _sqlExpressionFactory.Convert( - subqueryExpression, subqueryExpression.Type, subqueryExpression.TypeMapping), + subqueryExpression, PgAnyOperatorType.Equal)), // For ParameterExpression, and for all other cases - e.g. array returned from some function - diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 3131ed4470..3ac07d17fa 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -447,7 +447,7 @@ private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression bina { var newLeft = ApplyTypeMapping(left, typeMapping); var newRight = ApplyDefaultTypeMapping(right); - return new SqlBinaryExpression(binary.OperatorType, newLeft, newRight, binary.Type, newLeft.TypeMapping); + return new SqlBinaryExpression(binary.OperatorType, newLeft, newRight, binary.Type, typeMapping ?? newLeft.TypeMapping); } // DateTime - DateTime => TimeSpan diff --git a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs index 148feff911..92b92edc64 100644 --- a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs +++ b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs @@ -166,7 +166,7 @@ protected override void AppendUpdateColumnValue( if (segment.IsArray) { - stringBuilder.Append(jsonPath.Ordinals[ordinalIndex++]); + stringBuilder.Append(jsonPath.Indices[ordinalIndex++]); } else { diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs index 18bfe6e090..70af576bd5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs @@ -50,7 +50,7 @@ await AssertQuery( SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @dateTimeOffset_Date +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') >= @dateTimeOffset_Date """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPCGearsOfWarQueryNpgsqlTest.cs index be6b0adab3..32e84ffeea 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPCGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPCGearsOfWarQueryNpgsqlTest.cs @@ -64,7 +64,7 @@ await AssertQuery( SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @dateTimeOffset_Date +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') >= @dateTimeOffset_Date """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPTGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPTGearsOfWarQueryNpgsqlTest.cs index fe2cd77884..81c6da378e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPTGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Inheritance/TPTGearsOfWarQueryNpgsqlTest.cs @@ -68,7 +68,7 @@ await AssertQuery( SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @dateTimeOffset_Date +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') >= @dateTimeOffset_Date """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs index 6689159f8d..1a4015f5ed 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs @@ -43,11 +43,11 @@ public override async Task SqlQuery_composed_Join(bool async) AssertSql( """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", s."Value"::int AS p +SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", s."Value" AS p FROM "Orders" AS o INNER JOIN ( SELECT "ProductID" AS "Value" FROM "Products" -) AS s ON o."OrderID" = s."Value"::int +) AS s ON o."OrderID" = s."Value" """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index ff2d3a56ab..4613bd4fa9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -411,7 +411,7 @@ public override async Task Inline_collection_with_single_parameter_element_Count FROM "PrimitiveCollectionsEntity" AS p WHERE ( SELECT count(*)::int - FROM (VALUES (@i::int)) AS v("Value") + FROM (VALUES (@i)) AS v("Value") WHERE v."Value" > p."Id") = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs index 5ccd996d0e..8ec0c43dd7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs @@ -44,7 +44,7 @@ public override async Task First() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE length(b."ByteArray") >= 1 AND get_byte(b."ByteArray", 0)::smallint = 222 +WHERE length(b."ByteArray") >= 1 AND get_byte(b."ByteArray", 0) = 222 """); } @@ -101,7 +101,7 @@ public override async Task SequenceEqual() } [ConditionalFact] - public virtual async Task Any() + public override async Task Any() { await AssertQuery(ss => ss.Set().Where(e => e.ByteArray.Any())); diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/CitextTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/CitextTranslationsTest.cs index 06673f0109..e5b4187ff1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/CitextTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/CitextTranslationsTest.cs @@ -66,7 +66,7 @@ public void StartsWith_param_instance() SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE left(@param, length(s."CaseInsensitiveText"))::citext = s."CaseInsensitiveText" +WHERE left(@param, length(s."CaseInsensitiveText")) = s."CaseInsensitiveText" LIMIT 2 """); } @@ -120,7 +120,7 @@ public void EndsWith_param_instance() SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE right(@param, length(s."CaseInsensitiveText"))::citext = s."CaseInsensitiveText" +WHERE right(@param, length(s."CaseInsensitiveText")) = s."CaseInsensitiveText" LIMIT 2 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs index 36a6960e04..cb0ec09527 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs @@ -426,7 +426,7 @@ public override async Task Sign() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE sign(b."Double")::int > 0 +WHERE sign(b."Double") > 0 """); } @@ -438,7 +438,7 @@ public override async Task Sign_float() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE sign(b."Float")::int > 0 +WHERE sign(b."Float") > 0 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs index 437f31dbde..4cc7bf81f2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs @@ -109,64 +109,64 @@ public override async Task Convert_ToInt32() await base.Convert_ToInt32(); AssertSql( -""" + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Bool"::int = 1 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Byte"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Decimal"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Double"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Float"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Short"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE b."Int"::int = 12 +WHERE b."Int" = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Long"::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b WHERE b."Int"::text::int = 12 """, - // - """ + // + """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE b."Int"::int = 12 +WHERE b."Int" = 12 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/InstantTranslationsTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/InstantTranslationsTest.cs index 8081f36d89..a1ea1c0ee0 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/InstantTranslationsTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/NodaTime/InstantTranslationsTest.cs @@ -127,7 +127,7 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE n."Instant"::timestamptz = TIMESTAMPTZ '2018-04-20T10:31:33.666Z' +WHERE n."Instant" = TIMESTAMPTZ '2018-04-20T10:31:33.666Z' """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs index c89e1ec6e4..0ce043b1ce 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs @@ -89,7 +89,7 @@ public override async Task AddYears() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE CAST(b."DateOnly" + INTERVAL '3 years' AS date) = DATE '1993-11-10' +WHERE b."DateOnly" + INTERVAL '3 years' = DATE '1993-11-10' """); } @@ -101,7 +101,7 @@ public override async Task AddMonths() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE CAST(b."DateOnly" + INTERVAL '3 months' AS date) = DATE '1991-02-10' +WHERE b."DateOnly" + INTERVAL '3 months' = DATE '1991-02-10' """); } @@ -213,7 +213,7 @@ public override async Task ToDateTime_with_complex_DateTime() """ SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" FROM "BasicTypesEntities" AS b -WHERE CAST(b."DateOnly" + INTERVAL '1 years' AS date) + b."TimeOnly" = TIMESTAMP '2021-01-01T15:30:10' +WHERE b."DateOnly" + INTERVAL '1 years' + b."TimeOnly" = TIMESTAMP '2021-01-01T15:30:10' """); }