@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/CHANGELOG.md +7 -1
- package/COMPATIBILITY.md +2 -1
- package/dist/admin.js +243 -78
- package/dist/admin.js.map +3 -3
- package/dist/ast/nodes.d.ts +5 -0
- package/dist/constraints/enforce.d.ts +7 -1
- package/dist/executor/triggers.d.ts +13 -0
- package/dist/functions/pgtrgm.d.ts +4 -4
- package/dist/index.js +127 -12
- package/dist/index.js.map +2 -2
- package/dist/server.js +126 -11
- package/dist/server.js.map +3 -3
- package/dist/storage/database-state.d.ts +4 -0
- package/dist/unstable.js +126 -11
- package/dist/unstable.js.map +2 -2
- package/dist/wire/cli.js +236 -21
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/fleet.d.ts +18 -0
- package/dist/wire/index.js +126 -11
- package/dist/wire/index.js.map +3 -3
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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");
|