@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 +57 -21
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +59 -23
- package/package.json +3 -3
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
|
|
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
|
|
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
|
-
|
|
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
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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.
|
|
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.
|
|
59
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
58
|
+
"@atscript/db": "^0.1.137",
|
|
59
|
+
"@atscript/db-sql-tools": "^0.1.137"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"pg": {
|