@crvouga/mockingbird-service-postgres 2.3.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.
@@ -0,0 +1,3 @@
1
+ import type { CommentStmt } from "../ast/nodes.js";
2
+ import { type ExecEnv, type ExecResult } from "./relation.js";
3
+ export declare function executeComment(env: ExecEnv, stmt: CommentStmt): ExecResult;
@@ -14,6 +14,8 @@ export declare class EngineCtx implements CastEnv, CompareCtx {
14
14
  readonly stmtNow: bigint;
15
15
  constructor(state: DatabaseState, txNow?: bigint);
16
16
  timezone(): string;
17
+ namespaceOid(input: string): number;
18
+ namespaceName(oid: number): string | null;
17
19
  zoneOffsetAt(utcMicros: bigint): number;
18
20
  zoneOffsetForNaive(naiveMicros: bigint): number;
19
21
  enumLabels(enumType: TypeId): string[] | null;
package/dist/index.js CHANGED
@@ -367,6 +367,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
367
367
  "pg_enum",
368
368
  "pg_constraint",
369
369
  "pg_index",
370
+ "pg_description",
371
+ "pg_stat_user_tables",
372
+ "pg_stat_user_indexes",
370
373
  "pg_sequence",
371
374
  "pg_sequences",
372
375
  "pg_tables",
@@ -2644,6 +2647,7 @@ var TYPE_ALIASES = {
2644
2647
  regclass: "regclass",
2645
2648
  regtype: "regtype",
2646
2649
  regproc: "regproc",
2650
+ regnamespace: "regnamespace",
2647
2651
  oid: "oid",
2648
2652
  void: "void",
2649
2653
  record: "record",
@@ -2737,6 +2741,7 @@ var TYPE_OIDS = {
2737
2741
  numeric: 1700,
2738
2742
  regclass: 2205,
2739
2743
  regtype: 2206,
2744
+ regnamespace: 4089,
2740
2745
  record: 2249,
2741
2746
  uuid: 2950,
2742
2747
  jsonb: 3802,
@@ -3037,6 +3042,7 @@ function recordText(rec, ctx) {
3037
3042
  return `(${parts.join(",")})`;
3038
3043
  }
3039
3044
  function datumText(t, v, ctx) {
3045
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
3040
3046
  if (v === null) throw pgError("internal", "datumText called with null");
3041
3047
  if (isArrayType(t)) return arrayText(v, ctx);
3042
3048
  if (isEnumType(t)) return v;
@@ -3110,6 +3116,11 @@ function datumText(t, v, ctx) {
3110
3116
  }
3111
3117
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
3112
3118
  function datumFromText(t, text, ctx) {
3119
+ if (t === "regnamespace") {
3120
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
3121
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
3122
+ return ctx.namespaceOid(text);
3123
+ }
3113
3124
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
3114
3125
  if (isEnumType(t)) {
3115
3126
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -3966,7 +3977,11 @@ function castTo(env, value, target, opts = {}) {
3966
3977
  );
3967
3978
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
3968
3979
  }
3969
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
3980
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
3981
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
3982
+ return tv(target, target === "int8" ? BigInt(v) : v);
3983
+ }
3984
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
3970
3985
  return tv(target, Number(v));
3971
3986
  }
3972
3987
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -4914,6 +4929,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
4914
4929
  case "int2":
4915
4930
  case "int4":
4916
4931
  case "oid":
4932
+ case "regnamespace":
4917
4933
  case "date":
4918
4934
  return expectNumber(a, t) - expectNumber(b, t);
4919
4935
  case "float4":
@@ -7069,10 +7085,99 @@ function exactDegTan(x) {
7069
7085
  }
7070
7086
 
7071
7087
  // src/sql/deparse.ts
7088
+ function deparseSelect(stmt) {
7089
+ return deparseBody(stmt.body);
7090
+ }
7091
+ function deparseBody(body) {
7092
+ switch (body.type) {
7093
+ case "select_core":
7094
+ return deparseSelectCore(body);
7095
+ case "setop":
7096
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
7097
+ case "values":
7098
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
7099
+ }
7100
+ }
7101
+ function deparseSelectCore(body) {
7102
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
7103
+ const expr = deparseExpr(t.expr);
7104
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
7105
+ }).join(", ");
7106
+ let sql = `SELECT ${targets}`;
7107
+ if (body.from.length > 0) {
7108
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
7109
+ }
7110
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
7111
+ if (body.groupBy && body.groupBy.length > 0) {
7112
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
7113
+ }
7114
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
7115
+ return sql;
7116
+ }
7117
+ function deparseFrom(item) {
7118
+ if (item.type === "from_table") {
7119
+ const name = item.name.join(".");
7120
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
7121
+ }
7122
+ return "(subquery)";
7123
+ }
7072
7124
  function quoteIdent(name) {
7073
7125
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7074
7126
  return `"${name.replaceAll('"', '""')}"`;
7075
7127
  }
7128
+ function deparseExpr(e) {
7129
+ switch (e.type) {
7130
+ case "null_lit":
7131
+ return "NULL";
7132
+ case "string_lit":
7133
+ return `'${e.value.replaceAll("'", "''")}'`;
7134
+ case "number_lit":
7135
+ return e.raw;
7136
+ case "bool_lit":
7137
+ return e.value ? "true" : "false";
7138
+ case "colref":
7139
+ return e.parts.join(".");
7140
+ case "star":
7141
+ return e.table ? `${e.table.join(".")}.*` : "*";
7142
+ case "binop":
7143
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
7144
+ case "unop":
7145
+ return `${e.op}${deparseExpr(e.operand)}`;
7146
+ case "cast":
7147
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
7148
+ case "func":
7149
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
7150
+ case "case": {
7151
+ let out = "CASE";
7152
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
7153
+ for (const arm of e.whens) {
7154
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
7155
+ }
7156
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
7157
+ return `${out} END`;
7158
+ }
7159
+ case "subquery_expr":
7160
+ return `(${deparseSelect(e.query)})`;
7161
+ default:
7162
+ return "?";
7163
+ }
7164
+ }
7165
+
7166
+ // src/sql/deparse-index.ts
7167
+ function deparseIndexColumn(column) {
7168
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
7169
+ if (column.dir === "desc") result += " DESC";
7170
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
7171
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
7172
+ return result;
7173
+ }
7174
+ function deparseIndex(index) {
7175
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
7176
+ const unique = index.unique ? "UNIQUE " : "";
7177
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
7178
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
7179
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
7180
+ }
7076
7181
 
7077
7182
  // src/runtime/database-context.ts
7078
7183
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -7163,6 +7268,22 @@ function sequenceNextval(ctx, seq) {
7163
7268
  }
7164
7269
  function getMiscFunctions() {
7165
7270
  const m = /* @__PURE__ */ new Map();
7271
+ m.set(
7272
+ "pg_get_indexdef",
7273
+ strict("text", (ctx, args) => {
7274
+ const oid = argInt(ctx, args[0]);
7275
+ const column = args[1] ? argInt(ctx, args[1]) : 0;
7276
+ for (const schema of ctx.state.schemas.values()) {
7277
+ for (const index of schema.indexes.values()) {
7278
+ if (index.oid !== oid) continue;
7279
+ if (column === 0) return tv("text", deparseIndex(index));
7280
+ const part = index.columns[column - 1];
7281
+ return tv("text", part ? deparseIndexColumn(part) : "");
7282
+ }
7283
+ }
7284
+ return tv("text", null);
7285
+ })
7286
+ );
7166
7287
  m.set("version", () => tv("text", PG_VERSION_TEXT));
7167
7288
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7168
7289
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -12383,6 +12504,9 @@ function tryIndexedTableRows(env, table, alias, where) {
12383
12504
  const key = uniqueKeyOf(env, table, spec, probeRow);
12384
12505
  if (key === null) return [];
12385
12506
  const hits = indexStoreFor(env, table, spec).lookup(key);
12507
+ const schema = env.ctx.state.schemas.get(table.schema);
12508
+ const index = schema?.indexes.get(spec.name);
12509
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
12386
12510
  return hits.map((i) => table.rowAt(i));
12387
12511
  }
12388
12512
  function tryIndexedFromItem(env, item, where) {
@@ -15145,8 +15269,8 @@ var Parser = class {
15145
15269
  this.eatKw("column");
15146
15270
  const column = this.ident();
15147
15271
  let alterType = this.eatKw("type");
15148
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
15149
- this.pos += 1;
15272
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
15273
+ this.pos += 2;
15150
15274
  this.expectKw("type");
15151
15275
  alterType = true;
15152
15276
  }
@@ -19498,7 +19622,7 @@ function uniqueSpecsFor(env, table) {
19498
19622
  const schema = env.ctx.state.schemas.get(table.schema);
19499
19623
  if (schema) {
19500
19624
  for (const idx of schema.indexes.values()) {
19501
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
19625
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
19502
19626
  specs.push({
19503
19627
  name: idx.name,
19504
19628
  keys: idx.columns.map((c) => ({
@@ -20649,6 +20773,25 @@ var EngineCtx = class {
20649
20773
  timezone() {
20650
20774
  return this.state.getSetting("timezone") ?? "UTC";
20651
20775
  }
20776
+ namespaceOid(input) {
20777
+ const trimmed = input.trim();
20778
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
20779
+ if (name === "pg_catalog") return 11;
20780
+ if (name === "information_schema") return 13212;
20781
+ const schema = this.state.schemas.get(name);
20782
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
20783
+ return schema.oid;
20784
+ }
20785
+ namespaceName(oid) {
20786
+ if (oid === 11) return "pg_catalog";
20787
+ if (oid === 13212) return "information_schema";
20788
+ for (const schema of this.state.schemas.values()) {
20789
+ if (schema.oid === oid) {
20790
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
20791
+ }
20792
+ }
20793
+ return null;
20794
+ }
20652
20795
  zoneOffsetAt(utcMicros) {
20653
20796
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
20654
20797
  }
@@ -20919,7 +21062,7 @@ function pgClass(ctx) {
20919
21062
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
20920
21063
  for (const i of schemaData.indexes.values()) {
20921
21064
  push(
20922
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21065
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
20923
21066
  i.name,
20924
21067
  i.schema,
20925
21068
  "i",
@@ -21156,7 +21299,15 @@ function pgIndex(ctx) {
21156
21299
  for (const i of schema.indexes.values()) {
21157
21300
  const table = schema.tables.get(i.table);
21158
21301
  const isPrimary = i.isConstraint && table?.constraints.some((c) => c.kind === "primary_key" && c.name === i.name) === true;
21159
- rows.push([table?.oid ?? 0, table?.oid ?? 0, i.columns.length, i.unique, isPrimary, i.where !== null]);
21302
+ rows.push([
21303
+ i.oid ?? table?.oid ?? 0,
21304
+ table?.oid ?? 0,
21305
+ i.columns.length,
21306
+ i.unique,
21307
+ isPrimary,
21308
+ i.where !== null,
21309
+ i.valid !== false
21310
+ ]);
21160
21311
  }
21161
21312
  }
21162
21313
  return rel(
@@ -21166,12 +21317,73 @@ function pgIndex(ctx) {
21166
21317
  ["indnatts", "int2"],
21167
21318
  ["indisunique", "bool"],
21168
21319
  ["indisprimary", "bool"],
21169
- ["indpred", "bool"]
21320
+ ["indpred", "bool"],
21321
+ ["indisvalid", "bool"]
21170
21322
  ],
21171
21323
  rows,
21172
21324
  "pg_index"
21173
21325
  );
21174
21326
  }
21327
+ function pgStatUserTables(ctx) {
21328
+ return rel(
21329
+ [
21330
+ ["relid", "oid"],
21331
+ ["schemaname", "name"],
21332
+ ["relname", "name"],
21333
+ ["n_live_tup", "int8"]
21334
+ ],
21335
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
21336
+ "pg_stat_user_tables"
21337
+ );
21338
+ }
21339
+ function pgDescription(ctx) {
21340
+ const rows = [];
21341
+ for (const schema of ctx.state.schemas.values()) {
21342
+ for (const index of schema.indexes.values()) {
21343
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
21344
+ }
21345
+ }
21346
+ return rel(
21347
+ [
21348
+ ["objoid", "oid"],
21349
+ ["classoid", "oid"],
21350
+ ["objsubid", "int4"],
21351
+ ["description", "text"]
21352
+ ],
21353
+ rows,
21354
+ "pg_description"
21355
+ );
21356
+ }
21357
+ function pgStatUserIndexes(ctx) {
21358
+ const rows = [];
21359
+ for (const schema of ctx.state.schemas.values()) {
21360
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
21361
+ continue;
21362
+ for (const index of schema.indexes.values()) {
21363
+ const table = schema.tables.get(index.table);
21364
+ rows.push([
21365
+ table?.oid ?? 0,
21366
+ index.oid ?? table?.oid ?? 0,
21367
+ schema.name,
21368
+ index.table,
21369
+ index.name,
21370
+ index.scans ?? 0n
21371
+ ]);
21372
+ }
21373
+ }
21374
+ return rel(
21375
+ [
21376
+ ["relid", "oid"],
21377
+ ["indexrelid", "oid"],
21378
+ ["schemaname", "name"],
21379
+ ["relname", "name"],
21380
+ ["indexrelname", "name"],
21381
+ ["idx_scan", "int8"]
21382
+ ],
21383
+ rows,
21384
+ "pg_stat_user_indexes"
21385
+ );
21386
+ }
21175
21387
  function pgSequence(ctx) {
21176
21388
  const rows = [];
21177
21389
  for (const s of allSequences(ctx.state)) {
@@ -21286,7 +21498,7 @@ function pgIndexesView(ctx) {
21286
21498
  const rows = [];
21287
21499
  for (const schema of ctx.state.schemas.values()) {
21288
21500
  for (const i of schema.indexes.values()) {
21289
- rows.push([i.schema, i.table, i.name, null, null]);
21501
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
21290
21502
  }
21291
21503
  }
21292
21504
  return rel(
@@ -21607,6 +21819,12 @@ function buildCatalogRelation(ctx, schema, name) {
21607
21819
  return pgConstraint(ctx);
21608
21820
  case "pg_index":
21609
21821
  return pgIndex(ctx);
21822
+ case "pg_description":
21823
+ return pgDescription(ctx);
21824
+ case "pg_stat_user_tables":
21825
+ return pgStatUserTables(ctx);
21826
+ case "pg_stat_user_indexes":
21827
+ return pgStatUserIndexes(ctx);
21610
21828
  case "pg_sequence":
21611
21829
  return pgSequence(ctx);
21612
21830
  case "pg_sequences":
@@ -22190,6 +22408,7 @@ function executeCreateIndex(env, stmt) {
22190
22408
  }
22191
22409
  assertGinTrgmIndex(state, table, stmt);
22192
22410
  schema.indexes.set(name, {
22411
+ oid: state.nextOid(),
22193
22412
  name,
22194
22413
  schema: schema.name,
22195
22414
  table: table.name,
@@ -22202,7 +22421,8 @@ function executeCreateIndex(env, stmt) {
22202
22421
  })),
22203
22422
  where: stmt.where,
22204
22423
  nullsNotDistinct: stmt.nullsNotDistinct,
22205
- isConstraint: false
22424
+ isConstraint: false,
22425
+ valid: true
22206
22426
  });
22207
22427
  if (stmt.unique) {
22208
22428
  try {
@@ -22210,7 +22430,9 @@ function executeCreateIndex(env, stmt) {
22210
22430
  checkUnique(env, table, table.rowAt(i), i);
22211
22431
  }
22212
22432
  } catch (err) {
22213
- schema.indexes.delete(name);
22433
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
22434
+ else schema.indexes.delete(name);
22435
+ table.indexStores = null;
22214
22436
  throw err;
22215
22437
  }
22216
22438
  }
@@ -23330,6 +23552,9 @@ var Statement = class _Statement {
23330
23552
  const typed = params.map((p, i) => bindValueToTyped(p, i));
23331
23553
  let last = null;
23332
23554
  for (const stmt of this.statements) {
23555
+ if (stmt.type === "create_index" && stmt.concurrently && this.database.transactions.inTransaction) {
23556
+ throw pgError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
23557
+ }
23333
23558
  const env = {
23334
23559
  ctx: new EngineCtx(this.database.state),
23335
23560
  params: typed,