@atscript/db-postgres 0.1.140 → 0.1.141

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
@@ -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
- const nativeCollate = field.type?.metadata?.get("db.pg.collate");
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: `${quotedKeys.length === 1 ? quotedKeys[0] : `(${colList})`} ${quotedKeys.length === 1 ? "=" : "IN"} (SELECT ${colList} FROM ${quotedTable} WHERE ${where.sql} LIMIT 1)`,
732
+ sql: this._limitOnePredicate(quotedTable, where.sql),
700
733
  params: where.params
701
734
  };
702
- const versionColumn = this._table.versionColumn;
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.versionColumn;
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.versionColumn);
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 tableName = this.resolveTableName();
764
+ const quotedTable = quoteTableName(this.resolveTableName());
732
765
  const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
733
- sql: `DELETE FROM ${quoteTableName(tableName)} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
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
- name: r.column_name,
873
- type: normalizePgType(r.data_type, r.character_maximum_length, r.numeric_precision, r.numeric_scale, r.udt_name, r.formatted_type),
874
- notnull: r.is_nullable === "NO",
875
- pk: r.is_pk,
876
- dflt_value: normalizePgDefault(r.column_default, r.is_identity)
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
- if (field.collate) {
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.fieldDescriptors.filter((f) => !f.ignored && oldTypes.has(f.physicalName));
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
- const nativeCollate = field.type?.metadata?.get("db.pg.collate");
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: `${quotedKeys.length === 1 ? quotedKeys[0] : `(${colList})`} ${quotedKeys.length === 1 ? "=" : "IN"} (SELECT ${colList} FROM ${quotedTable} WHERE ${where.sql} LIMIT 1)`,
731
+ sql: this._limitOnePredicate(quotedTable, where.sql),
699
732
  params: where.params
700
733
  };
701
- const versionColumn = this._table.versionColumn;
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.versionColumn;
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.versionColumn);
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 tableName = this.resolveTableName();
763
+ const quotedTable = quoteTableName(this.resolveTableName());
731
764
  const { sql, params } = finalizeParams(pgDialect, {
732
- sql: `DELETE FROM ${quoteTableName(tableName)} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
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
- name: r.column_name,
872
- type: normalizePgType(r.data_type, r.character_maximum_length, r.numeric_precision, r.numeric_scale, r.udt_name, r.formatted_type),
873
- notnull: r.is_nullable === "NO",
874
- pk: r.is_pk,
875
- dflt_value: normalizePgDefault(r.column_default, r.is_identity)
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
- if (field.collate) {
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.fieldDescriptors.filter((f) => !f.ignored && oldTypes.has(f.physicalName));
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.140",
3
+ "version": "0.1.141",
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.92",
47
- "@atscript/typescript": "^0.1.92",
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.92"
51
+ "unplugin-atscript": "^0.1.95"
52
52
  },
53
53
  "peerDependencies": {
54
- "@atscript/core": "^0.1.92",
55
- "@atscript/typescript": "^0.1.92",
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.140",
59
- "@atscript/db-sql-tools": "^0.1.140"
58
+ "@atscript/db": "^0.1.141",
59
+ "@atscript/db-sql-tools": "^0.1.141"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "pg": {