@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/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 FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
25439
- 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;
25440
- 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;
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 instanceof PostgresError) {
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];
@@ -25852,8 +25898,8 @@ var Connection = class {
25852
25898
  return outcome;
25853
25899
  } catch (error) {
25854
25900
  if (this.inTransaction) this.aborted = true;
25855
- if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
25856
- const status = error instanceof PostgresError ? error.sqlState : "XX000";
25901
+ if (isPostgresError(error) && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
25902
+ const status = isPostgresError(error) ? error.sqlState : "XX000";
25857
25903
  this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
25858
25904
  throw error;
25859
25905
  }
@@ -26049,14 +26095,48 @@ var Connection = class {
26049
26095
  }
26050
26096
  });
26051
26097
  }
26098
+ /**
26099
+ * Hold the primary key until the transaction ends. PostgreSQL's unique index makes a
26100
+ * second `INSERT` of the same key wait; `ON CONFLICT DO UPDATE` then sees the committed row.
26101
+ * Without this, each session's workspace inserts the key and commit replay raises 23505.
26102
+ */
26103
+ lockInsertedPrimaryKey(db, text, params) {
26104
+ let statements;
26105
+ try {
26106
+ statements = parse(text);
26107
+ } catch {
26108
+ return;
26109
+ }
26110
+ const stmt = statements.length === 1 ? statements[0] : void 0;
26111
+ if (stmt?.type !== "insert" || stmt.source === "default_values" || stmt.source.body.type !== "values") return;
26112
+ if (!stmt.columns) return;
26113
+ const table = db.state.findTable(stmt.table);
26114
+ const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
26115
+ if (!primary || primary.columns.length === 0) return;
26116
+ const indexes = primary.columns.map((column) => stmt.columns?.indexOf(column) ?? -1);
26117
+ if (indexes.some((index) => index < 0)) return;
26118
+ const identities = [];
26119
+ for (const row of stmt.source.body.rows) {
26120
+ const identity = [];
26121
+ for (const index of indexes) {
26122
+ const value = lockLiteral(row[index], params);
26123
+ if (value === void 0) return;
26124
+ identity.push(value);
26125
+ }
26126
+ identities.push(identity);
26127
+ }
26128
+ const relation = stmt.table.join(".");
26129
+ for (const identity of identities) {
26130
+ const key = `row:${this.databaseName}:${relation}:${JSON.stringify(identity)}`;
26131
+ if (!this.cluster.tryLock(this, key, true)) throw new LockWait(key);
26132
+ }
26133
+ }
26052
26134
  runQuery(db, text, params) {
26053
26135
  const lock = ROW_LOCK.exec(text);
26054
26136
  if (!lock) {
26055
- const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
26056
- if (mutation) {
26057
- const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
26058
- this.lockRows(db, lockQuery, params, "wait");
26059
- }
26137
+ const lockQuery = lockedMutationQuery(text);
26138
+ if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
26139
+ this.lockInsertedPrimaryKey(db, text, params);
26060
26140
  return db.prepare(text).textResult(...params);
26061
26141
  }
26062
26142
  let query = text.slice(0, lock.index).trimEnd();