@crvouga/mockingbird-service-postgres 1.5.1 → 1.7.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 +27 -0
- package/COMPATIBILITY.md +14 -5
- package/README.md +30 -13
- package/dist/admin.js +191 -54
- 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/server.d.ts +9 -0
- package/dist/server.js +25808 -0
- package/dist/server.js.map +7 -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 +1766 -173
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/cluster.d.ts +23 -8
- package/dist/wire/connection.d.ts +25 -1
- package/dist/wire/fleet.d.ts +80 -0
- package/dist/wire/index.d.ts +11 -1
- package/dist/wire/index.js +608 -78
- package/dist/wire/index.js.map +3 -3
- package/dist/wire/probe.d.ts +2 -0
- package/dist/wire/protocol.d.ts +4 -0
- package/package.json +13 -2
package/dist/wire/index.js
CHANGED
|
@@ -3463,7 +3463,13 @@ var DatabaseState = class _DatabaseState {
|
|
|
3463
3463
|
this.settings.set("lc_numeric", "C");
|
|
3464
3464
|
this.settings.set("lc_time", "C");
|
|
3465
3465
|
this.settings.set("default_transaction_isolation", "read committed");
|
|
3466
|
+
this.settings.set("lock_timeout", "0");
|
|
3467
|
+
this.settings.set("idle_in_transaction_session_timeout", "0");
|
|
3468
|
+
this.settings.set("transaction_timeout", "0");
|
|
3466
3469
|
this.settings.set("statement_timeout", "0");
|
|
3470
|
+
this.settings.set("check_function_bodies", "on");
|
|
3471
|
+
this.settings.set("xmloption", "content");
|
|
3472
|
+
this.settings.set("row_security", "on");
|
|
3467
3473
|
this.settings.set("array_nulls", "on");
|
|
3468
3474
|
this.settings.set("backslash_quote", "safe_encoding");
|
|
3469
3475
|
}
|
|
@@ -7065,6 +7071,22 @@ function exactDegTan(x) {
|
|
|
7065
7071
|
return Math.tan(x * Math.PI / 180);
|
|
7066
7072
|
}
|
|
7067
7073
|
|
|
7074
|
+
// src/sql/deparse.ts
|
|
7075
|
+
function quoteIdent(name) {
|
|
7076
|
+
if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
|
|
7077
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
// src/runtime/database-context.ts
|
|
7081
|
+
var contexts = /* @__PURE__ */ new WeakMap();
|
|
7082
|
+
var fallback = { name: "postgres", names: () => ["postgres"] };
|
|
7083
|
+
function databaseCatalogContext(state) {
|
|
7084
|
+
return contexts.get(state) ?? fallback;
|
|
7085
|
+
}
|
|
7086
|
+
function setDatabaseCatalogContext(state, context) {
|
|
7087
|
+
contexts.set(state, context);
|
|
7088
|
+
}
|
|
7089
|
+
|
|
7068
7090
|
// src/functions/misc-fns.ts
|
|
7069
7091
|
var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
|
|
7070
7092
|
function findSequenceForCall(ctx, name) {
|
|
@@ -7148,8 +7170,8 @@ function sequenceNextval(ctx, seq) {
|
|
|
7148
7170
|
function getMiscFunctions() {
|
|
7149
7171
|
const m = /* @__PURE__ */ new Map();
|
|
7150
7172
|
m.set("version", () => tv("text", PG_VERSION_TEXT));
|
|
7151
|
-
m.set("current_database", () => tv("name",
|
|
7152
|
-
m.set("current_catalog", () => tv("name",
|
|
7173
|
+
m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7174
|
+
m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7153
7175
|
m.set("current_schema", (ctx) => {
|
|
7154
7176
|
const path = ctx.state.effectiveSearchPath();
|
|
7155
7177
|
const first = path.find((s) => ctx.state.schemas.has(s));
|
|
@@ -7271,7 +7293,7 @@ function getMiscFunctions() {
|
|
|
7271
7293
|
if (!col.identity) return tv("text", null);
|
|
7272
7294
|
return tv(
|
|
7273
7295
|
"text",
|
|
7274
|
-
col.identity.sequence.split(".").map(
|
|
7296
|
+
col.identity.sequence.split(".").map(quoteIdent).join(".")
|
|
7275
7297
|
);
|
|
7276
7298
|
})
|
|
7277
7299
|
);
|
|
@@ -14521,14 +14543,19 @@ var Parser = class {
|
|
|
14521
14543
|
parseIdentityOptions() {
|
|
14522
14544
|
const options = {};
|
|
14523
14545
|
if (this.eatPunct("(")) {
|
|
14524
|
-
Object.assign(options, this.parseSequenceOptions());
|
|
14546
|
+
Object.assign(options, this.parseSequenceOptions(true));
|
|
14525
14547
|
this.expectPunct(")");
|
|
14526
14548
|
}
|
|
14527
14549
|
return options;
|
|
14528
14550
|
}
|
|
14529
|
-
parseSequenceOptions() {
|
|
14551
|
+
parseSequenceOptions(identity = false) {
|
|
14530
14552
|
const options = {};
|
|
14531
14553
|
for (; ; ) {
|
|
14554
|
+
if (identity && this.eatKw("sequence")) {
|
|
14555
|
+
this.expectKw("name");
|
|
14556
|
+
options.sequenceName = this.qualifiedName();
|
|
14557
|
+
continue;
|
|
14558
|
+
}
|
|
14532
14559
|
if (this.eatKw("increment")) {
|
|
14533
14560
|
this.eatKw("by");
|
|
14534
14561
|
options.increment = this.parseSignedBigInt();
|
|
@@ -20530,6 +20557,27 @@ function parseCsvLine(line, delim, quote) {
|
|
|
20530
20557
|
}
|
|
20531
20558
|
return fields;
|
|
20532
20559
|
}
|
|
20560
|
+
function splitCsvRecords(data, quote) {
|
|
20561
|
+
const records = [];
|
|
20562
|
+
let start = 0;
|
|
20563
|
+
let quoted = false;
|
|
20564
|
+
for (let i = 0; i < data.length; i++) {
|
|
20565
|
+
const char = data[i];
|
|
20566
|
+
if (char === quote) {
|
|
20567
|
+
if (quoted && data[i + 1] === quote) i++;
|
|
20568
|
+
else quoted = !quoted;
|
|
20569
|
+
continue;
|
|
20570
|
+
}
|
|
20571
|
+
if (char === "\n" && !quoted) {
|
|
20572
|
+
const end = i > start && data[i - 1] === "\r" ? i - 1 : i;
|
|
20573
|
+
records.push(data.slice(start, end));
|
|
20574
|
+
start = i + 1;
|
|
20575
|
+
}
|
|
20576
|
+
}
|
|
20577
|
+
if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
20578
|
+
if (start < data.length) records.push(data.slice(start));
|
|
20579
|
+
return records;
|
|
20580
|
+
}
|
|
20533
20581
|
function executeCopyFromData(env, stmt, data) {
|
|
20534
20582
|
const format = String(stmt.options.format ?? "text").toLowerCase();
|
|
20535
20583
|
if (format !== "text" && format !== "csv") throw unsupported(`COPY format ${format}`);
|
|
@@ -20540,7 +20588,7 @@ function executeCopyFromData(env, stmt, data) {
|
|
|
20540
20588
|
const nullStr = String(stmt.options.null ?? (format === "csv" ? "" : "\\N"));
|
|
20541
20589
|
const quote = String(stmt.options.quote ?? '"');
|
|
20542
20590
|
const header = stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true";
|
|
20543
|
-
let lines = data.split(/\r?\n/);
|
|
20591
|
+
let lines = format === "csv" ? splitCsvRecords(data, quote) : data.split(/\r?\n/);
|
|
20544
20592
|
const endMarker = lines.indexOf("\\.");
|
|
20545
20593
|
if (endMarker !== -1) lines = lines.slice(0, endMarker);
|
|
20546
20594
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
@@ -21274,7 +21322,8 @@ function pgSettings(ctx) {
|
|
|
21274
21322
|
"pg_settings"
|
|
21275
21323
|
);
|
|
21276
21324
|
}
|
|
21277
|
-
function pgDatabase() {
|
|
21325
|
+
function pgDatabase(ctx) {
|
|
21326
|
+
const names = databaseCatalogContext(ctx.state).names();
|
|
21278
21327
|
return rel(
|
|
21279
21328
|
[
|
|
21280
21329
|
["oid", "oid"],
|
|
@@ -21283,7 +21332,7 @@ function pgDatabase() {
|
|
|
21283
21332
|
["datistemplate", "bool"],
|
|
21284
21333
|
["datallowconn", "bool"]
|
|
21285
21334
|
],
|
|
21286
|
-
[
|
|
21335
|
+
names.map((name, index) => [5 + index, name, 6, false, true]),
|
|
21287
21336
|
"pg_database"
|
|
21288
21337
|
);
|
|
21289
21338
|
}
|
|
@@ -21318,13 +21367,14 @@ function pgTrigger(ctx) {
|
|
|
21318
21367
|
"pg_trigger"
|
|
21319
21368
|
);
|
|
21320
21369
|
}
|
|
21321
|
-
var
|
|
21370
|
+
var catalogName = (state) => databaseCatalogContext(state).name;
|
|
21322
21371
|
function infoSchemata(state) {
|
|
21372
|
+
const catalog = catalogName(state);
|
|
21323
21373
|
const rows = [
|
|
21324
|
-
[
|
|
21325
|
-
[
|
|
21374
|
+
[catalog, "pg_catalog", OWNER],
|
|
21375
|
+
[catalog, "information_schema", OWNER]
|
|
21326
21376
|
];
|
|
21327
|
-
for (const s of state.schemas.values()) rows.push([
|
|
21377
|
+
for (const s of state.schemas.values()) rows.push([catalog, s.name, OWNER]);
|
|
21328
21378
|
return rel(
|
|
21329
21379
|
[
|
|
21330
21380
|
["catalog_name", "name"],
|
|
@@ -21336,13 +21386,14 @@ function infoSchemata(state) {
|
|
|
21336
21386
|
);
|
|
21337
21387
|
}
|
|
21338
21388
|
function infoTables(ctx) {
|
|
21389
|
+
const catalog = catalogName(ctx.state);
|
|
21339
21390
|
const rows = [];
|
|
21340
21391
|
for (const schema of ctx.state.schemas.values()) {
|
|
21341
21392
|
for (const t of schema.tables.values()) {
|
|
21342
|
-
rows.push([
|
|
21393
|
+
rows.push([catalog, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
|
|
21343
21394
|
}
|
|
21344
21395
|
for (const v of schema.views.values()) {
|
|
21345
|
-
if (!v.materialized) rows.push([
|
|
21396
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, "VIEW", "NO"]);
|
|
21346
21397
|
}
|
|
21347
21398
|
}
|
|
21348
21399
|
return rel(
|
|
@@ -21358,6 +21409,7 @@ function infoTables(ctx) {
|
|
|
21358
21409
|
);
|
|
21359
21410
|
}
|
|
21360
21411
|
function infoColumns(ctx) {
|
|
21412
|
+
const catalog = catalogName(ctx.state);
|
|
21361
21413
|
const rows = [];
|
|
21362
21414
|
for (const t of allTables(ctx.state)) {
|
|
21363
21415
|
t.columns.forEach((c, i) => {
|
|
@@ -21368,7 +21420,7 @@ function infoColumns(ctx) {
|
|
|
21368
21420
|
const dataType = isArrayType(id) ? "ARRAY" : isEnumType(id) ? "USER-DEFINED" : typeDisplayName(id);
|
|
21369
21421
|
const udt = isArrayType(id) ? `_${arrayUdt(id)}` : isEnumType(id) ? id.slice(5).split(".").pop() : id;
|
|
21370
21422
|
rows.push([
|
|
21371
|
-
|
|
21423
|
+
catalog,
|
|
21372
21424
|
t.schema,
|
|
21373
21425
|
t.name,
|
|
21374
21426
|
c.name,
|
|
@@ -21413,10 +21465,11 @@ function arrayUdt(t) {
|
|
|
21413
21465
|
return isEnumType(elem) ? elem.slice(5).split(".").pop() : elem;
|
|
21414
21466
|
}
|
|
21415
21467
|
function infoViews(ctx) {
|
|
21468
|
+
const catalog = catalogName(ctx.state);
|
|
21416
21469
|
const rows = [];
|
|
21417
21470
|
for (const schema of ctx.state.schemas.values()) {
|
|
21418
21471
|
for (const v of schema.views.values()) {
|
|
21419
|
-
if (!v.materialized) rows.push([
|
|
21472
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, null]);
|
|
21420
21473
|
}
|
|
21421
21474
|
}
|
|
21422
21475
|
return rel(
|
|
@@ -21431,10 +21484,11 @@ function infoViews(ctx) {
|
|
|
21431
21484
|
);
|
|
21432
21485
|
}
|
|
21433
21486
|
function infoSequences(ctx) {
|
|
21487
|
+
const catalog = catalogName(ctx.state);
|
|
21434
21488
|
const rows = [];
|
|
21435
21489
|
for (const s of allSequences(ctx.state)) {
|
|
21436
21490
|
rows.push([
|
|
21437
|
-
|
|
21491
|
+
catalog,
|
|
21438
21492
|
s.schema,
|
|
21439
21493
|
s.name,
|
|
21440
21494
|
typeDisplayName(s.dataType),
|
|
@@ -21462,11 +21516,12 @@ function infoSequences(ctx) {
|
|
|
21462
21516
|
);
|
|
21463
21517
|
}
|
|
21464
21518
|
function infoTableConstraints(ctx) {
|
|
21519
|
+
const catalog = catalogName(ctx.state);
|
|
21465
21520
|
const rows = [];
|
|
21466
21521
|
for (const t of allTables(ctx.state)) {
|
|
21467
21522
|
for (const c of t.constraints) {
|
|
21468
21523
|
const type = c.kind === "primary_key" ? "PRIMARY KEY" : c.kind === "unique" ? "UNIQUE" : c.kind === "check" ? "CHECK" : "FOREIGN KEY";
|
|
21469
|
-
rows.push([
|
|
21524
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, type]);
|
|
21470
21525
|
}
|
|
21471
21526
|
}
|
|
21472
21527
|
return rel(
|
|
@@ -21484,12 +21539,13 @@ function infoTableConstraints(ctx) {
|
|
|
21484
21539
|
);
|
|
21485
21540
|
}
|
|
21486
21541
|
function infoKeyColumnUsage(ctx) {
|
|
21542
|
+
const catalog = catalogName(ctx.state);
|
|
21487
21543
|
const rows = [];
|
|
21488
21544
|
for (const t of allTables(ctx.state)) {
|
|
21489
21545
|
for (const c of t.constraints) {
|
|
21490
21546
|
if (c.kind !== "primary_key" && c.kind !== "unique" && c.kind !== "foreign_key") continue;
|
|
21491
21547
|
c.columns.forEach((col, i) => {
|
|
21492
|
-
rows.push([
|
|
21548
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, col, i + 1]);
|
|
21493
21549
|
});
|
|
21494
21550
|
}
|
|
21495
21551
|
}
|
|
@@ -21509,12 +21565,13 @@ function infoKeyColumnUsage(ctx) {
|
|
|
21509
21565
|
);
|
|
21510
21566
|
}
|
|
21511
21567
|
function infoRoutines(ctx) {
|
|
21568
|
+
const catalog = catalogName(ctx.state);
|
|
21512
21569
|
const rows = [];
|
|
21513
21570
|
for (const schema of ctx.state.schemas.values()) {
|
|
21514
21571
|
for (const fns of schema.functions.values()) {
|
|
21515
21572
|
for (const f of fns) {
|
|
21516
21573
|
rows.push([
|
|
21517
|
-
|
|
21574
|
+
catalog,
|
|
21518
21575
|
f.schema,
|
|
21519
21576
|
f.name,
|
|
21520
21577
|
"FUNCTION",
|
|
@@ -21571,7 +21628,7 @@ function buildCatalogRelation(ctx, schema, name) {
|
|
|
21571
21628
|
case "pg_settings":
|
|
21572
21629
|
return pgSettings(ctx);
|
|
21573
21630
|
case "pg_database":
|
|
21574
|
-
return pgDatabase();
|
|
21631
|
+
return pgDatabase(ctx);
|
|
21575
21632
|
case "pg_roles":
|
|
21576
21633
|
case "pg_user":
|
|
21577
21634
|
return pgRoles();
|
|
@@ -21752,6 +21809,11 @@ var MAX_IDENT = 63;
|
|
|
21752
21809
|
function truncIdent(s) {
|
|
21753
21810
|
return s.length > MAX_IDENT ? s.slice(0, MAX_IDENT) : s;
|
|
21754
21811
|
}
|
|
21812
|
+
function identitySequenceTarget(env, tableSchema, defaultName, options) {
|
|
21813
|
+
const parts = options.sequenceName;
|
|
21814
|
+
if (!parts || parts.length === 0) return { schema: tableSchema, name: defaultName };
|
|
21815
|
+
return targetSchema(env, parts);
|
|
21816
|
+
}
|
|
21755
21817
|
var SEQ_LIMITS = {
|
|
21756
21818
|
int2: { min: -32768n, max: 32767n },
|
|
21757
21819
|
int4: { min: -2147483648n, max: 2147483647n },
|
|
@@ -21928,8 +21990,8 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21928
21990
|
break;
|
|
21929
21991
|
}
|
|
21930
21992
|
case "generated_identity": {
|
|
21931
|
-
const
|
|
21932
|
-
const seq = buildSequence(env, schema,
|
|
21993
|
+
const target = identitySequenceTarget(env, schema, truncIdent(`${tableName}_${def.name}_seq`), c.options);
|
|
21994
|
+
const seq = buildSequence(env, target.schema, target.name, c.options, false);
|
|
21933
21995
|
if (!c.options.as) {
|
|
21934
21996
|
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
21935
21997
|
const limits = SEQ_LIMITS[seq.dataType];
|
|
@@ -21938,7 +22000,7 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21938
22000
|
}
|
|
21939
22001
|
seq.ownedBy = { table: tableName, column: def.name };
|
|
21940
22002
|
out.sequences.push(seq);
|
|
21941
|
-
col.identity = { always: c.always, sequence: `${schema.name}.${
|
|
22003
|
+
col.identity = { always: c.always, sequence: `${target.schema.name}.${target.name}` };
|
|
21942
22004
|
col.notNull = true;
|
|
21943
22005
|
break;
|
|
21944
22006
|
}
|
|
@@ -22074,10 +22136,11 @@ function executeCreateTable(env, stmt) {
|
|
|
22074
22136
|
throw pgError("invalid_table_definition", `multiple primary keys for table "${name}" are not allowed`, "42P16");
|
|
22075
22137
|
}
|
|
22076
22138
|
for (const seq of built.sequences) {
|
|
22077
|
-
|
|
22139
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
22140
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
22078
22141
|
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
22079
22142
|
}
|
|
22080
|
-
|
|
22143
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
22081
22144
|
}
|
|
22082
22145
|
const table = new TableData(schema.name, name, built.columns, state.nextOid(), stmt.temp);
|
|
22083
22146
|
table.constraints = built.constraints;
|
|
@@ -22429,7 +22492,13 @@ function executeAlterTable(env, stmt) {
|
|
|
22429
22492
|
const built = { columns: [], constraints: [], sequences: [] };
|
|
22430
22493
|
const taken = constraintNames(table);
|
|
22431
22494
|
const col = buildColumn(env, schema, table.name, action.column, built, taken);
|
|
22432
|
-
for (const seq of built.sequences)
|
|
22495
|
+
for (const seq of built.sequences) {
|
|
22496
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
22497
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
22498
|
+
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
22499
|
+
}
|
|
22500
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
22501
|
+
}
|
|
22433
22502
|
table.columns.push(col);
|
|
22434
22503
|
table.constraints.push(...built.constraints);
|
|
22435
22504
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -22749,12 +22818,32 @@ function executeAlterTable(env, stmt) {
|
|
|
22749
22818
|
"42P16"
|
|
22750
22819
|
);
|
|
22751
22820
|
}
|
|
22752
|
-
|
|
22753
|
-
|
|
22821
|
+
if (!col.notNull) {
|
|
22822
|
+
throw pgError(
|
|
22823
|
+
"object_not_in_prerequisite_state",
|
|
22824
|
+
`column "${action.column}" of relation "${table.name}" must be declared NOT NULL before identity can be added`,
|
|
22825
|
+
"55000"
|
|
22826
|
+
);
|
|
22827
|
+
}
|
|
22828
|
+
const target = identitySequenceTarget(
|
|
22829
|
+
env,
|
|
22830
|
+
schema,
|
|
22831
|
+
truncIdent(`${table.name}_${action.column}_seq`),
|
|
22832
|
+
action.options
|
|
22833
|
+
);
|
|
22834
|
+
if (target.schema.hasRelation(target.name)) {
|
|
22835
|
+
throw pgError("duplicate_table", `relation "${target.name}" already exists`, "42P07");
|
|
22836
|
+
}
|
|
22837
|
+
const seq = buildSequence(env, target.schema, target.name, action.options, false);
|
|
22838
|
+
if (!action.options.as) {
|
|
22839
|
+
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
22840
|
+
const limits = SEQ_LIMITS[seq.dataType];
|
|
22841
|
+
if (action.options.maxValue === void 0) seq.maxValue = seq.increment > 0n ? limits.max : -1n;
|
|
22842
|
+
if (action.options.minValue === void 0) seq.minValue = seq.increment > 0n ? 1n : limits.min;
|
|
22843
|
+
}
|
|
22754
22844
|
seq.ownedBy = { table: table.name, column: action.column };
|
|
22755
|
-
schema.sequences.set(
|
|
22756
|
-
col.identity = { always:
|
|
22757
|
-
col.notNull = true;
|
|
22845
|
+
target.schema.sequences.set(target.name, seq);
|
|
22846
|
+
col.identity = { always: action.always, sequence: `${target.schema.name}.${target.name}` };
|
|
22758
22847
|
break;
|
|
22759
22848
|
}
|
|
22760
22849
|
case "drop_identity": {
|
|
@@ -23623,11 +23712,11 @@ if (typeof disposeKey === "symbol") {
|
|
|
23623
23712
|
|
|
23624
23713
|
// src/wire/cluster.ts
|
|
23625
23714
|
var Cluster = class {
|
|
23626
|
-
constructor(db) {
|
|
23627
|
-
this.
|
|
23628
|
-
|
|
23715
|
+
constructor(db, defaultDatabaseName = "postgres") {
|
|
23716
|
+
this.defaultDatabaseName = defaultDatabaseName;
|
|
23717
|
+
this.installDatabase(defaultDatabaseName, db);
|
|
23629
23718
|
}
|
|
23630
|
-
|
|
23719
|
+
defaultDatabaseName;
|
|
23631
23720
|
turnHolder = null;
|
|
23632
23721
|
turnQueue = [];
|
|
23633
23722
|
locks = /* @__PURE__ */ new Map();
|
|
@@ -23636,6 +23725,87 @@ var Cluster = class {
|
|
|
23636
23725
|
sessions = /* @__PURE__ */ new Map();
|
|
23637
23726
|
current = null;
|
|
23638
23727
|
nextPid = 1e3;
|
|
23728
|
+
databases = /* @__PURE__ */ new Map();
|
|
23729
|
+
/** The initial database, retained for the server's backwards-compatible public handle. */
|
|
23730
|
+
get db() {
|
|
23731
|
+
return this.requireDatabase(this.defaultDatabaseName);
|
|
23732
|
+
}
|
|
23733
|
+
databaseNames() {
|
|
23734
|
+
return [...this.databases.keys()].sort();
|
|
23735
|
+
}
|
|
23736
|
+
snapshotAll() {
|
|
23737
|
+
return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
|
|
23738
|
+
}
|
|
23739
|
+
restoreAll(points) {
|
|
23740
|
+
if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
|
|
23741
|
+
if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
|
|
23742
|
+
for (const db of this.databases.values()) db.close();
|
|
23743
|
+
this.databases.clear();
|
|
23744
|
+
for (const [name, point] of points) this.installDatabase(name, point.open());
|
|
23745
|
+
}
|
|
23746
|
+
getDatabase(name) {
|
|
23747
|
+
return this.databases.get(name);
|
|
23748
|
+
}
|
|
23749
|
+
requireDatabase(name) {
|
|
23750
|
+
const db = this.databases.get(name);
|
|
23751
|
+
if (!db) throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
23752
|
+
return db;
|
|
23753
|
+
}
|
|
23754
|
+
createDatabase(name, template) {
|
|
23755
|
+
if (this.databases.has(name)) {
|
|
23756
|
+
throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
|
|
23757
|
+
}
|
|
23758
|
+
const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
|
|
23759
|
+
this.installDatabase(name, db);
|
|
23760
|
+
}
|
|
23761
|
+
dropDatabase(name, requester) {
|
|
23762
|
+
if (name === requester.databaseName || this.activeConnections(name) > 0) {
|
|
23763
|
+
throw new PostgresError(
|
|
23764
|
+
"object_not_in_prerequisite_state",
|
|
23765
|
+
`database "${name}" is being accessed by other users`,
|
|
23766
|
+
"55006"
|
|
23767
|
+
);
|
|
23768
|
+
}
|
|
23769
|
+
if (!this.databases.delete(name)) {
|
|
23770
|
+
throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
23771
|
+
}
|
|
23772
|
+
}
|
|
23773
|
+
renameDatabase(from, to, requester) {
|
|
23774
|
+
if (this.databases.has(to)) {
|
|
23775
|
+
throw new PostgresError("duplicate_object", `database "${to}" already exists`, "42P04");
|
|
23776
|
+
}
|
|
23777
|
+
const db = this.requireDatabase(from);
|
|
23778
|
+
if (from === requester.databaseName || this.activeConnections(from) > 0) {
|
|
23779
|
+
throw new PostgresError(
|
|
23780
|
+
"object_not_in_prerequisite_state",
|
|
23781
|
+
`database "${from}" is being accessed by other users`,
|
|
23782
|
+
"55006"
|
|
23783
|
+
);
|
|
23784
|
+
}
|
|
23785
|
+
this.databases.delete(from);
|
|
23786
|
+
this.installDatabase(to, db);
|
|
23787
|
+
}
|
|
23788
|
+
requireTemplate(name) {
|
|
23789
|
+
if (this.activeConnections(name) > 0) {
|
|
23790
|
+
throw new PostgresError(
|
|
23791
|
+
"object_not_in_prerequisite_state",
|
|
23792
|
+
`source database "${name}" is being accessed by other users`,
|
|
23793
|
+
"55006"
|
|
23794
|
+
);
|
|
23795
|
+
}
|
|
23796
|
+
return this.requireDatabase(name);
|
|
23797
|
+
}
|
|
23798
|
+
activeConnections(name) {
|
|
23799
|
+
let count = 0;
|
|
23800
|
+
for (const session of this.sessions.values()) if (session.databaseName === name) count++;
|
|
23801
|
+
return count;
|
|
23802
|
+
}
|
|
23803
|
+
installDatabase(name, db) {
|
|
23804
|
+
const context = { name, names: () => this.databaseNames() };
|
|
23805
|
+
setDatabaseCatalogContext(db.state, context);
|
|
23806
|
+
this.databases.set(name, db);
|
|
23807
|
+
registerSessionFunctions(this, db);
|
|
23808
|
+
}
|
|
23639
23809
|
newPid() {
|
|
23640
23810
|
return this.nextPid++;
|
|
23641
23811
|
}
|
|
@@ -23727,7 +23897,7 @@ var Cluster = class {
|
|
|
23727
23897
|
}
|
|
23728
23898
|
this.unlockAll(session);
|
|
23729
23899
|
if (this.turnHolder === session) {
|
|
23730
|
-
if (
|
|
23900
|
+
if (session.database.transactions.inTransaction) session.database.transactions.rollback();
|
|
23731
23901
|
this.releaseTurn(session);
|
|
23732
23902
|
}
|
|
23733
23903
|
}
|
|
@@ -23759,8 +23929,7 @@ var LockWait = class extends Error {
|
|
|
23759
23929
|
key;
|
|
23760
23930
|
};
|
|
23761
23931
|
var lockKey = (args) => args.map((a) => String(a)).join(":");
|
|
23762
|
-
var registerSessionFunctions = (cluster) => {
|
|
23763
|
-
const db = cluster.db;
|
|
23932
|
+
var registerSessionFunctions = (cluster, db) => {
|
|
23764
23933
|
const me = () => {
|
|
23765
23934
|
if (!cluster.current) throw new PostgresError("internal", "no session is executing", "XX000");
|
|
23766
23935
|
return cluster.current;
|
|
@@ -23927,6 +24096,17 @@ var backend = {
|
|
|
23927
24096
|
for (const oid of oids) w.i32(oid);
|
|
23928
24097
|
}),
|
|
23929
24098
|
notificationResponse: (pid, channel, payload) => message("A", (w) => w.i32(pid).cstring(channel).cstring(payload)),
|
|
24099
|
+
copyInResponse: (columns) => message("G", (w) => {
|
|
24100
|
+
w.u8(0).i16(columns);
|
|
24101
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24102
|
+
}),
|
|
24103
|
+
copyOutResponse: (columns) => message("H", (w) => {
|
|
24104
|
+
w.u8(0).i16(columns);
|
|
24105
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24106
|
+
}),
|
|
24107
|
+
copyData: (data) => message("d", (w) => w.bytes(data)),
|
|
24108
|
+
copyDone: () => message("c", () => {
|
|
24109
|
+
}),
|
|
23930
24110
|
/** The one-byte answers to SSLRequest / GSSENCRequest: no encryption here. */
|
|
23931
24111
|
no: () => new Uint8Array([78])
|
|
23932
24112
|
};
|
|
@@ -24168,6 +24348,23 @@ var LISTEN = /^listen\s+("?)([A-Za-z_][\w$]*)\1\s*$/i;
|
|
|
24168
24348
|
var UNLISTEN = /^unlisten\s+(\*|"?[A-Za-z_][\w$]*"?)\s*$/i;
|
|
24169
24349
|
var NOTIFY = /^notify\s+("?)([A-Za-z_][\w$]*)\1\s*(?:,\s*'((?:[^']|'')*)')?\s*$/i;
|
|
24170
24350
|
var RETURNS_ROWS = /^(select|with|values|show|table|explain)\b/i;
|
|
24351
|
+
var CLUSTER_DDL = /^(create|drop|alter)\s+database\b/i;
|
|
24352
|
+
var IDENT = '(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))';
|
|
24353
|
+
var CREATE_DATABASE = new RegExp(
|
|
24354
|
+
`^create\\s+database\\s+${IDENT}(?:\\s+with)?(?:\\s+template\\s*(?:=\\s*)?${IDENT})?\\s*$`,
|
|
24355
|
+
"i"
|
|
24356
|
+
);
|
|
24357
|
+
var DROP_DATABASE = new RegExp(
|
|
24358
|
+
`^drop\\s+database\\s+(if\\s+exists\\s+)?${IDENT}(?:\\s+with\\s*\\(\\s*force\\s*\\))?\\s*$`,
|
|
24359
|
+
"i"
|
|
24360
|
+
);
|
|
24361
|
+
var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to\\s+${IDENT}\\s*$`, "i");
|
|
24362
|
+
var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
|
|
24363
|
+
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;
|
|
24364
|
+
var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
|
|
24365
|
+
var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
|
|
24366
|
+
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;
|
|
24367
|
+
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;
|
|
24171
24368
|
var TYPLEN = {
|
|
24172
24369
|
bool: 1,
|
|
24173
24370
|
int2: 2,
|
|
@@ -24260,6 +24457,7 @@ var binaryValue = (type, text) => {
|
|
|
24260
24457
|
}
|
|
24261
24458
|
};
|
|
24262
24459
|
var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
|
|
24460
|
+
var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "comment", "no_op"].includes(stmt.type));
|
|
24263
24461
|
var decodeParam = (oid, format, bytes) => {
|
|
24264
24462
|
if (bytes === null) return null;
|
|
24265
24463
|
if (format === 0) return utf83.decode(bytes);
|
|
@@ -24308,6 +24506,8 @@ var Connection = class {
|
|
|
24308
24506
|
this.socket = socket;
|
|
24309
24507
|
this.cluster = cluster;
|
|
24310
24508
|
this.options = options;
|
|
24509
|
+
this.database = cluster.db;
|
|
24510
|
+
this.databaseName = cluster.defaultDatabaseName;
|
|
24311
24511
|
this.pid = cluster.newPid();
|
|
24312
24512
|
this.secret = Math.floor(Math.random() * 2147483647);
|
|
24313
24513
|
cluster.sessions.set(this.pid, this);
|
|
@@ -24323,6 +24523,8 @@ var Connection = class {
|
|
|
24323
24523
|
waitingForTurn = null;
|
|
24324
24524
|
waitingForLock = null;
|
|
24325
24525
|
pendingNotifies = [];
|
|
24526
|
+
database;
|
|
24527
|
+
databaseName;
|
|
24326
24528
|
frames = new FrameParser();
|
|
24327
24529
|
inbox = [];
|
|
24328
24530
|
pumping = false;
|
|
@@ -24339,6 +24541,10 @@ var Connection = class {
|
|
|
24339
24541
|
prepared = /* @__PURE__ */ new Map();
|
|
24340
24542
|
portals = /* @__PURE__ */ new Map();
|
|
24341
24543
|
notifications = [];
|
|
24544
|
+
copyIn = null;
|
|
24545
|
+
copyDrain = false;
|
|
24546
|
+
/** Successful writes in this session's READ COMMITTED transaction workspace. */
|
|
24547
|
+
transaction = null;
|
|
24342
24548
|
closed = false;
|
|
24343
24549
|
receive(chunk) {
|
|
24344
24550
|
let messages;
|
|
@@ -24416,6 +24622,20 @@ var Connection = class {
|
|
|
24416
24622
|
this.fatal("08P01", "expected startup or password message");
|
|
24417
24623
|
return;
|
|
24418
24624
|
}
|
|
24625
|
+
if (this.copyIn && message2.kind === "typed") {
|
|
24626
|
+
await this.handleCopyIn(message2.type, message2.body);
|
|
24627
|
+
return;
|
|
24628
|
+
}
|
|
24629
|
+
if (this.copyDrain && message2.kind === "typed") {
|
|
24630
|
+
if (message2.type === "c" || message2.type === "f") {
|
|
24631
|
+
this.copyDrain = false;
|
|
24632
|
+
this.readyForQuery();
|
|
24633
|
+
} else if (message2.type === "X") {
|
|
24634
|
+
this.socket.end();
|
|
24635
|
+
this.dispose();
|
|
24636
|
+
}
|
|
24637
|
+
return;
|
|
24638
|
+
}
|
|
24419
24639
|
if (this.skipUntilSync && message2.type !== "S" && message2.type !== "X") return;
|
|
24420
24640
|
try {
|
|
24421
24641
|
switch (message2.type) {
|
|
@@ -24466,10 +24686,13 @@ var Connection = class {
|
|
|
24466
24686
|
}
|
|
24467
24687
|
this.user = parameters.user ?? "postgres";
|
|
24468
24688
|
const requested = parameters.database && parameters.database.length > 0 ? parameters.database : this.user;
|
|
24469
|
-
|
|
24689
|
+
const database = this.cluster.getDatabase(requested);
|
|
24690
|
+
if (!database || this.options.database !== void 0 && requested !== this.options.database) {
|
|
24470
24691
|
this.fatal("3D000", `database "${requested}" does not exist`);
|
|
24471
24692
|
return;
|
|
24472
24693
|
}
|
|
24694
|
+
this.database = database;
|
|
24695
|
+
this.databaseName = requested;
|
|
24473
24696
|
if (this.options.password !== void 0) {
|
|
24474
24697
|
this.scram = new ScramServer(this.options.password);
|
|
24475
24698
|
this.write(backend.authenticationSASL(["SCRAM-SHA-256"]));
|
|
@@ -24533,7 +24756,7 @@ var Connection = class {
|
|
|
24533
24756
|
}
|
|
24534
24757
|
// --- execution ----------------------------------------------------------------------------
|
|
24535
24758
|
get inTransaction() {
|
|
24536
|
-
return this.
|
|
24759
|
+
return this.transaction !== null;
|
|
24537
24760
|
}
|
|
24538
24761
|
status() {
|
|
24539
24762
|
if (this.aborted) return "E";
|
|
@@ -24556,6 +24779,7 @@ var Connection = class {
|
|
|
24556
24779
|
return outcome;
|
|
24557
24780
|
} catch (error) {
|
|
24558
24781
|
if (this.inTransaction) this.aborted = true;
|
|
24782
|
+
if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
24559
24783
|
const status = error instanceof PostgresError ? error.sqlState : "XX000";
|
|
24560
24784
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
24561
24785
|
throw error;
|
|
@@ -24580,7 +24804,7 @@ var Connection = class {
|
|
|
24580
24804
|
if (!this.inTransaction) this.flushNotifies();
|
|
24581
24805
|
return command("NOTIFY");
|
|
24582
24806
|
}
|
|
24583
|
-
|
|
24807
|
+
if (CLUSTER_DDL.test(text)) return this.executeClusterDdl(text);
|
|
24584
24808
|
for (; ; ) {
|
|
24585
24809
|
if (this.cancelled) throw pgError2("internal", "canceling statement due to user request", "57014");
|
|
24586
24810
|
await this.cluster.acquireTurn(this);
|
|
@@ -24590,7 +24814,7 @@ var Connection = class {
|
|
|
24590
24814
|
faults.delayStatementMs = void 0;
|
|
24591
24815
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
24592
24816
|
}
|
|
24593
|
-
if (faults.dropConnection &&
|
|
24817
|
+
if (faults.dropConnection && this.inTransaction) {
|
|
24594
24818
|
faults.dropConnection = void 0;
|
|
24595
24819
|
this.destroy();
|
|
24596
24820
|
throw new ConnectionDropped();
|
|
@@ -24602,7 +24826,7 @@ var Connection = class {
|
|
|
24602
24826
|
} catch (error) {
|
|
24603
24827
|
if (error instanceof LockWait) {
|
|
24604
24828
|
const key = error.key;
|
|
24605
|
-
|
|
24829
|
+
this.cluster.releaseTurn(this);
|
|
24606
24830
|
await this.waitForLock(key);
|
|
24607
24831
|
continue;
|
|
24608
24832
|
}
|
|
@@ -24632,57 +24856,196 @@ var Connection = class {
|
|
|
24632
24856
|
}
|
|
24633
24857
|
/** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
|
|
24634
24858
|
runStatement(text, params, control) {
|
|
24635
|
-
const db = this.cluster.db;
|
|
24636
|
-
const tx = db.transactions;
|
|
24637
24859
|
const faults = this.options.faults;
|
|
24638
24860
|
if (control && COMMIT.test(text)) {
|
|
24639
24861
|
if (this.aborted) {
|
|
24640
|
-
|
|
24862
|
+
this.transaction = null;
|
|
24641
24863
|
this.aborted = false;
|
|
24642
24864
|
return command("ROLLBACK");
|
|
24643
24865
|
}
|
|
24644
24866
|
if (faults.failCommit !== void 0) {
|
|
24645
24867
|
const code = faults.failCommit;
|
|
24646
24868
|
faults.failCommit = void 0;
|
|
24647
|
-
|
|
24869
|
+
this.transaction = null;
|
|
24648
24870
|
throw pgError2("internal", "could not serialize access due to concurrent update", code);
|
|
24649
24871
|
}
|
|
24650
24872
|
}
|
|
24651
24873
|
if (control && this.aborted && /^(rollback|abort)\b/i.test(text) && !/\bto\b/i.test(text)) {
|
|
24652
|
-
|
|
24874
|
+
this.transaction = null;
|
|
24653
24875
|
this.aborted = false;
|
|
24654
24876
|
return command("ROLLBACK");
|
|
24655
24877
|
}
|
|
24656
|
-
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
return outcome;
|
|
24668
|
-
} catch (error) {
|
|
24669
|
-
tx.rollbackToSavepoint(name);
|
|
24670
|
-
tx.releaseSavepoint(name);
|
|
24671
|
-
throw error;
|
|
24878
|
+
if (control) return this.transactionControl(text);
|
|
24879
|
+
const workspace = this.transaction === null ? null : this.openWorkspace();
|
|
24880
|
+
const db = workspace ?? this.database;
|
|
24881
|
+
try {
|
|
24882
|
+
const run = () => {
|
|
24883
|
+
const result = this.cluster.as(this, () => this.runQuery(db, text, params));
|
|
24884
|
+
return { empty: false, result, tag: commandTag(result) };
|
|
24885
|
+
};
|
|
24886
|
+
const outcome = db.transaction(run);
|
|
24887
|
+
if (this.transaction && mutatesDatabase(text)) {
|
|
24888
|
+
this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
|
|
24672
24889
|
}
|
|
24890
|
+
return outcome;
|
|
24891
|
+
} finally {
|
|
24892
|
+
workspace?.close();
|
|
24673
24893
|
}
|
|
24674
|
-
return db.transaction(run);
|
|
24675
24894
|
}
|
|
24676
|
-
/** Release the turn
|
|
24895
|
+
/** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
|
|
24677
24896
|
afterStatement() {
|
|
24678
|
-
if (this.cluster.db.transactions.inTransaction) return;
|
|
24679
|
-
this.cluster.unlockAll(this, true);
|
|
24680
24897
|
this.cluster.releaseTurn(this);
|
|
24681
|
-
this.
|
|
24898
|
+
if (!this.inTransaction) {
|
|
24899
|
+
this.cluster.unlockAll(this, true);
|
|
24900
|
+
this.flushNotifies();
|
|
24901
|
+
}
|
|
24902
|
+
}
|
|
24903
|
+
transactionControl(text) {
|
|
24904
|
+
const stmt = parse(text)[0];
|
|
24905
|
+
if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
|
|
24906
|
+
switch (stmt.action) {
|
|
24907
|
+
case "begin":
|
|
24908
|
+
this.transaction ??= { statements: [], savepoints: [] };
|
|
24909
|
+
return command("BEGIN");
|
|
24910
|
+
case "commit": {
|
|
24911
|
+
const transaction = this.transaction;
|
|
24912
|
+
this.transaction = null;
|
|
24913
|
+
if (transaction) {
|
|
24914
|
+
this.database.transaction(() => {
|
|
24915
|
+
for (const logged of transaction.statements) this.replay(this.database, logged);
|
|
24916
|
+
});
|
|
24917
|
+
}
|
|
24918
|
+
return command("COMMIT");
|
|
24919
|
+
}
|
|
24920
|
+
case "rollback":
|
|
24921
|
+
this.transaction = null;
|
|
24922
|
+
return command("ROLLBACK");
|
|
24923
|
+
case "savepoint": {
|
|
24924
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
24925
|
+
this.transaction.savepoints.push({
|
|
24926
|
+
name: stmt.savepointName,
|
|
24927
|
+
statementCount: this.transaction.statements.length
|
|
24928
|
+
});
|
|
24929
|
+
return command("SAVEPOINT");
|
|
24930
|
+
}
|
|
24931
|
+
case "release": {
|
|
24932
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
24933
|
+
this.transaction?.savepoints.splice(index);
|
|
24934
|
+
return command("RELEASE");
|
|
24935
|
+
}
|
|
24936
|
+
case "rollback_to": {
|
|
24937
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
24938
|
+
const savepoint = this.transaction?.savepoints[index];
|
|
24939
|
+
if (!this.transaction || !savepoint) throw pgError2("internal", "savepoint does not exist", "3B001");
|
|
24940
|
+
this.transaction.statements.splice(savepoint.statementCount);
|
|
24941
|
+
this.transaction.savepoints.splice(index + 1);
|
|
24942
|
+
this.aborted = false;
|
|
24943
|
+
return command("ROLLBACK");
|
|
24944
|
+
}
|
|
24945
|
+
}
|
|
24946
|
+
}
|
|
24947
|
+
findSavepoint(name) {
|
|
24948
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
24949
|
+
for (let i = this.transaction.savepoints.length - 1; i >= 0; i--) {
|
|
24950
|
+
if (this.transaction.savepoints[i]?.name === name) return i;
|
|
24951
|
+
}
|
|
24952
|
+
throw pgError2("internal", `savepoint "${name}" does not exist`, "3B001");
|
|
24953
|
+
}
|
|
24954
|
+
/** Rebase this transaction's successful writes onto the latest committed state. */
|
|
24955
|
+
openWorkspace() {
|
|
24956
|
+
const workspace = this.database.branch();
|
|
24957
|
+
setDatabaseCatalogContext(workspace.state, {
|
|
24958
|
+
name: this.databaseName,
|
|
24959
|
+
names: () => this.cluster.databaseNames()
|
|
24960
|
+
});
|
|
24961
|
+
for (const logged of this.transaction?.statements ?? []) {
|
|
24962
|
+
workspace.transaction(() => this.replay(workspace, logged));
|
|
24963
|
+
}
|
|
24964
|
+
return workspace;
|
|
24965
|
+
}
|
|
24966
|
+
replay(db, logged) {
|
|
24967
|
+
this.cluster.as(this, () => {
|
|
24968
|
+
if (logged.kind === "sql") db.prepare(logged.sql).textResult(...logged.params);
|
|
24969
|
+
else {
|
|
24970
|
+
const env = { ctx: new EngineCtx(db.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
|
|
24971
|
+
executeCopyFromData(env, logged.stmt, logged.data);
|
|
24972
|
+
}
|
|
24973
|
+
});
|
|
24974
|
+
}
|
|
24975
|
+
runQuery(db, text, params) {
|
|
24976
|
+
const lock = ROW_LOCK.exec(text);
|
|
24977
|
+
if (!lock) {
|
|
24978
|
+
const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
|
|
24979
|
+
if (mutation) {
|
|
24980
|
+
const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
|
|
24981
|
+
this.lockRows(db, lockQuery, params, "wait");
|
|
24982
|
+
}
|
|
24983
|
+
return db.prepare(text).textResult(...params);
|
|
24984
|
+
}
|
|
24985
|
+
let query = text.slice(0, lock.index).trimEnd();
|
|
24986
|
+
const limitMatch = LIMIT.exec(query);
|
|
24987
|
+
const limit = limitMatch ? Number(limitMatch[1]) : Number.POSITIVE_INFINITY;
|
|
24988
|
+
const offset = limitMatch ? Number(limitMatch[2] ?? 0) : 0;
|
|
24989
|
+
if (limitMatch) query = query.slice(0, limitMatch.index).trimEnd();
|
|
24990
|
+
return this.lockRows(db, query, params, lock[1]?.toLowerCase().replace(/\s+/g, "_") ?? "wait", limit, offset);
|
|
24991
|
+
}
|
|
24992
|
+
lockRows(db, query, params, policy, limit = Number.POSITIVE_INFINITY, offset = 0) {
|
|
24993
|
+
const result = db.prepare(query).textResult(...params);
|
|
24994
|
+
const rows = [];
|
|
24995
|
+
for (const row of result.rows.slice(offset)) {
|
|
24996
|
+
const key = this.rowLockKey(db, query, result.columns, row);
|
|
24997
|
+
if (this.cluster.tryLock(this, key, true)) {
|
|
24998
|
+
rows.push(row);
|
|
24999
|
+
if (rows.length >= limit) break;
|
|
25000
|
+
continue;
|
|
25001
|
+
}
|
|
25002
|
+
if (policy === "skip_locked") continue;
|
|
25003
|
+
if (policy === "nowait") {
|
|
25004
|
+
throw pgError2("internal", "could not obtain lock on row in relation", "55P03");
|
|
25005
|
+
}
|
|
25006
|
+
throw new LockWait(key);
|
|
25007
|
+
}
|
|
25008
|
+
return { ...result, rows, rowCount: rows.length };
|
|
25009
|
+
}
|
|
25010
|
+
rowLockKey(db, query, columns, row) {
|
|
25011
|
+
const rawName = FROM_TABLE.exec(query)?.[1] ?? "query";
|
|
25012
|
+
const parts = rawName.split(".").map((part) => part.startsWith('"') ? part.slice(1, -1).replaceAll('""', '"') : part.toLowerCase());
|
|
25013
|
+
const table = db.state.findTable(parts);
|
|
25014
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
25015
|
+
const indexes = primary?.columns.map((column) => columns.indexOf(column)) ?? [];
|
|
25016
|
+
const identity = indexes.length > 0 && indexes.every((index) => index >= 0) ? indexes.map((i) => row[i]) : row;
|
|
25017
|
+
return `row:${this.databaseName}:${parts.join(".")}:${JSON.stringify(identity)}`;
|
|
24682
25018
|
}
|
|
24683
25019
|
flushNotifies() {
|
|
24684
25020
|
for (const { channel, payload } of this.pendingNotifies.splice(0)) this.cluster.notify(this, channel, payload);
|
|
24685
25021
|
}
|
|
25022
|
+
executeClusterDdl(text) {
|
|
25023
|
+
if (this.inTransaction) {
|
|
25024
|
+
throw pgError2("internal", "CREATE/DROP/ALTER DATABASE cannot run inside a transaction block", "25001");
|
|
25025
|
+
}
|
|
25026
|
+
const create = CREATE_DATABASE.exec(text);
|
|
25027
|
+
if (create) {
|
|
25028
|
+
const name = identifier(create[1], create[2]);
|
|
25029
|
+
const template = create[3] === void 0 && create[4] === void 0 ? void 0 : identifier(create[3], create[4]);
|
|
25030
|
+
this.cluster.createDatabase(name, template);
|
|
25031
|
+
return command("CREATE DATABASE");
|
|
25032
|
+
}
|
|
25033
|
+
const drop = DROP_DATABASE.exec(text);
|
|
25034
|
+
if (drop) {
|
|
25035
|
+
const name = identifier(drop[2], drop[3]);
|
|
25036
|
+
if (drop[1] && !this.cluster.getDatabase(name)) return command("DROP DATABASE");
|
|
25037
|
+
this.cluster.dropDatabase(name, this);
|
|
25038
|
+
return command("DROP DATABASE");
|
|
25039
|
+
}
|
|
25040
|
+
const rename = RENAME_DATABASE.exec(text);
|
|
25041
|
+
if (rename) {
|
|
25042
|
+
const from = identifier(rename[1], rename[2]);
|
|
25043
|
+
const to = identifier(rename[3], rename[4]);
|
|
25044
|
+
this.cluster.renameDatabase(from, to, this);
|
|
25045
|
+
return command("ALTER DATABASE");
|
|
25046
|
+
}
|
|
25047
|
+
throw pgError2("syntax", "unsupported CREATE/DROP/ALTER DATABASE option", "0A000");
|
|
25048
|
+
}
|
|
24686
25049
|
listen(channel) {
|
|
24687
25050
|
const set = this.cluster.listeners.get(channel) ?? /* @__PURE__ */ new Set();
|
|
24688
25051
|
set.add(this);
|
|
@@ -24703,6 +25066,24 @@ var Connection = class {
|
|
|
24703
25066
|
this.readyForQuery();
|
|
24704
25067
|
return;
|
|
24705
25068
|
}
|
|
25069
|
+
if (statements.length === 1) {
|
|
25070
|
+
const copy = this.copyStatement(statements[0]);
|
|
25071
|
+
if (copy?.direction === "from") {
|
|
25072
|
+
await this.beginCopyIn(copy);
|
|
25073
|
+
return;
|
|
25074
|
+
}
|
|
25075
|
+
if (copy?.direction === "to") {
|
|
25076
|
+
try {
|
|
25077
|
+
const outcome = await this.execute(statements[0]);
|
|
25078
|
+
this.sendCopyOut(outcome, this.copyColumnCount(copy));
|
|
25079
|
+
} catch (error) {
|
|
25080
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
25081
|
+
if (this.inTransaction) this.aborted = true;
|
|
25082
|
+
}
|
|
25083
|
+
this.readyForQuery();
|
|
25084
|
+
return;
|
|
25085
|
+
}
|
|
25086
|
+
}
|
|
24706
25087
|
const implicit = statements.length > 1 && !this.inTransaction && !statements.some((s) => TX_CONTROL.test(s));
|
|
24707
25088
|
let failed = false;
|
|
24708
25089
|
try {
|
|
@@ -24723,6 +25104,138 @@ var Connection = class {
|
|
|
24723
25104
|
if (failed && this.inTransaction) this.aborted = true;
|
|
24724
25105
|
this.readyForQuery();
|
|
24725
25106
|
}
|
|
25107
|
+
copyStatement(sql) {
|
|
25108
|
+
try {
|
|
25109
|
+
const statements = parse(sql);
|
|
25110
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25111
|
+
return stmt?.type === "copy" ? stmt : null;
|
|
25112
|
+
} catch {
|
|
25113
|
+
return null;
|
|
25114
|
+
}
|
|
25115
|
+
}
|
|
25116
|
+
copyColumnCount(stmt) {
|
|
25117
|
+
if (stmt.columns) return stmt.columns.length;
|
|
25118
|
+
if (stmt.table) return this.database.state.findTable(stmt.table)?.columns.length ?? 0;
|
|
25119
|
+
return 0;
|
|
25120
|
+
}
|
|
25121
|
+
async handleCopyIn(type, body) {
|
|
25122
|
+
const copy = this.copyIn;
|
|
25123
|
+
if (type === "d") {
|
|
25124
|
+
try {
|
|
25125
|
+
copy.buffer += copy.decoder.decode(body.rest(), { stream: true });
|
|
25126
|
+
this.flushCopyRecords(copy, false);
|
|
25127
|
+
} catch (error) {
|
|
25128
|
+
this.abortCopyIn(copy, error, true);
|
|
25129
|
+
}
|
|
25130
|
+
return;
|
|
25131
|
+
}
|
|
25132
|
+
if (type === "f") {
|
|
25133
|
+
const detail = body.cstring();
|
|
25134
|
+
this.abortCopyIn(copy, pgError2("internal", `COPY from stdin failed: ${detail}`, "57014"), false);
|
|
25135
|
+
return;
|
|
25136
|
+
}
|
|
25137
|
+
if (type === "c") {
|
|
25138
|
+
this.finishCopyIn(copy);
|
|
25139
|
+
return;
|
|
25140
|
+
}
|
|
25141
|
+
if (type === "H") return;
|
|
25142
|
+
if (type === "X") {
|
|
25143
|
+
this.socket.end();
|
|
25144
|
+
this.dispose();
|
|
25145
|
+
return;
|
|
25146
|
+
}
|
|
25147
|
+
this.fatal("08P01", `unexpected frontend message type ${JSON.stringify(type)} during COPY`);
|
|
25148
|
+
}
|
|
25149
|
+
async beginCopyIn(stmt) {
|
|
25150
|
+
await this.cluster.acquireTurn(this);
|
|
25151
|
+
const workspace = this.transaction ? this.openWorkspace() : null;
|
|
25152
|
+
const database = workspace ?? this.database;
|
|
25153
|
+
database.transactions.begin();
|
|
25154
|
+
this.copyIn = {
|
|
25155
|
+
stmt,
|
|
25156
|
+
database,
|
|
25157
|
+
workspace: workspace !== null,
|
|
25158
|
+
buffer: "",
|
|
25159
|
+
decoder: new TextDecoder(),
|
|
25160
|
+
rowCount: 0,
|
|
25161
|
+
headerPending: stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true",
|
|
25162
|
+
loggedData: ""
|
|
25163
|
+
};
|
|
25164
|
+
this.write(backend.copyInResponse(this.copyColumnCount(stmt)));
|
|
25165
|
+
}
|
|
25166
|
+
finishCopyIn(copy) {
|
|
25167
|
+
const tx = copy.database.transactions;
|
|
25168
|
+
try {
|
|
25169
|
+
copy.buffer += copy.decoder.decode();
|
|
25170
|
+
this.flushCopyRecords(copy, true);
|
|
25171
|
+
tx.commit();
|
|
25172
|
+
if (copy.workspace) {
|
|
25173
|
+
this.transaction?.statements.push({ kind: "copy", stmt: copy.stmt, data: copy.loggedData });
|
|
25174
|
+
copy.database.close();
|
|
25175
|
+
}
|
|
25176
|
+
this.copyIn = null;
|
|
25177
|
+
this.write(backend.commandComplete(`COPY ${copy.rowCount}`));
|
|
25178
|
+
this.cluster.releaseTurn(this);
|
|
25179
|
+
this.readyForQuery();
|
|
25180
|
+
} catch (error) {
|
|
25181
|
+
this.abortCopyIn(copy, error, false);
|
|
25182
|
+
}
|
|
25183
|
+
}
|
|
25184
|
+
flushCopyRecords(copy, final) {
|
|
25185
|
+
const format = String(copy.stmt.options.format ?? "text").toLowerCase();
|
|
25186
|
+
const quote = String(copy.stmt.options.quote ?? '"');
|
|
25187
|
+
const boundaries = [];
|
|
25188
|
+
let quoted = false;
|
|
25189
|
+
for (let i = 0; i < copy.buffer.length; i++) {
|
|
25190
|
+
const char = copy.buffer[i];
|
|
25191
|
+
if (format === "csv" && char === quote) {
|
|
25192
|
+
if (quoted && copy.buffer[i + 1] === quote) i++;
|
|
25193
|
+
else quoted = !quoted;
|
|
25194
|
+
} else if (char === "\n" && !quoted) {
|
|
25195
|
+
boundaries.push(i + 1);
|
|
25196
|
+
}
|
|
25197
|
+
}
|
|
25198
|
+
if (final && quoted) {
|
|
25199
|
+
throw new PostgresError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
25200
|
+
}
|
|
25201
|
+
const end = boundaries.at(-1) ?? 0;
|
|
25202
|
+
const data = final ? copy.buffer : end > 0 ? copy.buffer.slice(0, end) : "";
|
|
25203
|
+
if (data === "") return;
|
|
25204
|
+
copy.buffer = final ? "" : copy.buffer.slice(end);
|
|
25205
|
+
const stmt = copy.headerPending ? copy.stmt : { ...copy.stmt, options: { ...copy.stmt.options, header: false } };
|
|
25206
|
+
const env = {
|
|
25207
|
+
ctx: new EngineCtx(copy.database.state),
|
|
25208
|
+
params: null,
|
|
25209
|
+
ctes: /* @__PURE__ */ new Map(),
|
|
25210
|
+
outer: null
|
|
25211
|
+
};
|
|
25212
|
+
const result = this.cluster.as(this, () => executeCopyFromData(env, stmt, data));
|
|
25213
|
+
copy.rowCount += result.rowCount;
|
|
25214
|
+
copy.loggedData += data;
|
|
25215
|
+
copy.headerPending = false;
|
|
25216
|
+
}
|
|
25217
|
+
abortCopyIn(copy, error, drain) {
|
|
25218
|
+
const tx = copy.database.transactions;
|
|
25219
|
+
if (tx.inTransaction) tx.rollback();
|
|
25220
|
+
if (copy.workspace) copy.database.close();
|
|
25221
|
+
if (this.inTransaction) this.aborted = true;
|
|
25222
|
+
this.cluster.releaseTurn(this);
|
|
25223
|
+
this.copyIn = null;
|
|
25224
|
+
this.copyDrain = drain;
|
|
25225
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
25226
|
+
if (!drain) this.readyForQuery();
|
|
25227
|
+
}
|
|
25228
|
+
sendCopyOut(outcome, columns) {
|
|
25229
|
+
if (outcome.empty) throw pgError2("internal", "COPY TO produced no result", "XX000");
|
|
25230
|
+
this.write(backend.copyOutResponse(columns));
|
|
25231
|
+
for (const row of outcome.result.rows) {
|
|
25232
|
+
const line = row[0] ?? "";
|
|
25233
|
+
this.write(backend.copyData(utf83.encode(`${line}
|
|
25234
|
+
`)));
|
|
25235
|
+
}
|
|
25236
|
+
this.write(backend.copyDone());
|
|
25237
|
+
this.write(backend.commandComplete(outcome.tag));
|
|
25238
|
+
}
|
|
24726
25239
|
sendOutcome(outcome, resultFormats, opts = { rowDescription: false }) {
|
|
24727
25240
|
const from = opts.from ?? 0;
|
|
24728
25241
|
const limit = opts.limit ?? 0;
|
|
@@ -24777,7 +25290,7 @@ var Connection = class {
|
|
|
24777
25290
|
for (let i = 0; i < count; i++) paramOids.push(body.i32());
|
|
24778
25291
|
const text = sql.trim();
|
|
24779
25292
|
if (text !== "" && !LISTEN.test(text) && !UNLISTEN.test(text) && !NOTIFY.test(text) && !TX_CONTROL.test(text)) {
|
|
24780
|
-
this.
|
|
25293
|
+
if (!CLUSTER_DDL.test(text)) this.database.prepare(text);
|
|
24781
25294
|
}
|
|
24782
25295
|
if (name !== "" && this.prepared.has(name)) {
|
|
24783
25296
|
throw pgError2("internal", `prepared statement "${name}" already exists`, "42P05");
|
|
@@ -24836,7 +25349,7 @@ var Connection = class {
|
|
|
24836
25349
|
*/
|
|
24837
25350
|
async shapeOf(prepared) {
|
|
24838
25351
|
if (!RETURNS_ROWS.test(prepared.sql)) return null;
|
|
24839
|
-
const db = this.
|
|
25352
|
+
const db = this.database;
|
|
24840
25353
|
await this.cluster.acquireTurn(this);
|
|
24841
25354
|
try {
|
|
24842
25355
|
const params = prepared.paramOids.map(() => null);
|
|
@@ -24953,7 +25466,8 @@ var isSnapshot = (value) => typeof value === "object" && value !== null && "open
|
|
|
24953
25466
|
var serve = (input = {}) => {
|
|
24954
25467
|
const options = normalize(input);
|
|
24955
25468
|
const database = isSnapshot(options.database) ? options.database.open() : options.database ?? new Database();
|
|
24956
|
-
const
|
|
25469
|
+
const initialName = options.databaseName ?? "postgres";
|
|
25470
|
+
const cluster = new Cluster(database, initialName);
|
|
24957
25471
|
const faults = {};
|
|
24958
25472
|
const connections = /* @__PURE__ */ new Set();
|
|
24959
25473
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -24965,7 +25479,7 @@ var serve = (input = {}) => {
|
|
|
24965
25479
|
serverVersion: options.serverVersion ?? "18.3",
|
|
24966
25480
|
parameters: options.parameters ?? {},
|
|
24967
25481
|
faults,
|
|
24968
|
-
...options.databaseName !== void 0 ? { database: options.databaseName } : {},
|
|
25482
|
+
...options.databaseName !== void 0 && !options.allowDatabaseSelection ? { database: options.databaseName } : {},
|
|
24969
25483
|
...options.onLog ? { onLog: options.onLog } : {}
|
|
24970
25484
|
});
|
|
24971
25485
|
connections.add(connection);
|
|
@@ -24977,13 +25491,21 @@ var serve = (input = {}) => {
|
|
|
24977
25491
|
server.removeListener("error", reject);
|
|
24978
25492
|
const address = server.address();
|
|
24979
25493
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
24980
|
-
const name =
|
|
25494
|
+
const name = initialName;
|
|
24981
25495
|
const auth = options.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options.password)}@` : `${encodeURIComponent(user)}@`;
|
|
24982
25496
|
resolve({
|
|
24983
25497
|
port,
|
|
24984
25498
|
host,
|
|
24985
25499
|
connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
|
|
24986
|
-
database
|
|
25500
|
+
get database() {
|
|
25501
|
+
return cluster.db;
|
|
25502
|
+
},
|
|
25503
|
+
databaseNames() {
|
|
25504
|
+
return cluster.databaseNames();
|
|
25505
|
+
},
|
|
25506
|
+
getDatabase(databaseName) {
|
|
25507
|
+
return cluster.requireDatabase(databaseName);
|
|
25508
|
+
},
|
|
24987
25509
|
server,
|
|
24988
25510
|
get connections() {
|
|
24989
25511
|
return connections.size;
|
|
@@ -24991,8 +25513,16 @@ var serve = (input = {}) => {
|
|
|
24991
25513
|
fault(next) {
|
|
24992
25514
|
Object.assign(faults, next);
|
|
24993
25515
|
},
|
|
24994
|
-
|
|
24995
|
-
|
|
25516
|
+
clearFaults() {
|
|
25517
|
+
for (const key of Object.keys(faults)) delete faults[key];
|
|
25518
|
+
},
|
|
25519
|
+
faultState() {
|
|
25520
|
+
return structuredClone(faults);
|
|
25521
|
+
},
|
|
25522
|
+
snapshotAll: () => cluster.snapshotAll(),
|
|
25523
|
+
restoreAll: (points) => cluster.restoreAll(points),
|
|
25524
|
+
snapshot(databaseName = initialName) {
|
|
25525
|
+
return cluster.requireDatabase(databaseName).snapshot();
|
|
24996
25526
|
},
|
|
24997
25527
|
close() {
|
|
24998
25528
|
for (const connection of connections) connection.destroy();
|