@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
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|