@atscript/db-postgres 0.1.140 → 0.1.142
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 +74 -33
- package/dist/index.d.cts +9 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.mjs +75 -34
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.mjs +1 -0
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -188,6 +188,29 @@ function buildAggregateCount(table, where, controls) {
|
|
|
188
188
|
return (0, _atscript_db_sql_tools.buildAggregateCount)(pgDialect, table, where, controls);
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
|
+
* The `COLLATE` clause of a column (native `@db.pg.collate`, else the
|
|
192
|
+
* portable `@db.column.collate` mapping) — `""` when none applies.
|
|
193
|
+
*/
|
|
194
|
+
function pgCollateClause(field) {
|
|
195
|
+
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
196
|
+
if (nativeCollate) return ` COLLATE "${nativeCollate}"`;
|
|
197
|
+
if (field.collate) {
|
|
198
|
+
const pgCollate = collationToPg(field.collate);
|
|
199
|
+
if (pgCollate) return ` COLLATE ${pgCollate}`;
|
|
200
|
+
}
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The definition of a `@db.column.derived` column (since 0.1.141):
|
|
205
|
+
* `"col" type [COLLATE …] GENERATED ALWAYS AS (<json extraction>) STORED` —
|
|
206
|
+
* always STORED (PostgreSQL's only generated kind before 18; adding one
|
|
207
|
+
* rewrites the table), nullable, no DEFAULT. Shared by CREATE TABLE and
|
|
208
|
+
* ADD COLUMN.
|
|
209
|
+
*/
|
|
210
|
+
function pgDerivedColumnDef(field, sqlType) {
|
|
211
|
+
return `${qi(field.physicalName)} ${sqlType}${pgCollateClause(field)} GENERATED ALWAYS AS (${(0, _atscript_db_sql_tools.derivedColumnExpr)(pgDialect, field)}) STORED`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
191
214
|
* Maps portable collation values to PostgreSQL collation names.
|
|
192
215
|
*
|
|
193
216
|
* Returns `null` for `'nocase'` — case-insensitive columns use CITEXT type instead
|
|
@@ -268,6 +291,10 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
268
291
|
for (const field of fields) {
|
|
269
292
|
if (field.ignored) continue;
|
|
270
293
|
const sqlType = options?.typeMapper?.(field) ?? pgTypeFromField(field);
|
|
294
|
+
if (field.derived) {
|
|
295
|
+
colDefs.push(pgDerivedColumnDef(field, sqlType));
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
271
298
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
272
299
|
if (options?.incrementFields?.has(field.physicalName)) {
|
|
273
300
|
const start = options.autoIncrementStart;
|
|
@@ -279,12 +306,7 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
279
306
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT gen_random_uuid()";
|
|
280
307
|
else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
281
308
|
}
|
|
282
|
-
|
|
283
|
-
if (nativeCollate) def += ` COLLATE "${nativeCollate}"`;
|
|
284
|
-
else if (field.collate) {
|
|
285
|
-
const pgCollate = collationToPg(field.collate);
|
|
286
|
-
if (pgCollate) def += ` COLLATE ${pgCollate}`;
|
|
287
|
-
}
|
|
309
|
+
def += pgCollateClause(field);
|
|
288
310
|
colDefs.push(def);
|
|
289
311
|
}
|
|
290
312
|
if (primaryKeys.length === 1) {
|
|
@@ -688,18 +710,29 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
688
710
|
throw error;
|
|
689
711
|
}
|
|
690
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* The predicate that narrows a single-row UPDATE/DELETE to the first row
|
|
715
|
+
* matching `whereSql`: `<key> <op> (SELECT <cols> … LIMIT 1)`. Single-col
|
|
716
|
+
* PKs use `<col> = (SELECT <col> …)`; composite PKs the row-constructor form
|
|
717
|
+
* `(c1, c2) IN (SELECT c1, c2 …)`. Tables with no declared PK fall back to
|
|
718
|
+
* ctid — concurrency on PK-less tables is already ill-defined; preserve
|
|
719
|
+
* existing behavior rather than guess.
|
|
720
|
+
*/
|
|
721
|
+
_limitOnePredicate(quotedTable, whereSql) {
|
|
722
|
+
const pkCols = this._table.primaryKeys;
|
|
723
|
+
const quotedKeys = pkCols.length > 0 ? pkCols.map((c) => qi(c)) : ["ctid"];
|
|
724
|
+
const colList = quotedKeys.join(", ");
|
|
725
|
+
return `${quotedKeys.length === 1 ? `${colList} =` : `(${colList}) IN`} (SELECT ${colList} FROM ${quotedTable} WHERE ${whereSql} LIMIT 1)`;
|
|
726
|
+
}
|
|
691
727
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
692
728
|
const where = buildWhere(filter);
|
|
693
729
|
const tableName = this.resolveTableName();
|
|
694
730
|
const quotedTable = quoteTableName(tableName);
|
|
695
|
-
const pkCols = this._table.primaryKeys;
|
|
696
|
-
const quotedKeys = (pkCols.length > 0 ? pkCols : ["ctid"]).map((c) => c === "ctid" ? "ctid" : qi(c));
|
|
697
|
-
const colList = quotedKeys.join(", ");
|
|
698
731
|
const limitedWhere = {
|
|
699
|
-
sql:
|
|
732
|
+
sql: this._limitOnePredicate(quotedTable, where.sql),
|
|
700
733
|
params: where.params
|
|
701
734
|
};
|
|
702
|
-
const versionColumn = this._table.
|
|
735
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
703
736
|
const { sql, params } = buildUpdate(tableName, data, limitedWhere, void 0, ops, versionColumn, expectedVersion);
|
|
704
737
|
this._log(sql, params);
|
|
705
738
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -710,7 +743,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
710
743
|
}
|
|
711
744
|
async updateMany(filter, data, ops) {
|
|
712
745
|
const where = buildWhere(filter);
|
|
713
|
-
const versionColumn = this._table.
|
|
746
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
714
747
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, ops, versionColumn);
|
|
715
748
|
this._log(sql, params);
|
|
716
749
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -720,7 +753,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
720
753
|
};
|
|
721
754
|
}
|
|
722
755
|
async replaceOne(filter, data, expectedVersion) {
|
|
723
|
-
const full = (0, _atscript_db_sql_tools.fillReplacePayload)(data, (0, _atscript_db_sql_tools.replaceColumnsFor)(this._table.fieldDescriptors, this.nativeDefaultFns()), this._table.
|
|
756
|
+
const full = (0, _atscript_db_sql_tools.fillReplacePayload)(data, (0, _atscript_db_sql_tools.replaceColumnsFor)(this._table.fieldDescriptors, this.nativeDefaultFns()), this._table.versionColumnPhysical);
|
|
724
757
|
return this.updateOne(filter, full, void 0, expectedVersion);
|
|
725
758
|
}
|
|
726
759
|
async replaceMany(filter, data) {
|
|
@@ -728,10 +761,10 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
728
761
|
}
|
|
729
762
|
async deleteOne(filter) {
|
|
730
763
|
const where = buildWhere(filter);
|
|
731
|
-
const
|
|
764
|
+
const quotedTable = quoteTableName(this.resolveTableName());
|
|
732
765
|
const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
|
|
733
|
-
sql: `DELETE FROM ${
|
|
734
|
-
params: where.params
|
|
766
|
+
sql: `DELETE FROM ${quotedTable} WHERE ${where.sql} AND ${this._limitOnePredicate(quotedTable, where.sql)}`,
|
|
767
|
+
params: [...where.params, ...where.params]
|
|
735
768
|
});
|
|
736
769
|
this._log(sql, params);
|
|
737
770
|
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
@@ -858,7 +891,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
858
891
|
/** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
|
|
859
892
|
async _readColumns(exec, tableName) {
|
|
860
893
|
const schema = this._schema;
|
|
861
|
-
return (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,
|
|
894
|
+
return (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, c.is_generated,
|
|
862
895
|
format_type(a.atttypid, a.atttypmod) AS formatted_type,
|
|
863
896
|
EXISTS (
|
|
864
897
|
SELECT 1 FROM pg_index i
|
|
@@ -868,13 +901,17 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
868
901
|
JOIN pg_attribute a ON a.attname = c.column_name
|
|
869
902
|
AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
|
|
870
903
|
WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
|
|
871
|
-
ORDER BY c.ordinal_position`, [tableName, schema])).map((r) =>
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
904
|
+
ORDER BY c.ordinal_position`, [tableName, schema])).map((r) => {
|
|
905
|
+
const column = {
|
|
906
|
+
name: r.column_name,
|
|
907
|
+
type: normalizePgType(r.data_type, r.character_maximum_length, r.numeric_precision, r.numeric_scale, r.udt_name, r.formatted_type),
|
|
908
|
+
notnull: r.is_nullable === "NO",
|
|
909
|
+
pk: r.is_pk,
|
|
910
|
+
dflt_value: normalizePgDefault(r.column_default, r.is_identity)
|
|
911
|
+
};
|
|
912
|
+
if (r.is_generated === "ALWAYS") column.generated = true;
|
|
913
|
+
return column;
|
|
914
|
+
});
|
|
878
915
|
}
|
|
879
916
|
async syncColumns(diff) {
|
|
880
917
|
if (this._nocaseColumns.size > 0) await this._exec().exec("CREATE EXTENSION IF NOT EXISTS citext");
|
|
@@ -887,8 +924,14 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
887
924
|
await this._exec().exec(ddl);
|
|
888
925
|
renamed.push(field.physicalName);
|
|
889
926
|
}
|
|
927
|
+
const derivedAdds = [];
|
|
890
928
|
for (const field of diff.added) {
|
|
891
929
|
const sqlType = this.typeMapper(field);
|
|
930
|
+
if (field.derived) {
|
|
931
|
+
derivedAdds.push(`ADD COLUMN ${pgDerivedColumnDef(field, sqlType)}`);
|
|
932
|
+
added.push(field.physicalName);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
892
935
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
893
936
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") ddl += " GENERATED BY DEFAULT AS IDENTITY";
|
|
894
937
|
else {
|
|
@@ -899,18 +942,16 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
899
942
|
else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
900
943
|
} else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
901
944
|
}
|
|
902
|
-
|
|
903
|
-
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
904
|
-
if (nativeCollate) ddl += ` COLLATE "${nativeCollate}"`;
|
|
905
|
-
else {
|
|
906
|
-
const pgCollate = collationToPg(field.collate);
|
|
907
|
-
if (pgCollate) ddl += ` COLLATE ${pgCollate}`;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
945
|
+
ddl += pgCollateClause(field);
|
|
910
946
|
this._log(ddl);
|
|
911
947
|
await this._exec().exec(ddl);
|
|
912
948
|
added.push(field.physicalName);
|
|
913
949
|
}
|
|
950
|
+
if (derivedAdds.length > 0) {
|
|
951
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${derivedAdds.join(", ")}`;
|
|
952
|
+
this._log(ddl);
|
|
953
|
+
await this._exec().exec(ddl);
|
|
954
|
+
}
|
|
914
955
|
for (const { field } of diff.typeChanged ?? []) {
|
|
915
956
|
const sqlType = this.typeMapper(field);
|
|
916
957
|
const col = qi(field.physicalName);
|
|
@@ -1016,7 +1057,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1016
1057
|
this._log(createSql);
|
|
1017
1058
|
await conn.exec(createSql);
|
|
1018
1059
|
const oldTypes = new Map((await this._readColumns(conn, this._table.tableName)).map((c) => [c.name, c.type]));
|
|
1019
|
-
const commonFields = this._table.
|
|
1060
|
+
const commonFields = this._table.storedDescriptors.filter((f) => oldTypes.has(f.physicalName));
|
|
1020
1061
|
if (commonFields.length > 0) {
|
|
1021
1062
|
const colNames = commonFields.map((f) => qi(f.physicalName)).join(", ");
|
|
1022
1063
|
const selectExprs = commonFields.map((field) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -156,6 +156,15 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
156
156
|
* older than the runtime's. Other errors propagate unchanged.
|
|
157
157
|
*/
|
|
158
158
|
private _wrapBucketZoneError;
|
|
159
|
+
/**
|
|
160
|
+
* The predicate that narrows a single-row UPDATE/DELETE to the first row
|
|
161
|
+
* matching `whereSql`: `<key> <op> (SELECT <cols> … LIMIT 1)`. Single-col
|
|
162
|
+
* PKs use `<col> = (SELECT <col> …)`; composite PKs the row-constructor form
|
|
163
|
+
* `(c1, c2) IN (SELECT c1, c2 …)`. Tables with no declared PK fall back to
|
|
164
|
+
* ctid — concurrency on PK-less tables is already ill-defined; preserve
|
|
165
|
+
* existing behavior rather than guess.
|
|
166
|
+
*/
|
|
167
|
+
private _limitOnePredicate;
|
|
159
168
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
160
169
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
161
170
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
package/dist/index.d.mts
CHANGED
|
@@ -156,6 +156,15 @@ declare class PostgresAdapter extends BaseDbAdapter {
|
|
|
156
156
|
* older than the runtime's. Other errors propagate unchanged.
|
|
157
157
|
*/
|
|
158
158
|
private _wrapBucketZoneError;
|
|
159
|
+
/**
|
|
160
|
+
* The predicate that narrows a single-row UPDATE/DELETE to the first row
|
|
161
|
+
* matching `whereSql`: `<key> <op> (SELECT <cols> … LIMIT 1)`. Single-col
|
|
162
|
+
* PKs use `<col> = (SELECT <col> …)`; composite PKs the row-constructor form
|
|
163
|
+
* `(c1, c2) IN (SELECT c1, c2 …)`. Tables with no declared PK fall back to
|
|
164
|
+
* ctid — concurrency on PK-less tables is already ill-defined; preserve
|
|
165
|
+
* existing behavior rather than guess.
|
|
166
|
+
*/
|
|
167
|
+
private _limitOnePredicate;
|
|
159
168
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
160
169
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
161
170
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral } from "@atscript/db-sql-tools";
|
|
3
|
+
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, 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
|
/**
|
|
@@ -187,6 +187,29 @@ function buildAggregateCount$1(table, where, controls) {
|
|
|
187
187
|
return buildAggregateCount(pgDialect, table, where, controls);
|
|
188
188
|
}
|
|
189
189
|
/**
|
|
190
|
+
* The `COLLATE` clause of a column (native `@db.pg.collate`, else the
|
|
191
|
+
* portable `@db.column.collate` mapping) — `""` when none applies.
|
|
192
|
+
*/
|
|
193
|
+
function pgCollateClause(field) {
|
|
194
|
+
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
195
|
+
if (nativeCollate) return ` COLLATE "${nativeCollate}"`;
|
|
196
|
+
if (field.collate) {
|
|
197
|
+
const pgCollate = collationToPg(field.collate);
|
|
198
|
+
if (pgCollate) return ` COLLATE ${pgCollate}`;
|
|
199
|
+
}
|
|
200
|
+
return "";
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The definition of a `@db.column.derived` column (since 0.1.141):
|
|
204
|
+
* `"col" type [COLLATE …] GENERATED ALWAYS AS (<json extraction>) STORED` —
|
|
205
|
+
* always STORED (PostgreSQL's only generated kind before 18; adding one
|
|
206
|
+
* rewrites the table), nullable, no DEFAULT. Shared by CREATE TABLE and
|
|
207
|
+
* ADD COLUMN.
|
|
208
|
+
*/
|
|
209
|
+
function pgDerivedColumnDef(field, sqlType) {
|
|
210
|
+
return `${qi(field.physicalName)} ${sqlType}${pgCollateClause(field)} GENERATED ALWAYS AS (${derivedColumnExpr(pgDialect, field)}) STORED`;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
190
213
|
* Maps portable collation values to PostgreSQL collation names.
|
|
191
214
|
*
|
|
192
215
|
* Returns `null` for `'nocase'` — case-insensitive columns use CITEXT type instead
|
|
@@ -267,6 +290,10 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
267
290
|
for (const field of fields) {
|
|
268
291
|
if (field.ignored) continue;
|
|
269
292
|
const sqlType = options?.typeMapper?.(field) ?? pgTypeFromField(field);
|
|
293
|
+
if (field.derived) {
|
|
294
|
+
colDefs.push(pgDerivedColumnDef(field, sqlType));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
270
297
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
271
298
|
if (options?.incrementFields?.has(field.physicalName)) {
|
|
272
299
|
const start = options.autoIncrementStart;
|
|
@@ -278,12 +305,7 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
278
305
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT gen_random_uuid()";
|
|
279
306
|
else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
280
307
|
}
|
|
281
|
-
|
|
282
|
-
if (nativeCollate) def += ` COLLATE "${nativeCollate}"`;
|
|
283
|
-
else if (field.collate) {
|
|
284
|
-
const pgCollate = collationToPg(field.collate);
|
|
285
|
-
if (pgCollate) def += ` COLLATE ${pgCollate}`;
|
|
286
|
-
}
|
|
308
|
+
def += pgCollateClause(field);
|
|
287
309
|
colDefs.push(def);
|
|
288
310
|
}
|
|
289
311
|
if (primaryKeys.length === 1) {
|
|
@@ -687,18 +709,29 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
687
709
|
throw error;
|
|
688
710
|
}
|
|
689
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* The predicate that narrows a single-row UPDATE/DELETE to the first row
|
|
714
|
+
* matching `whereSql`: `<key> <op> (SELECT <cols> … LIMIT 1)`. Single-col
|
|
715
|
+
* PKs use `<col> = (SELECT <col> …)`; composite PKs the row-constructor form
|
|
716
|
+
* `(c1, c2) IN (SELECT c1, c2 …)`. Tables with no declared PK fall back to
|
|
717
|
+
* ctid — concurrency on PK-less tables is already ill-defined; preserve
|
|
718
|
+
* existing behavior rather than guess.
|
|
719
|
+
*/
|
|
720
|
+
_limitOnePredicate(quotedTable, whereSql) {
|
|
721
|
+
const pkCols = this._table.primaryKeys;
|
|
722
|
+
const quotedKeys = pkCols.length > 0 ? pkCols.map((c) => qi(c)) : ["ctid"];
|
|
723
|
+
const colList = quotedKeys.join(", ");
|
|
724
|
+
return `${quotedKeys.length === 1 ? `${colList} =` : `(${colList}) IN`} (SELECT ${colList} FROM ${quotedTable} WHERE ${whereSql} LIMIT 1)`;
|
|
725
|
+
}
|
|
690
726
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
691
727
|
const where = buildWhere(filter);
|
|
692
728
|
const tableName = this.resolveTableName();
|
|
693
729
|
const quotedTable = quoteTableName(tableName);
|
|
694
|
-
const pkCols = this._table.primaryKeys;
|
|
695
|
-
const quotedKeys = (pkCols.length > 0 ? pkCols : ["ctid"]).map((c) => c === "ctid" ? "ctid" : qi(c));
|
|
696
|
-
const colList = quotedKeys.join(", ");
|
|
697
730
|
const limitedWhere = {
|
|
698
|
-
sql:
|
|
731
|
+
sql: this._limitOnePredicate(quotedTable, where.sql),
|
|
699
732
|
params: where.params
|
|
700
733
|
};
|
|
701
|
-
const versionColumn = this._table.
|
|
734
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
702
735
|
const { sql, params } = buildUpdate$1(tableName, data, limitedWhere, void 0, ops, versionColumn, expectedVersion);
|
|
703
736
|
this._log(sql, params);
|
|
704
737
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -709,7 +742,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
709
742
|
}
|
|
710
743
|
async updateMany(filter, data, ops) {
|
|
711
744
|
const where = buildWhere(filter);
|
|
712
|
-
const versionColumn = this._table.
|
|
745
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
713
746
|
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, ops, versionColumn);
|
|
714
747
|
this._log(sql, params);
|
|
715
748
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -719,7 +752,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
719
752
|
};
|
|
720
753
|
}
|
|
721
754
|
async replaceOne(filter, data, expectedVersion) {
|
|
722
|
-
const full = fillReplacePayload(data, replaceColumnsFor(this._table.fieldDescriptors, this.nativeDefaultFns()), this._table.
|
|
755
|
+
const full = fillReplacePayload(data, replaceColumnsFor(this._table.fieldDescriptors, this.nativeDefaultFns()), this._table.versionColumnPhysical);
|
|
723
756
|
return this.updateOne(filter, full, void 0, expectedVersion);
|
|
724
757
|
}
|
|
725
758
|
async replaceMany(filter, data) {
|
|
@@ -727,10 +760,10 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
727
760
|
}
|
|
728
761
|
async deleteOne(filter) {
|
|
729
762
|
const where = buildWhere(filter);
|
|
730
|
-
const
|
|
763
|
+
const quotedTable = quoteTableName(this.resolveTableName());
|
|
731
764
|
const { sql, params } = finalizeParams(pgDialect, {
|
|
732
|
-
sql: `DELETE FROM ${
|
|
733
|
-
params: where.params
|
|
765
|
+
sql: `DELETE FROM ${quotedTable} WHERE ${where.sql} AND ${this._limitOnePredicate(quotedTable, where.sql)}`,
|
|
766
|
+
params: [...where.params, ...where.params]
|
|
734
767
|
});
|
|
735
768
|
this._log(sql, params);
|
|
736
769
|
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
@@ -857,7 +890,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
857
890
|
/** Live columns of `tableName`, read through `exec` (the recreate's own connection, or the pool). */
|
|
858
891
|
async _readColumns(exec, tableName) {
|
|
859
892
|
const schema = this._schema;
|
|
860
|
-
return (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,
|
|
893
|
+
return (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, c.is_generated,
|
|
861
894
|
format_type(a.atttypid, a.atttypmod) AS formatted_type,
|
|
862
895
|
EXISTS (
|
|
863
896
|
SELECT 1 FROM pg_index i
|
|
@@ -867,13 +900,17 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
867
900
|
JOIN pg_attribute a ON a.attname = c.column_name
|
|
868
901
|
AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, 'public')))
|
|
869
902
|
WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, 'public')
|
|
870
|
-
ORDER BY c.ordinal_position`, [tableName, schema])).map((r) =>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
903
|
+
ORDER BY c.ordinal_position`, [tableName, schema])).map((r) => {
|
|
904
|
+
const column = {
|
|
905
|
+
name: r.column_name,
|
|
906
|
+
type: normalizePgType(r.data_type, r.character_maximum_length, r.numeric_precision, r.numeric_scale, r.udt_name, r.formatted_type),
|
|
907
|
+
notnull: r.is_nullable === "NO",
|
|
908
|
+
pk: r.is_pk,
|
|
909
|
+
dflt_value: normalizePgDefault(r.column_default, r.is_identity)
|
|
910
|
+
};
|
|
911
|
+
if (r.is_generated === "ALWAYS") column.generated = true;
|
|
912
|
+
return column;
|
|
913
|
+
});
|
|
877
914
|
}
|
|
878
915
|
async syncColumns(diff) {
|
|
879
916
|
if (this._nocaseColumns.size > 0) await this._exec().exec("CREATE EXTENSION IF NOT EXISTS citext");
|
|
@@ -886,8 +923,14 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
886
923
|
await this._exec().exec(ddl);
|
|
887
924
|
renamed.push(field.physicalName);
|
|
888
925
|
}
|
|
926
|
+
const derivedAdds = [];
|
|
889
927
|
for (const field of diff.added) {
|
|
890
928
|
const sqlType = this.typeMapper(field);
|
|
929
|
+
if (field.derived) {
|
|
930
|
+
derivedAdds.push(`ADD COLUMN ${pgDerivedColumnDef(field, sqlType)}`);
|
|
931
|
+
added.push(field.physicalName);
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
891
934
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
892
935
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") ddl += " GENERATED BY DEFAULT AS IDENTITY";
|
|
893
936
|
else {
|
|
@@ -898,18 +941,16 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
898
941
|
else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
|
|
899
942
|
} else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType$1(field.designType)}`;
|
|
900
943
|
}
|
|
901
|
-
|
|
902
|
-
const nativeCollate = field.type?.metadata?.get("db.pg.collate");
|
|
903
|
-
if (nativeCollate) ddl += ` COLLATE "${nativeCollate}"`;
|
|
904
|
-
else {
|
|
905
|
-
const pgCollate = collationToPg(field.collate);
|
|
906
|
-
if (pgCollate) ddl += ` COLLATE ${pgCollate}`;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
944
|
+
ddl += pgCollateClause(field);
|
|
909
945
|
this._log(ddl);
|
|
910
946
|
await this._exec().exec(ddl);
|
|
911
947
|
added.push(field.physicalName);
|
|
912
948
|
}
|
|
949
|
+
if (derivedAdds.length > 0) {
|
|
950
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${derivedAdds.join(", ")}`;
|
|
951
|
+
this._log(ddl);
|
|
952
|
+
await this._exec().exec(ddl);
|
|
953
|
+
}
|
|
913
954
|
for (const { field } of diff.typeChanged ?? []) {
|
|
914
955
|
const sqlType = this.typeMapper(field);
|
|
915
956
|
const col = qi(field.physicalName);
|
|
@@ -1015,7 +1056,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
|
|
|
1015
1056
|
this._log(createSql);
|
|
1016
1057
|
await conn.exec(createSql);
|
|
1017
1058
|
const oldTypes = new Map((await this._readColumns(conn, this._table.tableName)).map((c) => [c.name, c.type]));
|
|
1018
|
-
const commonFields = this._table.
|
|
1059
|
+
const commonFields = this._table.storedDescriptors.filter((f) => oldTypes.has(f.physicalName));
|
|
1019
1060
|
if (commonFields.length > 0) {
|
|
1020
1061
|
const colNames = commonFields.map((f) => qi(f.physicalName)).join(", ");
|
|
1021
1062
|
const selectExprs = commonFields.map((field) => {
|
package/dist/plugin.cjs
CHANGED
|
@@ -28,6 +28,7 @@ const annotations = {
|
|
|
28
28
|
schema: new _atscript_core.AnnotationSpec({
|
|
29
29
|
description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
|
|
30
30
|
nodeType: ["interface"],
|
|
31
|
+
passedWhenReferred: false,
|
|
31
32
|
multiple: false,
|
|
32
33
|
argument: {
|
|
33
34
|
name: "schema",
|
package/dist/plugin.mjs
CHANGED
|
@@ -24,6 +24,7 @@ const annotations = {
|
|
|
24
24
|
schema: new AnnotationSpec({
|
|
25
25
|
description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
|
|
26
26
|
nodeType: ["interface"],
|
|
27
|
+
passedWhenReferred: false,
|
|
27
28
|
multiple: false,
|
|
28
29
|
argument: {
|
|
29
30
|
name: "schema",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.142",
|
|
4
4
|
"description": "PostgreSQL adapter for @atscript/db with pg (node-postgres) driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -43,20 +43,20 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@atscript/core": "^0.1.
|
|
47
|
-
"@atscript/typescript": "^0.1.
|
|
46
|
+
"@atscript/core": "^0.1.95",
|
|
47
|
+
"@atscript/typescript": "^0.1.95",
|
|
48
48
|
"@types/pg": "^8.11.0",
|
|
49
49
|
"@uniqu/core": "^0.1.11",
|
|
50
50
|
"pg": "^8.13.0",
|
|
51
|
-
"unplugin-atscript": "^0.1.
|
|
51
|
+
"unplugin-atscript": "^0.1.95"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@atscript/core": "^0.1.
|
|
55
|
-
"@atscript/typescript": "^0.1.
|
|
54
|
+
"@atscript/core": "^0.1.95",
|
|
55
|
+
"@atscript/typescript": "^0.1.95",
|
|
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.142",
|
|
59
|
+
"@atscript/db-sql-tools": "^0.1.142"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"pg": {
|