@atscript/db-postgres 0.1.135 → 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
@@ -78,11 +78,30 @@ const pgDialect = {
78
78
  };
79
79
  },
80
80
  calendarBucket: pgCalendarBucket,
81
+ jsonExtract: pgJsonExtract,
81
82
  createViewPrefix: "CREATE OR REPLACE VIEW",
82
83
  paramPlaceholder(index) {
83
84
  return `$${index}`;
84
85
  }
85
86
  };
87
+ /** Cast applied to the `#>>` text per leaf type — `jsonb_typeof()` names equal the type names. */
88
+ const PG_JSON_LEAF_CAST = {
89
+ string: "",
90
+ number: "::double precision",
91
+ boolean: "::boolean"
92
+ };
93
+ /**
94
+ * Typed read of a JSON leaf (`SqlDialect.jsonExtract`): `jsonb_typeof()`
95
+ * guards the declared type, so a missing path, JSON `null` or another JSON
96
+ * type yields NULL (no coercion). Numbers are `double precision`, booleans
97
+ * native. The column is cast `::jsonb`, so JSON and JSONB storage both work.
98
+ */
99
+ function pgJsonExtract(quotedCol, path, type) {
100
+ const p = (0, _atscript_db_sql_tools.sqlStringLiteral)(`{${(0, _atscript_db_sql_tools.quotedJsonPathSegments)(path).join(",")}}`);
101
+ const j = `(${quotedCol})::jsonb`;
102
+ const cast = PG_JSON_LEAF_CAST[type];
103
+ return `CASE jsonb_typeof(${j} #> ${p}) WHEN '${type}' THEN ${cast ? `(${j} #>> ${p})${cast}` : `${j} #>> ${p}`} END`;
104
+ }
86
105
  /**
87
106
  * Calendar-bucket label over an epoch-ms column (BIGINT for
88
107
  * `@db.default.now`, DOUBLE PRECISION otherwise): TEXT `'YYYY-MM-DD'` — the
@@ -472,6 +491,10 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
472
491
  calendarBucketUnits() {
473
492
  return _atscript_db.ALL_BUCKET_UNITS;
474
493
  }
494
+ /** Every aggregate function, `countDistinct` included. */
495
+ aggregateFns() {
496
+ return _atscript_db.ALL_AGGREGATE_FNS;
497
+ }
475
498
  onBeforeFlatten(_type) {}
476
499
  onAfterFlatten() {
477
500
  for (const fd of this._table.fieldDescriptors) if (fd.collate === "nocase") this._nocaseColumns.add(fd.physicalName);
@@ -830,15 +853,19 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
830
853
  return this.getExistingColumnsForTable(this._table.tableName);
831
854
  }
832
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) {
833
860
  const schema = this._schema;
834
- 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,
835
862
  format_type(a.atttypid, a.atttypmod) AS formatted_type
836
863
  FROM information_schema.columns c
837
864
  JOIN pg_attribute a ON a.attname = c.column_name
838
865
  AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
839
866
  WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
840
867
  ORDER BY c.ordinal_position`, [tableName, schema]);
841
- const pkRows = await this._exec().all(`SELECT kcu.column_name
868
+ const pkRows = await exec.all(`SELECT kcu.column_name
842
869
  FROM information_schema.table_constraints tc
843
870
  JOIN information_schema.key_column_usage kcu
844
871
  ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
@@ -891,9 +918,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
891
918
  for (const { field } of diff.typeChanged ?? []) {
892
919
  const sqlType = this.typeMapper(field);
893
920
  const col = qi(field.physicalName);
894
- let ddl;
895
- 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`;
896
- 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)}`;
897
922
  this._log(ddl);
898
923
  await this._exec().exec(ddl);
899
924
  }
@@ -994,22 +1019,17 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
994
1019
  });
995
1020
  this._log(createSql);
996
1021
  await conn.exec(createSql);
997
- const oldColRows = await conn.all(`SELECT column_name FROM information_schema.columns
998
- WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')
999
- ORDER BY ordinal_position`, [this._table.tableName, schema]);
1000
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1001
- const oldColSet = new Set(oldColRows.map((c) => c.column_name));
1002
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1003
- if (commonCols.length > 0) {
1004
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1005
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1006
- const selectExprs = commonCols.map((c) => {
1007
- const field = fieldsByName.get(c);
1008
- if (field && !field.optional && !field.isPrimaryKey) {
1009
- const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
1010
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1011
- }
1012
- 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;
1013
1033
  }).join(", ");
1014
1034
  const copySql = `INSERT INTO ${quoteTableName(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1015
1035
  this._log(copySql);
@@ -1530,6 +1550,22 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
1530
1550
  });
1531
1551
  }
1532
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
+ }
1533
1569
  /**
1534
1570
  * Normalizes PostgreSQL information_schema data_type values
1535
1571
  * to match the format produced by `pgTypeFromField()`.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
2
2
  import { TMetadataMap } from "@atscript/typescript/utils";
3
3
  import { TSqlFragment } from "@atscript/db-sql-tools";
4
4
 
@@ -118,6 +118,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
118
118
  * server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
119
119
  */
120
120
  calendarBucketUnits(): ReadonlySet<BucketUnit>;
121
+ /** Every aggregate function, `countDistinct` included. */
122
+ aggregateFns(): ReadonlySet<AggregateFn>;
121
123
  onBeforeFlatten(_type: unknown): void;
122
124
  onAfterFlatten(): void;
123
125
  onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
@@ -186,6 +188,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
186
188
  private _ensureView;
187
189
  getExistingColumns(): Promise<TExistingColumn[]>;
188
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;
189
193
  syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
190
194
  recreateTable(): Promise<void>;
191
195
  /**
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
2
2
  import { TSqlFragment } from "@atscript/db-sql-tools";
3
3
  import { TMetadataMap } from "@atscript/typescript/utils";
4
4
 
@@ -118,6 +118,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
118
118
  * server's tz database (an unknown one maps to `BUCKET_TZ_UNAVAILABLE`).
119
119
  */
120
120
  calendarBucketUnits(): ReadonlySet<BucketUnit>;
121
+ /** Every aggregate function, `countDistinct` included. */
122
+ aggregateFns(): ReadonlySet<AggregateFn>;
121
123
  onBeforeFlatten(_type: unknown): void;
122
124
  onAfterFlatten(): void;
123
125
  onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
@@ -186,6 +188,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
186
188
  private _ensureView;
187
189
  getExistingColumns(): Promise<TExistingColumn[]>;
188
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;
189
193
  syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
190
194
  recreateTable(): Promise<void>;
191
195
  /**
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { 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
- import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, finalizeParams, geoWindowFromControls, insertManyColumns, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral } from "@atscript/db-sql-tools";
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";
5
5
  //#region src/sql-builder.ts
6
6
  /**
@@ -77,11 +77,30 @@ const pgDialect = {
77
77
  };
78
78
  },
79
79
  calendarBucket: pgCalendarBucket,
80
+ jsonExtract: pgJsonExtract,
80
81
  createViewPrefix: "CREATE OR REPLACE VIEW",
81
82
  paramPlaceholder(index) {
82
83
  return `$${index}`;
83
84
  }
84
85
  };
86
+ /** Cast applied to the `#>>` text per leaf type — `jsonb_typeof()` names equal the type names. */
87
+ const PG_JSON_LEAF_CAST = {
88
+ string: "",
89
+ number: "::double precision",
90
+ boolean: "::boolean"
91
+ };
92
+ /**
93
+ * Typed read of a JSON leaf (`SqlDialect.jsonExtract`): `jsonb_typeof()`
94
+ * guards the declared type, so a missing path, JSON `null` or another JSON
95
+ * type yields NULL (no coercion). Numbers are `double precision`, booleans
96
+ * native. The column is cast `::jsonb`, so JSON and JSONB storage both work.
97
+ */
98
+ function pgJsonExtract(quotedCol, path, type) {
99
+ const p = sqlStringLiteral(`{${quotedJsonPathSegments(path).join(",")}}`);
100
+ const j = `(${quotedCol})::jsonb`;
101
+ const cast = PG_JSON_LEAF_CAST[type];
102
+ return `CASE jsonb_typeof(${j} #> ${p}) WHEN '${type}' THEN ${cast ? `(${j} #>> ${p})${cast}` : `${j} #>> ${p}`} END`;
103
+ }
85
104
  /**
86
105
  * Calendar-bucket label over an epoch-ms column (BIGINT for
87
106
  * `@db.default.now`, DOUBLE PRECISION otherwise): TEXT `'YYYY-MM-DD'` — the
@@ -471,6 +490,10 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
471
490
  calendarBucketUnits() {
472
491
  return ALL_BUCKET_UNITS;
473
492
  }
493
+ /** Every aggregate function, `countDistinct` included. */
494
+ aggregateFns() {
495
+ return ALL_AGGREGATE_FNS;
496
+ }
474
497
  onBeforeFlatten(_type) {}
475
498
  onAfterFlatten() {
476
499
  for (const fd of this._table.fieldDescriptors) if (fd.collate === "nocase") this._nocaseColumns.add(fd.physicalName);
@@ -829,15 +852,19 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
829
852
  return this.getExistingColumnsForTable(this._table.tableName);
830
853
  }
831
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) {
832
859
  const schema = this._schema;
833
- 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,
834
861
  format_type(a.atttypid, a.atttypmod) AS formatted_type
835
862
  FROM information_schema.columns c
836
863
  JOIN pg_attribute a ON a.attname = c.column_name
837
864
  AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
838
865
  WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
839
866
  ORDER BY c.ordinal_position`, [tableName, schema]);
840
- const pkRows = await this._exec().all(`SELECT kcu.column_name
867
+ const pkRows = await exec.all(`SELECT kcu.column_name
841
868
  FROM information_schema.table_constraints tc
842
869
  JOIN information_schema.key_column_usage kcu
843
870
  ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
@@ -890,9 +917,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
890
917
  for (const { field } of diff.typeChanged ?? []) {
891
918
  const sqlType = this.typeMapper(field);
892
919
  const col = qi(field.physicalName);
893
- let ddl;
894
- 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`;
895
- 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)}`;
896
921
  this._log(ddl);
897
922
  await this._exec().exec(ddl);
898
923
  }
@@ -993,22 +1018,17 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
993
1018
  });
994
1019
  this._log(createSql);
995
1020
  await conn.exec(createSql);
996
- const oldColRows = await conn.all(`SELECT column_name FROM information_schema.columns
997
- WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')
998
- ORDER BY ordinal_position`, [this._table.tableName, schema]);
999
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1000
- const oldColSet = new Set(oldColRows.map((c) => c.column_name));
1001
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1002
- if (commonCols.length > 0) {
1003
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1004
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1005
- const selectExprs = commonCols.map((c) => {
1006
- const field = fieldsByName.get(c);
1007
- if (field && !field.optional && !field.isPrimaryKey) {
1008
- const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value) : defaultValueForType$1(field.designType);
1009
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1010
- }
1011
- 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;
1012
1032
  }).join(", ");
1013
1033
  const copySql = `INSERT INTO ${quoteTableName(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1014
1034
  this._log(copySql);
@@ -1529,6 +1549,22 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
1529
1549
  });
1530
1550
  }
1531
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
+ }
1532
1568
  /**
1533
1569
  * Normalizes PostgreSQL information_schema data_type values
1534
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.135",
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.135",
59
- "@atscript/db-sql-tools": "^0.1.135"
58
+ "@atscript/db": "^0.1.137",
59
+ "@atscript/db-sql-tools": "^0.1.137"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "pg": {