diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs index 30aeafb91..dc85a5565 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs @@ -31,4 +31,19 @@ public NpgsqlMigrationsAnnotationProvider(MigrationsAnnotationProviderDependenci /// public override IEnumerable ForRemove(IRelationalModel model) => model.Model.GetAnnotations().Where(NpgsqlAnnotationHelper.IsRelationalModelAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IEnumerable ForRemove(ITableIndex index) + { + // Model validation ensures that these facets are the same on all mapped indexes + if (index.MappedIndexes.First().IsCreatedConcurrently() is { } isCreatedConcurrently) + { + yield return new Annotation(NpgsqlAnnotationNames.CreatedConcurrently, isCreatedConcurrently); + } + } } diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index c5478e4cf..3f3e97bcf 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1491,14 +1491,21 @@ protected override void Generate( Check.NotNull(operation, nameof(operation)); Check.NotNull(builder, nameof(builder)); - builder - .Append("DROP INDEX ") - .Append(DelimitIdentifier(operation.Name, operation.Schema)); + builder.Append("DROP INDEX "); + + var concurrently = operation[NpgsqlAnnotationNames.CreatedConcurrently] as bool? == true; + if (concurrently) + { + builder.Append("CONCURRENTLY "); + } + + builder.Append(DelimitIdentifier(operation.Name, operation.Schema)); if (terminate) { builder.AppendLine(";"); - EndStatement(builder); + // Concurrent indexes cannot be dropped within a transaction + EndStatement(builder, suppressTransaction: concurrently); } } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index bfdc208ae..7a0455f96 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2019,6 +2019,24 @@ await Test( AssertSql("""CREATE INDEX CONCURRENTLY "IX_People_Age" ON "People" ("Age");"""); } + [Fact] + public virtual async Task Drop_index_concurrently() + { + await Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Age"); + }), + builder => builder.Entity("People").HasIndex("Age") + .IsCreatedConcurrently(), + _ => { }, + asserter: null); // No scaffolding for IsCreatedConcurrently + + AssertSql("""DROP INDEX CONCURRENTLY "IX_People_Age";"""); + } + [Fact] public virtual async Task Create_index_with_method() { diff --git a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs index 2cf1b21d9..c11e1bb5b 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs @@ -619,6 +619,43 @@ public override void InsertDataOperation_throws_for_unsupported_column_types() #pragma warning restore 618 + [Fact] + public virtual void DropIndexOperation_concurrently() + { + Generate( + new DropIndexOperation + { + Name = "IX_People_Name", + Table = "People", + Schema = "dbo", + [NpgsqlAnnotationNames.CreatedConcurrently] = true + }); + + AssertSql( + """ +DROP INDEX CONCURRENTLY dbo."IX_People_Name"; + +"""); + } + + [Fact] + public virtual void DropIndexOperation_not_concurrently() + { + Generate( + new DropIndexOperation + { + Name = "IX_People_Name", + Table = "People", + Schema = "dbo" + }); + + AssertSql( + """ +DROP INDEX dbo."IX_People_Name"; + +"""); + } + protected override string GetGeometryCollectionStoreType() => "GEOMETRY(GEOMETRYCOLLECTION)"; }