@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.
@@ -90,6 +90,10 @@ export interface TriggerMeta {
90
90
  timing: "before" | "after" | "instead_of";
91
91
  events: CreateTriggerStmt["events"];
92
92
  forEachRow: boolean;
93
+ /** `REFERENCING OLD TABLE AS …` name; only on AFTER statement triggers for UPDATE or DELETE */
94
+ oldTable?: string;
95
+ /** `REFERENCING NEW TABLE AS …` name; only on AFTER statement triggers for INSERT or UPDATE */
96
+ newTable?: string;
93
97
  when: Expr | null;
94
98
  funcSchema: string;
95
99
  funcName: string;
package/dist/unstable.js CHANGED
@@ -9693,7 +9693,7 @@ function evalTrgmWordSimilar(ctx, left, right) {
9693
9693
  const score = wordSimilarity(a.v, b.v);
9694
9694
  return tv("bool", score >= WORD_SIMILARITY_THRESHOLD);
9695
9695
  }
9696
- function assertGinTrgmIndex(state, table, stmt) {
9696
+ function assertGinTrgmIndex(state, table, stmt, expressionType) {
9697
9697
  const trgmCols = stmt.columns.filter((col2) => isGinTrgmOpclass(col2.opclass));
9698
9698
  if (trgmCols.length === 0) return;
9699
9699
  if (trgmCols.length !== stmt.columns.length || stmt.columns.length !== 1) {
@@ -9713,11 +9713,7 @@ function assertGinTrgmIndex(state, table, stmt) {
9713
9713
  if (stmt.unique) {
9714
9714
  throw pgError("feature_not_supported", `access method "gin" does not support unique indexes`, "0A000");
9715
9715
  }
9716
- if (col.expr.type !== "colref" || col.expr.parts.length !== 1) {
9717
- throw unsupported("expression gin_trgm_ops");
9718
- }
9719
- const index = table.columnIndex(col.expr.parts[0]);
9720
- const typeId = table.columns[index].type.id;
9716
+ const typeId = col.expr.type === "colref" && col.expr.parts.length === 1 ? table.columns[table.columnIndex(col.expr.parts[0])].type.id : expressionType(col.expr);
9721
9717
  if (!GIN_TRGM_TYPES.has(typeId)) {
9722
9718
  throw pgError(
9723
9719
  "datatype_mismatch",
@@ -13355,6 +13351,17 @@ function tableScope(table, row) {
13355
13351
  const cols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: table.name }));
13356
13352
  return new RowScope(cols, row, null, /* @__PURE__ */ new Set([table.name]));
13357
13353
  }
13354
+ function expressionTypeOf(env, table, expr) {
13355
+ const scope = makeEvalScope(
13356
+ env,
13357
+ tableScope(
13358
+ table,
13359
+ table.columns.map(() => null)
13360
+ )
13361
+ );
13362
+ const v = evalExpr(env.ctx, scope, expr);
13363
+ return v.t === "unknown" ? "text" : v.t;
13364
+ }
13358
13365
  function checkNotNull(env, table, row) {
13359
13366
  void env;
13360
13367
  for (let i = 0; i < table.columns.length; i++) {
@@ -16277,7 +16284,6 @@ var Parser = class {
16277
16284
  } while (this.eatKw("or"));
16278
16285
  this.expectKw("on");
16279
16286
  const table = this.qualifiedName();
16280
- if (this.eatKw("referencing")) throw unsupported("trigger transition tables");
16281
16287
  let deferrable = false;
16282
16288
  let initiallyDeferred = false;
16283
16289
  if (this.eatKw("not")) this.expectKw("deferrable");
@@ -16290,6 +16296,23 @@ var Parser = class {
16290
16296
  this.errorAt(this.peek(), "only constraint triggers can be deferred");
16291
16297
  if (initiallyDeferred && !deferrable)
16292
16298
  this.errorAt(this.peek(), "constraint declared INITIALLY DEFERRED must be DEFERRABLE");
16299
+ const transitions = [];
16300
+ if (!constraint && this.eatKw("referencing")) {
16301
+ do {
16302
+ const isNew = this.eatKw("new");
16303
+ if (!isNew) this.expectKw("old");
16304
+ if (this.eatKw("row")) {
16305
+ throw pgError(
16306
+ "feature_not_supported",
16307
+ "ROW variable naming in the REFERENCING clause is not supported",
16308
+ "0A000"
16309
+ );
16310
+ }
16311
+ this.expectKw("table");
16312
+ this.eatKw("as");
16313
+ transitions.push({ isNew, name: this.ident() });
16314
+ } while (this.atKw("new") || this.atKw("old"));
16315
+ }
16293
16316
  let forEachRow = false;
16294
16317
  if (this.eatKw("for")) {
16295
16318
  this.eatKw("each");
@@ -16327,7 +16350,8 @@ var Parser = class {
16327
16350
  forEachRow,
16328
16351
  when,
16329
16352
  funcName,
16330
- funcArgs
16353
+ funcArgs,
16354
+ ...transitions.length > 0 ? { transitions } : {}
16331
16355
  };
16332
16356
  }
16333
16357
  // --- ALTER --------------------------------------------------------------------
@@ -20883,6 +20907,29 @@ function fireRowTriggers(env, table, timing, event, oldRow, newRow) {
20883
20907
  }
20884
20908
  return { row };
20885
20909
  }
20910
+ function transitionRelation(table, name, rows) {
20911
+ return {
20912
+ columns: table.columns.map((c) => ({ name: c.name, type: c.type.id, table: name })),
20913
+ rows
20914
+ };
20915
+ }
20916
+ function fireStatementTriggers(env, table, timing, event, transitions) {
20917
+ 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);
20918
+ for (const t of fired) {
20919
+ if (!executor) throw unsupported(`trigger "${t.name}" execution`);
20920
+ const ctes = /* @__PURE__ */ new Map();
20921
+ if (t.oldTable !== void 0)
20922
+ ctes.set(t.oldTable, transitionRelation(table, t.oldTable, transitions?.oldRows ?? []));
20923
+ if (t.newTable !== void 0)
20924
+ ctes.set(t.newTable, transitionRelation(table, t.newTable, transitions?.newRows ?? []));
20925
+ const changes = env.ctx.state.changes;
20926
+ try {
20927
+ executor({ ctx: env.ctx, params: null, ctes, outer: null }, table, t, event, null, null);
20928
+ } finally {
20929
+ env.ctx.state.changes = changes;
20930
+ }
20931
+ }
20932
+ }
20886
20933
  var keyOf = (schema, name) => JSON.stringify([schema, name]);
20887
20934
  function flushConstraintTriggers(state, all = false) {
20888
20935
  const env = { ctx: new EngineCtx(state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
@@ -22425,7 +22472,7 @@ function pgTrigger(ctx) {
22425
22472
  let oid = 4e4;
22426
22473
  for (const t of allTables(ctx.state)) {
22427
22474
  for (const tr of t.triggers) {
22428
- rows.push([oid++, tr.name, t.oid, !tr.forEachRow]);
22475
+ rows.push([oid++, tr.name, t.oid, !tr.forEachRow, tr.oldTable ?? null, tr.newTable ?? null]);
22429
22476
  }
22430
22477
  }
22431
22478
  return rel2(
@@ -22433,7 +22480,9 @@ function pgTrigger(ctx) {
22433
22480
  ["oid", "oid"],
22434
22481
  ["tgname", "name"],
22435
22482
  ["tgrelid", "oid"],
22436
- ["tgisinternal", "bool"]
22483
+ ["tgisinternal", "bool"],
22484
+ ["tgoldtable", "name"],
22485
+ ["tgnewtable", "name"]
22437
22486
  ],
22438
22487
  rows,
22439
22488
  "pg_trigger"
@@ -23857,7 +23906,7 @@ function executeCreateIndex(env, stmt) {
23857
23906
  }
23858
23907
  }
23859
23908
  }
23860
- assertGinTrgmIndex(state, table, stmt);
23909
+ assertGinTrgmIndex(state, table, stmt, (expr) => expressionTypeOf(env, table, expr));
23861
23910
  const method = stmt.using ?? "btree";
23862
23911
  if (!ACCESS_METHODS.has(method) || method === "heap") {
23863
23912
  throw pgError("undefined_object", `access method "${method}" does not exist`, "42704");
@@ -24112,6 +24161,50 @@ function executeCreateFunction(env, stmt) {
24112
24161
  schema.functions.set(name, existing);
24113
24162
  return commandResult("CREATE FUNCTION", 0);
24114
24163
  }
24164
+ function validateTransitionTables(stmt) {
24165
+ const transitions = stmt.transitions ?? [];
24166
+ if (transitions.length === 0) return {};
24167
+ const invalid = (message) => pgError("invalid_table_definition", message, "42P17");
24168
+ const unsupportedClause = (message) => pgError("feature_not_supported", message, "0A000");
24169
+ if (stmt.timing !== "after") throw invalid("transition table name can only be specified for an AFTER trigger");
24170
+ if (stmt.events.some((e) => e.event === "truncate")) {
24171
+ throw unsupportedClause("TRUNCATE triggers with transition tables are not supported");
24172
+ }
24173
+ if (stmt.events.some((e) => e.columns !== null)) {
24174
+ throw unsupportedClause("transition tables cannot be specified for triggers with column lists");
24175
+ }
24176
+ if (stmt.events.length > 1) {
24177
+ throw unsupportedClause("transition tables cannot be specified for triggers with more than one event");
24178
+ }
24179
+ const event = stmt.events[0].event;
24180
+ let oldTable;
24181
+ let newTable;
24182
+ for (const transition of transitions) {
24183
+ if (transition.isNew) {
24184
+ if (event !== "insert" && event !== "update") {
24185
+ throw invalid("NEW TABLE can only be specified for an INSERT or UPDATE trigger");
24186
+ }
24187
+ if (newTable !== void 0) throw invalid("NEW TABLE cannot be specified multiple times");
24188
+ newTable = transition.name;
24189
+ } else {
24190
+ if (event !== "delete" && event !== "update") {
24191
+ throw invalid("OLD TABLE can only be specified for a DELETE or UPDATE trigger");
24192
+ }
24193
+ if (oldTable !== void 0) throw invalid("OLD TABLE cannot be specified multiple times");
24194
+ oldTable = transition.name;
24195
+ }
24196
+ }
24197
+ if (oldTable !== void 0 && oldTable === newTable) {
24198
+ throw invalid("OLD TABLE name and NEW TABLE name cannot be the same");
24199
+ }
24200
+ if (stmt.forEachRow) {
24201
+ throw pgError("unsupported", "transition tables on FOR EACH ROW triggers are not supported", "0A000");
24202
+ }
24203
+ return {
24204
+ ...oldTable !== void 0 ? { oldTable } : {},
24205
+ ...newTable !== void 0 ? { newTable } : {}
24206
+ };
24207
+ }
24115
24208
  function executeCreateTrigger(env, stmt) {
24116
24209
  if (stmt.constraint && (stmt.timing !== "after" || !stmt.forEachRow))
24117
24210
  throw pgError("syntax", "constraint triggers must be AFTER ROW triggers", "42601");
@@ -24121,6 +24214,7 @@ function executeCreateTrigger(env, stmt) {
24121
24214
  throw pgError("undefined_table", `relation "${stmt.table.join(".")}" does not exist`, "42P01");
24122
24215
  }
24123
24216
  const table = state.ensureWritableTable(found);
24217
+ const transitionTables = validateTransitionTables(stmt);
24124
24218
  const fnSchema = stmt.funcName.length >= 2 ? stmt.funcName[stmt.funcName.length - 2] : null;
24125
24219
  const fnName = stmt.funcName[stmt.funcName.length - 1];
24126
24220
  const fns = state.findFunctions(stmt.funcName);
@@ -24139,6 +24233,7 @@ function executeCreateTrigger(env, stmt) {
24139
24233
  timing: stmt.timing,
24140
24234
  events: stmt.events,
24141
24235
  forEachRow: stmt.forEachRow,
24236
+ ...transitionTables,
24142
24237
  when: stmt.when,
24143
24238
  funcSchema: fnSchema ?? fns[0].schema,
24144
24239
  funcName: fnName,
@@ -25093,7 +25188,13 @@ function executeInsert(env0, stmt) {
25093
25188
  const sourceRows = insertSourceRows(env, stmt, colIdxs);
25094
25189
  const insertedRows = [];
25095
25190
  let insertedCount = 0;
25191
+ const addedRows = [];
25192
+ const conflictOldRows = [];
25193
+ const conflictNewRows = [];
25194
+ const updatesOnConflict = stmt.onConflict !== null && stmt.onConflict.action !== "nothing";
25096
25195
  return withStatementRollback(env, () => {
25196
+ fireStatementTriggers(env, table, "before", "insert");
25197
+ if (updatesOnConflict) fireStatementTriggers(env, table, "before", "update");
25097
25198
  for (const srcRow of sourceRows) {
25098
25199
  const row = table.columns.map(() => null);
25099
25200
  const provided = /* @__PURE__ */ new Set();
@@ -25139,6 +25240,7 @@ function executeInsert(env0, stmt) {
25139
25240
  }
25140
25241
  if (conflictIdx !== null && conflictWhereOk) {
25141
25242
  if (stmt.onConflict.action === "nothing") continue;
25243
+ const replaced = rows[conflictIdx];
25142
25244
  const did = applyOnConflictUpdate(
25143
25245
  env,
25144
25246
  table,
@@ -25151,6 +25253,8 @@ function executeInsert(env0, stmt) {
25151
25253
  if (did) {
25152
25254
  insertedCount++;
25153
25255
  insertedRows.push(rows[conflictIdx]);
25256
+ conflictOldRows.push(replaced);
25257
+ conflictNewRows.push(rows[conflictIdx]);
25154
25258
  fireRowTriggers(env, table, "after", "update", null, rows[conflictIdx]);
25155
25259
  }
25156
25260
  continue;
@@ -25165,8 +25269,13 @@ function executeInsert(env0, stmt) {
25165
25269
  indexInsertRow(env, table, rows.length - 1, newRow);
25166
25270
  insertedCount++;
25167
25271
  insertedRows.push(newRow);
25272
+ addedRows.push(newRow);
25168
25273
  fireRowTriggers(env, table, "after", "insert", null, newRow);
25169
25274
  }
25275
+ fireStatementTriggers(env, table, "after", "insert", { oldRows: [], newRows: addedRows });
25276
+ if (updatesOnConflict) {
25277
+ fireStatementTriggers(env, table, "after", "update", { oldRows: conflictOldRows, newRows: conflictNewRows });
25278
+ }
25170
25279
  env.ctx.state.changes = insertedCount;
25171
25280
  if (stmt.returning) {
25172
25281
  const res = evalReturning(env, table, label, stmt.returning, insertedRows, "INSERT");
@@ -25247,10 +25356,12 @@ function executeUpdate(env0, stmt) {
25247
25356
  const env = applyWith(env0, stmt.with);
25248
25357
  const table = requireTargetTable(env, stmt.table, "update");
25249
25358
  const label = stmt.alias ?? table.name;
25359
+ fireStatementTriggers(env, table, "before", "update");
25250
25360
  const fromSource = stmt.from.length > 0 ? buildFrom(env, stmt.from) : null;
25251
25361
  const rows = table.mutableRows();
25252
25362
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
25253
25363
  const updatedRows = [];
25364
+ const replacedRows = [];
25254
25365
  let updateCount = 0;
25255
25366
  for (let ri = 0; ri < rows.length; ri++) {
25256
25367
  const oldRow = rows[ri];
@@ -25293,8 +25404,10 @@ function executeUpdate(env0, stmt) {
25293
25404
  handleReferencedUpdate(env, table, [oldRow], [finalRow]);
25294
25405
  updateCount++;
25295
25406
  updatedRows.push(finalRow);
25407
+ replacedRows.push(oldRow);
25296
25408
  fireRowTriggers(env, table, "after", "update", oldRow, finalRow);
25297
25409
  }
25410
+ fireStatementTriggers(env, table, "after", "update", { oldRows: replacedRows, newRows: updatedRows });
25298
25411
  env.ctx.state.changes = updateCount;
25299
25412
  if (stmt.returning) {
25300
25413
  const res = evalReturning(env, table, label, stmt.returning, updatedRows, "UPDATE");
@@ -25306,6 +25419,7 @@ function executeDelete(env0, stmt) {
25306
25419
  const env = applyWith(env0, stmt.with);
25307
25420
  const table = requireTargetTable(env, stmt.table, "delete from");
25308
25421
  const label = stmt.alias ?? table.name;
25422
+ fireStatementTriggers(env, table, "before", "delete");
25309
25423
  const rows = table.mutableRows();
25310
25424
  const usingSource = stmt.using.length > 0 ? buildFrom(env, stmt.using) : null;
25311
25425
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
@@ -25343,6 +25457,7 @@ function executeDelete(env0, stmt) {
25343
25457
  fireRowTriggers(env, table, "after", "delete", row, null);
25344
25458
  }
25345
25459
  if (deletedRows.length > 0) rebuildTableIndexes(env, table);
25460
+ fireStatementTriggers(env, table, "after", "delete", { oldRows: deletedRows, newRows: [] });
25346
25461
  env.ctx.state.changes = deletedRows.length;
25347
25462
  if (stmt.returning) {
25348
25463
  const res = evalReturning(env, table, label, stmt.returning, deletedRows, "DELETE");