@crvouga/mockingbird-service-postgres 2.3.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 {
@@ -1218,6 +1224,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
1218
1224
  "pg_enum",
1219
1225
  "pg_constraint",
1220
1226
  "pg_index",
1227
+ "pg_description",
1228
+ "pg_stat_user_tables",
1229
+ "pg_stat_user_indexes",
1221
1230
  "pg_sequence",
1222
1231
  "pg_sequences",
1223
1232
  "pg_tables",
@@ -3495,6 +3504,7 @@ var TYPE_ALIASES = {
3495
3504
  regclass: "regclass",
3496
3505
  regtype: "regtype",
3497
3506
  regproc: "regproc",
3507
+ regnamespace: "regnamespace",
3498
3508
  oid: "oid",
3499
3509
  void: "void",
3500
3510
  record: "record",
@@ -3588,6 +3598,7 @@ var TYPE_OIDS = {
3588
3598
  numeric: 1700,
3589
3599
  regclass: 2205,
3590
3600
  regtype: 2206,
3601
+ regnamespace: 4089,
3591
3602
  record: 2249,
3592
3603
  uuid: 2950,
3593
3604
  jsonb: 3802,
@@ -3848,6 +3859,7 @@ function arrayElemNeedsQuote(s) {
3848
3859
  function quoteArrayElem(s) {
3849
3860
  return `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
3850
3861
  }
3862
+ var UTC_OUTPUT = { zoneOffsetAt: () => 0 };
3851
3863
  function arrayText(arr, ctx) {
3852
3864
  if (arr.dims.length === 0) return "{}";
3853
3865
  const needsLb = arr.lbs.some((l) => l !== 1);
@@ -3888,6 +3900,7 @@ function recordText(rec, ctx) {
3888
3900
  return `(${parts.join(",")})`;
3889
3901
  }
3890
3902
  function datumText(t, v, ctx) {
3903
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
3891
3904
  if (v === null) throw pgError("internal", "datumText called with null");
3892
3905
  if (isArrayType(t)) return arrayText(v, ctx);
3893
3906
  if (isEnumType(t)) return v;
@@ -3961,6 +3974,11 @@ function datumText(t, v, ctx) {
3961
3974
  }
3962
3975
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
3963
3976
  function datumFromText(t, text, ctx) {
3977
+ if (t === "regnamespace") {
3978
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
3979
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
3980
+ return ctx.namespaceOid(text);
3981
+ }
3964
3982
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
3965
3983
  if (isEnumType(t)) {
3966
3984
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -4817,7 +4835,11 @@ function castTo(env, value, target, opts = {}) {
4817
4835
  );
4818
4836
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
4819
4837
  }
4820
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
4838
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
4839
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
4840
+ return tv(target, target === "int8" ? BigInt(v) : v);
4841
+ }
4842
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
4821
4843
  return tv(target, Number(v));
4822
4844
  }
4823
4845
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -5765,6 +5787,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
5765
5787
  case "int2":
5766
5788
  case "int4":
5767
5789
  case "oid":
5790
+ case "regnamespace":
5768
5791
  case "date":
5769
5792
  return expectNumber(a, t) - expectNumber(b, t);
5770
5793
  case "float4":
@@ -7920,10 +7943,99 @@ function exactDegTan(x) {
7920
7943
  }
7921
7944
 
7922
7945
  // src/sql/deparse.ts
7946
+ function deparseSelect(stmt) {
7947
+ return deparseBody(stmt.body);
7948
+ }
7949
+ function deparseBody(body) {
7950
+ switch (body.type) {
7951
+ case "select_core":
7952
+ return deparseSelectCore(body);
7953
+ case "setop":
7954
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
7955
+ case "values":
7956
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
7957
+ }
7958
+ }
7959
+ function deparseSelectCore(body) {
7960
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
7961
+ const expr = deparseExpr(t.expr);
7962
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
7963
+ }).join(", ");
7964
+ let sql = `SELECT ${targets}`;
7965
+ if (body.from.length > 0) {
7966
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
7967
+ }
7968
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
7969
+ if (body.groupBy && body.groupBy.length > 0) {
7970
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
7971
+ }
7972
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
7973
+ return sql;
7974
+ }
7975
+ function deparseFrom(item) {
7976
+ if (item.type === "from_table") {
7977
+ const name = item.name.join(".");
7978
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
7979
+ }
7980
+ return "(subquery)";
7981
+ }
7923
7982
  function quoteIdent(name) {
7924
7983
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7925
7984
  return `"${name.replaceAll('"', '""')}"`;
7926
7985
  }
7986
+ function deparseExpr(e) {
7987
+ switch (e.type) {
7988
+ case "null_lit":
7989
+ return "NULL";
7990
+ case "string_lit":
7991
+ return `'${e.value.replaceAll("'", "''")}'`;
7992
+ case "number_lit":
7993
+ return e.raw;
7994
+ case "bool_lit":
7995
+ return e.value ? "true" : "false";
7996
+ case "colref":
7997
+ return e.parts.join(".");
7998
+ case "star":
7999
+ return e.table ? `${e.table.join(".")}.*` : "*";
8000
+ case "binop":
8001
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
8002
+ case "unop":
8003
+ return `${e.op}${deparseExpr(e.operand)}`;
8004
+ case "cast":
8005
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
8006
+ case "func":
8007
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
8008
+ case "case": {
8009
+ let out = "CASE";
8010
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
8011
+ for (const arm of e.whens) {
8012
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
8013
+ }
8014
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
8015
+ return `${out} END`;
8016
+ }
8017
+ case "subquery_expr":
8018
+ return `(${deparseSelect(e.query)})`;
8019
+ default:
8020
+ return "?";
8021
+ }
8022
+ }
8023
+
8024
+ // src/sql/deparse-index.ts
8025
+ function deparseIndexColumn(column) {
8026
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
8027
+ if (column.dir === "desc") result += " DESC";
8028
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
8029
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
8030
+ return result;
8031
+ }
8032
+ function deparseIndex(index) {
8033
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
8034
+ const unique = index.unique ? "UNIQUE " : "";
8035
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
8036
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
8037
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
8038
+ }
7927
8039
 
7928
8040
  // src/runtime/database-context.ts
7929
8041
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -8017,6 +8129,22 @@ function sequenceNextval(ctx, seq) {
8017
8129
  }
8018
8130
  function getMiscFunctions() {
8019
8131
  const m = /* @__PURE__ */ new Map();
8132
+ m.set(
8133
+ "pg_get_indexdef",
8134
+ strict("text", (ctx, args2) => {
8135
+ const oid = argInt(ctx, args2[0]);
8136
+ const column = args2[1] ? argInt(ctx, args2[1]) : 0;
8137
+ for (const schema of ctx.state.schemas.values()) {
8138
+ for (const index of schema.indexes.values()) {
8139
+ if (index.oid !== oid) continue;
8140
+ if (column === 0) return tv("text", deparseIndex(index));
8141
+ const part = index.columns[column - 1];
8142
+ return tv("text", part ? deparseIndexColumn(part) : "");
8143
+ }
8144
+ }
8145
+ return tv("text", null);
8146
+ })
8147
+ );
8020
8148
  m.set("version", () => tv("text", PG_VERSION_TEXT));
8021
8149
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
8022
8150
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -13237,6 +13365,9 @@ function tryIndexedTableRows(env, table, alias, where) {
13237
13365
  const key = uniqueKeyOf(env, table, spec, probeRow);
13238
13366
  if (key === null) return [];
13239
13367
  const hits = indexStoreFor(env, table, spec).lookup(key);
13368
+ const schema = env.ctx.state.schemas.get(table.schema);
13369
+ const index = schema?.indexes.get(spec.name);
13370
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
13240
13371
  return hits.map((i) => table.rowAt(i));
13241
13372
  }
13242
13373
  function tryIndexedFromItem(env, item, where) {
@@ -15999,8 +16130,8 @@ var Parser = class {
15999
16130
  this.eatKw("column");
16000
16131
  const column = this.ident();
16001
16132
  let alterType = this.eatKw("type");
16002
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
16003
- this.pos += 1;
16133
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
16134
+ this.pos += 2;
16004
16135
  this.expectKw("type");
16005
16136
  alterType = true;
16006
16137
  }
@@ -20352,7 +20483,7 @@ function uniqueSpecsFor(env, table) {
20352
20483
  const schema = env.ctx.state.schemas.get(table.schema);
20353
20484
  if (schema) {
20354
20485
  for (const idx of schema.indexes.values()) {
20355
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
20486
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
20356
20487
  specs.push({
20357
20488
  name: idx.name,
20358
20489
  keys: idx.columns.map((c) => ({
@@ -21503,6 +21634,25 @@ var EngineCtx = class {
21503
21634
  timezone() {
21504
21635
  return this.state.getSetting("timezone") ?? "UTC";
21505
21636
  }
21637
+ namespaceOid(input) {
21638
+ const trimmed = input.trim();
21639
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
21640
+ if (name === "pg_catalog") return 11;
21641
+ if (name === "information_schema") return 13212;
21642
+ const schema = this.state.schemas.get(name);
21643
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
21644
+ return schema.oid;
21645
+ }
21646
+ namespaceName(oid) {
21647
+ if (oid === 11) return "pg_catalog";
21648
+ if (oid === 13212) return "information_schema";
21649
+ for (const schema of this.state.schemas.values()) {
21650
+ if (schema.oid === oid) {
21651
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
21652
+ }
21653
+ }
21654
+ return null;
21655
+ }
21506
21656
  zoneOffsetAt(utcMicros) {
21507
21657
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
21508
21658
  }
@@ -21773,7 +21923,7 @@ function pgClass(ctx) {
21773
21923
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
21774
21924
  for (const i of schemaData.indexes.values()) {
21775
21925
  push(
21776
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21926
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21777
21927
  i.name,
21778
21928
  i.schema,
21779
21929
  "i",
@@ -22010,7 +22160,15 @@ function pgIndex(ctx) {
22010
22160
  for (const i of schema.indexes.values()) {
22011
22161
  const table = schema.tables.get(i.table);
22012
22162
  const isPrimary = i.isConstraint && table?.constraints.some((c) => c.kind === "primary_key" && c.name === i.name) === true;
22013
- rows.push([table?.oid ?? 0, table?.oid ?? 0, i.columns.length, i.unique, isPrimary, i.where !== null]);
22163
+ rows.push([
22164
+ i.oid ?? table?.oid ?? 0,
22165
+ table?.oid ?? 0,
22166
+ i.columns.length,
22167
+ i.unique,
22168
+ isPrimary,
22169
+ i.where !== null,
22170
+ i.valid !== false
22171
+ ]);
22014
22172
  }
22015
22173
  }
22016
22174
  return rel(
@@ -22020,12 +22178,73 @@ function pgIndex(ctx) {
22020
22178
  ["indnatts", "int2"],
22021
22179
  ["indisunique", "bool"],
22022
22180
  ["indisprimary", "bool"],
22023
- ["indpred", "bool"]
22181
+ ["indpred", "bool"],
22182
+ ["indisvalid", "bool"]
22024
22183
  ],
22025
22184
  rows,
22026
22185
  "pg_index"
22027
22186
  );
22028
22187
  }
22188
+ function pgStatUserTables(ctx) {
22189
+ return rel(
22190
+ [
22191
+ ["relid", "oid"],
22192
+ ["schemaname", "name"],
22193
+ ["relname", "name"],
22194
+ ["n_live_tup", "int8"]
22195
+ ],
22196
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
22197
+ "pg_stat_user_tables"
22198
+ );
22199
+ }
22200
+ function pgDescription(ctx) {
22201
+ const rows = [];
22202
+ for (const schema of ctx.state.schemas.values()) {
22203
+ for (const index of schema.indexes.values()) {
22204
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
22205
+ }
22206
+ }
22207
+ return rel(
22208
+ [
22209
+ ["objoid", "oid"],
22210
+ ["classoid", "oid"],
22211
+ ["objsubid", "int4"],
22212
+ ["description", "text"]
22213
+ ],
22214
+ rows,
22215
+ "pg_description"
22216
+ );
22217
+ }
22218
+ function pgStatUserIndexes(ctx) {
22219
+ const rows = [];
22220
+ for (const schema of ctx.state.schemas.values()) {
22221
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
22222
+ continue;
22223
+ for (const index of schema.indexes.values()) {
22224
+ const table = schema.tables.get(index.table);
22225
+ rows.push([
22226
+ table?.oid ?? 0,
22227
+ index.oid ?? table?.oid ?? 0,
22228
+ schema.name,
22229
+ index.table,
22230
+ index.name,
22231
+ index.scans ?? 0n
22232
+ ]);
22233
+ }
22234
+ }
22235
+ return rel(
22236
+ [
22237
+ ["relid", "oid"],
22238
+ ["indexrelid", "oid"],
22239
+ ["schemaname", "name"],
22240
+ ["relname", "name"],
22241
+ ["indexrelname", "name"],
22242
+ ["idx_scan", "int8"]
22243
+ ],
22244
+ rows,
22245
+ "pg_stat_user_indexes"
22246
+ );
22247
+ }
22029
22248
  function pgSequence(ctx) {
22030
22249
  const rows = [];
22031
22250
  for (const s of allSequences(ctx.state)) {
@@ -22140,7 +22359,7 @@ function pgIndexesView(ctx) {
22140
22359
  const rows = [];
22141
22360
  for (const schema of ctx.state.schemas.values()) {
22142
22361
  for (const i of schema.indexes.values()) {
22143
- rows.push([i.schema, i.table, i.name, null, null]);
22362
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
22144
22363
  }
22145
22364
  }
22146
22365
  return rel(
@@ -22461,6 +22680,12 @@ function buildCatalogRelation(ctx, schema, name) {
22461
22680
  return pgConstraint(ctx);
22462
22681
  case "pg_index":
22463
22682
  return pgIndex(ctx);
22683
+ case "pg_description":
22684
+ return pgDescription(ctx);
22685
+ case "pg_stat_user_tables":
22686
+ return pgStatUserTables(ctx);
22687
+ case "pg_stat_user_indexes":
22688
+ return pgStatUserIndexes(ctx);
22464
22689
  case "pg_sequence":
22465
22690
  return pgSequence(ctx);
22466
22691
  case "pg_sequences":
@@ -23044,6 +23269,7 @@ function executeCreateIndex(env, stmt) {
23044
23269
  }
23045
23270
  assertGinTrgmIndex(state, table, stmt);
23046
23271
  schema.indexes.set(name, {
23272
+ oid: state.nextOid(),
23047
23273
  name,
23048
23274
  schema: schema.name,
23049
23275
  table: table.name,
@@ -23056,7 +23282,8 @@ function executeCreateIndex(env, stmt) {
23056
23282
  })),
23057
23283
  where: stmt.where,
23058
23284
  nullsNotDistinct: stmt.nullsNotDistinct,
23059
- isConstraint: false
23285
+ isConstraint: false,
23286
+ valid: true
23060
23287
  });
23061
23288
  if (stmt.unique) {
23062
23289
  try {
@@ -23064,7 +23291,9 @@ function executeCreateIndex(env, stmt) {
23064
23291
  checkUnique(env, table, table.rowAt(i), i);
23065
23292
  }
23066
23293
  } catch (err) {
23067
- schema.indexes.delete(name);
23294
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
23295
+ else schema.indexes.delete(name);
23296
+ table.indexStores = null;
23068
23297
  throw err;
23069
23298
  }
23070
23299
  }
@@ -24184,6 +24413,9 @@ var Statement = class _Statement {
24184
24413
  const typed = params.map((p, i) => bindValueToTyped(p, i));
24185
24414
  let last = null;
24186
24415
  for (const stmt of this.statements) {
24416
+ if (stmt.type === "create_index" && stmt.concurrently && this.database.transactions.inTransaction) {
24417
+ throw pgError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
24418
+ }
24187
24419
  const env = {
24188
24420
  ctx: new EngineCtx(this.database.state),
24189
24421
  params: typed,
@@ -25210,9 +25442,48 @@ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to
25210
25442
  var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
25211
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;
25212
25444
  var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
25213
- var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
25214
- 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;
25215
- 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
+ };
25216
25487
  var TYPLEN = {
25217
25488
  bool: 1,
25218
25489
  int2: 2,
@@ -25231,7 +25502,7 @@ var TEXT_TYPES2 = /* @__PURE__ */ new Set(["text", "varchar", "bpchar", "name",
25231
25502
  var PG_EPOCH_MS = 9466848e5;
25232
25503
  var pgError2 = (category, message2, code) => new PostgresError(category, message2, code);
25233
25504
  var errorFields = (error) => {
25234
- if (error instanceof PostgresError) {
25505
+ if (isPostgresError(error)) {
25235
25506
  const message2 = error.message;
25236
25507
  const fields = { code: error.sqlState, message: message2 };
25237
25508
  const constraint = /constraint "([^"]+)"/.exec(message2)?.[1];
@@ -25627,8 +25898,8 @@ var Connection = class {
25627
25898
  return outcome;
25628
25899
  } catch (error) {
25629
25900
  if (this.inTransaction) this.aborted = true;
25630
- if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
25631
- 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";
25632
25903
  this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
25633
25904
  throw error;
25634
25905
  }
@@ -25731,7 +26002,11 @@ var Connection = class {
25731
26002
  const result = this.cluster.as(this, () => this.runQuery(db, text, params));
25732
26003
  return { empty: false, result, tag: commandTag(result) };
25733
26004
  };
25734
- const outcome = db.transaction(run);
26005
+ const concurrentIndex = /\bconcurrently\b/i.test(text) && parse(text).some((stmt) => stmt.type === "create_index" && stmt.concurrently);
26006
+ if (concurrentIndex && this.transaction !== null) {
26007
+ throw new PostgresError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
26008
+ }
26009
+ const outcome = concurrentIndex ? run() : db.transaction(run);
25735
26010
  if (this.transaction && mutatesDatabase(text)) {
25736
26011
  this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
25737
26012
  }
@@ -25820,14 +26095,48 @@ var Connection = class {
25820
26095
  }
25821
26096
  });
25822
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
+ }
25823
26134
  runQuery(db, text, params) {
25824
26135
  const lock = ROW_LOCK.exec(text);
25825
26136
  if (!lock) {
25826
- const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
25827
- if (mutation) {
25828
- const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
25829
- this.lockRows(db, lockQuery, params, "wait");
25830
- }
26137
+ const lockQuery = lockedMutationQuery(text);
26138
+ if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
26139
+ this.lockInsertedPrimaryKey(db, text, params);
25831
26140
  return db.prepare(text).textResult(...params);
25832
26141
  }
25833
26142
  let query = text.slice(0, lock.index).trimEnd();