@vibeorm/adapter-pg 4.1.0 → 4.3.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 +15 -4
- package/dist/client-turns.d.ts +34 -0
- package/dist/errors.d.ts +8 -0
- package/dist/index.d.ts +21 -5
- package/dist/index.js +668 -25
- package/dist/index.js.map +10 -8
- package/dist/pool-config.d.ts +12 -0
- package/dist/statement-cache.d.ts +24 -5
- package/dist/timestamp-text.d.ts +34 -0
- package/dist/transaction-sql.d.ts +22 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -16,8 +16,12 @@ import {
|
|
|
16
16
|
transactionLifecycleRefusal,
|
|
17
17
|
createSavepointScope,
|
|
18
18
|
engineStatementBudgetMs,
|
|
19
|
+
findRefusedSetting,
|
|
19
20
|
isSavepointRollbackFailedError,
|
|
20
21
|
readOnlyRequested,
|
|
22
|
+
readTransactionSettings,
|
|
23
|
+
renderTransactionSettings,
|
|
24
|
+
transactionSettingRefusedError,
|
|
21
25
|
runNestedSavepoint,
|
|
22
26
|
runRootScope,
|
|
23
27
|
scopeTurn,
|
|
@@ -182,6 +186,26 @@ function firstBatchFailure(params) {
|
|
|
182
186
|
return;
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
// packages/adapter-pg/src/client-turns.ts
|
|
190
|
+
function createClientTurns() {
|
|
191
|
+
let outstanding = 0;
|
|
192
|
+
let previous = Promise.resolve();
|
|
193
|
+
const release = () => {
|
|
194
|
+
outstanding -= 1;
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
run: (send) => {
|
|
198
|
+
const running = outstanding === 0 ? new Promise((resolve) => {
|
|
199
|
+
resolve(send());
|
|
200
|
+
}) : previous.then(send);
|
|
201
|
+
outstanding += 1;
|
|
202
|
+
previous = running.then(release, release);
|
|
203
|
+
return running;
|
|
204
|
+
},
|
|
205
|
+
outstanding: () => outstanding
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
185
209
|
// packages/adapter-pg/src/read-cancellation.ts
|
|
186
210
|
import pg2 from "pg";
|
|
187
211
|
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
@@ -221,6 +245,11 @@ var PG_SQLSTATE_REASONS = Object.freeze({
|
|
|
221
245
|
"40001": "serialization-failure",
|
|
222
246
|
"25006": "read-only-transaction"
|
|
223
247
|
});
|
|
248
|
+
var NAMED_STATEMENT_POOLER_HINT = "Hint: with preparedStatements: true every query must reach the backend that prepared its name. " + "Use a direct connection, session pooling, or PgBouncer 1.21+ with max_prepared_statements > 0; " + "behind a transaction-mode pooler without prepared-statement support, turn preparedStatements off.";
|
|
249
|
+
var PG_SQLSTATE_HINTS = Object.freeze({
|
|
250
|
+
"26000": NAMED_STATEMENT_POOLER_HINT,
|
|
251
|
+
"42P05": NAMED_STATEMENT_POOLER_HINT
|
|
252
|
+
});
|
|
224
253
|
var SQLSTATE_PATTERN = /^(?=[A-Z]*\d)[0-9A-Z]{5}$/;
|
|
225
254
|
function readStringField(params) {
|
|
226
255
|
const value = params.source[params.key];
|
|
@@ -255,7 +284,9 @@ function mapPgDriverError(params) {
|
|
|
255
284
|
const reason = sqlstate === undefined ? undefined : PG_SQLSTATE_REASONS[sqlstate];
|
|
256
285
|
if (reason !== undefined)
|
|
257
286
|
meta.reason = reason;
|
|
258
|
-
const
|
|
287
|
+
const driverMessage = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown postgres driver error");
|
|
288
|
+
const hint = sqlstate === undefined ? undefined : PG_SQLSTATE_HINTS[sqlstate];
|
|
289
|
+
const message = hint === undefined ? driverMessage : `${driverMessage}. ${hint}`;
|
|
259
290
|
return new VibeError({ code, message, meta, cause: error });
|
|
260
291
|
}
|
|
261
292
|
async function runMapped(fn) {
|
|
@@ -515,8 +546,18 @@ function placePlanCacheMode(params) {
|
|
|
515
546
|
url.searchParams.set("options", merged);
|
|
516
547
|
return { connectionString: url.toString(), options: merged };
|
|
517
548
|
}
|
|
549
|
+
function assertIdleTimeout(params) {
|
|
550
|
+
const { value } = params;
|
|
551
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2147483647)
|
|
552
|
+
return value;
|
|
553
|
+
throw new VibeError3({
|
|
554
|
+
code: "VIBE_CONFIG",
|
|
555
|
+
message: "pgAdapter `idleTimeout` must be integer milliseconds from 0 through 2,147,483,647 (0 keeps idle connections open).",
|
|
556
|
+
meta: { adapter: "pg", reason: "invalid-idle-timeout", option: "idleTimeout" }
|
|
557
|
+
});
|
|
558
|
+
}
|
|
518
559
|
function buildPoolConfig(params) {
|
|
519
|
-
const { max, statementTimeout, connectionTimeout } = params;
|
|
560
|
+
const { max, statementTimeout, connectionTimeout, idleTimeout } = params;
|
|
520
561
|
const placement = placePlanCacheMode({
|
|
521
562
|
connectionString: params.connectionString,
|
|
522
563
|
planCacheMode: params.planCacheMode ?? "auto"
|
|
@@ -526,21 +567,43 @@ function buildPoolConfig(params) {
|
|
|
526
567
|
config.statement_timeout = statementTimeout;
|
|
527
568
|
if (connectionTimeout !== undefined)
|
|
528
569
|
config.connectionTimeoutMillis = connectionTimeout;
|
|
570
|
+
if (idleTimeout !== undefined)
|
|
571
|
+
config.idleTimeoutMillis = idleTimeout;
|
|
529
572
|
if (placement.options !== undefined)
|
|
530
573
|
config.options = placement.options;
|
|
531
574
|
return config;
|
|
532
575
|
}
|
|
533
576
|
|
|
534
577
|
// packages/adapter-pg/src/statement-cache.ts
|
|
578
|
+
import { createHash } from "crypto";
|
|
535
579
|
var DEFAULT_PROMOTE_AFTER = 2;
|
|
536
580
|
var SIGHTINGS_PER_NAME = 4;
|
|
581
|
+
var NAME_PREFIX = "vibeorm_";
|
|
582
|
+
var BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
583
|
+
function statementNameFor(params) {
|
|
584
|
+
const digest = createHash("sha256").update(params.text, "utf8").digest();
|
|
585
|
+
let name = NAME_PREFIX;
|
|
586
|
+
let buffer = 0;
|
|
587
|
+
let bits = 0;
|
|
588
|
+
for (const byte of digest) {
|
|
589
|
+
buffer = buffer << 8 | byte;
|
|
590
|
+
bits += 8;
|
|
591
|
+
while (bits >= 5) {
|
|
592
|
+
bits -= 5;
|
|
593
|
+
name += BASE32_ALPHABET[buffer >>> bits & 31];
|
|
594
|
+
}
|
|
595
|
+
buffer &= (1 << bits) - 1;
|
|
596
|
+
}
|
|
597
|
+
if (bits > 0)
|
|
598
|
+
name += BASE32_ALPHABET[buffer << 5 - bits & 31];
|
|
599
|
+
return name;
|
|
600
|
+
}
|
|
537
601
|
function createStatementNameCache(params) {
|
|
538
602
|
const max = Math.max(0, params.max);
|
|
539
603
|
const promoteAfter = Math.max(1, params.promoteAfter ?? DEFAULT_PROMOTE_AFTER);
|
|
540
604
|
const sightingsMax = max * SIGHTINGS_PER_NAME;
|
|
541
605
|
const names = new Map;
|
|
542
606
|
const sightings = new Map;
|
|
543
|
-
let counter = 0;
|
|
544
607
|
return {
|
|
545
608
|
getName(nameParams) {
|
|
546
609
|
const { text } = nameParams;
|
|
@@ -562,7 +625,7 @@ function createStatementNameCache(params) {
|
|
|
562
625
|
return;
|
|
563
626
|
}
|
|
564
627
|
sightings.delete(text);
|
|
565
|
-
const name =
|
|
628
|
+
const name = statementNameFor({ text });
|
|
566
629
|
names.set(text, name);
|
|
567
630
|
return name;
|
|
568
631
|
},
|
|
@@ -575,6 +638,86 @@ function createStatementNameCache(params) {
|
|
|
575
638
|
};
|
|
576
639
|
}
|
|
577
640
|
|
|
641
|
+
// packages/adapter-pg/src/timestamp-text.ts
|
|
642
|
+
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
643
|
+
var DIGIT_POSITIONS = [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18];
|
|
644
|
+
function parsePlainTimestampText(params) {
|
|
645
|
+
const { text, zoned } = params;
|
|
646
|
+
const length = text.length;
|
|
647
|
+
if (length < 19 || text.charCodeAt(4) !== 45 || text.charCodeAt(7) !== 45 || text.charCodeAt(10) !== 32 || text.charCodeAt(13) !== 58 || text.charCodeAt(16) !== 58) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
for (let position = 0;position < DIGIT_POSITIONS.length; position++) {
|
|
651
|
+
const code = text.charCodeAt(DIGIT_POSITIONS[position]);
|
|
652
|
+
if (code < 48 || code > 57)
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const year = (text.charCodeAt(0) - 48) * 1000 + (text.charCodeAt(1) - 48) * 100 + (text.charCodeAt(2) - 48) * 10 + (text.charCodeAt(3) - 48);
|
|
656
|
+
const month = (text.charCodeAt(5) - 48) * 10 + (text.charCodeAt(6) - 48);
|
|
657
|
+
const day = (text.charCodeAt(8) - 48) * 10 + (text.charCodeAt(9) - 48);
|
|
658
|
+
const hour = (text.charCodeAt(11) - 48) * 10 + (text.charCodeAt(12) - 48);
|
|
659
|
+
const minute = (text.charCodeAt(14) - 48) * 10 + (text.charCodeAt(15) - 48);
|
|
660
|
+
const second = (text.charCodeAt(17) - 48) * 10 + (text.charCodeAt(18) - 48);
|
|
661
|
+
if (year < 1000 || month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59)
|
|
662
|
+
return;
|
|
663
|
+
if (day > 28) {
|
|
664
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
665
|
+
const days = month === 2 ? leap ? 29 : 28 : DAYS_IN_MONTH[month - 1];
|
|
666
|
+
if (day > days)
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
let index = 19;
|
|
670
|
+
let milliseconds = 0;
|
|
671
|
+
if (index < length && text.charCodeAt(index) === 46) {
|
|
672
|
+
index++;
|
|
673
|
+
const start = index;
|
|
674
|
+
while (index < length) {
|
|
675
|
+
const code = text.charCodeAt(index);
|
|
676
|
+
if (code < 48 || code > 57)
|
|
677
|
+
break;
|
|
678
|
+
if (index - start < 3)
|
|
679
|
+
milliseconds = milliseconds * 10 + (code - 48);
|
|
680
|
+
index++;
|
|
681
|
+
}
|
|
682
|
+
const digits = index - start;
|
|
683
|
+
if (digits === 0 || digits > 6)
|
|
684
|
+
return;
|
|
685
|
+
if (digits === 1)
|
|
686
|
+
milliseconds *= 100;
|
|
687
|
+
else if (digits === 2)
|
|
688
|
+
milliseconds *= 10;
|
|
689
|
+
}
|
|
690
|
+
let offsetMinutes = 0;
|
|
691
|
+
if (zoned) {
|
|
692
|
+
if (index + 3 > length)
|
|
693
|
+
return;
|
|
694
|
+
const sign = text.charCodeAt(index);
|
|
695
|
+
if (sign !== 43 && sign !== 45)
|
|
696
|
+
return;
|
|
697
|
+
const hours1 = text.charCodeAt(index + 1) - 48;
|
|
698
|
+
const hours2 = text.charCodeAt(index + 2) - 48;
|
|
699
|
+
if (hours1 < 0 || hours1 > 9 || hours2 < 0 || hours2 > 9)
|
|
700
|
+
return;
|
|
701
|
+
offsetMinutes = (hours1 * 10 + hours2) * 60;
|
|
702
|
+
index += 3;
|
|
703
|
+
if (index < length) {
|
|
704
|
+
if (index + 3 !== length || text.charCodeAt(index) !== 58)
|
|
705
|
+
return;
|
|
706
|
+
const minutes1 = text.charCodeAt(index + 1) - 48;
|
|
707
|
+
const minutes2 = text.charCodeAt(index + 2) - 48;
|
|
708
|
+
if (minutes1 < 0 || minutes1 > 9 || minutes2 < 0 || minutes2 > 9)
|
|
709
|
+
return;
|
|
710
|
+
offsetMinutes += minutes1 * 10 + minutes2;
|
|
711
|
+
index = length;
|
|
712
|
+
}
|
|
713
|
+
if (sign === 45)
|
|
714
|
+
offsetMinutes = -offsetMinutes;
|
|
715
|
+
}
|
|
716
|
+
if (index !== length)
|
|
717
|
+
return;
|
|
718
|
+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second, milliseconds) - offsetMinutes * 60000);
|
|
719
|
+
}
|
|
720
|
+
|
|
578
721
|
// packages/adapter-pg/src/transaction-sql.ts
|
|
579
722
|
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
580
723
|
import { refuseNestedTransactionOptions } from "@vibeorm/runtime/adapter-kit";
|
|
@@ -616,6 +759,12 @@ function classifyRawTransactionControl(params) {
|
|
|
616
759
|
return "close";
|
|
617
760
|
return null;
|
|
618
761
|
}
|
|
762
|
+
var NOT_PROVABLY_ONE_STATEMENT = /[;$\u0000]|--|\/\*/;
|
|
763
|
+
var OWN_TURN_VERBS = /^\s*(?:begin|start|commit|end|rollback|abort|savepoint|release|prepare|copy)\b/i;
|
|
764
|
+
function isSingleStatementText(params) {
|
|
765
|
+
const { text } = params;
|
|
766
|
+
return text.trim() !== "" && !NOT_PROVABLY_ONE_STATEMENT.test(text) && !OWN_TURN_VERBS.test(text);
|
|
767
|
+
}
|
|
619
768
|
|
|
620
769
|
// packages/adapter-pg/src/index.ts
|
|
621
770
|
import { createSavepointCounter, nextSavepointName } from "@vibeorm/runtime/adapter-kit";
|
|
@@ -633,6 +782,16 @@ var MAX_STATEMENT_TIMEOUT_MS = 2147483647;
|
|
|
633
782
|
var ignore = () => {
|
|
634
783
|
return;
|
|
635
784
|
};
|
|
785
|
+
function predecessorClosedError() {
|
|
786
|
+
return new VibeError5({
|
|
787
|
+
code: "VIBE_TRANSACTION",
|
|
788
|
+
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
|
+
meta: { reason: "pipeline-predecessor-closed" }
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
function orderedLaneMisuse(params) {
|
|
793
|
+
return new VibeError5({ code: "VIBE_INTERNAL", message: `pipeline(): ${params.message}`, meta: { reason: "pipeline-ordered-lane" } });
|
|
794
|
+
}
|
|
636
795
|
function finalResult(params) {
|
|
637
796
|
return Array.isArray(params.result) ? params.result.at(-1) ?? { rows: [], rowCount: 0 } : params.result;
|
|
638
797
|
}
|
|
@@ -713,6 +872,8 @@ var DIALECT = "postgres";
|
|
|
713
872
|
var PROVIDER = "postgresql";
|
|
714
873
|
var WIRE = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
715
874
|
var ROUND_TRIPS = "network";
|
|
875
|
+
var READ_SNAPSHOT = Object.freeze({ isolationLevel: "RepeatableRead", accessMode: "readOnly" });
|
|
876
|
+
var PIPELINING = "batched";
|
|
716
877
|
var BUDGETS = Object.freeze({
|
|
717
878
|
acquireTimeout: true,
|
|
718
879
|
statementTimeout: "all-statements",
|
|
@@ -725,9 +886,14 @@ var TIMESTAMP_OID = 1114;
|
|
|
725
886
|
var TIMESTAMP_ARRAY_OID = 1115;
|
|
726
887
|
var DATE_OID = 1082;
|
|
727
888
|
var DATE_ARRAY_OID = 1182;
|
|
889
|
+
var TIMESTAMPTZ_OID = 1184;
|
|
890
|
+
var TIMESTAMPTZ_ARRAY_OID = 1185;
|
|
728
891
|
var driverTypeParser = pg3.types.getTypeParser;
|
|
729
892
|
var ZONE_LESS_TEXT = /^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)?$/;
|
|
730
893
|
function parseZoneLessAsUtc(value) {
|
|
894
|
+
const plain = parsePlainTimestampText({ text: value, zoned: false });
|
|
895
|
+
if (plain !== undefined)
|
|
896
|
+
return plain;
|
|
731
897
|
if (ZONE_LESS_TEXT.test(value)) {
|
|
732
898
|
const iso = value.includes(" ") ? `${value.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z` : value;
|
|
733
899
|
const parsed = new Date(iso);
|
|
@@ -746,6 +912,28 @@ function parseZoneLessArrayAsUtc(value) {
|
|
|
746
912
|
};
|
|
747
913
|
return mapElements(driverTypeParser(TEXT_ARRAY_OID, "text")(value));
|
|
748
914
|
}
|
|
915
|
+
var pgTypesArrayParser = (() => {
|
|
916
|
+
const candidate = pg3.types.arrayParser;
|
|
917
|
+
return typeof candidate?.create === "function" ? candidate : undefined;
|
|
918
|
+
})();
|
|
919
|
+
function recogniseBuiltinParser(params) {
|
|
920
|
+
const parser = driverTypeParser(params.oid, "text");
|
|
921
|
+
return parser.name === params.name && params.peers.every((peer) => driverTypeParser(peer, "text") === parser) ? parser : undefined;
|
|
922
|
+
}
|
|
923
|
+
var BUILTIN_TIMESTAMPTZ_PARSER = recogniseBuiltinParser({
|
|
924
|
+
oid: TIMESTAMPTZ_OID,
|
|
925
|
+
peers: [TIMESTAMP_OID, DATE_OID],
|
|
926
|
+
name: "parseDate"
|
|
927
|
+
});
|
|
928
|
+
var BUILTIN_TIMESTAMPTZ_ARRAY_PARSER = pgTypesArrayParser === undefined || BUILTIN_TIMESTAMPTZ_PARSER === undefined ? undefined : recogniseBuiltinParser({ oid: TIMESTAMPTZ_ARRAY_OID, peers: [TIMESTAMP_ARRAY_OID, DATE_ARRAY_OID], name: "parseDateArray" });
|
|
929
|
+
function parseTimestamptzText(value) {
|
|
930
|
+
return parsePlainTimestampText({ text: value, zoned: true }) ?? BUILTIN_TIMESTAMPTZ_PARSER(value);
|
|
931
|
+
}
|
|
932
|
+
function parseTimestamptzArrayText(value) {
|
|
933
|
+
if (!value)
|
|
934
|
+
return null;
|
|
935
|
+
return pgTypesArrayParser.create(value, (entry) => entry !== null ? parseTimestamptzText(entry) : entry).parse();
|
|
936
|
+
}
|
|
749
937
|
var EXACT_TYPES = {
|
|
750
938
|
getTypeParser(oid, format) {
|
|
751
939
|
if (format === undefined || format === "text") {
|
|
@@ -755,6 +943,14 @@ var EXACT_TYPES = {
|
|
|
755
943
|
return parseZoneLessAsUtc;
|
|
756
944
|
if (oid === TIMESTAMP_ARRAY_OID || oid === DATE_ARRAY_OID)
|
|
757
945
|
return parseZoneLessArrayAsUtc;
|
|
946
|
+
if (oid === TIMESTAMPTZ_OID) {
|
|
947
|
+
const driver = driverTypeParser(oid, "text");
|
|
948
|
+
return driver === BUILTIN_TIMESTAMPTZ_PARSER ? parseTimestamptzText : driver;
|
|
949
|
+
}
|
|
950
|
+
if (oid === TIMESTAMPTZ_ARRAY_OID) {
|
|
951
|
+
const driver = driverTypeParser(oid, "text");
|
|
952
|
+
return driver === BUILTIN_TIMESTAMPTZ_ARRAY_PARSER ? parseTimestamptzArrayText : driver;
|
|
953
|
+
}
|
|
758
954
|
}
|
|
759
955
|
return driverTypeParser(oid, format);
|
|
760
956
|
}
|
|
@@ -779,6 +975,7 @@ function pgAdapter(options) {
|
|
|
779
975
|
const usePrepared = options?.preparedStatements ?? false;
|
|
780
976
|
const stmtCacheMax = options?.stmtCacheMax ?? 1000;
|
|
781
977
|
const planCacheMode = options?.planCacheMode === undefined ? "auto" : assertPlanCacheMode({ value: options.planCacheMode });
|
|
978
|
+
const idleTimeout = "pool" in address || options?.idleTimeout === undefined ? undefined : assertIdleTimeout({ value: options.idleTimeout });
|
|
782
979
|
if (planCacheMode !== "auto" && "pool" in address) {
|
|
783
980
|
throw new VibeError5({
|
|
784
981
|
code: "VIBE_CONFIG",
|
|
@@ -804,6 +1001,7 @@ function pgAdapter(options) {
|
|
|
804
1001
|
max: options?.max ?? 10,
|
|
805
1002
|
...options?.statementTimeout === undefined ? {} : { statementTimeout: options.statementTimeout },
|
|
806
1003
|
...options?.connectionTimeout === undefined ? {} : { connectionTimeout: options.connectionTimeout },
|
|
1004
|
+
...idleTimeout === undefined ? {} : { idleTimeout },
|
|
807
1005
|
planCacheMode
|
|
808
1006
|
});
|
|
809
1007
|
const PoolCtor = pg3.Pool;
|
|
@@ -850,8 +1048,12 @@ function pgAdapter(options) {
|
|
|
850
1048
|
return { rows: final.rows, affectedRows: final.rowCount ?? 0 };
|
|
851
1049
|
}
|
|
852
1050
|
function createClientAdapter(params) {
|
|
853
|
-
const { dispatch, scope, budget, deferred, cancellation } = params;
|
|
854
|
-
const
|
|
1051
|
+
const { dispatch, scope, budget, deferred, cancellation, pipelines, lane } = params;
|
|
1052
|
+
const laneOf = lane === undefined ? {} : { lane };
|
|
1053
|
+
const waitAsLane = (wait) => lane === undefined || pipelines === null ? wait : pipelines.waitAs({ lane, wait });
|
|
1054
|
+
const rawClient = {
|
|
1055
|
+
query: (config) => dispatch({ config, batchable: (config.values?.length ?? 0) > 0 || isSingleStatementText({ text: config.text }), ...laneOf })
|
|
1056
|
+
};
|
|
855
1057
|
const savepointControl = (statement, cleanup) => {
|
|
856
1058
|
if (deferred !== null && deferred.routeSavepoint({ text: statement }) !== "send")
|
|
857
1059
|
return;
|
|
@@ -859,7 +1061,8 @@ function pgAdapter(options) {
|
|
|
859
1061
|
config: { text: statement },
|
|
860
1062
|
stage: "nested",
|
|
861
1063
|
batchable: true,
|
|
862
|
-
...cleanup ? { cleanup: true } : {}
|
|
1064
|
+
...cleanup ? { cleanup: true } : {},
|
|
1065
|
+
...laneOf
|
|
863
1066
|
}));
|
|
864
1067
|
};
|
|
865
1068
|
return {
|
|
@@ -869,6 +1072,37 @@ function pgAdapter(options) {
|
|
|
869
1072
|
budgets: BUDGETS,
|
|
870
1073
|
readCancellation: cancellation.support,
|
|
871
1074
|
roundTrips: ROUND_TRIPS,
|
|
1075
|
+
readSnapshot: READ_SNAPSHOT,
|
|
1076
|
+
pipelining: pipelines === null ? "ordered" : PIPELINING,
|
|
1077
|
+
...pipelines === null ? {} : {
|
|
1078
|
+
...params.ordered === true ? { orderedPipeline: true } : {},
|
|
1079
|
+
pipeline: ({ lanes: count, ordered }) => {
|
|
1080
|
+
budget.assertUsable({ stage: "statement" });
|
|
1081
|
+
if (!Number.isSafeInteger(count) || count < 1) {
|
|
1082
|
+
throw new VibeError5({ code: "VIBE_VALIDATION", message: "pipeline() needs a positive whole number of lanes", meta: { lanes: count } });
|
|
1083
|
+
}
|
|
1084
|
+
if (ordered === true && params.ordered !== true) {
|
|
1085
|
+
throw new VibeError5({
|
|
1086
|
+
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 or a timeout, outside a pipeline lane)",
|
|
1088
|
+
meta: { ordered: true, reason: "ordered-pipeline-undeclared" }
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const laneHandle = (opened) => ({
|
|
1092
|
+
adapter: createClientAdapter({ dispatch, scope, budget, deferred, cancellation, pipelines, lane: opened }),
|
|
1093
|
+
close: () => {
|
|
1094
|
+
pipelines.close(opened);
|
|
1095
|
+
},
|
|
1096
|
+
...opened.order === undefined ? {} : {
|
|
1097
|
+
follow: () => {
|
|
1098
|
+
budget.assertUsable({ stage: "statement" });
|
|
1099
|
+
return laneHandle(pipelines.follow(opened));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
return (ordered === true ? pipelines.openOrdered(count) : pipelines.open(count)).map(laneHandle);
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
872
1106
|
rowChanges: () => {
|
|
873
1107
|
budget.assertUsable({ stage: "statement" });
|
|
874
1108
|
return readRowChanges({ scope });
|
|
@@ -877,29 +1111,35 @@ function pgAdapter(options) {
|
|
|
877
1111
|
return runReadExecution({ ...execParams, support: cancellation.support, provider: PROVIDER, run: async () => {
|
|
878
1112
|
const turn = scopeTurn({ scope });
|
|
879
1113
|
if (turn !== undefined)
|
|
880
|
-
await turn;
|
|
1114
|
+
await waitAsLane(turn);
|
|
881
1115
|
budget.assertUsable({ stage: "statement" });
|
|
882
1116
|
assertReadExecution2({ ...execParams, support: cancellation.support, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
883
|
-
const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl }) };
|
|
1117
|
+
const queryable = { query: (config) => dispatch({ config, batchable: execParams.execution?.cancellation !== "running", execution: execParams.execution, onReadControl: execParams.onReadControl, ...laneOf }) };
|
|
884
1118
|
return runExecute({ queryable, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
|
|
885
1119
|
} });
|
|
886
1120
|
},
|
|
887
1121
|
async executeUnsafe(execParams) {
|
|
888
1122
|
const turn = scopeTurn({ scope });
|
|
889
1123
|
if (turn !== undefined)
|
|
890
|
-
await turn;
|
|
1124
|
+
await waitAsLane(turn);
|
|
891
1125
|
budget.assertUsable({ stage: "statement" });
|
|
892
1126
|
return runExecuteUnsafe({ queryable: rawClient, scope, ...execParams }).catch((error) => abortTransactionOnServerFailure({ scope, error }));
|
|
893
1127
|
},
|
|
894
1128
|
async transaction(fn, nestedOptions) {
|
|
895
1129
|
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
896
1130
|
budget.assertUsable({ stage: "nested" });
|
|
1131
|
+
pipelines?.flush();
|
|
1132
|
+
const leave = lane === undefined || pipelines === null ? ignore : pipelines.enter(lane);
|
|
897
1133
|
return runNestedSavepoint({
|
|
898
1134
|
scope,
|
|
899
1135
|
execute: (statement) => savepointControl(statement, false),
|
|
900
1136
|
cleanup: (statement) => savepointControl(statement, true),
|
|
901
|
-
body: (child) =>
|
|
902
|
-
|
|
1137
|
+
body: (child) => {
|
|
1138
|
+
leave();
|
|
1139
|
+
const ordered = lane === undefined && params.ordered === true ? { ordered: true } : {};
|
|
1140
|
+
return fn(createClientAdapter({ dispatch, scope: child, budget, deferred, cancellation, pipelines, ...laneOf, ...ordered }));
|
|
1141
|
+
}
|
|
1142
|
+
}).finally(leave);
|
|
903
1143
|
},
|
|
904
1144
|
async connect() {
|
|
905
1145
|
budget.assertUsable({ stage: "statement" });
|
|
@@ -1034,7 +1274,8 @@ function pgAdapter(options) {
|
|
|
1034
1274
|
validateTransactionDeadline({
|
|
1035
1275
|
options: transactionOptions,
|
|
1036
1276
|
support: BUDGETS.transactionDeadline,
|
|
1037
|
-
provider: PROVIDER
|
|
1277
|
+
provider: PROVIDER,
|
|
1278
|
+
settings: "transaction-local"
|
|
1038
1279
|
});
|
|
1039
1280
|
const openingOptions = {
|
|
1040
1281
|
isolationLevel: transactionOptions?.isolationLevel,
|
|
@@ -1042,7 +1283,19 @@ function pgAdapter(options) {
|
|
|
1042
1283
|
timeout: transactionOptions?.timeout
|
|
1043
1284
|
};
|
|
1044
1285
|
const openText = openTransactionStatement(openingOptions);
|
|
1286
|
+
const settings = readTransactionSettings({ settings: transactionOptions?.settings, provider: PROVIDER });
|
|
1287
|
+
const settingsStatement = settings.length === 0 ? undefined : renderTransactionSettings({ settings });
|
|
1045
1288
|
const client = await runMapped(() => getPool().connect());
|
|
1289
|
+
const turns = createClientTurns();
|
|
1290
|
+
const onClient = {
|
|
1291
|
+
query: (config) => turns.run(() => client.query(config)),
|
|
1292
|
+
get pipeline() {
|
|
1293
|
+
return client.pipeline;
|
|
1294
|
+
},
|
|
1295
|
+
get connectionParameters() {
|
|
1296
|
+
return client.connectionParameters;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1046
1299
|
let connectionFailure;
|
|
1047
1300
|
const unguard = guardBackgroundErrors({
|
|
1048
1301
|
client,
|
|
@@ -1054,7 +1307,7 @@ function pgAdapter(options) {
|
|
|
1054
1307
|
const scope = createSavepointScope();
|
|
1055
1308
|
const budget = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
|
|
1056
1309
|
const capable = supportsBatch({ client });
|
|
1057
|
-
const deferOpening = capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
|
|
1310
|
+
const deferOpening = settingsStatement === undefined && capable && (openingOptions.timeout === undefined || openingOptions.timeout <= MAX_STATEMENT_TIMEOUT_MS);
|
|
1058
1311
|
const opening = deferOpening ? [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]] : [];
|
|
1059
1312
|
const deferred = createDeferredControl({ opening });
|
|
1060
1313
|
let openingFailure;
|
|
@@ -1064,7 +1317,7 @@ function pgAdapter(options) {
|
|
|
1064
1317
|
const handOver = () => {
|
|
1065
1318
|
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1066
1319
|
dispatchParams.sent?.();
|
|
1067
|
-
return cancellation.run({ config, execution: dispatchParams.execution, onReadControl: dispatchParams.onReadControl, send: () =>
|
|
1320
|
+
return cancellation.run({ config, execution: dispatchParams.execution, onReadControl: dispatchParams.onReadControl, send: () => onClient.query(config) });
|
|
1068
1321
|
};
|
|
1069
1322
|
if (cleanup !== true && cancellationAttempted)
|
|
1070
1323
|
throw transactionAbortedError({ cause: readCancelledError2({ stage: "running" }) });
|
|
@@ -1083,7 +1336,7 @@ function pgAdapter(options) {
|
|
|
1083
1336
|
if (bound === undefined)
|
|
1084
1337
|
return handOver();
|
|
1085
1338
|
const stage = dispatchParams.stage ?? "statement";
|
|
1086
|
-
return runMapped(() =>
|
|
1339
|
+
return runMapped(() => onClient.query({ text: bound })).then(() => {
|
|
1087
1340
|
budget.assertUsable({ stage });
|
|
1088
1341
|
return handOver();
|
|
1089
1342
|
});
|
|
@@ -1100,7 +1353,7 @@ function pgAdapter(options) {
|
|
|
1100
1353
|
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1101
1354
|
if (statement.length > 0)
|
|
1102
1355
|
dispatchParams.sent?.();
|
|
1103
|
-
const burst = runBatch({ client, steps: [...control.map((text) => ({ text })), ...statement], types: EXACT_TYPES }).then((outcome) => {
|
|
1356
|
+
const burst = turns.run(() => runBatch({ client, steps: [...control.map((text) => ({ text })), ...statement], types: EXACT_TYPES })).then((outcome) => {
|
|
1104
1357
|
const failure = firstBatchFailure({ outcome });
|
|
1105
1358
|
if (failure !== undefined) {
|
|
1106
1359
|
if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0) {
|
|
@@ -1122,18 +1375,358 @@ function pgAdapter(options) {
|
|
|
1122
1375
|
}
|
|
1123
1376
|
return burst;
|
|
1124
1377
|
};
|
|
1125
|
-
const
|
|
1378
|
+
const guarded = (run) => {
|
|
1126
1379
|
try {
|
|
1127
|
-
return
|
|
1380
|
+
return run();
|
|
1128
1381
|
} catch (error) {
|
|
1129
1382
|
return Promise.reject(error);
|
|
1130
1383
|
}
|
|
1131
1384
|
};
|
|
1385
|
+
const send = (dispatchParams) => guarded(() => sendWithControl(dispatchParams));
|
|
1132
1386
|
let tail = Promise.resolve();
|
|
1133
1387
|
const activeDispatches = new Set;
|
|
1134
1388
|
let cancellationQueue = false;
|
|
1389
|
+
const lanes = new Set;
|
|
1390
|
+
let queued = [];
|
|
1391
|
+
const settleQueued = (entry, outcome) => {
|
|
1392
|
+
if (entry.settled)
|
|
1393
|
+
return;
|
|
1394
|
+
entry.settled = true;
|
|
1395
|
+
entry.lane.waiting -= 1;
|
|
1396
|
+
if ("result" in outcome) {
|
|
1397
|
+
entry.resolve(outcome.result);
|
|
1398
|
+
} else {
|
|
1399
|
+
entry.failure = outcome.error;
|
|
1400
|
+
entry.reject(outcome.error);
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
const distributeBurst = (burst) => {
|
|
1404
|
+
const { outcome, live, controlSteps, openingSteps } = burst;
|
|
1405
|
+
const failure = firstBatchFailure({ outcome });
|
|
1406
|
+
if (failure === undefined) {
|
|
1407
|
+
live.forEach((entry, index) => {
|
|
1408
|
+
const step = outcome.steps[controlSteps + index];
|
|
1409
|
+
settleQueued(entry, { result: step?.status === "ok" ? step.result : { rows: [], rowCount: 0 } });
|
|
1410
|
+
});
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const mapped = mapPgDriverError({ error: failure.error });
|
|
1414
|
+
if (failure.index < openingSteps || outcome.transportError !== undefined && openingSteps > 0)
|
|
1415
|
+
openingFailure = mapped;
|
|
1416
|
+
if (typeof mapped.meta.sqlstate === "string")
|
|
1417
|
+
markTransactionAborted({ scope, cause: mapped });
|
|
1418
|
+
const answered = outcome.transportError === undefined;
|
|
1419
|
+
if (failure.index < controlSteps) {
|
|
1420
|
+
const later = answered || openingFailure !== undefined ? transactionAbortedError({ cause: openingFailure ?? mapped }) : mapped;
|
|
1421
|
+
live.forEach((entry, index) => {
|
|
1422
|
+
settleQueued(entry, { error: index === 0 ? mapped : later });
|
|
1423
|
+
});
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
let abortCause = mapped;
|
|
1427
|
+
live.forEach((entry, index) => {
|
|
1428
|
+
const position = controlSteps + index;
|
|
1429
|
+
const step = outcome.steps[position];
|
|
1430
|
+
if (step === undefined) {
|
|
1431
|
+
settleQueued(entry, { error: position === failure.index || openingFailure === undefined ? mapped : transactionAbortedError({ cause: openingFailure }) });
|
|
1432
|
+
} else if (step.status === "ok") {
|
|
1433
|
+
settleQueued(entry, { result: step.result });
|
|
1434
|
+
} else if (step.status === "failed") {
|
|
1435
|
+
const error = position === failure.index ? mapped : mapPgDriverError({ error: step.error });
|
|
1436
|
+
if (typeof error.meta.sqlstate === "string")
|
|
1437
|
+
abortCause = error;
|
|
1438
|
+
settleQueued(entry, { error });
|
|
1439
|
+
} else {
|
|
1440
|
+
settleQueued(entry, { error: transactionAbortedError({ cause: abortCause }) });
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
};
|
|
1444
|
+
const sendRefusal = () => cancellationAttempted ? transactionAbortedError({ cause: readCancelledError2({ stage: "running" }) }) : openingFailure !== undefined ? transactionAbortedError({ cause: openingFailure }) : scope.state.poisoned ?? (scope.state.aborted === null ? undefined : transactionAbortedError({ cause: scope.state.aborted.cause }));
|
|
1445
|
+
const sendQueued = (entries) => {
|
|
1446
|
+
const refusal = sendRefusal();
|
|
1447
|
+
const live = [];
|
|
1448
|
+
for (const entry of entries) {
|
|
1449
|
+
if (refusal !== undefined)
|
|
1450
|
+
settleQueued(entry, { error: refusal });
|
|
1451
|
+
else if (entry.execution?.signal?.aborted === true)
|
|
1452
|
+
settleQueued(entry, { error: readCancelledError2({ stage: "waiting-for-connection" }) });
|
|
1453
|
+
else
|
|
1454
|
+
live.push(entry);
|
|
1455
|
+
}
|
|
1456
|
+
if (live.length === 0)
|
|
1457
|
+
return Promise.resolve();
|
|
1458
|
+
const carriesOpening = !deferred.opened();
|
|
1459
|
+
const control = deferred.take();
|
|
1460
|
+
const openingSteps = carriesOpening ? opening.length : 0;
|
|
1461
|
+
const burst = turns.run(() => runBatch({ client, steps: [...control.map((text) => ({ text })), ...live.map((entry) => ({ text: entry.text, values: entry.values }))], types: EXACT_TYPES })).then((outcome) => {
|
|
1462
|
+
distributeBurst({ outcome, live, controlSteps: control.length, openingSteps });
|
|
1463
|
+
}).then(undefined, (error) => {
|
|
1464
|
+
if (openingSteps > 0)
|
|
1465
|
+
openingFailure ??= mapPgDriverError({ error });
|
|
1466
|
+
for (const entry of live)
|
|
1467
|
+
settleQueued(entry, { error });
|
|
1468
|
+
});
|
|
1469
|
+
if (carriesOpening) {
|
|
1470
|
+
const settled = burst.then(() => {
|
|
1471
|
+
if (inFlight === settled)
|
|
1472
|
+
inFlight = null;
|
|
1473
|
+
});
|
|
1474
|
+
inFlight = settled;
|
|
1475
|
+
}
|
|
1476
|
+
return burst;
|
|
1477
|
+
};
|
|
1478
|
+
const sendAlone = (entry, alone) => {
|
|
1479
|
+
const refusal = sendRefusal();
|
|
1480
|
+
if (refusal !== undefined) {
|
|
1481
|
+
settleQueued(entry, { error: refusal });
|
|
1482
|
+
return Promise.resolve();
|
|
1483
|
+
}
|
|
1484
|
+
return dispatchOrdinary(alone).then((result) => {
|
|
1485
|
+
settleQueued(entry, { result });
|
|
1486
|
+
}, (error) => {
|
|
1487
|
+
const mapped = mapPgDriverError({ error });
|
|
1488
|
+
if (typeof mapped.meta.sqlstate === "string")
|
|
1489
|
+
markTransactionAborted({ scope, cause: mapped });
|
|
1490
|
+
settleQueued(entry, { error });
|
|
1491
|
+
});
|
|
1492
|
+
};
|
|
1493
|
+
const prefixOf = (lane) => {
|
|
1494
|
+
const order = lane.order;
|
|
1495
|
+
if (order === undefined)
|
|
1496
|
+
return "issued";
|
|
1497
|
+
let state = "issued";
|
|
1498
|
+
for (let index = 0;index < order.index; index++) {
|
|
1499
|
+
const earlier = order.scope.lanes[index];
|
|
1500
|
+
if (earlier.order.issued > 0)
|
|
1501
|
+
continue;
|
|
1502
|
+
if (!earlier.open)
|
|
1503
|
+
return "closed";
|
|
1504
|
+
state = "open";
|
|
1505
|
+
}
|
|
1506
|
+
return state;
|
|
1507
|
+
};
|
|
1508
|
+
const refuseOrphaned = () => {
|
|
1509
|
+
if (!queued.some((entry) => prefixOf(entry.lane) === "closed"))
|
|
1510
|
+
return;
|
|
1511
|
+
const kept = [];
|
|
1512
|
+
for (const entry of queued) {
|
|
1513
|
+
if (prefixOf(entry.lane) === "closed")
|
|
1514
|
+
settleQueued(entry, { error: predecessorClosedError() });
|
|
1515
|
+
else
|
|
1516
|
+
kept.push(entry);
|
|
1517
|
+
}
|
|
1518
|
+
queued = kept;
|
|
1519
|
+
};
|
|
1520
|
+
const orderedSend = (entries, send) => () => {
|
|
1521
|
+
const live = [];
|
|
1522
|
+
for (const entry of entries) {
|
|
1523
|
+
const failed = entry.lane.order?.scope.failed;
|
|
1524
|
+
if (failed === undefined)
|
|
1525
|
+
live.push(entry);
|
|
1526
|
+
else
|
|
1527
|
+
settleQueued(entry, { error: sendRefusal() ?? transactionAbortedError({ cause: failed.cause }) });
|
|
1528
|
+
}
|
|
1529
|
+
if (live.length === 0)
|
|
1530
|
+
return Promise.resolve();
|
|
1531
|
+
return send(live).then(() => {
|
|
1532
|
+
for (const entry of live) {
|
|
1533
|
+
const order = entry.lane.order;
|
|
1534
|
+
if (order !== undefined && entry.failure !== undefined)
|
|
1535
|
+
order.scope.failed ??= { cause: mapPgDriverError({ error: entry.failure }) };
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
};
|
|
1539
|
+
let orderedTail = null;
|
|
1540
|
+
const flushOrdered = () => {
|
|
1541
|
+
refuseOrphaned();
|
|
1542
|
+
const ready = [];
|
|
1543
|
+
const held = [];
|
|
1544
|
+
for (const entry of queued)
|
|
1545
|
+
(prefixOf(entry.lane) === "issued" ? ready : held).push(entry);
|
|
1546
|
+
queued = held;
|
|
1547
|
+
if (ready.length === 0)
|
|
1548
|
+
return;
|
|
1549
|
+
ready.sort((first, second) => (first.lane.order?.index ?? 0) - (second.lane.order?.index ?? 0));
|
|
1550
|
+
const sends = [];
|
|
1551
|
+
let run = [];
|
|
1552
|
+
for (const entry of ready) {
|
|
1553
|
+
const alone = entry.alone;
|
|
1554
|
+
if (alone === undefined) {
|
|
1555
|
+
run.push(entry);
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (run.length > 0) {
|
|
1559
|
+
sends.push(orderedSend(run, sendQueued));
|
|
1560
|
+
run = [];
|
|
1561
|
+
}
|
|
1562
|
+
sends.push(orderedSend([entry], () => sendAlone(entry, alone)));
|
|
1563
|
+
}
|
|
1564
|
+
if (run.length > 0)
|
|
1565
|
+
sends.push(orderedSend(run, sendQueued));
|
|
1566
|
+
const before = [orderedTail, inFlight].filter((wait) => wait !== null);
|
|
1567
|
+
let chain = before.length === 0 ? sends.shift()() : Promise.all(before).then(ignore);
|
|
1568
|
+
for (const next of sends)
|
|
1569
|
+
chain = chain.then(next);
|
|
1570
|
+
const tail = chain;
|
|
1571
|
+
orderedTail = tail;
|
|
1572
|
+
tail.then(() => {
|
|
1573
|
+
if (orderedTail === tail)
|
|
1574
|
+
orderedTail = null;
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1577
|
+
const flushQueue = () => {
|
|
1578
|
+
if (queued.length === 0)
|
|
1579
|
+
return;
|
|
1580
|
+
if (queued.some((entry) => entry.lane.order !== undefined)) {
|
|
1581
|
+
flushOrdered();
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
const entries = queued;
|
|
1585
|
+
queued = [];
|
|
1586
|
+
if (inFlight === null)
|
|
1587
|
+
sendQueued(entries);
|
|
1588
|
+
else
|
|
1589
|
+
inFlight.then(() => {
|
|
1590
|
+
sendQueued(entries);
|
|
1591
|
+
});
|
|
1592
|
+
};
|
|
1593
|
+
const flushIfReady = () => {
|
|
1594
|
+
if (queued.length === 0)
|
|
1595
|
+
return;
|
|
1596
|
+
for (const open of lanes)
|
|
1597
|
+
if (open.waiting === 0)
|
|
1598
|
+
return;
|
|
1599
|
+
flushQueue();
|
|
1600
|
+
};
|
|
1601
|
+
const pipelines = {
|
|
1602
|
+
open: (count) => Array.from({ length: count }, () => {
|
|
1603
|
+
const opened = { open: true, waiting: 0 };
|
|
1604
|
+
lanes.add(opened);
|
|
1605
|
+
return opened;
|
|
1606
|
+
}),
|
|
1607
|
+
openOrdered: (count) => {
|
|
1608
|
+
const ordered = { lanes: [] };
|
|
1609
|
+
for (let index = 0;index < count; index++) {
|
|
1610
|
+
const opened = { open: true, waiting: 0, order: { scope: ordered, index, issued: 0, followed: false } };
|
|
1611
|
+
ordered.lanes.push(opened);
|
|
1612
|
+
lanes.add(opened);
|
|
1613
|
+
}
|
|
1614
|
+
return [...ordered.lanes];
|
|
1615
|
+
},
|
|
1616
|
+
follow: (lane) => {
|
|
1617
|
+
const order = lane.order;
|
|
1618
|
+
if (order === undefined)
|
|
1619
|
+
throw orderedLaneMisuse({ message: "follow() exists only on the lanes of an ordered scope" });
|
|
1620
|
+
if (!lane.open)
|
|
1621
|
+
throw orderedLaneMisuse({ message: "follow() on a closed lane" });
|
|
1622
|
+
if (order.followed || order.scope.lanes.at(-1) !== lane)
|
|
1623
|
+
throw orderedLaneMisuse({ message: "follow() opens one lane after the LAST lane of its scope" });
|
|
1624
|
+
if (order.issued > 0)
|
|
1625
|
+
throw orderedLaneMisuse({ message: "follow() must come before the lane issues its statement" });
|
|
1626
|
+
order.followed = true;
|
|
1627
|
+
const next = { open: true, waiting: 0, order: { scope: order.scope, index: order.scope.lanes.length, issued: 0, followed: false } };
|
|
1628
|
+
order.scope.lanes.push(next);
|
|
1629
|
+
lanes.add(next);
|
|
1630
|
+
return next;
|
|
1631
|
+
},
|
|
1632
|
+
close: (lane) => {
|
|
1633
|
+
if (!lane.open)
|
|
1634
|
+
return;
|
|
1635
|
+
lane.open = false;
|
|
1636
|
+
lanes.delete(lane);
|
|
1637
|
+
if (lane.order !== undefined)
|
|
1638
|
+
refuseOrphaned();
|
|
1639
|
+
flushIfReady();
|
|
1640
|
+
},
|
|
1641
|
+
waitAs: ({ lane, wait }) => {
|
|
1642
|
+
if (!lane.open)
|
|
1643
|
+
return wait;
|
|
1644
|
+
lane.waiting += 1;
|
|
1645
|
+
flushIfReady();
|
|
1646
|
+
return wait.then((value) => {
|
|
1647
|
+
lane.waiting -= 1;
|
|
1648
|
+
return value;
|
|
1649
|
+
}, (error) => {
|
|
1650
|
+
lane.waiting -= 1;
|
|
1651
|
+
throw error;
|
|
1652
|
+
});
|
|
1653
|
+
},
|
|
1654
|
+
enter: (lane) => {
|
|
1655
|
+
if (!lane.open)
|
|
1656
|
+
return ignore;
|
|
1657
|
+
lane.waiting += 1;
|
|
1658
|
+
flushIfReady();
|
|
1659
|
+
let left = false;
|
|
1660
|
+
return () => {
|
|
1661
|
+
if (left)
|
|
1662
|
+
return;
|
|
1663
|
+
left = true;
|
|
1664
|
+
lane.waiting -= 1;
|
|
1665
|
+
};
|
|
1666
|
+
},
|
|
1667
|
+
flush: flushQueue
|
|
1668
|
+
};
|
|
1669
|
+
const endPipeline = () => {
|
|
1670
|
+
for (const open of lanes)
|
|
1671
|
+
open.open = false;
|
|
1672
|
+
lanes.clear();
|
|
1673
|
+
flushQueue();
|
|
1674
|
+
};
|
|
1675
|
+
const enqueue = (dispatchParams, lane, alone) => {
|
|
1676
|
+
if (cancellationAttempted)
|
|
1677
|
+
throw transactionAbortedError({ cause: readCancelledError2({ stage: "running" }) });
|
|
1678
|
+
if (openingFailure !== undefined)
|
|
1679
|
+
throw transactionAbortedError({ cause: openingFailure });
|
|
1680
|
+
assertReadExecution2({ execution: dispatchParams.execution, effect: "read", support: readCancellation, provider: PROVIDER, stage: "waiting-for-connection" });
|
|
1681
|
+
const values = alone === true ? [] : prepareBatchValues({ values: dispatchParams.config.values ?? [] });
|
|
1682
|
+
const pending = new Promise((resolve, reject) => {
|
|
1683
|
+
lane.waiting += 1;
|
|
1684
|
+
queued.push({
|
|
1685
|
+
text: dispatchParams.config.text,
|
|
1686
|
+
values,
|
|
1687
|
+
...dispatchParams.execution === undefined ? {} : { execution: dispatchParams.execution },
|
|
1688
|
+
...alone === true ? { alone: dispatchParams } : {},
|
|
1689
|
+
lane,
|
|
1690
|
+
resolve,
|
|
1691
|
+
reject,
|
|
1692
|
+
settled: false
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
if (lane.order !== undefined)
|
|
1696
|
+
lane.order.issued += 1;
|
|
1697
|
+
activeDispatches.add(pending);
|
|
1698
|
+
pending.then(() => activeDispatches.delete(pending), () => activeDispatches.delete(pending));
|
|
1699
|
+
flushIfReady();
|
|
1700
|
+
return pending;
|
|
1701
|
+
};
|
|
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";
|
|
1135
1703
|
const dispatch = (dispatchParams) => {
|
|
1704
|
+
const lane = dispatchParams.lane?.open === true ? dispatchParams.lane : undefined;
|
|
1705
|
+
const order = lane?.order;
|
|
1706
|
+
if (lane !== undefined && order !== undefined && dispatchParams.cleanup !== true) {
|
|
1707
|
+
return guarded(() => {
|
|
1708
|
+
if (order.followed && order.issued > 0)
|
|
1709
|
+
throw orderedLaneMisuse({ message: "a lane that has a follower may issue ONE statement" });
|
|
1710
|
+
return coalescable(dispatchParams) ? enqueue(dispatchParams, lane) : enqueue(dispatchParams, lane, true);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
if (lane !== undefined && coalescable(dispatchParams))
|
|
1714
|
+
return guarded(() => enqueue(dispatchParams, lane));
|
|
1715
|
+
if (lane === undefined)
|
|
1716
|
+
return dispatchOrdinary(dispatchParams);
|
|
1717
|
+
const leave = pipelines.enter(lane);
|
|
1718
|
+
flushQueue();
|
|
1719
|
+
return dispatchOrdinary(dispatchParams).then((result) => {
|
|
1720
|
+
leave();
|
|
1721
|
+
return result;
|
|
1722
|
+
}, (error) => {
|
|
1723
|
+
leave();
|
|
1724
|
+
throw error;
|
|
1725
|
+
});
|
|
1726
|
+
};
|
|
1727
|
+
const dispatchOrdinary = (dispatchParams) => {
|
|
1136
1728
|
if (dispatchParams.execution?.cancellation === "running" && !cancellationQueue) {
|
|
1729
|
+
flushQueue();
|
|
1137
1730
|
cancellationQueue = true;
|
|
1138
1731
|
tail = Promise.allSettled([...activeDispatches]);
|
|
1139
1732
|
}
|
|
@@ -1150,7 +1743,7 @@ function pgAdapter(options) {
|
|
|
1150
1743
|
let discard = false;
|
|
1151
1744
|
let released = false;
|
|
1152
1745
|
let cancellationAttempted = false;
|
|
1153
|
-
const cancellation = createPgReadCancellation({ connectionString: cancellationAddress, target:
|
|
1746
|
+
const cancellation = createPgReadCancellation({ connectionString: cancellationAddress, target: onClient, terminate: () => {
|
|
1154
1747
|
if (!released) {
|
|
1155
1748
|
released = true;
|
|
1156
1749
|
client.release(true);
|
|
@@ -1161,13 +1754,57 @@ function pgAdapter(options) {
|
|
|
1161
1754
|
markTransactionAborted({ scope, cause: readCancelledError2({ stage: "running" }) });
|
|
1162
1755
|
} });
|
|
1163
1756
|
let commitProgress = "not-sent";
|
|
1757
|
+
let openingRollback;
|
|
1758
|
+
const openWithSettings = async (statement) => {
|
|
1759
|
+
let failed;
|
|
1760
|
+
if (capable) {
|
|
1761
|
+
const control = [beginStatement(openingOptions), ...openingOptions.timeout === undefined ? [] : [statementTimeoutStatement({ timeout: openingOptions.timeout })]];
|
|
1762
|
+
const outcome = await turns.run(() => runBatch({
|
|
1763
|
+
client,
|
|
1764
|
+
steps: [...control.map((text) => ({ text })), { text: statement.text, values: prepareBatchValues({ values: statement.values }) }],
|
|
1765
|
+
types: EXACT_TYPES
|
|
1766
|
+
}));
|
|
1767
|
+
const failure = firstBatchFailure({ outcome });
|
|
1768
|
+
if (failure === undefined)
|
|
1769
|
+
return;
|
|
1770
|
+
if (failure.index < control.length || outcome.transportError !== undefined)
|
|
1771
|
+
throw mapPgDriverError({ error: failure.error });
|
|
1772
|
+
failed = { error: failure.error };
|
|
1773
|
+
} else {
|
|
1774
|
+
await runMapped(() => onClient.query({ text: openText }));
|
|
1775
|
+
failed = await onClient.query({ text: statement.text, values: [...statement.values] }).then(() => {
|
|
1776
|
+
return;
|
|
1777
|
+
}, (error) => ({ error }));
|
|
1778
|
+
if (failed === undefined)
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const cause = mapPgDriverError({ error: failed.error });
|
|
1782
|
+
if (typeof cause.meta.sqlstate !== "string")
|
|
1783
|
+
throw cause;
|
|
1784
|
+
openingRollback = await onClient.query({ text: "ROLLBACK" }).then(() => "acknowledged", () => "failed");
|
|
1785
|
+
if (openingRollback === "failed")
|
|
1786
|
+
discard = true;
|
|
1787
|
+
const setting = findRefusedSetting({ settings, cause });
|
|
1788
|
+
throw transactionSettingRefusedError({ provider: PROVIDER, settings, setting, cause });
|
|
1789
|
+
};
|
|
1164
1790
|
try {
|
|
1165
|
-
if (
|
|
1166
|
-
await
|
|
1791
|
+
if (settingsStatement !== undefined)
|
|
1792
|
+
await openWithSettings(settingsStatement);
|
|
1793
|
+
else if (!deferOpening)
|
|
1794
|
+
await runMapped(() => onClient.query({ text: openText }));
|
|
1167
1795
|
const result = await runRootScope({
|
|
1168
1796
|
scope,
|
|
1169
|
-
body: () => fn(createClientAdapter({
|
|
1797
|
+
body: () => fn(createClientAdapter({
|
|
1798
|
+
dispatch,
|
|
1799
|
+
scope,
|
|
1800
|
+
budget,
|
|
1801
|
+
deferred: capable ? deferred : null,
|
|
1802
|
+
cancellation,
|
|
1803
|
+
pipelines: capable ? pipelines : null,
|
|
1804
|
+
ordered: capable && budget.totalMs === null && openingOptions.timeout === undefined && !((client.connectionParameters?.query_timeout ?? 0) > 0)
|
|
1805
|
+
}))
|
|
1170
1806
|
});
|
|
1807
|
+
endPipeline();
|
|
1171
1808
|
budget.assertUsable({ stage: "commit" });
|
|
1172
1809
|
if (!deferred.opened()) {
|
|
1173
1810
|
budget.close({ outcome: "committed" });
|
|
@@ -1203,13 +1840,16 @@ function pgAdapter(options) {
|
|
|
1203
1840
|
budget.close({ outcome: "committed" });
|
|
1204
1841
|
return result;
|
|
1205
1842
|
} catch (error) {
|
|
1843
|
+
endPipeline();
|
|
1206
1844
|
if (isSavepointRollbackFailedError(error) || scope.state.poisoned !== null) {
|
|
1207
1845
|
discard = true;
|
|
1208
1846
|
budget.close({ outcome: "unknown" });
|
|
1209
1847
|
}
|
|
1210
1848
|
let rollback = "not-needed";
|
|
1211
1849
|
try {
|
|
1212
|
-
if (
|
|
1850
|
+
if (openingRollback !== undefined)
|
|
1851
|
+
rollback = openingRollback;
|
|
1852
|
+
else if (deferred.opened()) {
|
|
1213
1853
|
await dispatch({ config: { text: "ROLLBACK" }, cleanup: true });
|
|
1214
1854
|
rollback = "acknowledged";
|
|
1215
1855
|
}
|
|
@@ -1244,7 +1884,10 @@ function pgAdapter(options) {
|
|
|
1244
1884
|
budgets: BUDGETS,
|
|
1245
1885
|
readCancellation,
|
|
1246
1886
|
roundTrips: ROUND_TRIPS,
|
|
1887
|
+
readSnapshot: READ_SNAPSHOT,
|
|
1888
|
+
pipelining: PIPELINING,
|
|
1247
1889
|
transactionOutcome: "reported",
|
|
1890
|
+
transactionSettings: "transaction-local",
|
|
1248
1891
|
withSession({ run }) {
|
|
1249
1892
|
return lifecycle.admit({ run: () => root.withSession({ run: (executor) => lifecycle.inCallback({ run: () => run(executor) }) }) });
|
|
1250
1893
|
},
|
|
@@ -1297,4 +1940,4 @@ export {
|
|
|
1297
1940
|
validateTransactionDeadline2 as validateTransactionDeadline
|
|
1298
1941
|
};
|
|
1299
1942
|
|
|
1300
|
-
//# debugId=
|
|
1943
|
+
//# debugId=FB5284F22EED8D0A64756E2164756E21
|