@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/server.js CHANGED
@@ -370,6 +370,9 @@ var PG_CATALOG_RELATIONS = /* @__PURE__ */ new Set([
370
370
  "pg_enum",
371
371
  "pg_constraint",
372
372
  "pg_index",
373
+ "pg_description",
374
+ "pg_stat_user_tables",
375
+ "pg_stat_user_indexes",
373
376
  "pg_sequence",
374
377
  "pg_sequences",
375
378
  "pg_tables",
@@ -2647,6 +2650,7 @@ var TYPE_ALIASES = {
2647
2650
  regclass: "regclass",
2648
2651
  regtype: "regtype",
2649
2652
  regproc: "regproc",
2653
+ regnamespace: "regnamespace",
2650
2654
  oid: "oid",
2651
2655
  void: "void",
2652
2656
  record: "record",
@@ -2740,6 +2744,7 @@ var TYPE_OIDS = {
2740
2744
  numeric: 1700,
2741
2745
  regclass: 2205,
2742
2746
  regtype: 2206,
2747
+ regnamespace: 4089,
2743
2748
  record: 2249,
2744
2749
  uuid: 2950,
2745
2750
  jsonb: 3802,
@@ -3040,6 +3045,7 @@ function recordText(rec, ctx) {
3040
3045
  return `(${parts.join(",")})`;
3041
3046
  }
3042
3047
  function datumText(t, v, ctx) {
3048
+ if (t === "regnamespace") return ctx.namespaceName?.(v) ?? String(v);
3043
3049
  if (v === null) throw pgError("internal", "datumText called with null");
3044
3050
  if (isArrayType(t)) return arrayText(v, ctx);
3045
3051
  if (isEnumType(t)) return v;
@@ -3113,6 +3119,11 @@ function datumText(t, v, ctx) {
3113
3119
  }
3114
3120
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
3115
3121
  function datumFromText(t, text, ctx) {
3122
+ if (t === "regnamespace") {
3123
+ if (/^\s*\d+\s*$/.test(text)) return datumFromText("oid", text, ctx);
3124
+ if (!ctx.namespaceOid) throw unsupported("regnamespace input without a catalog");
3125
+ return ctx.namespaceOid(text);
3126
+ }
3116
3127
  if (isArrayType(t)) return parseArrayText(t, text, ctx);
3117
3128
  if (isEnumType(t)) {
3118
3129
  if (ctx.enumHasLabel && !ctx.enumHasLabel(t, text)) {
@@ -3969,7 +3980,11 @@ function castTo(env, value, target, opts = {}) {
3969
3980
  );
3970
3981
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
3971
3982
  }
3972
- if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc")) {
3983
+ if (from === "oid" && target === "regnamespace") return tv(target, v);
3984
+ if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
3985
+ return tv(target, target === "int8" ? BigInt(v) : v);
3986
+ }
3987
+ if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regclass" || target === "regtype" || target === "regproc" || target === "regnamespace")) {
3973
3988
  return tv(target, Number(v));
3974
3989
  }
3975
3990
  if (from === "oid" && (target === "int4" || target === "int8")) {
@@ -4917,6 +4932,7 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
4917
4932
  case "int2":
4918
4933
  case "int4":
4919
4934
  case "oid":
4935
+ case "regnamespace":
4920
4936
  case "date":
4921
4937
  return expectNumber(a, t) - expectNumber(b, t);
4922
4938
  case "float4":
@@ -7072,10 +7088,99 @@ function exactDegTan(x) {
7072
7088
  }
7073
7089
 
7074
7090
  // src/sql/deparse.ts
7091
+ function deparseSelect(stmt) {
7092
+ return deparseBody(stmt.body);
7093
+ }
7094
+ function deparseBody(body) {
7095
+ switch (body.type) {
7096
+ case "select_core":
7097
+ return deparseSelectCore(body);
7098
+ case "setop":
7099
+ return `${deparseBody(body.left)} ${body.op.toUpperCase()}${body.all ? " ALL" : ""} ${deparseBody(body.right)}`;
7100
+ case "values":
7101
+ return `VALUES ${body.rows.map((r) => `(${r.map(deparseExpr).join(", ")})`).join(", ")}`;
7102
+ }
7103
+ }
7104
+ function deparseSelectCore(body) {
7105
+ const targets = body.targets.length === 0 ? "*" : body.targets.map((t) => {
7106
+ const expr = deparseExpr(t.expr);
7107
+ return t.alias ? `${expr} AS ${quoteIdent(t.alias)}` : expr;
7108
+ }).join(", ");
7109
+ let sql = `SELECT ${targets}`;
7110
+ if (body.from.length > 0) {
7111
+ sql += ` FROM ${body.from.map(deparseFrom).join(", ")}`;
7112
+ }
7113
+ if (body.where) sql += ` WHERE ${deparseExpr(body.where)}`;
7114
+ if (body.groupBy && body.groupBy.length > 0) {
7115
+ sql += ` GROUP BY ${body.groupBy.map((g) => g.kind === "expr" ? deparseExpr(g.expr) : "?").join(", ")}`;
7116
+ }
7117
+ if (body.having) sql += ` HAVING ${deparseExpr(body.having)}`;
7118
+ return sql;
7119
+ }
7120
+ function deparseFrom(item) {
7121
+ if (item.type === "from_table") {
7122
+ const name = item.name.join(".");
7123
+ return item.alias ? `${name} ${quoteIdent(item.alias)}` : name;
7124
+ }
7125
+ return "(subquery)";
7126
+ }
7075
7127
  function quoteIdent(name) {
7076
7128
  if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7077
7129
  return `"${name.replaceAll('"', '""')}"`;
7078
7130
  }
7131
+ function deparseExpr(e) {
7132
+ switch (e.type) {
7133
+ case "null_lit":
7134
+ return "NULL";
7135
+ case "string_lit":
7136
+ return `'${e.value.replaceAll("'", "''")}'`;
7137
+ case "number_lit":
7138
+ return e.raw;
7139
+ case "bool_lit":
7140
+ return e.value ? "true" : "false";
7141
+ case "colref":
7142
+ return e.parts.join(".");
7143
+ case "star":
7144
+ return e.table ? `${e.table.join(".")}.*` : "*";
7145
+ case "binop":
7146
+ return `(${deparseExpr(e.left)} ${e.op} ${deparseExpr(e.right)})`;
7147
+ case "unop":
7148
+ return `${e.op}${deparseExpr(e.operand)}`;
7149
+ case "cast":
7150
+ return `CAST(${deparseExpr(e.expr)} AS ${e.target.parts.join(".")})`;
7151
+ case "func":
7152
+ return `${e.name.join(".")}(${e.args.map(deparseExpr).join(", ")})`;
7153
+ case "case": {
7154
+ let out = "CASE";
7155
+ if (e.operand) out += ` ${deparseExpr(e.operand)}`;
7156
+ for (const arm of e.whens) {
7157
+ out += ` WHEN ${deparseExpr(arm.when)} THEN ${deparseExpr(arm.then)}`;
7158
+ }
7159
+ if (e.elseExpr) out += ` ELSE ${deparseExpr(e.elseExpr)}`;
7160
+ return `${out} END`;
7161
+ }
7162
+ case "subquery_expr":
7163
+ return `(${deparseSelect(e.query)})`;
7164
+ default:
7165
+ return "?";
7166
+ }
7167
+ }
7168
+
7169
+ // src/sql/deparse-index.ts
7170
+ function deparseIndexColumn(column) {
7171
+ let result = column.column === null ? deparseExpr(column.expr) : quoteIdent(column.column);
7172
+ if (column.dir === "desc") result += " DESC";
7173
+ const defaultNulls = column.dir === "desc" ? "first" : "last";
7174
+ if (column.nulls !== defaultNulls) result += ` NULLS ${column.nulls.toUpperCase()}`;
7175
+ return result;
7176
+ }
7177
+ function deparseIndex(index) {
7178
+ const columns = index.columns.map(deparseIndexColumn).join(", ");
7179
+ const unique = index.unique ? "UNIQUE " : "";
7180
+ const nulls = index.nullsNotDistinct ? " NULLS NOT DISTINCT" : "";
7181
+ const predicate = index.where ? ` WHERE ${deparseExpr(index.where)}` : "";
7182
+ return `CREATE ${unique}INDEX ${quoteIdent(index.name)} ON ${quoteIdent(index.schema)}.${quoteIdent(index.table)} USING btree (${columns})${nulls}${predicate}`;
7183
+ }
7079
7184
 
7080
7185
  // src/runtime/database-context.ts
7081
7186
  var contexts = /* @__PURE__ */ new WeakMap();
@@ -7169,6 +7274,22 @@ function sequenceNextval(ctx, seq) {
7169
7274
  }
7170
7275
  function getMiscFunctions() {
7171
7276
  const m = /* @__PURE__ */ new Map();
7277
+ m.set(
7278
+ "pg_get_indexdef",
7279
+ strict("text", (ctx, args) => {
7280
+ const oid = argInt(ctx, args[0]);
7281
+ const column = args[1] ? argInt(ctx, args[1]) : 0;
7282
+ for (const schema of ctx.state.schemas.values()) {
7283
+ for (const index of schema.indexes.values()) {
7284
+ if (index.oid !== oid) continue;
7285
+ if (column === 0) return tv("text", deparseIndex(index));
7286
+ const part = index.columns[column - 1];
7287
+ return tv("text", part ? deparseIndexColumn(part) : "");
7288
+ }
7289
+ }
7290
+ return tv("text", null);
7291
+ })
7292
+ );
7172
7293
  m.set("version", () => tv("text", PG_VERSION_TEXT));
7173
7294
  m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7174
7295
  m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
@@ -12389,6 +12510,9 @@ function tryIndexedTableRows(env, table, alias, where) {
12389
12510
  const key = uniqueKeyOf(env, table, spec, probeRow);
12390
12511
  if (key === null) return [];
12391
12512
  const hits = indexStoreFor(env, table, spec).lookup(key);
12513
+ const schema = env.ctx.state.schemas.get(table.schema);
12514
+ const index = schema?.indexes.get(spec.name);
12515
+ if (schema && index) schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n });
12392
12516
  return hits.map((i) => table.rowAt(i));
12393
12517
  }
12394
12518
  function tryIndexedFromItem(env, item, where) {
@@ -15151,8 +15275,8 @@ var Parser = class {
15151
15275
  this.eatKw("column");
15152
15276
  const column = this.ident();
15153
15277
  let alterType = this.eatKw("type");
15154
- if (!alterType && this.eatKw("set") && this.atKw("data")) {
15155
- this.pos += 1;
15278
+ if (!alterType && this.atKw("set") && this.atKw("data", 1)) {
15279
+ this.pos += 2;
15156
15280
  this.expectKw("type");
15157
15281
  alterType = true;
15158
15282
  }
@@ -19504,7 +19628,7 @@ function uniqueSpecsFor(env, table) {
19504
19628
  const schema = env.ctx.state.schemas.get(table.schema);
19505
19629
  if (schema) {
19506
19630
  for (const idx of schema.indexes.values()) {
19507
- if (idx.table !== table.name || !idx.unique || idx.isConstraint) continue;
19631
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
19508
19632
  specs.push({
19509
19633
  name: idx.name,
19510
19634
  keys: idx.columns.map((c) => ({
@@ -20655,6 +20779,25 @@ var EngineCtx = class {
20655
20779
  timezone() {
20656
20780
  return this.state.getSetting("timezone") ?? "UTC";
20657
20781
  }
20782
+ namespaceOid(input) {
20783
+ const trimmed = input.trim();
20784
+ const name = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1).replaceAll('""', '"') : trimmed.toLowerCase();
20785
+ if (name === "pg_catalog") return 11;
20786
+ if (name === "information_schema") return 13212;
20787
+ const schema = this.state.schemas.get(name);
20788
+ if (!schema) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
20789
+ return schema.oid;
20790
+ }
20791
+ namespaceName(oid) {
20792
+ if (oid === 11) return "pg_catalog";
20793
+ if (oid === 13212) return "information_schema";
20794
+ for (const schema of this.state.schemas.values()) {
20795
+ if (schema.oid === oid) {
20796
+ return /^[a-z_][a-z0-9_$]*$/.test(schema.name) ? schema.name : `"${schema.name.replaceAll('"', '""')}"`;
20797
+ }
20798
+ }
20799
+ return null;
20800
+ }
20658
20801
  zoneOffsetAt(utcMicros) {
20659
20802
  return zoneOffsetAtUtc(this.timezone(), utcMicros);
20660
20803
  }
@@ -20925,7 +21068,7 @@ function pgClass(ctx) {
20925
21068
  for (const s of schemaData.sequences.values()) push(s.oid, s.name, s.schema, "S", 3, 1, false, true, s.temp);
20926
21069
  for (const i of schemaData.indexes.values()) {
20927
21070
  push(
20928
- state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
21071
+ i.oid ?? state.schemas.get(i.schema)?.tables.get(i.table)?.oid ?? 0,
20929
21072
  i.name,
20930
21073
  i.schema,
20931
21074
  "i",
@@ -21162,7 +21305,15 @@ function pgIndex(ctx) {
21162
21305
  for (const i of schema.indexes.values()) {
21163
21306
  const table = schema.tables.get(i.table);
21164
21307
  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]);
21308
+ rows.push([
21309
+ i.oid ?? table?.oid ?? 0,
21310
+ table?.oid ?? 0,
21311
+ i.columns.length,
21312
+ i.unique,
21313
+ isPrimary,
21314
+ i.where !== null,
21315
+ i.valid !== false
21316
+ ]);
21166
21317
  }
21167
21318
  }
21168
21319
  return rel(
@@ -21172,12 +21323,73 @@ function pgIndex(ctx) {
21172
21323
  ["indnatts", "int2"],
21173
21324
  ["indisunique", "bool"],
21174
21325
  ["indisprimary", "bool"],
21175
- ["indpred", "bool"]
21326
+ ["indpred", "bool"],
21327
+ ["indisvalid", "bool"]
21176
21328
  ],
21177
21329
  rows,
21178
21330
  "pg_index"
21179
21331
  );
21180
21332
  }
21333
+ function pgStatUserTables(ctx) {
21334
+ return rel(
21335
+ [
21336
+ ["relid", "oid"],
21337
+ ["schemaname", "name"],
21338
+ ["relname", "name"],
21339
+ ["n_live_tup", "int8"]
21340
+ ],
21341
+ [...allTables(ctx.state)].map((table) => [table.oid, table.schema, table.name, BigInt(table.rowCount())]),
21342
+ "pg_stat_user_tables"
21343
+ );
21344
+ }
21345
+ function pgDescription(ctx) {
21346
+ const rows = [];
21347
+ for (const schema of ctx.state.schemas.values()) {
21348
+ for (const index of schema.indexes.values()) {
21349
+ if (index.comment) rows.push([index.oid ?? 0, 1259, 0, index.comment]);
21350
+ }
21351
+ }
21352
+ return rel(
21353
+ [
21354
+ ["objoid", "oid"],
21355
+ ["classoid", "oid"],
21356
+ ["objsubid", "int4"],
21357
+ ["description", "text"]
21358
+ ],
21359
+ rows,
21360
+ "pg_description"
21361
+ );
21362
+ }
21363
+ function pgStatUserIndexes(ctx) {
21364
+ const rows = [];
21365
+ for (const schema of ctx.state.schemas.values()) {
21366
+ if (schema.name === "pg_catalog" || schema.name === "information_schema" || schema.name.startsWith("pg_toast"))
21367
+ continue;
21368
+ for (const index of schema.indexes.values()) {
21369
+ const table = schema.tables.get(index.table);
21370
+ rows.push([
21371
+ table?.oid ?? 0,
21372
+ index.oid ?? table?.oid ?? 0,
21373
+ schema.name,
21374
+ index.table,
21375
+ index.name,
21376
+ index.scans ?? 0n
21377
+ ]);
21378
+ }
21379
+ }
21380
+ return rel(
21381
+ [
21382
+ ["relid", "oid"],
21383
+ ["indexrelid", "oid"],
21384
+ ["schemaname", "name"],
21385
+ ["relname", "name"],
21386
+ ["indexrelname", "name"],
21387
+ ["idx_scan", "int8"]
21388
+ ],
21389
+ rows,
21390
+ "pg_stat_user_indexes"
21391
+ );
21392
+ }
21181
21393
  function pgSequence(ctx) {
21182
21394
  const rows = [];
21183
21395
  for (const s of allSequences(ctx.state)) {
@@ -21292,7 +21504,7 @@ function pgIndexesView(ctx) {
21292
21504
  const rows = [];
21293
21505
  for (const schema of ctx.state.schemas.values()) {
21294
21506
  for (const i of schema.indexes.values()) {
21295
- rows.push([i.schema, i.table, i.name, null, null]);
21507
+ rows.push([i.schema, i.table, i.name, null, deparseIndex(i)]);
21296
21508
  }
21297
21509
  }
21298
21510
  return rel(
@@ -21613,6 +21825,12 @@ function buildCatalogRelation(ctx, schema, name) {
21613
21825
  return pgConstraint(ctx);
21614
21826
  case "pg_index":
21615
21827
  return pgIndex(ctx);
21828
+ case "pg_description":
21829
+ return pgDescription(ctx);
21830
+ case "pg_stat_user_tables":
21831
+ return pgStatUserTables(ctx);
21832
+ case "pg_stat_user_indexes":
21833
+ return pgStatUserIndexes(ctx);
21616
21834
  case "pg_sequence":
21617
21835
  return pgSequence(ctx);
21618
21836
  case "pg_sequences":
@@ -22196,6 +22414,7 @@ function executeCreateIndex(env, stmt) {
22196
22414
  }
22197
22415
  assertGinTrgmIndex(state, table, stmt);
22198
22416
  schema.indexes.set(name, {
22417
+ oid: state.nextOid(),
22199
22418
  name,
22200
22419
  schema: schema.name,
22201
22420
  table: table.name,
@@ -22208,7 +22427,8 @@ function executeCreateIndex(env, stmt) {
22208
22427
  })),
22209
22428
  where: stmt.where,
22210
22429
  nullsNotDistinct: stmt.nullsNotDistinct,
22211
- isConstraint: false
22430
+ isConstraint: false,
22431
+ valid: true
22212
22432
  });
22213
22433
  if (stmt.unique) {
22214
22434
  try {
@@ -22216,7 +22436,9 @@ function executeCreateIndex(env, stmt) {
22216
22436
  checkUnique(env, table, table.rowAt(i), i);
22217
22437
  }
22218
22438
  } catch (err) {
22219
- schema.indexes.delete(name);
22439
+ if (stmt.concurrently) schema.indexes.get(name).valid = false;
22440
+ else schema.indexes.delete(name);
22441
+ table.indexStores = null;
22220
22442
  throw err;
22221
22443
  }
22222
22444
  }
@@ -23336,6 +23558,9 @@ var Statement = class _Statement {
23336
23558
  const typed = params.map((p, i) => bindValueToTyped(p, i));
23337
23559
  let last = null;
23338
23560
  for (const stmt of this.statements) {
23561
+ if (stmt.type === "create_index" && stmt.concurrently && this.database.transactions.inTransaction) {
23562
+ throw pgError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
23563
+ }
23339
23564
  const env = {
23340
23565
  ctx: new EngineCtx(this.database.state),
23341
23566
  params: typed,
@@ -24883,7 +25108,11 @@ var Connection = class {
24883
25108
  const result = this.cluster.as(this, () => this.runQuery(db, text, params));
24884
25109
  return { empty: false, result, tag: commandTag(result) };
24885
25110
  };
24886
- const outcome = db.transaction(run);
25111
+ const concurrentIndex = /\bconcurrently\b/i.test(text) && parse(text).some((stmt) => stmt.type === "create_index" && stmt.concurrently);
25112
+ if (concurrentIndex && this.transaction !== null) {
25113
+ throw new PostgresError("misuse", "CREATE INDEX CONCURRENTLY cannot run inside a transaction block", "25001");
25114
+ }
25115
+ const outcome = concurrentIndex ? run() : db.transaction(run);
24887
25116
  if (this.transaction && mutatesDatabase(text)) {
24888
25117
  this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
24889
25118
  }