@atscript/db-postgres 0.1.136 → 0.1.137

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
@@ -853,15 +853,19 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
853
853
  return this.getExistingColumnsForTable(this._table.tableName);
854
854
  }
855
855
  async getExistingColumnsForTable(tableName) {
856
+ return this._readColumns(this._exec(), tableName);
857
+ }
858
+ /** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
859
+ async _readColumns(exec, tableName) {
856
860
  const schema = this._schema;
857
- const rows = await this._exec().all(`SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default, c.is_identity,
861
+ const rows = await exec.all(`SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default, c.is_identity,
858
862
  format_type(a.atttypid, a.atttypmod) AS formatted_type
859
863
  FROM information_schema.columns c
860
864
  JOIN pg_attribute a ON a.attname = c.column_name
861
865
  AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
862
866
  WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
863
867
  ORDER BY c.ordinal_position`, [tableName, schema]);
864
- const pkRows = await this._exec().all(`SELECT kcu.column_name
868
+ const pkRows = await exec.all(`SELECT kcu.column_name
865
869
  FROM information_schema.table_constraints tc
866
870
  JOIN information_schema.key_column_usage kcu
867
871
  ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
@@ -914,9 +918,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
914
918
  for (const { field } of diff.typeChanged ?? []) {
915
919
  const sqlType = this.typeMapper(field);
916
920
  const col = qi(field.physicalName);
917
- let ddl;
918
- if (field.isGeoPoint && sqlType.startsWith("geography")) ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
919
- else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
921
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${convertColumnExpr(col, field, sqlType)}`;
920
922
  this._log(ddl);
921
923
  await this._exec().exec(ddl);
922
924
  }
@@ -1017,22 +1019,17 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
1017
1019
  });
1018
1020
  this._log(createSql);
1019
1021
  await conn.exec(createSql);
1020
- const oldColRows = await conn.all(`SELECT column_name FROM information_schema.columns
1021
- WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')
1022
- ORDER BY ordinal_position`, [this._table.tableName, schema]);
1023
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1024
- const oldColSet = new Set(oldColRows.map((c) => c.column_name));
1025
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1026
- if (commonCols.length > 0) {
1027
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1028
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1029
- const selectExprs = commonCols.map((c) => {
1030
- const field = fieldsByName.get(c);
1031
- if (field && !field.optional && !field.isPrimaryKey) {
1032
- const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
1033
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1034
- }
1035
- return qi(c);
1022
+ const oldTypes = new Map((await this._readColumns(conn, this._table.tableName)).map((c) => [c.name, c.type]));
1023
+ const commonFields = this._table.fieldDescriptors.filter((f) => !f.ignored && oldTypes.has(f.physicalName));
1024
+ if (commonFields.length > 0) {
1025
+ const colNames = commonFields.map((f) => qi(f.physicalName)).join(", ");
1026
+ const selectExprs = commonFields.map((field) => {
1027
+ const c = qi(field.physicalName);
1028
+ const sqlType = this.typeMapper(field);
1029
+ const changed = (0, _atscript_db.isColumnTypeChanged)(oldTypes.get(field.physicalName), sqlType);
1030
+ const value = changed ? convertColumnExpr(c, field, sqlType) : c;
1031
+ if (!field.optional && !field.isPrimaryKey) return `COALESCE(${value}, ${field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType)}) AS ${c}`;
1032
+ return changed ? `${value} AS ${c}` : c;
1036
1033
  }).join(", ");
1037
1034
  const copySql = `INSERT INTO ${quoteTableName(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1038
1035
  this._log(copySql);
@@ -1553,6 +1550,22 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
1553
1550
  });
1554
1551
  }
1555
1552
  };
1553
+ /** A text / character type: the value is converted by the assignment into the column. */
1554
+ const CHARACTER_TYPE = /^\s*(?:text|varchar|character varying|char|character|bpchar)\b/i;
1555
+ /**
1556
+ * The expression converting column `col` (quoted) to `sqlType`, the field's
1557
+ * new type — one rule for `ALTER COLUMN … TYPE … USING` and the recreate copy.
1558
+ * The value goes through text, so one that does not parse as the new type
1559
+ * fails the statement instead of being coerced. A TEXT / VARCHAR(n) / CHAR(n)
1560
+ * target stops at `::text`: an explicit cast to VARCHAR(n) / CHAR(n) would
1561
+ * truncate an over-long value, the assignment into the column rejects it
1562
+ * (since 0.1.137).
1563
+ */
1564
+ function convertColumnExpr(col, field, sqlType) {
1565
+ if (field.isGeoPoint && sqlType.startsWith("geography")) return `CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
1566
+ if (CHARACTER_TYPE.test(sqlType)) return `${col}::text`;
1567
+ return `${col}::text::${sqlType}`;
1568
+ }
1556
1569
  /**
1557
1570
  * Normalizes PostgreSQL information_schema data_type values
1558
1571
  * to match the format produced by `pgTypeFromField()`.
package/dist/index.d.cts CHANGED
@@ -188,6 +188,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
188
188
  private _ensureView;
189
189
  getExistingColumns(): Promise<TExistingColumn[]>;
190
190
  getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
191
+ /** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
192
+ private _readColumns;
191
193
  syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
192
194
  recreateTable(): Promise<void>;
193
195
  /**
package/dist/index.d.mts CHANGED
@@ -188,6 +188,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
188
188
  private _ensureView;
189
189
  getExistingColumns(): Promise<TExistingColumn[]>;
190
190
  getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
191
+ /** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
192
+ private _readColumns;
191
193
  syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
192
194
  recreateTable(): Promise<void>;
193
195
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, bucketTimeZoneUnavailable } from "@atscript/db";
1
+ import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, bucketTimeZoneUnavailable, isColumnTypeChanged } from "@atscript/db";
2
2
  import { resolveAggregateSearch } from "@atscript/db/agg";
3
3
  import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, insertManyColumns, normalizeGeoPointValue, parseRegexString, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral } from "@atscript/db-sql-tools";
4
4
  import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
@@ -852,15 +852,19 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
852
852
  return this.getExistingColumnsForTable(this._table.tableName);
853
853
  }
854
854
  async getExistingColumnsForTable(tableName) {
855
+ return this._readColumns(this._exec(), tableName);
856
+ }
857
+ /** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
858
+ async _readColumns(exec, tableName) {
855
859
  const schema = this._schema;
856
- const rows = await this._exec().all(`SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default, c.is_identity,
860
+ const rows = await exec.all(`SELECT c.column_name, c.data_type, c.udt_name, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default, c.is_identity,
857
861
  format_type(a.atttypid, a.atttypmod) AS formatted_type
858
862
  FROM information_schema.columns c
859
863
  JOIN pg_attribute a ON a.attname = c.column_name
860
864
  AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
861
865
  WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
862
866
  ORDER BY c.ordinal_position`, [tableName, schema]);
863
- const pkRows = await this._exec().all(`SELECT kcu.column_name
867
+ const pkRows = await exec.all(`SELECT kcu.column_name
864
868
  FROM information_schema.table_constraints tc
865
869
  JOIN information_schema.key_column_usage kcu
866
870
  ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
@@ -913,9 +917,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
913
917
  for (const { field } of diff.typeChanged ?? []) {
914
918
  const sqlType = this.typeMapper(field);
915
919
  const col = qi(field.physicalName);
916
- let ddl;
917
- if (field.isGeoPoint && sqlType.startsWith("geography")) ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
918
- else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
920
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${convertColumnExpr(col, field, sqlType)}`;
919
921
  this._log(ddl);
920
922
  await this._exec().exec(ddl);
921
923
  }
@@ -1016,22 +1018,17 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
1016
1018
  });
1017
1019
  this._log(createSql);
1018
1020
  await conn.exec(createSql);
1019
- const oldColRows = await conn.all(`SELECT column_name FROM information_schema.columns
1020
- WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')
1021
- ORDER BY ordinal_position`, [this._table.tableName, schema]);
1022
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1023
- const oldColSet = new Set(oldColRows.map((c) => c.column_name));
1024
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1025
- if (commonCols.length > 0) {
1026
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1027
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1028
- const selectExprs = commonCols.map((c) => {
1029
- const field = fieldsByName.get(c);
1030
- if (field && !field.optional && !field.isPrimaryKey) {
1031
- const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value) : defaultValueForType$1(field.designType);
1032
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1033
- }
1034
- return qi(c);
1021
+ const oldTypes = new Map((await this._readColumns(conn, this._table.tableName)).map((c) => [c.name, c.type]));
1022
+ const commonFields = this._table.fieldDescriptors.filter((f) => !f.ignored && oldTypes.has(f.physicalName));
1023
+ if (commonFields.length > 0) {
1024
+ const colNames = commonFields.map((f) => qi(f.physicalName)).join(", ");
1025
+ const selectExprs = commonFields.map((field) => {
1026
+ const c = qi(field.physicalName);
1027
+ const sqlType = this.typeMapper(field);
1028
+ const changed = isColumnTypeChanged(oldTypes.get(field.physicalName), sqlType);
1029
+ const value = changed ? convertColumnExpr(c, field, sqlType) : c;
1030
+ if (!field.optional && !field.isPrimaryKey) return `COALESCE(${value}, ${field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value) : defaultValueForType$1(field.designType)}) AS ${c}`;
1031
+ return changed ? `${value} AS ${c}` : c;
1035
1032
  }).join(", ");
1036
1033
  const copySql = `INSERT INTO ${quoteTableName(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1037
1034
  this._log(copySql);
@@ -1552,6 +1549,22 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
1552
1549
  });
1553
1550
  }
1554
1551
  };
1552
+ /** A text / character type: the value is converted by the assignment into the column. */
1553
+ const CHARACTER_TYPE = /^\s*(?:text|varchar|character varying|char|character|bpchar)\b/i;
1554
+ /**
1555
+ * The expression converting column `col` (quoted) to `sqlType`, the field's
1556
+ * new type — one rule for `ALTER COLUMN … TYPE … USING` and the recreate copy.
1557
+ * The value goes through text, so one that does not parse as the new type
1558
+ * fails the statement instead of being coerced. A TEXT / VARCHAR(n) / CHAR(n)
1559
+ * target stops at `::text`: an explicit cast to VARCHAR(n) / CHAR(n) would
1560
+ * truncate an over-long value, the assignment into the column rejects it
1561
+ * (since 0.1.137).
1562
+ */
1563
+ function convertColumnExpr(col, field, sqlType) {
1564
+ if (field.isGeoPoint && sqlType.startsWith("geography")) return `CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
1565
+ if (CHARACTER_TYPE.test(sqlType)) return `${col}::text`;
1566
+ return `${col}::text::${sqlType}`;
1567
+ }
1555
1568
  /**
1556
1569
  * Normalizes PostgreSQL information_schema data_type values
1557
1570
  * to match the format produced by `pgTypeFromField()`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-postgres",
3
- "version": "0.1.136",
3
+ "version": "0.1.137",
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.11",
57
57
  "pg": ">=8.0.0",
58
- "@atscript/db": "^0.1.136",
59
- "@atscript/db-sql-tools": "^0.1.136"
58
+ "@atscript/db": "^0.1.137",
59
+ "@atscript/db-sql-tools": "^0.1.137"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "pg": {