From fb33713fa955d1f11de572eb5f7ee2c366c88163 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Thu, 18 Jun 2020 20:38:31 -0400 Subject: [PATCH 01/11] prepend className to unique index to allow multiple unique indexes for different classes --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 6b58120778..669732fb86 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -2062,9 +2062,7 @@ export class PostgresStorageAdapter implements StorageAdapter { schema: SchemaType, fieldNames: string[] ) { - // Use the same name for every ensureUniqueness attempt, because postgres - // Will happily create the same index with multiple names. - const constraintName = `unique_${fieldNames.sort().join('_')}`; + const constraintName = `${className}_unique_${fieldNames.sort().join('_')}`; const constraintPatterns = fieldNames.map( (fieldName, index) => `$${index + 3}:name` ); From 839cee8d4dee256ba72e2c35ad9ea264ad52ee7a Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Mon, 22 Jun 2020 00:09:43 -0400 Subject: [PATCH 02/11] add testcase --- spec/PostgresStorageAdapter.spec.js | 112 +++++++++++++++++++++------- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index 6c91c6626e..2381527442 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -9,7 +9,7 @@ const getColumns = (client, className) => { return client.map( 'SELECT column_name FROM information_schema.columns WHERE table_name = $', { className }, - a => a.column_name + (a) => a.column_name ); }; @@ -25,7 +25,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { return adapter.deleteAllClasses(); }); - it('schemaUpgrade, upgrade the database schema when schema changes', done => { + it('schemaUpgrade, upgrade the database schema when schema changes', (done) => { const client = adapter._client; const className = '_PushStatus'; const schema = { @@ -39,7 +39,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { adapter .createTable(className, schema) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns).toContain('pushTime'); expect(columns).toContain('source'); expect(columns).toContain('query'); @@ -49,17 +49,17 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { return adapter.schemaUpgrade(className, schema); }) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns).toContain('pushTime'); expect(columns).toContain('source'); expect(columns).toContain('query'); expect(columns).toContain('expiration_interval'); done(); }) - .catch(error => done.fail(error)); + .catch((error) => done.fail(error)); }); - it('schemaUpgrade, maintain correct schema', done => { + it('schemaUpgrade, maintain correct schema', (done) => { const client = adapter._client; const className = 'Table'; const schema = { @@ -73,7 +73,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { adapter .createTable(className, schema) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns).toContain('columnA'); expect(columns).toContain('columnB'); expect(columns).toContain('columnC'); @@ -81,7 +81,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { return adapter.schemaUpgrade(className, schema); }) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns.length).toEqual(3); expect(columns).toContain('columnA'); expect(columns).toContain('columnB'); @@ -89,16 +89,16 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { done(); }) - .catch(error => done.fail(error)); + .catch((error) => done.fail(error)); }); - it('Create a table without columns and upgrade with columns', done => { + it('Create a table without columns and upgrade with columns', (done) => { const client = adapter._client; const className = 'EmptyTable'; dropTable(client, className) .then(() => adapter.createTable(className, {})) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns.length).toBe(0); const newSchema = { @@ -111,7 +111,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { return adapter.schemaUpgrade(className, newSchema); }) .then(() => getColumns(client, className)) - .then(columns => { + .then((columns) => { expect(columns.length).toEqual(2); expect(columns).toContain('columnA'); expect(columns).toContain('columnB'); @@ -176,10 +176,10 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { ); await client .one(analyzedExplainQuery, [tableName, 'objectId', caseInsensitiveData]) - .then(explained => { + .then((explained) => { const preIndexPlan = explained; - preIndexPlan['QUERY PLAN'].forEach(element => { + preIndexPlan['QUERY PLAN'].forEach((element) => { //Make sure search returned with only 1 result expect(element.Plan['Actual Rows']).toBe(1); expect(element.Plan['Node Type']).toBe('Seq Scan'); @@ -195,17 +195,17 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { 'objectId', caseInsensitiveData, ]) - .then(explained => { + .then((explained) => { const postIndexPlan = explained; - postIndexPlan['QUERY PLAN'].forEach(element => { + postIndexPlan['QUERY PLAN'].forEach((element) => { //Make sure search returned with only 1 result expect(element.Plan['Actual Rows']).toBe(1); //Should not be a sequential scan expect(element.Plan['Node Type']).not.toContain('Seq Scan'); //Should be using the index created for this - element.Plan.Plans.forEach(innerElement => { + element.Plan.Plans.forEach((innerElement) => { expect(innerElement['Index Name']).toBe(indexName); }); }); @@ -230,8 +230,8 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { 'objectId', caseInsensitiveData, ]) - .then(explained => { - explained['QUERY PLAN'].forEach(element => { + .then((explained) => { + explained['QUERY PLAN'].forEach((element) => { //Check that basic query plans isn't a sequential scan expect(element.Plan['Node Type']).not.toContain( 'Seq Scan' @@ -244,7 +244,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { }); }); }) - .catch(error => { + .catch((error) => { // Query on non existing table, don't crash if (error.code !== '42P01') { throw error; @@ -276,8 +276,8 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { { caseInsensitive: true, explain: true } ); - preIndexPlan.forEach(element => { - element['QUERY PLAN'].forEach(innerElement => { + preIndexPlan.forEach((element) => { + element['QUERY PLAN'].forEach((innerElement) => { //Check that basic query plans isn't a sequential scan, be careful as find uses "any" to query expect(innerElement.Plan['Node Type']).toBe('Seq Scan'); //Basic query plans shouldn't have an execution time @@ -302,8 +302,8 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { { caseInsensitive: true, explain: true } ); - postIndexPlan.forEach(element => { - element['QUERY PLAN'].forEach(innerElement => { + postIndexPlan.forEach((element) => { + element['QUERY PLAN'].forEach((innerElement) => { //Check that basic query plans isn't a sequential scan expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); @@ -339,13 +339,73 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { { username: caseInsensitiveData }, { caseInsensitive: true, explain: true } ); - indexPlan.forEach(element => { - element['QUERY PLAN'].forEach(innerElement => { + indexPlan.forEach((element) => { + element['QUERY PLAN'].forEach((innerElement) => { expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); expect(innerElement.Plan['Index Name']).toContain('parse_default'); }); }); }); + + it('should allow multiple unique indexes for same field name and different class', async () => { + const firstTableName = 'Test1'; + const firstTableSchema = new Parse.Schema(firstTableName); + const uniqueField = 'uuid'; + firstTableSchema.addString(uniqueField); + await firstTableSchema.save(); + await firstTableSchema.get(); + + const secondTableName = 'Test2'; + const secondTableSchema = new Parse.Schema(secondTableName); + secondTableSchema.addString(uniqueField); + await secondTableSchema.save(); + await secondTableSchema.get(); + + const database = Config.get(Parse.applicationId).database; + + //Create index before data is inserted + await adapter.ensureUniqueness(firstTableName, firstTableSchema, [ + uniqueField, + ]); + await adapter.ensureUniqueness(secondTableName, secondTableSchema, [ + uniqueField, + ]); + + //Postgres won't take advantage of the index until it has a lot of records because sequential is faster for small db's + const client = adapter._client; + await client.none( + 'INSERT INTO $1:name ($2:name, $3:name) SELECT MD5(random()::text), MD5(random()::text) FROM generate_series(1,5000)', + [firstTableName, 'objectId', uniqueField] + ); + await client.none( + 'INSERT INTO $1:name ($2:name, $3:name) SELECT MD5(random()::text), MD5(random()::text) FROM generate_series(1,5000)', + [secondTableName, 'objectId', uniqueField] + ); + + //Check using find method for Parse + const indexPlan = await database.find( + firstTableName, + { uuid: '1234' }, + { caseInsensitive: false, explain: true } + ); + indexPlan.forEach((element) => { + element['QUERY PLAN'].forEach((innerElement) => { + expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); + expect(innerElement.Plan['Index Name']).toContain(firstTableName); + }); + }); + const indexPlan2 = await database.find( + secondTableName, + { uuid: '1234' }, + { caseInsensitive: false, explain: true } + ); + indexPlan2.forEach((element) => { + element['QUERY PLAN'].forEach((innerElement) => { + expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); + expect(innerElement.Plan['Index Name']).toContain(secondTableName); + }); + }); + }); }); describe_only_db('postgres')('PostgresStorageAdapter shutdown', () => { From 74bc9ecb38b5765765296abfe8c6ef19c82aca66 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Mon, 22 Jun 2020 00:34:24 -0400 Subject: [PATCH 03/11] switched test so it can be tested on older versions of parse-server and show failure --- spec/PostgresStorageAdapter.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index 2381527442..8b02fecb69 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -391,7 +391,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { indexPlan.forEach((element) => { element['QUERY PLAN'].forEach((innerElement) => { expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); - expect(innerElement.Plan['Index Name']).toContain(firstTableName); + expect(innerElement.Plan['Index Name']).toContain(uniqueField); }); }); const indexPlan2 = await database.find( @@ -402,7 +402,7 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { indexPlan2.forEach((element) => { element['QUERY PLAN'].forEach((innerElement) => { expect(innerElement.Plan['Node Type']).not.toContain('Seq Scan'); - expect(innerElement.Plan['Index Name']).toContain(secondTableName); + expect(innerElement.Plan['Index Name']).toContain(uniqueField); }); }); }); From 832f64544af156cfb47f472ce9f241b6aade4c46 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Tue, 23 Jun 2020 21:44:22 -0400 Subject: [PATCH 04/11] get rid of console log messages on restart by checking if the index exists before creating it --- .../Storage/Postgres/PostgresStorageAdapter.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 669732fb86..5431bd7117 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -2488,11 +2488,10 @@ export class PostgresStorageAdapter implements StorageAdapter { return (conn || this._client).tx((t) => t.batch( indexes.map((i) => { - return t.none('CREATE INDEX $1:name ON $2:name ($3:name)', [ - i.name, - className, - i.key, - ]); + return t.none( + 'CREATE INDEX IF NOT EXISTS $1:name ON $2:name ($3:name)', + [i.name, className, i.key] + ); }) ) ); @@ -2506,7 +2505,7 @@ export class PostgresStorageAdapter implements StorageAdapter { ): Promise { await ( conn || this._client - ).none('CREATE INDEX $1:name ON $2:name ($3:name)', [ + ).none('CREATE INDEX IF NOT EXISTS $1:name ON $2:name ($3:name)', [ fieldName, className, type, @@ -2585,7 +2584,7 @@ export class PostgresStorageAdapter implements StorageAdapter { (fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops` ) : fieldNames.map((fieldName, index) => `$${index + 3}:name`); - const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`; + const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`; await conn .none(qs, [indexNameOptions.name, className, ...fieldNames]) .catch((error) => { From 9b349903785bac968e45ae9c5b13d4e45bb77374 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Tue, 23 Jun 2020 22:53:12 -0400 Subject: [PATCH 05/11] add IF NOT EXISTS and IF EXISTS to ALTER TABLE --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 5431bd7117..772bf9c86b 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (type.type !== 'Relation') { try { await t.none( - 'ALTER TABLE $ ADD COLUMN $ $', + 'ALTER TABLE $ ADD COLUMN IF NOT EXISTS $ $', { className, fieldName, @@ -1270,7 +1270,10 @@ export class PostgresStorageAdapter implements StorageAdapter { { schema, className } ); if (values.length > 1) { - await t.none(`ALTER TABLE $1:name DROP COLUMN ${columns}`, values); + await t.none( + `ALTER TABLE $1:name DROP COLUMN IF EXISTS ${columns}`, + values + ); } }); } @@ -2066,7 +2069,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const constraintPatterns = fieldNames.map( (fieldName, index) => `$${index + 3}:name` ); - const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`; + const qs = `ALTER TABLE $1:name ADD CONSTRAINT IF NOT EXISTS $2:name UNIQUE (${constraintPatterns.join()})`; return this._client .none(qs, [className, constraintName, ...fieldNames]) .catch((error) => { From 7aace756d0f36277a3670f8fec32d889af125d83 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Tue, 23 Jun 2020 23:39:59 -0400 Subject: [PATCH 06/11] revert some of code --- .../Storage/Postgres/PostgresStorageAdapter.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 772bf9c86b..d697838894 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (type.type !== 'Relation') { try { await t.none( - 'ALTER TABLE $ ADD COLUMN IF NOT EXISTS $ $', + 'ALTER TABLE $ ADD COLUMN $ $', { className, fieldName, @@ -1270,10 +1270,7 @@ export class PostgresStorageAdapter implements StorageAdapter { { schema, className } ); if (values.length > 1) { - await t.none( - `ALTER TABLE $1:name DROP COLUMN IF EXISTS ${columns}`, - values - ); + await t.none(`ALTER TABLE $1:name DROP COLUMN ${columns}`, values); } }); } @@ -2069,7 +2066,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const constraintPatterns = fieldNames.map( (fieldName, index) => `$${index + 3}:name` ); - const qs = `ALTER TABLE $1:name ADD CONSTRAINT IF NOT EXISTS $2:name UNIQUE (${constraintPatterns.join()})`; + const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`; return this._client .none(qs, [className, constraintName, ...fieldNames]) .catch((error) => { @@ -2587,7 +2584,7 @@ export class PostgresStorageAdapter implements StorageAdapter { (fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops` ) : fieldNames.map((fieldName, index) => `$${index + 3}:name`); - const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`; + const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`; await conn .none(qs, [indexNameOptions.name, className, ...fieldNames]) .catch((error) => { From 533ca0acc72b721c2d68c34e4ed9eb441e84ac7b Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Wed, 24 Jun 2020 09:23:55 -0400 Subject: [PATCH 07/11] ensureIndex use IF NOT EXISTS --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index d697838894..5431bd7117 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -2584,7 +2584,7 @@ export class PostgresStorageAdapter implements StorageAdapter { (fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops` ) : fieldNames.map((fieldName, index) => `$${index + 3}:name`); - const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`; + const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`; await conn .none(qs, [indexNameOptions.name, className, ...fieldNames]) .catch((error) => { From 84d5d1f3974aca061bb3531fb0869c65e29847b1 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Wed, 24 Jun 2020 09:51:32 -0400 Subject: [PATCH 08/11] ALTER TABLE CONSTRAINT can't use IF, ADD/DROP COLUMN can --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 5431bd7117..3b3374ce03 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (type.type !== 'Relation') { try { await t.none( - 'ALTER TABLE $ ADD COLUMN $ $', + 'ALTER TABLE $ ADD COLUMN IF NOT EXISTS $ $', { className, fieldName, @@ -1270,7 +1270,10 @@ export class PostgresStorageAdapter implements StorageAdapter { { schema, className } ); if (values.length > 1) { - await t.none(`ALTER TABLE $1:name DROP COLUMN ${columns}`, values); + await t.none( + `ALTER TABLE $1:name DROP COLUMN IF EXISTS ${columns}`, + values + ); } }); } From b0f781d65396db8bf3dced4acc3a09c018fee4f2 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Wed, 24 Jun 2020 10:36:23 -0400 Subject: [PATCH 09/11] retesting --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 3b3374ce03..1fc0fcd7ce 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (type.type !== 'Relation') { try { await t.none( - 'ALTER TABLE $ ADD COLUMN IF NOT EXISTS $ $', + 'ALTER TABLE $ ADD COLUMN $ $', { className, fieldName, From e1bafd76769964ae9178f6f01f9e7f9b1086a918 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Wed, 24 Jun 2020 10:37:21 -0400 Subject: [PATCH 10/11] update --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 1fc0fcd7ce..3b3374ce03 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (type.type !== 'Relation') { try { await t.none( - 'ALTER TABLE $ ADD COLUMN $ $', + 'ALTER TABLE $ ADD COLUMN IF NOT EXISTS $ $', { className, fieldName, From 1481aba54fbd4101147c84bc22b0f994f36d5247 Mon Sep 17 00:00:00 2001 From: Corey MacBook Pro Date: Wed, 24 Jun 2020 11:15:50 -0400 Subject: [PATCH 11/11] switchted to CREATE UNIQUE INDEX instrad of ALTER TABLE... ALTER TABLE doesn't seem to be needed --- src/Adapters/Storage/Postgres/PostgresStorageAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 3b3374ce03..a0264a9d1c 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -2069,7 +2069,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const constraintPatterns = fieldNames.map( (fieldName, index) => `$${index + 3}:name` ); - const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`; + const qs = `CREATE UNIQUE INDEX IF NOT EXISTS $2:name ON $1:name(${constraintPatterns.join()})`; return this._client .none(qs, [className, constraintName, ...fieldNames]) .catch((error) => {