@vibeorm/adapter-pg 4.3.0 → 5.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/README.md +22 -3
- package/dist/batch.d.ts +8 -0
- package/dist/client-turns.d.ts +10 -0
- package/dist/connection-group.d.ts +62 -0
- package/dist/connection-string.d.ts +55 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +704 -96
- package/dist/index.js.map +9 -7
- package/dist/pool-config.d.ts +13 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -27,10 +27,11 @@ import {
|
|
|
27
27
|
scopeTurn,
|
|
28
28
|
settleFailedTransaction,
|
|
29
29
|
startTransactionBudget,
|
|
30
|
-
transactionAbortedError,
|
|
31
|
-
validateTransactionDeadline
|
|
30
|
+
transactionAbortedError as transactionAbortedError2,
|
|
31
|
+
validateTransactionDeadline,
|
|
32
|
+
parseExtendedDateTimeText
|
|
32
33
|
} from "@vibeorm/runtime/adapter-kit";
|
|
33
|
-
import { VibeError as
|
|
34
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
34
35
|
import pg3 from "pg";
|
|
35
36
|
|
|
36
37
|
// packages/adapter-pg/src/batch.ts
|
|
@@ -39,6 +40,9 @@ var prepareValue = (() => {
|
|
|
39
40
|
const utils = pg.utils;
|
|
40
41
|
return typeof utils?.prepareValue === "function" ? utils.prepareValue : undefined;
|
|
41
42
|
})();
|
|
43
|
+
function batchDriverAvailable() {
|
|
44
|
+
return prepareValue !== undefined;
|
|
45
|
+
}
|
|
42
46
|
function supportsBatch(params) {
|
|
43
47
|
if (prepareValue === undefined)
|
|
44
48
|
return false;
|
|
@@ -97,13 +101,20 @@ class PgBatch {
|
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
handleRowDescription(message) {
|
|
104
|
+
this.rows = [];
|
|
105
|
+
if (this.steps[this.outcomes.length]?.discardRows === true) {
|
|
106
|
+
this.fields = [];
|
|
107
|
+
this.parsers = [];
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
100
110
|
this.fields = message.fields;
|
|
101
111
|
this.parsers = message.fields.map((field) => this.types.getTypeParser(field.dataTypeID, field.format ?? "text"));
|
|
102
|
-
this.rows = [];
|
|
103
112
|
}
|
|
104
113
|
handleDataRow(message) {
|
|
105
114
|
if (this.settled || this.rowError !== undefined)
|
|
106
115
|
return;
|
|
116
|
+
if (this.steps[this.outcomes.length]?.discardRows === true)
|
|
117
|
+
return;
|
|
107
118
|
const row = {};
|
|
108
119
|
try {
|
|
109
120
|
for (let index = 0;index < this.fields.length; index++) {
|
|
@@ -186,12 +197,162 @@ function firstBatchFailure(params) {
|
|
|
186
197
|
return;
|
|
187
198
|
}
|
|
188
199
|
|
|
200
|
+
// packages/adapter-pg/src/connection-group.ts
|
|
201
|
+
import { VibeError } from "@vibeorm/schema";
|
|
202
|
+
import { transactionAbortedError } from "@vibeorm/runtime/adapter-kit";
|
|
203
|
+
function connectionGroupMisuse(params) {
|
|
204
|
+
return new VibeError({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
|
|
205
|
+
}
|
|
206
|
+
function openConnectionGroup(params) {
|
|
207
|
+
const { driver } = params;
|
|
208
|
+
const seats = [];
|
|
209
|
+
let sealed = false;
|
|
210
|
+
let sent = false;
|
|
211
|
+
const settle = (entries, outcome) => {
|
|
212
|
+
let failure;
|
|
213
|
+
entries.forEach((seat, index) => {
|
|
214
|
+
const issued = seat.issued;
|
|
215
|
+
if (issued === undefined)
|
|
216
|
+
return;
|
|
217
|
+
const step = outcome.steps[index];
|
|
218
|
+
if (step === undefined) {
|
|
219
|
+
const error = driver.mapError(outcome.transportError);
|
|
220
|
+
failure ??= error;
|
|
221
|
+
issued.reject(error);
|
|
222
|
+
} else if (step.status === "ok") {
|
|
223
|
+
issued.resolve(step.result.rows);
|
|
224
|
+
} else if (step.status === "failed") {
|
|
225
|
+
const error = driver.mapError(step.error);
|
|
226
|
+
failure ??= error;
|
|
227
|
+
issued.reject(error);
|
|
228
|
+
} else {
|
|
229
|
+
issued.reject(transactionAbortedError({ cause: failure }));
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
const flushIfReady = () => {
|
|
234
|
+
if (!sealed || sent)
|
|
235
|
+
return;
|
|
236
|
+
for (const seat of seats)
|
|
237
|
+
if (seat.open && seat.issued === undefined && !seat.done)
|
|
238
|
+
return;
|
|
239
|
+
sent = true;
|
|
240
|
+
const entries = seats.filter((seat) => seat.issued !== undefined);
|
|
241
|
+
if (entries.length === 0)
|
|
242
|
+
return;
|
|
243
|
+
const sending = new Promise((resolve) => {
|
|
244
|
+
resolve(driver.send(entries.map((seat) => seat.issued.entry)));
|
|
245
|
+
});
|
|
246
|
+
sending.then((outcome) => {
|
|
247
|
+
settle(entries, outcome);
|
|
248
|
+
}, (error) => {
|
|
249
|
+
const mapped = driver.mapError(error);
|
|
250
|
+
for (const seat of entries)
|
|
251
|
+
seat.issued?.reject(mapped);
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const refused = (message) => () => Promise.reject(connectionGroupMisuse({ message }));
|
|
255
|
+
const subAdapter = (seat) => ({
|
|
256
|
+
...driver.base,
|
|
257
|
+
execute: (call) => {
|
|
258
|
+
if (seat.issued !== undefined || seat.done)
|
|
259
|
+
return Promise.reject(connectionGroupMisuse({ message: "a sub-lane issues ONE statement" }));
|
|
260
|
+
if (!seat.open || sent)
|
|
261
|
+
return Promise.reject(connectionGroupMisuse({ message: "a closed sub-lane issues nothing" }));
|
|
262
|
+
if (call.execution !== undefined) {
|
|
263
|
+
seat.done = true;
|
|
264
|
+
flushIfReady();
|
|
265
|
+
return Promise.reject(connectionGroupMisuse({ message: "a sub-lane statement takes no read options" }));
|
|
266
|
+
}
|
|
267
|
+
const pending = new Promise((resolve, reject) => {
|
|
268
|
+
const step = driver.prepare(call);
|
|
269
|
+
seat.issued = { entry: { step, call }, resolve, reject };
|
|
270
|
+
});
|
|
271
|
+
if (seat.issued === undefined) {
|
|
272
|
+
seat.done = true;
|
|
273
|
+
flushIfReady();
|
|
274
|
+
return pending.then(undefined, (error) => {
|
|
275
|
+
throw driver.mapError(error);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
flushIfReady();
|
|
279
|
+
return pending;
|
|
280
|
+
},
|
|
281
|
+
executeUnsafe: refused("a sub-lane runs no raw statement"),
|
|
282
|
+
transaction: refused("a sub-lane opens no transaction"),
|
|
283
|
+
connect: refused("a sub-lane does not connect"),
|
|
284
|
+
disconnect: refused("a sub-lane does not disconnect")
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
lane: () => {
|
|
288
|
+
if (sealed)
|
|
289
|
+
throw connectionGroupMisuse({ message: "a sub-lane is opened only before the group is sealed" });
|
|
290
|
+
const seat = { index: seats.length, open: true, done: false };
|
|
291
|
+
seats.push(seat);
|
|
292
|
+
return {
|
|
293
|
+
adapter: subAdapter(seat),
|
|
294
|
+
close: () => {
|
|
295
|
+
if (!seat.open)
|
|
296
|
+
return;
|
|
297
|
+
seat.open = false;
|
|
298
|
+
flushIfReady();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
seal: () => {
|
|
303
|
+
if (sealed)
|
|
304
|
+
return;
|
|
305
|
+
sealed = true;
|
|
306
|
+
flushIfReady();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// packages/adapter-pg/src/connection-string.ts
|
|
312
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
313
|
+
function lastValue(params) {
|
|
314
|
+
const values = params.search.getAll(params.key);
|
|
315
|
+
return values.length === 0 ? undefined : values[values.length - 1];
|
|
316
|
+
}
|
|
317
|
+
function honourSystemRootCert(params) {
|
|
318
|
+
const { connectionString, option } = params;
|
|
319
|
+
const queryStart = connectionString.indexOf("?");
|
|
320
|
+
if (queryStart === -1)
|
|
321
|
+
return connectionString;
|
|
322
|
+
const hashStart = connectionString.indexOf("#", queryStart);
|
|
323
|
+
const queryEnd = hashStart === -1 ? connectionString.length : hashStart;
|
|
324
|
+
const search = new URLSearchParams(connectionString.slice(queryStart + 1, queryEnd));
|
|
325
|
+
if (lastValue({ search, key: "sslrootcert" }) !== "system")
|
|
326
|
+
return connectionString;
|
|
327
|
+
const sslmode = lastValue({ search, key: "sslmode" });
|
|
328
|
+
const ssl = lastValue({ search, key: "ssl" });
|
|
329
|
+
const weaker = sslmode !== undefined && sslmode !== "verify-full" ? { parameter: "sslmode", value: sslmode } : ssl !== undefined && ssl !== "true" && ssl !== "1" ? { parameter: "ssl", value: ssl } : undefined;
|
|
330
|
+
if (weaker !== undefined) {
|
|
331
|
+
throw new VibeError2({
|
|
332
|
+
code: "VIBE_CONFIG",
|
|
333
|
+
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
|
+
meta: { adapter: "pg", reason: "sslrootcert-system-weak-sslmode", option, parameter: weaker.parameter, value: weaker.value }
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
search.delete("sslrootcert");
|
|
338
|
+
search.delete("ssl");
|
|
339
|
+
search.set("sslmode", "verify-full");
|
|
340
|
+
const query = search.toString().replace(/\+/g, "%20");
|
|
341
|
+
return `${connectionString.slice(0, queryStart)}?${query}${connectionString.slice(queryEnd)}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
189
344
|
// packages/adapter-pg/src/client-turns.ts
|
|
190
345
|
function createClientTurns() {
|
|
191
346
|
let outstanding = 0;
|
|
347
|
+
let failure;
|
|
192
348
|
let previous = Promise.resolve();
|
|
193
349
|
const release = () => {
|
|
194
350
|
outstanding -= 1;
|
|
351
|
+
failure = undefined;
|
|
352
|
+
};
|
|
353
|
+
const fail = (error) => {
|
|
354
|
+
outstanding -= 1;
|
|
355
|
+
failure = { error };
|
|
195
356
|
};
|
|
196
357
|
return {
|
|
197
358
|
run: (send) => {
|
|
@@ -199,16 +360,17 @@ function createClientTurns() {
|
|
|
199
360
|
resolve(send());
|
|
200
361
|
}) : previous.then(send);
|
|
201
362
|
outstanding += 1;
|
|
202
|
-
previous = running.then(release,
|
|
363
|
+
previous = running.then(release, fail);
|
|
203
364
|
return running;
|
|
204
365
|
},
|
|
205
|
-
outstanding: () => outstanding
|
|
366
|
+
outstanding: () => outstanding,
|
|
367
|
+
lastFailure: () => failure
|
|
206
368
|
};
|
|
207
369
|
}
|
|
208
370
|
|
|
209
371
|
// packages/adapter-pg/src/read-cancellation.ts
|
|
210
372
|
import pg2 from "pg";
|
|
211
|
-
import { VibeError as
|
|
373
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
212
374
|
import { assertReadExecution, readCancelledError } from "@vibeorm/runtime/adapter-kit";
|
|
213
375
|
|
|
214
376
|
// packages/adapter-pg/src/config.ts
|
|
@@ -217,7 +379,7 @@ var PG_READ_CANCELLATION = Object.freeze({
|
|
|
217
379
|
});
|
|
218
380
|
|
|
219
381
|
// packages/adapter-pg/src/errors.ts
|
|
220
|
-
import { VibeError } from "@vibeorm/schema";
|
|
382
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
221
383
|
var PG_SQLSTATE_ERROR_CODES = Object.freeze({
|
|
222
384
|
"23P01": "VIBE_EXCLUSION_VIOLATION",
|
|
223
385
|
"23505": "VIBE_UNIQUE_VIOLATION",
|
|
@@ -264,7 +426,7 @@ function pgErrorSqlstate(params) {
|
|
|
264
426
|
}
|
|
265
427
|
function mapPgDriverError(params) {
|
|
266
428
|
const { error } = params;
|
|
267
|
-
if (error instanceof
|
|
429
|
+
if (error instanceof VibeError3)
|
|
268
430
|
return error;
|
|
269
431
|
const source = error !== null && typeof error === "object" ? error : {};
|
|
270
432
|
const sqlstate = pgErrorSqlstate({ error });
|
|
@@ -287,7 +449,7 @@ function mapPgDriverError(params) {
|
|
|
287
449
|
const driverMessage = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
|
|
288
450
|
const hint = sqlstate === undefined ? undefined : PG_SQLSTATE_HINTS[sqlstate];
|
|
289
451
|
const message = hint === undefined ? driverMessage : `${driverMessage}. ${hint}`;
|
|
290
|
-
return new
|
|
452
|
+
return new VibeError3({ code, message, meta, cause: error });
|
|
291
453
|
}
|
|
292
454
|
async function runMapped(fn) {
|
|
293
455
|
try {
|
|
@@ -310,14 +472,14 @@ function createPgReadCancellation(params) {
|
|
|
310
472
|
if (query.execution?.cancellation !== "running")
|
|
311
473
|
return query.send();
|
|
312
474
|
if (params.target.pipeline === true || (params.target.connectionParameters?.query_timeout ?? 0) > 0) {
|
|
313
|
-
throw new
|
|
475
|
+
throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Running cancellation requires a non-pipelined target without local query_timeout", meta: { capability: "readCancellation", reason: "target-lifetime" } });
|
|
314
476
|
}
|
|
315
477
|
const address = params.connectionString;
|
|
316
478
|
if (address === undefined)
|
|
317
|
-
throw new
|
|
479
|
+
throw new VibeError4({ code: "VIBE_INTERNAL", message: "Running cancellation requires a control address" });
|
|
318
480
|
const token = `vibe_cancel_${crypto.randomUUID()}`;
|
|
319
481
|
if (!URL.canParse(address))
|
|
320
|
-
throw new
|
|
482
|
+
throw new VibeError4({ code: "VIBE_CONFIG", message: "The PostgreSQL cancellation control address must be a URL" });
|
|
321
483
|
const url = new URL(address);
|
|
322
484
|
url.searchParams.delete("query_timeout");
|
|
323
485
|
url.searchParams.set("connect_timeout", String(PG_READ_CANCELLATION.controlTimeoutMs / 1000));
|
|
@@ -364,7 +526,7 @@ function createPgReadCancellation(params) {
|
|
|
364
526
|
if (preflightAborted)
|
|
365
527
|
throw readCancelledError({ stage: "waiting-for-connection" });
|
|
366
528
|
if (witness === undefined || typeof witness.pid !== "number" || typeof witness.backend_start !== "string" || typeof witness.datname !== "string" || typeof witness.usename !== "string" || witness.tag !== token) {
|
|
367
|
-
throw new
|
|
529
|
+
throw new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Cannot prove the exact PostgreSQL cancellation target", meta: { capability: "readCancellation", reason: "target-witness" } });
|
|
368
530
|
}
|
|
369
531
|
assertReadExecution({ execution: query.execution, effect: "read", support, provider: "postgresql", stage: "waiting-for-connection" });
|
|
370
532
|
listener = () => {
|
|
@@ -381,7 +543,7 @@ function createPgReadCancellation(params) {
|
|
|
381
543
|
throw error;
|
|
382
544
|
};
|
|
383
545
|
settlementTimer = setTimeout(() => {
|
|
384
|
-
controlFailure = new
|
|
546
|
+
controlFailure = new VibeError4({ code: "VIBE_ADAPTER", message: "PostgreSQL read did not settle after cancellation; its exact session was discarded", meta: { reason: "read-cancellation-unsettled", databaseCancelled: false } });
|
|
385
547
|
if (!terminated) {
|
|
386
548
|
terminated = true;
|
|
387
549
|
params.terminate();
|
|
@@ -393,7 +555,7 @@ function createPgReadCancellation(params) {
|
|
|
393
555
|
})).then((result) => {
|
|
394
556
|
signalSent = rowsOf(result)[0]?.signalled === true;
|
|
395
557
|
if (!signalSent)
|
|
396
|
-
failControl(new
|
|
558
|
+
failControl(new VibeError4({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "The control endpoint did not signal the exact PostgreSQL target", meta: { capability: "readCancellation", reason: "target-not-signalled", databaseCancelled: false } }));
|
|
397
559
|
}, failControl);
|
|
398
560
|
cancel.then(() => {
|
|
399
561
|
return;
|
|
@@ -424,12 +586,12 @@ function createPgReadCancellation(params) {
|
|
|
424
586
|
}, () => {
|
|
425
587
|
return;
|
|
426
588
|
});
|
|
427
|
-
const sqlstate = error instanceof
|
|
589
|
+
const sqlstate = error instanceof VibeError4 ? error.meta.sqlstate : pgErrorSqlstate({ error });
|
|
428
590
|
if (terminated && controlFailure !== undefined && sqlstate === undefined)
|
|
429
591
|
throw controlFailure;
|
|
430
592
|
if (attempted && signalSent && sqlstate === "57014") {
|
|
431
593
|
const cancelled = readCancelledError({ stage: "running", databaseCancelled: true, cause: error });
|
|
432
|
-
throw new
|
|
594
|
+
throw new VibeError4({ code: cancelled.code, message: cancelled.message, meta: { ...cancelled.meta, sqlstate: "57014" }, cause: error });
|
|
433
595
|
}
|
|
434
596
|
throw error;
|
|
435
597
|
});
|
|
@@ -511,7 +673,7 @@ function serializeParamsRaw(params) {
|
|
|
511
673
|
}
|
|
512
674
|
|
|
513
675
|
// packages/adapter-pg/src/pool-config.ts
|
|
514
|
-
import { VibeError as
|
|
676
|
+
import { VibeError as VibeError5 } from "@vibeorm/schema";
|
|
515
677
|
var PLAN_CACHE_MODES = ["auto", "force_custom_plan", "force_generic_plan"];
|
|
516
678
|
function isPlanCacheMode(value) {
|
|
517
679
|
return typeof value === "string" && PLAN_CACHE_MODES.includes(value);
|
|
@@ -520,7 +682,7 @@ function assertPlanCacheMode(params) {
|
|
|
520
682
|
const { value } = params;
|
|
521
683
|
if (isPlanCacheMode(value))
|
|
522
684
|
return value;
|
|
523
|
-
throw new
|
|
685
|
+
throw new VibeError5({
|
|
524
686
|
code: "VIBE_CONFIG",
|
|
525
687
|
message: `planCacheMode must be one of ${PLAN_CACHE_MODES.map((mode) => `"${mode}"`).join(", ")}, received ${JSON.stringify(value)}`,
|
|
526
688
|
meta: { planCacheMode: value, allowed: PLAN_CACHE_MODES }
|
|
@@ -550,19 +712,31 @@ function assertIdleTimeout(params) {
|
|
|
550
712
|
const { value } = params;
|
|
551
713
|
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2147483647)
|
|
552
714
|
return value;
|
|
553
|
-
throw new
|
|
715
|
+
throw new VibeError5({
|
|
554
716
|
code: "VIBE_CONFIG",
|
|
555
717
|
message: "pgAdapter `idleTimeout` must be integer milliseconds from 0 through 2,147,483,647 (0 keeps idle connections open).",
|
|
556
718
|
meta: { adapter: "pg", reason: "invalid-idle-timeout", option: "idleTimeout" }
|
|
557
719
|
});
|
|
558
720
|
}
|
|
721
|
+
function assertPoolMin(params) {
|
|
722
|
+
const { value, max } = params;
|
|
723
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max)
|
|
724
|
+
return value;
|
|
725
|
+
throw new VibeError5({
|
|
726
|
+
code: "VIBE_CONFIG",
|
|
727
|
+
message: `pgAdapter \`min\` must be an integer from 0 through \`max\` (${max}).`,
|
|
728
|
+
meta: { adapter: "pg", reason: "invalid-pool-min", option: "min", max }
|
|
729
|
+
});
|
|
730
|
+
}
|
|
559
731
|
function buildPoolConfig(params) {
|
|
560
|
-
const { max, statementTimeout, connectionTimeout, idleTimeout } = params;
|
|
732
|
+
const { max, min, statementTimeout, connectionTimeout, idleTimeout } = params;
|
|
561
733
|
const placement = placePlanCacheMode({
|
|
562
734
|
connectionString: params.connectionString,
|
|
563
735
|
planCacheMode: params.planCacheMode ?? "auto"
|
|
564
736
|
});
|
|
565
737
|
const config = { connectionString: placement.connectionString, max };
|
|
738
|
+
if (min !== undefined)
|
|
739
|
+
config.min = min;
|
|
566
740
|
if (statementTimeout !== undefined)
|
|
567
741
|
config.statement_timeout = statementTimeout;
|
|
568
742
|
if (connectionTimeout !== undefined)
|
|
@@ -719,7 +893,7 @@ function parsePlainTimestampText(params) {
|
|
|
719
893
|
}
|
|
720
894
|
|
|
721
895
|
// packages/adapter-pg/src/transaction-sql.ts
|
|
722
|
-
import { VibeError as
|
|
896
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
723
897
|
import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
|
|
724
898
|
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
725
899
|
ReadCommitted: "READ COMMITTED",
|
|
@@ -742,7 +916,7 @@ function openTransactionStatement(params) {
|
|
|
742
916
|
function statementTimeoutStatement(params) {
|
|
743
917
|
const { timeout } = params;
|
|
744
918
|
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
745
|
-
throw new
|
|
919
|
+
throw new VibeError6({
|
|
746
920
|
code: "VIBE_VALIDATION",
|
|
747
921
|
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
748
922
|
meta: { timeout }
|
|
@@ -778,19 +952,42 @@ import {
|
|
|
778
952
|
transactionOutcomeUnknownError,
|
|
779
953
|
validateTransactionDeadline as validateTransactionDeadline2
|
|
780
954
|
} from "@vibeorm/runtime/adapter-kit";
|
|
955
|
+
function laneGroupMisuse(params) {
|
|
956
|
+
return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline lane group: ${params.message}`, meta: { reason: "pipeline-lane-group" } });
|
|
957
|
+
}
|
|
781
958
|
var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
|
|
782
959
|
var ignore = () => {
|
|
783
960
|
return;
|
|
784
961
|
};
|
|
785
962
|
function predecessorClosedError() {
|
|
786
|
-
return new
|
|
963
|
+
return new VibeError7({
|
|
787
964
|
code: "VIBE_TRANSACTION",
|
|
788
965
|
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)",
|
|
789
966
|
meta: { reason: "pipeline-predecessor-closed" }
|
|
790
967
|
});
|
|
791
968
|
}
|
|
792
969
|
function orderedLaneMisuse(params) {
|
|
793
|
-
return new
|
|
970
|
+
return new VibeError7({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
|
|
971
|
+
}
|
|
972
|
+
var COMMIT_STEP = Object.freeze({ text: "COMMIT" });
|
|
973
|
+
function commitSentRefusal() {
|
|
974
|
+
return new VibeError7({
|
|
975
|
+
code: "VIBE_TRANSACTION",
|
|
976
|
+
message: "this transaction already sent its COMMIT together with its last statement, so it accepts no further statement",
|
|
977
|
+
meta: { provider: PROVIDER, reason: "commit-sent" }
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
function finalStatementMisuse() {
|
|
981
|
+
return new VibeError7({
|
|
982
|
+
code: "VIBE_INTERNAL",
|
|
983
|
+
message: "withCommit(): a view carries ONE statement, the transaction's last",
|
|
984
|
+
meta: { reason: "with-commit-second-statement" }
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function beforeCommit(params) {
|
|
988
|
+
const { outcome, commitIndex } = params;
|
|
989
|
+
const steps = outcome.steps.slice(0, commitIndex);
|
|
990
|
+
return steps.length < commitIndex && outcome.transportError !== undefined ? { steps, transportError: outcome.transportError } : { steps };
|
|
794
991
|
}
|
|
795
992
|
function finalResult(params) {
|
|
796
993
|
return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
|
|
@@ -798,13 +995,13 @@ function finalResult(params) {
|
|
|
798
995
|
var POOL_STATEMENT_NAMES = new WeakMap;
|
|
799
996
|
function isConnectionLoss(params) {
|
|
800
997
|
const { error } = params;
|
|
801
|
-
if (!(error instanceof
|
|
998
|
+
if (!(error instanceof VibeError7))
|
|
802
999
|
return true;
|
|
803
1000
|
const sqlstate = error.meta.sqlstate;
|
|
804
1001
|
return typeof sqlstate !== "string" || sqlstate.startsWith("08") || ["57P01", "57P02", "57P03"].includes(sqlstate);
|
|
805
1002
|
}
|
|
806
1003
|
function commitOutcomeUnknownError(params) {
|
|
807
|
-
return new
|
|
1004
|
+
return new VibeError7({
|
|
808
1005
|
code: "VIBE_TRANSACTION",
|
|
809
1006
|
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.",
|
|
810
1007
|
meta: { provider: PROVIDER, reason: "transaction-outcome-unknown", outcome: "unknown", stage: "commit" },
|
|
@@ -833,7 +1030,7 @@ function observeReadyForQuery(params) {
|
|
|
833
1030
|
client.on("end", onEnd);
|
|
834
1031
|
return {
|
|
835
1032
|
reusableAfter: async ({ error, named }) => {
|
|
836
|
-
if (!(error instanceof
|
|
1033
|
+
if (!(error instanceof VibeError7))
|
|
837
1034
|
return false;
|
|
838
1035
|
const sqlstate = error.meta.sqlstate;
|
|
839
1036
|
if (typeof sqlstate !== "string" || NEVER_REUSE_SQLSTATE_CLASSES.has(sqlstate.slice(0, 2)))
|
|
@@ -870,7 +1067,7 @@ function guardBackgroundErrors(params) {
|
|
|
870
1067
|
}
|
|
871
1068
|
var DIALECT = "postgres";
|
|
872
1069
|
var PROVIDER = "postgresql";
|
|
873
|
-
var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
1070
|
+
var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native", bytes: "buffer" });
|
|
874
1071
|
var ROUND_TRIPS = "network";
|
|
875
1072
|
var READ_SNAPSHOT = Object.freeze({ isolationLevel: "RepeatableRead", accessMode: "readOnly" });
|
|
876
1073
|
var PIPELINING = "batched";
|
|
@@ -900,7 +1097,7 @@ function parseZoneLessAsUtc(value) {
|
|
|
900
1097
|
if (!Number.isNaN(parsed.getTime()))
|
|
901
1098
|
return parsed;
|
|
902
1099
|
}
|
|
903
|
-
return driverTypeParser(TIMESTAMP_OID, "text")(value);
|
|
1100
|
+
return parseExtendedDateTimeText({ text: value }) ?? driverTypeParser(TIMESTAMP_OID, "text")(value);
|
|
904
1101
|
}
|
|
905
1102
|
function parseZoneLessArrayAsUtc(value) {
|
|
906
1103
|
const mapElements = (element) => {
|
|
@@ -960,24 +1157,52 @@ function takeAddress(params) {
|
|
|
960
1157
|
if (options?.pool !== undefined && options.pool !== null)
|
|
961
1158
|
return { pool: options.pool };
|
|
962
1159
|
const connectionString = options?.connectionString;
|
|
963
|
-
if (typeof connectionString === "string" && connectionString.trim() !== "")
|
|
964
|
-
return { connectionString };
|
|
965
|
-
|
|
1160
|
+
if (typeof connectionString === "string" && connectionString.trim() !== "") {
|
|
1161
|
+
return { connectionString: honourSystemRootCert({ connectionString, option: "connectionString" }) };
|
|
1162
|
+
}
|
|
1163
|
+
throw new VibeError7({
|
|
966
1164
|
code: "VIBE_CONFIG",
|
|
967
1165
|
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.",
|
|
968
1166
|
meta: { adapter: "pg", reason: "missing-address" }
|
|
969
1167
|
});
|
|
970
1168
|
}
|
|
1169
|
+
function transactionsTakeOneRoundTrip(params) {
|
|
1170
|
+
const { address } = params;
|
|
1171
|
+
if (!batchDriverAvailable())
|
|
1172
|
+
return false;
|
|
1173
|
+
const config = "pool" in address ? address.pool.options ?? {} : { connectionString: address.connectionString };
|
|
1174
|
+
if (config.pipeline === true)
|
|
1175
|
+
return false;
|
|
1176
|
+
let timeout = config.query_timeout;
|
|
1177
|
+
const connectionString = config.connectionString;
|
|
1178
|
+
if (typeof connectionString === "string") {
|
|
1179
|
+
if (!URL.canParse(connectionString))
|
|
1180
|
+
return false;
|
|
1181
|
+
const fromUrl = new URL(connectionString).searchParams.get("query_timeout");
|
|
1182
|
+
if (fromUrl !== null)
|
|
1183
|
+
timeout = fromUrl;
|
|
1184
|
+
}
|
|
1185
|
+
timeout ||= pg3.defaults.query_timeout;
|
|
1186
|
+
return !(Number(timeout ?? 0) > 0);
|
|
1187
|
+
}
|
|
971
1188
|
function pgAdapter(options) {
|
|
972
1189
|
const address = takeAddress({ options });
|
|
973
|
-
const cancellationAddress = options.cancellationConnectionString
|
|
1190
|
+
const cancellationAddress = options.cancellationConnectionString !== undefined ? honourSystemRootCert({ connectionString: options.cancellationConnectionString, option: "cancellationConnectionString" }) : ("connectionString" in address) ? address.connectionString : undefined;
|
|
974
1191
|
const readCancellation = cancellationAddress === undefined ? "before-dispatch" : "running";
|
|
975
1192
|
const usePrepared = options?.preparedStatements ?? false;
|
|
976
1193
|
const stmtCacheMax = options?.stmtCacheMax ?? 1000;
|
|
977
1194
|
const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
|
|
978
1195
|
const idleTimeout = "pool" in address || options?.idleTimeout === undefined ? undefined : assertIdleTimeout({ value: options.idleTimeout });
|
|
1196
|
+
if (options?.min !== undefined && "pool" in address) {
|
|
1197
|
+
throw new VibeError7({
|
|
1198
|
+
code: "VIBE_CONFIG",
|
|
1199
|
+
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
|
+
meta: { adapter: "pg", reason: "pool-min-with-injected-pool", option: "min" }
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
const poolMin = options?.min === undefined ? undefined : assertPoolMin({ value: options.min, max: options.max ?? 10 });
|
|
979
1204
|
if (planCacheMode !== "auto" && "pool" in address) {
|
|
980
|
-
throw new
|
|
1205
|
+
throw new VibeError7({
|
|
981
1206
|
code: "VIBE_CONFIG",
|
|
982
1207
|
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',
|
|
983
1208
|
meta: { planCacheMode }
|
|
@@ -999,6 +1224,7 @@ function pgAdapter(options) {
|
|
|
999
1224
|
const config = buildPoolConfig({
|
|
1000
1225
|
connectionString: address.connectionString,
|
|
1001
1226
|
max: options?.max ?? 10,
|
|
1227
|
+
...poolMin === undefined ? {} : { min: poolMin },
|
|
1002
1228
|
...options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout },
|
|
1003
1229
|
...options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout },
|
|
1004
1230
|
...idleTimeout === undefined ? {} : { idleTimeout },
|
|
@@ -1048,14 +1274,22 @@ function pgAdapter(options) {
|
|
|
1048
1274
|
return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
|
|
1049
1275
|
}
|
|
1050
1276
|
function createClientAdapter(params) {
|
|
1051
|
-
const { dispatch, scope, budget, deferred, cancellation, pipelines, lane } = params;
|
|
1277
|
+
const { dispatch, scope, budget, deferred, cancellation, pipelines, lane, connection, final } = params;
|
|
1052
1278
|
const laneOf = lane === undefined ? {} : { lane };
|
|
1279
|
+
const finalOf = () => {
|
|
1280
|
+
if (final === undefined)
|
|
1281
|
+
return {};
|
|
1282
|
+
if (final.used)
|
|
1283
|
+
throw finalStatementMisuse();
|
|
1284
|
+
final.used = true;
|
|
1285
|
+
return { commit: true, ...final.discardRows === true ? { discardRows: true } : {} };
|
|
1286
|
+
};
|
|
1053
1287
|
const waitAsLane = (wait) => lane === undefined || pipelines === null ? wait : pipelines.waitAs({ lane, wait });
|
|
1054
1288
|
const rawClient = {
|
|
1055
|
-
query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 || isSingleStatementText({ text: config.text }), ...laneOf })
|
|
1289
|
+
query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 || isSingleStatementText({ text: config.text }), ...laneOf, ...finalOf() })
|
|
1056
1290
|
};
|
|
1057
1291
|
const savepointControl = (statement, cleanup) => {
|
|
1058
|
-
if (deferred !== null && deferred.routeSavepoint({ text: statement }) !== "send")
|
|
1292
|
+
if (deferred !== null && deferred.routeSavepoint({ text: statement, cleanup }) !== "send")
|
|
1059
1293
|
return;
|
|
1060
1294
|
return runMapped(() => dispatch({
|
|
1061
1295
|
config: { text: statement },
|
|
@@ -1074,22 +1308,46 @@ function pgAdapter(options) {
|
|
|
1074
1308
|
roundTrips: ROUND_TRIPS,
|
|
1075
1309
|
readSnapshot: READ_SNAPSHOT,
|
|
1076
1310
|
pipelining: pipelines === null ? "ordered" : PIPELINING,
|
|
1311
|
+
connectionIdentity: connection,
|
|
1312
|
+
...params.topLevel !== true || deferred === null ? {} : {
|
|
1313
|
+
deferStatement: (statement) => {
|
|
1314
|
+
budget.assertUsable({ stage: "statement" });
|
|
1315
|
+
deferred.defer({ text: statement.text, values: prepareBatchValues({ values: statement.values }) });
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
...params.commits !== true || deferred === null || scope.depth !== 0 || final !== undefined ? {} : {
|
|
1319
|
+
withCommit: (options) => createClientAdapter({
|
|
1320
|
+
dispatch,
|
|
1321
|
+
scope,
|
|
1322
|
+
budget,
|
|
1323
|
+
deferred,
|
|
1324
|
+
cancellation,
|
|
1325
|
+
pipelines,
|
|
1326
|
+
...laneOf,
|
|
1327
|
+
connection,
|
|
1328
|
+
final: { used: false, ...options?.rows === "discard" ? { discardRows: true } : {} }
|
|
1329
|
+
})
|
|
1330
|
+
},
|
|
1077
1331
|
...pipelines === null ? {} : {
|
|
1078
1332
|
...params.ordered === true ? { orderedPipeline: true } : {},
|
|
1079
1333
|
pipeline: ({ lanes: count, ordered }) => {
|
|
1080
1334
|
budget.assertUsable({ stage: "statement" });
|
|
1335
|
+
if (lane?.seat !== undefined)
|
|
1336
|
+
throw laneGroupMisuse({ message: "a sub-lane opens no pipeline scope" });
|
|
1337
|
+
if (lane?.group !== undefined)
|
|
1338
|
+
throw laneGroupMisuse({ message: "a lane opens no pipeline scope while its group is open" });
|
|
1081
1339
|
if (!Number.isSafeInteger(count) || count < 1) {
|
|
1082
|
-
throw new
|
|
1340
|
+
throw new VibeError7({ code: "VIBE_VALIDATION", message: "pipeline() needs a positive whole number of lanes", meta: { lanes: count } });
|
|
1083
1341
|
}
|
|
1084
1342
|
if (ordered === true && params.ordered !== true) {
|
|
1085
|
-
throw new
|
|
1343
|
+
throw new VibeError7({
|
|
1086
1344
|
code: "VIBE_VALIDATION",
|
|
1087
|
-
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
|
|
1345
|
+
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)",
|
|
1088
1346
|
meta: { ordered: true, reason: "ordered-pipeline-undeclared" }
|
|
1089
1347
|
});
|
|
1090
1348
|
}
|
|
1091
1349
|
const laneHandle = (opened) => ({
|
|
1092
|
-
adapter: createClientAdapter({ dispatch, scope, budget, deferred, cancellation, pipelines, lane: opened }),
|
|
1350
|
+
adapter: createClientAdapter({ dispatch, scope, budget, deferred, cancellation, pipelines, lane: opened, connection, ...params.commits === true && opened.seat === undefined ? { commits: true } : {} }),
|
|
1093
1351
|
close: () => {
|
|
1094
1352
|
pipelines.close(opened);
|
|
1095
1353
|
},
|
|
@@ -1098,6 +1356,19 @@ function pgAdapter(options) {
|
|
|
1098
1356
|
budget.assertUsable({ stage: "statement" });
|
|
1099
1357
|
return laneHandle(pipelines.follow(opened));
|
|
1100
1358
|
}
|
|
1359
|
+
},
|
|
1360
|
+
...opened.order !== undefined || opened.seat !== undefined || lane !== undefined ? {} : {
|
|
1361
|
+
group: (options) => {
|
|
1362
|
+
const opening = pipelines.group(opened, options?.final === true && params.commits === true);
|
|
1363
|
+
if (opening === undefined)
|
|
1364
|
+
return;
|
|
1365
|
+
return {
|
|
1366
|
+
lane: () => laneHandle(pipelines.seat(opening)),
|
|
1367
|
+
seal: () => {
|
|
1368
|
+
pipelines.seal(opening);
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1101
1372
|
}
|
|
1102
1373
|
});
|
|
1103
1374
|
return (ordered === true ? pipelines.openOrdered(count) : pipelines.open(count)).map(laneHandle);
|
|
@@ -1114,7 +1385,7 @@ function pgAdapter(options) {
|
|
|
1114
1385
|
await waitAsLane(turn);
|
|
1115
1386
|
budget.assertUsable({ stage: "statement" });
|
|
1116
1387
|
assertReadExecution2({ ...execParams, support: cancellation.support, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1117
|
-
const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl, ...laneOf }) };
|
|
1388
|
+
const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl, ...laneOf, ...finalOf() }) };
|
|
1118
1389
|
return runExecute({ queryable, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
|
|
1119
1390
|
} });
|
|
1120
1391
|
},
|
|
@@ -1128,6 +1399,10 @@ function pgAdapter(options) {
|
|
|
1128
1399
|
async transaction(fn, nestedOptions) {
|
|
1129
1400
|
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
1130
1401
|
budget.assertUsable({ stage: "nested" });
|
|
1402
|
+
if (lane?.group !== undefined)
|
|
1403
|
+
throw laneGroupMisuse({ message: "a lane opens no savepoint while its group is open" });
|
|
1404
|
+
if (lane?.seat !== undefined)
|
|
1405
|
+
throw laneGroupMisuse({ message: "a sub-lane opens no savepoint" });
|
|
1131
1406
|
pipelines?.flush();
|
|
1132
1407
|
const leave = lane === undefined || pipelines === null ? ignore : pipelines.enter(lane);
|
|
1133
1408
|
return runNestedSavepoint({
|
|
@@ -1137,7 +1412,7 @@ function pgAdapter(options) {
|
|
|
1137
1412
|
body: (child) => {
|
|
1138
1413
|
leave();
|
|
1139
1414
|
const ordered = lane === undefined && params.ordered === true ? { ordered: true } : {};
|
|
1140
|
-
return fn(createClientAdapter({ dispatch, scope: child, budget, deferred, cancellation, pipelines, ...laneOf, ...ordered }));
|
|
1415
|
+
return fn(createClientAdapter({ dispatch, scope: child, budget, deferred, cancellation, pipelines, connection, ...laneOf, ...ordered }));
|
|
1141
1416
|
}
|
|
1142
1417
|
}).finally(leave);
|
|
1143
1418
|
},
|
|
@@ -1287,8 +1562,14 @@ function pgAdapter(options) {
|
|
|
1287
1562
|
const settingsStatement = settings.length === 0 ? undefined : renderTransactionSettings({ settings });
|
|
1288
1563
|
const client = await runMapped(() => getPool().connect());
|
|
1289
1564
|
const turns = createClientTurns();
|
|
1565
|
+
let gathering = null;
|
|
1566
|
+
let gatheringsPending = 0;
|
|
1567
|
+
const runTurn = (send) => {
|
|
1568
|
+
gathering = null;
|
|
1569
|
+
return turns.run(send);
|
|
1570
|
+
};
|
|
1290
1571
|
const onClient = {
|
|
1291
|
-
query: (config) =>
|
|
1572
|
+
query: (config) => runTurn(() => client.query(config)),
|
|
1292
1573
|
get pipeline() {
|
|
1293
1574
|
return client.pipeline;
|
|
1294
1575
|
},
|
|
@@ -1307,11 +1588,55 @@ function pgAdapter(options) {
|
|
|
1307
1588
|
const scope = createSavepointScope();
|
|
1308
1589
|
const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
|
|
1309
1590
|
const capable = supportsBatch({ client });
|
|
1310
|
-
const deferOpening = settingsStatement === undefined && capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
|
|
1591
|
+
const deferOpening = (settingsStatement === undefined || transactionOptions?.deferSettings === true) && capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
|
|
1311
1592
|
const opening = deferOpening ? [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]] : [];
|
|
1312
|
-
const deferred = createDeferredControl({ opening });
|
|
1593
|
+
const deferred = createDeferredControl({ opening, ...budget.totalMs === null ? { deferReleases: true } : {} });
|
|
1594
|
+
const settingsDeferred = deferOpening && settingsStatement !== undefined;
|
|
1595
|
+
if (settingsDeferred)
|
|
1596
|
+
deferred.defer({ text: settingsStatement.text, values: prepareBatchValues({ values: settingsStatement.values }) });
|
|
1597
|
+
const openingLength = opening.length + (settingsDeferred ? 1 : 0);
|
|
1598
|
+
const openingStepError = (index, error) => {
|
|
1599
|
+
const mapped = mapPgDriverError({ error });
|
|
1600
|
+
if (!settingsDeferred || index !== opening.length || typeof mapped.meta.sqlstate !== "string")
|
|
1601
|
+
return mapped;
|
|
1602
|
+
return transactionSettingRefusedError({ provider: PROVIDER, settings, setting: findRefusedSetting({ settings, cause: mapped }), cause: mapped, deferred: true });
|
|
1603
|
+
};
|
|
1313
1604
|
let openingFailure;
|
|
1314
1605
|
let inFlight = null;
|
|
1606
|
+
const commits = capable && budget.totalMs === null && !((client.connectionParameters?.query_timeout ?? 0) > 0);
|
|
1607
|
+
let ridden;
|
|
1608
|
+
let ordinaryOpen = 0;
|
|
1609
|
+
const ridesNow = () => commits && ridden === undefined && !cancellationQueue && !cancellationAttempted && connectionFailure === undefined && openingFailure === undefined && scope.state.open === null && scope.state.aborted === null && scope.state.poisoned === null;
|
|
1610
|
+
const settleRidden = (entry, outcome, commitIndex) => {
|
|
1611
|
+
const step = outcome.steps[commitIndex];
|
|
1612
|
+
const answeredError = step?.status === "failed" ? mapPgDriverError({ error: step.error }) : undefined;
|
|
1613
|
+
if (step === undefined || answeredError !== undefined && isConnectionLoss({ error: answeredError })) {
|
|
1614
|
+
commitProgress = "unanswered";
|
|
1615
|
+
discard = true;
|
|
1616
|
+
budget.close({ outcome: "unknown" });
|
|
1617
|
+
entry.state = "lost";
|
|
1618
|
+
entry.error = commitOutcomeUnknownError({ cause: answeredError ?? outcome.transportError ?? firstBatchFailure({ outcome })?.error });
|
|
1619
|
+
} else if (step.status === "skipped") {
|
|
1620
|
+
commitProgress = "not-sent";
|
|
1621
|
+
entry.state = "skipped";
|
|
1622
|
+
} else if (step.status === "failed" || step.result.command === "ROLLBACK") {
|
|
1623
|
+
commitProgress = "answered";
|
|
1624
|
+
entry.state = "failed";
|
|
1625
|
+
entry.error = answeredError ?? transactionAbortedError2({ cause: scope.state.aborted?.cause });
|
|
1626
|
+
} else {
|
|
1627
|
+
entry.state = "committed";
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
const trackRidden = (batch, commitIndex) => {
|
|
1631
|
+
commitProgress = "sent";
|
|
1632
|
+
const entry = { settled: Promise.resolve(), state: "in-flight" };
|
|
1633
|
+
entry.settled = batch.then((outcome) => {
|
|
1634
|
+
settleRidden(entry, outcome, commitIndex);
|
|
1635
|
+
}, (error) => {
|
|
1636
|
+
settleRidden(entry, { steps: [], transportError: error }, commitIndex);
|
|
1637
|
+
});
|
|
1638
|
+
ridden = entry;
|
|
1639
|
+
};
|
|
1315
1640
|
const sendWithControl = (dispatchParams) => {
|
|
1316
1641
|
const { config, cleanup } = dispatchParams;
|
|
1317
1642
|
const handOver = () => {
|
|
@@ -1320,9 +1645,11 @@ function pgAdapter(options) {
|
|
|
1320
1645
|
return cancellation.run({ config, execution: dispatchParams.execution, onReadControl: dispatchParams.onReadControl, send: () => onClient.query(config) });
|
|
1321
1646
|
};
|
|
1322
1647
|
if (cleanup !== true && cancellationAttempted)
|
|
1323
|
-
throw
|
|
1648
|
+
throw transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) });
|
|
1324
1649
|
if (cleanup !== true && openingFailure !== undefined)
|
|
1325
|
-
throw
|
|
1650
|
+
throw transactionAbortedError2({ cause: openingFailure });
|
|
1651
|
+
if (cleanup !== true && ridden !== undefined)
|
|
1652
|
+
throw commitSentRefusal();
|
|
1326
1653
|
let bound;
|
|
1327
1654
|
if (cleanup !== true && budget.totalMs !== null) {
|
|
1328
1655
|
if (scope.state.poisoned !== null)
|
|
@@ -1342,24 +1669,39 @@ function pgAdapter(options) {
|
|
|
1342
1669
|
});
|
|
1343
1670
|
}
|
|
1344
1671
|
const pendingControl = cleanup !== true && deferred.hasPending();
|
|
1345
|
-
|
|
1672
|
+
const rides = dispatchParams.commit === true && cleanup !== true && dispatchParams.batchable === true && bound === undefined && ordinaryOpen === 1 && ridesNow();
|
|
1673
|
+
if (!pendingControl && bound === undefined && !rides) {
|
|
1674
|
+
if ((gathering?.phase === "turn" || turns.outstanding() > 0) && gathers(dispatchParams) && deferred.opened())
|
|
1675
|
+
return gatherInTurn(dispatchParams);
|
|
1346
1676
|
return handOver();
|
|
1347
|
-
|
|
1677
|
+
}
|
|
1678
|
+
const statement = dispatchParams.batchable === true ? [{ text: config.text, values: prepareBatchValues({ values: config.values ?? [] }), ...rides && dispatchParams.discardRows === true ? { discardRows: true } : {} }] : [];
|
|
1679
|
+
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1348
1680
|
const carriesOpening = !deferred.opened();
|
|
1349
|
-
const control = pendingControl ? [...deferred.
|
|
1681
|
+
const control = pendingControl ? [...deferred.takeSteps()] : [];
|
|
1350
1682
|
if (bound !== undefined)
|
|
1351
|
-
control.push(bound);
|
|
1352
|
-
const openingSteps = carriesOpening ?
|
|
1353
|
-
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1683
|
+
control.push({ text: bound });
|
|
1684
|
+
const openingSteps = carriesOpening ? openingLength : 0;
|
|
1354
1685
|
if (statement.length > 0)
|
|
1355
1686
|
dispatchParams.sent?.();
|
|
1356
|
-
const
|
|
1687
|
+
const commitIndex = control.length + statement.length;
|
|
1688
|
+
const batch = runTurn(() => runBatch({ client, steps: [...control, ...statement, ...rides ? [COMMIT_STEP] : []], types: EXACT_TYPES }));
|
|
1689
|
+
if (rides)
|
|
1690
|
+
trackRidden(batch, commitIndex);
|
|
1691
|
+
const burst = batch.then((received) => {
|
|
1692
|
+
const outcome = rides ? beforeCommit({ outcome: received, commitIndex }) : received;
|
|
1357
1693
|
const failure = firstBatchFailure({ outcome });
|
|
1358
1694
|
if (failure !== undefined) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1695
|
+
const openingFailed = failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0;
|
|
1696
|
+
if (openingFailed) {
|
|
1697
|
+
openingFailure = openingStepError(failure.index, failure.error);
|
|
1698
|
+
}
|
|
1699
|
+
if (gatheringsPending > 0) {
|
|
1700
|
+
const mapped = openingFailed && openingFailure !== undefined ? openingFailure : mapPgDriverError({ error: failure.error });
|
|
1701
|
+
if (typeof mapped.meta.sqlstate === "string")
|
|
1702
|
+
markTransactionAborted({ scope, cause: mapped });
|
|
1361
1703
|
}
|
|
1362
|
-
throw failure.error;
|
|
1704
|
+
throw openingFailed && settingsDeferred && failure.index === opening.length && openingFailure !== undefined ? openingFailure : failure.error;
|
|
1363
1705
|
}
|
|
1364
1706
|
if (statement.length === 0)
|
|
1365
1707
|
return handOver();
|
|
@@ -1410,14 +1752,14 @@ function pgAdapter(options) {
|
|
|
1410
1752
|
});
|
|
1411
1753
|
return;
|
|
1412
1754
|
}
|
|
1413
|
-
const mapped = mapPgDriverError({ error: failure.error });
|
|
1755
|
+
const mapped = failure.index < openingSteps ? openingStepError(failure.index, failure.error) : mapPgDriverError({ error: failure.error });
|
|
1414
1756
|
if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0)
|
|
1415
1757
|
openingFailure = mapped;
|
|
1416
1758
|
if (typeof mapped.meta.sqlstate === "string")
|
|
1417
1759
|
markTransactionAborted({ scope, cause: mapped });
|
|
1418
1760
|
const answered = outcome.transportError === undefined;
|
|
1419
1761
|
if (failure.index < controlSteps) {
|
|
1420
|
-
const later = answered || openingFailure !== undefined ?
|
|
1762
|
+
const later = answered || openingFailure !== undefined ? transactionAbortedError2({ cause: openingFailure ?? mapped }) : mapped;
|
|
1421
1763
|
live.forEach((entry, index) => {
|
|
1422
1764
|
settleQueued(entry, { error: index === 0 ? mapped : later });
|
|
1423
1765
|
});
|
|
@@ -1428,7 +1770,7 @@ function pgAdapter(options) {
|
|
|
1428
1770
|
const position = controlSteps + index;
|
|
1429
1771
|
const step = outcome.steps[position];
|
|
1430
1772
|
if (step === undefined) {
|
|
1431
|
-
settleQueued(entry, { error: position === failure.index || openingFailure === undefined ? mapped :
|
|
1773
|
+
settleQueued(entry, { error: position === failure.index || openingFailure === undefined ? mapped : transactionAbortedError2({ cause: openingFailure }) });
|
|
1432
1774
|
} else if (step.status === "ok") {
|
|
1433
1775
|
settleQueued(entry, { result: step.result });
|
|
1434
1776
|
} else if (step.status === "failed") {
|
|
@@ -1437,12 +1779,36 @@ function pgAdapter(options) {
|
|
|
1437
1779
|
abortCause = error;
|
|
1438
1780
|
settleQueued(entry, { error });
|
|
1439
1781
|
} else {
|
|
1440
|
-
settleQueued(entry, { error:
|
|
1782
|
+
settleQueued(entry, { error: transactionAbortedError2({ cause: abortCause }) });
|
|
1441
1783
|
}
|
|
1442
1784
|
});
|
|
1443
1785
|
};
|
|
1444
|
-
const sendRefusal = () => cancellationAttempted ?
|
|
1445
|
-
const
|
|
1786
|
+
const sendRefusal = () => ridden !== undefined ? commitSentRefusal() : cancellationAttempted ? transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) }) : openingFailure !== undefined ? transactionAbortedError2({ cause: openingFailure }) : scope.state.poisoned ?? (scope.state.aborted === null ? undefined : transactionAbortedError2({ cause: scope.state.aborted.cause }));
|
|
1787
|
+
const ridesQueued = (live) => {
|
|
1788
|
+
const last = live.at(-1);
|
|
1789
|
+
if (last === undefined || last.commit !== true && last.lane.seat?.group.final !== true || queued.length > 0 || inFlight !== null || ordinaryOpen > 0 || !ridesNow())
|
|
1790
|
+
return false;
|
|
1791
|
+
const counts = new Map;
|
|
1792
|
+
for (const entry of live) {
|
|
1793
|
+
if (entry.commit !== true && entry.lane.order?.followed !== true && entry.lane.seat?.group.final !== true)
|
|
1794
|
+
return false;
|
|
1795
|
+
counts.set(entry.lane, (counts.get(entry.lane) ?? 0) + 1);
|
|
1796
|
+
}
|
|
1797
|
+
for (const open of lanes) {
|
|
1798
|
+
const count = counts.get(open) ?? 0;
|
|
1799
|
+
if (count > 0 && open.waiting === count)
|
|
1800
|
+
continue;
|
|
1801
|
+
const group = open.group;
|
|
1802
|
+
if (count === 0 && group?.final === true && group.sealed && open.waiting === 1 && live.some((entry) => entry.lane.seat?.group === group))
|
|
1803
|
+
continue;
|
|
1804
|
+
if (count === 0 && open.final === true && open.waiting === 0)
|
|
1805
|
+
continue;
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
return true;
|
|
1809
|
+
};
|
|
1810
|
+
const sendQueued = (entries, options) => {
|
|
1811
|
+
const inTurn = options?.inTurn === true;
|
|
1446
1812
|
const refusal = sendRefusal();
|
|
1447
1813
|
const live = [];
|
|
1448
1814
|
for (const entry of entries) {
|
|
@@ -1455,11 +1821,19 @@ function pgAdapter(options) {
|
|
|
1455
1821
|
}
|
|
1456
1822
|
if (live.length === 0)
|
|
1457
1823
|
return Promise.resolve();
|
|
1458
|
-
const carriesOpening = !deferred.opened();
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1824
|
+
const carriesOpening = !inTurn && !deferred.opened();
|
|
1825
|
+
const rides = !inTurn && live.length === entries.length && ridesQueued(live);
|
|
1826
|
+
const control = inTurn ? [] : deferred.takeSteps();
|
|
1827
|
+
const openingSteps = carriesOpening ? openingLength : 0;
|
|
1828
|
+
const commitIndex = control.length + live.length;
|
|
1829
|
+
const sendBatch = () => runBatch({ client, steps: [...control, ...live.map((entry) => ({ text: entry.text, values: entry.values })), ...rides ? [COMMIT_STEP] : []], types: EXACT_TYPES });
|
|
1830
|
+
const batch = inTurn ? new Promise((resolve) => {
|
|
1831
|
+
resolve(sendBatch());
|
|
1832
|
+
}) : runTurn(sendBatch);
|
|
1833
|
+
if (rides)
|
|
1834
|
+
trackRidden(batch, commitIndex);
|
|
1835
|
+
const burst = batch.then((received) => {
|
|
1836
|
+
distributeBurst({ outcome: rides ? beforeCommit({ outcome: received, commitIndex }) : received, live, controlSteps: control.length, openingSteps });
|
|
1463
1837
|
}).then(undefined, (error) => {
|
|
1464
1838
|
if (openingSteps > 0)
|
|
1465
1839
|
openingFailure ??= mapPgDriverError({ error });
|
|
@@ -1475,6 +1849,81 @@ function pgAdapter(options) {
|
|
|
1475
1849
|
}
|
|
1476
1850
|
return burst;
|
|
1477
1851
|
};
|
|
1852
|
+
const gathers = (dispatchParams) => dispatchParams.commit !== true && coalescable(dispatchParams);
|
|
1853
|
+
const closeGathering = () => {
|
|
1854
|
+
gathering = null;
|
|
1855
|
+
};
|
|
1856
|
+
const gatheredLane = { open: false, waiting: 0 };
|
|
1857
|
+
const joinGathering = (group, dispatchParams, values) => new Promise((resolve, reject) => {
|
|
1858
|
+
group.members.push({ params: dispatchParams, values, resolve, reject });
|
|
1859
|
+
});
|
|
1860
|
+
const sendGathered = (members, options) => sendQueued(members.map((member) => {
|
|
1861
|
+
gatheredLane.waiting += 1;
|
|
1862
|
+
return {
|
|
1863
|
+
text: member.params.config.text,
|
|
1864
|
+
values: member.values,
|
|
1865
|
+
...member.params.execution === undefined || options?.inTurn === true ? {} : { execution: member.params.execution },
|
|
1866
|
+
lane: gatheredLane,
|
|
1867
|
+
resolve: member.resolve,
|
|
1868
|
+
reject: member.reject,
|
|
1869
|
+
settled: false
|
|
1870
|
+
};
|
|
1871
|
+
}), options);
|
|
1872
|
+
const gatherBehind = (dispatchParams, behind) => {
|
|
1873
|
+
const values = prepareBatchValues({ values: dispatchParams.config.values ?? [] });
|
|
1874
|
+
const open = gathering;
|
|
1875
|
+
if (open !== null && open.phase === "opening" && open.behind === behind)
|
|
1876
|
+
return joinGathering(open, dispatchParams, values);
|
|
1877
|
+
const group = { phase: "opening", behind, members: [] };
|
|
1878
|
+
gathering = group;
|
|
1879
|
+
gatheringsPending += 1;
|
|
1880
|
+
const joined = joinGathering(group, dispatchParams, values);
|
|
1881
|
+
behind.then(() => {
|
|
1882
|
+
if (gathering === group)
|
|
1883
|
+
gathering = null;
|
|
1884
|
+
gatheringsPending -= 1;
|
|
1885
|
+
const [only, ...more] = group.members;
|
|
1886
|
+
if (only !== undefined && more.length === 0) {
|
|
1887
|
+
send(only.params).then(only.resolve, only.reject);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
sendGathered(group.members);
|
|
1891
|
+
});
|
|
1892
|
+
return joined;
|
|
1893
|
+
};
|
|
1894
|
+
const gatherInTurn = (dispatchParams) => {
|
|
1895
|
+
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1896
|
+
const values = prepareBatchValues({ values: dispatchParams.config.values ?? [] });
|
|
1897
|
+
const open = gathering;
|
|
1898
|
+
if (open !== null && open.phase === "turn")
|
|
1899
|
+
return joinGathering(open, dispatchParams, values);
|
|
1900
|
+
const group = { phase: "turn", members: [] };
|
|
1901
|
+
gatheringsPending += 1;
|
|
1902
|
+
const joined = joinGathering(group, dispatchParams, values);
|
|
1903
|
+
turns.run(() => sendTurnGroup(group));
|
|
1904
|
+
gathering = group;
|
|
1905
|
+
return joined;
|
|
1906
|
+
};
|
|
1907
|
+
const sendTurnGroup = (group) => {
|
|
1908
|
+
if (gathering === group)
|
|
1909
|
+
gathering = null;
|
|
1910
|
+
gatheringsPending -= 1;
|
|
1911
|
+
const [only, ...more] = group.members;
|
|
1912
|
+
if (only !== undefined && more.length === 0) {
|
|
1913
|
+
const query = new Promise((resolve) => {
|
|
1914
|
+
resolve(client.query(only.params.config));
|
|
1915
|
+
});
|
|
1916
|
+
query.then(only.resolve, only.reject);
|
|
1917
|
+
return query;
|
|
1918
|
+
}
|
|
1919
|
+
const prior = turns.lastFailure();
|
|
1920
|
+
if (prior !== undefined) {
|
|
1921
|
+
const mapped = mapPgDriverError({ error: prior.error });
|
|
1922
|
+
if (typeof mapped.meta.sqlstate === "string")
|
|
1923
|
+
markTransactionAborted({ scope, cause: mapped });
|
|
1924
|
+
}
|
|
1925
|
+
return sendGathered(group.members, { inTurn: true });
|
|
1926
|
+
};
|
|
1478
1927
|
const sendAlone = (entry, alone) => {
|
|
1479
1928
|
const refusal = sendRefusal();
|
|
1480
1929
|
if (refusal !== undefined) {
|
|
@@ -1524,7 +1973,7 @@ function pgAdapter(options) {
|
|
|
1524
1973
|
if (failed === undefined)
|
|
1525
1974
|
live.push(entry);
|
|
1526
1975
|
else
|
|
1527
|
-
settleQueued(entry, { error: sendRefusal() ??
|
|
1976
|
+
settleQueued(entry, { error: sendRefusal() ?? transactionAbortedError2({ cause: failed.cause }) });
|
|
1528
1977
|
}
|
|
1529
1978
|
if (live.length === 0)
|
|
1530
1979
|
return Promise.resolve();
|
|
@@ -1577,11 +2026,12 @@ function pgAdapter(options) {
|
|
|
1577
2026
|
const flushQueue = () => {
|
|
1578
2027
|
if (queued.length === 0)
|
|
1579
2028
|
return;
|
|
2029
|
+
closeGathering();
|
|
1580
2030
|
if (queued.some((entry) => entry.lane.order !== undefined)) {
|
|
1581
2031
|
flushOrdered();
|
|
1582
2032
|
return;
|
|
1583
2033
|
}
|
|
1584
|
-
const entries = queued;
|
|
2034
|
+
const entries = queued.some((entry) => entry.lane.seat !== undefined) ? seatOrder(queued) : queued;
|
|
1585
2035
|
queued = [];
|
|
1586
2036
|
if (inFlight === null)
|
|
1587
2037
|
sendQueued(entries);
|
|
@@ -1598,6 +2048,26 @@ function pgAdapter(options) {
|
|
|
1598
2048
|
return;
|
|
1599
2049
|
flushQueue();
|
|
1600
2050
|
};
|
|
2051
|
+
const seatOrder = (entries) => [...entries].sort((first, second) => (first.lane.seat?.index ?? -1) - (second.lane.seat?.index ?? -1));
|
|
2052
|
+
const endGroupIfOver = (opening) => {
|
|
2053
|
+
if (!opening.sealed || opening.live > 0 || opening.parent.group !== opening)
|
|
2054
|
+
return;
|
|
2055
|
+
opening.parent.group = undefined;
|
|
2056
|
+
opening.parent.waiting -= 1;
|
|
2057
|
+
};
|
|
2058
|
+
const closeLane = (lane) => {
|
|
2059
|
+
if (!lane.open)
|
|
2060
|
+
return;
|
|
2061
|
+
lane.open = false;
|
|
2062
|
+
lanes.delete(lane);
|
|
2063
|
+
if (lane.order !== undefined)
|
|
2064
|
+
refuseOrphaned();
|
|
2065
|
+
if (lane.seat !== undefined) {
|
|
2066
|
+
lane.seat.group.live -= 1;
|
|
2067
|
+
endGroupIfOver(lane.seat.group);
|
|
2068
|
+
}
|
|
2069
|
+
flushIfReady();
|
|
2070
|
+
};
|
|
1601
2071
|
const pipelines = {
|
|
1602
2072
|
open: (count) => Array.from({ length: count }, () => {
|
|
1603
2073
|
const opened = { open: true, waiting: 0 };
|
|
@@ -1629,15 +2099,7 @@ function pgAdapter(options) {
|
|
|
1629
2099
|
lanes.add(next);
|
|
1630
2100
|
return next;
|
|
1631
2101
|
},
|
|
1632
|
-
close:
|
|
1633
|
-
if (!lane.open)
|
|
1634
|
-
return;
|
|
1635
|
-
lane.open = false;
|
|
1636
|
-
lanes.delete(lane);
|
|
1637
|
-
if (lane.order !== undefined)
|
|
1638
|
-
refuseOrphaned();
|
|
1639
|
-
flushIfReady();
|
|
1640
|
-
},
|
|
2102
|
+
close: closeLane,
|
|
1641
2103
|
waitAs: ({ lane, wait }) => {
|
|
1642
2104
|
if (!lane.open)
|
|
1643
2105
|
return wait;
|
|
@@ -1664,7 +2126,43 @@ function pgAdapter(options) {
|
|
|
1664
2126
|
lane.waiting -= 1;
|
|
1665
2127
|
};
|
|
1666
2128
|
},
|
|
1667
|
-
flush: flushQueue
|
|
2129
|
+
flush: flushQueue,
|
|
2130
|
+
group: (lane, final) => {
|
|
2131
|
+
if (!lane.open || lane.order !== undefined || lane.seat !== undefined || lane.gate === true || lane.group !== undefined || lane.final === true)
|
|
2132
|
+
return;
|
|
2133
|
+
if (!capable || budget.totalMs !== null || cancellationQueue || sendRefusal() !== undefined)
|
|
2134
|
+
return;
|
|
2135
|
+
if ((client.connectionParameters?.query_timeout ?? 0) > 0)
|
|
2136
|
+
return;
|
|
2137
|
+
for (const other of lanes) {
|
|
2138
|
+
if (other !== lane && (other.final !== true || other.waiting > 0))
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const gate = { open: true, waiting: 0, gate: true };
|
|
2142
|
+
const opening = { parent: lane, gate, opened: 0, live: 0, sealed: false, final };
|
|
2143
|
+
lanes.add(gate);
|
|
2144
|
+
lane.group = opening;
|
|
2145
|
+
lane.waiting += 1;
|
|
2146
|
+
return opening;
|
|
2147
|
+
},
|
|
2148
|
+
seat: (opening) => {
|
|
2149
|
+
if (opening.sealed)
|
|
2150
|
+
throw laneGroupMisuse({ message: "a sub-lane is opened only before the group is sealed" });
|
|
2151
|
+
if (!opening.parent.open)
|
|
2152
|
+
throw laneGroupMisuse({ message: "a sub-lane of a closed lane" });
|
|
2153
|
+
const seat = { open: true, waiting: 0, seat: { group: opening, index: opening.opened, issued: false } };
|
|
2154
|
+
opening.opened += 1;
|
|
2155
|
+
opening.live += 1;
|
|
2156
|
+
lanes.add(seat);
|
|
2157
|
+
return seat;
|
|
2158
|
+
},
|
|
2159
|
+
seal: (opening) => {
|
|
2160
|
+
if (opening.sealed)
|
|
2161
|
+
return;
|
|
2162
|
+
opening.sealed = true;
|
|
2163
|
+
endGroupIfOver(opening);
|
|
2164
|
+
closeLane(opening.gate);
|
|
2165
|
+
}
|
|
1668
2166
|
};
|
|
1669
2167
|
const endPipeline = () => {
|
|
1670
2168
|
for (const open of lanes)
|
|
@@ -1674,9 +2172,11 @@ function pgAdapter(options) {
|
|
|
1674
2172
|
};
|
|
1675
2173
|
const enqueue = (dispatchParams, lane, alone) => {
|
|
1676
2174
|
if (cancellationAttempted)
|
|
1677
|
-
throw
|
|
2175
|
+
throw transactionAbortedError2({ cause: readCancelledError2({ stage: "running" }) });
|
|
1678
2176
|
if (openingFailure !== undefined)
|
|
1679
|
-
throw
|
|
2177
|
+
throw transactionAbortedError2({ cause: openingFailure });
|
|
2178
|
+
if (ridden !== undefined)
|
|
2179
|
+
throw commitSentRefusal();
|
|
1680
2180
|
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1681
2181
|
const values = alone === true ? [] : prepareBatchValues({ values: dispatchParams.config.values ?? [] });
|
|
1682
2182
|
const pending = new Promise((resolve, reject) => {
|
|
@@ -1686,6 +2186,7 @@ function pgAdapter(options) {
|
|
|
1686
2186
|
values,
|
|
1687
2187
|
...dispatchParams.execution === undefined ? {} : { execution: dispatchParams.execution },
|
|
1688
2188
|
...alone === true ? { alone: dispatchParams } : {},
|
|
2189
|
+
...dispatchParams.commit === true ? { commit: true } : {},
|
|
1689
2190
|
lane,
|
|
1690
2191
|
resolve,
|
|
1691
2192
|
reject,
|
|
@@ -1699,9 +2200,23 @@ function pgAdapter(options) {
|
|
|
1699
2200
|
flushIfReady();
|
|
1700
2201
|
return pending;
|
|
1701
2202
|
};
|
|
1702
|
-
const coalescable = (dispatchParams) => capable && budget.totalMs === null && !cancellationQueue && dispatchParams.batchable === true && dispatchParams.cleanup !== true && dispatchParams.stage === undefined && dispatchParams.sent === undefined && dispatchParams.execution?.cancellation !== "running";
|
|
2203
|
+
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";
|
|
1703
2204
|
const dispatch = (dispatchParams) => {
|
|
1704
2205
|
const lane = dispatchParams.lane?.open === true ? dispatchParams.lane : undefined;
|
|
2206
|
+
if (lane !== undefined && dispatchParams.cleanup !== true) {
|
|
2207
|
+
if (lane.group !== undefined)
|
|
2208
|
+
return Promise.reject(laneGroupMisuse({ message: "a lane issues nothing while its group is open" }));
|
|
2209
|
+
const seat = lane.seat;
|
|
2210
|
+
if (seat !== undefined) {
|
|
2211
|
+
if (seat.issued)
|
|
2212
|
+
return Promise.reject(laneGroupMisuse({ message: "a sub-lane issues ONE statement" }));
|
|
2213
|
+
if (!coalescable(dispatchParams))
|
|
2214
|
+
return Promise.reject(laneGroupMisuse({ message: "a sub-lane statement must be able to join a batch" }));
|
|
2215
|
+
seat.issued = true;
|
|
2216
|
+
}
|
|
2217
|
+
if (dispatchParams.commit === true)
|
|
2218
|
+
lane.final = true;
|
|
2219
|
+
}
|
|
1705
2220
|
const order = lane?.order;
|
|
1706
2221
|
if (lane !== undefined && order !== undefined && dispatchParams.cleanup !== true) {
|
|
1707
2222
|
return guarded(() => {
|
|
@@ -1728,16 +2243,32 @@ function pgAdapter(options) {
|
|
|
1728
2243
|
if (dispatchParams.execution?.cancellation === "running" && !cancellationQueue) {
|
|
1729
2244
|
flushQueue();
|
|
1730
2245
|
cancellationQueue = true;
|
|
2246
|
+
closeGathering();
|
|
1731
2247
|
tail = Promise.allSettled([...activeDispatches]);
|
|
1732
2248
|
}
|
|
1733
2249
|
if (budget.totalMs === null && !cancellationQueue) {
|
|
1734
|
-
|
|
2250
|
+
ordinaryOpen += 1;
|
|
2251
|
+
const behind = gathering?.phase === "opening" ? gathering.behind ?? null : inFlight;
|
|
2252
|
+
let pending;
|
|
2253
|
+
if (behind === null)
|
|
2254
|
+
pending = send(dispatchParams);
|
|
2255
|
+
else if (gathers(dispatchParams))
|
|
2256
|
+
pending = guarded(() => gatherBehind(dispatchParams, behind));
|
|
2257
|
+
else {
|
|
2258
|
+
closeGathering();
|
|
2259
|
+
pending = behind.then(() => send(dispatchParams));
|
|
2260
|
+
}
|
|
1735
2261
|
activeDispatches.add(pending);
|
|
1736
|
-
|
|
2262
|
+
const settle = () => {
|
|
2263
|
+
activeDispatches.delete(pending);
|
|
2264
|
+
ordinaryOpen -= 1;
|
|
2265
|
+
};
|
|
2266
|
+
pending.then(settle, settle);
|
|
1737
2267
|
return pending;
|
|
1738
2268
|
}
|
|
1739
2269
|
const pending = tail.then(() => send(dispatchParams));
|
|
1740
2270
|
tail = pending.then(ignore, ignore);
|
|
2271
|
+
closeGathering();
|
|
1741
2272
|
return pending;
|
|
1742
2273
|
};
|
|
1743
2274
|
let discard = false;
|
|
@@ -1788,7 +2319,7 @@ function pgAdapter(options) {
|
|
|
1788
2319
|
throw transactionSettingRefusedError({ provider: PROVIDER, settings, setting, cause });
|
|
1789
2320
|
};
|
|
1790
2321
|
try {
|
|
1791
|
-
if (settingsStatement !== undefined)
|
|
2322
|
+
if (settingsStatement !== undefined && !settingsDeferred)
|
|
1792
2323
|
await openWithSettings(settingsStatement);
|
|
1793
2324
|
else if (!deferOpening)
|
|
1794
2325
|
await runMapped(() => onClient.query({ text: openText }));
|
|
@@ -1801,17 +2332,30 @@ function pgAdapter(options) {
|
|
|
1801
2332
|
deferred: capable ? deferred : null,
|
|
1802
2333
|
cancellation,
|
|
1803
2334
|
pipelines: capable ? pipelines : null,
|
|
1804
|
-
|
|
2335
|
+
connection: client,
|
|
2336
|
+
topLevel: true,
|
|
2337
|
+
ordered: capable && budget.totalMs === null && !((client.connectionParameters?.query_timeout ?? 0) > 0),
|
|
2338
|
+
commits
|
|
1805
2339
|
}))
|
|
1806
2340
|
});
|
|
1807
2341
|
endPipeline();
|
|
2342
|
+
const riding = ridden;
|
|
2343
|
+
if (riding !== undefined) {
|
|
2344
|
+
await riding.settled;
|
|
2345
|
+
if (riding.state === "committed") {
|
|
2346
|
+
budget.close({ outcome: "committed" });
|
|
2347
|
+
return result;
|
|
2348
|
+
}
|
|
2349
|
+
throw riding.state === "skipped" ? transactionAbortedError2({ cause: scope.state.aborted?.cause }) : riding.error;
|
|
2350
|
+
}
|
|
1808
2351
|
budget.assertUsable({ stage: "commit" });
|
|
1809
2352
|
if (!deferred.opened()) {
|
|
1810
2353
|
budget.close({ outcome: "committed" });
|
|
1811
2354
|
return result;
|
|
1812
2355
|
}
|
|
1813
2356
|
if (openingFailure !== undefined)
|
|
1814
|
-
throw
|
|
2357
|
+
throw transactionAbortedError2({ cause: openingFailure });
|
|
2358
|
+
deferred.dropReleases();
|
|
1815
2359
|
const commit = await runMapped(() => dispatch({
|
|
1816
2360
|
config: { text: "COMMIT" },
|
|
1817
2361
|
stage: "commit",
|
|
@@ -1835,12 +2379,20 @@ function pgAdapter(options) {
|
|
|
1835
2379
|
});
|
|
1836
2380
|
if (finalResult({ result: commit }).command === "ROLLBACK") {
|
|
1837
2381
|
commitProgress = "answered";
|
|
1838
|
-
throw
|
|
2382
|
+
throw transactionAbortedError2({ cause: scope.state.aborted?.cause });
|
|
1839
2383
|
}
|
|
1840
2384
|
budget.close({ outcome: "committed" });
|
|
1841
2385
|
return result;
|
|
1842
|
-
} catch (
|
|
2386
|
+
} catch (caught) {
|
|
1843
2387
|
endPipeline();
|
|
2388
|
+
const riding = ridden;
|
|
2389
|
+
if (riding !== undefined)
|
|
2390
|
+
await riding.settled;
|
|
2391
|
+
if (riding?.state === "committed") {
|
|
2392
|
+
budget.close({ outcome: "committed" });
|
|
2393
|
+
throw caught;
|
|
2394
|
+
}
|
|
2395
|
+
const error = riding?.state === "lost" ? riding.error : caught;
|
|
1844
2396
|
if (isSavepointRollbackFailedError(error) || scope.state.poisoned !== null) {
|
|
1845
2397
|
discard = true;
|
|
1846
2398
|
budget.close({ outcome: "unknown" });
|
|
@@ -1873,10 +2425,57 @@ function pgAdapter(options) {
|
|
|
1873
2425
|
}
|
|
1874
2426
|
},
|
|
1875
2427
|
async connect() {
|
|
1876
|
-
const
|
|
1877
|
-
|
|
2428
|
+
const opened = await Promise.allSettled(Array.from({ length: Math.max(1, poolMin ?? 0) }, () => runMapped(() => getPool().connect())));
|
|
2429
|
+
for (const outcome of opened) {
|
|
2430
|
+
if (outcome.status === "fulfilled")
|
|
2431
|
+
outcome.value.release();
|
|
2432
|
+
}
|
|
2433
|
+
const failed = opened.find((outcome) => outcome.status === "rejected");
|
|
2434
|
+
if (failed !== undefined)
|
|
2435
|
+
throw failed.reason;
|
|
1878
2436
|
}
|
|
1879
2437
|
};
|
|
2438
|
+
const sendConnectionGroup = async (entries) => {
|
|
2439
|
+
const rootPool = getPool();
|
|
2440
|
+
const client = rootViaPoolQuery ? undefined : await runMapped(() => rootPool.connect());
|
|
2441
|
+
if (client === undefined || !hasReadyForQuerySeam({ client }) || !supportsBatch({ client }) || (client.connectionParameters?.query_timeout ?? 0) > 0) {
|
|
2442
|
+
client?.release();
|
|
2443
|
+
const steps = [];
|
|
2444
|
+
for (const entry of entries) {
|
|
2445
|
+
if (steps.some((step) => step.status === "failed")) {
|
|
2446
|
+
steps.push({ status: "skipped" });
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
steps.push(await root.execute(entry.call).then((rows) => ({ status: "ok", result: { rows, rowCount: rows.length } }), (error) => ({ status: "failed", error })));
|
|
2450
|
+
}
|
|
2451
|
+
return { steps };
|
|
2452
|
+
}
|
|
2453
|
+
const seam = observeReadyForQuery({ client });
|
|
2454
|
+
let reuse = false;
|
|
2455
|
+
const outcome = await runBatch({ client, steps: entries.map((entry) => entry.step), types: EXACT_TYPES }).then((settled) => settled, (error) => ({ steps: [], transportError: error }));
|
|
2456
|
+
const failure = firstBatchFailure({ outcome });
|
|
2457
|
+
if (failure === undefined)
|
|
2458
|
+
reuse = true;
|
|
2459
|
+
else if (outcome.transportError === undefined)
|
|
2460
|
+
reuse = await seam.reusableAfter({ error: mapPgDriverError({ error: failure.error }), named: false });
|
|
2461
|
+
seam.detach();
|
|
2462
|
+
client.release(!reuse);
|
|
2463
|
+
return outcome;
|
|
2464
|
+
};
|
|
2465
|
+
const connectionGroupBase = {
|
|
2466
|
+
dialect: DIALECT,
|
|
2467
|
+
provider: PROVIDER,
|
|
2468
|
+
wire: WIRE,
|
|
2469
|
+
budgets: BUDGETS,
|
|
2470
|
+
roundTrips: ROUND_TRIPS,
|
|
2471
|
+
formatArrayParam: (values) => new PgArrayParam(values)
|
|
2472
|
+
};
|
|
2473
|
+
const connectionGroupDriver = {
|
|
2474
|
+
base: connectionGroupBase,
|
|
2475
|
+
prepare: (call) => ({ text: call.text, values: prepareBatchValues({ values: serializeParams({ values: call.values }) }) }),
|
|
2476
|
+
send: (entries) => lifecycle.admit({ run: () => sendConnectionGroup(entries) }),
|
|
2477
|
+
mapError: (error) => mapPgDriverError({ error })
|
|
2478
|
+
};
|
|
1880
2479
|
return {
|
|
1881
2480
|
dialect: DIALECT,
|
|
1882
2481
|
provider: PROVIDER,
|
|
@@ -1888,6 +2487,15 @@ function pgAdapter(options) {
|
|
|
1888
2487
|
pipelining: PIPELINING,
|
|
1889
2488
|
transactionOutcome: "reported",
|
|
1890
2489
|
transactionSettings: "transaction-local",
|
|
2490
|
+
...transactionsTakeOneRoundTrip({ address }) ? {
|
|
2491
|
+
oneRoundTripTransactions: true,
|
|
2492
|
+
connectionGroup: () => {
|
|
2493
|
+
const closed = lifecycle.refusal();
|
|
2494
|
+
if (closed !== undefined)
|
|
2495
|
+
throw closed;
|
|
2496
|
+
return rootViaPoolQuery ? undefined : openConnectionGroup({ driver: connectionGroupDriver });
|
|
2497
|
+
}
|
|
2498
|
+
} : {},
|
|
1891
2499
|
withSession({ run }) {
|
|
1892
2500
|
return lifecycle.admit({ run: () => root.withSession({ run: (executor) => lifecycle.inCallback({ run: () => run(executor) }) }) });
|
|
1893
2501
|
},
|
|
@@ -1940,4 +2548,4 @@ export {
|
|
|
1940
2548
|
validateTransactionDeadline2 as validateTransactionDeadline
|
|
1941
2549
|
};
|
|
1942
2550
|
|
|
1943
|
-
//# debugId=
|
|
2551
|
+
//# debugId=96B1BE63991D190764756E2164756E21
|