@atscript/db-postgres 0.1.146 → 0.1.148
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 +283 -86
- package/dist/index.d.cts +45 -13
- package/dist/index.d.mts +45 -13
- package/dist/index.mjs +285 -88
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,77 @@ let _atscript_db = require("@atscript/db");
|
|
|
3
3
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
4
4
|
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
5
5
|
let _uniqu_core = require("@uniqu/core");
|
|
6
|
+
//#region src/insert-ignore.ts
|
|
7
|
+
/**
|
|
8
|
+
* Whether two key values are the same value written differently: a number
|
|
9
|
+
* and its decimal text (`5` / `"5.00"` of a NUMERIC column), a string and its
|
|
10
|
+
* blank-padded form (`"ab"` / `"ab "` of a CHAR(n) column), or in another
|
|
11
|
+
* letter case (`uuid`, `citext`).
|
|
12
|
+
*/
|
|
13
|
+
function looselySame(a, b) {
|
|
14
|
+
const textA = typeof a === "number" || typeof a === "string" ? `${a}` : void 0;
|
|
15
|
+
const textB = typeof b === "number" || typeof b === "string" ? `${b}` : void 0;
|
|
16
|
+
if (textA === void 0 || textB === void 0) return false;
|
|
17
|
+
const na = Number(textA);
|
|
18
|
+
const nb = Number(textB);
|
|
19
|
+
if (textA.trim() !== "" && textB.trim() !== "" && Number.isFinite(na) && na === nb) return true;
|
|
20
|
+
return typeof a === "string" && typeof b === "string" && a.trimEnd().toLowerCase() === b.trimEnd().toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
/** A key value the server may store in another form than it was sent (NUMERIC / decimal rounding). */
|
|
23
|
+
function mayNormalize(value, col, formCols) {
|
|
24
|
+
if (typeof value === "string" && col !== void 0 && formCols?.has(col)) return true;
|
|
25
|
+
return typeof value === "number" && !Number.isInteger(value) || typeof value === "string" && /^-?\d+\.\d+$/.test(value);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Maps the rows a batched `INSERT … ON CONFLICT DO NOTHING RETURNING <keys>`
|
|
29
|
+
* returned back to the batch's input rows.
|
|
30
|
+
*
|
|
31
|
+
* PostgreSQL returns the inserted rows in VALUES order and omits skipped ones,
|
|
32
|
+
* so `returned` is an in-order subsequence of `batch`. A row is inserted
|
|
33
|
+
* exactly when the next returned row carries EVERY key set the row fully
|
|
34
|
+
* defines (key sets with an absent / null input component are never compared:
|
|
35
|
+
* a generated key's value is unknown to the input). Result: per input row, the
|
|
36
|
+
* index of its returned row, or `-1` when it was skipped.
|
|
37
|
+
*
|
|
38
|
+
* `undefined` when the mapping is not provably exact — a row that defines no
|
|
39
|
+
* complete key set (it may match a returned row only by a generated value), a
|
|
40
|
+
* skipped-looking row whose key a server could have normalized (non-integer
|
|
41
|
+
* numbers / decimals), a key equal to the returned one only when written
|
|
42
|
+
* differently (`5` / `"5.00"`, blank-padded CHAR, a `uuid` / `citext` in another
|
|
43
|
+
* letter case — the server lower-cases a returned uuid, so `"AAA"` / `"aaa"` could
|
|
44
|
+
* swap the mapping), or returned rows left unaccounted for (counts or keys that
|
|
45
|
+
* do not line up with an in-order subsequence). The caller must then redo the
|
|
46
|
+
* chunk row by row. An empty `returned` (everything skipped) and a fully-returned
|
|
47
|
+
* batch (everything inserted) are exact without any matching.
|
|
48
|
+
*
|
|
49
|
+
* @param keySets Physical column names of the primary key and every unique index.
|
|
50
|
+
* @param formCols Key columns whose physical type is not text (uuid, inet, cidr,
|
|
51
|
+
* macaddr, …, `@db.pg.type` overrides): a string key there may come back
|
|
52
|
+
* normalized, so a skipped-looking row of such a key is ambiguous.
|
|
53
|
+
*/
|
|
54
|
+
function mapIgnoredBatch(batch, returned, keySets, formCols) {
|
|
55
|
+
if (returned.length === 0) return batch.map(() => -1);
|
|
56
|
+
if (returned.length === batch.length) return batch.map((_, i) => i);
|
|
57
|
+
const out = [];
|
|
58
|
+
let next = 0;
|
|
59
|
+
for (const row of batch) {
|
|
60
|
+
const tuples = keySets.map((cols) => (0, _atscript_db.uniqueKeyTuple)(row, cols));
|
|
61
|
+
if (tuples.every((t) => t === void 0)) return void 0;
|
|
62
|
+
const candidate = returned[next];
|
|
63
|
+
if (candidate !== void 0) {
|
|
64
|
+
const strict = keySets.map((cols, k) => tuples[k] === void 0 || tuples[k] === (0, _atscript_db.uniqueKeyTuple)(candidate, cols));
|
|
65
|
+
if (strict.every(Boolean)) {
|
|
66
|
+
out.push(next++);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (keySets.every((cols, k) => tuples[k] === void 0 || strict[k] || cols.every((c) => looselySame(row[c], candidate[c])))) return void 0;
|
|
70
|
+
}
|
|
71
|
+
if (keySets.some((cols, k) => tuples[k] !== void 0 && cols.some((c) => mayNormalize(row[c], c, formCols)))) return;
|
|
72
|
+
out.push(-1);
|
|
73
|
+
}
|
|
74
|
+
return next === returned.length ? out : void 0;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
6
77
|
//#region src/sql-builder.ts
|
|
7
78
|
/**
|
|
8
79
|
* PostgreSQL-aware default value for a given design type.
|
|
@@ -79,6 +150,27 @@ const pgDialect = {
|
|
|
79
150
|
},
|
|
80
151
|
calendarBucket: pgCalendarBucket,
|
|
81
152
|
jsonExtract: pgJsonExtract,
|
|
153
|
+
castDouble: (expr) => `CAST(${expr} AS DOUBLE PRECISION)`,
|
|
154
|
+
anyValue: pgAnyValue,
|
|
155
|
+
booleanAggregates: {
|
|
156
|
+
min: "BOOL_AND",
|
|
157
|
+
max: "BOOL_OR"
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* SQLSTATE 22003 (`numeric_value_out_of_range`), a double overflow in
|
|
161
|
+
* aggregate arithmetic, is `INVALID_QUERY`; SQLSTATE 22023
|
|
162
|
+
* (`invalid_parameter_value`, `time zone "…" not recognized`), raised by a
|
|
163
|
+
* calendar bucket's `AT TIME ZONE '<tz>'`, is `BUCKET_TZ_UNAVAILABLE` — the
|
|
164
|
+
* zone passed the core's IANA validation, so the server's tz database is
|
|
165
|
+
* older than the runtime's.
|
|
166
|
+
*/
|
|
167
|
+
mapQueryError(error, arithmetic) {
|
|
168
|
+
const err = error;
|
|
169
|
+
if (err?.code === "22003") return arithmetic ? (0, _atscript_db_sql_tools.arithOverflowError)() : (0, _atscript_db_sql_tools.numericOutOfRangeError)();
|
|
170
|
+
const zone = err?.code === "22023" && typeof err.message === "string" ? /time zone "([^"]*)" not recognized/.exec(err.message)?.[1] : void 0;
|
|
171
|
+
return zone === void 0 ? void 0 : (0, _atscript_db.bucketTimeZoneUnavailable)(`PostgreSQL does not recognize time zone "${zone}" — update the server's time zone data`);
|
|
172
|
+
},
|
|
173
|
+
nullsSortLargest: true,
|
|
82
174
|
createViewPrefix: "CREATE OR REPLACE VIEW",
|
|
83
175
|
paramPlaceholder(index) {
|
|
84
176
|
return `$${index}`;
|
|
@@ -105,33 +197,47 @@ function pgJsonExtract(quotedCol, path, type) {
|
|
|
105
197
|
/**
|
|
106
198
|
* Calendar-bucket label over an epoch-ms column (BIGINT for
|
|
107
199
|
* `@db.default.now`, DOUBLE PRECISION otherwise): TEXT `'YYYY-MM-DD'` — the
|
|
108
|
-
* local date of the bucket's first day in `b.tz` — or
|
|
109
|
-
* or
|
|
200
|
+
* local date of the bucket's first day in `b.tz` — or, for `hour`,
|
|
201
|
+
* `'YYYY-MM-DDTHH:00'` — the local wall-clock hour — or NULL for a NULL
|
|
202
|
+
* source or one outside `[BUCKET_MIN_INSTANT, BUCKET_MAX_INSTANT)`.
|
|
110
203
|
*
|
|
111
|
-
* The
|
|
112
|
-
* `timestamptz AT TIME ZONE` yields the wall time in that zone,
|
|
113
|
-
* of the session `TimeZone
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
204
|
+
* The wall time is `to_timestamp(col / 1000) AT TIME ZONE '<tz>'`:
|
|
205
|
+
* `timestamptz AT TIME ZONE` yields the (zone-less) wall time in that zone,
|
|
206
|
+
* independent of the session `TimeZone` — so a `+05:30` zone's hour starts at
|
|
207
|
+
* :30 UTC and a DST fall-back's repeated hour is one label. The hour is
|
|
208
|
+
* formatted from it; the other units truncate its `::date` with calendar
|
|
209
|
+
* arithmetic — the week uses the generic `(isodow - weekStart + 7) % 7`
|
|
210
|
+
* formula (`date_trunc('week')` is Monday-only). `date_trunc` and `to_char`
|
|
211
|
+
* get an explicit `::timestamp`: a bare `date` resolves to their
|
|
212
|
+
* `timestamptz` overloads, which would route through the session zone.
|
|
118
213
|
*
|
|
119
214
|
* Parameter-free (the zone is an inlined, charset-checked literal), so the
|
|
120
215
|
* SELECT and GROUP BY renderings match structurally.
|
|
121
216
|
*/
|
|
122
217
|
function pgCalendarBucket(quotedCol, b) {
|
|
123
|
-
const
|
|
124
|
-
|
|
218
|
+
const wall = `(to_timestamp(${quotedCol}::double precision / 1000) AT TIME ZONE ${(0, _atscript_db_sql_tools.sqlTimeZoneLiteral)(b.tz)})`;
|
|
219
|
+
const local = `${wall}::date`;
|
|
220
|
+
let value;
|
|
221
|
+
let format = "YYYY-MM-DD";
|
|
125
222
|
switch (b.unit) {
|
|
223
|
+
case "hour":
|
|
224
|
+
value = wall;
|
|
225
|
+
format = "YYYY-MM-DD\"T\"HH24\":00\"";
|
|
226
|
+
break;
|
|
126
227
|
case "day":
|
|
127
|
-
|
|
228
|
+
value = local;
|
|
128
229
|
break;
|
|
129
230
|
case "week":
|
|
130
|
-
|
|
231
|
+
value = `(${local} - ((EXTRACT(ISODOW FROM ${local})::int - ${b.weekStartIso} + 7) % 7))`;
|
|
131
232
|
break;
|
|
132
|
-
|
|
233
|
+
case "month":
|
|
234
|
+
case "quarter":
|
|
235
|
+
case "year":
|
|
236
|
+
value = `date_trunc('${b.unit}', ${local}::timestamp)`;
|
|
237
|
+
break;
|
|
238
|
+
default: throw new Error(`Unhandled calendar bucket unit "${String(b.unit)}"`);
|
|
133
239
|
}
|
|
134
|
-
return `CASE WHEN ${quotedCol} >= ${_uniqu_core.BUCKET_MIN_INSTANT} AND ${quotedCol} < ${_uniqu_core.BUCKET_MAX_INSTANT} THEN ${`to_char(${
|
|
240
|
+
return `CASE WHEN ${quotedCol} >= ${_uniqu_core.BUCKET_MIN_INSTANT} AND ${quotedCol} < ${_uniqu_core.BUCKET_MAX_INSTANT} THEN ${`to_char(${value}::timestamp, '${format}')`} END`;
|
|
135
241
|
}
|
|
136
242
|
/**
|
|
137
243
|
* Distance-in-meters expression from a geography column to a query point.
|
|
@@ -241,6 +347,30 @@ function intTypeFromTags(tags) {
|
|
|
241
347
|
if (tags?.has("int64") || tags?.has("uint64")) return "BIGINT";
|
|
242
348
|
return "INTEGER";
|
|
243
349
|
}
|
|
350
|
+
/** Physical types `MIN` aggregates (base name, lower-cased): the streaming pick applies. */
|
|
351
|
+
const MIN_ORDERED_TYPE = /^(smallint|int2|integer|int|int4|bigint|int8|real|float4|double precision|float8|numeric|decimal|money|text|varchar|character varying|char|character|bpchar|date|time|timetz|timestamp|timestamptz|interval|time (with|without) time zone|timestamp (with|without) time zone)\b/;
|
|
352
|
+
/** Text key types: a value comes back in the form it was sent (modulo case for `citext`), so a returned key maps to its input row. */
|
|
353
|
+
const TEXT_KEY_TYPE = /^(text|varchar|character varying|char|character|bpchar|citext)\b/;
|
|
354
|
+
/** `true` when the physical column type `type` is a text type (see {@link TEXT_KEY_TYPE}). */
|
|
355
|
+
function isTextKeyType(type) {
|
|
356
|
+
return TEXT_KEY_TYPE.test(type.trim().toLowerCase());
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The "any value of the group" pick of a `first` / `last` derived column
|
|
360
|
+
* (constant within its group) over a PostgreSQL column: `MIN` (streaming — one
|
|
361
|
+
* accumulator per group) for the ordered types and for every array column
|
|
362
|
+
* (`MIN(anyarray)`; `ARRAY_AGG` over arrays builds a 2-D array and rejects NULL /
|
|
363
|
+
* empty ones), `BOOL_AND` for a boolean, and `(ARRAY_AGG(x))[1]` only where the
|
|
364
|
+
* physical type has no `MIN` (uuid, bytea, point, json / jsonb, citext,
|
|
365
|
+
* `@db.pg.type` overrides, …) or is unknown.
|
|
366
|
+
*/
|
|
367
|
+
function pgAnyValue(expr, field) {
|
|
368
|
+
const type = field ? pgTypeFromField(field).trim().toLowerCase() : "";
|
|
369
|
+
if (type.endsWith("[]")) return `MIN(${expr})`;
|
|
370
|
+
if (/^bool(ean)?\b/.test(type)) return `BOOL_AND(${expr})`;
|
|
371
|
+
if (MIN_ORDERED_TYPE.test(type)) return `MIN(${expr})`;
|
|
372
|
+
return `(ARRAY_AGG(${expr}))[1]`;
|
|
373
|
+
}
|
|
244
374
|
/**
|
|
245
375
|
* Maps an Atscript field descriptor to a PostgreSQL column type.
|
|
246
376
|
*
|
|
@@ -296,11 +426,11 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
296
426
|
continue;
|
|
297
427
|
}
|
|
298
428
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
299
|
-
if (options?.incrementFields?.has(field.
|
|
429
|
+
if (options?.incrementFields?.has(field.path)) {
|
|
300
430
|
const start = options.autoIncrementStart;
|
|
301
431
|
def += start !== void 0 ? ` GENERATED BY DEFAULT AS IDENTITY (START WITH ${start})` : " GENERATED BY DEFAULT AS IDENTITY";
|
|
302
432
|
}
|
|
303
|
-
if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.
|
|
433
|
+
if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.path)) def += " NOT NULL";
|
|
304
434
|
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
305
435
|
else if (field.defaultValue?.kind === "fn") {
|
|
306
436
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT gen_random_uuid()";
|
|
@@ -321,12 +451,7 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
321
451
|
}
|
|
322
452
|
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
323
453
|
if (options?.deferForeignKeysTo?.has(fk.targetTable)) continue;
|
|
324
|
-
|
|
325
|
-
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
326
|
-
let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
327
|
-
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
328
|
-
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
329
|
-
colDefs.push(constraint);
|
|
454
|
+
colDefs.push((0, _atscript_db_sql_tools.foreignKeySql)(qi, fk));
|
|
330
455
|
}
|
|
331
456
|
return `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${colDefs.join(", ")})`;
|
|
332
457
|
}
|
|
@@ -341,9 +466,15 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
341
466
|
*
|
|
342
467
|
* Case-insensitive columns (`@db.collate 'nocase'`) are handled by CITEXT
|
|
343
468
|
* column type at the storage level — no query-side wrapping needed.
|
|
469
|
+
*
|
|
470
|
+
* Relational predicates (`$some` / `$none`) render as correlated `EXISTS`
|
|
471
|
+
* subqueries referencing the outer table by `opts.qualifier` (default: the
|
|
472
|
+
* source table) — a statement with an aliased FROM must pass its alias.
|
|
473
|
+
*
|
|
474
|
+
* @param opts - since 0.1.147
|
|
344
475
|
*/
|
|
345
|
-
function buildWhere(filter) {
|
|
346
|
-
return (0, _atscript_db_sql_tools.buildWhere)(pgDialect, filter);
|
|
476
|
+
function buildWhere(filter, opts) {
|
|
477
|
+
return (0, _atscript_db_sql_tools.buildWhere)(pgDialect, filter, opts);
|
|
347
478
|
}
|
|
348
479
|
//#endregion
|
|
349
480
|
//#region src/postgres-adapter.ts
|
|
@@ -369,6 +500,8 @@ function parseCount(value) {
|
|
|
369
500
|
* ```
|
|
370
501
|
*/
|
|
371
502
|
/** The suffix PostgreSQL gives an auto-named constraint of each `pg_constraint.contype`. */
|
|
503
|
+
/** Savepoint guarding one `insertManyIgnore` chunk (see {@link PostgresAdapter.insertManyIgnore}). */
|
|
504
|
+
const IGNORE_SAVEPOINT = "atscript_ignore_chunk";
|
|
372
505
|
const PG_CONSTRAINT_LABELS = {
|
|
373
506
|
p: "pkey",
|
|
374
507
|
u: "key",
|
|
@@ -426,7 +559,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
426
559
|
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
427
560
|
/**
|
|
428
561
|
* Schema name for catalog queries and qualification — `@db.schema` of the
|
|
429
|
-
* bound table, or `null` (→ `
|
|
562
|
+
* bound table, or `null` (→ the connection's `current_schema()`, the same default the bound path
|
|
430
563
|
* uses) when the table declares none or the adapter is an administrative
|
|
431
564
|
* one with no readable (the name-taking schema-sync primitives run on such
|
|
432
565
|
* an adapter).
|
|
@@ -482,6 +615,15 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
482
615
|
_exec() {
|
|
483
616
|
return this._txConnection() ?? this.driver;
|
|
484
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Relational predicates (`$some` / `$none`) render as correlated
|
|
620
|
+
* `[NOT] EXISTS` subqueries — in reads and in mutation filters alike.
|
|
621
|
+
*
|
|
622
|
+
* @since 0.1.147
|
|
623
|
+
*/
|
|
624
|
+
supportsRelationFilters(_mode) {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
485
627
|
/** PostgreSQL enforces FK constraints natively. */
|
|
486
628
|
supportsNativeForeignKeys() {
|
|
487
629
|
return true;
|
|
@@ -502,10 +644,18 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
502
644
|
calendarBucketUnits() {
|
|
503
645
|
return _atscript_db.ALL_BUCKET_UNITS;
|
|
504
646
|
}
|
|
505
|
-
/** Every aggregate function
|
|
647
|
+
/** Every aggregate function: `countDistinct`, `first` and `last` included. */
|
|
506
648
|
aggregateFns() {
|
|
507
649
|
return _atscript_db.ALL_AGGREGATE_FNS;
|
|
508
650
|
}
|
|
651
|
+
/** Arithmetic in an aggregate `$select` (`{ $expr }`, `{ $fn, $expr }`). */
|
|
652
|
+
supportsAggregateExpressions() {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
/** Computed view columns and first-row joins. */
|
|
656
|
+
viewCapabilities() {
|
|
657
|
+
return _atscript_db.ALL_VIEW_CAPABILITIES;
|
|
658
|
+
}
|
|
509
659
|
onBeforeFlatten(_type) {}
|
|
510
660
|
onAfterFlatten() {
|
|
511
661
|
for (const fd of this._table.fieldDescriptors) if (fd.collate === "nocase") this._nocaseColumns.add(fd.physicalName);
|
|
@@ -571,17 +721,21 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
571
721
|
try {
|
|
572
722
|
return await fn();
|
|
573
723
|
} catch (error) {
|
|
574
|
-
|
|
575
|
-
const err = error;
|
|
576
|
-
if (err.code === "23505") throw new _atscript_db.DbError("CONFLICT", [{
|
|
577
|
-
path: this._extractFieldFromConstraint(err.constraint) ?? "",
|
|
578
|
-
message: err.detail ?? err.message
|
|
579
|
-
}]);
|
|
580
|
-
if (err.code === "23503") throw new _atscript_db.DbError("FK_VIOLATION", this._mapFkError(err.detail ?? err.message, err.constraint));
|
|
581
|
-
}
|
|
582
|
-
throw error;
|
|
724
|
+
return this._mapConstraintError(error);
|
|
583
725
|
}
|
|
584
726
|
}
|
|
727
|
+
/** Rethrows `error` as a structured `DbError` when it is a unique / FK violation, else as is. */
|
|
728
|
+
_mapConstraintError(error) {
|
|
729
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
730
|
+
const err = error;
|
|
731
|
+
if (err.code === "23505") throw new _atscript_db.DbError("CONFLICT", [{
|
|
732
|
+
path: this._extractFieldFromConstraint(err.constraint) ?? "",
|
|
733
|
+
message: err.detail ?? err.message
|
|
734
|
+
}]);
|
|
735
|
+
if (err.code === "23503") throw new _atscript_db.DbError("FK_VIOLATION", this._mapFkError(err.detail ?? err.message, err.constraint));
|
|
736
|
+
}
|
|
737
|
+
throw error;
|
|
738
|
+
}
|
|
585
739
|
_extractFieldFromConstraint(constraint) {
|
|
586
740
|
if (!constraint) return;
|
|
587
741
|
const tableName = this._table.tableName;
|
|
@@ -608,7 +762,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
608
762
|
}
|
|
609
763
|
async insertOne(data) {
|
|
610
764
|
let { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
611
|
-
const pkCols = this.
|
|
765
|
+
const pkCols = this._pkColumns().map((pk) => qi(pk));
|
|
612
766
|
if (pkCols.length > 0) sql += ` RETURNING ${pkCols.join(", ")}`;
|
|
613
767
|
this._log(sql, params);
|
|
614
768
|
const returned = (await this._wrapConstraintError(() => this._exec().run(sql, params))).rows?.[0];
|
|
@@ -621,13 +775,11 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
621
775
|
};
|
|
622
776
|
return this.withTransaction(async () => {
|
|
623
777
|
const tableName = this.resolveTableName();
|
|
624
|
-
const pkCols = this.
|
|
778
|
+
const pkCols = this._pkColumns();
|
|
625
779
|
const returningSuffix = pkCols.length > 0 ? ` RETURNING ${pkCols.map((pk) => qi(pk)).join(", ")}` : "";
|
|
626
|
-
const columns = (0, _atscript_db_sql_tools.
|
|
627
|
-
const maxRowsPerBatch = columns.length > 0 ? Math.floor(6e4 / columns.length) : data.length;
|
|
780
|
+
const { columns, batches } = (0, _atscript_db_sql_tools.chunkInsertRows)(data);
|
|
628
781
|
const allIds = [];
|
|
629
|
-
for (
|
|
630
|
-
const batch = data.slice(offset, offset + maxRowsPerBatch);
|
|
782
|
+
for (const batch of batches) {
|
|
631
783
|
const insert = buildInsertMany(tableName, batch, columns);
|
|
632
784
|
const sql = insert.sql + returningSuffix;
|
|
633
785
|
const params = insert.params;
|
|
@@ -644,6 +796,56 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
644
796
|
};
|
|
645
797
|
});
|
|
646
798
|
}
|
|
799
|
+
supportsInsertIgnore() {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Batched `INSERT … VALUES (…), (…) ON CONFLICT DO NOTHING RETURNING <pk +
|
|
804
|
+
* unique key columns>`: one statement per chunk (a conflict never raises, so
|
|
805
|
+
* the surrounding transaction survives). Skipped rows are the ones missing
|
|
806
|
+
* from RETURNING — mapped back by key values, see {@link mapIgnoredBatch}.
|
|
807
|
+
* Each chunk runs inside a SAVEPOINT: when the mapping is ambiguous (a key
|
|
808
|
+
* the server returns in another form than it was sent) the chunk is rolled
|
|
809
|
+
* back to the savepoint and redone row by row with the same
|
|
810
|
+
* `ON CONFLICT DO NOTHING`, so the result is always exact.
|
|
811
|
+
*/
|
|
812
|
+
async insertManyIgnore(data) {
|
|
813
|
+
if (data.length === 0) return [];
|
|
814
|
+
return this.withTransaction(async () => {
|
|
815
|
+
const tableName = this.resolveTableName();
|
|
816
|
+
const pkCols = this._pkColumns();
|
|
817
|
+
const keySets = this._table.uniqueKeySets;
|
|
818
|
+
const returning = [...new Set(keySets.flat())];
|
|
819
|
+
const returningCols = new Set(returning);
|
|
820
|
+
const nonTextKeyCols = new Set(this._table.fieldDescriptors.filter((f) => returningCols.has(f.physicalName) && !isTextKeyType(this.typeMapper(f))).map((f) => f.physicalName));
|
|
821
|
+
const returningSuffix = returning.length > 0 ? ` RETURNING ${returning.map((c) => qi(c)).join(", ")}` : "";
|
|
822
|
+
const insertedId = (row, returned) => ({ insertedId: this._resolveInsertedId(row, pkCols.length > 0 ? returned?.[pkCols[0]] : void 0) });
|
|
823
|
+
const { columns, batches } = (0, _atscript_db_sql_tools.chunkInsertRows)(data);
|
|
824
|
+
const slots = [];
|
|
825
|
+
for (const batch of batches) {
|
|
826
|
+
const insert = buildInsertMany(tableName, batch, columns);
|
|
827
|
+
const sql = `${insert.sql} ON CONFLICT DO NOTHING${returningSuffix}`;
|
|
828
|
+
await this._exec().run(`SAVEPOINT ${IGNORE_SAVEPOINT}`);
|
|
829
|
+
this._log(sql, insert.params);
|
|
830
|
+
const returned = (await this._wrapConstraintError(() => this._exec().run(sql, insert.params))).rows ?? [];
|
|
831
|
+
const mapping = returning.length === 0 ? batch.map((_, i) => i) : mapIgnoredBatch(batch, returned, keySets, nonTextKeyCols);
|
|
832
|
+
if (mapping) {
|
|
833
|
+
await this._exec().run(`RELEASE SAVEPOINT ${IGNORE_SAVEPOINT}`);
|
|
834
|
+
mapping.forEach((hit, i) => slots.push(hit < 0 ? null : insertedId(batch[i], returned[hit])));
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
await this._exec().run(`ROLLBACK TO SAVEPOINT ${IGNORE_SAVEPOINT}`);
|
|
838
|
+
for (const row of batch) {
|
|
839
|
+
const single = buildInsert(tableName, row);
|
|
840
|
+
const rowSql = `${single.sql} ON CONFLICT DO NOTHING${returningSuffix}`;
|
|
841
|
+
this._log(rowSql, single.params);
|
|
842
|
+
const rowResult = await this._wrapConstraintError(() => this._exec().run(rowSql, single.params));
|
|
843
|
+
slots.push(rowResult.rows?.length ? insertedId(row, rowResult.rows[0]) : null);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return slots;
|
|
847
|
+
});
|
|
848
|
+
}
|
|
647
849
|
async findOne(query) {
|
|
648
850
|
const where = buildWhere(query.filter);
|
|
649
851
|
const controls = {
|
|
@@ -660,6 +862,16 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
660
862
|
this._log(sql, params);
|
|
661
863
|
return this._exec().all(sql, params);
|
|
662
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* `$skip` / `$limit` per partition in one statement: a `ROW_NUMBER()`
|
|
867
|
+
* window over `partitionBy` (the generic `$with` loader's per-parent page).
|
|
868
|
+
*/
|
|
869
|
+
async findManyPerPartition(query, partitionBy) {
|
|
870
|
+
const where = buildWhere(query.filter);
|
|
871
|
+
const { sql, params } = (0, _atscript_db_sql_tools.buildPartitionedSelect)(pgDialect, this.resolveTableName(), where, query.controls, partitionBy);
|
|
872
|
+
this._log(sql, params);
|
|
873
|
+
return (0, _atscript_db_sql_tools.stripPartitionRowNumber)(await this._exec().all(sql, params));
|
|
874
|
+
}
|
|
663
875
|
async count(query) {
|
|
664
876
|
const where = buildWhere(query.filter);
|
|
665
877
|
const { sql, params } = (0, _atscript_db_sql_tools.finalizeParams)(pgDialect, {
|
|
@@ -676,28 +888,15 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
676
888
|
if (query.controls.$count) {
|
|
677
889
|
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
678
890
|
this._log(sql, params);
|
|
679
|
-
return [{ count: parseCount((await
|
|
891
|
+
return [{ count: parseCount((await (0, _atscript_db_sql_tools.mapQueryErrors)(pgDialect, () => this._exec().get(sql, params), query.controls))?.count) }];
|
|
680
892
|
}
|
|
681
893
|
const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
|
|
682
894
|
this._log(sql, params);
|
|
683
|
-
return
|
|
895
|
+
return (0, _atscript_db_sql_tools.mapQueryErrors)(pgDialect, () => this._exec().all(sql, params), query.controls);
|
|
684
896
|
}
|
|
685
|
-
/**
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
* calendar bucket's `AT TIME ZONE '<tz>'` to `BUCKET_TZ_UNAVAILABLE`. The
|
|
689
|
-
* zone passed the core's IANA validation, so the server's tz database is
|
|
690
|
-
* older than the runtime's. Other errors propagate unchanged.
|
|
691
|
-
*/
|
|
692
|
-
async _wrapBucketZoneError(fn) {
|
|
693
|
-
try {
|
|
694
|
-
return await fn();
|
|
695
|
-
} catch (error) {
|
|
696
|
-
const err = error;
|
|
697
|
-
const zone = err?.code === "22023" && typeof err.message === "string" ? /time zone "([^"]*)" not recognized/.exec(err.message)?.[1] : void 0;
|
|
698
|
-
if (zone !== void 0) throw (0, _atscript_db.bucketTimeZoneUnavailable)(`PostgreSQL does not recognize time zone "${zone}" — update the server's time zone data`);
|
|
699
|
-
throw error;
|
|
700
|
-
}
|
|
897
|
+
/** Physical primary-key columns (`@db.column` renames applied). */
|
|
898
|
+
_pkColumns() {
|
|
899
|
+
return this._table.primaryKeys.map((key) => this._table.physicalPath(key));
|
|
701
900
|
}
|
|
702
901
|
/**
|
|
703
902
|
* The predicate that narrows a single-row UPDATE/DELETE to the first row
|
|
@@ -708,7 +907,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
708
907
|
* existing behavior rather than guess.
|
|
709
908
|
*/
|
|
710
909
|
_limitOnePredicate(quotedTable, whereSql) {
|
|
711
|
-
const pkCols = this.
|
|
910
|
+
const pkCols = this._pkColumns();
|
|
712
911
|
const quotedKeys = pkCols.length > 0 ? pkCols.map((c) => qi(c)) : ["ctid"];
|
|
713
912
|
const colList = quotedKeys.join(", ");
|
|
714
913
|
return `${quotedKeys.length === 1 ? `${colList} =` : `(${colList}) IN`} (SELECT ${colList} FROM ${quotedTable} WHERE ${whereSql} LIMIT 1)`;
|
|
@@ -818,7 +1017,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
818
1017
|
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
819
1018
|
JOIN pg_class rcl ON rcl.oid = c.confrelid
|
|
820
1019
|
JOIN pg_namespace rn ON rn.oid = rcl.relnamespace
|
|
821
|
-
WHERE c.contype = 'f' AND rcl.relname = $1 AND rn.nspname = COALESCE($2,
|
|
1020
|
+
WHERE c.contype = 'f' AND rcl.relname = $1 AND rn.nspname = COALESCE($2, current_schema())
|
|
822
1021
|
ORDER BY cl.relname, c.conname`, [tableName, this._schema])).map((r) => ({
|
|
823
1022
|
table: r.table_name,
|
|
824
1023
|
fields: r.columns ?? [],
|
|
@@ -828,7 +1027,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
828
1027
|
async getObjectKind(name) {
|
|
829
1028
|
switch ((await this._exec().get(`SELECT c.relkind FROM pg_class c
|
|
830
1029
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
831
|
-
WHERE c.relname = $1 AND n.nspname = COALESCE($2,
|
|
1030
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, current_schema())
|
|
832
1031
|
AND c.relkind IN ('r', 'p', 'v', 'm')`, [name, this._schema]))?.relkind) {
|
|
833
1032
|
case "r":
|
|
834
1033
|
case "p": return "table";
|
|
@@ -856,7 +1055,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
856
1055
|
const row = await this._exec().get(`SELECT c.conname FROM pg_constraint c
|
|
857
1056
|
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
858
1057
|
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
859
|
-
WHERE c.contype = 'p' AND cl.relname = $1 AND n.nspname = COALESCE($2,
|
|
1058
|
+
WHERE c.contype = 'p' AND cl.relname = $1 AND n.nspname = COALESCE($2, current_schema())`, [this._table.tableName, this._schema]);
|
|
860
1059
|
if (row?.conname) clauses.push(`DROP CONSTRAINT ${qi(row.conname)}`);
|
|
861
1060
|
}
|
|
862
1061
|
if (change.to.length > 0) clauses.push(`ADD PRIMARY KEY (${change.to.map((c) => qi(c)).join(", ")})`);
|
|
@@ -888,8 +1087,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
888
1087
|
) AS is_pk
|
|
889
1088
|
FROM information_schema.columns c
|
|
890
1089
|
JOIN pg_attribute a ON a.attname = c.column_name
|
|
891
|
-
AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2,
|
|
892
|
-
WHERE c.table_name = $1 AND c.table_schema = COALESCE($2,
|
|
1090
|
+
AND a.attrelid = (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = COALESCE($2, current_schema())))
|
|
1091
|
+
WHERE c.table_name = $1 AND c.table_schema = COALESCE($2, current_schema())
|
|
893
1092
|
ORDER BY c.ordinal_position`, [tableName, schema])).map((r) => {
|
|
894
1093
|
const column = {
|
|
895
1094
|
name: r.column_name,
|
|
@@ -1009,10 +1208,10 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1009
1208
|
CROSS JOIN LATERAL unnest(c.conkey, c.confkey) WITH ORDINALITY AS k(attnum, refattnum, ord)
|
|
1010
1209
|
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum
|
|
1011
1210
|
JOIN pg_attribute ra ON ra.attrelid = c.confrelid AND ra.attnum = k.refattnum
|
|
1012
|
-
WHERE c.contype = 'f' AND rcl.relname = $2 AND rn.nspname = COALESCE($1,
|
|
1211
|
+
WHERE c.contype = 'f' AND rcl.relname = $2 AND rn.nspname = COALESCE($1, current_schema())
|
|
1013
1212
|
ORDER BY n.nspname, cl.relname, c.conname, k.ord`, [schema, this._table.tableName]);
|
|
1014
1213
|
const inboundFks = /* @__PURE__ */ new Map();
|
|
1015
|
-
const ownSchema = schema ?? "public";
|
|
1214
|
+
const ownSchema = schema ?? (await conn.get("SELECT current_schema() AS s"))?.s ?? "public";
|
|
1016
1215
|
for (const fk of fkRefs) {
|
|
1017
1216
|
if (fk.table_schema === ownSchema && fk.table_name === this._table.tableName) continue;
|
|
1018
1217
|
const key = `${fk.table_schema}.${fk.table_name}.${fk.constraint_name}`;
|
|
@@ -1107,7 +1306,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1107
1306
|
FROM pg_constraint c
|
|
1108
1307
|
JOIN pg_class cl ON cl.oid = c.conrelid
|
|
1109
1308
|
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1110
|
-
WHERE cl.relname = $1 AND n.nspname = COALESCE($2,
|
|
1309
|
+
WHERE cl.relname = $1 AND n.nspname = COALESCE($2, current_schema())
|
|
1111
1310
|
ORDER BY c.conname`, [table, this._schema]);
|
|
1112
1311
|
const taken = new Set(rows.map((r) => r.conname));
|
|
1113
1312
|
for (const { conname, contype, columns } of rows) {
|
|
@@ -1131,7 +1330,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1131
1330
|
async _relationExists(conn, name) {
|
|
1132
1331
|
return (await conn.get(`SELECT true AS present FROM pg_class c
|
|
1133
1332
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1134
|
-
WHERE c.relname = $1 AND n.nspname = COALESCE($2,
|
|
1333
|
+
WHERE c.relname = $1 AND n.nspname = COALESCE($2, current_schema())`, [name, this._schema]))?.present ?? false;
|
|
1135
1334
|
}
|
|
1136
1335
|
async afterSyncTable() {
|
|
1137
1336
|
await this._resetIdentitySequences();
|
|
@@ -1154,7 +1353,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1154
1353
|
}
|
|
1155
1354
|
}
|
|
1156
1355
|
async tableExists() {
|
|
1157
|
-
return (await this._exec().get(`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = COALESCE($2,
|
|
1356
|
+
return (await this._exec().get(`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = COALESCE($2, current_schema())) AS "exists"`, [this._table.tableName, this._schema]))?.exists ?? false;
|
|
1158
1357
|
}
|
|
1159
1358
|
/**
|
|
1160
1359
|
* No CASCADE: when something outside the sync inventory (a user view, an
|
|
@@ -1183,7 +1382,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1183
1382
|
JOIN pg_class i ON i.oid = x.indexrelid
|
|
1184
1383
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
1185
1384
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(x.indkey)
|
|
1186
|
-
WHERE t.relname = $1 AND n.nspname = COALESCE($2,
|
|
1385
|
+
WHERE t.relname = $1 AND n.nspname = COALESCE($2, current_schema())
|
|
1187
1386
|
AND i.relname LIKE 'atscript__%' AND a.attname = ANY($3)`, [
|
|
1188
1387
|
tableName,
|
|
1189
1388
|
schema,
|
|
@@ -1235,7 +1434,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1235
1434
|
JOIN pg_class t ON t.oid = x.indrelid
|
|
1236
1435
|
JOIN pg_class i ON i.oid = x.indexrelid
|
|
1237
1436
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
1238
|
-
WHERE t.relname = $1 AND n.nspname = COALESCE($2,
|
|
1437
|
+
WHERE t.relname = $1 AND n.nspname = COALESCE($2, current_schema())`, [tableName, schema])).map((r) => ({
|
|
1239
1438
|
name: r.name,
|
|
1240
1439
|
columns: r.columns ?? void 0
|
|
1241
1440
|
}));
|
|
@@ -1281,7 +1480,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1281
1480
|
async syncForeignKeys() {
|
|
1282
1481
|
const existingByName = await this._getExistingFkConstraints();
|
|
1283
1482
|
const desiredFkKeys = /* @__PURE__ */ new Set();
|
|
1284
|
-
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
|
|
1483
|
+
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...(0, _atscript_db.fkColumns)(fk).fields].toSorted().join(","));
|
|
1285
1484
|
for (const [constraintName, columns] of existingByName) {
|
|
1286
1485
|
const key = [...columns].toSorted().join(",");
|
|
1287
1486
|
if (!desiredFkKeys.has(key)) {
|
|
@@ -1292,13 +1491,9 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1292
1491
|
}
|
|
1293
1492
|
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
|
|
1294
1493
|
for (const fk of this._table.foreignKeys.values()) {
|
|
1295
|
-
const key = [...fk.fields].toSorted().join(",");
|
|
1494
|
+
const key = [...(0, _atscript_db.fkColumns)(fk).fields].toSorted().join(",");
|
|
1296
1495
|
if (!existingKeys.has(key)) {
|
|
1297
|
-
const
|
|
1298
|
-
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
1299
|
-
let ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ADD FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
1300
|
-
if (fk.onDelete) ddl += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
1301
|
-
if (fk.onUpdate) ddl += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
1496
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ADD ${(0, _atscript_db_sql_tools.foreignKeySql)(qi, fk)}`;
|
|
1302
1497
|
this._log(ddl);
|
|
1303
1498
|
await this._exec().exec(ddl);
|
|
1304
1499
|
}
|
|
@@ -1325,7 +1520,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1325
1520
|
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
1326
1521
|
CROSS JOIN LATERAL unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
|
|
1327
1522
|
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum
|
|
1328
|
-
WHERE c.contype = 'f' AND cl.relname = $1 AND n.nspname = COALESCE($2,
|
|
1523
|
+
WHERE c.contype = 'f' AND cl.relname = $1 AND n.nspname = COALESCE($2, current_schema())
|
|
1329
1524
|
ORDER BY c.conname, k.ord`, [this._table.tableName, this._schema]);
|
|
1330
1525
|
const byName = /* @__PURE__ */ new Map();
|
|
1331
1526
|
for (const row of rows) {
|
|
@@ -1470,7 +1665,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1470
1665
|
vec = first.value[1];
|
|
1471
1666
|
}
|
|
1472
1667
|
const distanceOp = similarityToPgOp(vec.similarity);
|
|
1473
|
-
const where = buildWhere(query.filter);
|
|
1668
|
+
const where = buildWhere(query.filter, { qualifier: pgDialect.quoteTable(_atscript_db_sql_tools.SEARCH_SOURCE_ALIAS) });
|
|
1474
1669
|
const controls = query.controls || {};
|
|
1475
1670
|
const threshold = this._resolveVectorThreshold(controls, vec.indexName);
|
|
1476
1671
|
return {
|
|
@@ -1563,7 +1758,7 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1563
1758
|
const controls = query.controls ?? {};
|
|
1564
1759
|
return {
|
|
1565
1760
|
tableName: this.resolveTableName(),
|
|
1566
|
-
where: buildWhere(query.filter),
|
|
1761
|
+
where: buildWhere(query.filter, { qualifier: pgDialect.quoteTable(_atscript_db_sql_tools.SEARCH_SOURCE_ALIAS) }),
|
|
1567
1762
|
dist: pgGeoDistanceExpr(qi(column), point),
|
|
1568
1763
|
window: (0, _atscript_db_sql_tools.geoWindowFromControls)(controls),
|
|
1569
1764
|
controls
|
|
@@ -1816,8 +2011,10 @@ var PgDriver = class {
|
|
|
1816
2011
|
}
|
|
1817
2012
|
};
|
|
1818
2013
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
2014
|
+
_closing;
|
|
2015
|
+
/** Idempotent: every call returns the first call's promise. */
|
|
2016
|
+
close() {
|
|
2017
|
+
return this._closing ??= Promise.resolve(this.getPool()).then((pool) => pool.end());
|
|
1821
2018
|
}
|
|
1822
2019
|
};
|
|
1823
2020
|
//#endregion
|
|
@@ -1834,7 +2031,7 @@ function createAdapter(uri, options) {
|
|
|
1834
2031
|
connectionString: uri,
|
|
1835
2032
|
...options
|
|
1836
2033
|
});
|
|
1837
|
-
return new _atscript_db.DbSpace(() => new PostgresAdapter(driver));
|
|
2034
|
+
return new _atscript_db.DbSpace(() => new PostgresAdapter(driver), { onClose: () => driver.close() });
|
|
1838
2035
|
}
|
|
1839
2036
|
//#endregion
|
|
1840
2037
|
exports.PgDriver = PgDriver;
|