@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.
@@ -0,0 +1,3 @@
1
+ import type { IndexMeta } from "../storage/database-state.js";
2
+ export declare function deparseIndexColumn(column: IndexMeta["columns"][number]): string;
3
+ export declare function deparseIndex(index: IndexMeta): string;
@@ -1,4 +1,5 @@
1
- import type { SelectStmt } from "../ast/nodes.js";
1
+ import type { Expr, SelectStmt } from "../ast/nodes.js";
2
2
  /** Minimal SELECT deparsing for pg_get_viewdef / information_schema. */
3
3
  export declare function deparseSelect(stmt: SelectStmt): string;
4
4
  export declare function quoteIdent(name: string): string;
5
+ export declare function deparseExpr(e: Expr): string;
@@ -42,10 +42,14 @@ export type ConstraintMeta = {
42
42
  match: "full" | "simple";
43
43
  };
44
44
  export interface IndexMeta {
45
+ oid?: number;
45
46
  name: string;
46
47
  schema: string;
47
48
  table: string;
48
49
  unique: boolean;
50
+ comment?: string | null;
51
+ valid?: boolean;
52
+ scans?: bigint;
49
53
  /** simple column indexes store names; expression indexes store the AST */
50
54
  columns: Array<{
51
55
  column: string | null;
@@ -80,6 +80,7 @@ export declare function parseUuidText(text: string): string;
80
80
  export declare function parseMoneyText(text: string): bigint;
81
81
  export declare function moneyText(cents: bigint): string;
82
82
  export interface OutputCtx {
83
+ namespaceName?(oid: number): string | null;
83
84
  /** offset seconds east of UTC for rendering timestamptz at given UTC micros */
84
85
  zoneOffsetAt(utcMicros: bigint): number;
85
86
  }
@@ -89,6 +90,7 @@ export declare function recordText(rec: PgRecord, ctx: OutputCtx): string;
89
90
  /** Render a non-null datum of type `t` to PG wire text. */
90
91
  export declare function datumText(t: TypeId, v: Datum, ctx: OutputCtx): string;
91
92
  export interface InputCtx {
93
+ namespaceOid?(name: string): number;
92
94
  /** session zone offset lookup for naive timestamptz input */
93
95
  zoneOffsetForNaive(naiveMicros: bigint): number;
94
96
  /** enum label validation: returns true when label is valid for the enum type */
package/dist/unstable.js CHANGED
@@ -2351,6 +2351,7 @@ var TYPE_ALIASES = {
2351
2351
  regclass: "regclass",
2352
2352
  regtype: "regtype",
2353
2353
  regproc: "regproc",
2354
+ regnamespace: "regnamespace",
2354
2355
  oid: "oid",
2355
2356
  void: "void",
2356
2357
  record: "record",
@@ -2444,6 +2445,7 @@ var TYPE_OIDS = {
2444
2445
  numeric: 1700,
2445
2446
  regclass: 2205,
2446
2447
  regtype: 2206,
2448
+ regnamespace: 4089,
2447
2449
  record: 2249,
2448
2450
  uuid: 2950,
2449
2451
  jsonb: 3802,
@@ -2744,6 +2746,7 @@ function recordText(rec, ctx) {
2744
2746
  return `(${parts.join(",")})`;
2745
2747
  }
2746
2748
  function datumText(t, v, ctx) {
2749
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
2747
2750
  if (v === null) throw pgError("internal", "datumText called with null");
2748
2751
  if (isArrayType(t)) return arrayText(v, ctx);
2749
2752
  if (isEnumType(t)) return v;
@@ -2817,6 +2820,11 @@ function datumText(t, v, ctx) {
2817
2820
  }
2818
2821
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
2819
2822
  function datumFromText(t, text, ctx) {
2823
+ if (t === "regnamespace") {
2824
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
2825
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
2826
+ return ctx.namespaceOid(text);
2827
+ }
2820
2828
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
2821
2829
  if (isEnumType(t)) {
2822
2830
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -3137,7 +3145,11 @@ function castTo(env, value, target, opts = {}) {
3137
3145
  );
3138
3146
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
3139
3147
  }
3140
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
3148
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
3149
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
3150
+ return tv(target, target === "int8" ? BigInt(v) : v);
3151
+ }
3152
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
3141
3153
  return tv(target, Number(v));
3142
3154
  }
3143
3155
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -3435,6 +3447,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
3435
3447
  case "int2":
3436
3448
  case "int4":
3437
3449
  case "oid":
3450
+ case "regnamespace":
3438
3451
  case "date":
3439
3452
  return expectNumber(a, t) - expectNumber(b, t);
3440
3453
  case "float4":
@@ -6525,10 +6538,99 @@ function exactDegTan(x) {
6525
6538
  }
6526
6539
 
6527
6540
  // src/sql/deparse.ts
6541
+ function deparseSelect(stmt) {
6542
+ return deparseBody(stmt.body);
6543
+ }
6544
+ function deparseBody(body) {
6545
+ switch (body.type) {
6546
+ case "select_core":
6547
+ return deparseSelectCore(body);
6548
+ case "setop":
6549
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
6550
+ case "values":
6551
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
6552
+ }
6553
+ }
6554
+ function deparseSelectCore(body) {
6555
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
6556
+ const expr = deparseExpr(t.expr);
6557
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
6558
+ }).join(", ");
6559
+ let sql = `SELECT ${targets}`;
6560
+ if (body.from.length > 0) {
6561
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
6562
+ }
6563
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
6564
+ if (body.groupBy && body.groupBy.length > 0) {
6565
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
6566
+ }
6567
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
6568
+ return sql;
6569
+ }
6570
+ function deparseFrom(item) {
6571
+ if (item.type === "from_table") {
6572
+ const name = item.name.join(".");
6573
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
6574
+ }
6575
+ return "(subquery)";
6576
+ }
6528
6577
  function quoteIdent(name) {
6529
6578
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
6530
6579
  return `"${name.replaceAll('"', '""')}"`;
6531
6580
  }
6581
+ function deparseExpr(e) {
6582
+ switch (e.type) {
6583
+ case "null_lit":
6584
+ return "NULL";
6585
+ case "string_lit":
6586
+ return `'${e.value.replaceAll("'", "''")}'`;
6587
+ case "number_lit":
6588
+ return e.raw;
6589
+ case "bool_lit":
6590
+ return e.value ? "true" : "false";
6591
+ case "colref":
6592
+ return e.parts.join(".");
6593
+ case "star":
6594
+ return e.table ? `${e.table.join(".")}.*` : "*";
6595
+ case "binop":
6596
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
6597
+ case "unop":
6598
+ return `${e.op}${deparseExpr(e.operand)}`;
6599
+ case "cast":
6600
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
6601
+ case "func":
6602
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
6603
+ case "case": {
6604
+ let out = "CASE";
6605
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
6606
+ for (const arm of e.whens) {
6607
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
6608
+ }
6609
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
6610
+ return `${out} END`;
6611
+ }
6612
+ case "subquery_expr":
6613
+ return `(${deparseSelect(e.query)})`;
6614
+ default:
6615
+ return "?";
6616
+ }
6617
+ }
6618
+
6619
+ // src/sql/deparse-index.ts
6620
+ function deparseIndexColumn(column) {
6621
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
6622
+ if (column.dir === "desc") result += " DESC";
6623
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
6624
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
6625
+ return result;
6626
+ }
6627
+ function deparseIndex(index) {
6628
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
6629
+ const unique = index.unique ? "UNIQUE " : "";
6630
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
6631
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
6632
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
6633
+ }
6532
6634
 
6533
6635
  // src/runtime/database-context.ts
6534
6636
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -6619,6 +6721,22 @@ function sequenceNextval(ctx, seq) {
6619
6721
  }
6620
6722
  function getMiscFunctions() {
6621
6723
  const m = /* @__PURE__ */ new Map();
6724
+ m.set(
6725
+ "pg_get_indexdef",
6726
+ strict("text", (ctx, args) => {
6727
+ const oid = argInt(ctx, args[0]);
6728
+ const column = args[1] ? argInt(ctx, args[1]) : 0;
6729
+ for (const schema of ctx.state.schemas.values()) {
6730
+ for (const index of schema.indexes.values()) {
6731
+ if (index.oid !== oid) continue;
6732
+ if (column === 0) return tv("text", deparseIndex(index));
6733
+ const part = index.columns[column - 1];
6734
+ return tv("text", part ? deparseIndexColumn(part) : "");
6735
+ }
6736
+ }
6737
+ return tv("text", null);
6738
+ })
6739
+ );
6622
6740
  m.set("version", () => tv("text", PG_VERSION_TEXT));
6623
6741
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
6624
6742
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -8483,6 +8601,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
8483
8601
  "pg_enum",
8484
8602
  "pg_constraint",
8485
8603
  "pg_index",
8604
+ "pg_description",
8605
+ "pg_stat_user_tables",
8606
+ "pg_stat_user_indexes",
8486
8607
  "pg_sequence",
8487
8608
  "pg_sequences",
8488
8609
  "pg_tables",
@@ -8605,7 +8726,7 @@ function pgClass(ctx) {
8605
8726
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
8606
8727
  for (const i of schemaData.indexes.values()) {
8607
8728
  push(
8608
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
8729
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
8609
8730
  i.name,
8610
8731
  i.schema,
8611
8732
  "i",
@@ -8842,7 +8963,15 @@ function pgIndex(ctx) {
8842
8963
  for (const i of schema.indexes.values()) {
8843
8964
  const table = schema.tables.get(i.table);
8844
8965
  const isPrimary = i.isConstraint && table?.constraints.some((c) => c.kind === "primary_key" && c.name === i.name) === true;
8845
- rows.push([table?.oid ?? 0, table?.oid ?? 0, i.columns.length, i.unique, isPrimary, i.where !== null]);
8966
+ rows.push([
8967
+ i.oid ?? table?.oid ?? 0,
8968
+ table?.oid ?? 0,
8969
+ i.columns.length,
8970
+ i.unique,
8971
+ isPrimary,
8972
+ i.where !== null,
8973
+ i.valid !== false
8974
+ ]);
8846
8975
  }
8847
8976
  }
8848
8977
  return rel(
@@ -8852,12 +8981,73 @@ function pgIndex(ctx) {
8852
8981
  ["indnatts", "int2"],
8853
8982
  ["indisunique", "bool"],
8854
8983
  ["indisprimary", "bool"],
8855
- ["indpred", "bool"]
8984
+ ["indpred", "bool"],
8985
+ ["indisvalid", "bool"]
8856
8986
  ],
8857
8987
  rows,
8858
8988
  "pg_index"
8859
8989
  );
8860
8990
  }
8991
+ function pgStatUserTables(ctx) {
8992
+ return rel(
8993
+ [
8994
+ ["relid", "oid"],
8995
+ ["schemaname", "name"],
8996
+ ["relname", "name"],
8997
+ ["n_live_tup", "int8"]
8998
+ ],
8999
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
9000
+ "pg_stat_user_tables"
9001
+ );
9002
+ }
9003
+ function pgDescription(ctx) {
9004
+ const rows = [];
9005
+ for (const schema of ctx.state.schemas.values()) {
9006
+ for (const index of schema.indexes.values()) {
9007
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
9008
+ }
9009
+ }
9010
+ return rel(
9011
+ [
9012
+ ["objoid", "oid"],
9013
+ ["classoid", "oid"],
9014
+ ["objsubid", "int4"],
9015
+ ["description", "text"]
9016
+ ],
9017
+ rows,
9018
+ "pg_description"
9019
+ );
9020
+ }
9021
+ function pgStatUserIndexes(ctx) {
9022
+ const rows = [];
9023
+ for (const schema of ctx.state.schemas.values()) {
9024
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
9025
+ continue;
9026
+ for (const index of schema.indexes.values()) {
9027
+ const table = schema.tables.get(index.table);
9028
+ rows.push([
9029
+ table?.oid ?? 0,
9030
+ index.oid ?? table?.oid ?? 0,
9031
+ schema.name,
9032
+ index.table,
9033
+ index.name,
9034
+ index.scans ?? 0n
9035
+ ]);
9036
+ }
9037
+ }
9038
+ return rel(
9039
+ [
9040
+ ["relid", "oid"],
9041
+ ["indexrelid", "oid"],
9042
+ ["schemaname", "name"],
9043
+ ["relname", "name"],
9044
+ ["indexrelname", "name"],
9045
+ ["idx_scan", "int8"]
9046
+ ],
9047
+ rows,
9048
+ "pg_stat_user_indexes"
9049
+ );
9050
+ }
8861
9051
  function pgSequence(ctx) {
8862
9052
  const rows = [];
8863
9053
  for (const s of allSequences(ctx.state)) {
@@ -8972,7 +9162,7 @@ function pgIndexesView(ctx) {
8972
9162
  const rows = [];
8973
9163
  for (const schema of ctx.state.schemas.values()) {
8974
9164
  for (const i of schema.indexes.values()) {
8975
- rows.push([i.schema, i.table, i.name, null, null]);
9165
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
8976
9166
  }
8977
9167
  }
8978
9168
  return rel(
@@ -9293,6 +9483,12 @@ function buildCatalogRelation(ctx, schema, name) {
9293
9483
  return pgConstraint(ctx);
9294
9484
  case "pg_index":
9295
9485
  return pgIndex(ctx);
9486
+ case "pg_description":
9487
+ return pgDescription(ctx);
9488
+ case "pg_stat_user_tables":
9489
+ return pgStatUserTables(ctx);
9490
+ case "pg_stat_user_indexes":
9491
+ return pgStatUserIndexes(ctx);
9296
9492
  case "pg_sequence":
9297
9493
  return pgSequence(ctx);
9298
9494
  case "pg_sequences":
@@ -12068,8 +12264,8 @@ var Parser = class {
12068
12264
  this.eatKw("column");
12069
12265
  const column = this.ident();
12070
12266
  let alterType = this.eatKw("type");
12071
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
12072
- this.pos += 1;
12267
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
12268
+ this.pos += 2;
12073
12269
  this.expectKw("type");
12074
12270
  alterType = true;
12075
12271
  }
@@ -16470,7 +16666,7 @@ function uniqueSpecsFor(env, table) {
16470
16666
  const schema = env.ctx.state.schemas.get(table.schema);
16471
16667
  if (schema) {
16472
16668
  for (const idx of schema.indexes.values()) {
16473
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
16669
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
16474
16670
  specs.push({
16475
16671
  name: idx.name,
16476
16672
  keys: idx.columns.map((c) => ({
@@ -16652,6 +16848,9 @@ function tryIndexedTableRows(env, table, alias, where) {
16652
16848
  const key = uniqueKeyOf(env, table, spec, probeRow);
16653
16849
  if (key === null) return [];
16654
16850
  const hits = indexStoreFor(env, table, spec).lookup(key);
16851
+ const schema = env.ctx.state.schemas.get(table.schema);
16852
+ const index = schema?.indexes.get(spec.name);
16853
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
16655
16854
  return hits.map((i) => table.rowAt(i));
16656
16855
  }
16657
16856
  function tryIndexedFromItem(env, item, where) {
@@ -19836,6 +20035,25 @@ var EngineCtx = class {
19836
20035
  timezone() {
19837
20036
  return this.state.getSetting("timezone") ?? "UTC";
19838
20037
  }
20038
+ namespaceOid(input) {
20039
+ const trimmed = input.trim();
20040
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
20041
+ if (name === "pg_catalog") return 11;
20042
+ if (name === "information_schema") return 13212;
20043
+ const schema = this.state.schemas.get(name);
20044
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
20045
+ return schema.oid;
20046
+ }
20047
+ namespaceName(oid) {
20048
+ if (oid === 11) return "pg_catalog";
20049
+ if (oid === 13212) return "information_schema";
20050
+ for (const schema of this.state.schemas.values()) {
20051
+ if (schema.oid === oid) {
20052
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
20053
+ }
20054
+ }
20055
+ return null;
20056
+ }
19839
20057
  zoneOffsetAt(utcMicros) {
19840
20058
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
19841
20059
  }
@@ -20730,6 +20948,7 @@ function executeCreateIndex(env, stmt) {
20730
20948
  }
20731
20949
  assertGinTrgmIndex(state, table, stmt);
20732
20950
  schema.indexes.set(name, {
20951
+ oid: state.nextOid(),
20733
20952
  name,
20734
20953
  schema: schema.name,
20735
20954
  table: table.name,
@@ -20742,7 +20961,8 @@ function executeCreateIndex(env, stmt) {
20742
20961
  })),
20743
20962
  where: stmt.where,
20744
20963
  nullsNotDistinct: stmt.nullsNotDistinct,
20745
- isConstraint: false
20964
+ isConstraint: false,
20965
+ valid: true
20746
20966
  });
20747
20967
  if (stmt.unique) {
20748
20968
  try {
@@ -20750,7 +20970,9 @@ function executeCreateIndex(env, stmt) {
20750
20970
  checkUnique(env, table, table.rowAt(i), i);
20751
20971
  }
20752
20972
  } catch (err) {
20753
- schema.indexes.delete(name);
20973
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
20974
+ else schema.indexes.delete(name);
20975
+ table.indexStores = null;
20754
20976
  throw err;
20755
20977
  }
20756
20978
  }