@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/server.js
CHANGED
|
@@ -250,6 +250,12 @@ function pgError(category, message2, sqlState) {
|
|
|
250
250
|
function unsupported(what) {
|
|
251
251
|
return new PostgresError("unsupported", `${what} is not supported`);
|
|
252
252
|
}
|
|
253
|
+
function isPostgresError(err) {
|
|
254
|
+
if (err instanceof PostgresError) return true;
|
|
255
|
+
if (!(err instanceof Error) || err.name !== "PostgresError") return false;
|
|
256
|
+
const sqlState = err.sqlState;
|
|
257
|
+
return typeof sqlState === "string" && /^[0-9A-Z]{5}$/.test(sqlState);
|
|
258
|
+
}
|
|
253
259
|
|
|
254
260
|
// src/runtime/prng.ts
|
|
255
261
|
var Prng = class _Prng {
|
|
@@ -3005,6 +3011,7 @@ function arrayElemNeedsQuote(s) {
|
|
|
3005
3011
|
function quoteArrayElem(s) {
|
|
3006
3012
|
return `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
3007
3013
|
}
|
|
3014
|
+
var UTC_OUTPUT = { zoneOffsetAt: () => 0 };
|
|
3008
3015
|
function arrayText(arr, ctx) {
|
|
3009
3016
|
if (arr.dims.length === 0) return "{}";
|
|
3010
3017
|
const needsLb = arr.lbs.some((l) => l !== 1);
|
|
@@ -24587,9 +24594,48 @@ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to
|
|
|
24587
24594
|
var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
|
|
24588
24595
|
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;
|
|
24589
24596
|
var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
|
|
24590
|
-
var
|
|
24591
|
-
var
|
|
24592
|
-
var
|
|
24597
|
+
var SQL_NAME = String.raw`(?:"[^"]+"|[A-Za-z_][\w$]*)`;
|
|
24598
|
+
var SQL_RELATION = String.raw`(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?`;
|
|
24599
|
+
var FROM_TABLE = new RegExp(String.raw`\bfrom\s+(${SQL_RELATION})`, "i");
|
|
24600
|
+
var UPDATE_TARGET = new RegExp(
|
|
24601
|
+
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]*)?$`,
|
|
24602
|
+
"i"
|
|
24603
|
+
);
|
|
24604
|
+
var DELETE_TARGET = new RegExp(
|
|
24605
|
+
String.raw`^delete\s+from\s+(${SQL_RELATION})(?:\s+(?:as\s+)?(${SQL_NAME}))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$`,
|
|
24606
|
+
"i"
|
|
24607
|
+
);
|
|
24608
|
+
var lockedMutationQuery = (sql) => {
|
|
24609
|
+
const match = UPDATE_TARGET.exec(sql) ?? DELETE_TARGET.exec(sql);
|
|
24610
|
+
const relation = match?.[1];
|
|
24611
|
+
if (!relation) return null;
|
|
24612
|
+
const alias = match?.[2];
|
|
24613
|
+
const where = match?.[3];
|
|
24614
|
+
const from = alias ? `${relation} AS ${alias}` : relation;
|
|
24615
|
+
return `SELECT * FROM ${from}${where ? ` WHERE ${where}` : ""}`;
|
|
24616
|
+
};
|
|
24617
|
+
var lockLiteral = (expr, params) => {
|
|
24618
|
+
if (!expr) return void 0;
|
|
24619
|
+
switch (expr.type) {
|
|
24620
|
+
case "null_lit":
|
|
24621
|
+
return null;
|
|
24622
|
+
case "string_lit":
|
|
24623
|
+
return expr.value;
|
|
24624
|
+
case "bool_lit":
|
|
24625
|
+
return expr.value ? "t" : "f";
|
|
24626
|
+
case "number_lit":
|
|
24627
|
+
return expr.raw.replace(/^\+/, "");
|
|
24628
|
+
case "param": {
|
|
24629
|
+
const value = params[expr.index - 1];
|
|
24630
|
+
if (value === void 0) return void 0;
|
|
24631
|
+
const typed = bindValueToTyped(value, expr.index - 1);
|
|
24632
|
+
if (typed.v === null) return null;
|
|
24633
|
+
return datumText(typed.t, typed.v, UTC_OUTPUT);
|
|
24634
|
+
}
|
|
24635
|
+
default:
|
|
24636
|
+
return void 0;
|
|
24637
|
+
}
|
|
24638
|
+
};
|
|
24593
24639
|
var TYPLEN = {
|
|
24594
24640
|
bool: 1,
|
|
24595
24641
|
int2: 2,
|
|
@@ -24608,7 +24654,7 @@ var TEXT_TYPES2 = /* @__PURE__ */ new Set(["text", "varchar", "bpchar", "name",
|
|
|
24608
24654
|
var PG_EPOCH_MS = 9466848e5;
|
|
24609
24655
|
var pgError2 = (category, message2, code) => new PostgresError(category, message2, code);
|
|
24610
24656
|
var errorFields = (error) => {
|
|
24611
|
-
if (error
|
|
24657
|
+
if (isPostgresError(error)) {
|
|
24612
24658
|
const message2 = error.message;
|
|
24613
24659
|
const fields = { code: error.sqlState, message: message2 };
|
|
24614
24660
|
const constraint = /constraint "([^"]+)"/.exec(message2)?.[1];
|
|
@@ -24726,6 +24772,46 @@ var decodeParam = (oid, format, bytes) => {
|
|
|
24726
24772
|
throw pgError2("internal", `binary parameter format is not supported for type oid ${oid}`, "0A000");
|
|
24727
24773
|
}
|
|
24728
24774
|
};
|
|
24775
|
+
function returningShape(db, sql) {
|
|
24776
|
+
let statements;
|
|
24777
|
+
try {
|
|
24778
|
+
statements = parse(sql);
|
|
24779
|
+
} catch {
|
|
24780
|
+
return null;
|
|
24781
|
+
}
|
|
24782
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
24783
|
+
if (!stmt || stmt.type !== "insert" && stmt.type !== "update" && stmt.type !== "delete") return null;
|
|
24784
|
+
const targets = stmt.returning;
|
|
24785
|
+
if (!targets || targets.length === 0) return null;
|
|
24786
|
+
const table = db.state.findTable(stmt.table);
|
|
24787
|
+
if (!table) return null;
|
|
24788
|
+
const label = stmt.alias ?? table.name;
|
|
24789
|
+
const columns = [];
|
|
24790
|
+
const columnTypes = [];
|
|
24791
|
+
for (const target of targets) {
|
|
24792
|
+
if (target.expr.type === "star") {
|
|
24793
|
+
const qualifier = target.expr.table?.at(-1);
|
|
24794
|
+
if (qualifier !== void 0 && qualifier !== label && qualifier !== table.name) return null;
|
|
24795
|
+
for (const column of table.columns) {
|
|
24796
|
+
columns.push(column.name);
|
|
24797
|
+
columnTypes.push(column.type.id);
|
|
24798
|
+
}
|
|
24799
|
+
continue;
|
|
24800
|
+
}
|
|
24801
|
+
if (target.expr.type === "colref") {
|
|
24802
|
+
const name = target.expr.parts.at(-1) ?? "?column?";
|
|
24803
|
+
const column = table.columns.find((item) => item.name === name);
|
|
24804
|
+
columns.push(target.alias ?? name);
|
|
24805
|
+
columnTypes.push(column?.type.id ?? "text");
|
|
24806
|
+
continue;
|
|
24807
|
+
}
|
|
24808
|
+
columns.push(target.alias ?? inferColumnName(target.expr));
|
|
24809
|
+
columnTypes.push("text");
|
|
24810
|
+
}
|
|
24811
|
+
if (columns.length === 0) return null;
|
|
24812
|
+
const command2 = stmt.type === "insert" ? "INSERT" : stmt.type === "update" ? "UPDATE" : "DELETE";
|
|
24813
|
+
return { columns, columnTypes, rows: [], rowCount: 0, command: command2 };
|
|
24814
|
+
}
|
|
24729
24815
|
var Connection = class {
|
|
24730
24816
|
constructor(socket, cluster, options) {
|
|
24731
24817
|
this.socket = socket;
|
|
@@ -25004,8 +25090,8 @@ var Connection = class {
|
|
|
25004
25090
|
return outcome;
|
|
25005
25091
|
} catch (error) {
|
|
25006
25092
|
if (this.inTransaction) this.aborted = true;
|
|
25007
|
-
if (error
|
|
25008
|
-
const status = error
|
|
25093
|
+
if (isPostgresError(error) && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
25094
|
+
const status = isPostgresError(error) ? error.sqlState : "XX000";
|
|
25009
25095
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
25010
25096
|
throw error;
|
|
25011
25097
|
}
|
|
@@ -25201,14 +25287,48 @@ var Connection = class {
|
|
|
25201
25287
|
}
|
|
25202
25288
|
});
|
|
25203
25289
|
}
|
|
25290
|
+
/**
|
|
25291
|
+
* Hold the primary key until the transaction ends. PostgreSQL's unique index makes a
|
|
25292
|
+
* second `INSERT` of the same key wait; `ON CONFLICT DO UPDATE` then sees the committed row.
|
|
25293
|
+
* Without this, each session's workspace inserts the key and commit replay raises 23505.
|
|
25294
|
+
*/
|
|
25295
|
+
lockInsertedPrimaryKey(db, text, params) {
|
|
25296
|
+
let statements;
|
|
25297
|
+
try {
|
|
25298
|
+
statements = parse(text);
|
|
25299
|
+
} catch {
|
|
25300
|
+
return;
|
|
25301
|
+
}
|
|
25302
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25303
|
+
if (stmt?.type !== "insert" || stmt.source === "default_values" || stmt.source.body.type !== "values") return;
|
|
25304
|
+
if (!stmt.columns) return;
|
|
25305
|
+
const table = db.state.findTable(stmt.table);
|
|
25306
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
25307
|
+
if (!primary || primary.columns.length === 0) return;
|
|
25308
|
+
const indexes = primary.columns.map((column) => stmt.columns?.indexOf(column) ?? -1);
|
|
25309
|
+
if (indexes.some((index) => index < 0)) return;
|
|
25310
|
+
const identities = [];
|
|
25311
|
+
for (const row of stmt.source.body.rows) {
|
|
25312
|
+
const identity = [];
|
|
25313
|
+
for (const index of indexes) {
|
|
25314
|
+
const value = lockLiteral(row[index], params);
|
|
25315
|
+
if (value === void 0) return;
|
|
25316
|
+
identity.push(value);
|
|
25317
|
+
}
|
|
25318
|
+
identities.push(identity);
|
|
25319
|
+
}
|
|
25320
|
+
const relation = stmt.table.join(".");
|
|
25321
|
+
for (const identity of identities) {
|
|
25322
|
+
const key = `row:${this.databaseName}:${relation}:${JSON.stringify(identity)}`;
|
|
25323
|
+
if (!this.cluster.tryLock(this, key, true)) throw new LockWait(key);
|
|
25324
|
+
}
|
|
25325
|
+
}
|
|
25204
25326
|
runQuery(db, text, params) {
|
|
25205
25327
|
const lock = ROW_LOCK.exec(text);
|
|
25206
25328
|
if (!lock) {
|
|
25207
|
-
const
|
|
25208
|
-
if (
|
|
25209
|
-
|
|
25210
|
-
this.lockRows(db, lockQuery, params, "wait");
|
|
25211
|
-
}
|
|
25329
|
+
const lockQuery = lockedMutationQuery(text);
|
|
25330
|
+
if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
|
|
25331
|
+
this.lockInsertedPrimaryKey(db, text, params);
|
|
25212
25332
|
return db.prepare(text).textResult(...params);
|
|
25213
25333
|
}
|
|
25214
25334
|
let query = text.slice(0, lock.index).trimEnd();
|
|
@@ -25572,11 +25692,16 @@ var Connection = class {
|
|
|
25572
25692
|
else this.write(backend.rowDescription(this.fields(outcome.result, portal.resultFormats)));
|
|
25573
25693
|
}
|
|
25574
25694
|
/**
|
|
25575
|
-
* The row shape of a statement that returns rows, for Describe on a statement
|
|
25576
|
-
*
|
|
25577
|
-
* fails
|
|
25695
|
+
* The row shape of a statement that returns rows, for Describe on a statement.
|
|
25696
|
+
* `INSERT`/`UPDATE`/`DELETE ... RETURNING` is described from the catalog: a trial run binds
|
|
25697
|
+
* nulls and fails NOT NULL, and Describe would answer NoData. Bun then reads one DataRow cell
|
|
25698
|
+
* and treats the rest of the message as a broken protocol frame.
|
|
25699
|
+
* Other row-returning statements are a trial run with null parameters, rolled back. A trial
|
|
25700
|
+
* that fails is described as returning no data.
|
|
25578
25701
|
*/
|
|
25579
25702
|
async shapeOf(prepared) {
|
|
25703
|
+
const described = returningShape(this.database, prepared.sql);
|
|
25704
|
+
if (described) return described;
|
|
25580
25705
|
if (!RETURNS_ROWS.test(prepared.sql)) return null;
|
|
25581
25706
|
const db = this.database;
|
|
25582
25707
|
await this.cluster.acquireTurn(this);
|