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