@vibeorm/adapter-pg 5.0.1 → 5.1.0
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/README.md +10 -0
- package/dist/batch.d.ts +56 -2
- package/dist/index.js +218 -51
- package/dist/index.js.map +6 -6
- package/dist/statement-cache.d.ts +25 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -29,13 +29,37 @@ import {
|
|
|
29
29
|
startTransactionBudget,
|
|
30
30
|
transactionAbortedError as transactionAbortedError2,
|
|
31
31
|
validateTransactionDeadline,
|
|
32
|
-
parseExtendedDateTimeText
|
|
32
|
+
parseExtendedDateTimeText,
|
|
33
|
+
assertStatementName
|
|
33
34
|
} from "@vibeorm/runtime/adapter-kit";
|
|
34
|
-
import { VibeError as
|
|
35
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
35
36
|
import pg3 from "pg";
|
|
36
37
|
|
|
37
38
|
// packages/adapter-pg/src/batch.ts
|
|
39
|
+
import { VibeError } from "@vibeorm/schema";
|
|
38
40
|
import pg from "pg";
|
|
41
|
+
function statementBookOf(params) {
|
|
42
|
+
const connection = params.connection;
|
|
43
|
+
if (connection === null || typeof connection !== "object")
|
|
44
|
+
return;
|
|
45
|
+
const { parsedStatements, submittedNamedStatements, on, removeListener } = connection;
|
|
46
|
+
if (typeof parsedStatements !== "object" || parsedStatements === null)
|
|
47
|
+
return;
|
|
48
|
+
if (typeof submittedNamedStatements !== "object" || submittedNamedStatements === null)
|
|
49
|
+
return;
|
|
50
|
+
if (typeof on !== "function" || typeof removeListener !== "function")
|
|
51
|
+
return;
|
|
52
|
+
return {
|
|
53
|
+
parsed: parsedStatements,
|
|
54
|
+
submitted: submittedNamedStatements,
|
|
55
|
+
onParseComplete: (listener) => {
|
|
56
|
+
on.call(connection, "parseComplete", listener);
|
|
57
|
+
return () => {
|
|
58
|
+
removeListener.call(connection, "parseComplete", listener);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
39
63
|
var prepareValue = (() => {
|
|
40
64
|
const utils = pg.utils;
|
|
41
65
|
return typeof utils?.prepareValue === "function" ? utils.prepareValue : undefined;
|
|
@@ -78,6 +102,11 @@ class PgBatch {
|
|
|
78
102
|
resolve = () => {
|
|
79
103
|
return;
|
|
80
104
|
};
|
|
105
|
+
book = undefined;
|
|
106
|
+
parses = [];
|
|
107
|
+
parsesAnswered = 0;
|
|
108
|
+
stopParseTracking = undefined;
|
|
109
|
+
written = false;
|
|
81
110
|
constructor(params) {
|
|
82
111
|
this.steps = params.steps;
|
|
83
112
|
this.types = params.types;
|
|
@@ -87,18 +116,85 @@ class PgBatch {
|
|
|
87
116
|
}
|
|
88
117
|
submit(connection) {
|
|
89
118
|
const writer = connection;
|
|
119
|
+
if (writer.stream?.writable === false) {
|
|
120
|
+
return new VibeError({
|
|
121
|
+
code: "VIBE_ADAPTER",
|
|
122
|
+
message: "the connection's socket is no longer writable: the batch was not sent",
|
|
123
|
+
meta: { adapter: "pg", reason: "connection-not-writable" }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const book = this.steps.some((step) => step.name !== undefined) ? statementBookOf({ connection }) : undefined;
|
|
127
|
+
const pending = new Map;
|
|
128
|
+
const names = [];
|
|
129
|
+
for (const step of this.steps) {
|
|
130
|
+
const name = step.name;
|
|
131
|
+
if (name === undefined || book === undefined) {
|
|
132
|
+
names.push({ parse: true });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const known = book.parsed[name] ?? book.submitted[name] ?? pending.get(name);
|
|
136
|
+
if (known !== undefined && known !== step.text) {
|
|
137
|
+
return new VibeError({
|
|
138
|
+
code: "VIBE_VALIDATION",
|
|
139
|
+
message: `statement name "${name}" already names another SQL text on this connection: one name means one text`,
|
|
140
|
+
meta: { adapter: "pg", reason: "statement-name-reused", name }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (known === undefined)
|
|
144
|
+
pending.set(name, step.text);
|
|
145
|
+
names.push({ parse: known === undefined, name });
|
|
146
|
+
}
|
|
147
|
+
if (book !== undefined)
|
|
148
|
+
for (const [name, text] of pending)
|
|
149
|
+
book.submitted[name] = text;
|
|
150
|
+
this.written = true;
|
|
151
|
+
if (names.some((entry) => entry.parse && entry.name !== undefined) && book !== undefined) {
|
|
152
|
+
this.book = book;
|
|
153
|
+
this.stopParseTracking = book.onParseComplete(() => {
|
|
154
|
+
this.answerParse();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
90
157
|
writer.stream?.cork?.();
|
|
91
158
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
159
|
+
this.steps.forEach((step, index) => {
|
|
160
|
+
const entry = names[index];
|
|
161
|
+
if (entry.parse) {
|
|
162
|
+
writer.parse(entry.name === undefined ? { text: step.text, types: [] } : { text: step.text, name: entry.name, types: [] });
|
|
163
|
+
this.parses.push(entry.name === undefined ? null : { name: entry.name, text: step.text });
|
|
164
|
+
}
|
|
165
|
+
writer.bind(entry.name === undefined ? { values: step.values ?? [] } : { statement: entry.name, values: step.values ?? [] });
|
|
95
166
|
writer.describe({ type: "P", name: "" });
|
|
96
167
|
writer.execute({ portal: "", rows: 0 });
|
|
97
|
-
}
|
|
168
|
+
});
|
|
98
169
|
writer.sync();
|
|
99
170
|
} finally {
|
|
100
171
|
writer.stream?.uncork?.();
|
|
101
172
|
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
answerParse() {
|
|
176
|
+
const book = this.book;
|
|
177
|
+
if (book === undefined || this.parsesAnswered >= this.parses.length)
|
|
178
|
+
return;
|
|
179
|
+
const parse = this.parses[this.parsesAnswered];
|
|
180
|
+
this.parsesAnswered += 1;
|
|
181
|
+
if (parse === null || parse === undefined)
|
|
182
|
+
return;
|
|
183
|
+
book.parsed[parse.name] = parse.text;
|
|
184
|
+
delete book.submitted[parse.name];
|
|
185
|
+
}
|
|
186
|
+
endParseTracking() {
|
|
187
|
+
const book = this.book;
|
|
188
|
+
if (book === undefined)
|
|
189
|
+
return;
|
|
190
|
+
for (const parse of this.parses.slice(this.parsesAnswered)) {
|
|
191
|
+
if (parse !== null && book.submitted[parse.name] === parse.text)
|
|
192
|
+
delete book.submitted[parse.name];
|
|
193
|
+
}
|
|
194
|
+
this.parsesAnswered = this.parses.length;
|
|
195
|
+
this.book = undefined;
|
|
196
|
+
this.stopParseTracking?.();
|
|
197
|
+
this.stopParseTracking = undefined;
|
|
102
198
|
}
|
|
103
199
|
handleRowDescription(message) {
|
|
104
200
|
this.rows = [];
|
|
@@ -152,6 +248,8 @@ class PgBatch {
|
|
|
152
248
|
this.outcomes.push({ status: "ok", result: { rows: [], rowCount: null } });
|
|
153
249
|
}
|
|
154
250
|
handleError(error) {
|
|
251
|
+
if (isServerError(error))
|
|
252
|
+
this.endParseTracking();
|
|
155
253
|
if (this.settled)
|
|
156
254
|
return;
|
|
157
255
|
if (!isServerError(error)) {
|
|
@@ -164,6 +262,7 @@ class PgBatch {
|
|
|
164
262
|
this.finish({});
|
|
165
263
|
}
|
|
166
264
|
handleReadyForQuery() {
|
|
265
|
+
this.endParseTracking();
|
|
167
266
|
this.finish({});
|
|
168
267
|
}
|
|
169
268
|
handlePortalSuspended() {}
|
|
@@ -178,7 +277,7 @@ class PgBatch {
|
|
|
178
277
|
this.callback?.(params.transportError ?? null);
|
|
179
278
|
this.resolve({
|
|
180
279
|
steps: this.outcomes,
|
|
181
|
-
...params.transportError === undefined ? {} : { transportError: params.transportError }
|
|
280
|
+
...params.transportError === undefined ? {} : { transportError: params.transportError, ...this.written ? {} : { unsent: true } }
|
|
182
281
|
});
|
|
183
282
|
}
|
|
184
283
|
}
|
|
@@ -198,10 +297,10 @@ function firstBatchFailure(params) {
|
|
|
198
297
|
}
|
|
199
298
|
|
|
200
299
|
// packages/adapter-pg/src/connection-group.ts
|
|
201
|
-
import { VibeError } from "@vibeorm/schema";
|
|
300
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
202
301
|
import { transactionAbortedError } from "@vibeorm/runtime/adapter-kit";
|
|
203
302
|
function connectionGroupMisuse(params) {
|
|
204
|
-
return new
|
|
303
|
+
return new VibeError2({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
|
|
205
304
|
}
|
|
206
305
|
function openConnectionGroup(params) {
|
|
207
306
|
const { driver } = params;
|
|
@@ -309,7 +408,7 @@ function openConnectionGroup(params) {
|
|
|
309
408
|
}
|
|
310
409
|
|
|
311
410
|
// packages/adapter-pg/src/connection-string.ts
|
|
312
|
-
import { VibeError as
|
|
411
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
313
412
|
function lastValue(params) {
|
|
314
413
|
const values = params.search.getAll(params.key);
|
|
315
414
|
return values.length === 0 ? undefined : values[values.length - 1];
|
|
@@ -328,7 +427,7 @@ function honourSystemRootCert(params) {
|
|
|
328
427
|
const ssl = lastValue({ search, key: "ssl" });
|
|
329
428
|
const weaker = sslmode !== undefined && sslmode !== "verify-full" ? { parameter: "sslmode", value: sslmode } : ssl !== undefined && ssl !== "true" && ssl !== "1" ? { parameter: "ssl", value: ssl } : undefined;
|
|
330
429
|
if (weaker !== undefined) {
|
|
331
|
-
throw new
|
|
430
|
+
throw new VibeError3({
|
|
332
431
|
code: "VIBE_CONFIG",
|
|
333
432
|
message: `pgAdapter \`${option}\` combines sslrootcert=system with ${weaker.parameter}=${JSON.stringify(weaker.value)}. ` + "libpq allows sslrootcert=system only with sslmode=verify-full (its default when sslmode is omitted): any certificate " + "the system trusts would otherwise vouch for a server that is not yours. Use sslmode=verify-full, or remove the setting.",
|
|
334
433
|
meta: { adapter: "pg", reason: "sslrootcert-system-weak-sslmode", option, parameter: weaker.parameter, value: weaker.value }
|
|
@@ -370,7 +469,7 @@ function createClientTurns() {
|
|
|
370
469
|
|
|
371
470
|
// packages/adapter-pg/src/read-cancellation.ts
|
|
372
471
|
import pg2 from "pg";
|
|
373
|
-
import { VibeError as
|
|
472
|
+
import { VibeError as VibeError5 } from "@vibeorm/schema";
|
|
374
473
|
import { assertReadExecution, readCancelledError } from "@vibeorm/runtime/adapter-kit";
|
|
375
474
|
|
|
376
475
|
// packages/adapter-pg/src/config.ts
|
|
@@ -379,7 +478,7 @@ var PG_READ_CANCELLATION = Object.freeze({
|
|
|
379
478
|
});
|
|
380
479
|
|
|
381
480
|
// packages/adapter-pg/src/errors.ts
|
|
382
|
-
import { VibeError as
|
|
481
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
383
482
|
var PG_SQLSTATE_ERROR_CODES = Object.freeze({
|
|
384
483
|
"23P01": "VIBE_EXCLUSION_VIOLATION",
|
|
385
484
|
"23505": "VIBE_UNIQUE_VIOLATION",
|
|
@@ -407,7 +506,7 @@ var PG_SQLSTATE_REASONS = Object.freeze({
|
|
|
407
506
|
"40001": "serialization-failure",
|
|
408
507
|
"25006": "read-only-transaction"
|
|
409
508
|
});
|
|
410
|
-
var NAMED_STATEMENT_POOLER_HINT = "Hint: with preparedStatements: true every query must reach the backend that prepared its name. " + "Use a direct connection, session pooling, or PgBouncer 1.21+ with max_prepared_statements > 0; " + "behind a transaction-mode pooler without prepared-statement support, turn preparedStatements off.";
|
|
509
|
+
var NAMED_STATEMENT_POOLER_HINT = "Hint: with preparedStatements: true or a named raw statement, every query must reach the backend that prepared its name. " + "Use a direct connection, session pooling, or PgBouncer 1.21+ with max_prepared_statements > 0; " + "behind a transaction-mode pooler without prepared-statement support, turn preparedStatements off and send raw statements without a name.";
|
|
411
510
|
var PG_SQLSTATE_HINTS = Object.freeze({
|
|
412
511
|
"26000": NAMED_STATEMENT_POOLER_HINT,
|
|
413
512
|
"42P05": NAMED_STATEMENT_POOLER_HINT
|
|
@@ -426,7 +525,7 @@ function pgErrorSqlstate(params) {
|
|
|
426
525
|
}
|
|
427
526
|
function mapPgDriverError(params) {
|
|
428
527
|
const { error } = params;
|
|
429
|
-
if (error instanceof
|
|
528
|
+
if (error instanceof VibeError4)
|
|
430
529
|
return error;
|
|
431
530
|
const source = error !== null && typeof error === "object" ? error : {};
|
|
432
531
|
const sqlstate = pgErrorSqlstate({ error });
|
|
@@ -449,7 +548,7 @@ function mapPgDriverError(params) {
|
|
|
449
548
|
const driverMessage = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
|
|
450
549
|
const hint = sqlstate === undefined ? undefined : PG_SQLSTATE_HINTS[sqlstate];
|
|
451
550
|
const message = hint === undefined ? driverMessage : `${driverMessage}. ${hint}`;
|
|
452
|
-
return new
|
|
551
|
+
return new VibeError4({ code, message, meta, cause: error });
|
|
453
552
|
}
|
|
454
553
|
async function runMapped(fn) {
|
|
455
554
|
try {
|
|
@@ -472,14 +571,14 @@ function createPgReadCancellation(params) {
|
|
|
472
571
|
if (query.execution?.cancellation !== "running")
|
|
473
572
|
return query.send();
|
|
474
573
|
if (params.target.pipeline === true || (params.target.connectionParameters?.query_timeout ?? 0) > 0) {
|
|
475
|
-
throw new
|
|
574
|
+
throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Running cancellation requires a non-pipelined target without local query_timeout", meta: { capability: "readCancellation", reason: "target-lifetime" } });
|
|
476
575
|
}
|
|
477
576
|
const address = params.connectionString;
|
|
478
577
|
if (address === undefined)
|
|
479
|
-
throw new
|
|
578
|
+
throw new VibeError5({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
|
|
480
579
|
const token = `vibe_cancel_${crypto.randomUUID()}`;
|
|
481
580
|
if (!URL.canParse(address))
|
|
482
|
-
throw new
|
|
581
|
+
throw new VibeError5({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
|
|
483
582
|
const url = new URL(address);
|
|
484
583
|
url.searchParams.delete("query_timeout");
|
|
485
584
|
url.searchParams.set("connect_timeout", String(PG_READ_CANCELLATION.controlTimeoutMs / 1000));
|
|
@@ -526,7 +625,7 @@ function createPgReadCancellation(params) {
|
|
|
526
625
|
if (preflightAborted)
|
|
527
626
|
throw readCancelledError({ stage: "waiting-for-connection" });
|
|
528
627
|
if (witness === undefined || typeof witness.pid !== "number" || typeof witness.backend_start !== "string" || typeof witness.datname !== "string" || typeof witness.usename !== "string" || witness.tag !== token) {
|
|
529
|
-
throw new
|
|
628
|
+
throw new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
|
|
530
629
|
}
|
|
531
630
|
assertReadExecution({ execution: query.execution, effect: "read", support, provider: "postgresql", stage: "waiting-for-connection" });
|
|
532
631
|
listener = () => {
|
|
@@ -543,7 +642,7 @@ function createPgReadCancellation(params) {
|
|
|
543
642
|
throw error;
|
|
544
643
|
};
|
|
545
644
|
settlementTimer = setTimeout(() => {
|
|
546
|
-
controlFailure = new
|
|
645
|
+
controlFailure = new VibeError5({ code: "VIBE_ADAPTER", message: "PostgreSQL read did not settle after cancellation; its exact session was discarded", meta: { reason: "read-cancellation-unsettled", databaseCancelled: false } });
|
|
547
646
|
if (!terminated) {
|
|
548
647
|
terminated = true;
|
|
549
648
|
params.terminate();
|
|
@@ -555,7 +654,7 @@ function createPgReadCancellation(params) {
|
|
|
555
654
|
})).then((result) => {
|
|
556
655
|
signalSent = rowsOf(result)[0]?.signalled === true;
|
|
557
656
|
if (!signalSent)
|
|
558
|
-
failControl(new
|
|
657
|
+
failControl(new VibeError5({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "The control endpoint did not signal the exact PostgreSQL target", meta: { capability: "readCancellation", reason: "target-not-signalled", databaseCancelled: false } }));
|
|
559
658
|
}, failControl);
|
|
560
659
|
cancel.then(() => {
|
|
561
660
|
return;
|
|
@@ -586,12 +685,12 @@ function createPgReadCancellation(params) {
|
|
|
586
685
|
}, () => {
|
|
587
686
|
return;
|
|
588
687
|
});
|
|
589
|
-
const sqlstate = error instanceof
|
|
688
|
+
const sqlstate = error instanceof VibeError5 ? error.meta.sqlstate : pgErrorSqlstate({ error });
|
|
590
689
|
if (terminated && controlFailure !== undefined && sqlstate === undefined)
|
|
591
690
|
throw controlFailure;
|
|
592
691
|
if (attempted && signalSent && sqlstate === "57014") {
|
|
593
692
|
const cancelled = readCancelledError({ stage: "running", databaseCancelled: true, cause: error });
|
|
594
|
-
throw new
|
|
693
|
+
throw new VibeError5({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
|
|
595
694
|
}
|
|
596
695
|
throw error;
|
|
597
696
|
});
|
|
@@ -673,7 +772,7 @@ function serializeParamsRaw(params) {
|
|
|
673
772
|
}
|
|
674
773
|
|
|
675
774
|
// packages/adapter-pg/src/pool-config.ts
|
|
676
|
-
import { VibeError as
|
|
775
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
677
776
|
var PLAN_CACHE_MODES = ["auto", "force_custom_plan", "force_generic_plan"];
|
|
678
777
|
function isPlanCacheMode(value) {
|
|
679
778
|
return typeof value === "string" && PLAN_CACHE_MODES.includes(value);
|
|
@@ -682,7 +781,7 @@ function assertPlanCacheMode(params) {
|
|
|
682
781
|
const { value } = params;
|
|
683
782
|
if (isPlanCacheMode(value))
|
|
684
783
|
return value;
|
|
685
|
-
throw new
|
|
784
|
+
throw new VibeError6({
|
|
686
785
|
code: "VIBE_CONFIG",
|
|
687
786
|
message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `"${mode}"`).join(", ")}, received ${JSON.stringify(value)}`,
|
|
688
787
|
meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES }
|
|
@@ -712,7 +811,7 @@ function assertIdleTimeout(params) {
|
|
|
712
811
|
const { value } = params;
|
|
713
812
|
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2147483647)
|
|
714
813
|
return value;
|
|
715
|
-
throw new
|
|
814
|
+
throw new VibeError6({
|
|
716
815
|
code: "VIBE_CONFIG",
|
|
717
816
|
message: "pgAdapter `idleTimeout` must be integer milliseconds from 0 through 2,147,483,647 (0 keeps idle connections open).",
|
|
718
817
|
meta: { adapter: "pg", reason: "invalid-idle-timeout", option: "idleTimeout" }
|
|
@@ -722,7 +821,7 @@ function assertPoolMin(params) {
|
|
|
722
821
|
const { value, max } = params;
|
|
723
822
|
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max)
|
|
724
823
|
return value;
|
|
725
|
-
throw new
|
|
824
|
+
throw new VibeError6({
|
|
726
825
|
code: "VIBE_CONFIG",
|
|
727
826
|
message: `pgAdapter \`min\` must be an integer from 0 through \`max\` (${max}).`,
|
|
728
827
|
meta: { adapter: "pg", reason: "invalid-pool-min", option: "min", max }
|
|
@@ -750,6 +849,7 @@ function buildPoolConfig(params) {
|
|
|
750
849
|
|
|
751
850
|
// packages/adapter-pg/src/statement-cache.ts
|
|
752
851
|
import { createHash } from "crypto";
|
|
852
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
753
853
|
var DEFAULT_PROMOTE_AFTER = 2;
|
|
754
854
|
var SIGHTINGS_PER_NAME = 4;
|
|
755
855
|
var NAME_PREFIX = "vibeorm_";
|
|
@@ -811,6 +911,25 @@ function createStatementNameCache(params) {
|
|
|
811
911
|
}
|
|
812
912
|
};
|
|
813
913
|
}
|
|
914
|
+
function createRawStatementNames() {
|
|
915
|
+
const texts = new Map;
|
|
916
|
+
return {
|
|
917
|
+
admit(params) {
|
|
918
|
+
const known = texts.get(params.name);
|
|
919
|
+
if (known === undefined) {
|
|
920
|
+
texts.set(params.name, params.text);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (known === params.text)
|
|
924
|
+
return;
|
|
925
|
+
throw new VibeError7({
|
|
926
|
+
code: "VIBE_VALIDATION",
|
|
927
|
+
message: `statement name "${params.name}" already names another SQL text on this pool: one name means one text (give the new text its own name)`,
|
|
928
|
+
meta: { adapter: "pg", reason: "statement-name-reused", name: params.name }
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
814
933
|
|
|
815
934
|
// packages/adapter-pg/src/timestamp-text.ts
|
|
816
935
|
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -893,7 +1012,7 @@ function parsePlainTimestampText(params) {
|
|
|
893
1012
|
}
|
|
894
1013
|
|
|
895
1014
|
// packages/adapter-pg/src/transaction-sql.ts
|
|
896
|
-
import { VibeError as
|
|
1015
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
897
1016
|
import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
|
|
898
1017
|
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
899
1018
|
ReadCommitted: "READ COMMITTED",
|
|
@@ -916,7 +1035,7 @@ function openTransactionStatement(params) {
|
|
|
916
1035
|
function statementTimeoutStatement(params) {
|
|
917
1036
|
const { timeout } = params;
|
|
918
1037
|
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
919
|
-
throw new
|
|
1038
|
+
throw new VibeError8({
|
|
920
1039
|
code: "VIBE_VALIDATION",
|
|
921
1040
|
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
922
1041
|
meta: { timeout }
|
|
@@ -953,32 +1072,32 @@ import {
|
|
|
953
1072
|
validateTransactionDeadline as validateTransactionDeadline2
|
|
954
1073
|
} from "@vibeorm/runtime/adapter-kit";
|
|
955
1074
|
function laneGroupMisuse(params) {
|
|
956
|
-
return new
|
|
1075
|
+
return new VibeError9({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
|
|
957
1076
|
}
|
|
958
1077
|
var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
|
|
959
1078
|
var ignore = () => {
|
|
960
1079
|
return;
|
|
961
1080
|
};
|
|
962
1081
|
function predecessorClosedError() {
|
|
963
|
-
return new
|
|
1082
|
+
return new VibeError9({
|
|
964
1083
|
code: "VIBE_TRANSACTION",
|
|
965
1084
|
message: "pipeline(): an earlier lane of this ordered scope closed without issuing its statement, so this statement was not sent " + "(the operation before it failed before it reached the database)",
|
|
966
1085
|
meta: { reason: "pipeline-predecessor-closed" }
|
|
967
1086
|
});
|
|
968
1087
|
}
|
|
969
1088
|
function orderedLaneMisuse(params) {
|
|
970
|
-
return new
|
|
1089
|
+
return new VibeError9({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
|
|
971
1090
|
}
|
|
972
1091
|
var COMMIT_STEP = Object.freeze({ text: "COMMIT" });
|
|
973
1092
|
function commitSentRefusal() {
|
|
974
|
-
return new
|
|
1093
|
+
return new VibeError9({
|
|
975
1094
|
code: "VIBE_TRANSACTION",
|
|
976
1095
|
message: "this transaction already sent its COMMIT together with its last statement, so it accepts no further statement",
|
|
977
1096
|
meta: { provider: PROVIDER, reason: "commit-sent" }
|
|
978
1097
|
});
|
|
979
1098
|
}
|
|
980
1099
|
function finalStatementMisuse() {
|
|
981
|
-
return new
|
|
1100
|
+
return new VibeError9({
|
|
982
1101
|
code: "VIBE_INTERNAL",
|
|
983
1102
|
message: "withCommit(): a view carries ONE statement, the transaction's last",
|
|
984
1103
|
meta: { reason: "with-commit-second-statement" }
|
|
@@ -993,15 +1112,16 @@ function finalResult(params) {
|
|
|
993
1112
|
return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
|
|
994
1113
|
}
|
|
995
1114
|
var POOL_STATEMENT_NAMES = new WeakMap;
|
|
1115
|
+
var POOL_RAW_STATEMENT_NAMES = new WeakMap;
|
|
996
1116
|
function isConnectionLoss(params) {
|
|
997
1117
|
const { error } = params;
|
|
998
|
-
if (!(error instanceof
|
|
1118
|
+
if (!(error instanceof VibeError9))
|
|
999
1119
|
return true;
|
|
1000
1120
|
const sqlstate = error.meta.sqlstate;
|
|
1001
1121
|
return typeof sqlstate !== "string" || sqlstate.startsWith("08") || ["57P01", "57P02", "57P03"].includes(sqlstate);
|
|
1002
1122
|
}
|
|
1003
1123
|
function commitOutcomeUnknownError(params) {
|
|
1004
|
-
return new
|
|
1124
|
+
return new VibeError9({
|
|
1005
1125
|
code: "VIBE_TRANSACTION",
|
|
1006
1126
|
message: "the connection was lost while COMMIT was in flight, so the transaction's outcome is unknown: the write may have committed. The connection was discarded; inspect the data before retrying.",
|
|
1007
1127
|
meta: { provider: PROVIDER, reason: "transaction-outcome-unknown", outcome: "unknown", stage: "commit" },
|
|
@@ -1010,6 +1130,10 @@ function commitOutcomeUnknownError(params) {
|
|
|
1010
1130
|
}
|
|
1011
1131
|
var NEVER_REUSE_SQLSTATE_CLASSES = new Set(["08", "53", "57", "58", "XX"]);
|
|
1012
1132
|
var STALE_STATEMENT_SQLSTATES = new Set(["0A000", "26000", "42P05"]);
|
|
1133
|
+
function isStaleStatementFailure(params) {
|
|
1134
|
+
const sqlstate = mapPgDriverError({ error: params.error }).meta.sqlstate;
|
|
1135
|
+
return typeof sqlstate === "string" && STALE_STATEMENT_SQLSTATES.has(sqlstate);
|
|
1136
|
+
}
|
|
1013
1137
|
function observeReadyForQuery(params) {
|
|
1014
1138
|
const client = params.client;
|
|
1015
1139
|
let backgroundFailure = false;
|
|
@@ -1030,7 +1154,7 @@ function observeReadyForQuery(params) {
|
|
|
1030
1154
|
client.on("end", onEnd);
|
|
1031
1155
|
return {
|
|
1032
1156
|
reusableAfter: async ({ error, named }) => {
|
|
1033
|
-
if (!(error instanceof
|
|
1157
|
+
if (!(error instanceof VibeError9))
|
|
1034
1158
|
return false;
|
|
1035
1159
|
const sqlstate = error.meta.sqlstate;
|
|
1036
1160
|
if (typeof sqlstate !== "string" || NEVER_REUSE_SQLSTATE_CLASSES.has(sqlstate.slice(0, 2)))
|
|
@@ -1160,7 +1284,7 @@ function takeAddress(params) {
|
|
|
1160
1284
|
if (typeof connectionString === "string" && connectionString.trim() !== "") {
|
|
1161
1285
|
return { connectionString: honourSystemRootCert({ connectionString, option: "connectionString" }) };
|
|
1162
1286
|
}
|
|
1163
|
-
throw new
|
|
1287
|
+
throw new VibeError9({
|
|
1164
1288
|
code: "VIBE_CONFIG",
|
|
1165
1289
|
message: "pgAdapter needs an explicit address: pass `connectionString` (a PostgreSQL URL) or an existing `pool`. " + "The adapter does not read DATABASE_URL or the PG* environment variables.",
|
|
1166
1290
|
meta: { adapter: "pg", reason: "missing-address" }
|
|
@@ -1194,7 +1318,7 @@ function pgAdapter(options) {
|
|
|
1194
1318
|
const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
|
|
1195
1319
|
const idleTimeout = "pool" in address || options?.idleTimeout === undefined ? undefined : assertIdleTimeout({ value: options.idleTimeout });
|
|
1196
1320
|
if (options?.min !== undefined && "pool" in address) {
|
|
1197
|
-
throw new
|
|
1321
|
+
throw new VibeError9({
|
|
1198
1322
|
code: "VIBE_CONFIG",
|
|
1199
1323
|
message: "pgAdapter `min` configures the pool the adapter builds \u2014 it cannot be applied to a caller-supplied `pool`; set `min` (and `idleTimeoutMillis`) on that pool instead",
|
|
1200
1324
|
meta: { adapter: "pg", reason: "pool-min-with-injected-pool", option: "min" }
|
|
@@ -1202,7 +1326,7 @@ function pgAdapter(options) {
|
|
|
1202
1326
|
}
|
|
1203
1327
|
const poolMin = options?.min === undefined ? undefined : assertPoolMin({ value: options.min, max: options.max ?? 10 });
|
|
1204
1328
|
if (planCacheMode !== "auto" && "pool" in address) {
|
|
1205
|
-
throw new
|
|
1329
|
+
throw new VibeError9({
|
|
1206
1330
|
code: "VIBE_CONFIG",
|
|
1207
1331
|
message: "planCacheMode configures the pool the adapter builds \u2014 it cannot be applied to a caller-supplied `pool`; " + 'set `options: "-c plan_cache_mode=\u2026"` on that pool instead',
|
|
1208
1332
|
meta: { planCacheMode }
|
|
@@ -1244,6 +1368,14 @@ function pgAdapter(options) {
|
|
|
1244
1368
|
POOL_STATEMENT_NAMES.set(params.pool, created);
|
|
1245
1369
|
return created;
|
|
1246
1370
|
}
|
|
1371
|
+
function rawStatementNamesFor(params) {
|
|
1372
|
+
const existing = POOL_RAW_STATEMENT_NAMES.get(params.pool);
|
|
1373
|
+
if (existing !== undefined)
|
|
1374
|
+
return existing;
|
|
1375
|
+
const created = createRawStatementNames();
|
|
1376
|
+
POOL_RAW_STATEMENT_NAMES.set(params.pool, created);
|
|
1377
|
+
return created;
|
|
1378
|
+
}
|
|
1247
1379
|
function buildQueryConfig(params) {
|
|
1248
1380
|
const values = serializeParams({ values: params.values });
|
|
1249
1381
|
if (!usePrepared)
|
|
@@ -1267,8 +1399,12 @@ function pgAdapter(options) {
|
|
|
1267
1399
|
return finalResult({ result }).rows;
|
|
1268
1400
|
}
|
|
1269
1401
|
async function runExecuteUnsafe(params) {
|
|
1402
|
+
const name = params.name === undefined ? undefined : assertStatementName({ name: params.name, method: "executeUnsafe" });
|
|
1403
|
+
if (name !== undefined)
|
|
1404
|
+
rawStatementNamesFor({ pool: getPool() }).admit({ name, text: params.text });
|
|
1270
1405
|
const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
|
|
1271
|
-
const
|
|
1406
|
+
const config = name === undefined ? { text: params.text, values, types: EXACT_TYPES } : { name, text: params.text, values, types: EXACT_TYPES };
|
|
1407
|
+
const result = await runMapped(() => params.queryable.query(config));
|
|
1272
1408
|
observeResult({ scope: params.scope, effect: params.effect, result });
|
|
1273
1409
|
const final = finalResult({ result });
|
|
1274
1410
|
return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
|
|
@@ -1286,7 +1422,13 @@ function pgAdapter(options) {
|
|
|
1286
1422
|
};
|
|
1287
1423
|
const waitAsLane = (wait) => lane === undefined || pipelines === null ? wait : pipelines.waitAs({ lane, wait });
|
|
1288
1424
|
const rawClient = {
|
|
1289
|
-
query: (config) => dispatch({
|
|
1425
|
+
query: (config) => dispatch({
|
|
1426
|
+
config,
|
|
1427
|
+
batchable: (config.values?.length ?? 0) > 0 || config.name !== undefined || isSingleStatementText({ text: config.text }),
|
|
1428
|
+
...config.name === undefined ? {} : { name: config.name },
|
|
1429
|
+
...laneOf,
|
|
1430
|
+
...finalOf()
|
|
1431
|
+
})
|
|
1290
1432
|
};
|
|
1291
1433
|
const savepointControl = (statement, cleanup) => {
|
|
1292
1434
|
if (deferred !== null && deferred.routeSavepoint({ text: statement, cleanup }) !== "send")
|
|
@@ -1337,10 +1479,10 @@ function pgAdapter(options) {
|
|
|
1337
1479
|
if (lane?.group !== undefined)
|
|
1338
1480
|
throw laneGroupMisuse({ message: "a lane opens no pipeline scope while its group is open" });
|
|
1339
1481
|
if (!Number.isSafeInteger(count) || count < 1) {
|
|
1340
|
-
throw new
|
|
1482
|
+
throw new VibeError9({ code: "VIBE_VALIDATION", message: "pipeline() needs a positive whole number of lanes", meta: { lanes: count } });
|
|
1341
1483
|
}
|
|
1342
1484
|
if (ordered === true && params.ordered !== true) {
|
|
1343
|
-
throw new
|
|
1485
|
+
throw new VibeError9({
|
|
1344
1486
|
code: "VIBE_VALIDATION",
|
|
1345
1487
|
message: "pipeline({ ordered: true }) is offered only on a handle that declares orderedPipeline (the top-level or a savepoint handle of a transaction without a deadline, outside a pipeline lane)",
|
|
1346
1488
|
meta: { ordered: true, reason: "ordered-pipeline-undeclared" }
|
|
@@ -1610,7 +1752,10 @@ function pgAdapter(options) {
|
|
|
1610
1752
|
const settleRidden = (entry, outcome, commitIndex) => {
|
|
1611
1753
|
const step = outcome.steps[commitIndex];
|
|
1612
1754
|
const answeredError = step?.status === "failed" ? mapPgDriverError({ error: step.error }) : undefined;
|
|
1613
|
-
if (step === undefined
|
|
1755
|
+
if (step === undefined && outcome.unsent === true) {
|
|
1756
|
+
commitProgress = "not-sent";
|
|
1757
|
+
entry.state = "skipped";
|
|
1758
|
+
} else if (step === undefined || answeredError !== undefined && isConnectionLoss({ error: answeredError })) {
|
|
1614
1759
|
commitProgress = "unanswered";
|
|
1615
1760
|
discard = true;
|
|
1616
1761
|
budget.close({ outcome: "unknown" });
|
|
@@ -1675,7 +1820,12 @@ function pgAdapter(options) {
|
|
|
1675
1820
|
return gatherInTurn(dispatchParams);
|
|
1676
1821
|
return handOver();
|
|
1677
1822
|
}
|
|
1678
|
-
const statement = dispatchParams.batchable === true ? [{
|
|
1823
|
+
const statement = dispatchParams.batchable === true ? [{
|
|
1824
|
+
text: config.text,
|
|
1825
|
+
values: prepareBatchValues({ values: config.values ?? [] }),
|
|
1826
|
+
...dispatchParams.name === undefined ? {} : { name: dispatchParams.name },
|
|
1827
|
+
...rides && dispatchParams.discardRows === true ? { discardRows: true } : {}
|
|
1828
|
+
}] : [];
|
|
1679
1829
|
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1680
1830
|
const carriesOpening = !deferred.opened();
|
|
1681
1831
|
const control = pendingControl ? [...deferred.takeSteps()] : [];
|
|
@@ -1826,7 +1976,11 @@ function pgAdapter(options) {
|
|
|
1826
1976
|
const control = inTurn ? [] : deferred.takeSteps();
|
|
1827
1977
|
const openingSteps = carriesOpening ? openingLength : 0;
|
|
1828
1978
|
const commitIndex = control.length + live.length;
|
|
1829
|
-
const sendBatch = () => runBatch({
|
|
1979
|
+
const sendBatch = () => runBatch({
|
|
1980
|
+
client,
|
|
1981
|
+
steps: [...control, ...live.map((entry) => entry.name === undefined ? { text: entry.text, values: entry.values } : { text: entry.text, values: entry.values, name: entry.name }), ...rides ? [COMMIT_STEP] : []],
|
|
1982
|
+
types: EXACT_TYPES
|
|
1983
|
+
});
|
|
1830
1984
|
const batch = inTurn ? new Promise((resolve) => {
|
|
1831
1985
|
resolve(sendBatch());
|
|
1832
1986
|
}) : runTurn(sendBatch);
|
|
@@ -1862,6 +2016,7 @@ function pgAdapter(options) {
|
|
|
1862
2016
|
return {
|
|
1863
2017
|
text: member.params.config.text,
|
|
1864
2018
|
values: member.values,
|
|
2019
|
+
...member.params.name === undefined ? {} : { name: member.params.name },
|
|
1865
2020
|
...member.params.execution === undefined || options?.inTurn === true ? {} : { execution: member.params.execution },
|
|
1866
2021
|
lane: gatheredLane,
|
|
1867
2022
|
resolve: member.resolve,
|
|
@@ -2184,6 +2339,7 @@ function pgAdapter(options) {
|
|
|
2184
2339
|
queued.push({
|
|
2185
2340
|
text: dispatchParams.config.text,
|
|
2186
2341
|
values,
|
|
2342
|
+
...dispatchParams.name === undefined ? {} : { name: dispatchParams.name },
|
|
2187
2343
|
...dispatchParams.execution === undefined ? {} : { execution: dispatchParams.execution },
|
|
2188
2344
|
...alone === true ? { alone: dispatchParams } : {},
|
|
2189
2345
|
...dispatchParams.commit === true ? { commit: true } : {},
|
|
@@ -2201,7 +2357,18 @@ function pgAdapter(options) {
|
|
|
2201
2357
|
return pending;
|
|
2202
2358
|
};
|
|
2203
2359
|
const coalescable = (dispatchParams) => capable && budget.totalMs === null && !cancellationQueue && !((client.connectionParameters?.query_timeout ?? 0) > 0) && dispatchParams.batchable === true && dispatchParams.cleanup !== true && dispatchParams.stage === undefined && dispatchParams.sent === undefined && dispatchParams.execution?.cancellation !== "running";
|
|
2360
|
+
let staleNamedStatement = false;
|
|
2204
2361
|
const dispatch = (dispatchParams) => {
|
|
2362
|
+
const dispatched = route(dispatchParams);
|
|
2363
|
+
if (dispatchParams.config.name === undefined)
|
|
2364
|
+
return dispatched;
|
|
2365
|
+
return dispatched.then(undefined, (error) => {
|
|
2366
|
+
if (isStaleStatementFailure({ error }))
|
|
2367
|
+
staleNamedStatement = true;
|
|
2368
|
+
throw error;
|
|
2369
|
+
});
|
|
2370
|
+
};
|
|
2371
|
+
const route = (dispatchParams) => {
|
|
2205
2372
|
const lane = dispatchParams.lane?.open === true ? dispatchParams.lane : undefined;
|
|
2206
2373
|
if (lane !== undefined && dispatchParams.cleanup !== true) {
|
|
2207
2374
|
if (lane.group !== undefined)
|
|
@@ -2416,12 +2583,12 @@ function pgAdapter(options) {
|
|
|
2416
2583
|
scope,
|
|
2417
2584
|
commit: commitProgress,
|
|
2418
2585
|
rollback,
|
|
2419
|
-
connection: discard ? "discarded" : "returned"
|
|
2586
|
+
connection: discard || staleNamedStatement ? "discarded" : "returned"
|
|
2420
2587
|
});
|
|
2421
2588
|
} finally {
|
|
2422
2589
|
unguard();
|
|
2423
2590
|
if (!released)
|
|
2424
|
-
client.release(discard);
|
|
2591
|
+
client.release(discard || staleNamedStatement);
|
|
2425
2592
|
}
|
|
2426
2593
|
},
|
|
2427
2594
|
async connect() {
|
|
@@ -2548,4 +2715,4 @@ export {
|
|
|
2548
2715
|
validateTransactionDeadline2 as validateTransactionDeadline
|
|
2549
2716
|
};
|
|
2550
2717
|
|
|
2551
|
-
//# debugId=
|
|
2718
|
+
//# debugId=7B0EC2D2FB69EE3264756E2164756E21
|