@vibeorm/adapter-pg 2.4.1 → 4.0.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/LICENSE +21 -0
- package/README.md +13 -9
- package/dist/batch.d.ts +152 -0
- package/dist/index.d.ts +29 -7
- package/dist/index.js +493 -95
- package/dist/index.js.map +7 -6
- package/dist/transaction-sql.d.ts +22 -1
- package/package.json +6 -4
- package/dist/errors.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/pool-config.d.ts.map +0 -1
- package/dist/statement-cache.d.ts.map +0 -1
- package/dist/transaction-sql.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,24 +2,181 @@
|
|
|
2
2
|
// packages/adapter-pg/src/index.ts
|
|
3
3
|
import {
|
|
4
4
|
abortTransactionOnServerFailure,
|
|
5
|
+
createDeferredControl,
|
|
5
6
|
observeRowChanges,
|
|
6
7
|
readRowChanges,
|
|
7
8
|
statementRowChanges,
|
|
9
|
+
createAdapterLifecycleGate,
|
|
8
10
|
runPinnedSession,
|
|
11
|
+
runShutdownCleanup,
|
|
12
|
+
transactionLifecycleRefusal,
|
|
9
13
|
createSavepointScope,
|
|
10
14
|
engineStatementBudgetMs,
|
|
11
15
|
isSavepointRollbackFailedError,
|
|
12
|
-
|
|
16
|
+
readOnlyRequested,
|
|
13
17
|
runNestedSavepoint,
|
|
14
18
|
runRootScope,
|
|
15
19
|
scopeTurn,
|
|
20
|
+
settleFailedTransaction,
|
|
16
21
|
startTransactionBudget,
|
|
17
22
|
transactionAbortedError,
|
|
18
|
-
transactionOutcomeUnknownError,
|
|
19
23
|
validateTransactionDeadline
|
|
20
24
|
} from "@vibeorm/runtime/adapter-kit";
|
|
21
25
|
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
26
|
+
import pg2 from "pg";
|
|
27
|
+
|
|
28
|
+
// packages/adapter-pg/src/batch.ts
|
|
22
29
|
import pg from "pg";
|
|
30
|
+
var prepareValue = (() => {
|
|
31
|
+
const utils = pg.utils;
|
|
32
|
+
return typeof utils?.prepareValue === "function" ? utils.prepareValue : undefined;
|
|
33
|
+
})();
|
|
34
|
+
function supportsBatch(params) {
|
|
35
|
+
if (prepareValue === undefined)
|
|
36
|
+
return false;
|
|
37
|
+
const client = params.client;
|
|
38
|
+
if (client === null || typeof client !== "object" || client.pipeline === true)
|
|
39
|
+
return false;
|
|
40
|
+
const connection = client.connection;
|
|
41
|
+
return connection !== null && typeof connection === "object" && typeof connection.parse === "function" && typeof connection.bind === "function" && typeof connection.describe === "function" && typeof connection.execute === "function" && typeof connection.sync === "function";
|
|
42
|
+
}
|
|
43
|
+
function prepareBatchValues(params) {
|
|
44
|
+
const prepare = prepareValue;
|
|
45
|
+
return params.values.map((value) => prepare(value));
|
|
46
|
+
}
|
|
47
|
+
var COMMAND_TAG = /^([A-Za-z]+)(?: (\d+))?(?: (\d+))?/;
|
|
48
|
+
function isServerError(error) {
|
|
49
|
+
if (typeof error !== "object" || error === null)
|
|
50
|
+
return false;
|
|
51
|
+
const candidate = error;
|
|
52
|
+
return typeof candidate.code === "string" && /^[0-9A-Z]{5}$/.test(candidate.code) && typeof candidate.severity === "string";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class PgBatch {
|
|
56
|
+
done;
|
|
57
|
+
callback = undefined;
|
|
58
|
+
steps;
|
|
59
|
+
types;
|
|
60
|
+
outcomes = [];
|
|
61
|
+
parsers = [];
|
|
62
|
+
fields = [];
|
|
63
|
+
rows = [];
|
|
64
|
+
rowError = undefined;
|
|
65
|
+
settled = false;
|
|
66
|
+
resolve = () => {
|
|
67
|
+
return;
|
|
68
|
+
};
|
|
69
|
+
constructor(params) {
|
|
70
|
+
this.steps = params.steps;
|
|
71
|
+
this.types = params.types;
|
|
72
|
+
this.done = new Promise((resolve) => {
|
|
73
|
+
this.resolve = resolve;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
submit(connection) {
|
|
77
|
+
const writer = connection;
|
|
78
|
+
writer.stream?.cork?.();
|
|
79
|
+
try {
|
|
80
|
+
for (const step of this.steps) {
|
|
81
|
+
writer.parse({ text: step.text, types: [] });
|
|
82
|
+
writer.bind({ values: step.values ?? [] });
|
|
83
|
+
writer.describe({ type: "P", name: "" });
|
|
84
|
+
writer.execute({ portal: "", rows: 0 });
|
|
85
|
+
}
|
|
86
|
+
writer.sync();
|
|
87
|
+
} finally {
|
|
88
|
+
writer.stream?.uncork?.();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
handleRowDescription(message) {
|
|
92
|
+
this.fields = message.fields;
|
|
93
|
+
this.parsers = message.fields.map((field) => this.types.getTypeParser(field.dataTypeID, field.format ?? "text"));
|
|
94
|
+
this.rows = [];
|
|
95
|
+
}
|
|
96
|
+
handleDataRow(message) {
|
|
97
|
+
if (this.settled || this.rowError !== undefined)
|
|
98
|
+
return;
|
|
99
|
+
const row = {};
|
|
100
|
+
try {
|
|
101
|
+
for (let index = 0;index < this.fields.length; index++) {
|
|
102
|
+
const raw = message.fields[index] ?? null;
|
|
103
|
+
row[this.fields[index].name] = raw === null ? null : this.parsers[index](raw);
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.rowError = { error };
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.rows.push(row);
|
|
110
|
+
}
|
|
111
|
+
handleCommandComplete(message) {
|
|
112
|
+
if (this.settled)
|
|
113
|
+
return;
|
|
114
|
+
if (this.rowError !== undefined) {
|
|
115
|
+
this.outcomes.push({ status: "failed", error: this.rowError.error });
|
|
116
|
+
this.rowError = undefined;
|
|
117
|
+
this.rows = [];
|
|
118
|
+
this.fields = [];
|
|
119
|
+
this.parsers = [];
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const match = COMMAND_TAG.exec(message.text ?? message.command ?? "");
|
|
123
|
+
const command = match?.[1];
|
|
124
|
+
const rowCount = match?.[3] !== undefined ? Number.parseInt(match[3], 10) : match?.[2] !== undefined ? Number.parseInt(match[2], 10) : null;
|
|
125
|
+
this.outcomes.push({ status: "ok", result: { rows: this.rows, rowCount, ...command === undefined ? {} : { command } } });
|
|
126
|
+
this.rows = [];
|
|
127
|
+
this.fields = [];
|
|
128
|
+
this.parsers = [];
|
|
129
|
+
}
|
|
130
|
+
handleEmptyQuery() {
|
|
131
|
+
if (this.settled)
|
|
132
|
+
return;
|
|
133
|
+
this.outcomes.push({ status: "ok", result: { rows: [], rowCount: null } });
|
|
134
|
+
}
|
|
135
|
+
handleError(error) {
|
|
136
|
+
if (this.settled)
|
|
137
|
+
return;
|
|
138
|
+
if (!isServerError(error)) {
|
|
139
|
+
this.finish({ transportError: error });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.outcomes.push({ status: "failed", error: this.rowError === undefined ? error : this.rowError.error });
|
|
143
|
+
while (this.outcomes.length < this.steps.length)
|
|
144
|
+
this.outcomes.push({ status: "skipped" });
|
|
145
|
+
this.finish({});
|
|
146
|
+
}
|
|
147
|
+
handleReadyForQuery() {
|
|
148
|
+
this.finish({});
|
|
149
|
+
}
|
|
150
|
+
handlePortalSuspended() {}
|
|
151
|
+
handleCopyInResponse(connection) {
|
|
152
|
+
connection.sendCopyFail?.("COPY FROM STDIN is not supported inside a batch");
|
|
153
|
+
}
|
|
154
|
+
handleCopyData() {}
|
|
155
|
+
finish(params) {
|
|
156
|
+
if (this.settled)
|
|
157
|
+
return;
|
|
158
|
+
this.settled = true;
|
|
159
|
+
this.callback?.(params.transportError ?? null);
|
|
160
|
+
this.resolve({
|
|
161
|
+
steps: this.outcomes,
|
|
162
|
+
...params.transportError === undefined ? {} : { transportError: params.transportError }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function runBatch(params) {
|
|
167
|
+
const batch = new PgBatch({ steps: params.steps, types: params.types });
|
|
168
|
+
params.client.query.call(params.client, batch);
|
|
169
|
+
return batch.done;
|
|
170
|
+
}
|
|
171
|
+
function firstBatchFailure(params) {
|
|
172
|
+
const { outcome } = params;
|
|
173
|
+
const index = outcome.steps.findIndex((step) => step.status === "failed");
|
|
174
|
+
if (index !== -1)
|
|
175
|
+
return { index, error: outcome.steps[index].error };
|
|
176
|
+
if (outcome.transportError !== undefined)
|
|
177
|
+
return { index: outcome.steps.length, error: outcome.transportError };
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
23
180
|
|
|
24
181
|
// packages/adapter-pg/src/errors.ts
|
|
25
182
|
import { VibeError } from "@vibeorm/schema";
|
|
@@ -39,16 +196,18 @@ var PG_SQLSTATE_ERROR_CODES = Object.freeze({
|
|
|
39
196
|
"40001": "VIBE_TRANSACTION",
|
|
40
197
|
"40P01": "VIBE_TRANSACTION",
|
|
41
198
|
"57014": "VIBE_TRANSACTION",
|
|
42
|
-
"55P03": "VIBE_LOCK_NOT_AVAILABLE"
|
|
199
|
+
"55P03": "VIBE_LOCK_NOT_AVAILABLE",
|
|
200
|
+
"25006": "VIBE_TRANSACTION"
|
|
43
201
|
});
|
|
44
202
|
var PG_SQLSTATE_REASONS = Object.freeze({
|
|
45
203
|
"23P01": "exclusion-violation",
|
|
46
204
|
"57014": "timeout",
|
|
47
205
|
"55P03": "lock-not-available",
|
|
48
206
|
"40P01": "deadlock",
|
|
49
|
-
"40001": "serialization-failure"
|
|
207
|
+
"40001": "serialization-failure",
|
|
208
|
+
"25006": "read-only-transaction"
|
|
50
209
|
});
|
|
51
|
-
var SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
|
|
210
|
+
var SQLSTATE_PATTERN = /^(?=[A-Z]*\d)[0-9A-Z]{5}$/;
|
|
52
211
|
function readStringField(params) {
|
|
53
212
|
const value = params.source[params.key];
|
|
54
213
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
@@ -247,8 +406,17 @@ var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
|
247
406
|
Serializable: "SERIALIZABLE"
|
|
248
407
|
});
|
|
249
408
|
function beginStatement(params) {
|
|
250
|
-
const { isolationLevel } = params;
|
|
251
|
-
|
|
409
|
+
const { isolationLevel, readOnly } = params;
|
|
410
|
+
const modes = [];
|
|
411
|
+
if (isolationLevel !== undefined)
|
|
412
|
+
modes.push(`ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`);
|
|
413
|
+
if (readOnly === true)
|
|
414
|
+
modes.push("READ ONLY");
|
|
415
|
+
return modes.length === 0 ? "BEGIN" : `BEGIN ${modes.join(", ")}`;
|
|
416
|
+
}
|
|
417
|
+
function openTransactionStatement(params) {
|
|
418
|
+
const begin = beginStatement(params);
|
|
419
|
+
return params.timeout === undefined ? begin : `${begin}; ${statementTimeoutStatement({ timeout: params.timeout })}`;
|
|
252
420
|
}
|
|
253
421
|
function statementTimeoutStatement(params) {
|
|
254
422
|
const { timeout } = params;
|
|
@@ -276,13 +444,17 @@ import { createSavepointCounter, nextSavepointName } from "@vibeorm/runtime/adap
|
|
|
276
444
|
import {
|
|
277
445
|
DEFAULT_DEADLINE_CLOCK,
|
|
278
446
|
engineStatementBudgetMs as engineStatementBudgetMs2,
|
|
279
|
-
isTransactionDeadlineError
|
|
447
|
+
isTransactionDeadlineError,
|
|
280
448
|
startTransactionBudget as startTransactionBudget2,
|
|
281
449
|
transactionClosedError,
|
|
282
450
|
transactionDeadlineError,
|
|
283
|
-
transactionOutcomeUnknownError
|
|
451
|
+
transactionOutcomeUnknownError,
|
|
284
452
|
validateTransactionDeadline as validateTransactionDeadline2
|
|
285
453
|
} from "@vibeorm/runtime/adapter-kit";
|
|
454
|
+
var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
|
|
455
|
+
var ignore = () => {
|
|
456
|
+
return;
|
|
457
|
+
};
|
|
286
458
|
function finalResult(params) {
|
|
287
459
|
return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
|
|
288
460
|
}
|
|
@@ -302,13 +474,58 @@ function commitOutcomeUnknownError(params) {
|
|
|
302
474
|
cause: params.cause
|
|
303
475
|
});
|
|
304
476
|
}
|
|
477
|
+
var NEVER_REUSE_SQLSTATE_CLASSES = new Set(["08", "53", "57", "58", "XX"]);
|
|
478
|
+
var STALE_STATEMENT_SQLSTATES = new Set(["0A000", "26000", "42P05"]);
|
|
479
|
+
function observeReadyForQuery(params) {
|
|
480
|
+
const client = params.client;
|
|
481
|
+
let backgroundFailure = false;
|
|
482
|
+
let settle = () => {
|
|
483
|
+
return;
|
|
484
|
+
};
|
|
485
|
+
const firstEvent = new Promise((resolve) => {
|
|
486
|
+
settle = resolve;
|
|
487
|
+
});
|
|
488
|
+
const onDrain = () => settle("drain");
|
|
489
|
+
const onError = () => {
|
|
490
|
+
backgroundFailure = true;
|
|
491
|
+
settle("error");
|
|
492
|
+
};
|
|
493
|
+
const onEnd = () => settle("end");
|
|
494
|
+
client.on("error", onError);
|
|
495
|
+
client.on("drain", onDrain);
|
|
496
|
+
client.on("end", onEnd);
|
|
497
|
+
return {
|
|
498
|
+
reusableAfter: async ({ error, named }) => {
|
|
499
|
+
if (!(error instanceof VibeError4))
|
|
500
|
+
return false;
|
|
501
|
+
const sqlstate = error.meta.sqlstate;
|
|
502
|
+
if (typeof sqlstate !== "string" || NEVER_REUSE_SQLSTATE_CLASSES.has(sqlstate.slice(0, 2)))
|
|
503
|
+
return false;
|
|
504
|
+
if (named && STALE_STATEMENT_SQLSTATES.has(sqlstate))
|
|
505
|
+
return false;
|
|
506
|
+
const event = await firstEvent;
|
|
507
|
+
return event === "drain" && !backgroundFailure && client.getTransactionStatus() === "I";
|
|
508
|
+
},
|
|
509
|
+
detach: () => {
|
|
510
|
+
client.removeListener("error", onError);
|
|
511
|
+
client.removeListener("drain", onDrain);
|
|
512
|
+
client.removeListener("end", onEnd);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function hasReadyForQuerySeam(params) {
|
|
517
|
+
const client = params.client;
|
|
518
|
+
return typeof client.on === "function" && typeof client.removeListener === "function" && typeof client.once === "function" && typeof client.getTransactionStatus === "function" && client.pipeline !== true;
|
|
519
|
+
}
|
|
305
520
|
function guardBackgroundErrors(params) {
|
|
306
|
-
const { client } = params;
|
|
521
|
+
const { client, onError } = params;
|
|
307
522
|
if (typeof client.on !== "function")
|
|
308
523
|
return () => {
|
|
309
524
|
return;
|
|
310
525
|
};
|
|
311
|
-
const swallow = () => {
|
|
526
|
+
const swallow = (error) => {
|
|
527
|
+
onError?.(error);
|
|
528
|
+
};
|
|
312
529
|
client.on("error", swallow);
|
|
313
530
|
return () => {
|
|
314
531
|
client.removeListener?.("error", swallow);
|
|
@@ -317,10 +534,12 @@ function guardBackgroundErrors(params) {
|
|
|
317
534
|
var DIALECT = "postgres";
|
|
318
535
|
var PROVIDER = "postgresql";
|
|
319
536
|
var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
537
|
+
var ROUND_TRIPS = "network";
|
|
320
538
|
var BUDGETS = Object.freeze({
|
|
321
539
|
acquireTimeout: true,
|
|
322
540
|
statementTimeout: "all-statements",
|
|
323
|
-
transactionDeadline: "cancel-running-statements"
|
|
541
|
+
transactionDeadline: "cancel-running-statements",
|
|
542
|
+
accessModes: Object.freeze(["readOnly"])
|
|
324
543
|
});
|
|
325
544
|
var NUMERIC_ARRAY_OID = 1231;
|
|
326
545
|
var TEXT_ARRAY_OID = 1009;
|
|
@@ -328,7 +547,7 @@ var TIMESTAMP_OID = 1114;
|
|
|
328
547
|
var TIMESTAMP_ARRAY_OID = 1115;
|
|
329
548
|
var DATE_OID = 1082;
|
|
330
549
|
var DATE_ARRAY_OID = 1182;
|
|
331
|
-
var driverTypeParser =
|
|
550
|
+
var driverTypeParser = pg2.types.getTypeParser;
|
|
332
551
|
var ZONE_LESS_TEXT = /^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)?$/;
|
|
333
552
|
function parseZoneLessAsUtc(value) {
|
|
334
553
|
if (ZONE_LESS_TEXT.test(value)) {
|
|
@@ -362,11 +581,25 @@ var EXACT_TYPES = {
|
|
|
362
581
|
return driverTypeParser(oid, format);
|
|
363
582
|
}
|
|
364
583
|
};
|
|
584
|
+
function takeAddress(params) {
|
|
585
|
+
const options = params.options;
|
|
586
|
+
if (options?.pool !== undefined && options.pool !== null)
|
|
587
|
+
return { pool: options.pool };
|
|
588
|
+
const connectionString = options?.connectionString;
|
|
589
|
+
if (typeof connectionString === "string" && connectionString.trim() !== "")
|
|
590
|
+
return { connectionString };
|
|
591
|
+
throw new VibeError4({
|
|
592
|
+
code: "VIBE_CONFIG",
|
|
593
|
+
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.",
|
|
594
|
+
meta: { adapter: "pg", reason: "missing-address" }
|
|
595
|
+
});
|
|
596
|
+
}
|
|
365
597
|
function pgAdapter(options) {
|
|
598
|
+
const address = takeAddress({ options });
|
|
366
599
|
const usePrepared = options?.preparedStatements ?? false;
|
|
367
600
|
const stmtCacheMax = options?.stmtCacheMax ?? 1000;
|
|
368
601
|
const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
|
|
369
|
-
if (planCacheMode !== "auto" &&
|
|
602
|
+
if (planCacheMode !== "auto" && "pool" in address) {
|
|
370
603
|
throw new VibeError4({
|
|
371
604
|
code: "VIBE_CONFIG",
|
|
372
605
|
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',
|
|
@@ -377,22 +610,23 @@ function pgAdapter(options) {
|
|
|
377
610
|
let ownsPool = false;
|
|
378
611
|
let rawSession = null;
|
|
379
612
|
let rawSessionUnguard = null;
|
|
613
|
+
let rootViaPoolQuery = false;
|
|
380
614
|
function getPool() {
|
|
381
615
|
if (pool !== null)
|
|
382
616
|
return pool;
|
|
383
|
-
if (
|
|
384
|
-
pool =
|
|
617
|
+
if ("pool" in address) {
|
|
618
|
+
pool = address.pool;
|
|
385
619
|
ownsPool = false;
|
|
386
620
|
return pool;
|
|
387
621
|
}
|
|
388
622
|
const config = buildPoolConfig({
|
|
389
|
-
connectionString:
|
|
623
|
+
connectionString: address.connectionString,
|
|
390
624
|
max: options?.max ?? 10,
|
|
391
625
|
...options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout },
|
|
392
626
|
...options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout },
|
|
393
627
|
planCacheMode
|
|
394
628
|
});
|
|
395
|
-
const PoolCtor =
|
|
629
|
+
const PoolCtor = pg2.Pool;
|
|
396
630
|
pool = new PoolCtor(config);
|
|
397
631
|
pool.on?.("error", () => {});
|
|
398
632
|
ownsPool = true;
|
|
@@ -420,7 +654,7 @@ function pgAdapter(options) {
|
|
|
420
654
|
if (results.length === 0)
|
|
421
655
|
observeRowChanges({ scope: params.scope, count: statementRowChanges({ effect: params.effect }) });
|
|
422
656
|
for (const result of results)
|
|
423
|
-
observeRowChanges({ scope: params.scope, count: statementRowChanges({ effect: params.effect, count: result.rowCount }) });
|
|
657
|
+
observeRowChanges({ scope: params.scope, count: statementRowChanges({ effect: params.effect, count: result.rowCount, rows: result.rows?.length, resultRows: result.rows }) });
|
|
424
658
|
}
|
|
425
659
|
async function runExecute(params) {
|
|
426
660
|
const config = buildQueryConfig({ text: params.text, values: params.values });
|
|
@@ -436,13 +670,25 @@ function pgAdapter(options) {
|
|
|
436
670
|
return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
|
|
437
671
|
}
|
|
438
672
|
function createClientAdapter(params) {
|
|
439
|
-
const { dispatch, scope, budget } = params;
|
|
440
|
-
const client = { query: (config) => dispatch({ config }) };
|
|
673
|
+
const { dispatch, scope, budget, deferred } = params;
|
|
674
|
+
const client = { query: (config) => dispatch({ config, batchable: true }) };
|
|
675
|
+
const rawClient = { query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 }) };
|
|
676
|
+
const savepointControl = (statement, cleanup) => {
|
|
677
|
+
if (deferred !== null && deferred.routeSavepoint({ text: statement }) !== "send")
|
|
678
|
+
return;
|
|
679
|
+
return runMapped(() => dispatch({
|
|
680
|
+
config: { text: statement },
|
|
681
|
+
stage: "nested",
|
|
682
|
+
batchable: true,
|
|
683
|
+
...cleanup ? { cleanup: true } : {}
|
|
684
|
+
}));
|
|
685
|
+
};
|
|
441
686
|
return {
|
|
442
687
|
dialect: DIALECT,
|
|
443
688
|
provider: PROVIDER,
|
|
444
689
|
wire: WIRE,
|
|
445
690
|
budgets: BUDGETS,
|
|
691
|
+
roundTrips: ROUND_TRIPS,
|
|
446
692
|
rowChanges: () => {
|
|
447
693
|
budget.assertUsable({ stage: "statement" });
|
|
448
694
|
return readRowChanges({ scope });
|
|
@@ -459,33 +705,61 @@ function pgAdapter(options) {
|
|
|
459
705
|
if (turn !== undefined)
|
|
460
706
|
await turn;
|
|
461
707
|
budget.assertUsable({ stage: "statement" });
|
|
462
|
-
return runExecuteUnsafe({ queryable:
|
|
708
|
+
return runExecuteUnsafe({ queryable: rawClient, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
|
|
463
709
|
},
|
|
464
710
|
async transaction(fn, nestedOptions) {
|
|
465
711
|
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
466
712
|
budget.assertUsable({ stage: "nested" });
|
|
467
713
|
return runNestedSavepoint({
|
|
468
714
|
scope,
|
|
469
|
-
execute: (statement) =>
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
cleanup: statement.startsWith("ROLLBACK TO SAVEPOINT ")
|
|
473
|
-
})),
|
|
474
|
-
body: (child) => fn(createClientAdapter({ dispatch, scope: child, budget }))
|
|
715
|
+
execute: (statement) => savepointControl(statement, false),
|
|
716
|
+
cleanup: (statement) => savepointControl(statement, true),
|
|
717
|
+
body: (child) => fn(createClientAdapter({ dispatch, scope: child, budget, deferred }))
|
|
475
718
|
});
|
|
476
719
|
},
|
|
477
|
-
async connect() {
|
|
478
|
-
|
|
720
|
+
async connect() {
|
|
721
|
+
budget.assertUsable({ stage: "statement" });
|
|
722
|
+
throw transactionLifecycleRefusal({ method: "connect" });
|
|
723
|
+
},
|
|
724
|
+
async disconnect() {
|
|
725
|
+
budget.assertUsable({ stage: "statement" });
|
|
726
|
+
throw transactionLifecycleRefusal({ method: "disconnect" });
|
|
727
|
+
},
|
|
479
728
|
formatArrayParam(values) {
|
|
480
729
|
return new PgArrayParam(values);
|
|
481
730
|
}
|
|
482
731
|
};
|
|
483
732
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
733
|
+
function closeOwnedResources() {
|
|
734
|
+
return runShutdownCleanup({
|
|
735
|
+
steps: [
|
|
736
|
+
async () => {
|
|
737
|
+
if (rawSession === null)
|
|
738
|
+
return;
|
|
739
|
+
const session = rawSession;
|
|
740
|
+
const unguard = rawSessionUnguard;
|
|
741
|
+
rawSession = null;
|
|
742
|
+
rawSessionUnguard = null;
|
|
743
|
+
await runMapped(() => session.query({ text: "ROLLBACK" })).then(() => {
|
|
744
|
+
unguard?.();
|
|
745
|
+
session.release();
|
|
746
|
+
}, (error) => {
|
|
747
|
+
unguard?.();
|
|
748
|
+
session.release(true);
|
|
749
|
+
throw error;
|
|
750
|
+
});
|
|
751
|
+
},
|
|
752
|
+
async () => {
|
|
753
|
+
if (pool === null || !ownsPool)
|
|
754
|
+
return;
|
|
755
|
+
const closing = pool;
|
|
756
|
+
await runMapped(() => closing.end());
|
|
757
|
+
}
|
|
758
|
+
]
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
const lifecycle = createAdapterLifecycleGate({ driver: "pg", cleanup: closeOwnedResources });
|
|
762
|
+
const root = {
|
|
489
763
|
async withSession({ run }) {
|
|
490
764
|
const client = await runMapped(() => getPool().connect());
|
|
491
765
|
let backgroundFailure;
|
|
@@ -508,7 +782,29 @@ function pgAdapter(options) {
|
|
|
508
782
|
});
|
|
509
783
|
},
|
|
510
784
|
async execute(params) {
|
|
511
|
-
|
|
785
|
+
const config = buildQueryConfig({ text: params.text, values: params.values });
|
|
786
|
+
const rootPool = getPool();
|
|
787
|
+
if (!rootViaPoolQuery) {
|
|
788
|
+
const client = await runMapped(() => rootPool.connect());
|
|
789
|
+
if (hasReadyForQuerySeam({ client })) {
|
|
790
|
+
const seam = observeReadyForQuery({ client });
|
|
791
|
+
let reuse = false;
|
|
792
|
+
try {
|
|
793
|
+
const result = await runMapped(() => client.query(config));
|
|
794
|
+
reuse = true;
|
|
795
|
+
return finalResult({ result }).rows;
|
|
796
|
+
} catch (error) {
|
|
797
|
+
reuse = await seam.reusableAfter({ error, named: config.name !== undefined });
|
|
798
|
+
throw error;
|
|
799
|
+
} finally {
|
|
800
|
+
seam.detach();
|
|
801
|
+
client.release(!reuse);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
client.release();
|
|
805
|
+
rootViaPoolQuery = true;
|
|
806
|
+
}
|
|
807
|
+
return finalResult({ result: await runMapped(() => rootPool.query(config)) }).rows;
|
|
512
808
|
},
|
|
513
809
|
async executeUnsafe(params) {
|
|
514
810
|
const control = classifyRawTransactionControl({ text: params.text });
|
|
@@ -549,55 +845,145 @@ function pgAdapter(options) {
|
|
|
549
845
|
support: BUDGETS.transactionDeadline,
|
|
550
846
|
provider: PROVIDER
|
|
551
847
|
});
|
|
848
|
+
const openingOptions = {
|
|
849
|
+
isolationLevel: transactionOptions?.isolationLevel,
|
|
850
|
+
readOnly: readOnlyRequested({ options: transactionOptions, provider: PROVIDER }),
|
|
851
|
+
timeout: transactionOptions?.timeout
|
|
852
|
+
};
|
|
853
|
+
const openText = openTransactionStatement(openingOptions);
|
|
552
854
|
const client = await runMapped(() => getPool().connect());
|
|
553
|
-
|
|
855
|
+
let connectionFailure;
|
|
856
|
+
const unguard = guardBackgroundErrors({
|
|
857
|
+
client,
|
|
858
|
+
onError: (cause) => {
|
|
859
|
+
if (connectionFailure === undefined)
|
|
860
|
+
connectionFailure = { cause };
|
|
861
|
+
}
|
|
862
|
+
});
|
|
554
863
|
const scope = createSavepointScope();
|
|
555
864
|
const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
865
|
+
const capable = supportsBatch({ client });
|
|
866
|
+
const deferOpening = capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
|
|
867
|
+
const opening = deferOpening ? [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]] : [];
|
|
868
|
+
const deferred = createDeferredControl({ opening });
|
|
869
|
+
let openingFailure;
|
|
870
|
+
let inFlight = null;
|
|
871
|
+
const sendWithControl = (dispatchParams) => {
|
|
872
|
+
const { config, cleanup } = dispatchParams;
|
|
873
|
+
const handOver = () => {
|
|
874
|
+
dispatchParams.sent?.();
|
|
875
|
+
return client.query(config);
|
|
876
|
+
};
|
|
877
|
+
if (cleanup !== true && openingFailure !== undefined)
|
|
878
|
+
throw transactionAbortedError({ cause: openingFailure });
|
|
879
|
+
let bound;
|
|
880
|
+
if (cleanup !== true && budget.totalMs !== null) {
|
|
881
|
+
if (scope.state.poisoned !== null)
|
|
882
|
+
throw scope.state.poisoned;
|
|
883
|
+
budget.assertUsable({ stage: dispatchParams.stage ?? "statement" });
|
|
884
|
+
const ms = engineStatementBudgetMs({ budget });
|
|
885
|
+
if (ms !== null)
|
|
886
|
+
bound = statementTimeoutStatement({ timeout: ms });
|
|
887
|
+
}
|
|
888
|
+
if (!capable) {
|
|
889
|
+
if (bound === undefined)
|
|
890
|
+
return handOver();
|
|
891
|
+
const stage = dispatchParams.stage ?? "statement";
|
|
892
|
+
return runMapped(() => client.query({ text: bound })).then(() => {
|
|
565
893
|
budget.assertUsable({ stage });
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
894
|
+
return handOver();
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
const pendingControl = cleanup !== true && deferred.hasPending();
|
|
898
|
+
if (!pendingControl && bound === undefined)
|
|
899
|
+
return handOver();
|
|
900
|
+
const statement = dispatchParams.batchable === true ? [{ text: config.text, values: prepareBatchValues({ values: config.values ?? [] }) }] : [];
|
|
901
|
+
const carriesOpening = !deferred.opened();
|
|
902
|
+
const control = pendingControl ? [...deferred.take()] : [];
|
|
903
|
+
if (bound !== undefined)
|
|
904
|
+
control.push(bound);
|
|
905
|
+
const openingSteps = carriesOpening ? opening.length : 0;
|
|
906
|
+
if (statement.length > 0)
|
|
907
|
+
dispatchParams.sent?.();
|
|
908
|
+
const burst = runBatch({ client, steps: [...control.map((text) => ({ text })), ...statement], types: EXACT_TYPES }).then((outcome) => {
|
|
909
|
+
const failure = firstBatchFailure({ outcome });
|
|
910
|
+
if (failure !== undefined) {
|
|
911
|
+
if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0) {
|
|
912
|
+
openingFailure = mapPgDriverError({ error: failure.error });
|
|
570
913
|
}
|
|
914
|
+
throw failure.error;
|
|
571
915
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return;
|
|
576
|
-
}, () => {
|
|
577
|
-
return;
|
|
916
|
+
if (statement.length === 0)
|
|
917
|
+
return handOver();
|
|
918
|
+
const last = outcome.steps.at(-1);
|
|
919
|
+
return last?.status === "ok" ? last.result : { rows: [], rowCount: 0 };
|
|
578
920
|
});
|
|
921
|
+
if (budget.totalMs === null && carriesOpening) {
|
|
922
|
+
const settled = burst.then(ignore, ignore).then(() => {
|
|
923
|
+
if (inFlight === settled)
|
|
924
|
+
inFlight = null;
|
|
925
|
+
});
|
|
926
|
+
inFlight = settled;
|
|
927
|
+
}
|
|
928
|
+
return burst;
|
|
929
|
+
};
|
|
930
|
+
const send = (dispatchParams) => {
|
|
931
|
+
try {
|
|
932
|
+
return sendWithControl(dispatchParams);
|
|
933
|
+
} catch (error) {
|
|
934
|
+
return Promise.reject(error);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
let tail = Promise.resolve();
|
|
938
|
+
const dispatch = (dispatchParams) => {
|
|
939
|
+
if (budget.totalMs === null) {
|
|
940
|
+
if (inFlight !== null)
|
|
941
|
+
return inFlight.then(() => dispatch(dispatchParams));
|
|
942
|
+
return send(dispatchParams);
|
|
943
|
+
}
|
|
944
|
+
const pending = tail.then(() => send(dispatchParams));
|
|
945
|
+
tail = pending.then(ignore, ignore);
|
|
579
946
|
return pending;
|
|
580
947
|
};
|
|
581
948
|
let discard = false;
|
|
949
|
+
let commitProgress = "not-sent";
|
|
582
950
|
try {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const text = statementTimeoutStatement({ timeout: transactionOptions.timeout });
|
|
586
|
-
await runMapped(() => client.query({ text }));
|
|
587
|
-
}
|
|
951
|
+
if (!deferOpening)
|
|
952
|
+
await runMapped(() => client.query({ text: openText }));
|
|
588
953
|
const result = await runRootScope({
|
|
589
954
|
scope,
|
|
590
|
-
body: () => fn(createClientAdapter({ dispatch, scope, budget }))
|
|
955
|
+
body: () => fn(createClientAdapter({ dispatch, scope, budget, deferred: capable ? deferred : null }))
|
|
591
956
|
});
|
|
592
957
|
budget.assertUsable({ stage: "commit" });
|
|
593
|
-
|
|
594
|
-
|
|
958
|
+
if (!deferred.opened()) {
|
|
959
|
+
budget.close({ outcome: "committed" });
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
if (openingFailure !== undefined)
|
|
963
|
+
throw transactionAbortedError({ cause: openingFailure });
|
|
964
|
+
const commit = await runMapped(() => dispatch({
|
|
965
|
+
config: { text: "COMMIT" },
|
|
966
|
+
stage: "commit",
|
|
967
|
+
batchable: true,
|
|
968
|
+
sent: () => {
|
|
969
|
+
if (connectionFailure !== undefined)
|
|
970
|
+
throw mapPgDriverError({ error: connectionFailure.cause });
|
|
971
|
+
commitProgress = "sent";
|
|
972
|
+
}
|
|
973
|
+
})).catch((cause) => {
|
|
974
|
+
if (commitProgress === "not-sent")
|
|
595
975
|
throw cause;
|
|
976
|
+
if (!isConnectionLoss({ error: cause })) {
|
|
977
|
+
commitProgress = "answered";
|
|
978
|
+
throw cause;
|
|
979
|
+
}
|
|
980
|
+
commitProgress = "unanswered";
|
|
596
981
|
discard = true;
|
|
597
982
|
budget.close({ outcome: "unknown" });
|
|
598
983
|
throw commitOutcomeUnknownError({ cause });
|
|
599
984
|
});
|
|
600
985
|
if (finalResult({ result: commit }).command === "ROLLBACK") {
|
|
986
|
+
commitProgress = "answered";
|
|
601
987
|
throw transactionAbortedError({ cause: scope.state.aborted?.cause });
|
|
602
988
|
}
|
|
603
989
|
budget.close({ outcome: "committed" });
|
|
@@ -607,21 +993,25 @@ function pgAdapter(options) {
|
|
|
607
993
|
discard = true;
|
|
608
994
|
budget.close({ outcome: "unknown" });
|
|
609
995
|
}
|
|
996
|
+
let rollback = "not-needed";
|
|
610
997
|
try {
|
|
611
|
-
|
|
612
|
-
|
|
998
|
+
if (deferred.opened()) {
|
|
999
|
+
await dispatch({ config: { text: "ROLLBACK" }, cleanup: true });
|
|
1000
|
+
rollback = "acknowledged";
|
|
1001
|
+
}
|
|
613
1002
|
} catch {
|
|
614
1003
|
discard = true;
|
|
615
|
-
|
|
616
|
-
if (budget.totalMs !== null && isTransactionDeadlineError(error)) {
|
|
617
|
-
throw transactionOutcomeUnknownError({
|
|
618
|
-
provider: PROVIDER,
|
|
619
|
-
totalMs: budget.totalMs,
|
|
620
|
-
cause: error
|
|
621
|
-
});
|
|
622
|
-
}
|
|
1004
|
+
rollback = "failed";
|
|
623
1005
|
}
|
|
624
|
-
throw
|
|
1006
|
+
throw settleFailedTransaction({
|
|
1007
|
+
error,
|
|
1008
|
+
provider: PROVIDER,
|
|
1009
|
+
budget,
|
|
1010
|
+
scope,
|
|
1011
|
+
commit: commitProgress,
|
|
1012
|
+
rollback,
|
|
1013
|
+
connection: discard ? "discarded" : "returned"
|
|
1014
|
+
});
|
|
625
1015
|
} finally {
|
|
626
1016
|
unguard();
|
|
627
1017
|
client.release(discard);
|
|
@@ -630,24 +1020,32 @@ function pgAdapter(options) {
|
|
|
630
1020
|
async connect() {
|
|
631
1021
|
const client = await runMapped(() => getPool().connect());
|
|
632
1022
|
client.release();
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
return {
|
|
1026
|
+
dialect: DIALECT,
|
|
1027
|
+
provider: PROVIDER,
|
|
1028
|
+
wire: WIRE,
|
|
1029
|
+
budgets: BUDGETS,
|
|
1030
|
+
roundTrips: ROUND_TRIPS,
|
|
1031
|
+
transactionOutcome: "reported",
|
|
1032
|
+
withSession({ run }) {
|
|
1033
|
+
return lifecycle.admit({ run: () => root.withSession({ run: (executor) => lifecycle.inCallback({ run: () => run(executor) }) }) });
|
|
633
1034
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
pool = null;
|
|
649
|
-
await runMapped(() => closing.end());
|
|
650
|
-
}
|
|
1035
|
+
execute(params) {
|
|
1036
|
+
return lifecycle.admit({ run: () => root.execute(params) });
|
|
1037
|
+
},
|
|
1038
|
+
executeUnsafe(params) {
|
|
1039
|
+
return lifecycle.admit({ run: () => root.executeUnsafe(params) });
|
|
1040
|
+
},
|
|
1041
|
+
transaction(fn, transactionOptions) {
|
|
1042
|
+
return lifecycle.admit({ run: () => root.transaction((txAdapter) => lifecycle.inCallback({ run: () => fn(txAdapter) }), transactionOptions) });
|
|
1043
|
+
},
|
|
1044
|
+
connect() {
|
|
1045
|
+
return lifecycle.admit({ run: () => root.connect() });
|
|
1046
|
+
},
|
|
1047
|
+
disconnect() {
|
|
1048
|
+
return lifecycle.disconnect();
|
|
651
1049
|
},
|
|
652
1050
|
formatArrayParam(values) {
|
|
653
1051
|
return new PgArrayParam(values);
|
|
@@ -666,7 +1064,7 @@ export {
|
|
|
666
1064
|
createSavepointCounter,
|
|
667
1065
|
createStatementNameCache,
|
|
668
1066
|
engineStatementBudgetMs2 as engineStatementBudgetMs,
|
|
669
|
-
|
|
1067
|
+
isTransactionDeadlineError,
|
|
670
1068
|
mapPgDriverError,
|
|
671
1069
|
nextSavepointName,
|
|
672
1070
|
pgAdapter,
|
|
@@ -679,8 +1077,8 @@ export {
|
|
|
679
1077
|
statementTimeoutStatement,
|
|
680
1078
|
transactionClosedError,
|
|
681
1079
|
transactionDeadlineError,
|
|
682
|
-
|
|
1080
|
+
transactionOutcomeUnknownError,
|
|
683
1081
|
validateTransactionDeadline2 as validateTransactionDeadline
|
|
684
1082
|
};
|
|
685
1083
|
|
|
686
|
-
//# debugId=
|
|
1084
|
+
//# debugId=9151AD009082F4C664756E2164756E21
|