@crvouga/mockingbird-service-postgres 2.2.0 → 3.0.0

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
@@ -1218,6 +1218,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
1218
1218
  "pg_enum",
1219
1219
  "pg_constraint",
1220
1220
  "pg_index",
1221
+ "pg_description",
1222
+ "pg_stat_user_tables",
1223
+ "pg_stat_user_indexes",
1221
1224
  "pg_sequence",
1222
1225
  "pg_sequences",
1223
1226
  "pg_tables",
@@ -3495,6 +3498,7 @@ var TYPE_ALIASES = {
3495
3498
  regclass: "regclass",
3496
3499
  regtype: "regtype",
3497
3500
  regproc: "regproc",
3501
+ regnamespace: "regnamespace",
3498
3502
  oid: "oid",
3499
3503
  void: "void",
3500
3504
  record: "record",
@@ -3588,6 +3592,7 @@ var TYPE_OIDS = {
3588
3592
  numeric: 1700,
3589
3593
  regclass: 2205,
3590
3594
  regtype: 2206,
3595
+ regnamespace: 4089,
3591
3596
  record: 2249,
3592
3597
  uuid: 2950,
3593
3598
  jsonb: 3802,
@@ -3888,6 +3893,7 @@ function recordText(rec, ctx) {
3888
3893
  return `(${parts.join(",")})`;
3889
3894
  }
3890
3895
  function datumText(t, v, ctx) {
3896
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
3891
3897
  if (v === null) throw pgError("internal", "datumText called with null");
3892
3898
  if (isArrayType(t)) return arrayText(v, ctx);
3893
3899
  if (isEnumType(t)) return v;
@@ -3961,6 +3967,11 @@ function datumText(t, v, ctx) {
3961
3967
  }
3962
3968
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
3963
3969
  function datumFromText(t, text, ctx) {
3970
+ if (t === "regnamespace") {
3971
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
3972
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
3973
+ return ctx.namespaceOid(text);
3974
+ }
3964
3975
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
3965
3976
  if (isEnumType(t)) {
3966
3977
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -4817,7 +4828,11 @@ function castTo(env, value, target, opts = {}) {
4817
4828
  );
4818
4829
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
4819
4830
  }
4820
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
4831
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
4832
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
4833
+ return tv(target, target === "int8" ? BigInt(v) : v);
4834
+ }
4835
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
4821
4836
  return tv(target, Number(v));
4822
4837
  }
4823
4838
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -5765,6 +5780,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
5765
5780
  case "int2":
5766
5781
  case "int4":
5767
5782
  case "oid":
5783
+ case "regnamespace":
5768
5784
  case "date":
5769
5785
  return expectNumber(a, t) - expectNumber(b, t);
5770
5786
  case "float4":
@@ -7920,10 +7936,99 @@ function exactDegTan(x) {
7920
7936
  }
7921
7937
 
7922
7938
  // src/sql/deparse.ts
7939
+ function deparseSelect(stmt) {
7940
+ return deparseBody(stmt.body);
7941
+ }
7942
+ function deparseBody(body) {
7943
+ switch (body.type) {
7944
+ case "select_core":
7945
+ return deparseSelectCore(body);
7946
+ case "setop":
7947
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
7948
+ case "values":
7949
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
7950
+ }
7951
+ }
7952
+ function deparseSelectCore(body) {
7953
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
7954
+ const expr = deparseExpr(t.expr);
7955
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
7956
+ }).join(", ");
7957
+ let sql = `SELECT ${targets}`;
7958
+ if (body.from.length > 0) {
7959
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
7960
+ }
7961
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
7962
+ if (body.groupBy && body.groupBy.length > 0) {
7963
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
7964
+ }
7965
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
7966
+ return sql;
7967
+ }
7968
+ function deparseFrom(item) {
7969
+ if (item.type === "from_table") {
7970
+ const name = item.name.join(".");
7971
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
7972
+ }
7973
+ return "(subquery)";
7974
+ }
7923
7975
  function quoteIdent(name) {
7924
7976
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7925
7977
  return `"${name.replaceAll('"', '""')}"`;
7926
7978
  }
7979
+ function deparseExpr(e) {
7980
+ switch (e.type) {
7981
+ case "null_lit":
7982
+ return "NULL";
7983
+ case "string_lit":
7984
+ return `'${e.value.replaceAll("'", "''")}'`;
7985
+ case "number_lit":
7986
+ return e.raw;
7987
+ case "bool_lit":
7988
+ return e.value ? "true" : "false";
7989
+ case "colref":
7990
+ return e.parts.join(".");
7991
+ case "star":
7992
+ return e.table ? `${e.table.join(".")}.*` : "*";
7993
+ case "binop":
7994
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
7995
+ case "unop":
7996
+ return `${e.op}${deparseExpr(e.operand)}`;
7997
+ case "cast":
7998
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
7999
+ case "func":
8000
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
8001
+ case "case": {
8002
+ let out = "CASE";
8003
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
8004
+ for (const arm of e.whens) {
8005
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
8006
+ }
8007
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
8008
+ return `${out} END`;
8009
+ }
8010
+ case "subquery_expr":
8011
+ return `(${deparseSelect(e.query)})`;
8012
+ default:
8013
+ return "?";
8014
+ }
8015
+ }
8016
+
8017
+ // src/sql/deparse-index.ts
8018
+ function deparseIndexColumn(column) {
8019
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
8020
+ if (column.dir === "desc") result += " DESC";
8021
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
8022
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
8023
+ return result;
8024
+ }
8025
+ function deparseIndex(index) {
8026
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
8027
+ const unique = index.unique ? "UNIQUE " : "";
8028
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
8029
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
8030
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
8031
+ }
7927
8032
 
7928
8033
  // src/runtime/database-context.ts
7929
8034
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -8017,6 +8122,22 @@ function sequenceNextval(ctx, seq) {
8017
8122
  }
8018
8123
  function getMiscFunctions() {
8019
8124
  const m = /* @__PURE__ */ new Map();
8125
+ m.set(
8126
+ "pg_get_indexdef",
8127
+ strict("text", (ctx, args2) => {
8128
+ const oid = argInt(ctx, args2[0]);
8129
+ const column = args2[1] ? argInt(ctx, args2[1]) : 0;
8130
+ for (const schema of ctx.state.schemas.values()) {
8131
+ for (const index of schema.indexes.values()) {
8132
+ if (index.oid !== oid) continue;
8133
+ if (column === 0) return tv("text", deparseIndex(index));
8134
+ const part = index.columns[column - 1];
8135
+ return tv("text", part ? deparseIndexColumn(part) : "");
8136
+ }
8137
+ }
8138
+ return tv("text", null);
8139
+ })
8140
+ );
8020
8141
  m.set("version", () => tv("text", PG_VERSION_TEXT));
8021
8142
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
8022
8143
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -13237,6 +13358,9 @@ function tryIndexedTableRows(env, table, alias, where) {
13237
13358
  const key = uniqueKeyOf(env, table, spec, probeRow);
13238
13359
  if (key === null) return [];
13239
13360
  const hits = indexStoreFor(env, table, spec).lookup(key);
13361
+ const schema = env.ctx.state.schemas.get(table.schema);
13362
+ const index = schema?.indexes.get(spec.name);
13363
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
13240
13364
  return hits.map((i) => table.rowAt(i));
13241
13365
  }
13242
13366
  function tryIndexedFromItem(env, item, where) {
@@ -15999,8 +16123,8 @@ var Parser = class {
15999
16123
  this.eatKw("column");
16000
16124
  const column = this.ident();
16001
16125
  let alterType = this.eatKw("type");
16002
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
16003
- this.pos += 1;
16126
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
16127
+ this.pos += 2;
16004
16128
  this.expectKw("type");
16005
16129
  alterType = true;
16006
16130
  }
@@ -20352,7 +20476,7 @@ function uniqueSpecsFor(env, table) {
20352
20476
  const schema = env.ctx.state.schemas.get(table.schema);
20353
20477
  if (schema) {
20354
20478
  for (const idx of schema.indexes.values()) {
20355
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
20479
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
20356
20480
  specs.push({
20357
20481
  name: idx.name,
20358
20482
  keys: idx.columns.map((c) => ({
@@ -21503,6 +21627,25 @@ var EngineCtx = class {
21503
21627
  timezone() {
21504
21628
  return this.state.getSetting("timezone") ?? "UTC";
21505
21629
  }
21630
+ namespaceOid(input) {
21631
+ const trimmed = input.trim();
21632
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
21633
+ if (name === "pg_catalog") return 11;
21634
+ if (name === "information_schema") return 13212;
21635
+ const schema = this.state.schemas.get(name);
21636
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
21637
+ return schema.oid;
21638
+ }
21639
+ namespaceName(oid) {
21640
+ if (oid === 11) return "pg_catalog";
21641
+ if (oid === 13212) return "information_schema";
21642
+ for (const schema of this.state.schemas.values()) {
21643
+ if (schema.oid === oid) {
21644
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
21645
+ }
21646
+ }
21647
+ return null;
21648
+ }
21506
21649
  zoneOffsetAt(utcMicros) {
21507
21650
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
21508
21651
  }
@@ -21773,7 +21916,7 @@ function pgClass(ctx) {
21773
21916
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
21774
21917
  for (const i of schemaData.indexes.values()) {
21775
21918
  push(
21776
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21919
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21777
21920
  i.name,
21778
21921
  i.schema,
21779
21922
  "i",
@@ -22010,7 +22153,15 @@ function pgIndex(ctx) {
22010
22153
  for (const i of schema.indexes.values()) {
22011
22154
  const table = schema.tables.get(i.table);
22012
22155
  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]);
22156
+ rows.push([
22157
+ i.oid ?? table?.oid ?? 0,
22158
+ table?.oid ?? 0,
22159
+ i.columns.length,
22160
+ i.unique,
22161
+ isPrimary,
22162
+ i.where !== null,
22163
+ i.valid !== false
22164
+ ]);
22014
22165
  }
22015
22166
  }
22016
22167
  return rel(
@@ -22020,12 +22171,73 @@ function pgIndex(ctx) {
22020
22171
  ["indnatts", "int2"],
22021
22172
  ["indisunique", "bool"],
22022
22173
  ["indisprimary", "bool"],
22023
- ["indpred", "bool"]
22174
+ ["indpred", "bool"],
22175
+ ["indisvalid", "bool"]
22024
22176
  ],
22025
22177
  rows,
22026
22178
  "pg_index"
22027
22179
  );
22028
22180
  }
22181
+ function pgStatUserTables(ctx) {
22182
+ return rel(
22183
+ [
22184
+ ["relid", "oid"],
22185
+ ["schemaname", "name"],
22186
+ ["relname", "name"],
22187
+ ["n_live_tup", "int8"]
22188
+ ],
22189
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
22190
+ "pg_stat_user_tables"
22191
+ );
22192
+ }
22193
+ function pgDescription(ctx) {
22194
+ const rows = [];
22195
+ for (const schema of ctx.state.schemas.values()) {
22196
+ for (const index of schema.indexes.values()) {
22197
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
22198
+ }
22199
+ }
22200
+ return rel(
22201
+ [
22202
+ ["objoid", "oid"],
22203
+ ["classoid", "oid"],
22204
+ ["objsubid", "int4"],
22205
+ ["description", "text"]
22206
+ ],
22207
+ rows,
22208
+ "pg_description"
22209
+ );
22210
+ }
22211
+ function pgStatUserIndexes(ctx) {
22212
+ const rows = [];
22213
+ for (const schema of ctx.state.schemas.values()) {
22214
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
22215
+ continue;
22216
+ for (const index of schema.indexes.values()) {
22217
+ const table = schema.tables.get(index.table);
22218
+ rows.push([
22219
+ table?.oid ?? 0,
22220
+ index.oid ?? table?.oid ?? 0,
22221
+ schema.name,
22222
+ index.table,
22223
+ index.name,
22224
+ index.scans ?? 0n
22225
+ ]);
22226
+ }
22227
+ }
22228
+ return rel(
22229
+ [
22230
+ ["relid", "oid"],
22231
+ ["indexrelid", "oid"],
22232
+ ["schemaname", "name"],
22233
+ ["relname", "name"],
22234
+ ["indexrelname", "name"],
22235
+ ["idx_scan", "int8"]
22236
+ ],
22237
+ rows,
22238
+ "pg_stat_user_indexes"
22239
+ );
22240
+ }
22029
22241
  function pgSequence(ctx) {
22030
22242
  const rows = [];
22031
22243
  for (const s of allSequences(ctx.state)) {
@@ -22140,7 +22352,7 @@ function pgIndexesView(ctx) {
22140
22352
  const rows = [];
22141
22353
  for (const schema of ctx.state.schemas.values()) {
22142
22354
  for (const i of schema.indexes.values()) {
22143
- rows.push([i.schema, i.table, i.name, null, null]);
22355
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
22144
22356
  }
22145
22357
  }
22146
22358
  return rel(
@@ -22461,6 +22673,12 @@ function buildCatalogRelation(ctx, schema, name) {
22461
22673
  return pgConstraint(ctx);
22462
22674
  case "pg_index":
22463
22675
  return pgIndex(ctx);
22676
+ case "pg_description":
22677
+ return pgDescription(ctx);
22678
+ case "pg_stat_user_tables":
22679
+ return pgStatUserTables(ctx);
22680
+ case "pg_stat_user_indexes":
22681
+ return pgStatUserIndexes(ctx);
22464
22682
  case "pg_sequence":
22465
22683
  return pgSequence(ctx);
22466
22684
  case "pg_sequences":
@@ -23044,6 +23262,7 @@ function executeCreateIndex(env, stmt) {
23044
23262
  }
23045
23263
  assertGinTrgmIndex(state, table, stmt);
23046
23264
  schema.indexes.set(name, {
23265
+ oid: state.nextOid(),
23047
23266
  name,
23048
23267
  schema: schema.name,
23049
23268
  table: table.name,
@@ -23056,7 +23275,8 @@ function executeCreateIndex(env, stmt) {
23056
23275
  })),
23057
23276
  where: stmt.where,
23058
23277
  nullsNotDistinct: stmt.nullsNotDistinct,
23059
- isConstraint: false
23278
+ isConstraint: false,
23279
+ valid: true
23060
23280
  });
23061
23281
  if (stmt.unique) {
23062
23282
  try {
@@ -23064,7 +23284,9 @@ function executeCreateIndex(env, stmt) {
23064
23284
  checkUnique(env, table, table.rowAt(i), i);
23065
23285
  }
23066
23286
  } catch (err) {
23067
- schema.indexes.delete(name);
23287
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
23288
+ else schema.indexes.delete(name);
23289
+ table.indexStores = null;
23068
23290
  throw err;
23069
23291
  }
23070
23292
  }
@@ -24184,6 +24406,9 @@ var Statement = class _Statement {
24184
24406
  const typed = params.map((p, i) => bindValueToTyped(p, i));
24185
24407
  let last = null;
24186
24408
  for (const stmt of this.statements) {
24409
+ if (stmt.type === "create_index" && stmt.concurrently && this.database.transactions.inTransaction) {
24410
+ throw pgError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
24411
+ }
24187
24412
  const env = {
24188
24413
  ctx: new EngineCtx(this.database.state),
24189
24414
  params: typed,
@@ -25731,7 +25956,11 @@ var Connection = class {
25731
25956
  const result = this.cluster.as(this, () => this.runQuery(db, text, params));
25732
25957
  return { empty: false, result, tag: commandTag(result) };
25733
25958
  };
25734
- const outcome = db.transaction(run);
25959
+ const concurrentIndex = /\bconcurrently\b/i.test(text) && parse(text).some((stmt) => stmt.type === "create_index" && stmt.concurrently);
25960
+ if (concurrentIndex && this.transaction !== null) {
25961
+ throw new PostgresError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
25962
+ }
25963
+ const outcome = concurrentIndex ? run() : db.transaction(run);
25735
25964
  if (this.transaction && mutatesDatabase(text)) {
25736
25965
  this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
25737
25966
  }