@crvouga/mockingbird-service-postgres 3.7.0 → 3.8.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
@@ -161,6 +161,16 @@ var Timeline = class {
161
161
  }
162
162
  };
163
163
 
164
+ // ../core/dist/ids.js
165
+ var normalizeIdBase = (value, label = "idBase", wallNow = Date.now) => {
166
+ if (value === "boot")
167
+ return wallNow();
168
+ const base = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value.trim()) : typeof value === "number" ? value : Number.NaN;
169
+ if (!Number.isSafeInteger(base) || base < 0)
170
+ throw new RangeError(`${label} must be a non-negative integer or "boot" (got ${JSON.stringify(value)})`);
171
+ return base;
172
+ };
173
+
164
174
  // ../core/dist/clock.js
165
175
  var createClock = (source = Date.now) => {
166
176
  let offsetMs = 0;
@@ -746,6 +756,19 @@ function ownedTransport(policy) {
746
756
  import { readFile as readFile5 } from "node:fs/promises";
747
757
  import { parseArgs } from "node:util";
748
758
 
759
+ // ../../adapters/node/dist/boot-settings.js
760
+ var checkSettings = (target, settings, label) => {
761
+ if (!target.parseSettings)
762
+ throw new Error(`${label}: ${target.name} takes no boot settings`);
763
+ try {
764
+ return target.parseSettings(settings);
765
+ } catch (error) {
766
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
767
+ cause: error
768
+ });
769
+ }
770
+ };
771
+
749
772
  // ../../adapters/node/dist/fleet.js
750
773
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
751
774
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
@@ -3395,6 +3418,9 @@ var fleet_config_schema_default = {
3395
3418
  adminControls: {
3396
3419
  $ref: "#/$defs/controls"
3397
3420
  },
3421
+ idBase: {
3422
+ $ref: "#/$defs/idBase"
3423
+ },
3398
3424
  namespace: {
3399
3425
  type: "string",
3400
3426
  pattern: "^[A-Za-z0-9_.-]{1,64}$"
@@ -3484,6 +3510,12 @@ var fleet_config_schema_default = {
3484
3510
  adminControls: {
3485
3511
  $ref: "#/$defs/controls"
3486
3512
  },
3513
+ idBase: {
3514
+ $ref: "#/$defs/idBase"
3515
+ },
3516
+ settings: {
3517
+ type: "object"
3518
+ },
3487
3519
  fixtures: {
3488
3520
  type: "object"
3489
3521
  },
@@ -3530,6 +3562,18 @@ var fleet_config_schema_default = {
3530
3562
  }
3531
3563
  }
3532
3564
  },
3565
+ idBase: {
3566
+ anyOf: [
3567
+ {
3568
+ type: "integer",
3569
+ minimum: 0
3570
+ },
3571
+ {
3572
+ type: "string",
3573
+ pattern: "^(boot|[0-9]+)$"
3574
+ }
3575
+ ]
3576
+ },
3533
3577
  controls: {
3534
3578
  type: "object",
3535
3579
  additionalProperties: false,
@@ -3679,6 +3723,8 @@ async function preflightFleet(config, options2) {
3679
3723
  throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
3680
3724
  resolveAdminPrefix(config.adminPrefix);
3681
3725
  normalizeBootControls(config.adminControls);
3726
+ if (config.idBase !== void 0)
3727
+ normalizeIdBase(config.idBase, "idBase");
3682
3728
  const targets = /* @__PURE__ */ new Map();
3683
3729
  const ports = /* @__PURE__ */ new Set();
3684
3730
  for (const [name2, entry] of Object.entries(config.services)) {
@@ -3710,6 +3756,13 @@ async function preflightFleet(config, options2) {
3710
3756
  throw new Error("invalid protocol namespace selector");
3711
3757
  }
3712
3758
  }
3759
+ if (entry.idBase !== void 0)
3760
+ normalizeIdBase(entry.idBase, "idBase");
3761
+ if (entry.settings !== void 0) {
3762
+ if (protocol !== "http" || !("create" in target))
3763
+ throw new Error("settings: only HTTP services take boot settings");
3764
+ checkSettings(target, entry.settings, "settings");
3765
+ }
3713
3766
  const port = entry.port ?? target.defaultPort;
3714
3767
  if (port !== 0) {
3715
3768
  const key = `${entry.host ?? "127.0.0.1"}:${port}`;
@@ -3745,7 +3798,7 @@ async function preflightFleet(config, options2) {
3745
3798
  break;
3746
3799
  }
3747
3800
  }
3748
- targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.7.0" });
3801
+ targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.8.0" });
3749
3802
  } catch (error) {
3750
3803
  throw new Error(`service ${name2}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3751
3804
  }
@@ -4065,6 +4118,7 @@ var httpChild = async (target, entry, options2, admission, generation, privateAc
4065
4118
  options2.onLog?.(entry2);
4066
4119
  },
4067
4120
  ...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
4121
+ ...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
4068
4122
  ...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
4069
4123
  ...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
4070
4124
  });
@@ -4083,6 +4137,8 @@ var httpChild = async (target, entry, options2, admission, generation, privateAc
4083
4137
  ].some((method) => typeof created[method] !== "function"))
4084
4138
  throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
4085
4139
  const runtime = created;
4140
+ if (entry.idBase !== void 0)
4141
+ runtime.setIdBase?.(entry.idBase);
4086
4142
  const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
4087
4143
  if (options2.outbound && runtime.webhooks)
4088
4144
  runtime.webhooks.setTransportPolicy(ownedTransport(options2.outbound));
@@ -4681,6 +4737,7 @@ async function startFleet(config, options2) {
4681
4737
  ...entry,
4682
4738
  adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
4683
4739
  ...adminKey !== void 0 ? { adminKey } : {},
4740
+ ...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
4684
4741
  // Fleet-wide controls, with the service's own keys replacing them.
4685
4742
  ...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
4686
4743
  }, options2, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
@@ -5365,6 +5422,16 @@ var COMMON_SERVE_OPTIONS = {
5365
5422
  value: "<seed>",
5366
5423
  description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
5367
5424
  },
5425
+ "id-base": {
5426
+ type: "string",
5427
+ value: "<n|boot>",
5428
+ description: "Start generated ids at this base so a restart does not reissue the last boot's ids; boot derives one from the clock (env MOCKINGBIRD_ID_BASE; default 0, the same ids every boot)"
5429
+ },
5430
+ settings: {
5431
+ type: "string",
5432
+ value: "<json|file>",
5433
+ description: "Service settings to boot with, as JSON or a JSON file; every namespace starts from them (env MOCKINGBIRD_SETTINGS; only for services that declare settings)"
5434
+ },
5368
5435
  "admin-controls": {
5369
5436
  type: "string",
5370
5437
  value: "<json|file>",
@@ -5459,19 +5526,40 @@ var readJsonFile = async (path, label) => {
5459
5526
  }
5460
5527
  };
5461
5528
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5529
+ var readJsonOption = async (source, label) => {
5530
+ const trimmed = source.trim();
5531
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
5532
+ try {
5533
+ return JSON.parse(trimmed);
5534
+ } catch {
5535
+ throw new Error(`${label}: not valid JSON`);
5536
+ }
5537
+ }
5538
+ return readJsonFile(source, label);
5539
+ };
5540
+ var resolveServeIdBase = (values, env = process.env) => {
5541
+ const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
5542
+ if (source === void 0 || source.trim() === "")
5543
+ return void 0;
5544
+ normalizeIdBase(source.trim(), "--id-base");
5545
+ return source.trim();
5546
+ };
5547
+ var resolveServeSettings = async (target, values, env = process.env) => {
5548
+ const flag2 = asString(values.settings);
5549
+ const source = flag2 ?? env.MOCKINGBIRD_SETTINGS;
5550
+ if (source === void 0 || source.trim() === "")
5551
+ return void 0;
5552
+ const label = flag2 !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
5553
+ const parsed = await readJsonOption(source, label);
5554
+ if (!isObject2(parsed))
5555
+ throw new Error(`${label}: expected a JSON object`);
5556
+ return checkSettings(target, parsed, label);
5557
+ };
5462
5558
  var resolveServeControls = async (values, env = process.env, base) => {
5463
5559
  const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
5464
5560
  let document = isObject2(base) ? { ...base } : {};
5465
5561
  if (source !== void 0 && source.trim() !== "") {
5466
- let parsed;
5467
- if (source.trim().startsWith("{")) {
5468
- try {
5469
- parsed = JSON.parse(source);
5470
- } catch {
5471
- throw new Error("--admin-controls: not valid JSON");
5472
- }
5473
- } else
5474
- parsed = await readJsonFile(source, "--admin-controls");
5562
+ const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
5475
5563
  if (!isObject2(parsed))
5476
5564
  throw new Error("--admin-controls: expected a JSON object");
5477
5565
  document = { ...document, ...parsed };
@@ -5521,8 +5609,11 @@ var start = async (target, values, config) => {
5521
5609
  adminKey: config.adminKey,
5522
5610
  seed: config.seed,
5523
5611
  onLog: formatLog(config.log),
5612
+ ...config.settings !== void 0 ? { settings: config.settings } : {},
5524
5613
  ...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
5525
5614
  });
5615
+ if (config.idBase !== void 0)
5616
+ runtime.setIdBase?.(config.idBase);
5526
5617
  const listening = await listen(runtime, { port: config.port, host: config.host });
5527
5618
  target.listening?.(listening);
5528
5619
  console.log(`${target.name} emulator listening on ${listening.url}`);
@@ -5568,6 +5659,11 @@ var serveCommand = (target) => ({
5568
5659
  const adminControls = await resolveServeControls(values, process.env, config.adminControls);
5569
5660
  if (adminControls !== void 0)
5570
5661
  config.adminControls = adminControls;
5662
+ if (values.settings !== void 0)
5663
+ throw new Error("--settings serves a single service; give each service its own `settings` in the config");
5664
+ const idBase = config.idBase ?? resolveServeIdBase(values);
5665
+ if (idBase !== void 0)
5666
+ config.idBase = idBase;
5571
5667
  const fleet = await startFleet(config, {
5572
5668
  load: (name2) => loadTarget(name2, target),
5573
5669
  onLog: formatLog(config.log ?? log2),
@@ -5597,8 +5693,12 @@ var serveCommand = (target) => ({
5597
5693
  let listening;
5598
5694
  try {
5599
5695
  const adminControls = await resolveServeControls(values);
5696
+ const idBase = resolveServeIdBase(values);
5697
+ const settings = await resolveServeSettings(target, values);
5600
5698
  listening = await start(target, values, {
5601
5699
  ...adminControls !== void 0 ? { adminControls } : {},
5700
+ ...idBase !== void 0 ? { idBase } : {},
5701
+ ...settings !== void 0 ? { settings } : {},
5602
5702
  adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
5603
5703
  port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
5604
5704
  host: asString(values.host) ?? "127.0.0.1",
@@ -15420,7 +15520,7 @@ function evalTrgmWordSimilar(ctx, left, right) {
15420
15520
  const score = wordSimilarity(a.v, b.v);
15421
15521
  return tv("bool", score >= WORD_SIMILARITY_THRESHOLD);
15422
15522
  }
15423
- function assertGinTrgmIndex(state, table, stmt) {
15523
+ function assertGinTrgmIndex(state, table, stmt, expressionType) {
15424
15524
  const trgmCols = stmt.columns.filter((col2) => isGinTrgmOpclass(col2.opclass));
15425
15525
  if (trgmCols.length === 0) return;
15426
15526
  if (trgmCols.length !== stmt.columns.length || stmt.columns.length !== 1) {
@@ -15440,11 +15540,7 @@ function assertGinTrgmIndex(state, table, stmt) {
15440
15540
  if (stmt.unique) {
15441
15541
  throw pgError("feature_not_supported", `access method "gin" does not support unique indexes`, "0A000");
15442
15542
  }
15443
- if (col.expr.type !== "colref" || col.expr.parts.length !== 1) {
15444
- throw unsupported("expression gin_trgm_ops");
15445
- }
15446
- const index = table.columnIndex(col.expr.parts[0]);
15447
- const typeId = table.columns[index].type.id;
15543
+ const typeId = col.expr.type === "colref" && col.expr.parts.length === 1 ? table.columns[table.columnIndex(col.expr.parts[0])].type.id : expressionType(col.expr);
15448
15544
  if (!GIN_TRGM_TYPES.has(typeId)) {
15449
15545
  throw pgError(
15450
15546
  "datatype_mismatch",
@@ -19082,6 +19178,17 @@ function tableScope(table, row) {
19082
19178
  const cols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: table.name }));
19083
19179
  return new RowScope(cols, row, null, /* @__PURE__ */ new Set([table.name]));
19084
19180
  }
19181
+ function expressionTypeOf(env, table, expr) {
19182
+ const scope = makeEvalScope(
19183
+ env,
19184
+ tableScope(
19185
+ table,
19186
+ table.columns.map(() => null)
19187
+ )
19188
+ );
19189
+ const v = evalExpr(env.ctx, scope, expr);
19190
+ return v.t === "unknown" ? "text" : v.t;
19191
+ }
19085
19192
  function checkNotNull(env, table, row) {
19086
19193
  void env;
19087
19194
  for (let i = 0; i < table.columns.length; i++) {
@@ -22004,7 +22111,6 @@ var Parser = class {
22004
22111
  } while (this.eatKw("or"));
22005
22112
  this.expectKw("on");
22006
22113
  const table = this.qualifiedName();
22007
- if (this.eatKw("referencing")) throw unsupported("trigger transition tables");
22008
22114
  let deferrable = false;
22009
22115
  let initiallyDeferred = false;
22010
22116
  if (this.eatKw("not")) this.expectKw("deferrable");
@@ -22017,6 +22123,23 @@ var Parser = class {
22017
22123
  this.errorAt(this.peek(), "only constraint triggers can be deferred");
22018
22124
  if (initiallyDeferred && !deferrable)
22019
22125
  this.errorAt(this.peek(), "constraint declared INITIALLY DEFERRED must be DEFERRABLE");
22126
+ const transitions = [];
22127
+ if (!constraint && this.eatKw("referencing")) {
22128
+ do {
22129
+ const isNew = this.eatKw("new");
22130
+ if (!isNew) this.expectKw("old");
22131
+ if (this.eatKw("row")) {
22132
+ throw pgError(
22133
+ "feature_not_supported",
22134
+ "ROW variable naming in the REFERENCING clause is not supported",
22135
+ "0A000"
22136
+ );
22137
+ }
22138
+ this.expectKw("table");
22139
+ this.eatKw("as");
22140
+ transitions.push({ isNew, name: this.ident() });
22141
+ } while (this.atKw("new") || this.atKw("old"));
22142
+ }
22020
22143
  let forEachRow = false;
22021
22144
  if (this.eatKw("for")) {
22022
22145
  this.eatKw("each");
@@ -22054,7 +22177,8 @@ var Parser = class {
22054
22177
  forEachRow,
22055
22178
  when,
22056
22179
  funcName,
22057
- funcArgs
22180
+ funcArgs,
22181
+ ...transitions.length > 0 ? { transitions } : {}
22058
22182
  };
22059
22183
  }
22060
22184
  // --- ALTER --------------------------------------------------------------------
@@ -26600,6 +26724,29 @@ function fireRowTriggers(env, table, timing, event, oldRow, newRow) {
26600
26724
  }
26601
26725
  return { row };
26602
26726
  }
26727
+ function transitionRelation(table, name2, rows) {
26728
+ return {
26729
+ columns: table.columns.map((c) => ({ name: c.name, type: c.type.id, table: name2 })),
26730
+ rows
26731
+ };
26732
+ }
26733
+ function fireStatementTriggers(env, table, timing, event, transitions) {
26734
+ const fired = table.triggers.filter((t) => !t.forEachRow && t.timing === timing && matches(t, event)).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
26735
+ for (const t of fired) {
26736
+ if (!executor) throw unsupported(`trigger "${t.name}" execution`);
26737
+ const ctes = /* @__PURE__ */ new Map();
26738
+ if (t.oldTable !== void 0)
26739
+ ctes.set(t.oldTable, transitionRelation(table, t.oldTable, transitions?.oldRows ?? []));
26740
+ if (t.newTable !== void 0)
26741
+ ctes.set(t.newTable, transitionRelation(table, t.newTable, transitions?.newRows ?? []));
26742
+ const changes = env.ctx.state.changes;
26743
+ try {
26744
+ executor({ ctx: env.ctx, params: null, ctes, outer: null }, table, t, event, null, null);
26745
+ } finally {
26746
+ env.ctx.state.changes = changes;
26747
+ }
26748
+ }
26749
+ }
26603
26750
  var keyOf = (schema, name2) => JSON.stringify([schema, name2]);
26604
26751
  function flushConstraintTriggers(state, all = false) {
26605
26752
  const env = { ctx: new EngineCtx(state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
@@ -26983,7 +27130,13 @@ function executeInsert(env0, stmt) {
26983
27130
  const sourceRows = insertSourceRows(env, stmt, colIdxs);
26984
27131
  const insertedRows = [];
26985
27132
  let insertedCount = 0;
27133
+ const addedRows = [];
27134
+ const conflictOldRows = [];
27135
+ const conflictNewRows = [];
27136
+ const updatesOnConflict = stmt.onConflict !== null && stmt.onConflict.action !== "nothing";
26986
27137
  return withStatementRollback(env, () => {
27138
+ fireStatementTriggers(env, table, "before", "insert");
27139
+ if (updatesOnConflict) fireStatementTriggers(env, table, "before", "update");
26987
27140
  for (const srcRow of sourceRows) {
26988
27141
  const row = table.columns.map(() => null);
26989
27142
  const provided = /* @__PURE__ */ new Set();
@@ -27029,6 +27182,7 @@ function executeInsert(env0, stmt) {
27029
27182
  }
27030
27183
  if (conflictIdx !== null && conflictWhereOk) {
27031
27184
  if (stmt.onConflict.action === "nothing") continue;
27185
+ const replaced = rows[conflictIdx];
27032
27186
  const did = applyOnConflictUpdate(
27033
27187
  env,
27034
27188
  table,
@@ -27041,6 +27195,8 @@ function executeInsert(env0, stmt) {
27041
27195
  if (did) {
27042
27196
  insertedCount++;
27043
27197
  insertedRows.push(rows[conflictIdx]);
27198
+ conflictOldRows.push(replaced);
27199
+ conflictNewRows.push(rows[conflictIdx]);
27044
27200
  fireRowTriggers(env, table, "after", "update", null, rows[conflictIdx]);
27045
27201
  }
27046
27202
  continue;
@@ -27055,8 +27211,13 @@ function executeInsert(env0, stmt) {
27055
27211
  indexInsertRow(env, table, rows.length - 1, newRow);
27056
27212
  insertedCount++;
27057
27213
  insertedRows.push(newRow);
27214
+ addedRows.push(newRow);
27058
27215
  fireRowTriggers(env, table, "after", "insert", null, newRow);
27059
27216
  }
27217
+ fireStatementTriggers(env, table, "after", "insert", { oldRows: [], newRows: addedRows });
27218
+ if (updatesOnConflict) {
27219
+ fireStatementTriggers(env, table, "after", "update", { oldRows: conflictOldRows, newRows: conflictNewRows });
27220
+ }
27060
27221
  env.ctx.state.changes = insertedCount;
27061
27222
  if (stmt.returning) {
27062
27223
  const res = evalReturning(env, table, label, stmt.returning, insertedRows, "INSERT");
@@ -27137,10 +27298,12 @@ function executeUpdate(env0, stmt) {
27137
27298
  const env = applyWith(env0, stmt.with);
27138
27299
  const table = requireTargetTable(env, stmt.table, "update");
27139
27300
  const label = stmt.alias ?? table.name;
27301
+ fireStatementTriggers(env, table, "before", "update");
27140
27302
  const fromSource = stmt.from.length > 0 ? buildFrom(env, stmt.from) : null;
27141
27303
  const rows = table.mutableRows();
27142
27304
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
27143
27305
  const updatedRows = [];
27306
+ const replacedRows = [];
27144
27307
  let updateCount = 0;
27145
27308
  for (let ri = 0; ri < rows.length; ri++) {
27146
27309
  const oldRow = rows[ri];
@@ -27183,8 +27346,10 @@ function executeUpdate(env0, stmt) {
27183
27346
  handleReferencedUpdate(env, table, [oldRow], [finalRow]);
27184
27347
  updateCount++;
27185
27348
  updatedRows.push(finalRow);
27349
+ replacedRows.push(oldRow);
27186
27350
  fireRowTriggers(env, table, "after", "update", oldRow, finalRow);
27187
27351
  }
27352
+ fireStatementTriggers(env, table, "after", "update", { oldRows: replacedRows, newRows: updatedRows });
27188
27353
  env.ctx.state.changes = updateCount;
27189
27354
  if (stmt.returning) {
27190
27355
  const res = evalReturning(env, table, label, stmt.returning, updatedRows, "UPDATE");
@@ -27196,6 +27361,7 @@ function executeDelete(env0, stmt) {
27196
27361
  const env = applyWith(env0, stmt.with);
27197
27362
  const table = requireTargetTable(env, stmt.table, "delete from");
27198
27363
  const label = stmt.alias ?? table.name;
27364
+ fireStatementTriggers(env, table, "before", "delete");
27199
27365
  const rows = table.mutableRows();
27200
27366
  const usingSource = stmt.using.length > 0 ? buildFrom(env, stmt.using) : null;
27201
27367
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
@@ -27233,6 +27399,7 @@ function executeDelete(env0, stmt) {
27233
27399
  fireRowTriggers(env, table, "after", "delete", row, null);
27234
27400
  }
27235
27401
  if (deletedRows.length > 0) rebuildTableIndexes(env, table);
27402
+ fireStatementTriggers(env, table, "after", "delete", { oldRows: deletedRows, newRows: [] });
27236
27403
  env.ctx.state.changes = deletedRows.length;
27237
27404
  if (stmt.returning) {
27238
27405
  const res = evalReturning(env, table, label, stmt.returning, deletedRows, "DELETE");
@@ -29375,7 +29542,7 @@ function pgTrigger(ctx) {
29375
29542
  let oid = 4e4;
29376
29543
  for (const t of allTables(ctx.state)) {
29377
29544
  for (const tr of t.triggers) {
29378
- rows.push([oid++, tr.name, t.oid, !tr.forEachRow]);
29545
+ rows.push([oid++, tr.name, t.oid, !tr.forEachRow, tr.oldTable ?? null, tr.newTable ?? null]);
29379
29546
  }
29380
29547
  }
29381
29548
  return rel2(
@@ -29383,7 +29550,9 @@ function pgTrigger(ctx) {
29383
29550
  ["oid", "oid"],
29384
29551
  ["tgname", "name"],
29385
29552
  ["tgrelid", "oid"],
29386
- ["tgisinternal", "bool"]
29553
+ ["tgisinternal", "bool"],
29554
+ ["tgoldtable", "name"],
29555
+ ["tgnewtable", "name"]
29387
29556
  ],
29388
29557
  rows,
29389
29558
  "pg_trigger"
@@ -30810,7 +30979,7 @@ function executeCreateIndex(env, stmt) {
30810
30979
  }
30811
30980
  }
30812
30981
  }
30813
- assertGinTrgmIndex(state, table, stmt);
30982
+ assertGinTrgmIndex(state, table, stmt, (expr) => expressionTypeOf(env, table, expr));
30814
30983
  const method = stmt.using ?? "btree";
30815
30984
  if (!ACCESS_METHODS.has(method) || method === "heap") {
30816
30985
  throw pgError("undefined_object", `access method "${method}" does not exist`, "42704");
@@ -31065,6 +31234,50 @@ function executeCreateFunction(env, stmt) {
31065
31234
  schema.functions.set(name2, existing);
31066
31235
  return commandResult("CREATE FUNCTION", 0);
31067
31236
  }
31237
+ function validateTransitionTables(stmt) {
31238
+ const transitions = stmt.transitions ?? [];
31239
+ if (transitions.length === 0) return {};
31240
+ const invalid = (message2) => pgError("invalid_table_definition", message2, "42P17");
31241
+ const unsupportedClause = (message2) => pgError("feature_not_supported", message2, "0A000");
31242
+ if (stmt.timing !== "after") throw invalid("transition table name can only be specified for an AFTER trigger");
31243
+ if (stmt.events.some((e) => e.event === "truncate")) {
31244
+ throw unsupportedClause("TRUNCATE triggers with transition tables are not supported");
31245
+ }
31246
+ if (stmt.events.some((e) => e.columns !== null)) {
31247
+ throw unsupportedClause("transition tables cannot be specified for triggers with column lists");
31248
+ }
31249
+ if (stmt.events.length > 1) {
31250
+ throw unsupportedClause("transition tables cannot be specified for triggers with more than one event");
31251
+ }
31252
+ const event = stmt.events[0].event;
31253
+ let oldTable;
31254
+ let newTable;
31255
+ for (const transition of transitions) {
31256
+ if (transition.isNew) {
31257
+ if (event !== "insert" && event !== "update") {
31258
+ throw invalid("NEW TABLE can only be specified for an INSERT or UPDATE trigger");
31259
+ }
31260
+ if (newTable !== void 0) throw invalid("NEW TABLE cannot be specified multiple times");
31261
+ newTable = transition.name;
31262
+ } else {
31263
+ if (event !== "delete" && event !== "update") {
31264
+ throw invalid("OLD TABLE can only be specified for a DELETE or UPDATE trigger");
31265
+ }
31266
+ if (oldTable !== void 0) throw invalid("OLD TABLE cannot be specified multiple times");
31267
+ oldTable = transition.name;
31268
+ }
31269
+ }
31270
+ if (oldTable !== void 0 && oldTable === newTable) {
31271
+ throw invalid("OLD TABLE name and NEW TABLE name cannot be the same");
31272
+ }
31273
+ if (stmt.forEachRow) {
31274
+ throw pgError("unsupported", "transition tables on FOR EACH ROW triggers are not supported", "0A000");
31275
+ }
31276
+ return {
31277
+ ...oldTable !== void 0 ? { oldTable } : {},
31278
+ ...newTable !== void 0 ? { newTable } : {}
31279
+ };
31280
+ }
31068
31281
  function executeCreateTrigger(env, stmt) {
31069
31282
  if (stmt.constraint && (stmt.timing !== "after" || !stmt.forEachRow))
31070
31283
  throw pgError("syntax", "constraint triggers must be AFTER ROW triggers", "42601");
@@ -31074,6 +31287,7 @@ function executeCreateTrigger(env, stmt) {
31074
31287
  throw pgError("undefined_table", `relation "${stmt.table.join(".")}" does not exist`, "42P01");
31075
31288
  }
31076
31289
  const table = state.ensureWritableTable(found);
31290
+ const transitionTables = validateTransitionTables(stmt);
31077
31291
  const fnSchema = stmt.funcName.length >= 2 ? stmt.funcName[stmt.funcName.length - 2] : null;
31078
31292
  const fnName = stmt.funcName[stmt.funcName.length - 1];
31079
31293
  const fns = state.findFunctions(stmt.funcName);
@@ -31092,6 +31306,7 @@ function executeCreateTrigger(env, stmt) {
31092
31306
  timing: stmt.timing,
31093
31307
  events: stmt.events,
31094
31308
  forEachRow: stmt.forEachRow,
31309
+ ...transitionTables,
31095
31310
  when: stmt.when,
31096
31311
  funcSchema: fnSchema ?? fns[0].schema,
31097
31312
  funcName: fnName,