@crvouga/mockingbird-service-postgres 1.5.1 → 1.6.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/AGENTS.md +5 -6
- package/CHANGELOG.md +10 -0
- package/COMPATIBILITY.md +10 -5
- package/README.md +23 -13
- package/dist/admin.js +127 -41
- package/dist/admin.js.map +4 -4
- package/dist/ast/nodes.d.ts +1 -0
- package/dist/index.js +118 -32
- package/dist/index.js.map +3 -3
- package/dist/runtime/database-context.d.ts +4 -0
- package/dist/sql/deparse.d.ts +1 -0
- package/dist/unstable.js +96 -31
- package/dist/unstable.js.map +3 -3
- package/dist/wire/cli.js +586 -76
- package/dist/wire/cli.js.map +3 -3
- package/dist/wire/cluster.d.ts +20 -8
- package/dist/wire/connection.d.ts +25 -1
- package/dist/wire/index.d.ts +5 -1
- package/dist/wire/index.js +586 -76
- package/dist/wire/index.js.map +3 -3
- package/dist/wire/protocol.d.ts +4 -0
- package/package.json +1 -1
package/dist/wire/cli.js
CHANGED
|
@@ -3465,7 +3465,13 @@ var DatabaseState = class _DatabaseState {
|
|
|
3465
3465
|
this.settings.set("lc_numeric", "C");
|
|
3466
3466
|
this.settings.set("lc_time", "C");
|
|
3467
3467
|
this.settings.set("default_transaction_isolation", "read committed");
|
|
3468
|
+
this.settings.set("lock_timeout", "0");
|
|
3469
|
+
this.settings.set("idle_in_transaction_session_timeout", "0");
|
|
3470
|
+
this.settings.set("transaction_timeout", "0");
|
|
3468
3471
|
this.settings.set("statement_timeout", "0");
|
|
3472
|
+
this.settings.set("check_function_bodies", "on");
|
|
3473
|
+
this.settings.set("xmloption", "content");
|
|
3474
|
+
this.settings.set("row_security", "on");
|
|
3469
3475
|
this.settings.set("array_nulls", "on");
|
|
3470
3476
|
this.settings.set("backslash_quote", "safe_encoding");
|
|
3471
3477
|
}
|
|
@@ -7067,6 +7073,22 @@ function exactDegTan(x) {
|
|
|
7067
7073
|
return Math.tan(x * Math.PI / 180);
|
|
7068
7074
|
}
|
|
7069
7075
|
|
|
7076
|
+
// src/sql/deparse.ts
|
|
7077
|
+
function quoteIdent(name) {
|
|
7078
|
+
if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
|
|
7079
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
7080
|
+
}
|
|
7081
|
+
|
|
7082
|
+
// src/runtime/database-context.ts
|
|
7083
|
+
var contexts = /* @__PURE__ */ new WeakMap();
|
|
7084
|
+
var fallback = { name: "postgres", names: () => ["postgres"] };
|
|
7085
|
+
function databaseCatalogContext(state) {
|
|
7086
|
+
return contexts.get(state) ?? fallback;
|
|
7087
|
+
}
|
|
7088
|
+
function setDatabaseCatalogContext(state, context) {
|
|
7089
|
+
contexts.set(state, context);
|
|
7090
|
+
}
|
|
7091
|
+
|
|
7070
7092
|
// src/functions/misc-fns.ts
|
|
7071
7093
|
var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
|
|
7072
7094
|
function findSequenceForCall(ctx, name) {
|
|
@@ -7150,8 +7172,8 @@ function sequenceNextval(ctx, seq) {
|
|
|
7150
7172
|
function getMiscFunctions() {
|
|
7151
7173
|
const m = /* @__PURE__ */ new Map();
|
|
7152
7174
|
m.set("version", () => tv("text", PG_VERSION_TEXT));
|
|
7153
|
-
m.set("current_database", () => tv("name",
|
|
7154
|
-
m.set("current_catalog", () => tv("name",
|
|
7175
|
+
m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7176
|
+
m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7155
7177
|
m.set("current_schema", (ctx) => {
|
|
7156
7178
|
const path = ctx.state.effectiveSearchPath();
|
|
7157
7179
|
const first = path.find((s) => ctx.state.schemas.has(s));
|
|
@@ -7273,7 +7295,7 @@ function getMiscFunctions() {
|
|
|
7273
7295
|
if (!col.identity) return tv("text", null);
|
|
7274
7296
|
return tv(
|
|
7275
7297
|
"text",
|
|
7276
|
-
col.identity.sequence.split(".").map(
|
|
7298
|
+
col.identity.sequence.split(".").map(quoteIdent).join(".")
|
|
7277
7299
|
);
|
|
7278
7300
|
})
|
|
7279
7301
|
);
|
|
@@ -14523,14 +14545,19 @@ var Parser = class {
|
|
|
14523
14545
|
parseIdentityOptions() {
|
|
14524
14546
|
const options2 = {};
|
|
14525
14547
|
if (this.eatPunct("(")) {
|
|
14526
|
-
Object.assign(options2, this.parseSequenceOptions());
|
|
14548
|
+
Object.assign(options2, this.parseSequenceOptions(true));
|
|
14527
14549
|
this.expectPunct(")");
|
|
14528
14550
|
}
|
|
14529
14551
|
return options2;
|
|
14530
14552
|
}
|
|
14531
|
-
parseSequenceOptions() {
|
|
14553
|
+
parseSequenceOptions(identity = false) {
|
|
14532
14554
|
const options2 = {};
|
|
14533
14555
|
for (; ; ) {
|
|
14556
|
+
if (identity && this.eatKw("sequence")) {
|
|
14557
|
+
this.expectKw("name");
|
|
14558
|
+
options2.sequenceName = this.qualifiedName();
|
|
14559
|
+
continue;
|
|
14560
|
+
}
|
|
14534
14561
|
if (this.eatKw("increment")) {
|
|
14535
14562
|
this.eatKw("by");
|
|
14536
14563
|
options2.increment = this.parseSignedBigInt();
|
|
@@ -20532,6 +20559,27 @@ function parseCsvLine(line, delim, quote) {
|
|
|
20532
20559
|
}
|
|
20533
20560
|
return fields;
|
|
20534
20561
|
}
|
|
20562
|
+
function splitCsvRecords(data, quote) {
|
|
20563
|
+
const records = [];
|
|
20564
|
+
let start = 0;
|
|
20565
|
+
let quoted = false;
|
|
20566
|
+
for (let i = 0; i < data.length; i++) {
|
|
20567
|
+
const char = data[i];
|
|
20568
|
+
if (char === quote) {
|
|
20569
|
+
if (quoted && data[i + 1] === quote) i++;
|
|
20570
|
+
else quoted = !quoted;
|
|
20571
|
+
continue;
|
|
20572
|
+
}
|
|
20573
|
+
if (char === "\n" && !quoted) {
|
|
20574
|
+
const end = i > start && data[i - 1] === "\r" ? i - 1 : i;
|
|
20575
|
+
records.push(data.slice(start, end));
|
|
20576
|
+
start = i + 1;
|
|
20577
|
+
}
|
|
20578
|
+
}
|
|
20579
|
+
if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
20580
|
+
if (start < data.length) records.push(data.slice(start));
|
|
20581
|
+
return records;
|
|
20582
|
+
}
|
|
20535
20583
|
function executeCopyFromData(env, stmt, data) {
|
|
20536
20584
|
const format = String(stmt.options.format ?? "text").toLowerCase();
|
|
20537
20585
|
if (format !== "text" && format !== "csv") throw unsupported(`COPY format ${format}`);
|
|
@@ -20542,7 +20590,7 @@ function executeCopyFromData(env, stmt, data) {
|
|
|
20542
20590
|
const nullStr = String(stmt.options.null ?? (format === "csv" ? "" : "\\N"));
|
|
20543
20591
|
const quote = String(stmt.options.quote ?? '"');
|
|
20544
20592
|
const header = stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true";
|
|
20545
|
-
let lines = data.split(/\r?\n/);
|
|
20593
|
+
let lines = format === "csv" ? splitCsvRecords(data, quote) : data.split(/\r?\n/);
|
|
20546
20594
|
const endMarker = lines.indexOf("\\.");
|
|
20547
20595
|
if (endMarker !== -1) lines = lines.slice(0, endMarker);
|
|
20548
20596
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
@@ -21276,7 +21324,8 @@ function pgSettings(ctx) {
|
|
|
21276
21324
|
"pg_settings"
|
|
21277
21325
|
);
|
|
21278
21326
|
}
|
|
21279
|
-
function pgDatabase() {
|
|
21327
|
+
function pgDatabase(ctx) {
|
|
21328
|
+
const names = databaseCatalogContext(ctx.state).names();
|
|
21280
21329
|
return rel(
|
|
21281
21330
|
[
|
|
21282
21331
|
["oid", "oid"],
|
|
@@ -21285,7 +21334,7 @@ function pgDatabase() {
|
|
|
21285
21334
|
["datistemplate", "bool"],
|
|
21286
21335
|
["datallowconn", "bool"]
|
|
21287
21336
|
],
|
|
21288
|
-
[
|
|
21337
|
+
names.map((name, index) => [5 + index, name, 6, false, true]),
|
|
21289
21338
|
"pg_database"
|
|
21290
21339
|
);
|
|
21291
21340
|
}
|
|
@@ -21320,13 +21369,14 @@ function pgTrigger(ctx) {
|
|
|
21320
21369
|
"pg_trigger"
|
|
21321
21370
|
);
|
|
21322
21371
|
}
|
|
21323
|
-
var
|
|
21372
|
+
var catalogName = (state) => databaseCatalogContext(state).name;
|
|
21324
21373
|
function infoSchemata(state) {
|
|
21374
|
+
const catalog = catalogName(state);
|
|
21325
21375
|
const rows = [
|
|
21326
|
-
[
|
|
21327
|
-
[
|
|
21376
|
+
[catalog, "pg_catalog", OWNER],
|
|
21377
|
+
[catalog, "information_schema", OWNER]
|
|
21328
21378
|
];
|
|
21329
|
-
for (const s of state.schemas.values()) rows.push([
|
|
21379
|
+
for (const s of state.schemas.values()) rows.push([catalog, s.name, OWNER]);
|
|
21330
21380
|
return rel(
|
|
21331
21381
|
[
|
|
21332
21382
|
["catalog_name", "name"],
|
|
@@ -21338,13 +21388,14 @@ function infoSchemata(state) {
|
|
|
21338
21388
|
);
|
|
21339
21389
|
}
|
|
21340
21390
|
function infoTables(ctx) {
|
|
21391
|
+
const catalog = catalogName(ctx.state);
|
|
21341
21392
|
const rows = [];
|
|
21342
21393
|
for (const schema of ctx.state.schemas.values()) {
|
|
21343
21394
|
for (const t of schema.tables.values()) {
|
|
21344
|
-
rows.push([
|
|
21395
|
+
rows.push([catalog, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
|
|
21345
21396
|
}
|
|
21346
21397
|
for (const v of schema.views.values()) {
|
|
21347
|
-
if (!v.materialized) rows.push([
|
|
21398
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, "VIEW", "NO"]);
|
|
21348
21399
|
}
|
|
21349
21400
|
}
|
|
21350
21401
|
return rel(
|
|
@@ -21360,6 +21411,7 @@ function infoTables(ctx) {
|
|
|
21360
21411
|
);
|
|
21361
21412
|
}
|
|
21362
21413
|
function infoColumns(ctx) {
|
|
21414
|
+
const catalog = catalogName(ctx.state);
|
|
21363
21415
|
const rows = [];
|
|
21364
21416
|
for (const t of allTables(ctx.state)) {
|
|
21365
21417
|
t.columns.forEach((c, i) => {
|
|
@@ -21370,7 +21422,7 @@ function infoColumns(ctx) {
|
|
|
21370
21422
|
const dataType = isArrayType(id) ? "ARRAY" : isEnumType(id) ? "USER-DEFINED" : typeDisplayName(id);
|
|
21371
21423
|
const udt = isArrayType(id) ? `_${arrayUdt(id)}` : isEnumType(id) ? id.slice(5).split(".").pop() : id;
|
|
21372
21424
|
rows.push([
|
|
21373
|
-
|
|
21425
|
+
catalog,
|
|
21374
21426
|
t.schema,
|
|
21375
21427
|
t.name,
|
|
21376
21428
|
c.name,
|
|
@@ -21415,10 +21467,11 @@ function arrayUdt(t) {
|
|
|
21415
21467
|
return isEnumType(elem) ? elem.slice(5).split(".").pop() : elem;
|
|
21416
21468
|
}
|
|
21417
21469
|
function infoViews(ctx) {
|
|
21470
|
+
const catalog = catalogName(ctx.state);
|
|
21418
21471
|
const rows = [];
|
|
21419
21472
|
for (const schema of ctx.state.schemas.values()) {
|
|
21420
21473
|
for (const v of schema.views.values()) {
|
|
21421
|
-
if (!v.materialized) rows.push([
|
|
21474
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, null]);
|
|
21422
21475
|
}
|
|
21423
21476
|
}
|
|
21424
21477
|
return rel(
|
|
@@ -21433,10 +21486,11 @@ function infoViews(ctx) {
|
|
|
21433
21486
|
);
|
|
21434
21487
|
}
|
|
21435
21488
|
function infoSequences(ctx) {
|
|
21489
|
+
const catalog = catalogName(ctx.state);
|
|
21436
21490
|
const rows = [];
|
|
21437
21491
|
for (const s of allSequences(ctx.state)) {
|
|
21438
21492
|
rows.push([
|
|
21439
|
-
|
|
21493
|
+
catalog,
|
|
21440
21494
|
s.schema,
|
|
21441
21495
|
s.name,
|
|
21442
21496
|
typeDisplayName(s.dataType),
|
|
@@ -21464,11 +21518,12 @@ function infoSequences(ctx) {
|
|
|
21464
21518
|
);
|
|
21465
21519
|
}
|
|
21466
21520
|
function infoTableConstraints(ctx) {
|
|
21521
|
+
const catalog = catalogName(ctx.state);
|
|
21467
21522
|
const rows = [];
|
|
21468
21523
|
for (const t of allTables(ctx.state)) {
|
|
21469
21524
|
for (const c of t.constraints) {
|
|
21470
21525
|
const type = c.kind === "primary_key" ? "PRIMARY KEY" : c.kind === "unique" ? "UNIQUE" : c.kind === "check" ? "CHECK" : "FOREIGN KEY";
|
|
21471
|
-
rows.push([
|
|
21526
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, type]);
|
|
21472
21527
|
}
|
|
21473
21528
|
}
|
|
21474
21529
|
return rel(
|
|
@@ -21486,12 +21541,13 @@ function infoTableConstraints(ctx) {
|
|
|
21486
21541
|
);
|
|
21487
21542
|
}
|
|
21488
21543
|
function infoKeyColumnUsage(ctx) {
|
|
21544
|
+
const catalog = catalogName(ctx.state);
|
|
21489
21545
|
const rows = [];
|
|
21490
21546
|
for (const t of allTables(ctx.state)) {
|
|
21491
21547
|
for (const c of t.constraints) {
|
|
21492
21548
|
if (c.kind !== "primary_key" && c.kind !== "unique" && c.kind !== "foreign_key") continue;
|
|
21493
21549
|
c.columns.forEach((col, i) => {
|
|
21494
|
-
rows.push([
|
|
21550
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, col, i + 1]);
|
|
21495
21551
|
});
|
|
21496
21552
|
}
|
|
21497
21553
|
}
|
|
@@ -21511,12 +21567,13 @@ function infoKeyColumnUsage(ctx) {
|
|
|
21511
21567
|
);
|
|
21512
21568
|
}
|
|
21513
21569
|
function infoRoutines(ctx) {
|
|
21570
|
+
const catalog = catalogName(ctx.state);
|
|
21514
21571
|
const rows = [];
|
|
21515
21572
|
for (const schema of ctx.state.schemas.values()) {
|
|
21516
21573
|
for (const fns of schema.functions.values()) {
|
|
21517
21574
|
for (const f of fns) {
|
|
21518
21575
|
rows.push([
|
|
21519
|
-
|
|
21576
|
+
catalog,
|
|
21520
21577
|
f.schema,
|
|
21521
21578
|
f.name,
|
|
21522
21579
|
"FUNCTION",
|
|
@@ -21573,7 +21630,7 @@ function buildCatalogRelation(ctx, schema, name) {
|
|
|
21573
21630
|
case "pg_settings":
|
|
21574
21631
|
return pgSettings(ctx);
|
|
21575
21632
|
case "pg_database":
|
|
21576
|
-
return pgDatabase();
|
|
21633
|
+
return pgDatabase(ctx);
|
|
21577
21634
|
case "pg_roles":
|
|
21578
21635
|
case "pg_user":
|
|
21579
21636
|
return pgRoles();
|
|
@@ -21754,6 +21811,11 @@ var MAX_IDENT = 63;
|
|
|
21754
21811
|
function truncIdent(s) {
|
|
21755
21812
|
return s.length > MAX_IDENT ? s.slice(0, MAX_IDENT) : s;
|
|
21756
21813
|
}
|
|
21814
|
+
function identitySequenceTarget(env, tableSchema, defaultName, options2) {
|
|
21815
|
+
const parts = options2.sequenceName;
|
|
21816
|
+
if (!parts || parts.length === 0) return { schema: tableSchema, name: defaultName };
|
|
21817
|
+
return targetSchema(env, parts);
|
|
21818
|
+
}
|
|
21757
21819
|
var SEQ_LIMITS = {
|
|
21758
21820
|
int2: { min: -32768n, max: 32767n },
|
|
21759
21821
|
int4: { min: -2147483648n, max: 2147483647n },
|
|
@@ -21930,8 +21992,8 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21930
21992
|
break;
|
|
21931
21993
|
}
|
|
21932
21994
|
case "generated_identity": {
|
|
21933
|
-
const
|
|
21934
|
-
const seq = buildSequence(env, schema,
|
|
21995
|
+
const target = identitySequenceTarget(env, schema, truncIdent(`${tableName}_${def.name}_seq`), c.options);
|
|
21996
|
+
const seq = buildSequence(env, target.schema, target.name, c.options, false);
|
|
21935
21997
|
if (!c.options.as) {
|
|
21936
21998
|
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
21937
21999
|
const limits = SEQ_LIMITS[seq.dataType];
|
|
@@ -21940,7 +22002,7 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21940
22002
|
}
|
|
21941
22003
|
seq.ownedBy = { table: tableName, column: def.name };
|
|
21942
22004
|
out.sequences.push(seq);
|
|
21943
|
-
col.identity = { always: c.always, sequence: `${schema.name}.${
|
|
22005
|
+
col.identity = { always: c.always, sequence: `${target.schema.name}.${target.name}` };
|
|
21944
22006
|
col.notNull = true;
|
|
21945
22007
|
break;
|
|
21946
22008
|
}
|
|
@@ -22076,10 +22138,11 @@ function executeCreateTable(env, stmt) {
|
|
|
22076
22138
|
throw pgError("invalid_table_definition", `multiple primary keys for table "${name}" are not allowed`, "42P16");
|
|
22077
22139
|
}
|
|
22078
22140
|
for (const seq of built.sequences) {
|
|
22079
|
-
|
|
22141
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
22142
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
22080
22143
|
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
22081
22144
|
}
|
|
22082
|
-
|
|
22145
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
22083
22146
|
}
|
|
22084
22147
|
const table = new TableData(schema.name, name, built.columns, state.nextOid(), stmt.temp);
|
|
22085
22148
|
table.constraints = built.constraints;
|
|
@@ -22431,7 +22494,13 @@ function executeAlterTable(env, stmt) {
|
|
|
22431
22494
|
const built = { columns: [], constraints: [], sequences: [] };
|
|
22432
22495
|
const taken = constraintNames(table);
|
|
22433
22496
|
const col = buildColumn(env, schema, table.name, action.column, built, taken);
|
|
22434
|
-
for (const seq of built.sequences)
|
|
22497
|
+
for (const seq of built.sequences) {
|
|
22498
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
22499
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
22500
|
+
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
22501
|
+
}
|
|
22502
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
22503
|
+
}
|
|
22435
22504
|
table.columns.push(col);
|
|
22436
22505
|
table.constraints.push(...built.constraints);
|
|
22437
22506
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -22751,12 +22820,32 @@ function executeAlterTable(env, stmt) {
|
|
|
22751
22820
|
"42P16"
|
|
22752
22821
|
);
|
|
22753
22822
|
}
|
|
22754
|
-
|
|
22755
|
-
|
|
22823
|
+
if (!col.notNull) {
|
|
22824
|
+
throw pgError(
|
|
22825
|
+
"object_not_in_prerequisite_state",
|
|
22826
|
+
`column "${action.column}" of relation "${table.name}" must be declared NOT NULL before identity can be added`,
|
|
22827
|
+
"55000"
|
|
22828
|
+
);
|
|
22829
|
+
}
|
|
22830
|
+
const target = identitySequenceTarget(
|
|
22831
|
+
env,
|
|
22832
|
+
schema,
|
|
22833
|
+
truncIdent(`${table.name}_${action.column}_seq`),
|
|
22834
|
+
action.options
|
|
22835
|
+
);
|
|
22836
|
+
if (target.schema.hasRelation(target.name)) {
|
|
22837
|
+
throw pgError("duplicate_table", `relation "${target.name}" already exists`, "42P07");
|
|
22838
|
+
}
|
|
22839
|
+
const seq = buildSequence(env, target.schema, target.name, action.options, false);
|
|
22840
|
+
if (!action.options.as) {
|
|
22841
|
+
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
22842
|
+
const limits = SEQ_LIMITS[seq.dataType];
|
|
22843
|
+
if (action.options.maxValue === void 0) seq.maxValue = seq.increment > 0n ? limits.max : -1n;
|
|
22844
|
+
if (action.options.minValue === void 0) seq.minValue = seq.increment > 0n ? 1n : limits.min;
|
|
22845
|
+
}
|
|
22756
22846
|
seq.ownedBy = { table: table.name, column: action.column };
|
|
22757
|
-
schema.sequences.set(
|
|
22758
|
-
col.identity = { always:
|
|
22759
|
-
col.notNull = true;
|
|
22847
|
+
target.schema.sequences.set(target.name, seq);
|
|
22848
|
+
col.identity = { always: action.always, sequence: `${target.schema.name}.${target.name}` };
|
|
22760
22849
|
break;
|
|
22761
22850
|
}
|
|
22762
22851
|
case "drop_identity": {
|
|
@@ -23625,11 +23714,11 @@ if (typeof disposeKey === "symbol") {
|
|
|
23625
23714
|
|
|
23626
23715
|
// src/wire/cluster.ts
|
|
23627
23716
|
var Cluster = class {
|
|
23628
|
-
constructor(db) {
|
|
23629
|
-
this.
|
|
23630
|
-
|
|
23717
|
+
constructor(db, defaultDatabaseName = "postgres") {
|
|
23718
|
+
this.defaultDatabaseName = defaultDatabaseName;
|
|
23719
|
+
this.installDatabase(defaultDatabaseName, db);
|
|
23631
23720
|
}
|
|
23632
|
-
|
|
23721
|
+
defaultDatabaseName;
|
|
23633
23722
|
turnHolder = null;
|
|
23634
23723
|
turnQueue = [];
|
|
23635
23724
|
locks = /* @__PURE__ */ new Map();
|
|
@@ -23638,6 +23727,77 @@ var Cluster = class {
|
|
|
23638
23727
|
sessions = /* @__PURE__ */ new Map();
|
|
23639
23728
|
current = null;
|
|
23640
23729
|
nextPid = 1e3;
|
|
23730
|
+
databases = /* @__PURE__ */ new Map();
|
|
23731
|
+
/** The initial database, retained for the server's backwards-compatible public handle. */
|
|
23732
|
+
get db() {
|
|
23733
|
+
return this.requireDatabase(this.defaultDatabaseName);
|
|
23734
|
+
}
|
|
23735
|
+
databaseNames() {
|
|
23736
|
+
return [...this.databases.keys()].sort();
|
|
23737
|
+
}
|
|
23738
|
+
getDatabase(name) {
|
|
23739
|
+
return this.databases.get(name);
|
|
23740
|
+
}
|
|
23741
|
+
requireDatabase(name) {
|
|
23742
|
+
const db = this.databases.get(name);
|
|
23743
|
+
if (!db) throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
23744
|
+
return db;
|
|
23745
|
+
}
|
|
23746
|
+
createDatabase(name, template) {
|
|
23747
|
+
if (this.databases.has(name)) {
|
|
23748
|
+
throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
|
|
23749
|
+
}
|
|
23750
|
+
const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
|
|
23751
|
+
this.installDatabase(name, db);
|
|
23752
|
+
}
|
|
23753
|
+
dropDatabase(name, requester) {
|
|
23754
|
+
if (name === requester.databaseName || this.activeConnections(name) > 0) {
|
|
23755
|
+
throw new PostgresError(
|
|
23756
|
+
"object_not_in_prerequisite_state",
|
|
23757
|
+
`database "${name}" is being accessed by other users`,
|
|
23758
|
+
"55006"
|
|
23759
|
+
);
|
|
23760
|
+
}
|
|
23761
|
+
if (!this.databases.delete(name)) {
|
|
23762
|
+
throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
23763
|
+
}
|
|
23764
|
+
}
|
|
23765
|
+
renameDatabase(from, to, requester) {
|
|
23766
|
+
if (this.databases.has(to)) {
|
|
23767
|
+
throw new PostgresError("duplicate_object", `database "${to}" already exists`, "42P04");
|
|
23768
|
+
}
|
|
23769
|
+
const db = this.requireDatabase(from);
|
|
23770
|
+
if (from === requester.databaseName || this.activeConnections(from) > 0) {
|
|
23771
|
+
throw new PostgresError(
|
|
23772
|
+
"object_not_in_prerequisite_state",
|
|
23773
|
+
`database "${from}" is being accessed by other users`,
|
|
23774
|
+
"55006"
|
|
23775
|
+
);
|
|
23776
|
+
}
|
|
23777
|
+
this.databases.delete(from);
|
|
23778
|
+
this.installDatabase(to, db);
|
|
23779
|
+
}
|
|
23780
|
+
requireTemplate(name) {
|
|
23781
|
+
if (this.activeConnections(name) > 0) {
|
|
23782
|
+
throw new PostgresError(
|
|
23783
|
+
"object_not_in_prerequisite_state",
|
|
23784
|
+
`source database "${name}" is being accessed by other users`,
|
|
23785
|
+
"55006"
|
|
23786
|
+
);
|
|
23787
|
+
}
|
|
23788
|
+
return this.requireDatabase(name);
|
|
23789
|
+
}
|
|
23790
|
+
activeConnections(name) {
|
|
23791
|
+
let count = 0;
|
|
23792
|
+
for (const session of this.sessions.values()) if (session.databaseName === name) count++;
|
|
23793
|
+
return count;
|
|
23794
|
+
}
|
|
23795
|
+
installDatabase(name, db) {
|
|
23796
|
+
const context = { name, names: () => this.databaseNames() };
|
|
23797
|
+
setDatabaseCatalogContext(db.state, context);
|
|
23798
|
+
this.databases.set(name, db);
|
|
23799
|
+
registerSessionFunctions(this, db);
|
|
23800
|
+
}
|
|
23641
23801
|
newPid() {
|
|
23642
23802
|
return this.nextPid++;
|
|
23643
23803
|
}
|
|
@@ -23729,7 +23889,7 @@ var Cluster = class {
|
|
|
23729
23889
|
}
|
|
23730
23890
|
this.unlockAll(session);
|
|
23731
23891
|
if (this.turnHolder === session) {
|
|
23732
|
-
if (
|
|
23892
|
+
if (session.database.transactions.inTransaction) session.database.transactions.rollback();
|
|
23733
23893
|
this.releaseTurn(session);
|
|
23734
23894
|
}
|
|
23735
23895
|
}
|
|
@@ -23761,8 +23921,7 @@ var LockWait = class extends Error {
|
|
|
23761
23921
|
key;
|
|
23762
23922
|
};
|
|
23763
23923
|
var lockKey = (args2) => args2.map((a) => String(a)).join(":");
|
|
23764
|
-
var registerSessionFunctions = (cluster) => {
|
|
23765
|
-
const db = cluster.db;
|
|
23924
|
+
var registerSessionFunctions = (cluster, db) => {
|
|
23766
23925
|
const me = () => {
|
|
23767
23926
|
if (!cluster.current) throw new PostgresError("internal", "no session is executing", "XX000");
|
|
23768
23927
|
return cluster.current;
|
|
@@ -23929,6 +24088,17 @@ var backend = {
|
|
|
23929
24088
|
for (const oid of oids) w.i32(oid);
|
|
23930
24089
|
}),
|
|
23931
24090
|
notificationResponse: (pid, channel, payload) => message("A", (w) => w.i32(pid).cstring(channel).cstring(payload)),
|
|
24091
|
+
copyInResponse: (columns) => message("G", (w) => {
|
|
24092
|
+
w.u8(0).i16(columns);
|
|
24093
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24094
|
+
}),
|
|
24095
|
+
copyOutResponse: (columns) => message("H", (w) => {
|
|
24096
|
+
w.u8(0).i16(columns);
|
|
24097
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24098
|
+
}),
|
|
24099
|
+
copyData: (data) => message("d", (w) => w.bytes(data)),
|
|
24100
|
+
copyDone: () => message("c", () => {
|
|
24101
|
+
}),
|
|
23932
24102
|
/** The one-byte answers to SSLRequest / GSSENCRequest: no encryption here. */
|
|
23933
24103
|
no: () => new Uint8Array([78])
|
|
23934
24104
|
};
|
|
@@ -24170,6 +24340,23 @@ var LISTEN = /^listen\s+("?)([A-Za-z_][\w$]*)\1\s*$/i;
|
|
|
24170
24340
|
var UNLISTEN = /^unlisten\s+(\*|"?[A-Za-z_][\w$]*"?)\s*$/i;
|
|
24171
24341
|
var NOTIFY = /^notify\s+("?)([A-Za-z_][\w$]*)\1\s*(?:,\s*'((?:[^']|'')*)')?\s*$/i;
|
|
24172
24342
|
var RETURNS_ROWS = /^(select|with|values|show|table|explain)\b/i;
|
|
24343
|
+
var CLUSTER_DDL = /^(create|drop|alter)\s+database\b/i;
|
|
24344
|
+
var IDENT = '(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))';
|
|
24345
|
+
var CREATE_DATABASE = new RegExp(
|
|
24346
|
+
`^create\\s+database\\s+${IDENT}(?:\\s+with)?(?:\\s+template\\s*(?:=\\s*)?${IDENT})?\\s*$`,
|
|
24347
|
+
"i"
|
|
24348
|
+
);
|
|
24349
|
+
var DROP_DATABASE = new RegExp(
|
|
24350
|
+
`^drop\\s+database\\s+(if\\s+exists\\s+)?${IDENT}(?:\\s+with\\s*\\(\\s*force\\s*\\))?\\s*$`,
|
|
24351
|
+
"i"
|
|
24352
|
+
);
|
|
24353
|
+
var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to\\s+${IDENT}\\s*$`, "i");
|
|
24354
|
+
var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
|
|
24355
|
+
var ROW_LOCK = /\s+for\s+(?:no\s+key\s+update|key\s+share|update|share)(?:\s+of\s+(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s*,\s*(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?))*)?(?:\s+(nowait|skip\s+locked))?\s*$/i;
|
|
24356
|
+
var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
|
|
24357
|
+
var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
|
|
24358
|
+
var UPDATE_TARGET = /^update\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?\s+set\s+[\s\S]*?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
|
|
24359
|
+
var DELETE_TARGET = /^delete\s+from\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
|
|
24173
24360
|
var TYPLEN = {
|
|
24174
24361
|
bool: 1,
|
|
24175
24362
|
int2: 2,
|
|
@@ -24262,6 +24449,7 @@ var binaryValue = (type, text) => {
|
|
|
24262
24449
|
}
|
|
24263
24450
|
};
|
|
24264
24451
|
var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
|
|
24452
|
+
var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "comment", "no_op"].includes(stmt.type));
|
|
24265
24453
|
var decodeParam = (oid, format, bytes) => {
|
|
24266
24454
|
if (bytes === null) return null;
|
|
24267
24455
|
if (format === 0) return utf83.decode(bytes);
|
|
@@ -24310,6 +24498,8 @@ var Connection = class {
|
|
|
24310
24498
|
this.socket = socket;
|
|
24311
24499
|
this.cluster = cluster;
|
|
24312
24500
|
this.options = options2;
|
|
24501
|
+
this.database = cluster.db;
|
|
24502
|
+
this.databaseName = cluster.defaultDatabaseName;
|
|
24313
24503
|
this.pid = cluster.newPid();
|
|
24314
24504
|
this.secret = Math.floor(Math.random() * 2147483647);
|
|
24315
24505
|
cluster.sessions.set(this.pid, this);
|
|
@@ -24325,6 +24515,8 @@ var Connection = class {
|
|
|
24325
24515
|
waitingForTurn = null;
|
|
24326
24516
|
waitingForLock = null;
|
|
24327
24517
|
pendingNotifies = [];
|
|
24518
|
+
database;
|
|
24519
|
+
databaseName;
|
|
24328
24520
|
frames = new FrameParser();
|
|
24329
24521
|
inbox = [];
|
|
24330
24522
|
pumping = false;
|
|
@@ -24341,6 +24533,10 @@ var Connection = class {
|
|
|
24341
24533
|
prepared = /* @__PURE__ */ new Map();
|
|
24342
24534
|
portals = /* @__PURE__ */ new Map();
|
|
24343
24535
|
notifications = [];
|
|
24536
|
+
copyIn = null;
|
|
24537
|
+
copyDrain = false;
|
|
24538
|
+
/** Successful writes in this session's READ COMMITTED transaction workspace. */
|
|
24539
|
+
transaction = null;
|
|
24344
24540
|
closed = false;
|
|
24345
24541
|
receive(chunk) {
|
|
24346
24542
|
let messages;
|
|
@@ -24418,6 +24614,20 @@ var Connection = class {
|
|
|
24418
24614
|
this.fatal("08P01", "expected startup or password message");
|
|
24419
24615
|
return;
|
|
24420
24616
|
}
|
|
24617
|
+
if (this.copyIn && message2.kind === "typed") {
|
|
24618
|
+
await this.handleCopyIn(message2.type, message2.body);
|
|
24619
|
+
return;
|
|
24620
|
+
}
|
|
24621
|
+
if (this.copyDrain && message2.kind === "typed") {
|
|
24622
|
+
if (message2.type === "c" || message2.type === "f") {
|
|
24623
|
+
this.copyDrain = false;
|
|
24624
|
+
this.readyForQuery();
|
|
24625
|
+
} else if (message2.type === "X") {
|
|
24626
|
+
this.socket.end();
|
|
24627
|
+
this.dispose();
|
|
24628
|
+
}
|
|
24629
|
+
return;
|
|
24630
|
+
}
|
|
24421
24631
|
if (this.skipUntilSync && message2.type !== "S" && message2.type !== "X") return;
|
|
24422
24632
|
try {
|
|
24423
24633
|
switch (message2.type) {
|
|
@@ -24468,10 +24678,13 @@ var Connection = class {
|
|
|
24468
24678
|
}
|
|
24469
24679
|
this.user = parameters.user ?? "postgres";
|
|
24470
24680
|
const requested = parameters.database && parameters.database.length > 0 ? parameters.database : this.user;
|
|
24471
|
-
|
|
24681
|
+
const database = this.cluster.getDatabase(requested);
|
|
24682
|
+
if (!database || this.options.database !== void 0 && requested !== this.options.database) {
|
|
24472
24683
|
this.fatal("3D000", `database "${requested}" does not exist`);
|
|
24473
24684
|
return;
|
|
24474
24685
|
}
|
|
24686
|
+
this.database = database;
|
|
24687
|
+
this.databaseName = requested;
|
|
24475
24688
|
if (this.options.password !== void 0) {
|
|
24476
24689
|
this.scram = new ScramServer(this.options.password);
|
|
24477
24690
|
this.write(backend.authenticationSASL(["SCRAM-SHA-256"]));
|
|
@@ -24535,7 +24748,7 @@ var Connection = class {
|
|
|
24535
24748
|
}
|
|
24536
24749
|
// --- execution ----------------------------------------------------------------------------
|
|
24537
24750
|
get inTransaction() {
|
|
24538
|
-
return this.
|
|
24751
|
+
return this.transaction !== null;
|
|
24539
24752
|
}
|
|
24540
24753
|
status() {
|
|
24541
24754
|
if (this.aborted) return "E";
|
|
@@ -24558,6 +24771,7 @@ var Connection = class {
|
|
|
24558
24771
|
return outcome;
|
|
24559
24772
|
} catch (error) {
|
|
24560
24773
|
if (this.inTransaction) this.aborted = true;
|
|
24774
|
+
if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
24561
24775
|
const status = error instanceof PostgresError ? error.sqlState : "XX000";
|
|
24562
24776
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
24563
24777
|
throw error;
|
|
@@ -24582,7 +24796,7 @@ var Connection = class {
|
|
|
24582
24796
|
if (!this.inTransaction) this.flushNotifies();
|
|
24583
24797
|
return command("NOTIFY");
|
|
24584
24798
|
}
|
|
24585
|
-
|
|
24799
|
+
if (CLUSTER_DDL.test(text)) return this.executeClusterDdl(text);
|
|
24586
24800
|
for (; ; ) {
|
|
24587
24801
|
if (this.cancelled) throw pgError2("internal", "canceling statement due to user request", "57014");
|
|
24588
24802
|
await this.cluster.acquireTurn(this);
|
|
@@ -24592,7 +24806,7 @@ var Connection = class {
|
|
|
24592
24806
|
faults.delayStatementMs = void 0;
|
|
24593
24807
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
24594
24808
|
}
|
|
24595
|
-
if (faults.dropConnection &&
|
|
24809
|
+
if (faults.dropConnection && this.inTransaction) {
|
|
24596
24810
|
faults.dropConnection = void 0;
|
|
24597
24811
|
this.destroy();
|
|
24598
24812
|
throw new ConnectionDropped();
|
|
@@ -24604,7 +24818,7 @@ var Connection = class {
|
|
|
24604
24818
|
} catch (error) {
|
|
24605
24819
|
if (error instanceof LockWait) {
|
|
24606
24820
|
const key = error.key;
|
|
24607
|
-
|
|
24821
|
+
this.cluster.releaseTurn(this);
|
|
24608
24822
|
await this.waitForLock(key);
|
|
24609
24823
|
continue;
|
|
24610
24824
|
}
|
|
@@ -24634,57 +24848,196 @@ var Connection = class {
|
|
|
24634
24848
|
}
|
|
24635
24849
|
/** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
|
|
24636
24850
|
runStatement(text, params, control) {
|
|
24637
|
-
const db = this.cluster.db;
|
|
24638
|
-
const tx = db.transactions;
|
|
24639
24851
|
const faults = this.options.faults;
|
|
24640
24852
|
if (control && COMMIT.test(text)) {
|
|
24641
24853
|
if (this.aborted) {
|
|
24642
|
-
|
|
24854
|
+
this.transaction = null;
|
|
24643
24855
|
this.aborted = false;
|
|
24644
24856
|
return command("ROLLBACK");
|
|
24645
24857
|
}
|
|
24646
24858
|
if (faults.failCommit !== void 0) {
|
|
24647
24859
|
const code = faults.failCommit;
|
|
24648
24860
|
faults.failCommit = void 0;
|
|
24649
|
-
|
|
24861
|
+
this.transaction = null;
|
|
24650
24862
|
throw pgError2("internal", "could not serialize access due to concurrent update", code);
|
|
24651
24863
|
}
|
|
24652
24864
|
}
|
|
24653
24865
|
if (control && this.aborted && /^(rollback|abort)\b/i.test(text) && !/\bto\b/i.test(text)) {
|
|
24654
|
-
|
|
24866
|
+
this.transaction = null;
|
|
24655
24867
|
this.aborted = false;
|
|
24656
24868
|
return command("ROLLBACK");
|
|
24657
24869
|
}
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
return outcome;
|
|
24670
|
-
} catch (error) {
|
|
24671
|
-
tx.rollbackToSavepoint(name);
|
|
24672
|
-
tx.releaseSavepoint(name);
|
|
24673
|
-
throw error;
|
|
24870
|
+
if (control) return this.transactionControl(text);
|
|
24871
|
+
const workspace = this.transaction === null ? null : this.openWorkspace();
|
|
24872
|
+
const db = workspace ?? this.database;
|
|
24873
|
+
try {
|
|
24874
|
+
const run = () => {
|
|
24875
|
+
const result = this.cluster.as(this, () => this.runQuery(db, text, params));
|
|
24876
|
+
return { empty: false, result, tag: commandTag(result) };
|
|
24877
|
+
};
|
|
24878
|
+
const outcome = db.transaction(run);
|
|
24879
|
+
if (this.transaction && mutatesDatabase(text)) {
|
|
24880
|
+
this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
|
|
24674
24881
|
}
|
|
24882
|
+
return outcome;
|
|
24883
|
+
} finally {
|
|
24884
|
+
workspace?.close();
|
|
24675
24885
|
}
|
|
24676
|
-
return db.transaction(run);
|
|
24677
24886
|
}
|
|
24678
|
-
/** Release the turn
|
|
24887
|
+
/** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
|
|
24679
24888
|
afterStatement() {
|
|
24680
|
-
if (this.cluster.db.transactions.inTransaction) return;
|
|
24681
|
-
this.cluster.unlockAll(this, true);
|
|
24682
24889
|
this.cluster.releaseTurn(this);
|
|
24683
|
-
this.
|
|
24890
|
+
if (!this.inTransaction) {
|
|
24891
|
+
this.cluster.unlockAll(this, true);
|
|
24892
|
+
this.flushNotifies();
|
|
24893
|
+
}
|
|
24894
|
+
}
|
|
24895
|
+
transactionControl(text) {
|
|
24896
|
+
const stmt = parse(text)[0];
|
|
24897
|
+
if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
|
|
24898
|
+
switch (stmt.action) {
|
|
24899
|
+
case "begin":
|
|
24900
|
+
this.transaction ??= { statements: [], savepoints: [] };
|
|
24901
|
+
return command("BEGIN");
|
|
24902
|
+
case "commit": {
|
|
24903
|
+
const transaction = this.transaction;
|
|
24904
|
+
this.transaction = null;
|
|
24905
|
+
if (transaction) {
|
|
24906
|
+
this.database.transaction(() => {
|
|
24907
|
+
for (const logged of transaction.statements) this.replay(this.database, logged);
|
|
24908
|
+
});
|
|
24909
|
+
}
|
|
24910
|
+
return command("COMMIT");
|
|
24911
|
+
}
|
|
24912
|
+
case "rollback":
|
|
24913
|
+
this.transaction = null;
|
|
24914
|
+
return command("ROLLBACK");
|
|
24915
|
+
case "savepoint": {
|
|
24916
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
24917
|
+
this.transaction.savepoints.push({
|
|
24918
|
+
name: stmt.savepointName,
|
|
24919
|
+
statementCount: this.transaction.statements.length
|
|
24920
|
+
});
|
|
24921
|
+
return command("SAVEPOINT");
|
|
24922
|
+
}
|
|
24923
|
+
case "release": {
|
|
24924
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
24925
|
+
this.transaction?.savepoints.splice(index);
|
|
24926
|
+
return command("RELEASE");
|
|
24927
|
+
}
|
|
24928
|
+
case "rollback_to": {
|
|
24929
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
24930
|
+
const savepoint = this.transaction?.savepoints[index];
|
|
24931
|
+
if (!this.transaction || !savepoint) throw pgError2("internal", "savepoint does not exist", "3B001");
|
|
24932
|
+
this.transaction.statements.splice(savepoint.statementCount);
|
|
24933
|
+
this.transaction.savepoints.splice(index + 1);
|
|
24934
|
+
this.aborted = false;
|
|
24935
|
+
return command("ROLLBACK");
|
|
24936
|
+
}
|
|
24937
|
+
}
|
|
24938
|
+
}
|
|
24939
|
+
findSavepoint(name) {
|
|
24940
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
24941
|
+
for (let i = this.transaction.savepoints.length - 1; i >= 0; i--) {
|
|
24942
|
+
if (this.transaction.savepoints[i]?.name === name) return i;
|
|
24943
|
+
}
|
|
24944
|
+
throw pgError2("internal", `savepoint "${name}" does not exist`, "3B001");
|
|
24945
|
+
}
|
|
24946
|
+
/** Rebase this transaction's successful writes onto the latest committed state. */
|
|
24947
|
+
openWorkspace() {
|
|
24948
|
+
const workspace = this.database.branch();
|
|
24949
|
+
setDatabaseCatalogContext(workspace.state, {
|
|
24950
|
+
name: this.databaseName,
|
|
24951
|
+
names: () => this.cluster.databaseNames()
|
|
24952
|
+
});
|
|
24953
|
+
for (const logged of this.transaction?.statements ?? []) {
|
|
24954
|
+
workspace.transaction(() => this.replay(workspace, logged));
|
|
24955
|
+
}
|
|
24956
|
+
return workspace;
|
|
24957
|
+
}
|
|
24958
|
+
replay(db, logged) {
|
|
24959
|
+
this.cluster.as(this, () => {
|
|
24960
|
+
if (logged.kind === "sql") db.prepare(logged.sql).textResult(...logged.params);
|
|
24961
|
+
else {
|
|
24962
|
+
const env = { ctx: new EngineCtx(db.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
|
|
24963
|
+
executeCopyFromData(env, logged.stmt, logged.data);
|
|
24964
|
+
}
|
|
24965
|
+
});
|
|
24966
|
+
}
|
|
24967
|
+
runQuery(db, text, params) {
|
|
24968
|
+
const lock = ROW_LOCK.exec(text);
|
|
24969
|
+
if (!lock) {
|
|
24970
|
+
const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
|
|
24971
|
+
if (mutation) {
|
|
24972
|
+
const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
|
|
24973
|
+
this.lockRows(db, lockQuery, params, "wait");
|
|
24974
|
+
}
|
|
24975
|
+
return db.prepare(text).textResult(...params);
|
|
24976
|
+
}
|
|
24977
|
+
let query = text.slice(0, lock.index).trimEnd();
|
|
24978
|
+
const limitMatch = LIMIT.exec(query);
|
|
24979
|
+
const limit = limitMatch ? Number(limitMatch[1]) : Number.POSITIVE_INFINITY;
|
|
24980
|
+
const offset = limitMatch ? Number(limitMatch[2] ?? 0) : 0;
|
|
24981
|
+
if (limitMatch) query = query.slice(0, limitMatch.index).trimEnd();
|
|
24982
|
+
return this.lockRows(db, query, params, lock[1]?.toLowerCase().replace(/\s+/g, "_") ?? "wait", limit, offset);
|
|
24983
|
+
}
|
|
24984
|
+
lockRows(db, query, params, policy, limit = Number.POSITIVE_INFINITY, offset = 0) {
|
|
24985
|
+
const result = db.prepare(query).textResult(...params);
|
|
24986
|
+
const rows = [];
|
|
24987
|
+
for (const row of result.rows.slice(offset)) {
|
|
24988
|
+
const key = this.rowLockKey(db, query, result.columns, row);
|
|
24989
|
+
if (this.cluster.tryLock(this, key, true)) {
|
|
24990
|
+
rows.push(row);
|
|
24991
|
+
if (rows.length >= limit) break;
|
|
24992
|
+
continue;
|
|
24993
|
+
}
|
|
24994
|
+
if (policy === "skip_locked") continue;
|
|
24995
|
+
if (policy === "nowait") {
|
|
24996
|
+
throw pgError2("internal", "could not obtain lock on row in relation", "55P03");
|
|
24997
|
+
}
|
|
24998
|
+
throw new LockWait(key);
|
|
24999
|
+
}
|
|
25000
|
+
return { ...result, rows, rowCount: rows.length };
|
|
25001
|
+
}
|
|
25002
|
+
rowLockKey(db, query, columns, row) {
|
|
25003
|
+
const rawName = FROM_TABLE.exec(query)?.[1] ?? "query";
|
|
25004
|
+
const parts = rawName.split(".").map((part) => part.startsWith('"') ? part.slice(1, -1).replaceAll('""', '"') : part.toLowerCase());
|
|
25005
|
+
const table = db.state.findTable(parts);
|
|
25006
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
25007
|
+
const indexes = primary?.columns.map((column) => columns.indexOf(column)) ?? [];
|
|
25008
|
+
const identity = indexes.length > 0 && indexes.every((index) => index >= 0) ? indexes.map((i) => row[i]) : row;
|
|
25009
|
+
return `row:${this.databaseName}:${parts.join(".")}:${JSON.stringify(identity)}`;
|
|
24684
25010
|
}
|
|
24685
25011
|
flushNotifies() {
|
|
24686
25012
|
for (const { channel, payload } of this.pendingNotifies.splice(0)) this.cluster.notify(this, channel, payload);
|
|
24687
25013
|
}
|
|
25014
|
+
executeClusterDdl(text) {
|
|
25015
|
+
if (this.inTransaction) {
|
|
25016
|
+
throw pgError2("internal", "CREATE/DROP/ALTER DATABASE cannot run inside a transaction block", "25001");
|
|
25017
|
+
}
|
|
25018
|
+
const create = CREATE_DATABASE.exec(text);
|
|
25019
|
+
if (create) {
|
|
25020
|
+
const name = identifier(create[1], create[2]);
|
|
25021
|
+
const template = create[3] === void 0 && create[4] === void 0 ? void 0 : identifier(create[3], create[4]);
|
|
25022
|
+
this.cluster.createDatabase(name, template);
|
|
25023
|
+
return command("CREATE DATABASE");
|
|
25024
|
+
}
|
|
25025
|
+
const drop = DROP_DATABASE.exec(text);
|
|
25026
|
+
if (drop) {
|
|
25027
|
+
const name = identifier(drop[2], drop[3]);
|
|
25028
|
+
if (drop[1] && !this.cluster.getDatabase(name)) return command("DROP DATABASE");
|
|
25029
|
+
this.cluster.dropDatabase(name, this);
|
|
25030
|
+
return command("DROP DATABASE");
|
|
25031
|
+
}
|
|
25032
|
+
const rename = RENAME_DATABASE.exec(text);
|
|
25033
|
+
if (rename) {
|
|
25034
|
+
const from = identifier(rename[1], rename[2]);
|
|
25035
|
+
const to = identifier(rename[3], rename[4]);
|
|
25036
|
+
this.cluster.renameDatabase(from, to, this);
|
|
25037
|
+
return command("ALTER DATABASE");
|
|
25038
|
+
}
|
|
25039
|
+
throw pgError2("syntax", "unsupported CREATE/DROP/ALTER DATABASE option", "0A000");
|
|
25040
|
+
}
|
|
24688
25041
|
listen(channel) {
|
|
24689
25042
|
const set = this.cluster.listeners.get(channel) ?? /* @__PURE__ */ new Set();
|
|
24690
25043
|
set.add(this);
|
|
@@ -24705,6 +25058,24 @@ var Connection = class {
|
|
|
24705
25058
|
this.readyForQuery();
|
|
24706
25059
|
return;
|
|
24707
25060
|
}
|
|
25061
|
+
if (statements.length === 1) {
|
|
25062
|
+
const copy = this.copyStatement(statements[0]);
|
|
25063
|
+
if (copy?.direction === "from") {
|
|
25064
|
+
await this.beginCopyIn(copy);
|
|
25065
|
+
return;
|
|
25066
|
+
}
|
|
25067
|
+
if (copy?.direction === "to") {
|
|
25068
|
+
try {
|
|
25069
|
+
const outcome = await this.execute(statements[0]);
|
|
25070
|
+
this.sendCopyOut(outcome, this.copyColumnCount(copy));
|
|
25071
|
+
} catch (error) {
|
|
25072
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
25073
|
+
if (this.inTransaction) this.aborted = true;
|
|
25074
|
+
}
|
|
25075
|
+
this.readyForQuery();
|
|
25076
|
+
return;
|
|
25077
|
+
}
|
|
25078
|
+
}
|
|
24708
25079
|
const implicit = statements.length > 1 && !this.inTransaction && !statements.some((s) => TX_CONTROL.test(s));
|
|
24709
25080
|
let failed = false;
|
|
24710
25081
|
try {
|
|
@@ -24725,6 +25096,138 @@ var Connection = class {
|
|
|
24725
25096
|
if (failed && this.inTransaction) this.aborted = true;
|
|
24726
25097
|
this.readyForQuery();
|
|
24727
25098
|
}
|
|
25099
|
+
copyStatement(sql) {
|
|
25100
|
+
try {
|
|
25101
|
+
const statements = parse(sql);
|
|
25102
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25103
|
+
return stmt?.type === "copy" ? stmt : null;
|
|
25104
|
+
} catch {
|
|
25105
|
+
return null;
|
|
25106
|
+
}
|
|
25107
|
+
}
|
|
25108
|
+
copyColumnCount(stmt) {
|
|
25109
|
+
if (stmt.columns) return stmt.columns.length;
|
|
25110
|
+
if (stmt.table) return this.database.state.findTable(stmt.table)?.columns.length ?? 0;
|
|
25111
|
+
return 0;
|
|
25112
|
+
}
|
|
25113
|
+
async handleCopyIn(type, body) {
|
|
25114
|
+
const copy = this.copyIn;
|
|
25115
|
+
if (type === "d") {
|
|
25116
|
+
try {
|
|
25117
|
+
copy.buffer += copy.decoder.decode(body.rest(), { stream: true });
|
|
25118
|
+
this.flushCopyRecords(copy, false);
|
|
25119
|
+
} catch (error) {
|
|
25120
|
+
this.abortCopyIn(copy, error, true);
|
|
25121
|
+
}
|
|
25122
|
+
return;
|
|
25123
|
+
}
|
|
25124
|
+
if (type === "f") {
|
|
25125
|
+
const detail = body.cstring();
|
|
25126
|
+
this.abortCopyIn(copy, pgError2("internal", `COPY from stdin failed: ${detail}`, "57014"), false);
|
|
25127
|
+
return;
|
|
25128
|
+
}
|
|
25129
|
+
if (type === "c") {
|
|
25130
|
+
this.finishCopyIn(copy);
|
|
25131
|
+
return;
|
|
25132
|
+
}
|
|
25133
|
+
if (type === "H") return;
|
|
25134
|
+
if (type === "X") {
|
|
25135
|
+
this.socket.end();
|
|
25136
|
+
this.dispose();
|
|
25137
|
+
return;
|
|
25138
|
+
}
|
|
25139
|
+
this.fatal("08P01", `unexpected frontend message type ${JSON.stringify(type)} during COPY`);
|
|
25140
|
+
}
|
|
25141
|
+
async beginCopyIn(stmt) {
|
|
25142
|
+
await this.cluster.acquireTurn(this);
|
|
25143
|
+
const workspace = this.transaction ? this.openWorkspace() : null;
|
|
25144
|
+
const database = workspace ?? this.database;
|
|
25145
|
+
database.transactions.begin();
|
|
25146
|
+
this.copyIn = {
|
|
25147
|
+
stmt,
|
|
25148
|
+
database,
|
|
25149
|
+
workspace: workspace !== null,
|
|
25150
|
+
buffer: "",
|
|
25151
|
+
decoder: new TextDecoder(),
|
|
25152
|
+
rowCount: 0,
|
|
25153
|
+
headerPending: stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true",
|
|
25154
|
+
loggedData: ""
|
|
25155
|
+
};
|
|
25156
|
+
this.write(backend.copyInResponse(this.copyColumnCount(stmt)));
|
|
25157
|
+
}
|
|
25158
|
+
finishCopyIn(copy) {
|
|
25159
|
+
const tx = copy.database.transactions;
|
|
25160
|
+
try {
|
|
25161
|
+
copy.buffer += copy.decoder.decode();
|
|
25162
|
+
this.flushCopyRecords(copy, true);
|
|
25163
|
+
tx.commit();
|
|
25164
|
+
if (copy.workspace) {
|
|
25165
|
+
this.transaction?.statements.push({ kind: "copy", stmt: copy.stmt, data: copy.loggedData });
|
|
25166
|
+
copy.database.close();
|
|
25167
|
+
}
|
|
25168
|
+
this.copyIn = null;
|
|
25169
|
+
this.write(backend.commandComplete(`COPY ${copy.rowCount}`));
|
|
25170
|
+
this.cluster.releaseTurn(this);
|
|
25171
|
+
this.readyForQuery();
|
|
25172
|
+
} catch (error) {
|
|
25173
|
+
this.abortCopyIn(copy, error, false);
|
|
25174
|
+
}
|
|
25175
|
+
}
|
|
25176
|
+
flushCopyRecords(copy, final) {
|
|
25177
|
+
const format = String(copy.stmt.options.format ?? "text").toLowerCase();
|
|
25178
|
+
const quote = String(copy.stmt.options.quote ?? '"');
|
|
25179
|
+
const boundaries = [];
|
|
25180
|
+
let quoted = false;
|
|
25181
|
+
for (let i = 0; i < copy.buffer.length; i++) {
|
|
25182
|
+
const char = copy.buffer[i];
|
|
25183
|
+
if (format === "csv" && char === quote) {
|
|
25184
|
+
if (quoted && copy.buffer[i + 1] === quote) i++;
|
|
25185
|
+
else quoted = !quoted;
|
|
25186
|
+
} else if (char === "\n" && !quoted) {
|
|
25187
|
+
boundaries.push(i + 1);
|
|
25188
|
+
}
|
|
25189
|
+
}
|
|
25190
|
+
if (final && quoted) {
|
|
25191
|
+
throw new PostgresError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
25192
|
+
}
|
|
25193
|
+
const end = boundaries.at(-1) ?? 0;
|
|
25194
|
+
const data = final ? copy.buffer : end > 0 ? copy.buffer.slice(0, end) : "";
|
|
25195
|
+
if (data === "") return;
|
|
25196
|
+
copy.buffer = final ? "" : copy.buffer.slice(end);
|
|
25197
|
+
const stmt = copy.headerPending ? copy.stmt : { ...copy.stmt, options: { ...copy.stmt.options, header: false } };
|
|
25198
|
+
const env = {
|
|
25199
|
+
ctx: new EngineCtx(copy.database.state),
|
|
25200
|
+
params: null,
|
|
25201
|
+
ctes: /* @__PURE__ */ new Map(),
|
|
25202
|
+
outer: null
|
|
25203
|
+
};
|
|
25204
|
+
const result = this.cluster.as(this, () => executeCopyFromData(env, stmt, data));
|
|
25205
|
+
copy.rowCount += result.rowCount;
|
|
25206
|
+
copy.loggedData += data;
|
|
25207
|
+
copy.headerPending = false;
|
|
25208
|
+
}
|
|
25209
|
+
abortCopyIn(copy, error, drain) {
|
|
25210
|
+
const tx = copy.database.transactions;
|
|
25211
|
+
if (tx.inTransaction) tx.rollback();
|
|
25212
|
+
if (copy.workspace) copy.database.close();
|
|
25213
|
+
if (this.inTransaction) this.aborted = true;
|
|
25214
|
+
this.cluster.releaseTurn(this);
|
|
25215
|
+
this.copyIn = null;
|
|
25216
|
+
this.copyDrain = drain;
|
|
25217
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
25218
|
+
if (!drain) this.readyForQuery();
|
|
25219
|
+
}
|
|
25220
|
+
sendCopyOut(outcome, columns) {
|
|
25221
|
+
if (outcome.empty) throw pgError2("internal", "COPY TO produced no result", "XX000");
|
|
25222
|
+
this.write(backend.copyOutResponse(columns));
|
|
25223
|
+
for (const row of outcome.result.rows) {
|
|
25224
|
+
const line = row[0] ?? "";
|
|
25225
|
+
this.write(backend.copyData(utf83.encode(`${line}
|
|
25226
|
+
`)));
|
|
25227
|
+
}
|
|
25228
|
+
this.write(backend.copyDone());
|
|
25229
|
+
this.write(backend.commandComplete(outcome.tag));
|
|
25230
|
+
}
|
|
24728
25231
|
sendOutcome(outcome, resultFormats, opts = { rowDescription: false }) {
|
|
24729
25232
|
const from = opts.from ?? 0;
|
|
24730
25233
|
const limit = opts.limit ?? 0;
|
|
@@ -24779,7 +25282,7 @@ var Connection = class {
|
|
|
24779
25282
|
for (let i = 0; i < count; i++) paramOids.push(body.i32());
|
|
24780
25283
|
const text = sql.trim();
|
|
24781
25284
|
if (text !== "" && !LISTEN.test(text) && !UNLISTEN.test(text) && !NOTIFY.test(text) && !TX_CONTROL.test(text)) {
|
|
24782
|
-
this.
|
|
25285
|
+
if (!CLUSTER_DDL.test(text)) this.database.prepare(text);
|
|
24783
25286
|
}
|
|
24784
25287
|
if (name !== "" && this.prepared.has(name)) {
|
|
24785
25288
|
throw pgError2("internal", `prepared statement "${name}" already exists`, "42P05");
|
|
@@ -24838,7 +25341,7 @@ var Connection = class {
|
|
|
24838
25341
|
*/
|
|
24839
25342
|
async shapeOf(prepared) {
|
|
24840
25343
|
if (!RETURNS_ROWS.test(prepared.sql)) return null;
|
|
24841
|
-
const db = this.
|
|
25344
|
+
const db = this.database;
|
|
24842
25345
|
await this.cluster.acquireTurn(this);
|
|
24843
25346
|
try {
|
|
24844
25347
|
const params = prepared.paramOids.map(() => null);
|
|
@@ -24955,7 +25458,8 @@ var isSnapshot = (value) => typeof value === "object" && value !== null && "open
|
|
|
24955
25458
|
var serve = (input = {}) => {
|
|
24956
25459
|
const options2 = normalize(input);
|
|
24957
25460
|
const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
|
|
24958
|
-
const
|
|
25461
|
+
const initialName = options2.databaseName ?? "postgres";
|
|
25462
|
+
const cluster = new Cluster(database, initialName);
|
|
24959
25463
|
const faults = {};
|
|
24960
25464
|
const connections = /* @__PURE__ */ new Set();
|
|
24961
25465
|
const host = options2.host ?? "127.0.0.1";
|
|
@@ -24979,13 +25483,19 @@ var serve = (input = {}) => {
|
|
|
24979
25483
|
server2.removeListener("error", reject);
|
|
24980
25484
|
const address = server2.address();
|
|
24981
25485
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
24982
|
-
const name =
|
|
25486
|
+
const name = initialName;
|
|
24983
25487
|
const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
|
|
24984
25488
|
resolve({
|
|
24985
25489
|
port,
|
|
24986
25490
|
host,
|
|
24987
25491
|
connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
|
|
24988
25492
|
database,
|
|
25493
|
+
databaseNames() {
|
|
25494
|
+
return cluster.databaseNames();
|
|
25495
|
+
},
|
|
25496
|
+
getDatabase(databaseName) {
|
|
25497
|
+
return cluster.requireDatabase(databaseName);
|
|
25498
|
+
},
|
|
24989
25499
|
server: server2,
|
|
24990
25500
|
get connections() {
|
|
24991
25501
|
return connections.size;
|
|
@@ -24993,8 +25503,8 @@ var serve = (input = {}) => {
|
|
|
24993
25503
|
fault(next) {
|
|
24994
25504
|
Object.assign(faults, next);
|
|
24995
25505
|
},
|
|
24996
|
-
snapshot() {
|
|
24997
|
-
return
|
|
25506
|
+
snapshot(databaseName = initialName) {
|
|
25507
|
+
return cluster.requireDatabase(databaseName).snapshot();
|
|
24998
25508
|
},
|
|
24999
25509
|
close() {
|
|
25000
25510
|
for (const connection of connections) connection.destroy();
|