@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 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 where = buildWhere(query.filter);
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.ordinal_position
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') AND constraint_type = 'PRIMARY KEY'
877
- )`, [schema, this._table.tableName]);
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
- await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)} CASCADE`);
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 where = buildWhere(query.filter);
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.ordinal_position
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') AND constraint_type = 'PRIMARY KEY'
876
- )`, [schema, this._table.tableName]);
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
- await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)} CASCADE`);
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.128",
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-sql-tools": "^0.1.128",
59
- "@atscript/db": "^0.1.128"
58
+ "@atscript/db": "^0.1.130",
59
+ "@atscript/db-sql-tools": "^0.1.130"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "pg": {