diff --git a/clients/typescript/src/migrators/query-builder/builder.ts b/clients/typescript/src/migrators/query-builder/builder.ts index 4d1a6b2543..64f4aaf3f6 100644 --- a/clients/typescript/src/migrators/query-builder/builder.ts +++ b/clients/typescript/src/migrators/query-builder/builder.ts @@ -27,6 +27,11 @@ export abstract class QueryBuilder { */ abstract readonly getVersion: string + /** + * Disables foreign key checks. + */ + abstract readonly disableForeignKeys: string + /** * Returns the given query if the current SQL dialect is PostgreSQL. */ diff --git a/clients/typescript/src/migrators/query-builder/pgBuilder.ts b/clients/typescript/src/migrators/query-builder/pgBuilder.ts index 4be9c21875..a4eba8648c 100644 --- a/clients/typescript/src/migrators/query-builder/pgBuilder.ts +++ b/clients/typescript/src/migrators/query-builder/pgBuilder.ts @@ -14,6 +14,9 @@ class PgBuilder extends QueryBuilder { readonly paramSign = '$' readonly defaultNamespace = 'public' + /** **Disables** FKs for the duration of the transaction */ + readonly disableForeignKeys = 'SET LOCAL session_replication_role = replica;' + pgOnly(query: string) { return query } diff --git a/clients/typescript/src/migrators/query-builder/sqliteBuilder.ts b/clients/typescript/src/migrators/query-builder/sqliteBuilder.ts index a733616424..5890e2535a 100644 --- a/clients/typescript/src/migrators/query-builder/sqliteBuilder.ts +++ b/clients/typescript/src/migrators/query-builder/sqliteBuilder.ts @@ -18,6 +18,8 @@ class SqliteBuilder extends QueryBuilder { 'sqlite_temp_schema', ] + readonly disableForeignKeys = 'PRAGMA foreign_keys = OFF;' + pgOnly(_query: string) { return '' } diff --git a/clients/typescript/src/satellite/process.ts b/clients/typescript/src/satellite/process.ts index 79453922b7..8c7ca93b1b 100644 --- a/clients/typescript/src/satellite/process.ts +++ b/clients/typescript/src/satellite/process.ts @@ -475,7 +475,17 @@ export class SatelliteProcess implements Satellite { ) { const namespace = this.builder.defaultNamespace const stmts: Statement[] = [] - stmts.push({ sql: this.builder.deferForeignKeys }) + + if (this.builder.dialect === 'Postgres') { + // disable FK checks because order of inserts + // may not respect referential integrity + // and Postgres doesn't let us defer FKs + // that were not originally defined as deferrable + stmts.push({ sql: this.builder.disableForeignKeys }) + } else { + // Defer FKs on SQLite + stmts.push({ sql: this.builder.deferForeignKeys }) + } // It's much faster[1] to do less statements to insert the data instead of doing an insert statement for each row // so we're going to do just that, but with a caveat: SQLite has a max number of parameters in prepared statements, @@ -1294,6 +1304,17 @@ export class SatelliteProcess implements Satellite { const lsn = transaction.lsn let firstDMLChunk = true + if (this.builder.dialect === 'Postgres') { + // Temporarily disable FK checks because order of inserts + // may not respect referential integrity + // and Postgres doesn't let us defer FKs + // that were not originally defined as deferrable + stmts.push({ sql: this.builder.disableForeignKeys }) + } else { + // Defer FKs on SQLite + stmts.push({ sql: this.builder.deferForeignKeys }) + } + // update lsn. stmts.push(this.updateLsnStmt(lsn)) stmts.push(this._resetSeenAdditionalDataStmt()) diff --git a/clients/typescript/test/satellite/process.test.ts b/clients/typescript/test/satellite/process.test.ts index 0663505bbb..5b35bd1d78 100644 --- a/clients/typescript/test/satellite/process.test.ts +++ b/clients/typescript/test/satellite/process.test.ts @@ -1148,6 +1148,14 @@ export const processTests = (test: TestFn) => { builder, namespace, } = t.context + if (builder.dialect === 'Postgres') { + // Ignore this unit test for Postgres + // because we don't defer FK checks + // but completely disable them for incoming transactions + t.pass() + return + } + await runMigrations() if (builder.dialect === 'SQLite') { @@ -1269,6 +1277,18 @@ export const processTests = (test: TestFn) => { builder, namespace, } = t.context + // since this test disables compensations + // by putting the flag on 0 + // it is expecting a FK violation + if (builder.dialect === 'Postgres') { + // if we're running Postgres + // we are not deferring FK checks + // but completely disabling them for incoming transactions + // so the FK violation will not occur + t.pass() + return + } + await runMigrations() if (builder.dialect === 'SQLite') { @@ -1912,7 +1932,16 @@ export const processTests = (test: TestFn) => { authState, token, namespace, + builder, } = t.context + if (builder.dialect === 'Postgres') { + // Ignore this unit test for Postgres + // because we don't defer FK checks + // but completely disable them for incoming transactions + t.pass() + return + } + await runMigrations() const tablename = 'child'