@crvouga/mockingbird-service-postgres 3.0.0 → 3.0.1
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 +6 -0
- package/dist/admin.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server.js +91 -11
- package/dist/server.js.map +2 -2
- package/dist/unstable.js.map +1 -1
- package/dist/wire/cli.js +91 -11
- package/dist/wire/cli.js.map +2 -2
- package/dist/wire/connection.d.ts +6 -0
- package/dist/wire/index.js +91 -11
- 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];
|
|
@@ -25004,8 +25050,8 @@ var Connection = class {
|
|
|
25004
25050
|
return outcome;
|
|
25005
25051
|
} catch (error) {
|
|
25006
25052
|
if (this.inTransaction) this.aborted = true;
|
|
25007
|
-
if (error
|
|
25008
|
-
const status = error
|
|
25053
|
+
if (isPostgresError(error) && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
25054
|
+
const status = isPostgresError(error) ? error.sqlState : "XX000";
|
|
25009
25055
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
25010
25056
|
throw error;
|
|
25011
25057
|
}
|
|
@@ -25201,14 +25247,48 @@ var Connection = class {
|
|
|
25201
25247
|
}
|
|
25202
25248
|
});
|
|
25203
25249
|
}
|
|
25250
|
+
/**
|
|
25251
|
+
* Hold the primary key until the transaction ends. PostgreSQL's unique index makes a
|
|
25252
|
+
* second `INSERT` of the same key wait; `ON CONFLICT DO UPDATE` then sees the committed row.
|
|
25253
|
+
* Without this, each session's workspace inserts the key and commit replay raises 23505.
|
|
25254
|
+
*/
|
|
25255
|
+
lockInsertedPrimaryKey(db, text, params) {
|
|
25256
|
+
let statements;
|
|
25257
|
+
try {
|
|
25258
|
+
statements = parse(text);
|
|
25259
|
+
} catch {
|
|
25260
|
+
return;
|
|
25261
|
+
}
|
|
25262
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25263
|
+
if (stmt?.type !== "insert" || stmt.source === "default_values" || stmt.source.body.type !== "values") return;
|
|
25264
|
+
if (!stmt.columns) return;
|
|
25265
|
+
const table = db.state.findTable(stmt.table);
|
|
25266
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
25267
|
+
if (!primary || primary.columns.length === 0) return;
|
|
25268
|
+
const indexes = primary.columns.map((column) => stmt.columns?.indexOf(column) ?? -1);
|
|
25269
|
+
if (indexes.some((index) => index < 0)) return;
|
|
25270
|
+
const identities = [];
|
|
25271
|
+
for (const row of stmt.source.body.rows) {
|
|
25272
|
+
const identity = [];
|
|
25273
|
+
for (const index of indexes) {
|
|
25274
|
+
const value = lockLiteral(row[index], params);
|
|
25275
|
+
if (value === void 0) return;
|
|
25276
|
+
identity.push(value);
|
|
25277
|
+
}
|
|
25278
|
+
identities.push(identity);
|
|
25279
|
+
}
|
|
25280
|
+
const relation = stmt.table.join(".");
|
|
25281
|
+
for (const identity of identities) {
|
|
25282
|
+
const key = `row:${this.databaseName}:${relation}:${JSON.stringify(identity)}`;
|
|
25283
|
+
if (!this.cluster.tryLock(this, key, true)) throw new LockWait(key);
|
|
25284
|
+
}
|
|
25285
|
+
}
|
|
25204
25286
|
runQuery(db, text, params) {
|
|
25205
25287
|
const lock = ROW_LOCK.exec(text);
|
|
25206
25288
|
if (!lock) {
|
|
25207
|
-
const
|
|
25208
|
-
if (
|
|
25209
|
-
|
|
25210
|
-
this.lockRows(db, lockQuery, params, "wait");
|
|
25211
|
-
}
|
|
25289
|
+
const lockQuery = lockedMutationQuery(text);
|
|
25290
|
+
if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
|
|
25291
|
+
this.lockInsertedPrimaryKey(db, text, params);
|
|
25212
25292
|
return db.prepare(text).textResult(...params);
|
|
25213
25293
|
}
|
|
25214
25294
|
let query = text.slice(0, lock.index).trimEnd();
|