diff --git a/Directory.Packages.props b/Directory.Packages.props index 77ce318ac..93aecba8c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 9.0.0-preview.1.24081.2 - 9.0.0-preview.1.24080.9 + 9.0.0-preview.2.24128.4 + 9.0.0-preview.2.24128.5 8.0.2 diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 2b43ed701..03fc496de 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -786,6 +786,68 @@ protected override void Generate(RenameSequenceOperation operation, IModel? mode EndStatement(builder); } + /// + protected override void SequenceOptions( + string? schema, + string name, + SequenceOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool forAlter) + { + var intTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(int)); + var longTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(long)); + + builder + .Append(" INCREMENT BY ") + .Append(intTypeMapping.GenerateSqlLiteral(operation.IncrementBy)); + + if (operation.MinValue != null) + { + builder + .Append(" MINVALUE ") + .Append(longTypeMapping.GenerateSqlLiteral(operation.MinValue)); + } + else if (forAlter) + { + builder + .Append(" NO MINVALUE"); + } + + if (operation.MaxValue != null) + { + builder + .Append(" MAXVALUE ") + .Append(longTypeMapping.GenerateSqlLiteral(operation.MaxValue)); + } + else if (forAlter) + { + builder + .Append(" NO MAXVALUE"); + } + + builder.Append(operation.IsCyclic ? " CYCLE" : " NO CYCLE"); + + if (!operation.IsCached) + { + // The base implementation appends NO CACHE, which isn't supported by PG + builder + .Append(" CACHE 1"); + } + else if (operation.CacheSize != null) + { + builder + .Append(" CACHE ") + .Append(intTypeMapping.GenerateSqlLiteral(operation.CacheSize.Value)); + } + else if (forAlter) + { + // The base implementation just appends CACHE, which isn't supported by PG + builder + .Append(" CACHE 1"); + } + } + /// protected override void Generate(RestartSequenceOperation operation, IModel? model, MigrationCommandListBuilder builder) { diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 5cb3103cd..37ea2c68a 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -10,6 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Scaffolding.Internal; +// ReSharper disable StringLiteralTypo + /// /// The default database model factory for Npgsql. /// @@ -458,7 +460,7 @@ ORDER BY attnum MaxValue = seqInfo.MaxValue, IncrementBy = (int)(seqInfo.IncrementBy ?? 1), IsCyclic = seqInfo.IsCyclic ?? false, - NumbersToCache = seqInfo.NumbersToCache ?? 1 + NumbersToCache = seqInfo.CacheSize ?? 1 }; if (!sequenceData.Equals(IdentitySequenceOptionsData.Empty)) @@ -976,11 +978,67 @@ private static IEnumerable GetSequences( Func? schemaFilter, IDiagnosticsLogger logger) { - // Note: we consult information_schema.sequences instead of pg_sequence but the latter was only introduced in PG 10 - var commandText = $""" + // pg_sequence was only introduced in PG 10; we prefer that (cleaner and also exposes sequence caching info), but retain the old + // code for backwards compat + return connection.PostgreSqlVersion >= new Version(10, 0) + ? GetSequencesNew(connection, databaseModel, schemaFilter, logger) + : GetSequencesOld(connection, databaseModel, schemaFilter, logger); + + static IEnumerable GetSequencesNew( + NpgsqlConnection connection, + DatabaseModel databaseModel, + Func? schemaFilter, + IDiagnosticsLogger logger) + { + var commandText = $""" +SELECT nspname, relname, typname, seqstart, seqincrement, seqmax, seqmin, seqcache, seqcycle +FROM pg_sequence +JOIN pg_class AS cls ON cls.oid=seqrelid +JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace +JOIN pg_type AS typ ON typ.oid = seqtypid +/* Filter out owned serial and identity sequences */ +WHERE NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep.deptype IN ('i', 'I', 'a')) + {(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)} +"""; + + using var command = new NpgsqlCommand(commandText, connection); + using var reader = command.ExecuteReader(); + + foreach (var record in reader.Cast()) + { + var sequenceSchema = reader.GetFieldValue("nspname"); + var sequenceName = reader.GetFieldValue("relname"); + + var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); + var sequence = new DatabaseSequence + { + Database = databaseModel, + Name = sequenceName, + Schema = sequenceSchema, + StoreType = seqInfo.StoreType, + StartValue = seqInfo.StartValue, + MinValue = seqInfo.MinValue, + MaxValue = seqInfo.MaxValue, + IncrementBy = (int?)seqInfo.IncrementBy, + IsCyclic = seqInfo.IsCyclic, + IsCached = seqInfo.CacheSize is not null, + CacheSize = seqInfo.CacheSize + }; + + yield return sequence; + } + } + + static IEnumerable GetSequencesOld( + NpgsqlConnection connection, + DatabaseModel databaseModel, + Func? schemaFilter, + IDiagnosticsLogger logger) + { + var commandText = $""" SELECT sequence_schema, sequence_name, - data_type AS seqtype, + data_type AS typname, {(connection.PostgreSqlVersion >= new Version(9, 1) ? "start_value" : "1")}::bigint AS seqstart, minimum_value::bigint AS seqmin, maximum_value::bigint AS seqmax, @@ -1001,29 +1059,30 @@ AND NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep {(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)} """; - using var command = new NpgsqlCommand(commandText, connection); - using var reader = command.ExecuteReader(); - - foreach (var record in reader.Cast()) - { - var sequenceName = reader.GetFieldValue("sequence_name"); - var sequenceSchema = reader.GetFieldValue("sequence_schema"); + using var command = new NpgsqlCommand(commandText, connection); + using var reader = command.ExecuteReader(); - var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); - var sequence = new DatabaseSequence + foreach (var record in reader.Cast()) { - Database = databaseModel, - Name = sequenceName, - Schema = sequenceSchema, - StoreType = seqInfo.StoreType, - StartValue = seqInfo.StartValue, - MinValue = seqInfo.MinValue, - MaxValue = seqInfo.MaxValue, - IncrementBy = (int?)seqInfo.IncrementBy, - IsCyclic = seqInfo.IsCyclic - }; - - yield return sequence; + var sequenceName = reader.GetFieldValue("sequence_name"); + var sequenceSchema = reader.GetFieldValue("sequence_schema"); + + var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); + var sequence = new DatabaseSequence + { + Database = databaseModel, + Name = sequenceName, + Schema = sequenceSchema, + StoreType = seqInfo.StoreType, + StartValue = seqInfo.StartValue, + MinValue = seqInfo.MinValue, + MaxValue = seqInfo.MaxValue, + IncrementBy = (int?)seqInfo.IncrementBy, + IsCyclic = seqInfo.IsCyclic + }; + + yield return sequence; + } } } @@ -1225,16 +1284,24 @@ private static void AdjustDefaults(DatabaseColumn column, string systemTypeName) private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgresVersion) { - var storeType = record.GetFieldValue("seqtype"); + var storeType = record.GetFieldValue("typname"); var startValue = record.GetValueOrDefault("seqstart"); var minValue = record.GetValueOrDefault("seqmin"); var maxValue = record.GetValueOrDefault("seqmax"); var incrementBy = record.GetValueOrDefault("seqincrement"); var isCyclic = record.GetValueOrDefault("seqcycle"); - var numbersToCache = (int)record.GetValueOrDefault("seqcache"); + var cacheSize = (int?)record.GetValueOrDefault("seqcache"); long defaultStart, defaultMin, defaultMax; + storeType = storeType switch + { + "int2" => "smallint", + "int4" => "integer", + "int8" => "bigint", + _ => storeType + }; + switch (storeType) { case "smallint" when incrementBy > 0: @@ -1293,7 +1360,7 @@ private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgr MaxValue = maxValue == defaultMax ? null : maxValue, IncrementBy = incrementBy == 1 ? null : incrementBy, IsCyclic = isCyclic == false ? null : true, - NumbersToCache = numbersToCache == 1 ? null : numbersToCache + CacheSize = cacheSize is 1 or null ? null : cacheSize }; } @@ -1305,7 +1372,7 @@ private sealed class SequenceInfo(string storeType) public long? MaxValue { get; set; } public long? IncrementBy { get; set; } public bool? IsCyclic { get; set; } - public long? NumbersToCache { get; set; } + public int? CacheSize { get; set; } } #endregion diff --git a/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs b/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs index 4663f098b..51d91b739 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs @@ -12,8 +12,9 @@ public interface INpgsqlSequenceValueGeneratorFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - ValueGenerator Create( + ValueGenerator? TryCreate( IProperty property, + Type clrType, NpgsqlSequenceValueGeneratorState generatorState, INpgsqlRelationalConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs index 936cf3a06..37bf81d93 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs @@ -26,15 +26,14 @@ public NpgsqlSequenceValueGeneratorFactory( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ValueGenerator Create( + public virtual ValueGenerator? TryCreate( IProperty property, + Type type, NpgsqlSequenceValueGeneratorState generatorState, INpgsqlRelationalConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, IRelationalCommandDiagnosticsLogger commandLogger) { - var type = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - if (type == typeof(long)) { return new NpgsqlSequenceHiLoValueGenerator( @@ -89,8 +88,6 @@ public virtual ValueGenerator Create( rawSqlCommandBuilder, _sqlGenerator, generatorState, connection, commandLogger); } - throw new ArgumentException( - CoreStrings.InvalidValueGeneratorFactoryProperty( - nameof(NpgsqlSequenceValueGeneratorFactory), property.Name, property.DeclaringType.DisplayName())); + return null; } } diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs index f18313457..4b44c178e 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs @@ -49,16 +49,50 @@ public NpgsqlValueGeneratorSelector( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override ValueGenerator Select(IProperty property, ITypeBase typeBase) - => property.GetValueGeneratorFactory() is null - && property.GetValueGenerationStrategy() == NpgsqlValueGenerationStrategy.SequenceHiLo - ? _sequenceFactory.Create( - property, - Cache.GetOrAddSequenceState(property, _connection), - _connection, - _rawSqlCommandBuilder, - _commandLogger) - : base.Select(property, typeBase); + public override bool TrySelect(IProperty property, ITypeBase typeBase, out ValueGenerator? valueGenerator) + { + if (property.GetValueGeneratorFactory() != null + || property.GetValueGenerationStrategy() != NpgsqlValueGenerationStrategy.SequenceHiLo) + { + return base.TrySelect(property, typeBase, out valueGenerator); + } + + var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); + + valueGenerator = _sequenceFactory.TryCreate( + property, + propertyType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (valueGenerator != null) + { + return true; + } + + var converter = property.GetTypeMapping().Converter; + if (converter != null + && converter.ProviderClrType != propertyType) + { + valueGenerator = _sequenceFactory.TryCreate( + property, + converter.ProviderClrType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (valueGenerator != null) + { + valueGenerator = valueGenerator.WithConverter(converter); + return true; + } + } + + return false; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs index 9cb600c39..4dfa8eea2 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesFixture - where TModelCustomizer : IModelCustomizer, new() + where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory => NpgsqlNorthwindTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs index 7edbcc6ab..e56daca7a 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ManyToManyFieldsLoadNpgsqlTest(ManyToManyFieldsLoadNpgsqlTest.ManyToManyFieldsLoadNpgsqlFixture fixture) : ManyToManyFieldsLoadTestBase(fixture) { - public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase + public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs index eaa5fea6a..c25a46d38 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ManyToManyLoadNpgsqlTest(ManyToManyLoadNpgsqlTest.ManyToManyLoadNpgsqlFixture fixture) : ManyToManyLoadTestBase(fixture) { - public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase + public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs index b7a906ffd..9faf5f107 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs @@ -10,11 +10,14 @@ public class ManyToManyTrackingNpgsqlTest(ManyToManyTrackingNpgsqlTest.ManyToMan protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); - public class ManyToManyTrackingNpgsqlFixture : ManyToManyTrackingRelationalFixture + public class ManyToManyTrackingNpgsqlFixture : ManyToManyTrackingRelationalFixture, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 0c558c943..190860809 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -1519,10 +1519,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""People_Id_seq"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE "People_Id_seq" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('""People_Id_seq""')); -ALTER SEQUENCE ""People_Id_seq"" OWNED BY ""People"".""Id"";"); + """ +ALTER TABLE "People" ALTER COLUMN "Id" SET DEFAULT (nextval('"People_Id_seq"')); +ALTER SEQUENCE "People_Id_seq" OWNED BY "People"."Id"; +"""); } [Fact] @@ -1545,10 +1549,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE some_schema.""People_Id_seq"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE some_schema."People_Id_seq" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE some_schema.""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('some_schema.""People_Id_seq""')); -ALTER SEQUENCE some_schema.""People_Id_seq"" OWNED BY some_schema.""People"".""Id"";"); + """ +ALTER TABLE some_schema."People" ALTER COLUMN "Id" SET DEFAULT (nextval('some_schema."People_Id_seq"')); +ALTER SEQUENCE some_schema."People_Id_seq" OWNED BY some_schema."People"."Id"; +"""); } [Fact] @@ -1571,10 +1579,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""People_Id_seq"" START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE "People_Id_seq" START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('""People_Id_seq""')); -ALTER SEQUENCE ""People_Id_seq"" OWNED BY ""People"".""Id"";"); + """ +ALTER TABLE "People" ALTER COLUMN "Id" SET DEFAULT (nextval('"People_Id_seq"')); +ALTER SEQUENCE "People_Id_seq" OWNED BY "People"."Id"; +"""); } [Fact] @@ -2614,7 +2626,9 @@ public override async Task Create_sequence() await base.Create_sequence(); AssertSql( - @"CREATE SEQUENCE ""TestSequence"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;"); + """ +CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +"""); } public override async Task Create_sequence_all_settings() @@ -2631,7 +2645,39 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN END $EF$; """, // - @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE;"); + @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20;"); + } + + public override async Task Create_sequence_nocache() + { + await base.Create_sequence_nocache(); + + AssertSql("""CREATE SEQUENCE "Alpha" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 1;"""); + } + + public override async Task Create_sequence_cache() + { + await base.Create_sequence_cache(); + + AssertSql("""CREATE SEQUENCE "Beta" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 20;"""); + } + + public override async Task Create_sequence_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => { }, + builder => builder.HasSequence("Gamma").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("Gamma", sequence.Name); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql("""CREATE SEQUENCE "Gamma" START WITH 1 INCREMENT BY 1 NO CYCLE;"""); } [Fact] @@ -2648,34 +2694,19 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""TestSequence"" AS smallint START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;"); + """ +CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE; +"""); } [Fact] public override async Task Alter_sequence_all_settings() { - await Test( - builder => builder.HasSequence("foo"), - _ => { }, - builder => builder.HasSequence("foo") - .StartsAt(-3) - .IncrementsBy(2) - .HasMin(-5) - .HasMax(10) - .IsCyclic(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal(-3, sequence.StartValue); - Assert.Equal(2, sequence.IncrementBy); - Assert.Equal(-5, sequence.MinValue); - Assert.Equal(10, sequence.MaxValue); - Assert.True(sequence.IsCyclic); - }); + await base.Alter_sequence_all_settings(); AssertSql( """ -ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; +ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE CACHE 20; """, // """ @@ -2689,7 +2720,79 @@ public override async Task Alter_sequence_increment_by() await base.Alter_sequence_increment_by(); AssertSql( - @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE;"); + @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); + } + + public override async Task Alter_sequence_default_cache_to_cache() + { + await base.Alter_sequence_default_cache_to_cache(); + + AssertSql( + """ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); + } + + public override async Task Alter_sequence_default_cache_to_nocache() + { + await base.Alter_sequence_default_cache_to_nocache(); + + AssertSql( + """ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_cache_to_nocache() + { + await base.Alter_sequence_cache_to_nocache(); + + AssertSql( + """ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_cache_to_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => builder.HasSequence("Eta").UseCache(20), + builder => { }, + builder => builder.HasSequence("Eta").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql( + """ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_nocache_to_cache() + { + await base.Alter_sequence_nocache_to_cache(); + + AssertSql( + """ +ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20; +"""); + } + + public override async Task Alter_sequence_nocache_to_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => builder.HasSequence("Iota").UseNoCache(), + builder => { }, + builder => builder.HasSequence("Iota").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql( + """ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_restart_with() @@ -2887,7 +2990,16 @@ SELECT setval( """); } - #endregion + + #endregion Data seeding + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[3, 2, 1]"); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT (ARRAY[3, 2, 1]);"""); + } #region PostgreSQL extensions diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index edb0f85fc..c218b8a5d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -31,52 +31,59 @@ protected override void Seed29219(MyContext29219 ctx) ctx.Entities.AddRange(entity1, entity2); ctx.SaveChanges(); - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Id", "Reference", "Collection") -VALUES(3, '{{ "NonNullableScalar" : 30 }}', '[{{ "NonNullableScalar" : 10001 }}]') +VALUES(3, '{ "NonNullableScalar" : 30 }', '[{ "NonNullableScalar" : 10001 }]') """); } protected override void Seed30028(MyContext30028 ctx) { // complete - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 1, -'{{"RootName":"e1","Collection":[{{"BranchName":"e1 c1","Nested":{{"LeafName":"e1 c1 l"}}}},{{"BranchName":"e1 c2","Nested":{{"LeafName":"e1 c2 l"}}}}],"OptionalReference":{{"BranchName":"e1 or","Nested":{{"LeafName":"e1 or l"}}}},"RequiredReference":{{"BranchName":"e1 rr","Nested":{{"LeafName":"e1 rr l"}}}}}}') +'{"RootName":"e1","Collection":[{"BranchName":"e1 c1","Nested":{"LeafName":"e1 c1 l"}},{"BranchName":"e1 c2","Nested":{"LeafName":"e1 c2 l"}}],"OptionalReference":{"BranchName":"e1 or","Nested":{"LeafName":"e1 or l"}},"RequiredReference":{"BranchName":"e1 rr","Nested":{"LeafName":"e1 rr l"}}}') """); // missing collection - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 2, -'{{"RootName":"e2","OptionalReference":{{"BranchName":"e2 or","Nested":{{"LeafName":"e2 or l"}}}},"RequiredReference":{{"BranchName":"e2 rr","Nested":{{"LeafName":"e2 rr l"}}}}}}') +'{"RootName":"e2","OptionalReference":{"BranchName":"e2 or","Nested":{"LeafName":"e2 or l"}},"RequiredReference":{"BranchName":"e2 rr","Nested":{"LeafName":"e2 rr l"}}}') """); // missing optional reference - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 3, -'{{"RootName":"e3","Collection":[{{"BranchName":"e3 c1","Nested":{{"LeafName":"e3 c1 l"}}}},{{"BranchName":"e3 c2","Nested":{{"LeafName":"e3 c2 l"}}}}],"RequiredReference":{{"BranchName":"e3 rr","Nested":{{"LeafName":"e3 rr l"}}}}}}') +'{"RootName":"e3","Collection":[{"BranchName":"e3 c1","Nested":{"LeafName":"e3 c1 l"}},{"BranchName":"e3 c2","Nested":{"LeafName":"e3 c2 l"}}],"RequiredReference":{"BranchName":"e3 rr","Nested":{"LeafName":"e3 rr l"}}}') """); // missing required reference - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 4, -'{{"RootName":"e4","Collection":[{{"BranchName":"e4 c1","Nested":{{"LeafName":"e4 c1 l"}}}},{{"BranchName":"e4 c2","Nested":{{"LeafName":"e4 c2 l"}}}}],"OptionalReference":{{"BranchName":"e4 or","Nested":{{"LeafName":"e4 or l"}}}}}}') +'{"RootName":"e4","Collection":[{"BranchName":"e4 c1","Nested":{"LeafName":"e4 c1 l"}},{"BranchName":"e4 c2","Nested":{"LeafName":"e4 c2 l"}}],"OptionalReference":{"BranchName":"e4 or","Nested":{"LeafName":"e4 or l"}}}') """); } + protected override void Seed33046(Context33046 ctx) + => ctx.Database.ExecuteSql( + $$""" +INSERT INTO "Reviews" ("Rounds", "Id") +VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) +"""); + protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) { var entity1 = new MyEntityArrayOfPrimitives @@ -124,53 +131,53 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) } protected override void SeedJunkInJson(MyContextJunkInJson ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") VALUES( -'[{{"JunkReference":{{"Something":"SomeValue" }},"Name":"c11","JunkProperty1":50,"Number":11.5,"JunkCollection1":[],"JunkCollection2":[{{"Foo":"junk value"}}],"NestedCollection":[{{"DoB":"2002-04-01T00:00:00","DummyProp":"Dummy value"}},{{"DoB":"2002-04-02T00:00:00","DummyReference":{{"Foo":5}}}}],"NestedReference":{{"DoB":"2002-03-01T00:00:00"}}}},{{"Name":"c12","Number":12.5,"NestedCollection":[{{"DoB":"2002-06-01T00:00:00"}},{{"DoB":"2002-06-02T00:00:00"}}],"NestedDummy":59,"NestedReference":{{"DoB":"2002-05-01T00:00:00"}}}}]', -'[{{"MyBool":true,"Name":"c11 ctor","JunkReference":{{"Something":"SomeValue","JunkCollection":[{{"Foo":"junk value"}}]}},"NestedCollection":[{{"DoB":"2002-08-01T00:00:00"}},{{"DoB":"2002-08-02T00:00:00"}}],"NestedReference":{{"DoB":"2002-07-01T00:00:00"}}}},{{"MyBool":false,"Name":"c12 ctor","NestedCollection":[{{"DoB":"2002-10-01T00:00:00"}},{{"DoB":"2002-10-02T00:00:00"}}],"JunkCollection":[{{"Foo":"junk value"}}],"NestedReference":{{"DoB":"2002-09-01T00:00:00"}}}}]', -'{{"Name":"r1","JunkCollection":[{{"Foo":"junk value"}}],"JunkReference":{{"Something":"SomeValue" }},"Number":1.5,"NestedCollection":[{{"DoB":"2000-02-01T00:00:00","JunkReference":{{"Something":"SomeValue"}}}},{{"DoB":"2000-02-02T00:00:00"}}],"NestedReference":{{"DoB":"2000-01-01T00:00:00"}}}}', -'{{"MyBool":true,"JunkCollection":[{{"Foo":"junk value"}}],"Name":"r1 ctor","JunkReference":{{"Something":"SomeValue" }},"NestedCollection":[{{"DoB":"2001-02-01T00:00:00"}},{{"DoB":"2001-02-02T00:00:00"}}],"NestedReference":{{"JunkCollection":[{{"Foo":"junk value"}}],"DoB":"2001-01-01T00:00:00"}}}}', +'[{"JunkReference":{"Something":"SomeValue" },"Name":"c11","JunkProperty1":50,"Number":11.5,"JunkCollection1":[],"JunkCollection2":[{"Foo":"junk value"}],"NestedCollection":[{"DoB":"2002-04-01T00:00:00","DummyProp":"Dummy value"},{"DoB":"2002-04-02T00:00:00","DummyReference":{"Foo":5}}],"NestedReference":{"DoB":"2002-03-01T00:00:00"}},{"Name":"c12","Number":12.5,"NestedCollection":[{"DoB":"2002-06-01T00:00:00"},{"DoB":"2002-06-02T00:00:00"}],"NestedDummy":59,"NestedReference":{"DoB":"2002-05-01T00:00:00"}}]', +'[{"MyBool":true,"Name":"c11 ctor","JunkReference":{"Something":"SomeValue","JunkCollection":[{"Foo":"junk value"}]},"NestedCollection":[{"DoB":"2002-08-01T00:00:00"},{"DoB":"2002-08-02T00:00:00"}],"NestedReference":{"DoB":"2002-07-01T00:00:00"}},{"MyBool":false,"Name":"c12 ctor","NestedCollection":[{"DoB":"2002-10-01T00:00:00"},{"DoB":"2002-10-02T00:00:00"}],"JunkCollection":[{"Foo":"junk value"}],"NestedReference":{"DoB":"2002-09-01T00:00:00"}}]', +'{"Name":"r1","JunkCollection":[{"Foo":"junk value"}],"JunkReference":{"Something":"SomeValue" },"Number":1.5,"NestedCollection":[{"DoB":"2000-02-01T00:00:00","JunkReference":{"Something":"SomeValue"}},{"DoB":"2000-02-02T00:00:00"}],"NestedReference":{"DoB":"2000-01-01T00:00:00"}}', +'{"MyBool":true,"JunkCollection":[{"Foo":"junk value"}],"Name":"r1 ctor","JunkReference":{"Something":"SomeValue" },"NestedCollection":[{"DoB":"2001-02-01T00:00:00"},{"DoB":"2001-02-02T00:00:00"}],"NestedReference":{"JunkCollection":[{"Foo":"junk value"}],"DoB":"2001-01-01T00:00:00"}}', 1) """); protected override void SeedTrickyBuffering(MyContextTrickyBuffering ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$$""" INSERT INTO "Entities" ("Reference", "Id") VALUES( -'{{"Name": "r1", "Number": 7, "JunkReference":{{"Something": "SomeValue" }}, "JunkCollection": [{{"Foo": "junk value"}}], "NestedReference": {{"DoB": "2000-01-01T00:00:00Z"}}, "NestedCollection": [{{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {{"Something": "SomeValue"}}}}, {{"DoB": "2000-02-02T00:00:00Z"}}]}}',1) +'{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00Z"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00Z"}]}',1) """); protected override void SeedShadowProperties(MyContextShadowProperties ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") VALUES( -'[{{"Name":"e1_c1","ShadowDouble":5.5}},{{"ShadowDouble":20.5,"Name":"e1_c2"}}]', -'[{{"Name":"e1_c1 ctor","ShadowNullableByte":6}},{{"ShadowNullableByte":null,"Name":"e1_c2 ctor"}}]', -'{{"Name":"e1_r", "ShadowString":"Foo"}}', -'{{"ShadowInt":143,"Name":"e1_r ctor"}}', +'[{"Name":"e1_c1","ShadowDouble":5.5},{"ShadowDouble":20.5,"Name":"e1_c2"}]', +'[{"Name":"e1_c1 ctor","ShadowNullableByte":6},{"ShadowNullableByte":null,"Name":"e1_c2 ctor"}]', +'{"Name":"e1_r", "ShadowString":"Foo"}', +'{"ShadowInt":143,"Name":"e1_r ctor"}', 1, 'e1') """); protected override void SeedNotICollection(MyContextNotICollection ctx) { - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( -'{{"Collection":[{{"Bar":11,"Foo":"c11"}},{{"Bar":12,"Foo":"c12"}},{{"Bar":13,"Foo":"c13"}}]}}', +'{"Collection":[{"Bar":11,"Foo":"c11"},{"Bar":12,"Foo":"c12"},{"Bar":13,"Foo":"c13"}]}', 1) """); - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( -'{{"Collection":[{{"Bar":21,"Foo":"c21"}},{{"Bar":22,"Foo":"c22"}}]}}', +'{"Collection":[{"Bar":21,"Foo":"c21"},{"Bar":22,"Foo":"c22"}]}', 2) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs index 0e1b6a5d4..6d44a9bdd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs @@ -14,16 +14,6 @@ protected override void Seed2951(Context2951 context) INSERT INTO "ZeroKey" VALUES (NULL) """); - // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 - public override Task Nested_queries_does_not_cause_concurrency_exception_sync(bool tracking) - => Assert.ThrowsAsync( - () => base.Nested_queries_does_not_cause_concurrency_exception_sync(tracking)); - - // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 - public override Task Select_nested_projection() - => Assert.ThrowsAsync( - () => base.Select_nested_projection()); - // Writes DateTime with Kind=Unspecified to timestamptz public override Task SelectMany_where_Select(bool async) => Task.CompletedTask; diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs index dbbd481b3..90feaf094 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs @@ -8,10 +8,6 @@ public class AdHocNavigationsQueryNpgsqlTest : AdHocNavigationsQueryRelationalTe public override Task Reference_include_on_derived_type_with_sibling_works() => Assert.ThrowsAsync(() => base.Reference_include_on_derived_type_with_sibling_works()); - // https://github.com/dotnet/efcore/pull/32542/files#r1485618022 - public override Task Nested_include_queries_do_not_populate_navigation_twice() - => Assert.ThrowsAsync(() => base.Nested_include_queries_do_not_populate_navigation_twice()); - protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 3387bac2a..b89306d03 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -685,11 +685,11 @@ await AssertQuery( AssertSql( """ -@__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__patternsActual_1={ 'a%', 'b%', 'c%' } (DbType = Object) SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."NullableText" LIKE ANY (@__patternsActual_0) +WHERE s."NullableText" LIKE ANY (@__patternsActual_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 0dd9a8753..99e88ded6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -703,11 +703,11 @@ await AssertQuery( AssertSql( """ -@__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__patternsActual_1={ 'a%', 'b%', 'c%' } (DbType = Object) SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."NullableText" LIKE ANY (@__patternsActual_0) +WHERE s."NullableText" LIKE ANY (@__patternsActual_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs index 3d684dfdb..2c470c46f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public abstract class ArrayQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase +public abstract class ArrayQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs index 44b42b2b9..207ba6a23 100644 --- a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs @@ -145,7 +145,7 @@ public class Entity public BigInteger BigInteger { get; set; } } - public class BigIntegerQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class BigIntegerQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { private BigIntegerData _expectedData; diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs index 1771836d8..ae27cc581 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs @@ -10,10 +10,6 @@ public ComplexNavigationsQueryNpgsqlTest(ComplexNavigationsQueryNpgsqlFixture fi Fixture.TestSqlLoggerFactory.Clear(); } - // https://github.com/dotnet/efcore/pull/33060 - public override Task Max_in_multi_level_nested_subquery(bool async) - => Assert.ThrowsAsync(() => base.Max_in_multi_level_nested_subquery(async)); - public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async) => await Assert.ThrowsAsync( () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async)); diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index d490537cc..6cedca28b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -742,6 +742,287 @@ public override async Task Union_two_different_struct_complex_type(bool async) AssertSql(); } + public override async Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 +) AS s +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 +) AS s +"""); + } + + public override async Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."Id", s0."Name", s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_Tags", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."ShippingAddress_AddressLine1", s0."ShippingAddress_AddressLine2", s0."ShippingAddress_Tags", s0."ShippingAddress_ZipCode", s0."ShippingAddress_Country_Code", s0."ShippingAddress_Country_FullName", s0."Id0", s0."Name0", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_Tags0", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0", s0."ShippingAddress_AddressLine10", s0."ShippingAddress_AddressLine20", s0."ShippingAddress_Tags0", s0."ShippingAddress_ZipCode0", s0."ShippingAddress_Country_Code0", s0."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + ORDER BY c."Id" NULLS FIRST, c0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_Tags", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_Tags0", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" + FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + ORDER BY c."Id" NULLS FIRST, c0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName", v0."Id" AS "Id0", v0."Name" AS "Name0", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", v0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", v0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", v0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", v0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", v0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 +) AS s +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 +) AS s +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."Id", s0."Name", s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."ShippingAddress_AddressLine1", s0."ShippingAddress_AddressLine2", s0."ShippingAddress_ZipCode", s0."ShippingAddress_Country_Code", s0."ShippingAddress_Country_FullName", s0."Id0", s0."Name0", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0", s0."ShippingAddress_AddressLine10", s0."ShippingAddress_AddressLine20", s0."ShippingAddress_ZipCode0", s0."ShippingAddress_Country_Code0", s0."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" + FROM ( + SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName", v0."Id" AS "Id0", v0."Name" AS "Name0", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", v0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", v0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", v0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", v0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", v0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 + ORDER BY v."Id" NULLS FIRST, v0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" + FROM ( + SELECT v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 + ORDER BY v."Id" NULLS FIRST, v0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u."Id", u."Name", u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."ShippingAddress_AddressLine1", u."ShippingAddress_AddressLine2", u."ShippingAddress_Tags", u."ShippingAddress_ZipCode", u."ShippingAddress_Country_Code", u."ShippingAddress_Country_FullName", u."Id0", u."Name0", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0", u."ShippingAddress_AddressLine10", u."ShippingAddress_AddressLine20", u."ShippingAddress_Tags0", u."ShippingAddress_ZipCode0", u."ShippingAddress_Country_Code0", u."ShippingAddress_Country_FullName0" +FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c2."Id" AS "Id0", c2."Name" AS "Name0", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c2."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c2."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c2."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c2."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c2."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c2."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 +) AS u +ORDER BY u."Id" NULLS FIRST, u."Id0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u1."Id", u1."Name", u1."BillingAddress_AddressLine1", u1."BillingAddress_AddressLine2", u1."BillingAddress_Tags", u1."BillingAddress_ZipCode", u1."BillingAddress_Country_Code", u1."BillingAddress_Country_FullName", u1."ShippingAddress_AddressLine1", u1."ShippingAddress_AddressLine2", u1."ShippingAddress_Tags", u1."ShippingAddress_ZipCode", u1."ShippingAddress_Country_Code", u1."ShippingAddress_Country_FullName", u1."Id0", u1."Name0", u1."BillingAddress_AddressLine10", u1."BillingAddress_AddressLine20", u1."BillingAddress_Tags0", u1."BillingAddress_ZipCode0", u1."BillingAddress_Country_Code0", u1."BillingAddress_Country_FullName0", u1."ShippingAddress_AddressLine10", u1."ShippingAddress_AddressLine20", u1."ShippingAddress_Tags0", u1."ShippingAddress_ZipCode0", u1."ShippingAddress_Country_Code0", u1."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT u0."Id", u0."Name", u0."BillingAddress_AddressLine1", u0."BillingAddress_AddressLine2", u0."BillingAddress_Tags", u0."BillingAddress_ZipCode", u0."BillingAddress_Country_Code", u0."BillingAddress_Country_FullName", u0."ShippingAddress_AddressLine1", u0."ShippingAddress_AddressLine2", u0."ShippingAddress_Tags", u0."ShippingAddress_ZipCode", u0."ShippingAddress_Country_Code", u0."ShippingAddress_Country_FullName", u0."Id0", u0."Name0", u0."BillingAddress_AddressLine10", u0."BillingAddress_AddressLine20", u0."BillingAddress_Tags0", u0."BillingAddress_ZipCode0", u0."BillingAddress_Country_Code0", u0."BillingAddress_Country_FullName0", u0."ShippingAddress_AddressLine10", u0."ShippingAddress_AddressLine20", u0."ShippingAddress_Tags0", u0."ShippingAddress_ZipCode0", u0."ShippingAddress_Country_Code0", u0."ShippingAddress_Country_FullName0" + FROM ( + SELECT u."Id", u."Name", u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."ShippingAddress_AddressLine1", u."ShippingAddress_AddressLine2", u."ShippingAddress_Tags", u."ShippingAddress_ZipCode", u."ShippingAddress_Country_Code", u."ShippingAddress_Country_FullName", u."Id0", u."Name0", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0", u."ShippingAddress_AddressLine10", u."ShippingAddress_AddressLine20", u."ShippingAddress_Tags0", u."ShippingAddress_ZipCode0", u."ShippingAddress_Country_Code0", u."ShippingAddress_Country_FullName0" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c2."Id" AS "Id0", c2."Name" AS "Name0", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c2."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c2."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c2."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c2."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c2."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c2."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 + ) AS u + ORDER BY u."Id" NULLS FIRST, u."Id0" NULLS FIRST + LIMIT @__p_0 + ) AS u0 +) AS u1 +ORDER BY u1."Id" NULLS FIRST, u1."Id0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0" +FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 +) AS u +ORDER BY u."BillingAddress_ZipCode" NULLS FIRST, u."BillingAddress_ZipCode0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u1."BillingAddress_AddressLine1", u1."BillingAddress_AddressLine2", u1."BillingAddress_Tags", u1."BillingAddress_ZipCode", u1."BillingAddress_Country_Code", u1."BillingAddress_Country_FullName", u1."BillingAddress_AddressLine10", u1."BillingAddress_AddressLine20", u1."BillingAddress_Tags0", u1."BillingAddress_ZipCode0", u1."BillingAddress_Country_Code0", u1."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT u0."BillingAddress_AddressLine1", u0."BillingAddress_AddressLine2", u0."BillingAddress_Tags", u0."BillingAddress_ZipCode", u0."BillingAddress_Country_Code", u0."BillingAddress_Country_FullName", u0."BillingAddress_AddressLine10", u0."BillingAddress_AddressLine20", u0."BillingAddress_Tags0", u0."BillingAddress_ZipCode0", u0."BillingAddress_Country_Code0", u0."BillingAddress_Country_FullName0" + FROM ( + SELECT u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0" + FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 + ) AS u + ORDER BY u."BillingAddress_ZipCode" NULLS FIRST, u."BillingAddress_ZipCode0" NULLS FIRST + LIMIT @__p_0 + ) AS u0 +) AS u1 +ORDER BY u1."BillingAddress_ZipCode" NULLS FIRST, u1."BillingAddress_ZipCode0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql( + """ +SELECT c."Id", s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0", s.c +FROM "Customer" AS c +LEFT JOIN LATERAL ( + SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", c1."Id" AS "Id0", c1."Name" AS "Name0", c1."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c1."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c1."BillingAddress_Tags" AS "BillingAddress_Tags0", c1."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c1."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c1."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c1."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c1."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c1."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c1."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c1."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c1."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0", 1 AS c + FROM "Customer" AS c0 + CROSS JOIN "Customer" AS c1 + ORDER BY c0."Id" NULLS FIRST, c1."Id" DESC NULLS LAST + LIMIT 1 +) AS s ON TRUE +"""); + } + + public override async Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql(""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs index b83d9748a..2e7a5b46e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs @@ -79,7 +79,7 @@ SELECT p0."MiddleInitial" private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class Ef6GroupByNpgsqlFixture : Ef6GroupByFixtureBase + public class Ef6GroupByNpgsqlFixture : Ef6GroupByFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index c7f577afc..479506646 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -287,7 +287,7 @@ public enum UnmappedByteEnum : byte } // ReSharper disable once ClassNeverInstantiated.Global - public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override string StoreName => "EnumQueryTest"; diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 0d9e16de1..6c46a8ff5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -38,7 +38,7 @@ await AssertQuery( private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class FunkyDataQueryNpgsqlFixture : FunkyDataQueryFixtureBase + public class FunkyDataQueryNpgsqlFixture : FunkyDataQueryFixtureBase, ITestSqlLoggerFactory { private FunkyDataData _expectedData; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index 188c1a88a..89f18e27e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -314,11 +314,11 @@ public async Task Array_Any_Like(bool async) AssertSql( """ -@__collection_0={ 'A%', 'B%', 'C%' } (DbType = Object) +@__collection_1={ 'A%', 'B%', 'C%' } (DbType = Object) 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."Address" LIKE ANY (@__collection_0) +WHERE c."Address" LIKE ANY (@__collection_1) """); } @@ -336,11 +336,11 @@ public async Task Array_All_Like(bool async) AssertSql( """ -@__collection_0={ 'A%', 'B%', 'C%' } (DbType = Object) +@__collection_1={ 'A%', 'B%', 'C%' } (DbType = Object) 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."Address" LIKE ALL (@__collection_0) +WHERE c."Address" LIKE ALL (@__collection_1) """); } @@ -383,11 +383,11 @@ public async Task Array_Any_ILike(bool async) AssertSql( """ -@__collection_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__collection_1={ 'a%', 'b%', 'c%' } (DbType = Object) 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."Address" ILIKE ANY (@__collection_0) +WHERE c."Address" ILIKE ANY (@__collection_1) """); } @@ -405,11 +405,11 @@ public async Task Array_All_ILike(bool async) AssertSql( """ -@__collection_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__collection_1={ 'a%', 'b%', 'c%' } (DbType = Object) 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."Address" ILIKE ALL (@__collection_0) +WHERE c."Address" ILIKE ALL (@__collection_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs index 0c2142a98..e8d412e21 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class NorthwindQueryNpgsqlFixture : NorthwindQueryRelationalFixture - where TModelCustomizer : IModelCustomizer, new() + where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory => NpgsqlNorthwindTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs index cc6da3a3b..18109a3c8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs @@ -328,11 +328,11 @@ public async Task Row_value_GreaterThan_with_parameter() AssertSql( """ -@__city1_1='Buenos Aires' +@__city1_0='Buenos Aires' SELECT count(*)::int FROM "Customers" AS c -WHERE (c."City", c."CustomerID") > (@__city1_1, 'OCEAN') +WHERE (c."City", c."CustomerID") > (@__city1_0, 'OCEAN') """); } @@ -352,11 +352,11 @@ public async Task Row_value_GreaterThan_with_parameter_with_ValueTuple_construct AssertSql( """ -@__city1_1='Buenos Aires' +@__city1_0='Buenos Aires' SELECT count(*)::int FROM "Customers" AS c -WHERE (c."City", c."CustomerID") > (@__city1_1, 'OCEAN') +WHERE (c."City", c."CustomerID") > (@__city1_0, 'OCEAN') """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index e18c0bfa7..3f73b78cf 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1426,15 +1426,15 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi AssertSql( """ -@__ints_1={ '1', '2', '3' } (DbType = Object) -@__strings_0={ 'one', 'two', 'three' } (DbType = Object) +@__ints_0={ '1', '2', '3' } (DbType = Object) +@__strings_1={ 'one', 'two', 'three' } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE CASE - WHEN p."Int" = ANY (@__ints_1) THEN 'one' + WHEN p."Int" = ANY (@__ints_0) THEN 'one' ELSE 'two' -END = ANY (@__strings_0) +END = ANY (@__strings_1) """); } @@ -1444,15 +1444,15 @@ public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapp AssertSql( """ -@__ints_1={ '1', '2', '3' } (DbType = Object) -@__strings_0={ 'one', 'two', 'three' } (DbType = Object) +@__ints_0={ '1', '2', '3' } (DbType = Object) +@__strings_1={ 'one', 'two', 'three' } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE CASE - WHEN p."Int" = ANY (@__ints_1) THEN 'one' + WHEN p."Int" = ANY (@__ints_0) THEN 'one' ELSE 'two' -END = ANY (@__strings_0) +END = ANY (@__strings_1) """); } @@ -1466,7 +1466,7 @@ private void AssertSql(params string[] expected) private PrimitiveCollectionsContext CreateContext() => Fixture.CreateContext(); - public class PrimitiveCollectionsQueryNpgsqlFixture : PrimitiveCollectionsQueryFixtureBase + public class PrimitiveCollectionsQueryNpgsqlFixture : PrimitiveCollectionsQueryFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs index fac71b515..2c10adc6f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs @@ -2,9 +2,15 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) - : SqlExecutorTestBase>(fixture) +public class SqlExecutorNpgsqlTest : SqlExecutorTestBase> { + public SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + protected override DbParameter CreateDbParameter(string name, object value) => new NpgsqlParameter { ParameterName = name, Value = value }; diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index f94f7c692..4721eba93 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -860,7 +860,7 @@ public class Entity public NpgsqlRange TimestampDateTimeRange { get; set; } } - public class TimestampQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class TimestampQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override string StoreName => "TimestampQueryTest"; diff --git a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs index ec24c062d..02ed4cdf2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs +++ b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs @@ -232,12 +232,12 @@ public override void Scalar_Function_Let_Nested_Static() AssertSql( """ -@__starCount_0='3' -@__customerId_1='1' +@__starCount_1='3' +@__customerId_0='1' -SELECT c."LastName", "StarValue"(@__starCount_0, "CustomerOrderCount"(@__customerId_1)) AS "OrderCount" +SELECT c."LastName", "StarValue"(@__starCount_1, "CustomerOrderCount"(@__customerId_0)) AS "OrderCount" FROM "Customers" AS c -WHERE c."Id" = @__customerId_1 +WHERE c."Id" = @__customerId_0 LIMIT 2 """); } @@ -530,12 +530,12 @@ public override void Scalar_Function_Let_Nested_Instance() AssertSql( """ -@__starCount_1='3' -@__customerId_2='1' +@__starCount_2='3' +@__customerId_1='1' -SELECT c."LastName", "StarValue"(@__starCount_1, "CustomerOrderCount"(@__customerId_2)) AS "OrderCount" +SELECT c."LastName", "StarValue"(@__starCount_2, "CustomerOrderCount"(@__customerId_1)) AS "OrderCount" FROM "Customers" AS c -WHERE c."Id" = @__customerId_2 +WHERE c."Id" = @__customerId_1 LIMIT 2 """); } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index cd67af7b5..8b64a5cb1 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -1986,20 +1986,20 @@ public override Task Edit_single_property_relational_collection_of_nullable_enum => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum()); public override Task Edit_single_property_relational_collection_of_nullable_enum_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_set_to_null()); + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_set_to_null()); public override Task Edit_single_property_relational_collection_of_nullable_enum_with_int_converter() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_with_int_converter()); public override Task Edit_single_property_relational_collection_of_nullable_enum_with_int_converter_set_to_null() - => Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Edit_single_property_relational_collection_of_nullable_enum_with_int_converter_set_to_null()); public override Task Edit_single_property_relational_collection_of_nullable_int32() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32()); public override Task Edit_single_property_relational_collection_of_nullable_int32_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32_set_to_null()); + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32_set_to_null()); public override Task Edit_single_property_relational_collection_of_uint16() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_uint16()); diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 7841a2a3c..134e4e820 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1,5 +1,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -339,34 +340,43 @@ await AssertQuery( t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).ToInstant() == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + Assert.Throws( + () => + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE 'Europe/Berlin' = @__ToInstant_0 -"""); +""")); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task LocalDateTime_InZoneLeniently_ToInstant_with_column_time_zone(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() + == new ZonedDateTime( + new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE n."TimeZoneId" = @__ToInstant_0 """); + }); } [ConditionalFact] @@ -1491,20 +1501,25 @@ WHERE @__dateRange_0 @> n."LocalDate" [MemberData(nameof(IsAsyncData))] public async Task Instance_InUtc(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.Instant.InUtc() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InUtc() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); + + AssertSql( + """ @__p_0='2018-04-20T10:31:33 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."Instant" = @__p_0 """); + }); } [ConditionalTheory] @@ -1797,36 +1812,45 @@ await AssertQuery( [MemberData(nameof(IsAsyncData))] public async Task ZonedDateTime_ToInstant(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.ZonedDateTime.ToInstant() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.ZonedDateTime.ToInstant() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T10:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."ZonedDateTime" = @__ToInstant_0 """); + }); } [ConditionalFact] public async Task ZonedDateTime_Distance() { await using var context = CreateContext(); - var closest = await context.NodaTimeTypes - .OrderBy( - t => EF.Functions.Distance( - t.ZonedDateTime, - new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); - - Assert.Equal(1, closest.Id); - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + var closest = await context.NodaTimeTypes + .OrderBy( + t => EF.Functions.Distance( + t.ZonedDateTime, + new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); + Assert.Equal(1, closest.Id); + + AssertSql( + """ @__p_1='2018-04-01T00:00:00 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" @@ -1834,6 +1858,7 @@ public async Task ZonedDateTime_Distance() ORDER BY n."ZonedDateTime" <-> @__p_1 NULLS FIRST LIMIT 1 """); + }); } #endregion ZonedDateTime diff --git a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs index c5ce7a25a..1b69e2dbe 100644 --- a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs @@ -47,10 +47,13 @@ private void AssertGenerator(string propertyName, bool setSequences = var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType(selector.Select(entityType.FindProperty(propertyName), entityType)); + var property = entityType.FindProperty(propertyName)!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + + Assert.IsType(generator); } - [Fact] + [ConditionalFact] public void Returns_temp_guid_generator_when_default_sql_set() { var builder = NpgsqlTestHelpers.Instance.CreateConventionBuilder(); @@ -65,7 +68,9 @@ public void Returns_temp_guid_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType(selector.Select(entityType.FindProperty("Guid"), entityType)); + var property = entityType.FindProperty("Guid")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); } [ConditionalFact] @@ -83,7 +88,9 @@ public void Returns_temp_string_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - var generator = selector.Select(entityType.FindProperty("String"), entityType); + var property = entityType.FindProperty("String")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); Assert.True(generator.GeneratesTemporaryValues); } @@ -103,7 +110,9 @@ public void Returns_temp_binary_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - var generator = selector.Select(entityType.FindProperty("Binary"), entityType); + var property = entityType.FindProperty("Binary")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); Assert.True(generator.GeneratesTemporaryValues); } @@ -159,7 +168,10 @@ public void Returns_generator_configured_on_model_when_property_is_identity() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType>(selector.Select(entityType.FindProperty("Id"), entityType)); + var property = entityType.FindProperty("Id")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + + Assert.IsType>(generator); } private class AnEntity