@crvouga/mockingbird-service-postgres 3.0.0 → 3.0.2
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/CHANGELOG.md +12 -0
- package/dist/admin.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.js +139 -14
- package/dist/server.js.map +2 -2
- package/dist/unstable.js.map +1 -1
- package/dist/wire/cli.js +139 -14
- package/dist/wire/cli.js.map +2 -2
- package/dist/wire/connection.d.ts +12 -3
- package/dist/wire/index.js +139 -14
- package/dist/wire/index.js.map +2 -2
- package/package.json +1 -1
package/dist/wire/cli.js
CHANGED
|
@@ -1098,6 +1098,12 @@ function pgError(category, message2, sqlState) {
|
|
|
1098
1098
|
function unsupported(what) {
|
|
1099
1099
|
return new PostgresError("unsupported", `${what} is not supported`);
|
|
1100
1100
|
}
|
|
1101
|
+
function isPostgresError(err) {
|
|
1102
|
+
if (err instanceof PostgresError) return true;
|
|
1103
|
+
if (!(err instanceof Error) || err.name !== "PostgresError") return false;
|
|
1104
|
+
const sqlState = err.sqlState;
|
|
1105
|
+
return typeof sqlState === "string" && /^[0-9A-Z]{5}$/.test(sqlState);
|
|
1106
|
+
}
|
|
1101
1107
|
|
|
1102
1108
|
// src/runtime/prng.ts
|
|
1103
1109
|
var Prng = class _Prng {
|
|
@@ -3853,6 +3859,7 @@ function arrayElemNeedsQuote(s) {
|
|
|
3853
3859
|
function quoteArrayElem(s) {
|
|
3854
3860
|
return `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
3855
3861
|
}
|
|
3862
|
+
var UTC_OUTPUT = { zoneOffsetAt: () => 0 };
|
|
3856
3863
|
function arrayText(arr, ctx) {
|
|
3857
3864
|
if (arr.dims.length === 0) return "{}";
|
|
3858
3865
|
const needsLb = arr.lbs.some((l) => l !== 1);
|
|
@@ -25435,9 +25442,48 @@ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to
|
|
|
25435
25442
|
var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
|
|
25436
25443
|
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;
|
|
25437
25444
|
var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
|
|
25438
|
-
var
|
|
25439
|
-
var
|
|
25440
|
-
var
|
|
25445
|
+
var SQL_NAME = String.raw`(?:"[^"]+"|[A-Za-z_][\w$]*)`;
|
|
25446
|
+
var SQL_RELATION = String.raw`(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?`;
|
|
25447
|
+
var FROM_TABLE = new RegExp(String.raw`\bfrom\s+(${SQL_RELATION})`, "i");
|
|
25448
|
+
var UPDATE_TARGET = new RegExp(
|
|
25449
|
+
String.raw`^update\s+(${SQL_RELATION})(?:\s+(?:as\s+)?(${SQL_NAME}))?\s+set\s+[\s\S]*?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$`,
|
|
25450
|
+
"i"
|
|
25451
|
+
);
|
|
25452
|
+
var DELETE_TARGET = new RegExp(
|
|
25453
|
+
String.raw`^delete\s+from\s+(${SQL_RELATION})(?:\s+(?:as\s+)?(${SQL_NAME}))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$`,
|
|
25454
|
+
"i"
|
|
25455
|
+
);
|
|
25456
|
+
var lockedMutationQuery = (sql) => {
|
|
25457
|
+
const match = UPDATE_TARGET.exec(sql) ?? DELETE_TARGET.exec(sql);
|
|
25458
|
+
const relation = match?.[1];
|
|
25459
|
+
if (!relation) return null;
|
|
25460
|
+
const alias = match?.[2];
|
|
25461
|
+
const where = match?.[3];
|
|
25462
|
+
const from = alias ? `${relation} AS ${alias}` : relation;
|
|
25463
|
+
return `SELECT * FROM ${from}${where ? ` WHERE ${where}` : ""}`;
|
|
25464
|
+
};
|
|
25465
|
+
var lockLiteral = (expr, params) => {
|
|
25466
|
+
if (!expr) return void 0;
|
|
25467
|
+
switch (expr.type) {
|
|
25468
|
+
case "null_lit":
|
|
25469
|
+
return null;
|
|
25470
|
+
case "string_lit":
|
|
25471
|
+
return expr.value;
|
|
25472
|
+
case "bool_lit":
|
|
25473
|
+
return expr.value ? "t" : "f";
|
|
25474
|
+
case "number_lit":
|
|
25475
|
+
return expr.raw.replace(/^\+/, "");
|
|
25476
|
+
case "param": {
|
|
25477
|
+
const value = params[expr.index - 1];
|
|
25478
|
+
if (value === void 0) return void 0;
|
|
25479
|
+
const typed = bindValueToTyped(value, expr.index - 1);
|
|
25480
|
+
if (typed.v === null) return null;
|
|
25481
|
+
return datumText(typed.t, typed.v, UTC_OUTPUT);
|
|
25482
|
+
}
|
|
25483
|
+
default:
|
|
25484
|
+
return void 0;
|
|
25485
|
+
}
|
|
25486
|
+
};
|
|
25441
25487
|
var TYPLEN = {
|
|
25442
25488
|
bool: 1,
|
|
25443
25489
|
int2: 2,
|
|
@@ -25456,7 +25502,7 @@ var TEXT_TYPES2 = /* @__PURE__ */ new Set(["text", "varchar", "bpchar", "name",
|
|
|
25456
25502
|
var PG_EPOCH_MS = 9466848e5;
|
|
25457
25503
|
var pgError2 = (category, message2, code) => new PostgresError(category, message2, code);
|
|
25458
25504
|
var errorFields = (error) => {
|
|
25459
|
-
if (error
|
|
25505
|
+
if (isPostgresError(error)) {
|
|
25460
25506
|
const message2 = error.message;
|
|
25461
25507
|
const fields = { code: error.sqlState, message: message2 };
|
|
25462
25508
|
const constraint = /constraint "([^"]+)"/.exec(message2)?.[1];
|
|
@@ -25574,6 +25620,46 @@ var decodeParam = (oid, format, bytes) => {
|
|
|
25574
25620
|
throw pgError2("internal", `binary parameter format is not supported for type oid ${oid}`, "0A000");
|
|
25575
25621
|
}
|
|
25576
25622
|
};
|
|
25623
|
+
function returningShape(db, sql) {
|
|
25624
|
+
let statements;
|
|
25625
|
+
try {
|
|
25626
|
+
statements = parse(sql);
|
|
25627
|
+
} catch {
|
|
25628
|
+
return null;
|
|
25629
|
+
}
|
|
25630
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25631
|
+
if (!stmt || stmt.type !== "insert" && stmt.type !== "update" && stmt.type !== "delete") return null;
|
|
25632
|
+
const targets = stmt.returning;
|
|
25633
|
+
if (!targets || targets.length === 0) return null;
|
|
25634
|
+
const table = db.state.findTable(stmt.table);
|
|
25635
|
+
if (!table) return null;
|
|
25636
|
+
const label = stmt.alias ?? table.name;
|
|
25637
|
+
const columns = [];
|
|
25638
|
+
const columnTypes = [];
|
|
25639
|
+
for (const target of targets) {
|
|
25640
|
+
if (target.expr.type === "star") {
|
|
25641
|
+
const qualifier = target.expr.table?.at(-1);
|
|
25642
|
+
if (qualifier !== void 0 && qualifier !== label && qualifier !== table.name) return null;
|
|
25643
|
+
for (const column of table.columns) {
|
|
25644
|
+
columns.push(column.name);
|
|
25645
|
+
columnTypes.push(column.type.id);
|
|
25646
|
+
}
|
|
25647
|
+
continue;
|
|
25648
|
+
}
|
|
25649
|
+
if (target.expr.type === "colref") {
|
|
25650
|
+
const name = target.expr.parts.at(-1) ?? "?column?";
|
|
25651
|
+
const column = table.columns.find((item) => item.name === name);
|
|
25652
|
+
columns.push(target.alias ?? name);
|
|
25653
|
+
columnTypes.push(column?.type.id ?? "text");
|
|
25654
|
+
continue;
|
|
25655
|
+
}
|
|
25656
|
+
columns.push(target.alias ?? inferColumnName(target.expr));
|
|
25657
|
+
columnTypes.push("text");
|
|
25658
|
+
}
|
|
25659
|
+
if (columns.length === 0) return null;
|
|
25660
|
+
const command2 = stmt.type === "insert" ? "INSERT" : stmt.type === "update" ? "UPDATE" : "DELETE";
|
|
25661
|
+
return { columns, columnTypes, rows: [], rowCount: 0, command: command2 };
|
|
25662
|
+
}
|
|
25577
25663
|
var Connection = class {
|
|
25578
25664
|
constructor(socket, cluster, options2) {
|
|
25579
25665
|
this.socket = socket;
|
|
@@ -25852,8 +25938,8 @@ var Connection = class {
|
|
|
25852
25938
|
return outcome;
|
|
25853
25939
|
} catch (error) {
|
|
25854
25940
|
if (this.inTransaction) this.aborted = true;
|
|
25855
|
-
if (error
|
|
25856
|
-
const status = error
|
|
25941
|
+
if (isPostgresError(error) && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
25942
|
+
const status = isPostgresError(error) ? error.sqlState : "XX000";
|
|
25857
25943
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
25858
25944
|
throw error;
|
|
25859
25945
|
}
|
|
@@ -26049,14 +26135,48 @@ var Connection = class {
|
|
|
26049
26135
|
}
|
|
26050
26136
|
});
|
|
26051
26137
|
}
|
|
26138
|
+
/**
|
|
26139
|
+
* Hold the primary key until the transaction ends. PostgreSQL's unique index makes a
|
|
26140
|
+
* second `INSERT` of the same key wait; `ON CONFLICT DO UPDATE` then sees the committed row.
|
|
26141
|
+
* Without this, each session's workspace inserts the key and commit replay raises 23505.
|
|
26142
|
+
*/
|
|
26143
|
+
lockInsertedPrimaryKey(db, text, params) {
|
|
26144
|
+
let statements;
|
|
26145
|
+
try {
|
|
26146
|
+
statements = parse(text);
|
|
26147
|
+
} catch {
|
|
26148
|
+
return;
|
|
26149
|
+
}
|
|
26150
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
26151
|
+
if (stmt?.type !== "insert" || stmt.source === "default_values" || stmt.source.body.type !== "values") return;
|
|
26152
|
+
if (!stmt.columns) return;
|
|
26153
|
+
const table = db.state.findTable(stmt.table);
|
|
26154
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
26155
|
+
if (!primary || primary.columns.length === 0) return;
|
|
26156
|
+
const indexes = primary.columns.map((column) => stmt.columns?.indexOf(column) ?? -1);
|
|
26157
|
+
if (indexes.some((index) => index < 0)) return;
|
|
26158
|
+
const identities = [];
|
|
26159
|
+
for (const row of stmt.source.body.rows) {
|
|
26160
|
+
const identity = [];
|
|
26161
|
+
for (const index of indexes) {
|
|
26162
|
+
const value = lockLiteral(row[index], params);
|
|
26163
|
+
if (value === void 0) return;
|
|
26164
|
+
identity.push(value);
|
|
26165
|
+
}
|
|
26166
|
+
identities.push(identity);
|
|
26167
|
+
}
|
|
26168
|
+
const relation = stmt.table.join(".");
|
|
26169
|
+
for (const identity of identities) {
|
|
26170
|
+
const key = `row:${this.databaseName}:${relation}:${JSON.stringify(identity)}`;
|
|
26171
|
+
if (!this.cluster.tryLock(this, key, true)) throw new LockWait(key);
|
|
26172
|
+
}
|
|
26173
|
+
}
|
|
26052
26174
|
runQuery(db, text, params) {
|
|
26053
26175
|
const lock = ROW_LOCK.exec(text);
|
|
26054
26176
|
if (!lock) {
|
|
26055
|
-
const
|
|
26056
|
-
if (
|
|
26057
|
-
|
|
26058
|
-
this.lockRows(db, lockQuery, params, "wait");
|
|
26059
|
-
}
|
|
26177
|
+
const lockQuery = lockedMutationQuery(text);
|
|
26178
|
+
if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
|
|
26179
|
+
this.lockInsertedPrimaryKey(db, text, params);
|
|
26060
26180
|
return db.prepare(text).textResult(...params);
|
|
26061
26181
|
}
|
|
26062
26182
|
let query = text.slice(0, lock.index).trimEnd();
|
|
@@ -26420,11 +26540,16 @@ var Connection = class {
|
|
|
26420
26540
|
else this.write(backend.rowDescription(this.fields(outcome.result, portal.resultFormats)));
|
|
26421
26541
|
}
|
|
26422
26542
|
/**
|
|
26423
|
-
* The row shape of a statement that returns rows, for Describe on a statement
|
|
26424
|
-
*
|
|
26425
|
-
* fails
|
|
26543
|
+
* The row shape of a statement that returns rows, for Describe on a statement.
|
|
26544
|
+
* `INSERT`/`UPDATE`/`DELETE ... RETURNING` is described from the catalog: a trial run binds
|
|
26545
|
+
* nulls and fails NOT NULL, and Describe would answer NoData. Bun then reads one DataRow cell
|
|
26546
|
+
* and treats the rest of the message as a broken protocol frame.
|
|
26547
|
+
* Other row-returning statements are a trial run with null parameters, rolled back. A trial
|
|
26548
|
+
* that fails is described as returning no data.
|
|
26426
26549
|
*/
|
|
26427
26550
|
async shapeOf(prepared) {
|
|
26551
|
+
const described = returningShape(this.database, prepared.sql);
|
|
26552
|
+
if (described) return described;
|
|
26428
26553
|
if (!RETURNS_ROWS.test(prepared.sql)) return null;
|
|
26429
26554
|
const db = this.database;
|
|
26430
26555
|
await this.cluster.acquireTurn(this);
|