From 174a0e3f566bc76eca6bee06d148f02a287dbba6 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 22 Dec 2024 11:20:25 +0100 Subject: [PATCH] Sync to EF 10.0.0-alpha.1.24620.1 (#3416) Implement the new translation tests --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- Directory.Build.props | 1 + Directory.Packages.props | 4 +- global.json | 4 +- src/EFCore.PG.NTS/EFCore.PG.NTS.csproj | 2 - .../EFCore.PG.NodaTime.csproj | 2 - src/EFCore.PG/EFCore.PG.csproj | 1 - test/Directory.Build.props | 1 - .../EFCore.PG.FunctionalTests.csproj | 2 +- .../Query/GearsOfWarQueryNpgsqlTest.cs | 509 ------ .../NorthwindFunctionsQueryNpgsqlTest.cs | 172 -- .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 12 - .../Query/NorthwindWhereQueryNpgsqlTest.cs | 156 -- .../Query/TPCGearsOfWarQueryNpgsqlTest.cs | 61 - .../Query/TPTGearsOfWarQueryNpgsqlTest.cs | 61 - .../BasicTypesQueryNpgsqlFixture.cs | 103 ++ .../EnumTranslationsNpgsqlTest.cs | 310 ++++ .../MathTranslationsNpgsqlTest.cs | 763 +++++++++ .../MiscellaneousTranslationsNpgsqlTest.cs | 517 ++++++ .../OperatorTranslationsNpgsqlTest.cs | 203 +++ .../StringTranslationsNpgsqlTest.cs | 1505 +++++++++++++++++ .../TemporalTranslationsNpgsqlTest.cs | 940 ++++++++++ ...tionsNpgsqlTimestampWithoutTimeZoneTest.cs | 997 +++++++++++ 24 files changed, 5346 insertions(+), 984 deletions(-) create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/BasicTypesQueryNpgsqlFixture.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/EnumTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/OperatorTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/StringTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fca39f9ce..e65edd09d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100' + dotnet_sdk_version: '10.0.100-alpha.1.24620.13' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c5e68a34c..0423de65a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100' + dotnet_sdk_version: '10.0.100-alpha.1.24620.13' jobs: analyze: diff --git a/Directory.Build.props b/Directory.Build.props index 0d7be262f..a1a2c26d3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,7 @@ 10.0.0 + net10.0 latest true latest diff --git a/Directory.Packages.props b/Directory.Packages.props index c2997a8d8..7698fb554 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 10.0.0-alpha.1.24610.3 - 10.0.0-alpha.1.24609.1 + 10.0.0-alpha.1.24620.1 + 10.0.0-alpha.1.24616.1 9.0.2 diff --git a/global.json b/global.json index 733b653c1..d3a56aff6 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.100-alpha.1.24620.13", "rollForward": "latestMajor", - "allowPrerelease": false + "allowPrerelease": true } } diff --git a/src/EFCore.PG.NTS/EFCore.PG.NTS.csproj b/src/EFCore.PG.NTS/EFCore.PG.NTS.csproj index fba625a4a..46e3bf109 100644 --- a/src/EFCore.PG.NTS/EFCore.PG.NTS.csproj +++ b/src/EFCore.PG.NTS/EFCore.PG.NTS.csproj @@ -2,8 +2,6 @@ Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite - net8.0 - net8.0 Shay Rojansky NetTopologySuite PostGIS spatial support plugin for PostgreSQL/Npgsql Entity Framework Core provider. diff --git a/src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj b/src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj index 12f12137c..5d4c0d4a4 100644 --- a/src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj +++ b/src/EFCore.PG.NodaTime/EFCore.PG.NodaTime.csproj @@ -2,8 +2,6 @@ Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime - net8.0 - net8.0 Shay Rojansky NodaTime support plugin for PostgreSQL/Npgsql Entity Framework Core provider. diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj index c344ed2e2..f6dd61b6c 100644 --- a/src/EFCore.PG/EFCore.PG.csproj +++ b/src/EFCore.PG/EFCore.PG.csproj @@ -3,7 +3,6 @@ Npgsql.EntityFrameworkCore.PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL - net8.0 Shay Rojansky;Austin Drenski;Yoh Deadfall; PostgreSQL/Npgsql provider for Entity Framework Core. diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 8648a51f4..b4dbbaa8a 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,7 +3,6 @@ - net9.0 false false diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index ce5bd43a8..8050bc586 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -2,7 +2,7 @@ Npgsql.EntityFrameworkCore.PostgreSQL.FunctionalTests - Npgsql.EntityFrameworkCore.PostgreSQL + Microsoft.EntityFrameworkCore true diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs index 301af4a33..ce9b5378a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs @@ -97,60 +97,11 @@ WHERE length(s."Banner") = length(@byteArrayParam) #region DateTimeOffset - public override async Task Where_datetimeoffset_utcnow(bool async) - { - await base.Where_datetimeoffset_utcnow(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Timeline" <> now() -"""); - } - - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a - // non-UTC DateTimeOffset. - public override Task Where_datetimeoffset_now(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_now(async)); - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a // non-UTC DateTimeOffset. public override Task DateTimeOffsetNow_minus_timespan(bool async) => Assert.ThrowsAsync(() => base.DateTimeOffsetNow_minus_timespan(async)); - // SQL translation not implemented, too annoying - public override Task Where_datetimeoffset_millisecond_component(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_millisecond_component(async)); - - public override async Task Where_datetimeoffset_date_component(bool async) - { - await AssertQuery( - async, - ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1) - select m); - - AssertSql( - """ -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 '0001-01-01T00:00:00' -"""); - } - - public override async Task Where_datetimeoffset_hour_component(bool async) - { - await base.Where_datetimeoffset_hour_component(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('hour', m."Timeline" AT TIME ZONE 'UTC')::int = 10 -"""); - } - public override async Task DateTimeOffset_Date_returns_datetime(bool async) { var dateTimeOffset = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)); @@ -208,23 +159,6 @@ await AssertQueryScalar( """); } - public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_milliseconds(async)); - - public override Task DateTimeOffset_to_unix_time_seconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); - - public override async Task Time_of_day_datetimeoffset(bool async) - { - await base.Time_of_day_datetimeoffset(async); - - AssertSql( - """ -SELECT CAST(m."Timeline" AT TIME ZONE 'UTC' AS time) -FROM "Missions" AS m -"""); - } - #endregion DateTimeOffset #region DateTime @@ -241,92 +175,6 @@ public virtual Task Where_datetime_subtraction(bool async) #endregion DateTime - #region TimeSpan - - public override async Task TimeSpan_Hours(bool async) - { - await base.TimeSpan_Hours(async); - - AssertSql( - """ -SELECT floor(date_part('hour', m."Duration"))::int -FROM "Missions" AS m -"""); - } - - public override async Task TimeSpan_Minutes(bool async) - { - await base.TimeSpan_Minutes(async); - - AssertSql( - """ -SELECT floor(date_part('minute', m."Duration"))::int -FROM "Missions" AS m -"""); - } - - public override async Task TimeSpan_Seconds(bool async) - { - await base.TimeSpan_Seconds(async); - - AssertSql( - """ -SELECT floor(date_part('second', m."Duration"))::int -FROM "Missions" AS m -"""); - } - - public override async Task TimeSpan_Milliseconds(bool async) - { - await base.TimeSpan_Milliseconds(async); - - AssertSql( - """ -SELECT floor(date_part('millisecond', m."Duration"))::int % 1000 -FROM "Missions" AS m -"""); - } - - // Test runs successfully, but some time difference and precision issues and fail the assertion - public override Task Where_TimeSpan_Hours(bool async) - => Task.CompletedTask; - - public override async Task Where_TimeSpan_Minutes(bool async) - { - await base.Where_TimeSpan_Minutes(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE floor(date_part('minute', m."Duration"))::int = 2 -"""); - } - - public override async Task Where_TimeSpan_Seconds(bool async) - { - await base.Where_TimeSpan_Seconds(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE floor(date_part('second', m."Duration"))::int = 3 -"""); - } - - public override async Task Where_TimeSpan_Milliseconds(bool async) - { - await base.Where_TimeSpan_Milliseconds(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE floor(date_part('millisecond', m."Duration"))::int % 1000 = 456 -"""); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Where_TimeSpan_TotalDays(bool async) @@ -449,363 +297,6 @@ GROUP BY m."Id" """); } - #endregion TimeSpan - - #region DateOnly - - [ConditionalTheory(Skip = "https://github.com/npgsql/efcore.pg/issues/2039")] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Where_DateOnly_ctor(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where( - m => - new DateOnly(EF.Property(m, "Date").Year, EF.Property(m, "Date").Month, 1) - == new DateOnly(1996, 9, 11))); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE make_date(date_part('year', m."Date")::int, date_part('month', m."Date")::int, 1) = DATE '1996-09-11' -"""); - } - - [ConditionalTheory(Skip = "https://github.com/npgsql/efcore.pg/issues/2039")] - public override async Task Where_DateOnly_Year(bool async) - { - await base.Where_DateOnly_Year(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('year', m."Date")::int = 1990 -"""); - } - - public override async Task Where_DateOnly_Month(bool async) - { - await base.Where_DateOnly_Month(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('month', m."Date")::int = 11 -"""); - } - - public override async Task Where_DateOnly_Day(bool async) - { - await base.Where_DateOnly_Day(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('day', m."Date")::int = 10 -"""); - } - - public override async Task Where_DateOnly_DayOfYear(bool async) - { - await base.Where_DateOnly_DayOfYear(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('doy', m."Date")::int = 314 -"""); - } - - public override async Task Where_DateOnly_DayOfWeek(bool async) - { - await base.Where_DateOnly_DayOfWeek(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE floor(date_part('dow', m."Date"))::int = 6 -"""); - } - - public override async Task Where_DateOnly_AddYears(bool async) - { - await base.Where_DateOnly_AddYears(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE CAST(m."Date" + INTERVAL '3 years' AS date) = DATE '1993-11-10' -"""); - } - - public override async Task Where_DateOnly_AddMonths(bool async) - { - await base.Where_DateOnly_AddMonths(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE CAST(m."Date" + INTERVAL '3 months' AS date) = DATE '1991-02-10' -"""); - } - - public override async Task Where_DateOnly_AddDays(bool async) - { - await base.Where_DateOnly_AddDays(async); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Date" + 3 = DATE '1990-11-13' -"""); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Select_DateOnly_AddDays(bool async) - { - await AssertQuery( - async, - ss => ss.Set() - // We filter out DateOnly.MinValue which maps to -infinity - .Where(m => m.Date != DateOnly.MinValue) - .Select(m => m.Date.AddDays(3))); - - AssertSql( - """ -@MinValue='01/01/0001' (DbType = Date) - -SELECT m."Date" + 3 -FROM "Missions" AS m -WHERE m."Date" <> @MinValue -"""); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Select_DateOnly_AddMonths(bool async) - { - await AssertQuery( - async, - ss => ss.Set() - // We filter out DateOnly.MinValue which maps to -infinity - .Where(m => m.Date != DateOnly.MinValue) - .Select(m => m.Date.AddMonths(3))); - - AssertSql( - """ -@MinValue='01/01/0001' (DbType = Date) - -SELECT CAST(m."Date" + INTERVAL '3 months' AS date) -FROM "Missions" AS m -WHERE m."Date" <> @MinValue -"""); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Select_DateOnly_AddYears(bool async) - { - await AssertQuery( - async, - ss => ss.Set() - // We filter out DateOnly.MinValue which maps to -infinity - .Where(m => m.Date != DateOnly.MinValue) - .Select(m => m.Date.AddYears(3))); - - AssertSql( - """ -@MinValue='01/01/0001' (DbType = Date) - -SELECT CAST(m."Date" + INTERVAL '3 years' AS date) -FROM "Missions" AS m -WHERE m."Date" <> @MinValue -"""); - } - - #endregion DateOnly - - #region TimeOnly - - public override async Task Where_TimeOnly_Hour(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.Hour == 10).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('hour', m."Time")::int = 10 -"""); - } - - public override async Task Where_TimeOnly_Minute(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.Minute == 15).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('minute', m."Time")::int = 15 -"""); - } - - public override async Task Where_TimeOnly_Second(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.Second == 50).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE date_part('second', m."Time")::int = 50 -"""); - } - - public override Task Where_TimeOnly_Millisecond(bool async) - => Task.CompletedTask; // Translation not implemented - - public override async Task Where_TimeOnly_AddHours(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.AddHours(3) == new TimeOnly(13, 15, 50, 500)).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time" + INTERVAL '3 hours' = TIME '13:15:50.5' -"""); - } - - public override async Task Where_TimeOnly_AddMinutes(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.AddMinutes(3) == new TimeOnly(10, 18, 50, 500)).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time" + INTERVAL '3 mins' = TIME '10:18:50.5' -"""); - } - - public override async Task Where_TimeOnly_Add_TimeSpan(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.Add(new TimeSpan(3, 0, 0)) == new TimeOnly(13, 15, 50, 500)).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time" + INTERVAL '03:00:00' = TIME '13:15:50.5' -"""); - } - - public override async Task Where_TimeOnly_IsBetween(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.IsBetween(new TimeOnly(10, 0, 0), new TimeOnly(11, 0, 0))).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time" >= TIME '10:00:00' AND m."Time" < TIME '11:00:00' -"""); - } - - public override async Task Where_TimeOnly_subtract_TimeOnly(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time - new TimeOnly(10, 0, 0) == new TimeSpan(0, 0, 15, 50, 500)).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time" - TIME '10:00:00' = INTERVAL '00:15:50.5' -"""); - } - - [ConditionalFact] - public virtual async Task TimeOnly_FromTimeSpan() - { - // We cannot evaluate TimeOnly.FromTimeSpan in .NET since there are some rows were the result is a day or more. - await using var ctx = CreateContext(); - - var id = (await ctx.Set().Where(m => TimeOnly.FromTimeSpan(m.Duration) == new TimeOnly(1, 2, 3)).SingleAsync()).Id; - Assert.Equal(1, id); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Duration"::time without time zone = TIME '01:02:03' -LIMIT 2 -"""); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Where_TimeOnly_ToTimeSpan(bool async) - { - await AssertQuery( - async, - ss => ss.Set().Where(m => m.Time.ToTimeSpan() == new TimeSpan(15, 30, 10)).AsTracking()); - - AssertSql( - """ -SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" -FROM "Missions" AS m -WHERE m."Time"::interval = INTERVAL '15:30:10' -"""); - } - - #endregion TimeOnly - - // TODO: #3406 - public override Task Where_datetimeoffset_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); - - public override Task Where_datetimeoffset_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); - - public override Task Where_timespan_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); - - public override Task Where_timespan_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); - - public override Task Where_timeonly_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); - - public override Task Where_timeonly_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index ebd49c4d3..fc6ecc4a5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -17,50 +17,6 @@ public NorthwindFunctionsQueryNpgsqlTest( Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public override async Task IsNullOrWhiteSpace_in_predicate(bool async) - { - await base.IsNullOrWhiteSpace_in_predicate(async); - - AssertSql( - """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" -FROM "Customers" AS c -WHERE c."Region" IS NULL OR btrim(c."Region", E' \t\n\r') = '' -"""); - } - - // PostgreSQL only has log(x, base) over numeric, may be possible to cast back and forth though - public override Task Where_math_log_new_base(bool async) - => AssertTranslationFailed(() => base.Where_math_log_new_base(async)); - - // PostgreSQL only has log(x, base) over numeric, may be possible to cast back and forth though - public override Task Where_mathf_log_new_base(bool async) - => AssertTranslationFailed(() => base.Where_mathf_log_new_base(async)); - - // PostgreSQL only has round(v, s) over numeric, may be possible to cast back and forth though - public override Task Where_mathf_round2(bool async) - => AssertTranslationFailed(() => base.Where_mathf_round2(async)); - - // Convert on DateTime not yet supported - public override Task Convert_ToString(bool async) - => AssertTranslationFailed(() => base.Convert_ToString(async)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public override async Task String_Join_non_aggregate(bool async) - { - await base.String_Join_non_aggregate(async); - - AssertSql( - """ -@foo='foo' - -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" -FROM "Customers" AS c -WHERE concat_ws('|', c."CompanyName", @foo, '', 'bar') = 'Around the Horn|foo||bar' -"""); - } - #region Substring [ConditionalTheory] @@ -570,42 +526,6 @@ public void Regex_Count_with_unsupported_option() #endregion Regex - #region Guid - - private static string UuidGenerationFunction { get; } = TestEnvironment.PostgresVersion.AtLeast(13) - ? "gen_random_uuid" - : "uuid_generate_v4"; - - public override async Task Where_guid_newguid(bool async) - { - await base.Where_guid_newguid(async); - - AssertSql( - $""" -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" -FROM "Customers" AS c -WHERE {UuidGenerationFunction}() <> '00000000-0000-0000-0000-000000000000' -"""); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task OrderBy_Guid_NewGuid(bool async) - { - await AssertQuery( - async, - ods => ods.Set().OrderBy(od => Guid.NewGuid()).Select(x => x)); - - AssertSql( - $""" -SELECT o."OrderID", o."ProductID", o."Discount", o."Quantity", o."UnitPrice" -FROM "Order Details" AS o -ORDER BY {UuidGenerationFunction}() NULLS FIRST -"""); - } - - #endregion - #region PadLeft, PadRight [ConditionalTheory] @@ -684,66 +604,6 @@ public Task PadRight_char_with_parameter(bool async) #region Aggregate functions - public override async Task String_Join_over_non_nullable_column(bool async) - { - await base.String_Join_over_non_nullable_column(async); - - AssertSql( - """ -SELECT c."City", COALESCE(string_agg(c."CustomerID", '|'), '') AS "Customers" -FROM "Customers" AS c -GROUP BY c."City" -"""); - } - - public override async Task String_Join_over_nullable_column(bool async) - { - await base.String_Join_over_nullable_column(async); - - AssertSql( - """ -SELECT c."City", COALESCE(string_agg(COALESCE(c."Region", ''), '|'), '') AS "Regions" -FROM "Customers" AS c -GROUP BY c."City" -"""); - } - - public override async Task String_Join_with_predicate(bool async) - { - await base.String_Join_with_predicate(async); - - AssertSql( - """ -SELECT c."City", COALESCE(string_agg(c."CustomerID", '|') FILTER (WHERE length(c."ContactName")::int > 10), '') AS "Customers" -FROM "Customers" AS c -GROUP BY c."City" -"""); - } - - public override async Task String_Join_with_ordering(bool async) - { - await base.String_Join_with_ordering(async); - - AssertSql( - """ -SELECT c."City", COALESCE(string_agg(c."CustomerID", '|' ORDER BY c."CustomerID" DESC NULLS LAST), '') AS "Customers" -FROM "Customers" AS c -GROUP BY c."City" -"""); - } - - public override async Task String_Concat(bool async) - { - await base.String_Concat(async); - - AssertSql( - """ -SELECT c."City", COALESCE(string_agg(c."CustomerID", ''), '') AS "Customers" -FROM "Customers" AS c -GROUP BY c."City" -"""); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task GroupBy_ArrayAgg(bool async) @@ -1193,38 +1053,6 @@ SELECT NULLIF(o."OrderID", 1) #endregion - #region Unsupported - - // PostgreSQL does not have strpos with starting position - public override Task Indexof_with_constant_starting_position(bool async) - => AssertTranslationFailed(() => base.Indexof_with_constant_starting_position(async)); - - // PostgreSQL does not have strpos with starting position - public override Task Indexof_with_parameter_starting_position(bool async) - => AssertTranslationFailed(() => base.Indexof_with_parameter_starting_position(async)); - - // These tests convert (among other things) to and from boolean, which PostgreSQL - // does not support (https://github.com/dotnet/efcore/issues/19606) - public override Task Convert_ToBoolean(bool async) - => Task.CompletedTask; - - public override Task Convert_ToByte(bool async) - => Task.CompletedTask; - - public override Task Convert_ToDecimal(bool async) - => Task.CompletedTask; - - public override Task Convert_ToDouble(bool async) - => Task.CompletedTask; - - public override Task Convert_ToInt16(bool async) - => Task.CompletedTask; - - public override Task Convert_ToInt64(bool async) - => Task.CompletedTask; - - #endregion Unsupported - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index f92e815ea..0374ac3b4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -199,18 +199,6 @@ ORDER BY o0."CustomerID" NULLS FIRST """); } - public override async Task Where_bitwise_binary_xor(bool async) - { - await base.Where_bitwise_binary_xor(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE (o."OrderID" # 1) = 10249 -"""); - } - // TODO: #3406 public override Task Where_nanosecond_and_microsecond_component(bool async) => AssertTranslationFailed(() => base.Where_nanosecond_and_microsecond_component(async)); diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs index 21ed30228..f1a26d851 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs @@ -13,162 +13,6 @@ public NorthwindWhereQueryNpgsqlTest(NorthwindQueryNpgsqlFixture Task.CompletedTask; // See TimestampQueryTest - - public override Task Where_datetime_utcnow(bool async) - => Task.CompletedTask; // See TimestampQueryTest - - public override async Task Where_datetime_today(bool async) - { - await base.Where_datetime_today(async); - - AssertSql( - """ -SELECT e."EmployeeID", e."City", e."Country", e."FirstName", e."ReportsTo", e."Title" -FROM "Employees" AS e -"""); - } - - public override async Task Time_of_day_datetime(bool async) - { - await base.Time_of_day_datetime(async); - - AssertSql( - """ -SELECT o."OrderDate"::time -FROM "Orders" AS o -"""); - } - - public override async Task Where_datetime_date_component(bool async) - { - await base.Where_datetime_date_component(async); - - AssertSql( - """ -@myDatetime='1998-05-04T00:00:00.0000000' - -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_trunc('day', o."OrderDate") = @myDatetime -"""); - } - - public override async Task Where_date_add_year_constant_component(bool async) - { - await base.Where_date_add_year_constant_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('year', o."OrderDate" + INTERVAL '-1 years')::int = 1997 -"""); - } - - public override async Task Where_datetime_year_component(bool async) - { - await base.Where_datetime_year_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('year', o."OrderDate")::int = 1998 -"""); - } - - public override async Task Where_datetime_month_component(bool async) - { - await base.Where_datetime_month_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('month', o."OrderDate")::int = 4 -"""); - } - - public override async Task Where_datetime_dayOfYear_component(bool async) - { - await base.Where_datetime_dayOfYear_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('doy', o."OrderDate")::int = 68 -"""); - } - - public override async Task Where_datetime_day_component(bool async) - { - await base.Where_datetime_day_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('day', o."OrderDate")::int = 4 -"""); - } - - public override async Task Where_datetime_hour_component(bool async) - { - await base.Where_datetime_hour_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('hour', o."OrderDate")::int = 0 -"""); - } - - public override async Task Where_datetime_minute_component(bool async) - { - await base.Where_datetime_minute_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('minute', o."OrderDate")::int = 0 -"""); - } - - public override async Task Where_datetime_second_component(bool async) - { - await base.Where_datetime_second_component(async); - - AssertSql( - """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" -FROM "Orders" AS o -WHERE date_part('second', o."OrderDate")::int = 0 -"""); - } - - public override Task Where_datetime_millisecond_component(bool async) - => Task.CompletedTask; // SQL translation not implemented, too annoying - - public override Task Where_datetimeoffset_now_component(bool async) - => Task.CompletedTask; // https://github.com/npgsql/efcore.pg/issues/873 - - public override Task Where_datetimeoffset_utcnow_component(bool async) - => Task.CompletedTask; // https://github.com/npgsql/efcore.pg/issues/873 - - #endregion Date and time - - // Test uses DateTimeOffset with non-zero offset, which we don't support. - // See supported DateTimeOffset scenarios in TimestampQueryTest - public override Task Where_datetimeoffset_utcnow(bool async) - => Task.CompletedTask; - public override async Task Where_bitwise_xor(bool async) { await base.Where_bitwise_xor(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs index 455b7b4fd..83eec6989 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs @@ -11,10 +11,6 @@ public TPCGearsOfWarQueryNpgsqlTest(TPCGearsOfWarQueryNpgsqlFixture fixture, ITe Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - [ConditionalTheory(Skip = "https://github.com/npgsql/efcore.pg/issues/2039")] - public override Task Where_DateOnly_Year(bool async) - => base.Where_DateOnly_Year(async); - // Base implementation uses DateTimeOffset.Now, which we don't translate by design. Use DateTimeOffset.UtcNow instead. public override async Task Select_datetimeoffset_comparison_in_projection(bool async) { @@ -72,68 +68,11 @@ WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @dateTime """); } - public override async Task Where_datetimeoffset_date_component(bool async) - { - await AssertQuery( - async, - ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1) - select m); - - AssertSql( - """ -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 '0001-01-01T00:00:00' -"""); - } - - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a - // non-UTC DateTimeOffset. - public override Task Where_datetimeoffset_now(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_now(async)); - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a // non-UTC DateTimeOffset. public override Task DateTimeOffsetNow_minus_timespan(bool async) => Assert.ThrowsAsync(() => base.DateTimeOffsetNow_minus_timespan(async)); - // SQL translation not implemented, too annoying - public override Task Where_datetimeoffset_millisecond_component(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_millisecond_component(async)); - - public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_milliseconds(async)); - - public override Task DateTimeOffset_to_unix_time_seconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); - - // Test runs successfully, but some time difference and precision issues and fail the assertion - public override Task Where_TimeSpan_Hours(bool async) - => Task.CompletedTask; - - public override Task Where_TimeOnly_Millisecond(bool async) - => Task.CompletedTask; // Translation not implemented - - // TODO: #3406 - public override Task Where_datetimeoffset_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); - - public override Task Where_datetimeoffset_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); - - public override Task Where_timespan_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); - - public override Task Where_timespan_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); - - public override Task Where_timeonly_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); - - public override Task Where_timeonly_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs index 441594383..e2512a42c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs @@ -15,10 +15,6 @@ public TPTGearsOfWarQueryNpgsqlTest(TPTGearsOfWarQueryNpgsqlFixture fixture, ITe // TODO: #1232 // protected override bool CanExecuteQueryString => true; - [ConditionalTheory(Skip = "https://github.com/npgsql/efcore.pg/issues/2039")] - public override Task Where_DateOnly_Year(bool async) - => base.Where_DateOnly_Year(async); - // Base implementation uses DateTimeOffset.Now, which we don't translate by design. Use DateTimeOffset.UtcNow instead. public override async Task Select_datetimeoffset_comparison_in_projection(bool async) { @@ -76,68 +72,11 @@ WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @dateTime """); } - public override async Task Where_datetimeoffset_date_component(bool async) - { - await AssertQuery( - async, - ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1) - select m); - - AssertSql( - """ -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 '0001-01-01T00:00:00' -"""); - } - - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a - // non-UTC DateTimeOffset. - public override Task Where_datetimeoffset_now(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_now(async)); - // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a // non-UTC DateTimeOffset. public override Task DateTimeOffsetNow_minus_timespan(bool async) => Assert.ThrowsAsync(() => base.DateTimeOffsetNow_minus_timespan(async)); - // SQL translation not implemented, too annoying - public override Task Where_datetimeoffset_millisecond_component(bool async) - => Assert.ThrowsAsync(() => base.Where_datetimeoffset_millisecond_component(async)); - - public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_milliseconds(async)); - - public override Task DateTimeOffset_to_unix_time_seconds(bool async) - => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); - - // Test runs successfully, but some time difference and precision issues and fail the assertion - public override Task Where_TimeSpan_Hours(bool async) - => Task.CompletedTask; - - public override Task Where_TimeOnly_Millisecond(bool async) - => Task.CompletedTask; // Translation not implemented - - // TODO: #3406 - public override Task Where_datetimeoffset_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); - - public override Task Where_datetimeoffset_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); - - public override Task Where_timespan_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); - - public override Task Where_timespan_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); - - public override Task Where_timeonly_microsecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); - - public override Task Where_timeonly_nanosecond_component(bool async) - => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/BasicTypesQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/BasicTypesQueryNpgsqlFixture.cs new file mode 100644 index 000000000..03140627c --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/BasicTypesQueryNpgsqlFixture.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +public class BasicTypesQueryNpgsqlFixture : BasicTypesQueryFixtureBase, ITestSqlLoggerFactory +{ + private BasicTypesData? _expectedData; + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + new NpgsqlDbContextOptionsBuilder(base.AddOptions(builder)).SetPostgresVersion(TestEnvironment.PostgresVersion); + return builder; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + => modelBuilder.HasPostgresExtension("uuid-ossp"); + + protected override Task SeedAsync(BasicTypesContext context) + { + _expectedData ??= LoadAndTweakData(); + context.AddRange(_expectedData.BasicTypesEntities); + context.AddRange(_expectedData.NullableBasicTypesEntities); + return context.SaveChangesAsync(); + } + + public override ISetSource GetExpectedData() + => _expectedData ??= LoadAndTweakData(); + + private BasicTypesData LoadAndTweakData() + { + var data = (BasicTypesData)base.GetExpectedData(); + + foreach (var item in data.BasicTypesEntities) + { + // For all relevant temporal types, chop sub-microsecond precision which PostgreSQL does not support. + // Temporal types which aren't set (default) get mapped to -infinity on PostgreSQL; this value causes many tests to fail. + + if (item.DateTime == default) + { + item.DateTime += TimeSpan.FromSeconds(1); + } + + // PostgreSQL maps DateTime to timestamptz by default, but that represents UTC timestamps which require DateTimeKind.Utc. + item.DateTime = DateTime.SpecifyKind(new DateTime(StripSubMicrosecond(item.DateTime.Ticks)), DateTimeKind.Utc); + + if (item.DateOnly == default) + { + item.DateOnly = item.DateOnly.AddDays(1); + } + + item.TimeOnly = new TimeOnly(StripSubMicrosecond(item.TimeOnly.Ticks)); + item.TimeSpan = new TimeSpan(StripSubMicrosecond(item.TimeSpan.Ticks)); + + if (item.DateTimeOffset == default) + { + item.DateTimeOffset += TimeSpan.FromSeconds(1); + } + + // PostgreSQL doesn't have a real DateTimeOffset type; we map .NET DateTimeOffset to timestamptz, which represents a UTC + // timestamp, and so we only support offset=0. + // Also chop sub-microsecond precision which PostgreSQL does not support. + item.DateTimeOffset = new DateTimeOffset(StripSubMicrosecond(item.DateTimeOffset.Ticks), TimeSpan.Zero); + } + + // Do the same for the nullable counterparts + foreach (var item in data.NullableBasicTypesEntities) + { + if (item.DateTime.HasValue) + { + item.DateTime = DateTime.SpecifyKind(new DateTime(StripSubMicrosecond(item.DateTime.Value.Ticks)), DateTimeKind.Utc); + } + + if (item.TimeOnly.HasValue) + { + item.TimeOnly = new TimeOnly(StripSubMicrosecond(item.TimeOnly.Value.Ticks)); + } + + if (item.TimeSpan.HasValue) + { + item.TimeSpan = new TimeSpan(StripSubMicrosecond(item.TimeSpan.Value.Ticks)); + } + + if (item.DateTimeOffset.HasValue) + { + item.DateTimeOffset = new DateTimeOffset(StripSubMicrosecond(item.DateTimeOffset.Value.Ticks), TimeSpan.Zero); + } + } + + return data; + + static long StripSubMicrosecond(long ticks) => ticks - (ticks % (TimeSpan.TicksPerMillisecond / 1000)); + } + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/EnumTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/EnumTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..807b6e767 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/EnumTranslationsNpgsqlTest.cs @@ -0,0 +1,310 @@ +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +#nullable enable + +public class EnumTranslationsNpgsqlTest : EnumTranslationsTestBase +{ + public EnumTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region Equality + + public override async Task Equality_to_constant(bool async) + { + await base.Equality_to_constant(async); + + 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."Enum" = 0 +"""); + } + + public override async Task Equality_to_parameter(bool async) + { + await base.Equality_to_parameter(async); + + AssertSql( + """ +@basicEnum='0' + +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."Enum" = @basicEnum +"""); + } + + public override async Task Equality_nullable_enum_to_constant(bool async) + { + await base.Equality_nullable_enum_to_constant(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."Enum" = 0 +"""); + } + + public override async Task Equality_nullable_enum_to_parameter(bool async) + { + await base.Equality_nullable_enum_to_parameter(async); + + AssertSql( + """ +@basicEnum='0' + +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."Enum" = @basicEnum +"""); + } + + public override async Task Equality_nullable_enum_to_null_constant(bool async) + { + await base.Equality_nullable_enum_to_null_constant(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."Enum" IS NULL +"""); + } + + public override async Task Equality_nullable_enum_to_null_parameter(bool async) + { + await base.Equality_nullable_enum_to_null_parameter(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."Enum" IS NULL +"""); + } + + public override async Task Equality_nullable_enum_to_nullable_parameter(bool async) + { + await base.Equality_nullable_enum_to_nullable_parameter(async); + + AssertSql( + """ +@basicEnum='0' (Nullable = true) + +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."Enum" = @basicEnum +"""); + } + + #endregion Equality + + public override async Task Bitwise_and_enum_constant(bool async) + { + await base.Bitwise_and_enum_constant(async); + + 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."FlagsEnum" & 1 > 0 +""", + // + """ +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."FlagsEnum" & 1 = 1 +"""); + } + + public override async Task Bitwise_and_integral_constant(bool async) + { + await base.Bitwise_and_integral_constant(async); + + 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."FlagsEnum" & 8 = 8 +""", + // + """ +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."FlagsEnum"::bigint & 8 = 8 +""", + // + """ +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."FlagsEnum"::smallint & 8 = 8 +"""); + } + + public override async Task Bitwise_and_nullable_enum_with_constant(bool async) + { + await base.Bitwise_and_nullable_enum_with_constant(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."FlagsEnum" & 8 > 0 +"""); + } + + public override async Task Where_bitwise_and_nullable_enum_with_null_constant(bool async) + { + await base.Where_bitwise_and_nullable_enum_with_null_constant(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."FlagsEnum" & NULL > 0 +"""); + } + + public override async Task Where_bitwise_and_nullable_enum_with_non_nullable_parameter(bool async) + { + await base.Where_bitwise_and_nullable_enum_with_non_nullable_parameter(async); + + AssertSql( + """ +@flagsEnum='8' + +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."FlagsEnum" & @flagsEnum > 0 +"""); + } + + public override async Task Where_bitwise_and_nullable_enum_with_nullable_parameter(bool async) + { + await base.Where_bitwise_and_nullable_enum_with_nullable_parameter(async); + + AssertSql( + """ +@flagsEnum='8' (Nullable = true) + +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."FlagsEnum" & @flagsEnum > 0 +""", + // + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."FlagsEnum" & NULL > 0 +"""); + } + + public override async Task Bitwise_or(bool async) + { + await base.Bitwise_or(async); + + 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."FlagsEnum" | 8 > 0 +"""); + } + + public override async Task Bitwise_projects_values_in_select(bool async) + { + await base.Bitwise_projects_values_in_select(async); + + AssertSql( + """ +SELECT b."FlagsEnum" & 8 = 8 AS "BitwiseTrue", b."FlagsEnum" & 8 = 4 AS "BitwiseFalse", b."FlagsEnum" & 8 AS "BitwiseValue" +FROM "BasicTypesEntities" AS b +WHERE b."FlagsEnum" & 8 = 8 +LIMIT 1 +"""); + } + + public override async Task HasFlag(bool async) + { + await base.HasFlag(async); + +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."FlagsEnum" & 8 = 8 +""", + // + """ +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."FlagsEnum" & 12 = 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."FlagsEnum" & 8 = 8 +""", + // + """ +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."FlagsEnum" & 8 = 8 +""", + // + """ +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 8 & b."FlagsEnum" = b."FlagsEnum" +""", + // + """ +SELECT b."FlagsEnum" & 8 = 8 AS "hasFlagTrue", b."FlagsEnum" & 4 = 4 AS "hasFlagFalse" +FROM "BasicTypesEntities" AS b +WHERE b."FlagsEnum" & 8 = 8 +LIMIT 1 +"""); + } + + public override async Task HasFlag_with_non_nullable_parameter(bool async) + { + await base.HasFlag_with_non_nullable_parameter(async); + + AssertSql( + """ +@flagsEnum='8' + +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."FlagsEnum" & @flagsEnum = @flagsEnum +"""); + } + + public override async Task HasFlag_with_nullable_parameter(bool async) + { + await base.HasFlag_with_nullable_parameter(async); + + AssertSql( + """ +@flagsEnum='8' (Nullable = true) + +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."FlagsEnum" & @flagsEnum = @flagsEnum +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..3f7fade26 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/MathTranslationsNpgsqlTest.cs @@ -0,0 +1,763 @@ +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +#nullable enable + +public class MathTranslationsNpgsqlTest : MathTranslationsTestBase +{ + public MathTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Abs_decimal(bool async) + { + await base.Abs_decimal(async); + + 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 abs(b."Decimal") = 9.5 +"""); + } + + public override async Task Abs_int(bool async) + { + await base.Abs_int(async); + + 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 abs(b."Int") = 9 +"""); + } + + public override async Task Abs_double(bool async) + { + await base.Abs_double(async); + + 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 abs(b."Double") = 9.5 +"""); + } + + public override async Task Abs_float(bool async) + { + await base.Abs_float(async); + + 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 abs(b."Float")::double precision = 9.5 +"""); + } + + public override async Task Ceiling(bool async) + { + await base.Ceiling(async); + + 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 ceiling(b."Double") = 9.0 +"""); + } + + public override async Task Ceiling_float(bool async) + { + await base.Ceiling_float(async); + + 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 ceiling(b."Float") = 9 +"""); + } + + public override async Task Floor_decimal(bool async) + { + await base.Floor_decimal(async); + + 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 floor(b."Decimal") = 8.0 +"""); + } + + public override async Task Floor_double(bool async) + { + await base.Floor_double(async); + + 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 floor(b."Double") = 8.0 +"""); + } + + public override async Task Floor_float(bool async) + { + await base.Floor_float(async); + + 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 floor(b."Float") = 8 +"""); + } + + public override async Task Power(bool async) + { + await base.Power(async); + + 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 power(b."Int"::double precision, 2.0) = 64.0 +"""); + } + + public override async Task Power_float(bool async) + { + await base.Power_float(async); + + 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 power(b."Float", 2) > 73 AND power(b."Float", 2) < 74 +"""); + } + + public override async Task Round_decimal(bool async) + { + await base.Round_decimal(async); + + 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 round(b."Decimal") = 9.0 +""", + // + """ +SELECT round(b."Decimal") +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Round_double(bool async) + { + await base.Round_double(async); + + 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 round(b."Double") = 9.0 +""", + // + """ +SELECT round(b."Double") +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Round_float(bool async) + { + await base.Round_float(async); + + 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 round(b."Float")::real = 9 +""", + // + """ +SELECT round(b."Float")::real +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Round_with_digits_decimal(bool async) + { + await base.Round_with_digits_decimal(async); + + 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 round(b."Decimal", 1) = 255.1 +"""); + } + + // PostgreSQL only has round(v, s) over numeric, may be possible to cast back and forth though + public override Task Round_with_digits_double(bool async) + => AssertTranslationFailed(() => base.Round_with_digits_double(async)); + + // PostgreSQL only has round(v, s) over numeric, may be possible to cast back and forth though + public override Task Round_with_digits_float(bool async) + => AssertTranslationFailed(() => base.Round_with_digits_float(async)); + + public override async Task Truncate_decimal(bool async) + { + await base.Truncate_decimal(async); + + 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 trunc(b."Decimal") = 8.0 +""", + // + """ +SELECT trunc(b."Decimal") +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Truncate_double(bool async) + { + await base.Truncate_double(async); + + 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 trunc(b."Double") = 8.0 +""", + // + """ +SELECT trunc(b."Double") +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Truncate_float(bool async) + { + await base.Truncate_float(async); + + 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 trunc(b."Float")::real = 8 +""", + // + """ +SELECT trunc(b."Float")::real +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Truncate_project_and_order_by_it_twice(bool async) + { + await base.Truncate_project_and_order_by_it_twice(async); + + AssertSql( + """ +SELECT trunc(b."Double") AS "A" +FROM "BasicTypesEntities" AS b +ORDER BY trunc(b."Double") NULLS FIRST +"""); + } + + // issue #16038 + // AssertSql( + // @"SELECT ROUND(CAST([o].[OrderID] AS float), 0, 1) AS [A] + //FROM [Orders] AS [o] + //WHERE [o].[OrderID] < 10250 + //ORDER BY [A]"); + public override async Task Truncate_project_and_order_by_it_twice2(bool async) + { + await base.Truncate_project_and_order_by_it_twice2(async); + + AssertSql( + """ +SELECT trunc(b."Double") AS "A" +FROM "BasicTypesEntities" AS b +ORDER BY trunc(b."Double") DESC NULLS LAST +"""); + } + + // issue #16038 + // AssertSql( + // @"SELECT ROUND(CAST([o].[OrderID] AS float), 0, 1) AS [A] + //FROM [Orders] AS [o] + //WHERE [o].[OrderID] < 10250 + //ORDER BY [A] DESC"); + public override async Task Truncate_project_and_order_by_it_twice3(bool async) + { + await base.Truncate_project_and_order_by_it_twice3(async); + + AssertSql( + """ +SELECT trunc(b."Double") AS "A" +FROM "BasicTypesEntities" AS b +ORDER BY trunc(b."Double") DESC NULLS LAST +"""); + } + + public override async Task Exp(bool async) + { + await base.Exp(async); + + 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 exp(b."Double") > 1.0 +"""); + } + + public override async Task Exp_float(bool async) + { + await base.Exp_float(async); + + 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 exp(b."Float") > 1 +"""); + } + + public override async Task Log(bool async) + { + await base.Log(async); + + 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."Double" > 0.0 AND ln(b."Double") <> 0.0 +"""); + } + + public override async Task Log_float(bool async) + { + await base.Log_float(async); + + 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."Float" > 0 AND ln(b."Float") <> 0 +"""); + } + + // PostgreSQL only has log(x, base) over numeric, may be possible to cast back and forth though + public override Task Log_with_newBase(bool async) + => AssertTranslationFailed(() => base.Log_with_newBase(async)); + + // PostgreSQL only has log(x, base) over numeric, may be possible to cast back and forth though + public override Task Log_with_newBase_float(bool async) + => AssertTranslationFailed(() => base.Log_with_newBase_float(async)); + + public override async Task Log10(bool async) + { + await base.Log10(async); + + 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."Double" > 0.0 AND log(b."Double") <> 0.0 +"""); + } + + public override async Task Log10_float(bool async) + { + await base.Log10_float(async); + + 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."Float" > 0 AND log(b."Float") <> 0 +"""); + } + + public override async Task Log2(bool async) + => await AssertTranslationFailed(() => base.Log2(async)); + + public override async Task Sqrt(bool async) + { + await base.Sqrt(async); + + 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."Double" > 0.0 AND sqrt(b."Double") > 0.0 +"""); + } + + public override async Task Sqrt_float(bool async) + { + await base.Sqrt_float(async); + + 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."Float" > 0 AND sqrt(b."Float") > 0 +"""); + } + + public override async Task Sign(bool async) + { + await base.Sign(async); + + 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 sign(b."Double")::int > 0 +"""); + } + + public override async Task Sign_float(bool async) + { + await base.Sign_float(async); + + 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 sign(b."Float")::int > 0 +"""); + } + + public override async Task Max(bool async) + { + await base.Max(async); + + 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 GREATEST(b."Int", b."Short" - 3) = b."Int" +"""); + } + + public override async Task Max_nested(bool async) + { + await base.Max_nested(async); + + 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 GREATEST(b."Short" - 3, b."Int", 1) = b."Int" +"""); + } + + public override async Task Max_nested_twice(bool async) + { + await base.Max_nested_twice(async); + + 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 GREATEST(1, b."Int", 2, b."Short" - 3) = b."Int" +"""); + } + + public override async Task Min(bool async) + { + await base.Min(async); + + 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 LEAST(b."Int", b."Short" + 3) = b."Int" +"""); + } + + public override async Task Min_nested(bool async) + { + await base.Min_nested(async); + + 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 LEAST(b."Short" + 3, b."Int", 99999) = b."Int" +"""); + } + + public override async Task Min_nested_twice(bool async) + { + await base.Min_nested_twice(async); + + 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 LEAST(99999, b."Int", 99998, b."Short" + 3) = b."Int" +"""); + } + + public override async Task Degrees(bool async) + { + await base.Degrees(async); + + 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 degrees(b."Double") > 0.0 +"""); + } + + public override async Task Degrees_float(bool async) + { + await base.Degrees_float(async); + + 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 degrees(b."Float") > 0 +"""); + } + + public override async Task Radians(bool async) + { + await base.Radians(async); + + 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 radians(b."Double") > 0.0 +"""); + } + + public override async Task Radians_float(bool async) + { + await base.Radians_float(async); + + 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 radians(b."Float") > 0 +"""); + } + + #region Trigonometry + + public override async Task Acos(bool async) + { + await base.Acos(async); + + 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."Double" >= -1.0 AND b."Double" <= 1.0 AND acos(b."Double") > 1.0 +"""); + } + + public override async Task Acos_float(bool async) + { + await base.Acos_float(async); + + 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."Float" >= -1 AND b."Float" <= 1 AND acos(b."Float") > 0 +"""); + } + + public override async Task Acosh(bool async) + => await AssertTranslationFailed(() => base.Acosh(async)); + + public override async Task Asin(bool async) + { + await base.Asin(async); + + 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."Double" >= -1.0 AND b."Double" <= 1.0 AND asin(b."Double") > -1.7976931348623157E+308 +"""); + } + + public override async Task Asin_float(bool async) + { + await base.Asin_float(async); + + 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."Float" >= -1 AND b."Float" <= 1 AND asin(b."Float")::double precision > -1.7976931348623157E+308 +"""); + } + + public override async Task Asinh(bool async) + => await AssertTranslationFailed(() => base.Asinh(async)); + + public override async Task Atan(bool async) + { + await base.Atan(async); + + 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 atan(b."Double") > 0.0 +"""); + } + + public override async Task Atan_float(bool async) + { + await base.Atan_float(async); + + 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 atan(b."Float") > 0 +"""); + } + + public override async Task Atanh(bool async) + => await AssertTranslationFailed(() => base.Atanh(async)); + + public override async Task Atan2(bool async) + { + await base.Atan2(async); + + 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 atan2(b."Double", 1.0) > 0.0 +"""); + } + + public override async Task Atan2_float(bool async) + { + await base.Atan2_float(async); + + 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 atan2(b."Float", 1) > 0 +"""); + } + + public override async Task Cos(bool async) + { + await base.Cos(async); + + 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 cos(b."Double") > 0.0 +"""); + } + + public override async Task Cos_float(bool async) + { + await base.Cos_float(async); + + 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 cos(b."Float") > 0 +"""); + } + + public override async Task Cosh(bool async) + => await AssertTranslationFailed(() => base.Cosh(async)); + + public override async Task Sin(bool async) + { + await base.Sin(async); + + 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 sin(b."Double") > 0.0 +"""); + } + + public override async Task Sin_float(bool async) + { + await base.Sin_float(async); + + 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 sin(b."Float") > 0 +"""); + } + + public override async Task Sinh(bool async) + => await AssertTranslationFailed(() => base.Sinh(async)); + + public override async Task Tan(bool async) + { + await base.Tan(async); + + 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 tan(b."Double") > 0.0 +"""); + } + + public override async Task Tan_float(bool async) + { + await base.Tan_float(async); + + 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 tan(b."Float") > 0 +"""); + } + + public override async Task Tanh(bool async) + => await AssertTranslationFailed(() => base.Tanh(async)); + + #endregion Trigonometry + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..c67594a82 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/MiscellaneousTranslationsNpgsqlTest.cs @@ -0,0 +1,517 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +public class MiscellaneousTranslationsNpgsqlTest : MiscellaneousTranslationsRelationalTestBase +{ + public MiscellaneousTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region Guid + + public override async Task Guid_new_with_constant(bool async) + { + await base.Guid_new_with_constant(async); + + 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."Guid" = 'df36f493-463f-4123-83f9-6b135deeb7ba' +"""); + } + + public override async Task Guid_new_with_parameter(bool async) + { + await base.Guid_new_with_parameter(async); + + AssertSql( + """ +@p='df36f493-463f-4123-83f9-6b135deeb7ba' + +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."Guid" = @p +"""); + } + + public override async Task Guid_ToString_projection(bool async) + { + await base.Guid_ToString_projection(async); + + AssertSql( + """ +SELECT b."Guid"::text +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Guid_NewGuid(bool async) + { + await base.Guid_NewGuid(async); + + if (TestEnvironment.PostgresVersion >= new Version(13, 0)) + { + 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 gen_random_uuid() <> '00000000-0000-0000-0000-000000000000' +"""); + } + else + { + 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 uuid_generate_v4() <> '00000000-0000-0000-0000-000000000000' +"""); + } + } + + #endregion Guid + + #region Byte array + + public override async Task Byte_array_Length(bool async) + { + await base.Byte_array_Length(async); + + 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 length(b."ByteArray") = 4 +"""); + } + + public override async Task Byte_array_array_index(bool async) + { + await base.Byte_array_array_index(async); + + 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 length(b."ByteArray") >= 3 AND get_byte(b."ByteArray", 2) = 190 +"""); + } + + public override async Task Byte_array_First(bool async) + { + await base.Byte_array_First(async); + + 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 length(b."ByteArray") >= 1 AND get_byte(b."ByteArray", 0)::smallint = 222 +"""); + } + + #endregion Byte array + + #region Random + + public override async Task Random_on_EF_Functions(bool async) + { + await base.Random_on_EF_Functions(async); + + AssertSql( + """ +SELECT count(*)::int +FROM "BasicTypesEntities" AS b +WHERE random() >= 0.0 AND random() < 1.0 +"""); + } + + public override async Task Random_Shared_Next_with_no_args(bool async) + { + await base.Random_Shared_Next_with_no_args(async); + + AssertSql(); + } + + public override async Task Random_Shared_Next_with_one_arg(bool async) + { + await base.Random_Shared_Next_with_one_arg(async); + + AssertSql(); + } + + public override async Task Random_Shared_Next_with_two_args(bool async) + { + await base.Random_Shared_Next_with_two_args(async); + + AssertSql(); + } + + public override async Task Random_new_Next_with_no_args(bool async) + { + await base.Random_new_Next_with_no_args(async); + + AssertSql(); + } + + public override async Task Random_new_Next_with_one_arg(bool async) + { + await base.Random_new_Next_with_one_arg(async); + + AssertSql(); + } + + public override async Task Random_new_Next_with_two_args(bool async) + { + await base.Random_new_Next_with_two_args(async); + + AssertSql(); + } + + #endregion Random + + #region Convert + + // These tests convert (among other things) to and from boolean, which PostgreSQL + // does not support (https://github.com/dotnet/efcore/issues/19606) + + public override async Task Convert_ToBoolean(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToBoolean(async)); + Assert.Equal("42846", exception.SqlState); + } + + public override async Task Convert_ToByte(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToByte(async)); + Assert.Equal("42846", exception.SqlState); + } + + public override async Task Convert_ToDecimal(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToDecimal(async)); + Assert.Equal("42846", exception.SqlState); + } + + public override async Task Convert_ToDouble(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToDouble(async)); + Assert.Equal("42846", exception.SqlState); + } + + public override async Task Convert_ToInt16(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToInt16(async)); + Assert.Equal("42846", exception.SqlState); + } + + public override async Task Convert_ToInt32(bool async) + { + await base.Convert_ToInt32(async); + +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 +""", + // + """ +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 +"""); + } + + public override async Task Convert_ToInt64(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Convert_ToInt64(async)); + Assert.Equal("42846", exception.SqlState); + } + + // Convert on DateTime not yet supported + public override Task Convert_ToString(bool async) + => AssertTranslationFailed(() => base.Convert_ToString(async)); + + #endregion Convert + + #region Compare + + public override async Task Int_Compare_to_simple_zero(bool async) + { + await base.Int_Compare_to_simple_zero(async); + +AssertSql( + """ +@orderId='8' + +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" = @orderId +""", + // + """ +@orderId='8' + +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" <> @orderId +""", + // + """ +@orderId='8' + +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" > @orderId +""", + // + """ +@orderId='8' + +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" <= @orderId +""", + // + """ +@orderId='8' + +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" > @orderId +""", + // + """ +@orderId='8' + +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" <= @orderId +"""); + } + + public override async Task DateTime_Compare_to_simple_zero(bool async, bool compareTo) + { + // The base test implementation uses an Unspecified DateTime, which isn't supported with PostgreSQL timestamptz + var dateTime = new DateTime(1998, 5, 4, 15, 30, 10, DateTimeKind.Utc); + + if (compareTo) + { + await AssertQuery( + async, + ss => ss.Set().Where(c => c.DateTime.CompareTo(dateTime) == 0)); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 != c.DateTime.CompareTo(dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => c.DateTime.CompareTo(dateTime) > 0)); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 >= c.DateTime.CompareTo(dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 < c.DateTime.CompareTo(dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => c.DateTime.CompareTo(dateTime) <= 0)); + } + else + { + await AssertQuery( + async, + ss => ss.Set().Where(c => DateTime.Compare(c.DateTime, dateTime) == 0)); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 != DateTime.Compare(c.DateTime, dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => DateTime.Compare(c.DateTime, dateTime) > 0)); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 >= DateTime.Compare(c.DateTime, dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => 0 < DateTime.Compare(c.DateTime, dateTime))); + + await AssertQuery( + async, + ss => ss.Set().Where(c => DateTime.Compare(c.DateTime, dateTime) <= 0)); + } + +AssertSql( +""" +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" = @dateTime +""", + // + """ +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" <> @dateTime +""", + // + """ +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" > @dateTime +""", + // + """ +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" <= @dateTime +""", + // + """ +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" > @dateTime +""", + // + """ +@dateTime='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" <= @dateTime +"""); + } + + public override async Task TimeSpan_Compare_to_simple_zero(bool async, bool compareTo) + { + await base.TimeSpan_Compare_to_simple_zero(async, compareTo); + + AssertSql( + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" = @timeSpan +""", + // + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" <> @timeSpan +""", + // + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" > @timeSpan +""", + // + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" <= @timeSpan +""", + // + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" > @timeSpan +""", + // + """ +@timeSpan='01:02:03' (DbType = Object) + +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."TimeSpan" <= @timeSpan +"""); + } + + #endregion Compare + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/OperatorTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/OperatorTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..070f971e6 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/OperatorTranslationsNpgsqlTest.cs @@ -0,0 +1,203 @@ +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +public class OperatorTranslationsNpgsqlTest : OperatorTranslationsTestBase +{ + public OperatorTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region Bitwise + + public override async Task Bitwise_or(bool async) + { + await base.Bitwise_or(async); + + 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."Int"::bigint | b."Long" = 7 +""", + // + """ +SELECT b."Int"::bigint | b."Long" +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Bitwise_or_over_boolean(bool async) + { + await base.Bitwise_or_over_boolean(async); + + 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."Int" = 12 OR b."String" = 'Seattle' +""", + // + """ +SELECT b."Int" = 12 OR b."String" = 'Seattle' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Bitwise_or_multiple(bool async) + { + await base.Bitwise_or_multiple(async); + + 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 CAST(b."Int" | b."Short" AS bigint) | b."Long" = 7 +"""); + } + + public override async Task Bitwise_and(bool async) + { + await base.Bitwise_and(async); + + 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."Int" & b."Short" = 2 +""", + // + """ +SELECT b."Int" & b."Short" +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Bitwise_and_over_boolean(bool async) + { + await base.Bitwise_and_over_boolean(async); + + 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."Int" = 8 AND b."String" = 'Seattle' +""", + // + """ +SELECT b."Int" = 8 AND b."String" = 'Seattle' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Bitwise_xor(bool async) + { + await base.Bitwise_xor(async); + + 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."Int" # b."Short") = 1 +""", + // + """ +SELECT b."Int" # b."Short" +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Bitwise_xor_over_boolean(bool async) + { + await base.Bitwise_xor_over_boolean(async); + + 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."Int" = b."Short") <> (b."String" = 'Seattle') +"""); + } + + public override async Task Bitwise_complement(bool async) + { + await base.Bitwise_complement(async); + + 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."Int" = -9 +"""); + } + + public override async Task Bitwise_and_or_over_boolean(bool async) + { + await base.Bitwise_and_or_over_boolean(async); + + 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."Int" = 12 AND b."Short" = 12) OR b."String" = 'Seattle' +"""); + } + + public override async Task Bitwise_or_with_logical_or(bool async) + { + await base.Bitwise_or_with_logical_or(async); + + 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."Int" = 12 OR b."Short" = 12 OR b."String" = 'Seattle' +"""); + } + + public override async Task Bitwise_and_with_logical_and(bool async) + { + await base.Bitwise_and_with_logical_and(async); + + 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."Int" = 8 AND b."Short" = 8 AND b."String" = 'Seattle' +"""); + } + + public override async Task Bitwise_or_with_logical_and(bool async) + { + await base.Bitwise_or_with_logical_and(async); + + 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."Int" = 8 OR b."Short" = 9) AND b."String" = 'Seattle' +"""); + } + + public override async Task Bitwise_and_with_logical_or(bool async) + { + await base.Bitwise_and_with_logical_or(async); + + 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."Int" = 12 AND b."Short" = 12) OR b."String" = 'Seattle' +"""); + } + + #endregion Bitwise + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/StringTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/StringTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..1b8e1d801 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/StringTranslationsNpgsqlTest.cs @@ -0,0 +1,1505 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; + +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +#nullable enable + +public class StringTranslationsNpgsqlTest : StringTranslationsRelationalTestBase +{ + public StringTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region Equals + + public override async Task Equals(bool async) + { + await base.Equals(async); + + 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."String" = 'Seattle' +"""); + } + + public override async Task Equals_with_OrdinalIgnoreCase(bool async) + { + await base.Equals_with_OrdinalIgnoreCase(async); + + AssertSql(); + } + + public override async Task Equals_with_Ordinal(bool async) + { + await base.Equals_with_Ordinal(async); + + AssertSql(); + } + + public override async Task Static_Equals(bool async) + { + await base.Static_Equals(async); + + 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."String" = 'Seattle' +"""); + } + + public override async Task Static_Equals_with_OrdinalIgnoreCase(bool async) + { + await base.Static_Equals_with_OrdinalIgnoreCase(async); + + AssertSql(); + } + + public override async Task Static_Equals_with_Ordinal(bool async) + { + await base.Static_Equals_with_Ordinal(async); + + AssertSql(); + } + + #endregion Equals + + #region Miscellaneous + + public override async Task Length(bool async) + { + await base.Length(async); + + 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 length(b."String")::int = 7 +"""); + } + + public override async Task ToUpper(bool async) + { + await base.ToUpper(async); + + 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 upper(b."String") = 'SEATTLE' +"""); + } + + public override async Task ToLower(bool async) + { + await base.ToLower(async); + + 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 lower(b."String") = 'seattle' +"""); + } + + #endregion Miscellaneous + + #region IndexOf + + public override async Task IndexOf(bool async) + { + await base.IndexOf(async); + + 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 strpos(b."String", 'eattl') - 1 <> -1 +"""); + } + + public override async Task IndexOf_with_empty_string(bool async) + { + await base.IndexOf_with_empty_string(async); + + 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 strpos(b."String", '') - 1 = 0 +"""); + } + + public override async Task IndexOf_with_one_parameter_arg(bool async) + { + await base.IndexOf_with_one_parameter_arg(async); + + AssertSql( + """ +@pattern='eattl' + +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 strpos(b."String", @pattern) - 1 = 1 +"""); + + } + + // PostgreSQL does not have strpos with starting position + public override Task IndexOf_with_constant_starting_position(bool async) + => AssertTranslationFailed(() => base.IndexOf_with_constant_starting_position(async)); + + // PostgreSQL does not have strpos with starting position + public override Task IndexOf_with_parameter_starting_position(bool async) + => AssertTranslationFailed(() => base.IndexOf_with_parameter_starting_position(async)); + + public override async Task IndexOf_after_ToString(bool async) + { + await base.IndexOf_after_ToString(async); + + 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 strpos(b."Int"::text, '55') - 1 = 1 +"""); + } + + public override async Task IndexOf_over_ToString(bool async) + { + await base.IndexOf_over_ToString(async); + + 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 strpos('12559', b."Int"::text) - 1 = 1 +"""); + } + + #endregion IndexOf + + #region Replace + + public override async Task Replace(bool async) + { + await base.Replace(async); + + 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 replace(b."String", 'Sea', 'Rea') = 'Reattle' +"""); + } + + public override async Task Replace_with_empty_string(bool async) + { + await base.Replace_with_empty_string(async); + + 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."String" <> '' AND replace(b."String", b."String", '') = '' +"""); + } + + public override async Task Replace_using_property_arguments(bool async) + { + await base.Replace_using_property_arguments(async); + + 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."String" <> '' AND replace(b."String", b."String", b."Int"::text) = b."Int"::text +"""); + } + + #endregion Replace + + #region Substring + + public override async Task Substring(bool async) + { + await base.Substring(async); + + 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 length(b."String")::int >= 3 AND substring(b."String", 2, 2) = 'ea' +"""); + } + + public override async Task Substring_with_one_arg_with_zero_startIndex(bool async) + { + await base.Substring_with_one_arg_with_zero_startIndex(async); + + 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 substring(b."String", 1) = 'Seattle' +"""); + } + + public override async Task Substring_with_one_arg_with_constant(bool async) + { + await base.Substring_with_one_arg_with_constant(async); + + 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 length(b."String")::int >= 1 AND substring(b."String", 2) = 'eattle' +"""); + } + + public override async Task Substring_with_one_arg_with_parameter(bool async) + { + await base.Substring_with_one_arg_with_parameter(async); + + AssertSql( + """ +@start='2' + +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."String")::int >= 2 AND substring(b."String", @start + 1) = 'attle' +"""); + } + + public override async Task Substring_with_two_args_with_zero_startIndex(bool async) + { + await base.Substring_with_two_args_with_zero_startIndex(async); + + 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 length(b."String")::int >= 3 AND substring(b."String", 1, 3) = 'Sea' +"""); + } + + public override async Task Substring_with_two_args_with_zero_length(bool async) + { + await base.Substring_with_two_args_with_zero_length(async); + + 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 length(b."String")::int >= 2 AND substring(b."String", 3, 0) = '' +"""); + } + + public override async Task Substring_with_two_args_with_parameter(bool async) + { + await base.Substring_with_two_args_with_parameter(async); + + AssertSql( + """ +@start='2' + +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."String")::int >= 5 AND substring(b."String", @start + 1, 3) = 'att' +"""); + } + + public override async Task Substring_with_two_args_with_IndexOf(bool async) + { + await base.Substring_with_two_args_with_IndexOf(async); + + 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."String" LIKE '%a%' AND substring(b."String", (strpos(b."String", 'a') - 1) + 1, 3) = 'att' +"""); + } + + #endregion Substring + + #region IsNullOrEmpty/Whitespace + + public override async Task IsNullOrEmpty(bool async) + { + await base.IsNullOrEmpty(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."String" IS NULL OR n."String" = '' +""", + // + """ +SELECT n."String" IS NULL OR n."String" = '' +FROM "NullableBasicTypesEntities" AS n +"""); + } + + public override async Task IsNullOrEmpty_negated(bool async) + { + await base.IsNullOrEmpty_negated(async); + + AssertSql( + """ +SELECT n."Id", n."Bool", n."Byte", n."ByteArray", n."DateOnly", n."DateTime", n."DateTimeOffset", n."Decimal", n."Double", n."Enum", n."FlagsEnum", n."Float", n."Guid", n."Int", n."Long", n."Short", n."String", n."TimeOnly", n."TimeSpan" +FROM "NullableBasicTypesEntities" AS n +WHERE n."String" IS NOT NULL AND n."String" <> '' +""", + // + """ +SELECT n."String" IS NOT NULL AND n."String" <> '' +FROM "NullableBasicTypesEntities" AS n +"""); + } + + public override async Task IsNullOrWhiteSpace(bool async) + { + await base.IsNullOrWhiteSpace(async); + + 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 btrim(b."String", E' \t\n\r') = '' +"""); + } + + #endregion IsNullOrEmpty/Whitespace + + #region StartsWith + + public override async Task StartsWith_Literal(bool async) + { + await base.StartsWith_Literal(async); + + 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."String" LIKE 'Se%' +"""); + } + + public override async Task StartsWith_Parameter(bool async) + { + await base.StartsWith_Parameter(async); + + AssertSql( + """ +@pattern_startswith='Se%' + +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."String" LIKE @pattern_startswith +"""); + } + + public override async Task StartsWith_Column(bool async) + { + await base.StartsWith_Column(async); + + 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 left(b."String", length(b."String")) = b."String" +"""); + } + + public override async Task StartsWith_with_StringComparison_Ordinal(bool async) + { + await base.StartsWith_with_StringComparison_Ordinal(async); + + AssertSql(); + } + + public override async Task StartsWith_with_StringComparison_OrdinalIgnoreCase(bool async) + { + await base.StartsWith_with_StringComparison_OrdinalIgnoreCase(async); + + AssertSql(); + } + + public override async Task StartsWith_with_StringComparison_unsupported(bool async) + { + await base.StartsWith_with_StringComparison_unsupported(async); + + AssertSql(); + } + + #endregion StartsWith + + #region EndsWith + + public override async Task EndsWith_Literal(bool async) + { + await base.EndsWith_Literal(async); + + 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."String" LIKE '%le' +"""); + } + + public override async Task EndsWith_Parameter(bool async) + { + await base.EndsWith_Parameter(async); + + AssertSql( + """ +@pattern_endswith='%le' + +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."String" LIKE @pattern_endswith +"""); + } + + public override async Task EndsWith_Column(bool async) + { + // SQL Server trims trailing whitespace for length calculations, making our EndsWith() column translation not work reliably in that + // case + await AssertQuery( + async, + ss => ss.Set().Where(b => b.String == "Seattle" && b.String.EndsWith(b.String))); + + 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."String" = 'Seattle' AND right(b."String", length(b."String")) = b."String" +"""); + } + + public override async Task EndsWith_with_StringComparison_Ordinal(bool async) + { + await base.EndsWith_with_StringComparison_Ordinal(async); + + AssertSql(); + } + + public override async Task EndsWith_with_StringComparison_OrdinalIgnoreCase(bool async) + { + await base.EndsWith_with_StringComparison_OrdinalIgnoreCase(async); + + AssertSql(); + } + + public override async Task EndsWith_with_StringComparison_unsupported(bool async) + { + await base.EndsWith_with_StringComparison_unsupported(async); + + AssertSql(); + } + + #endregion EndsWith + + #region Contains + + public override async Task Contains_Literal(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(c => c.String.Contains("eattl")), // SQL Server is case-insensitive by default + ss => ss.Set().Where(c => c.String.Contains("eattl", StringComparison.OrdinalIgnoreCase))); + + 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."String" LIKE '%eattl%' +"""); + } + + public override async Task Contains_Column(bool async) + { + await base.Contains_Column(async); + + 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 strpos(b."String", b."String") > 0 +""", + // + """ +SELECT strpos(b."String", b."String") > 0 +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Contains_negated(bool async) + { + await base.Contains_negated(async); + + 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."String" NOT LIKE '%eattle%' +""", + // + """ +SELECT b."String" NOT LIKE '%eattle%' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task Contains_with_StringComparison_Ordinal(bool async) + { + await base.Contains_with_StringComparison_Ordinal(async); + + AssertSql(); + } + + public override async Task Contains_with_StringComparison_OrdinalIgnoreCase(bool async) + { + await base.Contains_with_StringComparison_OrdinalIgnoreCase(async); + + AssertSql(); + } + + public override async Task Contains_with_StringComparison_unsupported(bool async) + { + await base.Contains_with_StringComparison_unsupported(async); + + AssertSql(); + } + + public override async Task Contains_constant_with_whitespace(bool async) + { + await base.Contains_constant_with_whitespace(async); + + 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."String" LIKE '% %' +"""); + } + + public override async Task Contains_parameter_with_whitespace(bool async) + { + await base.Contains_parameter_with_whitespace(async); + + AssertSql( + """ +@pattern_contains='% %' + +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."String" LIKE @pattern_contains +"""); + } + + #endregion Contains + + #region TrimStart + + public override async Task TrimStart_without_arguments(bool async) + { + await base.TrimStart_without_arguments(async); + + 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 ltrim(b."String", E' \t\n\r') = 'Boston ' +"""); + } + + public override async Task TrimStart_with_char_argument(bool async) + { + await base.TrimStart_with_char_argument(async); + + 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 ltrim(b."String", 'S') = 'eattle' +"""); + } + + public override async Task TrimStart_with_char_array_argument(bool async) + { + await base.TrimStart_with_char_array_argument(async); + + 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 ltrim(b."String", 'Se') = 'attle' +"""); + } + + #endregion TrimStart + + #region TrimEnd + + public override async Task TrimEnd_without_arguments(bool async) + { + await base.TrimEnd_without_arguments(async); + + 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 rtrim(b."String", E' \t\n\r') = ' Boston' +"""); + } + + public override async Task TrimEnd_with_char_argument(bool async) + { + await base.TrimEnd_with_char_argument(async); + + 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 rtrim(b."String", 'e') = 'Seattl' +"""); + } + + public override async Task TrimEnd_with_char_array_argument(bool async) + { + await base.TrimEnd_with_char_array_argument(async); + + 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 rtrim(b."String", 'le') = 'Seatt' +"""); + } + + #endregion TrimEnd + + #region Trim + + public override async Task Trim_without_argument_in_predicate(bool async) + { + await base.Trim_without_argument_in_predicate(async); + + 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 btrim(b."String", E' \t\n\r') = 'Boston' +"""); + } + + public override async Task Trim_with_char_argument_in_predicate(bool async) + { + await base.Trim_with_char_argument_in_predicate(async); + + 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 btrim(b."String", 'S') = 'eattle' +"""); + } + + public override async Task Trim_with_char_array_argument_in_predicate(bool async) + { + await base.Trim_with_char_array_argument_in_predicate(async); + + 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 btrim(b."String", 'Se') = 'attl' +"""); + } + + #endregion Trim + + #region Compare + + public override async Task Compare_simple_zero(bool async) + { + await base.Compare_simple_zero(async); + + 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."String" = 'Seattle' +""", + // + """ +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."String" <> 'Seattle' +""", + // + """ +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."String" > 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" > 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +"""); + } + + public override async Task Compare_simple_one(bool async) + { + await base.Compare_simple_one(async); + + 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."String" > 'Seattle' +""", + // + """ +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."String" < 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" >= 'Seattle' +""", + // + """ +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."String" >= 'Seattle' +"""); + } + + public override async Task Compare_with_parameter(bool async) + { + await base.Compare_with_parameter(async); + + AssertSql( + """ +@basicTypeEntity_String='Seattle' + +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."String" > @basicTypeEntity_String +""", + // + """ +@basicTypeEntity_String='Seattle' + +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."String" < @basicTypeEntity_String +""", + // + """ +@basicTypeEntity_String='Seattle' + +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."String" <= @basicTypeEntity_String +""", + // + """ +@basicTypeEntity_String='Seattle' + +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."String" <= @basicTypeEntity_String +""", + // + """ +@basicTypeEntity_String='Seattle' + +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."String" >= @basicTypeEntity_String +""", + // + """ +@basicTypeEntity_String='Seattle' + +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."String" >= @basicTypeEntity_String +"""); + } + + public override async Task Compare_simple_more_than_one(bool async) + { + await base.Compare_simple_more_than_one(async); + + 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 CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END = 42 +""", + // + """ +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 CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END > 42 +""", + // + """ +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 42 > CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END +"""); + } + + public override async Task Compare_nested(bool async) + { + await base.Compare_nested(async); + + 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."String" = 'M' || b."String" +""", + // + """ +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."String" <> substring(b."String", 1, 0) +""", + // + """ +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."String" > replace('Seattle', 'Sea', b."String") +""", + // + """ +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."String" <= 'M' || b."String" +""", + // + """ +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."String" > substring(b."String", 1, 0) +""", + // + """ +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."String" < replace('Seattle', 'Sea', b."String") +"""); + } + + public override async Task Compare_multi_predicate(bool async) + { + await base.Compare_multi_predicate(async); + + 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."String" >= 'Seattle' AND b."String" < 'Toronto' +"""); + } + + public override async Task CompareTo_simple_zero(bool async) + { + await base.CompareTo_simple_zero(async); + + 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."String" = 'Seattle' +""", + // + """ +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."String" <> 'Seattle' +""", + // + """ +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."String" > 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" > 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +"""); + } + + public override async Task CompareTo_simple_one(bool async) + { + await base.CompareTo_simple_one(async); + + 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."String" > 'Seattle' +""", + // + """ +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."String" < 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" <= 'Seattle' +""", + // + """ +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."String" >= 'Seattle' +""", + // + """ +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."String" >= 'Seattle' +"""); + } + + public override async Task CompareTo_with_parameter(bool async) + { + await base.CompareTo_with_parameter(async); + + AssertSql( + """ +@basicTypesEntity_String='Seattle' + +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."String" > @basicTypesEntity_String +""", + // + """ +@basicTypesEntity_String='Seattle' + +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."String" < @basicTypesEntity_String +""", + // + """ +@basicTypesEntity_String='Seattle' + +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."String" <= @basicTypesEntity_String +""", + // + """ +@basicTypesEntity_String='Seattle' + +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."String" <= @basicTypesEntity_String +""", + // + """ +@basicTypesEntity_String='Seattle' + +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."String" >= @basicTypesEntity_String +""", + // + """ +@basicTypesEntity_String='Seattle' + +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."String" >= @basicTypesEntity_String +"""); + } + + public override async Task CompareTo_simple_more_than_one(bool async) + { + await base.CompareTo_simple_more_than_one(async); + + 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 CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END = 42 +""", + // + """ +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 CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END > 42 +""", + // + """ +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 42 > CASE + WHEN b."String" = 'Seattle' THEN 0 + WHEN b."String" > 'Seattle' THEN 1 + WHEN b."String" < 'Seattle' THEN -1 +END +"""); + } + + public override async Task CompareTo_nested(bool async) + { + await base.CompareTo_nested(async); + + 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."String" = 'M' || b."String" +""", + // + """ +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."String" <> substring(b."String", 1, 0) +""", + // + """ +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."String" > replace('Seattle', 'Sea', b."String") +""", + // + """ +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."String" <= 'M' || b."String" +""", + // + """ +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."String" > substring(b."String", 1, 0) +""", + // + """ +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."String" < replace('Seattle', 'Sea', b."String") +"""); + } + + public override async Task Compare_to_multi_predicate(bool async) + { + await base.Compare_to_multi_predicate(async); + + 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."String" >= 'Seattle' AND b."String" < 'Toronto' +"""); + } + + #endregion Compare + + #region Join + + public override async Task Join_over_non_nullable_column(bool async) + { + await base.Join_over_non_nullable_column(async); + + AssertSql( + """ +SELECT b."Int" AS "Key", COALESCE(string_agg(b."String", '|'), '') AS "Strings" +FROM "BasicTypesEntities" AS b +GROUP BY b."Int" +"""); + } + + public override async Task Join_over_nullable_column(bool async) + { + await base.Join_over_nullable_column(async); + + AssertSql( + """ +SELECT n0."Key", COALESCE(string_agg(COALESCE(n0."String", ''), '|'), '') AS "Regions" +FROM ( + SELECT n."String", COALESCE(n."Int", 0) AS "Key" + FROM "NullableBasicTypesEntities" AS n +) AS n0 +GROUP BY n0."Key" +"""); + } + + public override async Task Join_with_predicate(bool async) + { + await base.Join_with_predicate(async); + + AssertSql( + """ +SELECT b."Int" AS "Key", COALESCE(string_agg(b."String", '|') FILTER (WHERE length(b."String")::int > 6), '') AS "Strings" +FROM "BasicTypesEntities" AS b +GROUP BY b."Int" +"""); + } + + public override async Task Join_with_ordering(bool async) + { + await base.Join_with_ordering(async); + + AssertSql( + """ +SELECT b."Int" AS "Key", COALESCE(string_agg(b."String", '|' ORDER BY b."Id" DESC NULLS LAST), '') AS "Strings" +FROM "BasicTypesEntities" AS b +GROUP BY b."Int" +"""); + } + + public override async Task Join_non_aggregate(bool async) + { + await base.Join_non_aggregate(async); + + AssertSql( + """ +@foo='foo' + +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 concat_ws('|', b."String", @foo, '', 'bar') = 'Seattle|foo||bar' +"""); + } + + #endregion Join + + #region Concatenation + + public override async Task Concat_aggregate(bool async) + { + await base.Concat_aggregate(async); + + AssertSql( + """ +SELECT b."Int" AS "Key", COALESCE(string_agg(b."String", ''), '') AS "BasicTypesEntitys" +FROM "BasicTypesEntities" AS b +GROUP BY b."Int" +"""); + } + + public override async Task Concat_string_int_comparison1(bool async) + { + await base.Concat_string_int_comparison1(async); + + AssertSql( + """ +@i='10' + +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."String" || @i::text = 'Seattle10' +"""); + } + + public override async Task Concat_string_int_comparison2(bool async) + { + await base.Concat_string_int_comparison2(async); + + AssertSql( + """ +@i='10' + +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 @i::text || b."String" = '10Seattle' +"""); + } + + public override async Task Concat_string_int_comparison3(bool async) + { + await base.Concat_string_int_comparison3(async); + + AssertSql( + """ +@p='30' +@j='21' + +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 @p::text || b."String" || @j::text || 42::text = '30Seattle2142' +"""); + } + + public override async Task Concat_string_int_comparison4(bool async) + { + await base.Concat_string_int_comparison4(async); + + 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."Int"::text || b."String" = '8Seattle' +"""); + } + + public override async Task Concat_string_string_comparison(bool async) + { + await base.Concat_string_string_comparison(async); + + AssertSql( + """ +@i='A' + +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 @i || b."String" = 'ASeattle' +"""); + } + + public override async Task Concat_method_comparison(bool async) + { + await base.Concat_method_comparison(async); + + AssertSql( + """ +@i='A' + +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 @i || b."String" = 'ASeattle' +"""); + } + + public override async Task Concat_method_comparison_2(bool async) + { + await base.Concat_method_comparison_2(async); + + AssertSql( + """ +@i='A' +@j='B' + +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 @i || @j || b."String" = 'ABSeattle' +"""); + } + + public override async Task Concat_method_comparison_3(bool async) + { + await base.Concat_method_comparison_3(async); + + AssertSql( + """ +@i='A' +@j='B' +@k='C' + +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 @i || @j || @k || b."String" = 'ABCSeattle' +"""); + } + + #endregion Concatenation + + #region LINQ Operators + + public override async Task FirstOrDefault(bool async) + { + await base.FirstOrDefault(async); + 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 substr(b."String", 1, 1) = 'S' +"""); + } + + public override async Task LastOrDefault(bool async) + { + await base.LastOrDefault(async); + 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 substr(b."String", length(b."String"), 1) = 'e' +"""); + } + + #endregion LINQ Operators + + #region Like + + public override async Task Where_Like_and_comparison(bool async) + { + await base.Where_Like_and_comparison(async); + + 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."String" LIKE 'S%' AND b."Int" = 8 +"""); + } + + public override async Task Where_Like_or_comparison(bool async) + { + await base.Where_Like_or_comparison(async); + + 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."String" LIKE 'S%' OR b."Int" = 2147483647 +"""); + } + + public override async Task Like_with_non_string_column_using_ToString(bool async) + { + await base.Like_with_non_string_column_using_ToString(async); + + 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."Int"::text LIKE '%5%' +"""); + } + + public override async Task Like_with_non_string_column_using_double_cast(bool async) + { + await base.Like_with_non_string_column_using_double_cast(async); + + 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."Int"::text LIKE '%5%' +"""); + } + + #endregion Like + + #region Regex + + public override async Task Regex_IsMatch(bool async) + { + await base.Regex_IsMatch(async); + + 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."String" ~ '(?p)^S' +"""); + } + + public override async Task Regex_IsMatch_constant_input(bool async) + { + await base.Regex_IsMatch_constant_input(async); + + 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 'Seattle' ~ ('(?p)' || b."String") +"""); + } + + #endregion Regex + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + protected override void ClearLog() + => Fixture.TestSqlLoggerFactory.Clear(); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTest.cs new file mode 100644 index 000000000..1d966e4da --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTest.cs @@ -0,0 +1,940 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; +using Xunit.Sdk; + +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +/// +/// Note that is mapped to PG timestamp with time zone, as is the provider default; +/// this causes issues with various tests. See also , which +/// explicitly maps to timestamp without time zone. +/// +public class TemporalTranslationsNpgsqlTest : TemporalTranslationsTestBase +{ + public TemporalTranslationsNpgsqlTest(BasicTypesQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region DateTime + + public override async Task DateTime_Now(bool async) + { + await base.DateTime_Now(async); + + AssertSql( + """ +@myDatetime='2015-04-10T00:00:00.0000000' + +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 now()::timestamp <> @myDatetime +"""); + } + + public override async Task DateTime_UtcNow(bool async) + { + // Overriding to set Kind=Utc for timestamptz + var myDatetime = DateTime.SpecifyKind(new DateTime(2015, 4, 10), DateTimeKind.Utc); + + await AssertQuery( + async, + ss => ss.Set().Where(c => DateTime.UtcNow != myDatetime)); + + AssertSql( + """ +@myDatetime='2015-04-10T00:00:00.0000000Z' (DbType = 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 now() <> @myDatetime +"""); + } + + // DateTime.Today returns a Local DateTime, which can't be compared with timestamptz + // (see TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest for a working version of this test) + public override Task DateTime_Today(bool async) + => Assert.ThrowsAsync(() => base.DateTime_Today(async)); + + public override async Task DateTime_Date(bool async) + { + // Overriding to set Kind=Utc for timestamptz + var myDatetime = DateTime.SpecifyKind(new DateTime(1998, 5, 4), DateTimeKind.Utc); + + await AssertQuery( + async, + ss => ss.Set().Where(o => o.DateTime.Date == myDatetime)); + + AssertSql( + """ +@myDatetime='1998-05-04T00:00:00.0000000Z' (DbType = 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 date_trunc('day', b."DateTime", 'UTC') = @myDatetime +"""); + } + + public override async Task DateTime_AddYear(bool async) + { + await base.DateTime_AddYear(async); + + 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 date_part('year', (b."DateTime" + INTERVAL '1 years') AT TIME ZONE 'UTC')::int = 1999 +"""); + } + + public override async Task DateTime_Year(bool async) + { + await base.DateTime_Year(async); + + 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 date_part('year', b."DateTime" AT TIME ZONE 'UTC')::int = 1998 +"""); + } + + public override async Task DateTime_Month(bool async) + { + await base.DateTime_Month(async); + + 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 date_part('month', b."DateTime" AT TIME ZONE 'UTC')::int = 5 +"""); + } + + public override async Task DateTime_DayOfYear(bool async) + { + await base.DateTime_DayOfYear(async); + + 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 date_part('doy', b."DateTime" AT TIME ZONE 'UTC')::int = 124 +"""); + } + + public override async Task DateTime_Day(bool async) + { + await base.DateTime_Day(async); + + 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 date_part('day', b."DateTime" AT TIME ZONE 'UTC')::int = 4 +"""); + } + + public override async Task DateTime_Hour(bool async) + { + await base.DateTime_Hour(async); + + 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 date_part('hour', b."DateTime" AT TIME ZONE 'UTC')::int = 15 +"""); + } + + public override async Task DateTime_Minute(bool async) + { + await base.DateTime_Minute(async); + + 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 date_part('minute', b."DateTime" AT TIME ZONE 'UTC')::int = 30 +"""); + } + + public override async Task DateTime_Second(bool async) + { + await base.DateTime_Second(async); + + 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 date_part('second', b."DateTime" AT TIME ZONE 'UTC')::int = 10 +"""); + } + + // SQL translation not implemented, too annoying + public override Task DateTime_Millisecond(bool async) + => AssertTranslationFailed(() => base.DateTime_Millisecond(async)); + + public override async Task DateTime_TimeOfDay(bool async) + { + await base.DateTime_TimeOfDay(async); + + 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 CAST(b."DateTime" AT TIME ZONE 'UTC' AS time) = TIME '00:00:00' +"""); + } + + public override async Task DateTime_subtract_and_TotalDays(bool async) + { + // Overriding to set Kind=Utc for timestamptz + var date = DateTime.SpecifyKind(new DateTime(1997, 1, 1), DateTimeKind.Utc); + + await AssertQuery( + async, + ss => ss.Set().Where(o => (o.DateTime - date).TotalDays > 365)); + + AssertSql( + """ +@date='1997-01-01T00:00:00.0000000Z' (DbType = 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 date_part('epoch', b."DateTime" - @date) / 86400.0 > 365.0 +"""); + } + + // DateTime.Parse() returns either a Local or Unspecified DateTime, which can't be compared with timestamptz + // (see TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest for a working version of this test) + public override Task DateTime_Parse_with_constant(bool async) + => Assert.ThrowsAsync(() => base.DateTime_Parse_with_constant(async)); + + // DateTime.Parse() returns either a Local or Unspecified DateTime, which can't be compared with timestamptz + // (see TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest for a working version of this test) + public override Task DateTime_Parse_with_parameter(bool async) + => Assert.ThrowsAsync(() => base.DateTime_Parse_with_parameter(async)); + + public override async Task DateTime_new_with_constant(bool async) + { + // Overriding to set Kind=Utc for timestamptz + await AssertQuery( + async, + ss => ss.Set().Where(o => o.DateTime == new DateTime(1998, 5, 4, 15, 30, 10, DateTimeKind.Utc))); + + 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."DateTime" = TIMESTAMPTZ '1998-05-04T15:30:10Z' +"""); + } + + public override async Task DateTime_new_with_parameters(bool async) + { + // Overriding to set Kind=Utc for timestamptz + var year = 1998; + var month = 5; + var date = 4; + var hour = 15; + + await AssertQuery( + async, + ss => ss.Set().Where(o => o.DateTime == new DateTime(year, month, date, hour, 30, 10, DateTimeKind.Utc))); + + AssertSql( + """ +@p='1998-05-04T15:30:10.0000000Z' (DbType = 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 b."DateTime" = @p +"""); + } + + #endregion DateTime + + #region DateOnly + + public override async Task DateOnly_Year(bool async) + { + await base.DateOnly_Year(async); + + 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 date_part('year', b."DateOnly")::int = 1990 +"""); + } + + public override async Task DateOnly_Month(bool async) + { + await base.DateOnly_Month(async); + + 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 date_part('month', b."DateOnly")::int = 11 +"""); + } + + public override async Task DateOnly_Day(bool async) + { + await base.DateOnly_Day(async); + + 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 date_part('day', b."DateOnly")::int = 10 +"""); + } + + public override async Task DateOnly_DayOfYear(bool async) + { + await base.DateOnly_DayOfYear(async); + + 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 date_part('doy', b."DateOnly")::int = 314 +"""); + } + + public override async Task DateOnly_DayOfWeek(bool async) + { + await base.DateOnly_DayOfWeek(async); + + 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 floor(date_part('dow', b."DateOnly"))::int = 6 +"""); + } + + public override async Task DateOnly_AddYears(bool async) + { + await base.DateOnly_AddYears(async); + + 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 CAST(b."DateOnly" + INTERVAL '3 years' AS date) = DATE '1993-11-10' +"""); + } + + public override async Task DateOnly_AddMonths(bool async) + { + await base.DateOnly_AddMonths(async); + + 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 CAST(b."DateOnly" + INTERVAL '3 months' AS date) = DATE '1991-02-10' +"""); + } + + public override async Task DateOnly_AddDays(bool async) + { + await base.DateOnly_AddDays(async); + + 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."DateOnly" + 3 = DATE '1990-11-13' +"""); + } + + public override async Task DateOnly_FromDateTime(bool async) + { + await base.DateOnly_FromDateTime(async); + + 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 CAST(b."DateTime" AT TIME ZONE 'UTC' AS date) = DATE '1998-05-04' +"""); + } + + public override async Task DateOnly_FromDateTime_compared_to_property(bool async) + { + await base.DateOnly_FromDateTime_compared_to_property(async); + + 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 CAST(b."DateTime" AT TIME ZONE 'UTC' AS date) = b."DateOnly" +"""); + } + + public override async Task DateOnly_FromDateTime_compared_to_constant_and_parameter(bool async) + { + await base.DateOnly_FromDateTime_compared_to_constant_and_parameter(async); + + AssertSql( + """ +@dateOnly='10/11/0002' (DbType = Date) + +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."DateTime" AT TIME ZONE 'UTC' AS date) IN (@dateOnly, DATE '1998-05-04') +"""); + } + + public override async Task DateOnly_ToDateTime_property_DateOnly_with_constant_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_property_DateOnly_with_constant_TimeOnly(async); + + 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."DateOnly" + TIME '21:05:19.9405' = TIMESTAMP '2020-01-01T21:05:19.9405' +"""); + } + + public override async Task DateOnly_ToDateTime_property_DateOnly_with_property_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_property_DateOnly_with_property_TimeOnly(async); + + 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."DateOnly" + b."TimeOnly" = TIMESTAMP '2020-01-01T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_constant_DateTime_with_property_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_constant_DateTime_with_property_TimeOnly(async); + + 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 DATE '1990-11-10' + b."TimeOnly" = TIMESTAMP '1990-11-10T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_with_complex_DateTime(bool async) + { + await base.DateOnly_ToDateTime_with_complex_DateTime(async); + + 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 CAST(b."DateOnly" + INTERVAL '1 years' AS date) + b."TimeOnly" = TIMESTAMP '2021-01-01T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_with_complex_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_with_complex_TimeOnly(async); + + 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."DateOnly" + b."TimeOnly" + INTERVAL '1 hours' = TIMESTAMP '2020-01-01T16:30:10' +"""); + } + + #endregion DateOnly + + #region TimeOnly + + public override async Task TimeOnly_Hour(bool async) + { + await base.TimeOnly_Hour(async); + + 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 date_part('hour', b."TimeOnly")::int = 15 +"""); + } + + public override async Task TimeOnly_Minute(bool async) + { + await base.TimeOnly_Minute(async); + + 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 date_part('minute', b."TimeOnly")::int = 30 +"""); + } + + public override async Task TimeOnly_Second(bool async) + { + await base.TimeOnly_Second(async); + + 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 date_part('second', b."TimeOnly")::int = 10 +"""); + } + + // Translation not yet implemented + public override Task TimeOnly_Millisecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + // Translation not yet implemented + public override Task TimeOnly_Microsecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + // Probably not relevant for PostgreSQL, which supports microsecond precision only + public override Task TimeOnly_Nanosecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + public override async Task TimeOnly_AddHours(bool async) + { + await base.TimeOnly_AddHours(async); + + 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."TimeOnly" + INTERVAL '3 hours' = TIME '18:30:10' +"""); + } + + public override async Task TimeOnly_AddMinutes(bool async) + { + await base.TimeOnly_AddMinutes(async); + + 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."TimeOnly" + INTERVAL '3 mins' = TIME '15:33:10' +"""); + } + + public override async Task TimeOnly_Add_TimeSpan(bool async) + { + await base.TimeOnly_Add_TimeSpan(async); + + 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."TimeOnly" + INTERVAL '03:00:00' = TIME '18:30:10' +"""); + } + + public override async Task TimeOnly_IsBetween(bool async) + { + await base.TimeOnly_IsBetween(async); + + 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."TimeOnly" >= TIME '14:00:00' AND b."TimeOnly" < TIME '16:00:00' +"""); + } + + public override async Task TimeOnly_subtract_TimeOnly(bool async) + { + await base.TimeOnly_subtract_TimeOnly(async); + + 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."TimeOnly" - TIME '03:00:00' = INTERVAL '12:30:10' +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_property(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_property(async); + + 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 CAST(b."DateTime" AT TIME ZONE 'UTC' AS time without time zone) = b."TimeOnly" +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_parameter(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_parameter(async); + + AssertSql( + """ +@time='15:30' (DbType = Time) + +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."DateTime" AT TIME ZONE 'UTC' AS time without time zone) = @time +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_constant(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_constant(async); + + 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 CAST(b."DateTime" AT TIME ZONE 'UTC' AS time without time zone) = TIME '15:30:10' +"""); + } + + public override async Task TimeOnly_FromTimeSpan_compared_to_property(bool async) + { + await base.TimeOnly_FromTimeSpan_compared_to_property(async); + + 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."TimeSpan"::time without time zone < b."TimeOnly" +"""); + } + + public override async Task TimeOnly_FromTimeSpan_compared_to_parameter(bool async) + { + await base.TimeOnly_FromTimeSpan_compared_to_parameter(async); + + AssertSql( + """ +@time='01:02' (DbType = Time) + +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."TimeSpan"::time without time zone = @time +"""); + } + + public override async Task Order_by_TimeOnly_FromTimeSpan(bool async) + { + // TODO: Base implementation is non-deterministic, remove this override once that's fixed on the EF side. + await AssertQuery( + async, + ss => ss.Set().OrderBy(x => TimeOnly.FromTimeSpan(x.TimeSpan)).ThenBy(x => x.Id), + assertOrder: true); + + 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 +ORDER BY b."TimeSpan"::time without time zone NULLS FIRST, b."Id" NULLS FIRST +"""); + } + + #endregion TimeOnly + + #region DateTimeOffset + + // Not supported by design (DateTimeOffset with non-zero offset) + public override Task DateTimeOffset_Now(bool async) + => Assert.ThrowsAsync(() => base.DateTimeOffset_Now(async)); + + public override async Task DateTimeOffset_UtcNow(bool async) + { + await base.DateTimeOffset_UtcNow(async); + + 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."DateTimeOffset" <> now() +"""); + } + + // The test compares with new DateTimeOffset().Date, which Npgsql sends as -infinity, causing a discrepancy with the client behavior + // which uses 1/1/1:0:0:0 + public override Task DateTimeOffset_Date(bool async) + => Assert.ThrowsAsync(() => base.DateTimeOffset_Date(async)); + + public override async Task DateTimeOffset_Year(bool async) + { + await base.DateTimeOffset_Year(async); + + 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 date_part('year', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 1998 +"""); + } + + public override async Task DateTimeOffset_Month(bool async) + { + await base.DateTimeOffset_Month(async); + + 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 date_part('month', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 5 +"""); + } + + public override async Task DateTimeOffset_DayOfYear(bool async) + { + await base.DateTimeOffset_DayOfYear(async); + + 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 date_part('doy', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 124 +"""); + } + + public override async Task DateTimeOffset_Day(bool async) + { + await base.DateTimeOffset_Day(async); + + 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 date_part('day', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 4 +"""); + } + + public override async Task DateTimeOffset_Hour(bool async) + { + await base.DateTimeOffset_Hour(async); + + 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 date_part('hour', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 15 +"""); + } + + public override async Task DateTimeOffset_Minute(bool async) + { + await base.DateTimeOffset_Minute(async); + + 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 date_part('minute', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 30 +"""); + } + + public override async Task DateTimeOffset_Second(bool async) + { + await base.DateTimeOffset_Second(async); + + 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 date_part('second', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 10 +"""); + } + + // SQL translation not implemented, too annoying + public override Task DateTimeOffset_Millisecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Millisecond(async)); + + // TODO: #3406 + public override Task DateTimeOffset_Microsecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Microsecond(async)); + + // TODO: #3406 + public override Task DateTimeOffset_Nanosecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Nanosecond(async)); + + public override async Task DateTimeOffset_TimeOfDay(bool async) + { + await base.DateTimeOffset_TimeOfDay(async); + + AssertSql( + """ +SELECT CAST(b."DateTimeOffset" AT TIME ZONE 'UTC' AS time) +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddYears(bool async) + { + await base.DateTimeOffset_AddYears(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 years' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMonths(bool async) + { + await base.DateTimeOffset_AddMonths(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 months' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddDays(bool async) + { + await base.DateTimeOffset_AddDays(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 days' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddHours(bool async) + { + await base.DateTimeOffset_AddHours(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 hours' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMinutes(bool async) + { + await base.DateTimeOffset_AddMinutes(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 mins' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddSeconds(bool async) + { + await base.DateTimeOffset_AddSeconds(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 secs' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMilliseconds(bool async) + { + await base.DateTimeOffset_AddMilliseconds(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" +FROM "BasicTypesEntities" AS b +"""); + } + + public override Task DateTimeOffset_ToUnixTimeMilliseconds(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_ToUnixTimeMilliseconds(async)); + + public override Task DateTimeOffset_ToUnixTimeSecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_ToUnixTimeSecond(async)); + + public override async Task DateTimeOffset_milliseconds_parameter_and_constant(bool async) + { + await base.DateTimeOffset_milliseconds_parameter_and_constant(async); + + AssertSql( + """ +SELECT count(*)::int +FROM "BasicTypesEntities" AS b +WHERE b."DateTimeOffset" = TIMESTAMPTZ '1902-01-02T10:00:00.123456+01:30' +"""); + } + + #endregion DateTimeOffset + + #region TimeSpan + + public override async Task TimeSpan_Hours(bool async) + { + await base.TimeSpan_Hours(async); + + 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 floor(date_part('hour', b."TimeSpan"))::int = 3 +"""); + } + + public override async Task TimeSpan_Minutes(bool async) + { + await base.TimeSpan_Minutes(async); + + 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 floor(date_part('minute', b."TimeSpan"))::int = 4 +"""); + } + + public override async Task TimeSpan_Seconds(bool async) + { + await base.TimeSpan_Seconds(async); + + 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 floor(date_part('second', b."TimeSpan"))::int = 5 +"""); + } + + public override async Task TimeSpan_Milliseconds(bool async) + { + await base.TimeSpan_Milliseconds(async); + + 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 floor(date_part('millisecond', b."TimeSpan"))::int % 1000 = 678 +"""); + } + + public override Task TimeSpan_Microseconds(bool async) + => AssertTranslationFailed(() => base.TimeSpan_Microseconds(async)); + + public override Task TimeSpan_Nanoseconds(bool async) + => AssertTranslationFailed(() => base.TimeSpan_Nanoseconds(async)); + + #endregion TimeSpan + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest.cs new file mode 100644 index 000000000..1717c417c --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest.cs @@ -0,0 +1,997 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; +using Xunit.Sdk; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Query.Translations; + +/// +/// Same as , but the property is mapped to a PostgreSQL +/// timestamp without time zone, which corresponds to a with Kind +/// . +/// +public class TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest + : TemporalTranslationsTestBase +{ + public TemporalTranslationsNpgsqlTimestampWithoutTimeZoneTest( + BasicTypesQueryNpgsqlTimestampWithoutTimeZoneFixture fixture, + ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region DateTime + + public override async Task DateTime_Now(bool async) + { + await base.DateTime_Now(async); + + AssertSql( + """ +@myDatetime='2015-04-10T00:00:00.0000000' + +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 now()::timestamp <> @myDatetime +"""); + } + + public override async Task DateTime_UtcNow(bool async) + { + // Overriding to set Kind=Utc for timestamptz. This test generally doesn't make much sense here. + var myDatetime = DateTime.SpecifyKind(new DateTime(2015, 4, 10), DateTimeKind.Utc); + + await AssertQuery( + async, + ss => ss.Set().Where(c => DateTime.UtcNow != myDatetime)); + + AssertSql( + """ +@myDatetime='2015-04-10T00:00:00.0000000Z' (DbType = 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 now() <> @myDatetime +"""); + } + + public override async Task DateTime_Today(bool async) + { + await base.DateTime_Today(async); + + 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."DateTime" = date_trunc('day', now()::timestamp) +"""); + } + + public override async Task DateTime_Date(bool async) + { + await base.DateTime_Date(async); + + AssertSql( + """ +@myDatetime='1998-05-04T00:00:00.0000000' + +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 date_trunc('day', b."DateTime") = @myDatetime +"""); + } + + public override async Task DateTime_AddYear(bool async) + { + await base.DateTime_AddYear(async); + + 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 date_part('year', b."DateTime" + INTERVAL '1 years')::int = 1999 +"""); + } + + public override async Task DateTime_Year(bool async) + { + await base.DateTime_Year(async); + + 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 date_part('year', b."DateTime")::int = 1998 +"""); + } + + public override async Task DateTime_Month(bool async) + { + await base.DateTime_Month(async); + + 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 date_part('month', b."DateTime")::int = 5 +"""); + } + + public override async Task DateTime_DayOfYear(bool async) + { + await base.DateTime_DayOfYear(async); + + 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 date_part('doy', b."DateTime")::int = 124 +"""); + } + + public override async Task DateTime_Day(bool async) + { + await base.DateTime_Day(async); + + 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 date_part('day', b."DateTime")::int = 4 +"""); + } + + public override async Task DateTime_Hour(bool async) + { + await base.DateTime_Hour(async); + + 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 date_part('hour', b."DateTime")::int = 15 +"""); + } + + public override async Task DateTime_Minute(bool async) + { + await base.DateTime_Minute(async); + + 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 date_part('minute', b."DateTime")::int = 30 +"""); + } + + public override async Task DateTime_Second(bool async) + { + await base.DateTime_Second(async); + + 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 date_part('second', b."DateTime")::int = 10 +"""); + } + + // SQL translation not implemented, too annoying + public override Task DateTime_Millisecond(bool async) + => AssertTranslationFailed(() => base.DateTime_Millisecond(async)); + + public override async Task DateTime_TimeOfDay(bool async) + { + await base.DateTime_TimeOfDay(async); + + 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."DateTime"::time = TIME '00:00:00' +"""); + } + + public override async Task DateTime_subtract_and_TotalDays(bool async) + { + await base.DateTime_subtract_and_TotalDays(async); + + AssertSql( + """ +@date='1997-01-01T00:00:00.0000000' + +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 date_part('epoch', b."DateTime" - @date) / 86400.0 > 365.0 +"""); + } + + public override async Task DateTime_Parse_with_constant(bool async) + { + await base.DateTime_Parse_with_constant(async); + + 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."DateTime" = TIMESTAMP '1998-05-04T15:30:10' +"""); + } + + public override async Task DateTime_Parse_with_parameter(bool async) + { + await base.DateTime_Parse_with_parameter(async); + + AssertSql( + """ +@Parse='1998-05-04T15:30:10.0000000' + +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."DateTime" = @Parse +"""); + } + + public override async Task DateTime_new_with_constant(bool async) + { + await base.DateTime_new_with_constant(async); + + 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."DateTime" = TIMESTAMP '1998-05-04T15:30:10' +"""); + } + + public override async Task DateTime_new_with_parameters(bool async) + { + await base.DateTime_new_with_parameters(async); + + AssertSql( + """ +@p='1998-05-04T15:30:10.0000000' + +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."DateTime" = @p +"""); + } + + #endregion DateTime + + #region DateOnly + + public override async Task DateOnly_Year(bool async) + { + await base.DateOnly_Year(async); + + 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 date_part('year', b."DateOnly")::int = 1990 +"""); + } + + public override async Task DateOnly_Month(bool async) + { + await base.DateOnly_Month(async); + + 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 date_part('month', b."DateOnly")::int = 11 +"""); + } + + public override async Task DateOnly_Day(bool async) + { + await base.DateOnly_Day(async); + + 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 date_part('day', b."DateOnly")::int = 10 +"""); + } + + public override async Task DateOnly_DayOfYear(bool async) + { + await base.DateOnly_DayOfYear(async); + + 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 date_part('doy', b."DateOnly")::int = 314 +"""); + } + + public override async Task DateOnly_DayOfWeek(bool async) + { + await base.DateOnly_DayOfWeek(async); + + 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 floor(date_part('dow', b."DateOnly"))::int = 6 +"""); + } + + public override async Task DateOnly_AddYears(bool async) + { + await base.DateOnly_AddYears(async); + + 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 CAST(b."DateOnly" + INTERVAL '3 years' AS date) = DATE '1993-11-10' +"""); + } + + public override async Task DateOnly_AddMonths(bool async) + { + await base.DateOnly_AddMonths(async); + + 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 CAST(b."DateOnly" + INTERVAL '3 months' AS date) = DATE '1991-02-10' +"""); + } + + public override async Task DateOnly_AddDays(bool async) + { + await base.DateOnly_AddDays(async); + + 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."DateOnly" + 3 = DATE '1990-11-13' +"""); + } + + public override async Task DateOnly_FromDateTime(bool async) + { + await base.DateOnly_FromDateTime(async); + + 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."DateTime"::date = DATE '1998-05-04' +"""); + } + + public override async Task DateOnly_FromDateTime_compared_to_property(bool async) + { + await base.DateOnly_FromDateTime_compared_to_property(async); + + 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."DateTime"::date = b."DateOnly" +"""); + } + + public override async Task DateOnly_FromDateTime_compared_to_constant_and_parameter(bool async) + { + await base.DateOnly_FromDateTime_compared_to_constant_and_parameter(async); + + AssertSql( + """ +@dateOnly='10/11/0002' (DbType = Date) + +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."DateTime"::date IN (@dateOnly, DATE '1998-05-04') +"""); + } + + public override async Task DateOnly_ToDateTime_property_DateOnly_with_constant_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_property_DateOnly_with_constant_TimeOnly(async); + + 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."DateOnly" + TIME '21:05:19.9405' = TIMESTAMP '2020-01-01T21:05:19.9405' +"""); + } + + public override async Task DateOnly_ToDateTime_property_DateOnly_with_property_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_property_DateOnly_with_property_TimeOnly(async); + + 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."DateOnly" + b."TimeOnly" = TIMESTAMP '2020-01-01T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_constant_DateTime_with_property_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_constant_DateTime_with_property_TimeOnly(async); + + 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 DATE '1990-11-10' + b."TimeOnly" = TIMESTAMP '1990-11-10T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_with_complex_DateTime(bool async) + { + await base.DateOnly_ToDateTime_with_complex_DateTime(async); + + 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 CAST(b."DateOnly" + INTERVAL '1 years' AS date) + b."TimeOnly" = TIMESTAMP '2021-01-01T15:30:10' +"""); + } + + public override async Task DateOnly_ToDateTime_with_complex_TimeOnly(bool async) + { + await base.DateOnly_ToDateTime_with_complex_TimeOnly(async); + + 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."DateOnly" + b."TimeOnly" + INTERVAL '1 hours' = TIMESTAMP '2020-01-01T16:30:10' +"""); + } + + #endregion DateOnly + + #region TimeOnly + + public override async Task TimeOnly_Hour(bool async) + { + await base.TimeOnly_Hour(async); + + 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 date_part('hour', b."TimeOnly")::int = 15 +"""); + } + + public override async Task TimeOnly_Minute(bool async) + { + await base.TimeOnly_Minute(async); + + 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 date_part('minute', b."TimeOnly")::int = 30 +"""); + } + + public override async Task TimeOnly_Second(bool async) + { + await base.TimeOnly_Second(async); + + 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 date_part('second', b."TimeOnly")::int = 10 +"""); + } + + // Translation not yet implemented + public override Task TimeOnly_Millisecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + // Translation not yet implemented + public override Task TimeOnly_Microsecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + // Probably not relevant for PostgreSQL, which supports microsecond precision only + public override Task TimeOnly_Nanosecond(bool async) + => AssertTranslationFailed(() => base.TimeOnly_Millisecond(async)); + + public override async Task TimeOnly_AddHours(bool async) + { + await base.TimeOnly_AddHours(async); + + 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."TimeOnly" + INTERVAL '3 hours' = TIME '18:30:10' +"""); + } + + public override async Task TimeOnly_AddMinutes(bool async) + { + await base.TimeOnly_AddMinutes(async); + + 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."TimeOnly" + INTERVAL '3 mins' = TIME '15:33:10' +"""); + } + + public override async Task TimeOnly_Add_TimeSpan(bool async) + { + await base.TimeOnly_Add_TimeSpan(async); + + 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."TimeOnly" + INTERVAL '03:00:00' = TIME '18:30:10' +"""); + } + + public override async Task TimeOnly_IsBetween(bool async) + { + await base.TimeOnly_IsBetween(async); + + 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."TimeOnly" >= TIME '14:00:00' AND b."TimeOnly" < TIME '16:00:00' +"""); + } + + public override async Task TimeOnly_subtract_TimeOnly(bool async) + { + await base.TimeOnly_subtract_TimeOnly(async); + + 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."TimeOnly" - TIME '03:00:00' = INTERVAL '12:30:10' +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_property(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_property(async); + + 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."DateTime"::time without time zone = b."TimeOnly" +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_parameter(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_parameter(async); + + AssertSql( + """ +@time='15:30' (DbType = Time) + +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."DateTime"::time without time zone = @time +"""); + } + + public override async Task TimeOnly_FromDateTime_compared_to_constant(bool async) + { + await base.TimeOnly_FromDateTime_compared_to_constant(async); + + 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."DateTime"::time without time zone = TIME '15:30:10' +"""); + } + + public override async Task TimeOnly_FromTimeSpan_compared_to_property(bool async) + { + await base.TimeOnly_FromTimeSpan_compared_to_property(async); + + 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."TimeSpan"::time without time zone < b."TimeOnly" +"""); + } + + public override async Task TimeOnly_FromTimeSpan_compared_to_parameter(bool async) + { + await base.TimeOnly_FromTimeSpan_compared_to_parameter(async); + + AssertSql( + """ +@time='01:02' (DbType = Time) + +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."TimeSpan"::time without time zone = @time +"""); + } + + public override async Task Order_by_TimeOnly_FromTimeSpan(bool async) + { + // TODO: Base implementation is non-deterministic, remove this override once that's fixed on the EF side. + await AssertQuery( + async, + ss => ss.Set().OrderBy(x => TimeOnly.FromTimeSpan(x.TimeSpan)).ThenBy(x => x.Id), + assertOrder: true); + + 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 +ORDER BY b."TimeSpan"::time without time zone NULLS FIRST, b."Id" NULLS FIRST +"""); + } + + #endregion TimeOnly + + #region DateTimeOffset + + // Not supported by design (DateTimeOffset with non-zero offset) + public override Task DateTimeOffset_Now(bool async) + => Assert.ThrowsAsync(() => base.DateTimeOffset_Now(async)); + + public override async Task DateTimeOffset_UtcNow(bool async) + { + await base.DateTimeOffset_UtcNow(async); + + 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."DateTimeOffset" <> now() +"""); + } + + // The test compares with new DateTimeOffset().Date, which Npgsql sends as -infinity, causing a discrepancy with the client behavior + // which uses 1/1/1:0:0:0 + public override Task DateTimeOffset_Date(bool async) + => Assert.ThrowsAsync(() => base.DateTimeOffset_Date(async)); + + public override async Task DateTimeOffset_Year(bool async) + { + await base.DateTimeOffset_Year(async); + + 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 date_part('year', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 1998 +"""); + } + + public override async Task DateTimeOffset_Month(bool async) + { + await base.DateTimeOffset_Month(async); + + 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 date_part('month', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 5 +"""); + } + + public override async Task DateTimeOffset_DayOfYear(bool async) + { + await base.DateTimeOffset_DayOfYear(async); + + 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 date_part('doy', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 124 +"""); + } + + public override async Task DateTimeOffset_Day(bool async) + { + await base.DateTimeOffset_Day(async); + + 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 date_part('day', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 4 +"""); + } + + public override async Task DateTimeOffset_Hour(bool async) + { + await base.DateTimeOffset_Hour(async); + + 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 date_part('hour', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 15 +"""); + } + + public override async Task DateTimeOffset_Minute(bool async) + { + await base.DateTimeOffset_Minute(async); + + 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 date_part('minute', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 30 +"""); + } + + public override async Task DateTimeOffset_Second(bool async) + { + await base.DateTimeOffset_Second(async); + + 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 date_part('second', b."DateTimeOffset" AT TIME ZONE 'UTC')::int = 10 +"""); + } + + // SQL translation not implemented, too annoying + public override Task DateTimeOffset_Millisecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Millisecond(async)); + + // TODO: #3406 + public override Task DateTimeOffset_Microsecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Microsecond(async)); + + // TODO: #3406 + public override Task DateTimeOffset_Nanosecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_Nanosecond(async)); + + public override async Task DateTimeOffset_TimeOfDay(bool async) + { + await base.DateTimeOffset_TimeOfDay(async); + + AssertSql( + """ +SELECT CAST(b."DateTimeOffset" AT TIME ZONE 'UTC' AS time) +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddYears(bool async) + { + await base.DateTimeOffset_AddYears(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 years' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMonths(bool async) + { + await base.DateTimeOffset_AddMonths(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 months' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddDays(bool async) + { + await base.DateTimeOffset_AddDays(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 days' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddHours(bool async) + { + await base.DateTimeOffset_AddHours(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 hours' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMinutes(bool async) + { + await base.DateTimeOffset_AddMinutes(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 mins' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddSeconds(bool async) + { + await base.DateTimeOffset_AddSeconds(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" + INTERVAL '1 secs' +FROM "BasicTypesEntities" AS b +"""); + } + + public override async Task DateTimeOffset_AddMilliseconds(bool async) + { + await base.DateTimeOffset_AddMilliseconds(async); + + AssertSql( + """ +SELECT b."DateTimeOffset" +FROM "BasicTypesEntities" AS b +"""); + } + + public override Task DateTimeOffset_ToUnixTimeMilliseconds(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_ToUnixTimeMilliseconds(async)); + + public override Task DateTimeOffset_ToUnixTimeSecond(bool async) + => AssertTranslationFailed(() => base.DateTimeOffset_ToUnixTimeSecond(async)); + + // SQL translation not implemented, too annoying + public override async Task DateTimeOffset_milliseconds_parameter_and_constant(bool async) + { + await base.DateTimeOffset_milliseconds_parameter_and_constant(async); + + AssertSql( + """ +SELECT count(*)::int +FROM "BasicTypesEntities" AS b +WHERE b."DateTimeOffset" = TIMESTAMPTZ '1902-01-02T10:00:00.123456+01:30' +"""); + } + + #endregion DateTimeOffset + + #region TimeSpan + + public override async Task TimeSpan_Hours(bool async) + { + await base.TimeSpan_Hours(async); + + 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 floor(date_part('hour', b."TimeSpan"))::int = 3 +"""); + } + + public override async Task TimeSpan_Minutes(bool async) + { + await base.TimeSpan_Minutes(async); + + 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 floor(date_part('minute', b."TimeSpan"))::int = 4 +"""); + } + + public override async Task TimeSpan_Seconds(bool async) + { + await base.TimeSpan_Seconds(async); + + 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 floor(date_part('second', b."TimeSpan"))::int = 5 +"""); + } + + public override async Task TimeSpan_Milliseconds(bool async) + { + await base.TimeSpan_Milliseconds(async); + + 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 floor(date_part('millisecond', b."TimeSpan"))::int % 1000 = 678 +"""); + } + + public override Task TimeSpan_Microseconds(bool async) + => AssertTranslationFailed(() => base.TimeSpan_Microseconds(async)); + + public override Task TimeSpan_Nanoseconds(bool async) + => AssertTranslationFailed(() => base.TimeSpan_Nanoseconds(async)); + + #endregion TimeSpan + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + public class BasicTypesQueryNpgsqlTimestampWithoutTimeZoneFixture : BasicTypesQueryNpgsqlFixture + { + private BasicTypesData? _expectedData; + + protected override string StoreName + => "BasicTypesTimestampWithoutTimeZoneTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.Entity().Property(b => b.DateTime).HasColumnType("timestamp without time zone"); + modelBuilder.Entity().Property(b => b.DateTime).HasColumnType("timestamp without time zone"); + } + + protected override Task SeedAsync(BasicTypesContext context) + { + _expectedData ??= LoadAndTweakData(); + context.AddRange(_expectedData.BasicTypesEntities); + context.AddRange(_expectedData.NullableBasicTypesEntities); + return context.SaveChangesAsync(); + } + + public override ISetSource GetExpectedData() + => _expectedData ??= LoadAndTweakData(); + + private BasicTypesData LoadAndTweakData() + { + var data = (BasicTypesData)base.GetExpectedData(); + + foreach (var item in data.BasicTypesEntities) + { + // Change Kind fo all DateTimes from Utc to Unspecified, as we're mapping to 'timestamp without time zone' + item.DateTime = DateTime.SpecifyKind(item.DateTime, DateTimeKind.Unspecified); + } + + // Do the same for the nullable counterparts + foreach (var item in data.NullableBasicTypesEntities) + { + if (item.DateTime.HasValue) + { + item.DateTime = DateTime.SpecifyKind(item.DateTime.Value, DateTimeKind.Unspecified); + } + } + + return data; + } + } +}