@atscript/db-postgres 0.1.128 → 0.1.129
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +97 -7
- package/dist/index.d.cts +14 -16
- package/dist/index.d.mts +14 -16
- package/dist/index.mjs +97 -7
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -300,6 +300,40 @@ function parseCount(value) {
|
|
|
300
300
|
* const users = space.getTable(UsersType)
|
|
301
301
|
* ```
|
|
302
302
|
*/
|
|
303
|
+
/** The suffix PostgreSQL gives an auto-named constraint of each `pg_constraint.contype`. */
|
|
304
|
+
const PG_CONSTRAINT_LABELS = {
|
|
305
|
+
p: "pkey",
|
|
306
|
+
u: "key",
|
|
307
|
+
f: "fkey",
|
|
308
|
+
n: "not_null"
|
|
309
|
+
};
|
|
310
|
+
/** UTF-8 byte length of `s`. */
|
|
311
|
+
function byteLength(s) {
|
|
312
|
+
return Buffer.byteLength(s, "utf8");
|
|
313
|
+
}
|
|
314
|
+
/** Cuts `s` to at most `bytes` UTF-8 bytes (whole characters only). */
|
|
315
|
+
function truncateBytes(s, bytes) {
|
|
316
|
+
let out = s;
|
|
317
|
+
while (byteLength(out) > bytes) out = out.slice(0, -1);
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The name PostgreSQL generates for an unnamed constraint (`makeObjectName`):
|
|
322
|
+
* `<table>_<columns joined by _>_<label>`, with the table and column parts
|
|
323
|
+
* shortened — the longer one first — until the whole fits 63 bytes
|
|
324
|
+
* (`NAMEDATALEN - 1`). Used to recognise and to rename the recreated table's
|
|
325
|
+
* constraints. Ignores the `1`, `2`, … suffix PostgreSQL appends on a clash.
|
|
326
|
+
*/
|
|
327
|
+
function pgObjectName(table, columns, label) {
|
|
328
|
+
const name2 = columns.join("_");
|
|
329
|
+
const avail = 63 - (label.length + 1 + (name2 ? 1 : 0));
|
|
330
|
+
let n1 = byteLength(table);
|
|
331
|
+
let n2 = byteLength(name2);
|
|
332
|
+
while (n1 + n2 > avail) if (n1 > n2) n1--;
|
|
333
|
+
else n2--;
|
|
334
|
+
const head = truncateBytes(table, n1);
|
|
335
|
+
return name2 ? `${head}_${truncateBytes(name2, n2)}_${label}` : `${head}_${label}`;
|
|
336
|
+
}
|
|
303
337
|
var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
304
338
|
driver;
|
|
305
339
|
supportsColumnModify = true;
|
|
@@ -869,14 +903,18 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
869
903
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
870
904
|
JOIN information_schema.key_column_usage kcur
|
|
871
905
|
ON kcur.constraint_name = rc.unique_constraint_name AND kcur.table_schema = rc.unique_constraint_schema
|
|
872
|
-
AND kcur.ordinal_position = kcu.
|
|
906
|
+
AND kcur.ordinal_position = kcu.position_in_unique_constraint
|
|
873
907
|
WHERE rc.unique_constraint_schema = COALESCE($1, 'public')
|
|
874
908
|
AND rc.unique_constraint_name IN (
|
|
875
909
|
SELECT constraint_name FROM information_schema.table_constraints
|
|
876
|
-
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
877
|
-
|
|
910
|
+
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
911
|
+
AND constraint_type IN ('PRIMARY KEY', 'UNIQUE')
|
|
912
|
+
)
|
|
913
|
+
ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema, this._table.tableName]);
|
|
878
914
|
const fkByName = /* @__PURE__ */ new Map();
|
|
915
|
+
const ownSchema = schema ?? "public";
|
|
879
916
|
for (const fk of fkRefs) {
|
|
917
|
+
if (fk.table_schema === ownSchema && fk.table_name === this._table.tableName) continue;
|
|
880
918
|
let entry = fkByName.get(fk.constraint_name);
|
|
881
919
|
if (!entry) {
|
|
882
920
|
entry = {
|
|
@@ -900,7 +938,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
900
938
|
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
901
939
|
incrementFields: this._incrementFields,
|
|
902
940
|
autoIncrementStart: this._autoIncrementStart,
|
|
903
|
-
typeMapper: (field) => this.typeMapper(field)
|
|
941
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
942
|
+
deferForeignKeysTo: new Set([this._table.tableName])
|
|
904
943
|
});
|
|
905
944
|
this._log(createSql);
|
|
906
945
|
await conn.exec(createSql);
|
|
@@ -925,13 +964,16 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
925
964
|
this._log(copySql);
|
|
926
965
|
await conn.exec(copySql);
|
|
927
966
|
}
|
|
928
|
-
|
|
967
|
+
const dropSql = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
|
|
968
|
+
this._log(dropSql);
|
|
969
|
+
await conn.exec(dropSql);
|
|
929
970
|
await conn.exec(`ALTER TABLE ${quoteTableName(tempName)} RENAME TO ${qi(this._table.tableName)}`);
|
|
971
|
+
await this._renameTempConstraints(conn, baseTempName);
|
|
930
972
|
const resolvedTable = this.resolveTableName();
|
|
931
|
-
for (const [, fk] of fkByName) {
|
|
973
|
+
for (const [name, fk] of fkByName) {
|
|
932
974
|
const localCols = fk.cols.map((c) => qi(c)).join(", ");
|
|
933
975
|
const refCols = fk.refCols.map((c) => qi(c)).join(", ");
|
|
934
|
-
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
976
|
+
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD CONSTRAINT ${qi(name)} FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
935
977
|
if (fk.onDelete !== "NO ACTION") ddl += ` ON DELETE ${fk.onDelete}`;
|
|
936
978
|
if (fk.onUpdate !== "NO ACTION") ddl += ` ON UPDATE ${fk.onUpdate}`;
|
|
937
979
|
this._log(ddl);
|
|
@@ -941,11 +983,59 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
941
983
|
await this._resetIdentitySequences();
|
|
942
984
|
} catch (err) {
|
|
943
985
|
await conn.exec("ROLLBACK").catch(() => {});
|
|
986
|
+
const detail = err.detail;
|
|
987
|
+
if (err instanceof Error && typeof detail === "string" && detail.length > 0) err.message = `${err.message} — ${detail}`;
|
|
944
988
|
throw err;
|
|
945
989
|
} finally {
|
|
946
990
|
conn.release();
|
|
947
991
|
}
|
|
948
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
995
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
996
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
997
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
998
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
999
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
1000
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
1001
|
+
* unique constraint's index shares its name with every relation in the
|
|
1002
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
1003
|
+
*/
|
|
1004
|
+
async _renameTempConstraints(conn, baseTempName) {
|
|
1005
|
+
const table = this._table.tableName;
|
|
1006
|
+
const rows = await conn.all(`SELECT c.conname, c.contype,
|
|
1007
|
+
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
1008
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1009
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS columns
|
|
1010
|
+
FROM pg_constraint c
|
|
1011
|
+
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
1012
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1013
|
+
WHERE cl.relname = $1 AND n.nspname = COALESCE($2, 'public')
|
|
1014
|
+
ORDER BY c.conname`, [table, this._schema]);
|
|
1015
|
+
const taken = new Set(rows.map((r) => r.conname));
|
|
1016
|
+
for (const { conname, contype, columns } of rows) {
|
|
1017
|
+
const label = PG_CONSTRAINT_LABELS[contype];
|
|
1018
|
+
if (!label) continue;
|
|
1019
|
+
const cols = contype === "p" ? [] : columns ?? [];
|
|
1020
|
+
if (conname !== pgObjectName(baseTempName, cols, label)) continue;
|
|
1021
|
+
const target = pgObjectName(table, cols, label);
|
|
1022
|
+
if (target === conname) continue;
|
|
1023
|
+
if (contype === "p" || contype === "u" ? await this._relationExists(conn, target) : taken.has(target)) {
|
|
1024
|
+
this._log(`-- constraint "${conname}" keeps its name: "${target}" already exists`);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} RENAME CONSTRAINT ${qi(conname)} TO ${qi(target)}`;
|
|
1028
|
+
this._log(ddl);
|
|
1029
|
+
await conn.exec(ddl);
|
|
1030
|
+
taken.add(target);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
1034
|
+
async _relationExists(conn, name) {
|
|
1035
|
+
return (await conn.get(`SELECT true AS present FROM pg_class c
|
|
1036
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1037
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [name, this._schema]))?.present ?? false;
|
|
1038
|
+
}
|
|
949
1039
|
async afterSyncTable() {
|
|
950
1040
|
await this._resetIdentitySequences();
|
|
951
1041
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -69,22 +69,6 @@ interface TPgConnection {
|
|
|
69
69
|
}
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/postgres-adapter.d.ts
|
|
72
|
-
/**
|
|
73
|
-
* PostgreSQL adapter for {@link AtscriptDbTable}.
|
|
74
|
-
*
|
|
75
|
-
* Accepts any {@link TPgDriver} implementation — the actual PostgreSQL driver
|
|
76
|
-
* is fully swappable (pg Pool, custom implementations, etc.).
|
|
77
|
-
*
|
|
78
|
-
* Usage:
|
|
79
|
-
* ```typescript
|
|
80
|
-
* import { PgDriver, PostgresAdapter } from '@atscript/db-postgres'
|
|
81
|
-
* import { DbSpace } from '@atscript/db'
|
|
82
|
-
*
|
|
83
|
-
* const driver = new PgDriver('postgresql://user@localhost:5432/mydb')
|
|
84
|
-
* const space = new DbSpace(() => new PostgresAdapter(driver))
|
|
85
|
-
* const users = space.getTable(UsersType)
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
72
|
declare class PostgresAdapter extends BaseDbAdapter {
|
|
89
73
|
protected readonly driver: TPgDriver;
|
|
90
74
|
supportsColumnModify: boolean;
|
|
@@ -191,6 +175,20 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
191
175
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
|
192
176
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
193
177
|
recreateTable(): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
180
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
181
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
182
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
183
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
184
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
185
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
186
|
+
* unique constraint's index shares its name with every relation in the
|
|
187
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
188
|
+
*/
|
|
189
|
+
private _renameTempConstraints;
|
|
190
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
191
|
+
private _relationExists;
|
|
194
192
|
afterSyncTable(): Promise<void>;
|
|
195
193
|
/**
|
|
196
194
|
* Resets IDENTITY sequences to MAX(column) so that the next auto-generated
|
package/dist/index.d.mts
CHANGED
|
@@ -69,22 +69,6 @@ interface TPgConnection {
|
|
|
69
69
|
}
|
|
70
70
|
//#endregion
|
|
71
71
|
//#region src/postgres-adapter.d.ts
|
|
72
|
-
/**
|
|
73
|
-
* PostgreSQL adapter for {@link AtscriptDbTable}.
|
|
74
|
-
*
|
|
75
|
-
* Accepts any {@link TPgDriver} implementation — the actual PostgreSQL driver
|
|
76
|
-
* is fully swappable (pg Pool, custom implementations, etc.).
|
|
77
|
-
*
|
|
78
|
-
* Usage:
|
|
79
|
-
* ```typescript
|
|
80
|
-
* import { PgDriver, PostgresAdapter } from '@atscript/db-postgres'
|
|
81
|
-
* import { DbSpace } from '@atscript/db'
|
|
82
|
-
*
|
|
83
|
-
* const driver = new PgDriver('postgresql://user@localhost:5432/mydb')
|
|
84
|
-
* const space = new DbSpace(() => new PostgresAdapter(driver))
|
|
85
|
-
* const users = space.getTable(UsersType)
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
72
|
declare class PostgresAdapter extends BaseDbAdapter {
|
|
89
73
|
protected readonly driver: TPgDriver;
|
|
90
74
|
supportsColumnModify: boolean;
|
|
@@ -191,6 +175,20 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
191
175
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
|
192
176
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
193
177
|
recreateTable(): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
180
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
181
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
182
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
183
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
184
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
185
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
186
|
+
* unique constraint's index shares its name with every relation in the
|
|
187
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
188
|
+
*/
|
|
189
|
+
private _renameTempConstraints;
|
|
190
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
191
|
+
private _relationExists;
|
|
194
192
|
afterSyncTable(): Promise<void>;
|
|
195
193
|
/**
|
|
196
194
|
* Resets IDENTITY sequences to MAX(column) so that the next auto-generated
|
package/dist/index.mjs
CHANGED
|
@@ -299,6 +299,40 @@ function parseCount(value) {
|
|
|
299
299
|
* const users = space.getTable(UsersType)
|
|
300
300
|
* ```
|
|
301
301
|
*/
|
|
302
|
+
/** The suffix PostgreSQL gives an auto-named constraint of each `pg_constraint.contype`. */
|
|
303
|
+
const PG_CONSTRAINT_LABELS = {
|
|
304
|
+
p: "pkey",
|
|
305
|
+
u: "key",
|
|
306
|
+
f: "fkey",
|
|
307
|
+
n: "not_null"
|
|
308
|
+
};
|
|
309
|
+
/** UTF-8 byte length of `s`. */
|
|
310
|
+
function byteLength(s) {
|
|
311
|
+
return Buffer.byteLength(s, "utf8");
|
|
312
|
+
}
|
|
313
|
+
/** Cuts `s` to at most `bytes` UTF-8 bytes (whole characters only). */
|
|
314
|
+
function truncateBytes(s, bytes) {
|
|
315
|
+
let out = s;
|
|
316
|
+
while (byteLength(out) > bytes) out = out.slice(0, -1);
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The name PostgreSQL generates for an unnamed constraint (`makeObjectName`):
|
|
321
|
+
* `<table>_<columns joined by _>_<label>`, with the table and column parts
|
|
322
|
+
* shortened — the longer one first — until the whole fits 63 bytes
|
|
323
|
+
* (`NAMEDATALEN - 1`). Used to recognise and to rename the recreated table's
|
|
324
|
+
* constraints. Ignores the `1`, `2`, … suffix PostgreSQL appends on a clash.
|
|
325
|
+
*/
|
|
326
|
+
function pgObjectName(table, columns, label) {
|
|
327
|
+
const name2 = columns.join("_");
|
|
328
|
+
const avail = 63 - (label.length + 1 + (name2 ? 1 : 0));
|
|
329
|
+
let n1 = byteLength(table);
|
|
330
|
+
let n2 = byteLength(name2);
|
|
331
|
+
while (n1 + n2 > avail) if (n1 > n2) n1--;
|
|
332
|
+
else n2--;
|
|
333
|
+
const head = truncateBytes(table, n1);
|
|
334
|
+
return name2 ? `${head}_${truncateBytes(name2, n2)}_${label}` : `${head}_${label}`;
|
|
335
|
+
}
|
|
302
336
|
var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
303
337
|
driver;
|
|
304
338
|
supportsColumnModify = true;
|
|
@@ -868,14 +902,18 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
868
902
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
869
903
|
JOIN information_schema.key_column_usage kcur
|
|
870
904
|
ON kcur.constraint_name = rc.unique_constraint_name AND kcur.table_schema = rc.unique_constraint_schema
|
|
871
|
-
AND kcur.ordinal_position = kcu.
|
|
905
|
+
AND kcur.ordinal_position = kcu.position_in_unique_constraint
|
|
872
906
|
WHERE rc.unique_constraint_schema = COALESCE($1, 'public')
|
|
873
907
|
AND rc.unique_constraint_name IN (
|
|
874
908
|
SELECT constraint_name FROM information_schema.table_constraints
|
|
875
|
-
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
876
|
-
|
|
909
|
+
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
910
|
+
AND constraint_type IN ('PRIMARY KEY', 'UNIQUE')
|
|
911
|
+
)
|
|
912
|
+
ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema, this._table.tableName]);
|
|
877
913
|
const fkByName = /* @__PURE__ */ new Map();
|
|
914
|
+
const ownSchema = schema ?? "public";
|
|
878
915
|
for (const fk of fkRefs) {
|
|
916
|
+
if (fk.table_schema === ownSchema && fk.table_name === this._table.tableName) continue;
|
|
879
917
|
let entry = fkByName.get(fk.constraint_name);
|
|
880
918
|
if (!entry) {
|
|
881
919
|
entry = {
|
|
@@ -899,7 +937,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
899
937
|
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
900
938
|
incrementFields: this._incrementFields,
|
|
901
939
|
autoIncrementStart: this._autoIncrementStart,
|
|
902
|
-
typeMapper: (field) => this.typeMapper(field)
|
|
940
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
941
|
+
deferForeignKeysTo: new Set([this._table.tableName])
|
|
903
942
|
});
|
|
904
943
|
this._log(createSql);
|
|
905
944
|
await conn.exec(createSql);
|
|
@@ -924,13 +963,16 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
924
963
|
this._log(copySql);
|
|
925
964
|
await conn.exec(copySql);
|
|
926
965
|
}
|
|
927
|
-
|
|
966
|
+
const dropSql = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
|
|
967
|
+
this._log(dropSql);
|
|
968
|
+
await conn.exec(dropSql);
|
|
928
969
|
await conn.exec(`ALTER TABLE ${quoteTableName(tempName)} RENAME TO ${qi(this._table.tableName)}`);
|
|
970
|
+
await this._renameTempConstraints(conn, baseTempName);
|
|
929
971
|
const resolvedTable = this.resolveTableName();
|
|
930
|
-
for (const [, fk] of fkByName) {
|
|
972
|
+
for (const [name, fk] of fkByName) {
|
|
931
973
|
const localCols = fk.cols.map((c) => qi(c)).join(", ");
|
|
932
974
|
const refCols = fk.refCols.map((c) => qi(c)).join(", ");
|
|
933
|
-
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
975
|
+
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD CONSTRAINT ${qi(name)} FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
934
976
|
if (fk.onDelete !== "NO ACTION") ddl += ` ON DELETE ${fk.onDelete}`;
|
|
935
977
|
if (fk.onUpdate !== "NO ACTION") ddl += ` ON UPDATE ${fk.onUpdate}`;
|
|
936
978
|
this._log(ddl);
|
|
@@ -940,11 +982,59 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
940
982
|
await this._resetIdentitySequences();
|
|
941
983
|
} catch (err) {
|
|
942
984
|
await conn.exec("ROLLBACK").catch(() => {});
|
|
985
|
+
const detail = err.detail;
|
|
986
|
+
if (err instanceof Error && typeof detail === "string" && detail.length > 0) err.message = `${err.message} — ${detail}`;
|
|
943
987
|
throw err;
|
|
944
988
|
} finally {
|
|
945
989
|
conn.release();
|
|
946
990
|
}
|
|
947
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
994
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
995
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
996
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
997
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
998
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
999
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
1000
|
+
* unique constraint's index shares its name with every relation in the
|
|
1001
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
1002
|
+
*/
|
|
1003
|
+
async _renameTempConstraints(conn, baseTempName) {
|
|
1004
|
+
const table = this._table.tableName;
|
|
1005
|
+
const rows = await conn.all(`SELECT c.conname, c.contype,
|
|
1006
|
+
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
1007
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1008
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS columns
|
|
1009
|
+
FROM pg_constraint c
|
|
1010
|
+
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
1011
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1012
|
+
WHERE cl.relname = $1 AND n.nspname = COALESCE($2, 'public')
|
|
1013
|
+
ORDER BY c.conname`, [table, this._schema]);
|
|
1014
|
+
const taken = new Set(rows.map((r) => r.conname));
|
|
1015
|
+
for (const { conname, contype, columns } of rows) {
|
|
1016
|
+
const label = PG_CONSTRAINT_LABELS[contype];
|
|
1017
|
+
if (!label) continue;
|
|
1018
|
+
const cols = contype === "p" ? [] : columns ?? [];
|
|
1019
|
+
if (conname !== pgObjectName(baseTempName, cols, label)) continue;
|
|
1020
|
+
const target = pgObjectName(table, cols, label);
|
|
1021
|
+
if (target === conname) continue;
|
|
1022
|
+
if (contype === "p" || contype === "u" ? await this._relationExists(conn, target) : taken.has(target)) {
|
|
1023
|
+
this._log(`-- constraint "${conname}" keeps its name: "${target}" already exists`);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} RENAME CONSTRAINT ${qi(conname)} TO ${qi(target)}`;
|
|
1027
|
+
this._log(ddl);
|
|
1028
|
+
await conn.exec(ddl);
|
|
1029
|
+
taken.add(target);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
1033
|
+
async _relationExists(conn, name) {
|
|
1034
|
+
return (await conn.get(`SELECT true AS present FROM pg_class c
|
|
1035
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1036
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [name, this._schema]))?.present ?? false;
|
|
1037
|
+
}
|
|
948
1038
|
async afterSyncTable() {
|
|
949
1039
|
await this._resetIdentitySequences();
|
|
950
1040
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.129",
|
|
4
4
|
"description": "PostgreSQL adapter for @atscript/db with pg (node-postgres) driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@atscript/typescript": "^0.1.92",
|
|
56
56
|
"@uniqu/core": "^0.1.8",
|
|
57
57
|
"pg": ">=8.0.0",
|
|
58
|
-
"@atscript/db
|
|
59
|
-
"@atscript/db": "^0.1.
|
|
58
|
+
"@atscript/db": "^0.1.129",
|
|
59
|
+
"@atscript/db-sql-tools": "^0.1.129"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"pg": {
|