@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/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 {
@@ -370,6 +376,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
370
376
  "pg_enum",
371
377
  "pg_constraint",
372
378
  "pg_index",
379
+ "pg_description",
380
+ "pg_stat_user_tables",
381
+ "pg_stat_user_indexes",
373
382
  "pg_sequence",
374
383
  "pg_sequences",
375
384
  "pg_tables",
@@ -2647,6 +2656,7 @@ var TYPE_ALIASES = {
2647
2656
  regclass: "regclass",
2648
2657
  regtype: "regtype",
2649
2658
  regproc: "regproc",
2659
+ regnamespace: "regnamespace",
2650
2660
  oid: "oid",
2651
2661
  void: "void",
2652
2662
  record: "record",
@@ -2740,6 +2750,7 @@ var TYPE_OIDS = {
2740
2750
  numeric: 1700,
2741
2751
  regclass: 2205,
2742
2752
  regtype: 2206,
2753
+ regnamespace: 4089,
2743
2754
  record: 2249,
2744
2755
  uuid: 2950,
2745
2756
  jsonb: 3802,
@@ -3000,6 +3011,7 @@ function arrayElemNeedsQuote(s) {
3000
3011
  function quoteArrayElem(s) {
3001
3012
  return `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
3002
3013
  }
3014
+ var UTC_OUTPUT = { zoneOffsetAt: () => 0 };
3003
3015
  function arrayText(arr, ctx) {
3004
3016
  if (arr.dims.length === 0) return "{}";
3005
3017
  const needsLb = arr.lbs.some((l) => l !== 1);
@@ -3040,6 +3052,7 @@ function recordText(rec, ctx) {
3040
3052
  return `(${parts.join(",")})`;
3041
3053
  }
3042
3054
  function datumText(t, v, ctx) {
3055
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
3043
3056
  if (v === null) throw pgError("internal", "datumText called with null");
3044
3057
  if (isArrayType(t)) return arrayText(v, ctx);
3045
3058
  if (isEnumType(t)) return v;
@@ -3113,6 +3126,11 @@ function datumText(t, v, ctx) {
3113
3126
  }
3114
3127
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
3115
3128
  function datumFromText(t, text, ctx) {
3129
+ if (t === "regnamespace") {
3130
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
3131
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
3132
+ return ctx.namespaceOid(text);
3133
+ }
3116
3134
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
3117
3135
  if (isEnumType(t)) {
3118
3136
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -3969,7 +3987,11 @@ function castTo(env, value, target, opts = {}) {
3969
3987
  );
3970
3988
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
3971
3989
  }
3972
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
3990
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
3991
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
3992
+ return tv(target, target === "int8" ? BigInt(v) : v);
3993
+ }
3994
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
3973
3995
  return tv(target, Number(v));
3974
3996
  }
3975
3997
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -4917,6 +4939,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
4917
4939
  case "int2":
4918
4940
  case "int4":
4919
4941
  case "oid":
4942
+ case "regnamespace":
4920
4943
  case "date":
4921
4944
  return expectNumber(a, t) - expectNumber(b, t);
4922
4945
  case "float4":
@@ -7072,10 +7095,99 @@ function exactDegTan(x) {
7072
7095
  }
7073
7096
 
7074
7097
  // src/sql/deparse.ts
7098
+ function deparseSelect(stmt) {
7099
+ return deparseBody(stmt.body);
7100
+ }
7101
+ function deparseBody(body) {
7102
+ switch (body.type) {
7103
+ case "select_core":
7104
+ return deparseSelectCore(body);
7105
+ case "setop":
7106
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
7107
+ case "values":
7108
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
7109
+ }
7110
+ }
7111
+ function deparseSelectCore(body) {
7112
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
7113
+ const expr = deparseExpr(t.expr);
7114
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
7115
+ }).join(", ");
7116
+ let sql = `SELECT ${targets}`;
7117
+ if (body.from.length > 0) {
7118
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
7119
+ }
7120
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
7121
+ if (body.groupBy && body.groupBy.length > 0) {
7122
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
7123
+ }
7124
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
7125
+ return sql;
7126
+ }
7127
+ function deparseFrom(item) {
7128
+ if (item.type === "from_table") {
7129
+ const name = item.name.join(".");
7130
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
7131
+ }
7132
+ return "(subquery)";
7133
+ }
7075
7134
  function quoteIdent(name) {
7076
7135
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7077
7136
  return `"${name.replaceAll('"', '""')}"`;
7078
7137
  }
7138
+ function deparseExpr(e) {
7139
+ switch (e.type) {
7140
+ case "null_lit":
7141
+ return "NULL";
7142
+ case "string_lit":
7143
+ return `'${e.value.replaceAll("'", "''")}'`;
7144
+ case "number_lit":
7145
+ return e.raw;
7146
+ case "bool_lit":
7147
+ return e.value ? "true" : "false";
7148
+ case "colref":
7149
+ return e.parts.join(".");
7150
+ case "star":
7151
+ return e.table ? `${e.table.join(".")}.*` : "*";
7152
+ case "binop":
7153
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
7154
+ case "unop":
7155
+ return `${e.op}${deparseExpr(e.operand)}`;
7156
+ case "cast":
7157
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
7158
+ case "func":
7159
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
7160
+ case "case": {
7161
+ let out = "CASE";
7162
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
7163
+ for (const arm of e.whens) {
7164
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
7165
+ }
7166
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
7167
+ return `${out} END`;
7168
+ }
7169
+ case "subquery_expr":
7170
+ return `(${deparseSelect(e.query)})`;
7171
+ default:
7172
+ return "?";
7173
+ }
7174
+ }
7175
+
7176
+ // src/sql/deparse-index.ts
7177
+ function deparseIndexColumn(column) {
7178
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
7179
+ if (column.dir === "desc") result += " DESC";
7180
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
7181
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
7182
+ return result;
7183
+ }
7184
+ function deparseIndex(index) {
7185
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
7186
+ const unique = index.unique ? "UNIQUE " : "";
7187
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
7188
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
7189
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
7190
+ }
7079
7191
 
7080
7192
  // src/runtime/database-context.ts
7081
7193
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -7169,6 +7281,22 @@ function sequenceNextval(ctx, seq) {
7169
7281
  }
7170
7282
  function getMiscFunctions() {
7171
7283
  const m = /* @__PURE__ */ new Map();
7284
+ m.set(
7285
+ "pg_get_indexdef",
7286
+ strict("text", (ctx, args) => {
7287
+ const oid = argInt(ctx, args[0]);
7288
+ const column = args[1] ? argInt(ctx, args[1]) : 0;
7289
+ for (const schema of ctx.state.schemas.values()) {
7290
+ for (const index of schema.indexes.values()) {
7291
+ if (index.oid !== oid) continue;
7292
+ if (column === 0) return tv("text", deparseIndex(index));
7293
+ const part = index.columns[column - 1];
7294
+ return tv("text", part ? deparseIndexColumn(part) : "");
7295
+ }
7296
+ }
7297
+ return tv("text", null);
7298
+ })
7299
+ );
7172
7300
  m.set("version", () => tv("text", PG_VERSION_TEXT));
7173
7301
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7174
7302
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -12389,6 +12517,9 @@ function tryIndexedTableRows(env, table, alias, where) {
12389
12517
  const key = uniqueKeyOf(env, table, spec, probeRow);
12390
12518
  if (key === null) return [];
12391
12519
  const hits = indexStoreFor(env, table, spec).lookup(key);
12520
+ const schema = env.ctx.state.schemas.get(table.schema);
12521
+ const index = schema?.indexes.get(spec.name);
12522
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
12392
12523
  return hits.map((i) => table.rowAt(i));
12393
12524
  }
12394
12525
  function tryIndexedFromItem(env, item, where) {
@@ -15151,8 +15282,8 @@ var Parser = class {
15151
15282
  this.eatKw("column");
15152
15283
  const column = this.ident();
15153
15284
  let alterType = this.eatKw("type");
15154
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
15155
- this.pos += 1;
15285
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
15286
+ this.pos += 2;
15156
15287
  this.expectKw("type");
15157
15288
  alterType = true;
15158
15289
  }
@@ -19504,7 +19635,7 @@ function uniqueSpecsFor(env, table) {
19504
19635
  const schema = env.ctx.state.schemas.get(table.schema);
19505
19636
  if (schema) {
19506
19637
  for (const idx of schema.indexes.values()) {
19507
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
19638
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
19508
19639
  specs.push({
19509
19640
  name: idx.name,
19510
19641
  keys: idx.columns.map((c) => ({
@@ -20655,6 +20786,25 @@ var EngineCtx = class {
20655
20786
  timezone() {
20656
20787
  return this.state.getSetting("timezone") ?? "UTC";
20657
20788
  }
20789
+ namespaceOid(input) {
20790
+ const trimmed = input.trim();
20791
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
20792
+ if (name === "pg_catalog") return 11;
20793
+ if (name === "information_schema") return 13212;
20794
+ const schema = this.state.schemas.get(name);
20795
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
20796
+ return schema.oid;
20797
+ }
20798
+ namespaceName(oid) {
20799
+ if (oid === 11) return "pg_catalog";
20800
+ if (oid === 13212) return "information_schema";
20801
+ for (const schema of this.state.schemas.values()) {
20802
+ if (schema.oid === oid) {
20803
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
20804
+ }
20805
+ }
20806
+ return null;
20807
+ }
20658
20808
  zoneOffsetAt(utcMicros) {
20659
20809
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
20660
20810
  }
@@ -20925,7 +21075,7 @@ function pgClass(ctx) {
20925
21075
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
20926
21076
  for (const i of schemaData.indexes.values()) {
20927
21077
  push(
20928
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21078
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
20929
21079
  i.name,
20930
21080
  i.schema,
20931
21081
  "i",
@@ -21162,7 +21312,15 @@ function pgIndex(ctx) {
21162
21312
  for (const i of schema.indexes.values()) {
21163
21313
  const table = schema.tables.get(i.table);
21164
21314
  const isPrimary = i.isConstraint && table?.constraints.some((c) => c.kind === "primary_key" && c.name === i.name) === true;
21165
- rows.push([table?.oid ?? 0, table?.oid ?? 0, i.columns.length, i.unique, isPrimary, i.where !== null]);
21315
+ rows.push([
21316
+ i.oid ?? table?.oid ?? 0,
21317
+ table?.oid ?? 0,
21318
+ i.columns.length,
21319
+ i.unique,
21320
+ isPrimary,
21321
+ i.where !== null,
21322
+ i.valid !== false
21323
+ ]);
21166
21324
  }
21167
21325
  }
21168
21326
  return rel(
@@ -21172,12 +21330,73 @@ function pgIndex(ctx) {
21172
21330
  ["indnatts", "int2"],
21173
21331
  ["indisunique", "bool"],
21174
21332
  ["indisprimary", "bool"],
21175
- ["indpred", "bool"]
21333
+ ["indpred", "bool"],
21334
+ ["indisvalid", "bool"]
21176
21335
  ],
21177
21336
  rows,
21178
21337
  "pg_index"
21179
21338
  );
21180
21339
  }
21340
+ function pgStatUserTables(ctx) {
21341
+ return rel(
21342
+ [
21343
+ ["relid", "oid"],
21344
+ ["schemaname", "name"],
21345
+ ["relname", "name"],
21346
+ ["n_live_tup", "int8"]
21347
+ ],
21348
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
21349
+ "pg_stat_user_tables"
21350
+ );
21351
+ }
21352
+ function pgDescription(ctx) {
21353
+ const rows = [];
21354
+ for (const schema of ctx.state.schemas.values()) {
21355
+ for (const index of schema.indexes.values()) {
21356
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
21357
+ }
21358
+ }
21359
+ return rel(
21360
+ [
21361
+ ["objoid", "oid"],
21362
+ ["classoid", "oid"],
21363
+ ["objsubid", "int4"],
21364
+ ["description", "text"]
21365
+ ],
21366
+ rows,
21367
+ "pg_description"
21368
+ );
21369
+ }
21370
+ function pgStatUserIndexes(ctx) {
21371
+ const rows = [];
21372
+ for (const schema of ctx.state.schemas.values()) {
21373
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
21374
+ continue;
21375
+ for (const index of schema.indexes.values()) {
21376
+ const table = schema.tables.get(index.table);
21377
+ rows.push([
21378
+ table?.oid ?? 0,
21379
+ index.oid ?? table?.oid ?? 0,
21380
+ schema.name,
21381
+ index.table,
21382
+ index.name,
21383
+ index.scans ?? 0n
21384
+ ]);
21385
+ }
21386
+ }
21387
+ return rel(
21388
+ [
21389
+ ["relid", "oid"],
21390
+ ["indexrelid", "oid"],
21391
+ ["schemaname", "name"],
21392
+ ["relname", "name"],
21393
+ ["indexrelname", "name"],
21394
+ ["idx_scan", "int8"]
21395
+ ],
21396
+ rows,
21397
+ "pg_stat_user_indexes"
21398
+ );
21399
+ }
21181
21400
  function pgSequence(ctx) {
21182
21401
  const rows = [];
21183
21402
  for (const s of allSequences(ctx.state)) {
@@ -21292,7 +21511,7 @@ function pgIndexesView(ctx) {
21292
21511
  const rows = [];
21293
21512
  for (const schema of ctx.state.schemas.values()) {
21294
21513
  for (const i of schema.indexes.values()) {
21295
- rows.push([i.schema, i.table, i.name, null, null]);
21514
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
21296
21515
  }
21297
21516
  }
21298
21517
  return rel(
@@ -21613,6 +21832,12 @@ function buildCatalogRelation(ctx, schema, name) {
21613
21832
  return pgConstraint(ctx);
21614
21833
  case "pg_index":
21615
21834
  return pgIndex(ctx);
21835
+ case "pg_description":
21836
+ return pgDescription(ctx);
21837
+ case "pg_stat_user_tables":
21838
+ return pgStatUserTables(ctx);
21839
+ case "pg_stat_user_indexes":
21840
+ return pgStatUserIndexes(ctx);
21616
21841
  case "pg_sequence":
21617
21842
  return pgSequence(ctx);
21618
21843
  case "pg_sequences":
@@ -22196,6 +22421,7 @@ function executeCreateIndex(env, stmt) {
22196
22421
  }
22197
22422
  assertGinTrgmIndex(state, table, stmt);
22198
22423
  schema.indexes.set(name, {
22424
+ oid: state.nextOid(),
22199
22425
  name,
22200
22426
  schema: schema.name,
22201
22427
  table: table.name,
@@ -22208,7 +22434,8 @@ function executeCreateIndex(env, stmt) {
22208
22434
  })),
22209
22435
  where: stmt.where,
22210
22436
  nullsNotDistinct: stmt.nullsNotDistinct,
22211
- isConstraint: false
22437
+ isConstraint: false,
22438
+ valid: true
22212
22439
  });
22213
22440
  if (stmt.unique) {
22214
22441
  try {
@@ -22216,7 +22443,9 @@ function executeCreateIndex(env, stmt) {
22216
22443
  checkUnique(env, table, table.rowAt(i), i);
22217
22444
  }
22218
22445
  } catch (err) {
22219
- schema.indexes.delete(name);
22446
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
22447
+ else schema.indexes.delete(name);
22448
+ table.indexStores = null;
22220
22449
  throw err;
22221
22450
  }
22222
22451
  }
@@ -23336,6 +23565,9 @@ var Statement = class _Statement {
23336
23565
  const typed = params.map((p, i) => bindValueToTyped(p, i));
23337
23566
  let last = null;
23338
23567
  for (const stmt of this.statements) {
23568
+ if (stmt.type === "create_index" && stmt.concurrently && this.database.transactions.inTransaction) {
23569
+ throw pgError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
23570
+ }
23339
23571
  const env = {
23340
23572
  ctx: new EngineCtx(this.database.state),
23341
23573
  params: typed,
@@ -24362,9 +24594,48 @@ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to
24362
24594
  var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
24363
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;
24364
24596
  var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
24365
- var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
24366
- 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;
24367
- 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;
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
+ };
24368
24639
  var TYPLEN = {
24369
24640
  bool: 1,
24370
24641
  int2: 2,
@@ -24383,7 +24654,7 @@ var TEXT_TYPES2 = /* @__PURE__ */ new Set(["text", "varchar", "bpchar", "name",
24383
24654
  var PG_EPOCH_MS = 9466848e5;
24384
24655
  var pgError2 = (category, message2, code) => new PostgresError(category, message2, code);
24385
24656
  var errorFields = (error) => {
24386
- if (error instanceof PostgresError) {
24657
+ if (isPostgresError(error)) {
24387
24658
  const message2 = error.message;
24388
24659
  const fields = { code: error.sqlState, message: message2 };
24389
24660
  const constraint = /constraint "([^"]+)"/.exec(message2)?.[1];
@@ -24779,8 +25050,8 @@ var Connection = class {
24779
25050
  return outcome;
24780
25051
  } catch (error) {
24781
25052
  if (this.inTransaction) this.aborted = true;
24782
- if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
24783
- const status = error instanceof PostgresError ? error.sqlState : "XX000";
25053
+ if (isPostgresError(error) && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
25054
+ const status = isPostgresError(error) ? error.sqlState : "XX000";
24784
25055
  this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
24785
25056
  throw error;
24786
25057
  }
@@ -24883,7 +25154,11 @@ var Connection = class {
24883
25154
  const result = this.cluster.as(this, () => this.runQuery(db, text, params));
24884
25155
  return { empty: false, result, tag: commandTag(result) };
24885
25156
  };
24886
- const outcome = db.transaction(run);
25157
+ const concurrentIndex = /\bconcurrently\b/i.test(text) && parse(text).some((stmt) => stmt.type === "create_index" && stmt.concurrently);
25158
+ if (concurrentIndex && this.transaction !== null) {
25159
+ throw new PostgresError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
25160
+ }
25161
+ const outcome = concurrentIndex ? run() : db.transaction(run);
24887
25162
  if (this.transaction && mutatesDatabase(text)) {
24888
25163
  this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
24889
25164
  }
@@ -24972,14 +25247,48 @@ var Connection = class {
24972
25247
  }
24973
25248
  });
24974
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
+ }
24975
25286
  runQuery(db, text, params) {
24976
25287
  const lock = ROW_LOCK.exec(text);
24977
25288
  if (!lock) {
24978
- const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
24979
- if (mutation) {
24980
- const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
24981
- this.lockRows(db, lockQuery, params, "wait");
24982
- }
25289
+ const lockQuery = lockedMutationQuery(text);
25290
+ if (lockQuery) this.lockRows(db, lockQuery, params, "wait");
25291
+ this.lockInsertedPrimaryKey(db, text, params);
24983
25292
  return db.prepare(text).textResult(...params);
24984
25293
  }
24985
25294
  let query = text.slice(0, lock.index).trimEnd();