@atscript/db-postgres 0.1.128 → 0.1.130
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 +100 -8
- package/dist/index.d.cts +14 -16
- package/dist/index.d.mts +14 -16
- package/dist/index.mjs +100 -8
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _atscript_db = require("@atscript/db");
|
|
3
|
+
let _atscript_db_agg = require("@atscript/db/agg");
|
|
3
4
|
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
4
5
|
//#region src/sql-builder.ts
|
|
5
6
|
/**
|
|
@@ -300,6 +301,40 @@ function parseCount(value) {
|
|
|
300
301
|
* const users = space.getTable(UsersType)
|
|
301
302
|
* ```
|
|
302
303
|
*/
|
|
304
|
+
/** The suffix PostgreSQL gives an auto-named constraint of each `pg_constraint.contype`. */
|
|
305
|
+
const PG_CONSTRAINT_LABELS = {
|
|
306
|
+
p: "pkey",
|
|
307
|
+
u: "key",
|
|
308
|
+
f: "fkey",
|
|
309
|
+
n: "not_null"
|
|
310
|
+
};
|
|
311
|
+
/** UTF-8 byte length of `s`. */
|
|
312
|
+
function byteLength(s) {
|
|
313
|
+
return Buffer.byteLength(s, "utf8");
|
|
314
|
+
}
|
|
315
|
+
/** Cuts `s` to at most `bytes` UTF-8 bytes (whole characters only). */
|
|
316
|
+
function truncateBytes(s, bytes) {
|
|
317
|
+
let out = s;
|
|
318
|
+
while (byteLength(out) > bytes) out = out.slice(0, -1);
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* The name PostgreSQL generates for an unnamed constraint (`makeObjectName`):
|
|
323
|
+
* `<table>_<columns joined by _>_<label>`, with the table and column parts
|
|
324
|
+
* shortened — the longer one first — until the whole fits 63 bytes
|
|
325
|
+
* (`NAMEDATALEN - 1`). Used to recognise and to rename the recreated table's
|
|
326
|
+
* constraints. Ignores the `1`, `2`, … suffix PostgreSQL appends on a clash.
|
|
327
|
+
*/
|
|
328
|
+
function pgObjectName(table, columns, label) {
|
|
329
|
+
const name2 = columns.join("_");
|
|
330
|
+
const avail = 63 - (label.length + 1 + (name2 ? 1 : 0));
|
|
331
|
+
let n1 = byteLength(table);
|
|
332
|
+
let n2 = byteLength(name2);
|
|
333
|
+
while (n1 + n2 > avail) if (n1 > n2) n1--;
|
|
334
|
+
else n2--;
|
|
335
|
+
const head = truncateBytes(table, n1);
|
|
336
|
+
return name2 ? `${head}_${truncateBytes(name2, n2)}_${label}` : `${head}_${label}`;
|
|
337
|
+
}
|
|
303
338
|
var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
304
339
|
driver;
|
|
305
340
|
supportsColumnModify = true;
|
|
@@ -569,7 +604,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
569
604
|
return parseCount((await this._exec().get(sql, params))?.cnt);
|
|
570
605
|
}
|
|
571
606
|
async aggregate(query) {
|
|
572
|
-
const
|
|
607
|
+
const search = (0, _atscript_db_agg.resolveAggregateSearch)(query.controls);
|
|
608
|
+
const where = search ? this._buildSearchWhere(search.text, query, search.indexName) : buildWhere(query.filter);
|
|
573
609
|
const tableName = this.resolveTableName();
|
|
574
610
|
if (query.controls.$count) {
|
|
575
611
|
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
@@ -869,14 +905,18 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
869
905
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
870
906
|
JOIN information_schema.key_column_usage kcur
|
|
871
907
|
ON kcur.constraint_name = rc.unique_constraint_name AND kcur.table_schema = rc.unique_constraint_schema
|
|
872
|
-
AND kcur.ordinal_position = kcu.
|
|
908
|
+
AND kcur.ordinal_position = kcu.position_in_unique_constraint
|
|
873
909
|
WHERE rc.unique_constraint_schema = COALESCE($1, 'public')
|
|
874
910
|
AND rc.unique_constraint_name IN (
|
|
875
911
|
SELECT constraint_name FROM information_schema.table_constraints
|
|
876
|
-
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
877
|
-
|
|
912
|
+
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
913
|
+
AND constraint_type IN ('PRIMARY KEY', 'UNIQUE')
|
|
914
|
+
)
|
|
915
|
+
ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema, this._table.tableName]);
|
|
878
916
|
const fkByName = /* @__PURE__ */ new Map();
|
|
917
|
+
const ownSchema = schema ?? "public";
|
|
879
918
|
for (const fk of fkRefs) {
|
|
919
|
+
if (fk.table_schema === ownSchema && fk.table_name === this._table.tableName) continue;
|
|
880
920
|
let entry = fkByName.get(fk.constraint_name);
|
|
881
921
|
if (!entry) {
|
|
882
922
|
entry = {
|
|
@@ -900,7 +940,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
900
940
|
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
901
941
|
incrementFields: this._incrementFields,
|
|
902
942
|
autoIncrementStart: this._autoIncrementStart,
|
|
903
|
-
typeMapper: (field) => this.typeMapper(field)
|
|
943
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
944
|
+
deferForeignKeysTo: new Set([this._table.tableName])
|
|
904
945
|
});
|
|
905
946
|
this._log(createSql);
|
|
906
947
|
await conn.exec(createSql);
|
|
@@ -925,13 +966,16 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
925
966
|
this._log(copySql);
|
|
926
967
|
await conn.exec(copySql);
|
|
927
968
|
}
|
|
928
|
-
|
|
969
|
+
const dropSql = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
|
|
970
|
+
this._log(dropSql);
|
|
971
|
+
await conn.exec(dropSql);
|
|
929
972
|
await conn.exec(`ALTER TABLE ${quoteTableName(tempName)} RENAME TO ${qi(this._table.tableName)}`);
|
|
973
|
+
await this._renameTempConstraints(conn, baseTempName);
|
|
930
974
|
const resolvedTable = this.resolveTableName();
|
|
931
|
-
for (const [, fk] of fkByName) {
|
|
975
|
+
for (const [name, fk] of fkByName) {
|
|
932
976
|
const localCols = fk.cols.map((c) => qi(c)).join(", ");
|
|
933
977
|
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})`;
|
|
978
|
+
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD CONSTRAINT ${qi(name)} FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
935
979
|
if (fk.onDelete !== "NO ACTION") ddl += ` ON DELETE ${fk.onDelete}`;
|
|
936
980
|
if (fk.onUpdate !== "NO ACTION") ddl += ` ON UPDATE ${fk.onUpdate}`;
|
|
937
981
|
this._log(ddl);
|
|
@@ -941,11 +985,59 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
941
985
|
await this._resetIdentitySequences();
|
|
942
986
|
} catch (err) {
|
|
943
987
|
await conn.exec("ROLLBACK").catch(() => {});
|
|
988
|
+
const detail = err.detail;
|
|
989
|
+
if (err instanceof Error && typeof detail === "string" && detail.length > 0) err.message = `${err.message} — ${detail}`;
|
|
944
990
|
throw err;
|
|
945
991
|
} finally {
|
|
946
992
|
conn.release();
|
|
947
993
|
}
|
|
948
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
997
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
998
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
999
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
1000
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
1001
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
1002
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
1003
|
+
* unique constraint's index shares its name with every relation in the
|
|
1004
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
1005
|
+
*/
|
|
1006
|
+
async _renameTempConstraints(conn, baseTempName) {
|
|
1007
|
+
const table = this._table.tableName;
|
|
1008
|
+
const rows = await conn.all(`SELECT c.conname, c.contype,
|
|
1009
|
+
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
1010
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1011
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS columns
|
|
1012
|
+
FROM pg_constraint c
|
|
1013
|
+
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
1014
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1015
|
+
WHERE cl.relname = $1 AND n.nspname = COALESCE($2, 'public')
|
|
1016
|
+
ORDER BY c.conname`, [table, this._schema]);
|
|
1017
|
+
const taken = new Set(rows.map((r) => r.conname));
|
|
1018
|
+
for (const { conname, contype, columns } of rows) {
|
|
1019
|
+
const label = PG_CONSTRAINT_LABELS[contype];
|
|
1020
|
+
if (!label) continue;
|
|
1021
|
+
const cols = contype === "p" ? [] : columns ?? [];
|
|
1022
|
+
if (conname !== pgObjectName(baseTempName, cols, label)) continue;
|
|
1023
|
+
const target = pgObjectName(table, cols, label);
|
|
1024
|
+
if (target === conname) continue;
|
|
1025
|
+
if (contype === "p" || contype === "u" ? await this._relationExists(conn, target) : taken.has(target)) {
|
|
1026
|
+
this._log(`-- constraint "${conname}" keeps its name: "${target}" already exists`);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} RENAME CONSTRAINT ${qi(conname)} TO ${qi(target)}`;
|
|
1030
|
+
this._log(ddl);
|
|
1031
|
+
await conn.exec(ddl);
|
|
1032
|
+
taken.add(target);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
1036
|
+
async _relationExists(conn, name) {
|
|
1037
|
+
return (await conn.get(`SELECT true AS present FROM pg_class c
|
|
1038
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1039
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [name, this._schema]))?.present ?? false;
|
|
1040
|
+
}
|
|
949
1041
|
async afterSyncTable() {
|
|
950
1042
|
await this._resetIdentitySequences();
|
|
951
1043
|
}
|
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
|
+
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
2
3
|
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor } from "@atscript/db-sql-tools";
|
|
3
4
|
//#region src/sql-builder.ts
|
|
4
5
|
/**
|
|
@@ -299,6 +300,40 @@ function parseCount(value) {
|
|
|
299
300
|
* const users = space.getTable(UsersType)
|
|
300
301
|
* ```
|
|
301
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
|
+
}
|
|
302
337
|
var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
303
338
|
driver;
|
|
304
339
|
supportsColumnModify = true;
|
|
@@ -568,7 +603,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
568
603
|
return parseCount((await this._exec().get(sql, params))?.cnt);
|
|
569
604
|
}
|
|
570
605
|
async aggregate(query) {
|
|
571
|
-
const
|
|
606
|
+
const search = resolveAggregateSearch(query.controls);
|
|
607
|
+
const where = search ? this._buildSearchWhere(search.text, query, search.indexName) : buildWhere(query.filter);
|
|
572
608
|
const tableName = this.resolveTableName();
|
|
573
609
|
if (query.controls.$count) {
|
|
574
610
|
const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
|
|
@@ -868,14 +904,18 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
868
904
|
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
|
|
869
905
|
JOIN information_schema.key_column_usage kcur
|
|
870
906
|
ON kcur.constraint_name = rc.unique_constraint_name AND kcur.table_schema = rc.unique_constraint_schema
|
|
871
|
-
AND kcur.ordinal_position = kcu.
|
|
907
|
+
AND kcur.ordinal_position = kcu.position_in_unique_constraint
|
|
872
908
|
WHERE rc.unique_constraint_schema = COALESCE($1, 'public')
|
|
873
909
|
AND rc.unique_constraint_name IN (
|
|
874
910
|
SELECT constraint_name FROM information_schema.table_constraints
|
|
875
|
-
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
876
|
-
|
|
911
|
+
WHERE table_name = $2 AND table_schema = COALESCE($1, 'public')
|
|
912
|
+
AND constraint_type IN ('PRIMARY KEY', 'UNIQUE')
|
|
913
|
+
)
|
|
914
|
+
ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema, this._table.tableName]);
|
|
877
915
|
const fkByName = /* @__PURE__ */ new Map();
|
|
916
|
+
const ownSchema = schema ?? "public";
|
|
878
917
|
for (const fk of fkRefs) {
|
|
918
|
+
if (fk.table_schema === ownSchema && fk.table_name === this._table.tableName) continue;
|
|
879
919
|
let entry = fkByName.get(fk.constraint_name);
|
|
880
920
|
if (!entry) {
|
|
881
921
|
entry = {
|
|
@@ -899,7 +939,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
899
939
|
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
900
940
|
incrementFields: this._incrementFields,
|
|
901
941
|
autoIncrementStart: this._autoIncrementStart,
|
|
902
|
-
typeMapper: (field) => this.typeMapper(field)
|
|
942
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
943
|
+
deferForeignKeysTo: new Set([this._table.tableName])
|
|
903
944
|
});
|
|
904
945
|
this._log(createSql);
|
|
905
946
|
await conn.exec(createSql);
|
|
@@ -924,13 +965,16 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
924
965
|
this._log(copySql);
|
|
925
966
|
await conn.exec(copySql);
|
|
926
967
|
}
|
|
927
|
-
|
|
968
|
+
const dropSql = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
|
|
969
|
+
this._log(dropSql);
|
|
970
|
+
await conn.exec(dropSql);
|
|
928
971
|
await conn.exec(`ALTER TABLE ${quoteTableName(tempName)} RENAME TO ${qi(this._table.tableName)}`);
|
|
972
|
+
await this._renameTempConstraints(conn, baseTempName);
|
|
929
973
|
const resolvedTable = this.resolveTableName();
|
|
930
|
-
for (const [, fk] of fkByName) {
|
|
974
|
+
for (const [name, fk] of fkByName) {
|
|
931
975
|
const localCols = fk.cols.map((c) => qi(c)).join(", ");
|
|
932
976
|
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})`;
|
|
977
|
+
let ddl = `ALTER TABLE ${qi(fk.schema)}.${qi(fk.table)} ADD CONSTRAINT ${qi(name)} FOREIGN KEY (${localCols}) REFERENCES ${quoteTableName(resolvedTable)} (${refCols})`;
|
|
934
978
|
if (fk.onDelete !== "NO ACTION") ddl += ` ON DELETE ${fk.onDelete}`;
|
|
935
979
|
if (fk.onUpdate !== "NO ACTION") ddl += ` ON UPDATE ${fk.onUpdate}`;
|
|
936
980
|
this._log(ddl);
|
|
@@ -940,11 +984,59 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
940
984
|
await this._resetIdentitySequences();
|
|
941
985
|
} catch (err) {
|
|
942
986
|
await conn.exec("ROLLBACK").catch(() => {});
|
|
987
|
+
const detail = err.detail;
|
|
988
|
+
if (err instanceof Error && typeof detail === "string" && detail.length > 0) err.message = `${err.message} — ${detail}`;
|
|
943
989
|
throw err;
|
|
944
990
|
} finally {
|
|
945
991
|
conn.release();
|
|
946
992
|
}
|
|
947
993
|
}
|
|
994
|
+
/**
|
|
995
|
+
* Renames the recreated table's auto-named constraints from the temp
|
|
996
|
+
* table's names to the final table's: `<tmp>_pkey` → `<table>_pkey`,
|
|
997
|
+
* `<tmp>_<cols>_fkey` → `<table>_<cols>_fkey`, and so on. Runs inside the
|
|
998
|
+
* recreate transaction, after the RENAME. A constraint is recognised by
|
|
999
|
+
* its kind and columns — its name equals the one PostgreSQL generates for
|
|
1000
|
+
* the temp table ({@link pgObjectName}, 63-byte truncation included) — so
|
|
1001
|
+
* a long table name matches too. A target name already taken (a primary /
|
|
1002
|
+
* unique constraint's index shares its name with every relation in the
|
|
1003
|
+
* schema; other kinds with the table's constraints) is skipped and logged.
|
|
1004
|
+
*/
|
|
1005
|
+
async _renameTempConstraints(conn, baseTempName) {
|
|
1006
|
+
const table = this._table.tableName;
|
|
1007
|
+
const rows = await conn.all(`SELECT c.conname, c.contype,
|
|
1008
|
+
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
1009
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1010
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum) AS columns
|
|
1011
|
+
FROM pg_constraint c
|
|
1012
|
+
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
1013
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1014
|
+
WHERE cl.relname = $1 AND n.nspname = COALESCE($2, 'public')
|
|
1015
|
+
ORDER BY c.conname`, [table, this._schema]);
|
|
1016
|
+
const taken = new Set(rows.map((r) => r.conname));
|
|
1017
|
+
for (const { conname, contype, columns } of rows) {
|
|
1018
|
+
const label = PG_CONSTRAINT_LABELS[contype];
|
|
1019
|
+
if (!label) continue;
|
|
1020
|
+
const cols = contype === "p" ? [] : columns ?? [];
|
|
1021
|
+
if (conname !== pgObjectName(baseTempName, cols, label)) continue;
|
|
1022
|
+
const target = pgObjectName(table, cols, label);
|
|
1023
|
+
if (target === conname) continue;
|
|
1024
|
+
if (contype === "p" || contype === "u" ? await this._relationExists(conn, target) : taken.has(target)) {
|
|
1025
|
+
this._log(`-- constraint "${conname}" keeps its name: "${target}" already exists`);
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} RENAME CONSTRAINT ${qi(conname)} TO ${qi(target)}`;
|
|
1029
|
+
this._log(ddl);
|
|
1030
|
+
await conn.exec(ddl);
|
|
1031
|
+
taken.add(target);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/** Whether a relation (table, index, view, …) named `name` exists in this adapter's schema. */
|
|
1035
|
+
async _relationExists(conn, name) {
|
|
1036
|
+
return (await conn.get(`SELECT true AS present FROM pg_class c
|
|
1037
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1038
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [name, this._schema]))?.present ?? false;
|
|
1039
|
+
}
|
|
948
1040
|
async afterSyncTable() {
|
|
949
1041
|
await this._resetIdentitySequences();
|
|
950
1042
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.130",
|
|
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.130",
|
|
59
|
+
"@atscript/db-sql-tools": "^0.1.130"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"pg": {
|