From 8aa8690f94c12c9740bf746b2ec55def13941d35 Mon Sep 17 00:00:00 2001 From: Alessio Napolitano <121714208+alenap93@users.noreply.github.com> Date: Fri, 26 Jan 2024 06:38:30 +0100 Subject: [PATCH] fix: resolve issue CREATE/DROP Index concurrently (#10634) Closes: #10626 --- docs/indices.md | 1 + src/driver/postgres/PostgresQueryRunner.ts | 10 ++-- test/github-issues/10626/entity/user.ts | 13 +++++ test/github-issues/10626/issue-10626.ts | 62 ++++++++++++++++++++++ 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 test/github-issues/10626/entity/user.ts create mode 100644 test/github-issues/10626/issue-10626.ts diff --git a/docs/indices.md b/docs/indices.md index 292e2e73e..e943245c4 100644 --- a/docs/indices.md +++ b/docs/indices.md @@ -140,6 +140,7 @@ export class Thing { ## Concurrent creation In order to avoid having to obtain an access exclusive lock when creating and dropping indexes in postgres, you may create them using the CONCURRENTLY modifier. +If you want use the concurrent option, you need set `migrationsTransactionMode: none` between data source options. Typeorm supports generating SQL with this option if when the concurrent option is specified on the index. diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index 50d64e34c..fb30aa228 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -4297,9 +4297,9 @@ export class PostgresQueryRunner .map((columnName) => `"${columnName}"`) .join(", ") return new Query( - `CREATE ${index.isUnique ? "UNIQUE " : ""}${ - index.isConcurrent ? "CONCURRENTLY " : "" - }INDEX "${index.name}" ON ${this.escapePath(table)} ${ + `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX${ + index.isConcurrent ? " CONCURRENTLY" : "" + } "${index.name}" ON ${this.escapePath(table)} ${ index.isSpatial ? "USING GiST " : "" }(${columns}) ${index.where ? "WHERE " + index.where : ""}`, ) @@ -4338,12 +4338,12 @@ export class PostgresQueryRunner return schema ? new Query( `DROP INDEX ${ - concurrent ? "CONCURRENTLY" : "" + concurrent ? "CONCURRENTLY " : "" }"${schema}"."${indexName}"`, ) : new Query( `DROP INDEX ${ - concurrent ? "CONCURRENTLY" : "" + concurrent ? "CONCURRENTLY " : "" }"${indexName}"`, ) } diff --git a/test/github-issues/10626/entity/user.ts b/test/github-issues/10626/entity/user.ts new file mode 100644 index 000000000..ae8d78db5 --- /dev/null +++ b/test/github-issues/10626/entity/user.ts @@ -0,0 +1,13 @@ +import { Column, Entity, Index, PrimaryGeneratedColumn } from "../../../../src" + +@Entity({ + name: "user", +}) +export class User { + @PrimaryGeneratedColumn() + id: number + + @Index("concurrentTest", { concurrent: true }) + @Column({ nullable: true }) + name: string +} diff --git a/test/github-issues/10626/issue-10626.ts b/test/github-issues/10626/issue-10626.ts new file mode 100644 index 000000000..12dd3e076 --- /dev/null +++ b/test/github-issues/10626/issue-10626.ts @@ -0,0 +1,62 @@ +import "reflect-metadata" +import { + createTestingConnections, + closeTestingConnections, +} from "../../utils/test-utils" +import { DataSource } from "../../../src/index.js" +import { expect } from "chai" + +describe("github issues > #10626 Postgres CREATE INDEX CONCURRENTLY bug", () => { + let dataSources: DataSource[] + + before( + async () => + (dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: false, + dropSchema: true, + enabledDrivers: ["postgres"], + logging: true, + })), + ) + + after(() => closeTestingConnections(dataSources)) + + it("has to create INDEX CONCURRENTLY", () => + Promise.all( + dataSources.map(async (dataSource) => { + await dataSource.setOptions({ + ...dataSource.options, + migrationsTransactionMode: "none", + }) + await dataSource.synchronize() + const concurrentTestIndexes = await dataSource.query( + `SELECT * FROM pg_indexes WHERE indexname = 'concurrentTest'`, + ) + expect(concurrentTestIndexes).has.length(1) + }), + )) + + it("has to drop INDEX CONCURRENTLY", () => + Promise.all( + dataSources.map(async (dataSource) => { + await dataSource.setOptions({ + ...dataSource.options, + migrationsTransactionMode: "none", + }) + await dataSource.synchronize() + + const queryRunner = dataSource.createQueryRunner() + let table = await queryRunner.getTable("user") + if (table) { + await queryRunner.dropIndex(table, table?.indices[0]) + } + const queries = queryRunner.getMemorySql().upQueries + expect(queries[0].query).to.be.eql( + 'DROP INDEX "public"."concurrentTest"', + ) + + await queryRunner.release() + }), + )) +})