@crvouga/mockingbird-service-postgres 3.4.1 → 3.6.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
@@ -257,6 +257,13 @@ function isPostgresError(err) {
257
257
  return typeof sqlState === "string" && /^[0-9A-Z]{5}$/.test(sqlState);
258
258
  }
259
259
 
260
+ // src/storage/guc.ts
261
+ function validateStorageSetting(name, value) {
262
+ if (name === "default_table_access_method" && value !== "heap" || name === "default_tablespace" && value !== "" && value !== "pg_default") {
263
+ throw pgError("invalid_parameter_value", `invalid value for parameter "${name}": "${value}"`, "22023");
264
+ }
265
+ }
266
+
260
267
  // src/runtime/prng.ts
261
268
  var Prng = class _Prng {
262
269
  state;
@@ -3712,6 +3719,9 @@ var DatabaseState = class _DatabaseState {
3712
3719
  changes = 0;
3713
3720
  oidCounter = 16384;
3714
3721
  inTransaction = false;
3722
+ /** Transaction-only events: included in rollback clones, never in exported snapshots. */
3723
+ pendingTriggers = [];
3724
+ constraintModes = /* @__PURE__ */ new Map();
3715
3725
  /** most recent sequence touched by nextval/setval, for lastval() */
3716
3726
  lastSequence = null;
3717
3727
  /** Session currval per sequence; absent entry means currval is undefined. */
@@ -3754,6 +3764,8 @@ var DatabaseState = class _DatabaseState {
3754
3764
  this.settings.set("row_security", "on");
3755
3765
  this.settings.set("array_nulls", "on");
3756
3766
  this.settings.set("backslash_quote", "safe_encoding");
3767
+ this.settings.set("default_tablespace", "");
3768
+ this.settings.set("default_table_access_method", "heap");
3757
3769
  }
3758
3770
  nextOid() {
3759
3771
  return this.oidCounter++;
@@ -3913,6 +3925,8 @@ var DatabaseState = class _DatabaseState {
3913
3925
  s.prepared = new Map(this.prepared);
3914
3926
  s.changes = this.changes;
3915
3927
  s.inTransaction = this.inTransaction;
3928
+ s.pendingTriggers = this.pendingTriggers.slice();
3929
+ s.constraintModes = new Map(this.constraintModes);
3916
3930
  s.lastSequence = this.lastSequence ? { ...this.lastSequence } : null;
3917
3931
  s.sequenceCurrval = new Map(this.sequenceCurrval);
3918
3932
  s.tableStats = new Map(this.tableStats);
@@ -3930,6 +3944,8 @@ var DatabaseState = class _DatabaseState {
3930
3944
  s.prepared = new Map(this.prepared);
3931
3945
  s.changes = this.changes;
3932
3946
  s.inTransaction = this.inTransaction;
3947
+ s.pendingTriggers = this.pendingTriggers.slice();
3948
+ s.constraintModes = new Map(this.constraintModes);
3933
3949
  s.lastSequence = this.lastSequence ? { ...this.lastSequence } : null;
3934
3950
  s.sequenceCurrval = new Map(this.sequenceCurrval);
3935
3951
  s.tableStats = new Map(this.tableStats);
@@ -4002,6 +4018,8 @@ var DatabaseState = class _DatabaseState {
4002
4018
  this.settings = other.settings;
4003
4019
  this.localSettings = other.localSettings;
4004
4020
  this.prepared = other.prepared;
4021
+ this.pendingTriggers = other.pendingTriggers.slice();
4022
+ this.constraintModes = new Map(other.constraintModes);
4005
4023
  this.changes = other.changes;
4006
4024
  this.lastSequence = other.lastSequence;
4007
4025
  this.sequenceCurrval = other.sequenceCurrval;
@@ -4018,90 +4036,192 @@ var DatabaseState = class _DatabaseState {
4018
4036
  }
4019
4037
  };
4020
4038
 
4021
- // src/transactions/manager.ts
4022
- var TransactionManager = class {
4023
- constructor(state) {
4024
- this.state = state;
4039
+ // src/types/timezone.ts
4040
+ var intlCache = /* @__PURE__ */ new Map();
4041
+ function intlFor(zone) {
4042
+ const cached = intlCache.get(zone);
4043
+ if (cached) return cached;
4044
+ try {
4045
+ const fmt = new Intl.DateTimeFormat("en-US", {
4046
+ timeZone: zone,
4047
+ timeZoneName: "longOffset",
4048
+ year: "numeric"
4049
+ });
4050
+ intlCache.set(zone, fmt);
4051
+ return fmt;
4052
+ } catch {
4053
+ return null;
4054
+ }
4055
+ }
4056
+ function parseFixedOffset(zone) {
4057
+ const m = /^([+-]?)(\d{1,2})(?::(\d{2}))?(?::(\d{2}))?$/.exec(zone.trim());
4058
+ if (!m) return null;
4059
+ const sign = m[1] === "-" ? -1 : 1;
4060
+ return sign * (Number(m[2]) * 3600 + Number(m[3] ?? 0) * 60 + Number(m[4] ?? 0));
4061
+ }
4062
+ function parsePosixOffset(zone) {
4063
+ const m = /^(?:utc|gmt)([+-])(\d{1,2})(?::(\d{2}))?$/i.exec(zone.trim());
4064
+ if (!m) return null;
4065
+ const sign = m[1] === "-" ? 1 : -1;
4066
+ return sign * (Number(m[2]) * 3600 + Number(m[3] ?? 0) * 60);
4067
+ }
4068
+ function pgMicrosToUnixMs(utcMicros) {
4069
+ return Number((utcMicros - UNIX_EPOCH_MICROS_FROM_PG) / 1000n);
4070
+ }
4071
+ function zoneOffsetAtUtc(zone, utcMicros) {
4072
+ const z = zone.trim();
4073
+ const lower = z.toLowerCase();
4074
+ if (lower === "utc" || lower === "gmt" || lower === "z" || lower === "zulu" || lower === "universal") return 0;
4075
+ const posix = parsePosixOffset(z);
4076
+ if (posix !== null) return posix;
4077
+ const fixed = parseFixedOffset(z);
4078
+ if (fixed !== null) return fixed;
4079
+ const fmt = intlFor(z);
4080
+ if (fmt) {
4081
+ const ms = clampDateMs(pgMicrosToUnixMs(utcMicros));
4082
+ const parts = fmt.formatToParts(new Date(ms));
4083
+ const tzName = parts.find((p) => p.type === "timeZoneName")?.value ?? "GMT";
4084
+ const m = /GMT([+-])(\d{2}):(\d{2})(?::(\d{2}))?/.exec(tzName);
4085
+ if (!m) return 0;
4086
+ const sign = m[1] === "-" ? -1 : 1;
4087
+ return sign * (Number(m[2]) * 3600 + Number(m[3]) * 60 + Number(m[4] ?? 0));
4025
4088
  }
4089
+ throw pgError("invalid_parameter_value", `time zone "${zone}" not recognized`);
4090
+ }
4091
+ function clampDateMs(ms) {
4092
+ const MAX = 864e13;
4093
+ return Math.max(-MAX, Math.min(MAX, ms));
4094
+ }
4095
+ function zoneOffsetForNaive(zone, naiveMicros) {
4096
+ const first = zoneOffsetAtUtc(zone, naiveMicros);
4097
+ const second = zoneOffsetAtUtc(zone, naiveMicros - BigInt(first) * 1000000n);
4098
+ return second;
4099
+ }
4100
+ function validateZone(zone) {
4101
+ zoneOffsetAtUtc(zone, 0n);
4102
+ }
4103
+
4104
+ // src/expressions/context.ts
4105
+ var EngineCtx = class {
4026
4106
  state;
4027
- base = null;
4028
- savepoints = [];
4029
- freezeDepth = 0;
4030
- get inTransaction() {
4031
- return this.base !== null;
4107
+ /** transaction timestamp (now(), current_timestamp) — UTC micros since PG epoch */
4108
+ txNow;
4109
+ /** statement timestamp */
4110
+ stmtNow;
4111
+ constructor(state, txNow) {
4112
+ this.state = state;
4113
+ const nowMs = state.clock().getTime();
4114
+ const nowMicros = BigInt(Math.round(nowMs)) * 1000n + UNIX_EPOCH_MICROS_FROM_PG;
4115
+ this.txNow = txNow ?? nowMicros;
4116
+ this.stmtNow = nowMicros;
4032
4117
  }
4033
- takeSnapshot() {
4034
- this.state.freezeShared();
4035
- this.freezeDepth++;
4036
- return { state: this.state.cloneShallow(), prngState: this.state.prng.getState() };
4118
+ timezone() {
4119
+ return this.state.getSetting("timezone") ?? "UTC";
4037
4120
  }
4038
- restore(snap) {
4039
- this.state.restoreFrom(snap.state);
4040
- this.state.prng.setState(snap.prngState);
4121
+ namespaceOid(name) {
4122
+ const oid = builtinNamespaceOid(name) ?? this.state.schemas.get(name)?.oid;
4123
+ if (oid === void 0) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
4124
+ return oid;
4041
4125
  }
4042
- thawOnce() {
4043
- if (this.freezeDepth > 0) {
4044
- this.state.thawShared();
4045
- this.freezeDepth--;
4126
+ namespaceName(oid) {
4127
+ const builtin = builtinNamespaceName(oid);
4128
+ if (builtin !== void 0) return builtin;
4129
+ for (const schema of this.state.schemas.values()) {
4130
+ if (schema.oid === oid) return schema.name;
4046
4131
  }
4132
+ return null;
4047
4133
  }
4048
- begin() {
4049
- if (this.base !== null) {
4050
- return;
4051
- }
4052
- this.base = this.takeSnapshot();
4053
- this.state.inTransaction = true;
4054
- assert(this.freezeDepth === 1, "BEGIN freeze depth");
4134
+ zoneOffsetAt(utcMicros) {
4135
+ return zoneOffsetAtUtc(this.timezone(), utcMicros);
4055
4136
  }
4056
- commit() {
4057
- this.base = null;
4058
- this.savepoints = [];
4059
- this.state.inTransaction = false;
4060
- this.state.localSettings.clear();
4061
- while (this.freezeDepth > 0) this.thawOnce();
4137
+ zoneOffsetForNaive(naiveMicros) {
4138
+ return zoneOffsetForNaive(this.timezone(), naiveMicros);
4062
4139
  }
4063
- rollback() {
4064
- if (this.base !== null) {
4065
- this.restore(this.base);
4066
- }
4067
- this.base = null;
4068
- this.savepoints = [];
4069
- this.state.inTransaction = false;
4070
- this.state.localSettings.clear();
4071
- while (this.freezeDepth > 0) this.thawOnce();
4140
+ enumLabels(enumType) {
4141
+ if (!isEnumType(enumType)) return null;
4142
+ const e = this.state.findEnumByKey(enumType.slice(5));
4143
+ return e?.labels ?? null;
4072
4144
  }
4073
- savepoint(name) {
4074
- if (this.base === null) {
4075
- throw pgError("transaction_state", "SAVEPOINT can only be used in transaction blocks", "25P01");
4076
- }
4077
- this.savepoints.push({ name, snap: this.takeSnapshot() });
4145
+ enumHasLabel(enumType, label) {
4146
+ const labels = this.enumLabels(enumType);
4147
+ return labels === null ? true : labels.includes(label);
4078
4148
  }
4079
- releaseSavepoint(name) {
4080
- const idx = this.findSavepoint(name);
4081
- this.savepoints.splice(idx);
4149
+ enumOrder(enumType, label) {
4150
+ const labels = this.enumLabels(enumType);
4151
+ if (!labels) return 0;
4152
+ const idx = labels.indexOf(label);
4153
+ return idx === -1 ? labels.length : idx;
4082
4154
  }
4083
- rollbackToSavepoint(name) {
4084
- const idx = this.findSavepoint(name);
4085
- const sp = this.savepoints[idx];
4086
- this.restore(sp.snap);
4087
- this.savepoints.splice(idx + 1);
4155
+ requireEnum(enumType) {
4156
+ const labels = this.enumLabels(enumType);
4157
+ if (!labels) throw pgError("undefined_object", `type "${enumType.slice(5)}" does not exist`);
4158
+ return labels;
4088
4159
  }
4089
- findSavepoint(name) {
4090
- if (this.base === null) {
4091
- throw pgError("transaction_state", `SAVEPOINT can only be used in transaction blocks`, "25P01");
4092
- }
4093
- for (let i = this.savepoints.length - 1; i >= 0; i--) {
4094
- if (this.savepoints[i].name === name) return i;
4160
+ };
4161
+
4162
+ // src/api/bind.ts
4163
+ var INT4_MIN2 = -2147483648;
4164
+ var INT4_MAX2 = 2147483647;
4165
+ function bindValueToTyped(value, index) {
4166
+ if (value === null || value === void 0) return tv(UNKNOWN, null);
4167
+ switch (typeof value) {
4168
+ case "boolean":
4169
+ return tv("bool", value);
4170
+ case "number":
4171
+ if (Number.isInteger(value) && value >= INT4_MIN2 && value <= INT4_MAX2) {
4172
+ return tv("int4", value);
4173
+ }
4174
+ if (Number.isInteger(value) && Number.isSafeInteger(value)) {
4175
+ return tv("int8", BigInt(value));
4176
+ }
4177
+ return tv("float8", value);
4178
+ case "bigint":
4179
+ if (value < -9223372036854775808n || value > 9223372036854775807n) {
4180
+ throw pgError("numeric_value_out_of_range", `bigint parameter $${index + 1} out of int8 range`, "22003");
4181
+ }
4182
+ return tv("int8", value);
4183
+ case "string":
4184
+ return tv(UNKNOWN, value);
4185
+ case "object": {
4186
+ if (value instanceof Uint8Array) return tv("bytea", value);
4187
+ if (value instanceof Date) {
4188
+ const ms = value.getTime();
4189
+ if (Number.isNaN(ms)) {
4190
+ throw pgError("invalid_datetime", `invalid Date parameter $${index + 1}`, "22008");
4191
+ }
4192
+ return tv("timestamptz", BigInt(Math.round(ms)) * 1000n + UNIX_EPOCH_MICROS_FROM_PG);
4193
+ }
4194
+ break;
4095
4195
  }
4096
- throw pgError("invalid_savepoint_specification", `savepoint "${name}" does not exist`, "3B001");
4196
+ default:
4197
+ break;
4097
4198
  }
4098
- reset() {
4099
- this.base = null;
4100
- this.savepoints = [];
4101
- this.state.inTransaction = false;
4102
- this.freezeDepth = 0;
4199
+ throw pgError("misuse", `unsupported parameter type for $${index + 1}: ${typeof value}`, "XX000");
4200
+ }
4201
+ function datumToJs(t, v, ctx, int8 = "bigint") {
4202
+ if (v === null) return null;
4203
+ switch (t) {
4204
+ case "bool":
4205
+ return v;
4206
+ case "int2":
4207
+ case "int4":
4208
+ case "oid":
4209
+ return typeof v === "bigint" ? Number(v) : v;
4210
+ case "int8": {
4211
+ const n = typeof v === "bigint" ? v : BigInt(v);
4212
+ if (int8 === "string") return n.toString();
4213
+ if (int8 === "number") return Number(n);
4214
+ return n;
4215
+ }
4216
+ case "float4":
4217
+ case "float8":
4218
+ return v;
4219
+ case "bytea":
4220
+ return v;
4221
+ default:
4222
+ return datumText(t === UNKNOWN ? "text" : t, v, ctx);
4103
4223
  }
4104
- };
4224
+ }
4105
4225
 
4106
4226
  // src/types/cast.ts
4107
4227
  var UTC_CAST_ENV = {
@@ -4579,259 +4699,6 @@ function unifyTypes(a, b) {
4579
4699
  return null;
4580
4700
  }
4581
4701
 
4582
- // src/types/resolve.ts
4583
- var SERIALS = {
4584
- smallserial: "int2",
4585
- serial2: "int2",
4586
- serial: "int4",
4587
- serial4: "int4",
4588
- bigserial: "int8",
4589
- serial8: "int8"
4590
- };
4591
- function modOf(t, mods) {
4592
- if (mods.length === 0) return null;
4593
- if (t === "numeric") return { a: mods[0], b: mods[1] ?? 0 };
4594
- return { a: mods[0] };
4595
- }
4596
- function resolveTypeName(state, tn) {
4597
- const joined = tn.parts.join(".");
4598
- const bare = tn.parts.length === 1 ? tn.parts[0] : tn.parts.length === 2 && tn.parts[0] === "pg_catalog" ? tn.parts[1] : null;
4599
- if (bare !== null) {
4600
- const serial = SERIALS[bare];
4601
- if (serial !== void 0 && tn.arrayDims === 0) {
4602
- return { column: { id: serial, mod: null }, domain: null, serial };
4603
- }
4604
- const builtin = normalizeTypeName(bare);
4605
- if (builtin !== null) {
4606
- const id = tn.arrayDims > 0 ? arrayTypeOf(builtin) : builtin;
4607
- return { column: { id, mod: modOf(builtin, tn.mods) }, domain: null, serial: null };
4608
- }
4609
- }
4610
- const enumData = state.findEnum(tn.parts);
4611
- if (enumData) {
4612
- const elem = `enum:${enumData.schema}.${enumData.name}`;
4613
- const id = tn.arrayDims > 0 ? arrayTypeOf(elem) : elem;
4614
- return { column: { id, mod: null }, domain: null, serial: null };
4615
- }
4616
- const domainData = state.findDomain(tn.parts);
4617
- if (domainData) {
4618
- if (tn.arrayDims > 0) {
4619
- const id = arrayTypeOf(domainData.baseType.id);
4620
- return { column: { id, mod: domainData.baseType.mod }, domain: null, serial: null };
4621
- }
4622
- return {
4623
- column: domainData.baseType,
4624
- domain: `${domainData.schema}.${domainData.name}`,
4625
- serial: null
4626
- };
4627
- }
4628
- throw pgError("undefined_object", `type "${joined}" does not exist`);
4629
- }
4630
-
4631
- // src/executor/relation.ts
4632
- var RowScope = class {
4633
- constructor(columns, row, parent = null, rangeVars = /* @__PURE__ */ new Set()) {
4634
- this.columns = columns;
4635
- this.row = row;
4636
- this.parent = parent;
4637
- this.rangeVars = rangeVars;
4638
- }
4639
- columns;
4640
- row;
4641
- parent;
4642
- rangeVars;
4643
- /** Resolve a (possibly qualified) column reference. undefined = not found. */
4644
- lookup(parts) {
4645
- if (parts.length === 1) {
4646
- const name2 = parts[0];
4647
- let found2;
4648
- let count2 = 0;
4649
- for (let i = 0; i < this.columns.length; i++) {
4650
- const c = this.columns[i];
4651
- if (c.name === name2 && !c.hidden) {
4652
- count2++;
4653
- if (count2 > 1) {
4654
- throw pgError("ambiguous_column", `column reference "${name2}" is ambiguous`);
4655
- }
4656
- found2 = tv(c.type, this.row[i] ?? null);
4657
- }
4658
- }
4659
- if (found2 !== void 0) return found2;
4660
- if (this.rangeVars.has(name2)) {
4661
- return this.wholeRow(name2);
4662
- }
4663
- return this.parent?.lookup(parts);
4664
- }
4665
- const table = parts[parts.length - 2];
4666
- const name = parts[parts.length - 1];
4667
- let found;
4668
- let count = 0;
4669
- for (let i = 0; i < this.columns.length; i++) {
4670
- const c = this.columns[i];
4671
- if (c.table === table && c.name === name) {
4672
- count++;
4673
- if (count > 1) {
4674
- throw pgError("ambiguous_column", `column reference "${name}" is ambiguous`);
4675
- }
4676
- found = tv(c.type, this.row[i] ?? null);
4677
- }
4678
- }
4679
- if (found !== void 0) return found;
4680
- return this.parent?.lookup(parts);
4681
- }
4682
- /** Build a record value from all columns belonging to a range variable. */
4683
- wholeRow(rangeVar) {
4684
- const types = [];
4685
- const values = [];
4686
- const names = [];
4687
- for (let i = 0; i < this.columns.length; i++) {
4688
- const c = this.columns[i];
4689
- if (c.table === rangeVar) {
4690
- types.push(c.type);
4691
- values.push(this.row[i] ?? null);
4692
- names.push(c.name);
4693
- }
4694
- }
4695
- if (types.length === 0) return void 0;
4696
- return tv("record", { kind: "pgrecord", types, values, names });
4697
- }
4698
- hasRangeVar(name) {
4699
- if (this.rangeVars.has(name)) return true;
4700
- return this.parent?.hasRangeVar(name) ?? false;
4701
- }
4702
- };
4703
- function childEnv(env, outer) {
4704
- return {
4705
- ctx: env.ctx,
4706
- params: env.params,
4707
- ctes: env.ctes,
4708
- outer,
4709
- allowUnpopulatedMatviews: env.allowUnpopulatedMatviews
4710
- };
4711
- }
4712
- function relationResult(rel3, command2) {
4713
- const visible = rel3.columns.map((c, i) => ({ i, c })).filter(({ c }) => !c.hidden);
4714
- return {
4715
- columns: visible.map(({ c }) => ({ name: c.name, type: c.type })),
4716
- rows: rel3.rows.map((r) => visible.map(({ i }) => r[i] ?? null)),
4717
- command: command2,
4718
- rowCount: rel3.rows.length
4719
- };
4720
- }
4721
- function commandResult(command2, rowCount) {
4722
- return { columns: [], rows: [], command: command2, rowCount };
4723
- }
4724
- function inferColumnName(e) {
4725
- switch (e.type) {
4726
- case "colref":
4727
- return e.parts[e.parts.length - 1];
4728
- case "func":
4729
- return e.name[e.name.length - 1];
4730
- case "cast": {
4731
- const inner = inferColumnName(e.expr);
4732
- if (inner !== "?column?") return inner;
4733
- return e.target.parts[e.target.parts.length - 1];
4734
- }
4735
- case "collate":
4736
- return inferColumnName(e.expr);
4737
- case "case":
4738
- return "case";
4739
- case "array_ctor":
4740
- case "array_query":
4741
- return "array";
4742
- case "row":
4743
- return "row";
4744
- case "subquery_expr":
4745
- return e.kind === "exists" ? "exists" : "?column?";
4746
- case "bool_lit":
4747
- return "bool";
4748
- case "extract":
4749
- return "extract";
4750
- case "position":
4751
- return "position";
4752
- case "substring_sql":
4753
- return "substring";
4754
- case "overlay":
4755
- return "overlay";
4756
- case "trim":
4757
- return e.side === "leading" ? "ltrim" : e.side === "trailing" ? "rtrim" : "btrim";
4758
- case "field_select":
4759
- return e.field === "*" ? "?column?" : e.field;
4760
- case "at_time_zone":
4761
- return "timezone";
4762
- case "grouping_func":
4763
- return "grouping";
4764
- case "subscript":
4765
- return inferColumnName(e.base);
4766
- default:
4767
- return "?column?";
4768
- }
4769
- }
4770
-
4771
- // src/api/bind.ts
4772
- var INT4_MIN2 = -2147483648;
4773
- var INT4_MAX2 = 2147483647;
4774
- function bindValueToTyped(value, index) {
4775
- if (value === null || value === void 0) return tv(UNKNOWN, null);
4776
- switch (typeof value) {
4777
- case "boolean":
4778
- return tv("bool", value);
4779
- case "number":
4780
- if (Number.isInteger(value) && value >= INT4_MIN2 && value <= INT4_MAX2) {
4781
- return tv("int4", value);
4782
- }
4783
- if (Number.isInteger(value) && Number.isSafeInteger(value)) {
4784
- return tv("int8", BigInt(value));
4785
- }
4786
- return tv("float8", value);
4787
- case "bigint":
4788
- if (value < -9223372036854775808n || value > 9223372036854775807n) {
4789
- throw pgError("numeric_value_out_of_range", `bigint parameter $${index + 1} out of int8 range`, "22003");
4790
- }
4791
- return tv("int8", value);
4792
- case "string":
4793
- return tv(UNKNOWN, value);
4794
- case "object": {
4795
- if (value instanceof Uint8Array) return tv("bytea", value);
4796
- if (value instanceof Date) {
4797
- const ms = value.getTime();
4798
- if (Number.isNaN(ms)) {
4799
- throw pgError("invalid_datetime", `invalid Date parameter $${index + 1}`, "22008");
4800
- }
4801
- return tv("timestamptz", BigInt(Math.round(ms)) * 1000n + UNIX_EPOCH_MICROS_FROM_PG);
4802
- }
4803
- break;
4804
- }
4805
- default:
4806
- break;
4807
- }
4808
- throw pgError("misuse", `unsupported parameter type for $${index + 1}: ${typeof value}`, "XX000");
4809
- }
4810
- function datumToJs(t, v, ctx, int8 = "bigint") {
4811
- if (v === null) return null;
4812
- switch (t) {
4813
- case "bool":
4814
- return v;
4815
- case "int2":
4816
- case "int4":
4817
- case "oid":
4818
- return typeof v === "bigint" ? Number(v) : v;
4819
- case "int8": {
4820
- const n = typeof v === "bigint" ? v : BigInt(v);
4821
- if (int8 === "string") return n.toString();
4822
- if (int8 === "number") return Number(n);
4823
- return n;
4824
- }
4825
- case "float4":
4826
- case "float8":
4827
- return v;
4828
- case "bytea":
4829
- return v;
4830
- default:
4831
- return datumText(t === UNKNOWN ? "text" : t, v, ctx);
4832
- }
4833
- }
4834
-
4835
4702
  // src/functions/datetime-fns.ts
4836
4703
  function extractDatePart(ctx, field0, src0, asNumeric) {
4837
4704
  const field = field0.toLowerCase();
@@ -5746,71 +5613,6 @@ function getArrayFunctions() {
5746
5613
  return m;
5747
5614
  }
5748
5615
 
5749
- // src/types/timezone.ts
5750
- var intlCache = /* @__PURE__ */ new Map();
5751
- function intlFor(zone) {
5752
- const cached = intlCache.get(zone);
5753
- if (cached) return cached;
5754
- try {
5755
- const fmt = new Intl.DateTimeFormat("en-US", {
5756
- timeZone: zone,
5757
- timeZoneName: "longOffset",
5758
- year: "numeric"
5759
- });
5760
- intlCache.set(zone, fmt);
5761
- return fmt;
5762
- } catch {
5763
- return null;
5764
- }
5765
- }
5766
- function parseFixedOffset(zone) {
5767
- const m = /^([+-]?)(\d{1,2})(?::(\d{2}))?(?::(\d{2}))?$/.exec(zone.trim());
5768
- if (!m) return null;
5769
- const sign = m[1] === "-" ? -1 : 1;
5770
- return sign * (Number(m[2]) * 3600 + Number(m[3] ?? 0) * 60 + Number(m[4] ?? 0));
5771
- }
5772
- function parsePosixOffset(zone) {
5773
- const m = /^(?:utc|gmt)([+-])(\d{1,2})(?::(\d{2}))?$/i.exec(zone.trim());
5774
- if (!m) return null;
5775
- const sign = m[1] === "-" ? 1 : -1;
5776
- return sign * (Number(m[2]) * 3600 + Number(m[3] ?? 0) * 60);
5777
- }
5778
- function pgMicrosToUnixMs(utcMicros) {
5779
- return Number((utcMicros - UNIX_EPOCH_MICROS_FROM_PG) / 1000n);
5780
- }
5781
- function zoneOffsetAtUtc(zone, utcMicros) {
5782
- const z = zone.trim();
5783
- const lower = z.toLowerCase();
5784
- if (lower === "utc" || lower === "gmt" || lower === "z" || lower === "zulu" || lower === "universal") return 0;
5785
- const posix = parsePosixOffset(z);
5786
- if (posix !== null) return posix;
5787
- const fixed = parseFixedOffset(z);
5788
- if (fixed !== null) return fixed;
5789
- const fmt = intlFor(z);
5790
- if (fmt) {
5791
- const ms = clampDateMs(pgMicrosToUnixMs(utcMicros));
5792
- const parts = fmt.formatToParts(new Date(ms));
5793
- const tzName = parts.find((p) => p.type === "timeZoneName")?.value ?? "GMT";
5794
- const m = /GMT([+-])(\d{2}):(\d{2})(?::(\d{2}))?/.exec(tzName);
5795
- if (!m) return 0;
5796
- const sign = m[1] === "-" ? -1 : 1;
5797
- return sign * (Number(m[2]) * 3600 + Number(m[3]) * 60 + Number(m[4] ?? 0));
5798
- }
5799
- throw pgError("invalid_parameter_value", `time zone "${zone}" not recognized`);
5800
- }
5801
- function clampDateMs(ms) {
5802
- const MAX = 864e13;
5803
- return Math.max(-MAX, Math.min(MAX, ms));
5804
- }
5805
- function zoneOffsetForNaive(zone, naiveMicros) {
5806
- const first = zoneOffsetAtUtc(zone, naiveMicros);
5807
- const second = zoneOffsetAtUtc(zone, naiveMicros - BigInt(first) * 1000000n);
5808
- return second;
5809
- }
5810
- function validateZone(zone) {
5811
- zoneOffsetAtUtc(zone, 0n);
5812
- }
5813
-
5814
5616
  // src/functions/datetime-registry.ts
5815
5617
  function getDatetimeFunctions() {
5816
5618
  const m = /* @__PURE__ */ new Map();
@@ -7410,6 +7212,28 @@ function exactDegTan(x) {
7410
7212
  return Math.tan(x * Math.PI / 180);
7411
7213
  }
7412
7214
 
7215
+ // src/storage/indexable-relation.ts
7216
+ function materializedTable(view) {
7217
+ const table = new TableData(
7218
+ view.schema,
7219
+ view.name,
7220
+ (view.matColumns ?? []).map((c) => ({
7221
+ name: c.name,
7222
+ type: { id: c.type, mod: null },
7223
+ notNull: false,
7224
+ defaultExpr: null,
7225
+ identity: null,
7226
+ generated: null,
7227
+ collate: null,
7228
+ domain: null
7229
+ })),
7230
+ view.oid,
7231
+ view.temp
7232
+ );
7233
+ table.rows = view.matRows ?? [];
7234
+ return table;
7235
+ }
7236
+
7413
7237
  // src/schema/index-catalog.ts
7414
7238
  var isKeyConstraint = (constraint) => constraint.kind === "primary_key" || constraint.kind === "unique";
7415
7239
  var ACCESS_METHODS = /* @__PURE__ */ new Map([
@@ -7438,6 +7262,8 @@ function syncIndexCatalog(state) {
7438
7262
  const tables = /* @__PURE__ */ new Map();
7439
7263
  for (const schema of state.schemas.values()) {
7440
7264
  for (const table of schema.tables.values()) tables.set(table.oid, { schema, table });
7265
+ for (const view of schema.views.values())
7266
+ if (view.materialized) tables.set(view.oid, { schema, table: materializedTable(view) });
7441
7267
  }
7442
7268
  const backing = /* @__PURE__ */ new Map();
7443
7269
  for (const { table } of tables.values()) {
@@ -7635,6 +7461,55 @@ function deparseExpr(e) {
7635
7461
  }
7636
7462
  }
7637
7463
 
7464
+ // src/types/resolve.ts
7465
+ var SERIALS = {
7466
+ smallserial: "int2",
7467
+ serial2: "int2",
7468
+ serial: "int4",
7469
+ serial4: "int4",
7470
+ bigserial: "int8",
7471
+ serial8: "int8"
7472
+ };
7473
+ function modOf(t, mods) {
7474
+ if (mods.length === 0) return null;
7475
+ if (t === "numeric") return { a: mods[0], b: mods[1] ?? 0 };
7476
+ return { a: mods[0] };
7477
+ }
7478
+ function resolveTypeName(state, tn) {
7479
+ const joined = tn.parts.join(".");
7480
+ const bare = tn.parts.length === 1 ? tn.parts[0] : tn.parts.length === 2 && tn.parts[0] === "pg_catalog" ? tn.parts[1] : null;
7481
+ if (bare !== null) {
7482
+ const serial = SERIALS[bare];
7483
+ if (serial !== void 0 && tn.arrayDims === 0) {
7484
+ return { column: { id: serial, mod: null }, domain: null, serial };
7485
+ }
7486
+ const builtin = normalizeTypeName(bare);
7487
+ if (builtin !== null) {
7488
+ const id = tn.arrayDims > 0 ? arrayTypeOf(builtin) : builtin;
7489
+ return { column: { id, mod: modOf(builtin, tn.mods) }, domain: null, serial: null };
7490
+ }
7491
+ }
7492
+ const enumData = state.findEnum(tn.parts);
7493
+ if (enumData) {
7494
+ const elem = `enum:${enumData.schema}.${enumData.name}`;
7495
+ const id = tn.arrayDims > 0 ? arrayTypeOf(elem) : elem;
7496
+ return { column: { id, mod: null }, domain: null, serial: null };
7497
+ }
7498
+ const domainData = state.findDomain(tn.parts);
7499
+ if (domainData) {
7500
+ if (tn.arrayDims > 0) {
7501
+ const id = arrayTypeOf(domainData.baseType.id);
7502
+ return { column: { id, mod: domainData.baseType.mod }, domain: null, serial: null };
7503
+ }
7504
+ return {
7505
+ column: domainData.baseType,
7506
+ domain: `${domainData.schema}.${domainData.name}`,
7507
+ serial: null
7508
+ };
7509
+ }
7510
+ throw pgError("undefined_object", `type "${joined}" does not exist`);
7511
+ }
7512
+
7638
7513
  // src/sql/ruleutils.ts
7639
7514
  var BOOL = "bool";
7640
7515
  var TEXT = "text";
@@ -8432,7 +8307,11 @@ function getMiscFunctions() {
8432
8307
  }
8433
8308
  const name = argText(ctx, args[0]).toLowerCase();
8434
8309
  const value = args[1].v === null ? "" : argText(ctx, args[1]);
8435
- ctx.state.settings.set(name, value);
8310
+ if (!ctx.state.settings.has(name) && !name.includes("."))
8311
+ throw pgError("undefined_object", `unrecognized configuration parameter "${name}"`, "42704");
8312
+ validateStorageSetting(name, value);
8313
+ const local = args[2]?.v === true;
8314
+ if (!local || ctx.state.inTransaction) (local ? ctx.state.localSettings : ctx.state.settings).set(name, value);
8436
8315
  return tv("text", value);
8437
8316
  });
8438
8317
  m.set(
@@ -8498,10 +8377,7 @@ function getMiscFunctions() {
8498
8377
  if (!col)
8499
8378
  throw pgError("undefined_column", `column "${column}" of relation "${table.name}" does not exist`, "42703");
8500
8379
  if (!col.identity) return tv("text", null);
8501
- return tv(
8502
- "text",
8503
- col.identity.sequence.split(".").map(quoteIdent).join(".")
8504
- );
8380
+ return tv("text", col.identity.sequence.split(".").map(quoteIdent).join("."));
8505
8381
  })
8506
8382
  );
8507
8383
  m.set("format_type", (ctx, args) => {
@@ -13398,31 +13274,171 @@ function jsonEachSrf(asText, jsonOut) {
13398
13274
  return { columns, rows };
13399
13275
  };
13400
13276
  }
13401
- SRFS.set("json_each", jsonEachSrf(false, true));
13402
- SRFS.set("jsonb_each", jsonEachSrf(false, false));
13403
- SRFS.set("json_each_text", jsonEachSrf(true, true));
13404
- SRFS.set("jsonb_each_text", jsonEachSrf(true, false));
13405
- function jsonKeysSrf() {
13406
- return (ctx, args, alias) => {
13407
- if (args[0].v === null) return { columns: [{ name: alias, type: "text" }], rows: [] };
13408
- const v = jsonInput(ctx, args[0]);
13409
- if (v.j !== "obj") {
13410
- throw pgError(
13411
- "invalid_parameter_value",
13412
- `cannot call ${alias} on a ${v.j === "arr" ? "array" : "scalar"}`,
13413
- "22023"
13414
- );
13415
- }
13416
- return { columns: [{ name: alias, type: "text" }], rows: [...v.v.keys()].map((k) => [k]) };
13277
+ SRFS.set("json_each", jsonEachSrf(false, true));
13278
+ SRFS.set("jsonb_each", jsonEachSrf(false, false));
13279
+ SRFS.set("json_each_text", jsonEachSrf(true, true));
13280
+ SRFS.set("jsonb_each_text", jsonEachSrf(true, false));
13281
+ function jsonKeysSrf() {
13282
+ return (ctx, args, alias) => {
13283
+ if (args[0].v === null) return { columns: [{ name: alias, type: "text" }], rows: [] };
13284
+ const v = jsonInput(ctx, args[0]);
13285
+ if (v.j !== "obj") {
13286
+ throw pgError(
13287
+ "invalid_parameter_value",
13288
+ `cannot call ${alias} on a ${v.j === "arr" ? "array" : "scalar"}`,
13289
+ "22023"
13290
+ );
13291
+ }
13292
+ return { columns: [{ name: alias, type: "text" }], rows: [...v.v.keys()].map((k) => [k]) };
13293
+ };
13294
+ }
13295
+ SRFS.set("json_object_keys", jsonKeysSrf());
13296
+ SRFS.set("jsonb_object_keys", jsonKeysSrf());
13297
+ function getSrfFunctions() {
13298
+ return SRFS;
13299
+ }
13300
+ function isSrfName(name) {
13301
+ return SRFS.has(name);
13302
+ }
13303
+
13304
+ // src/executor/relation.ts
13305
+ var RowScope = class {
13306
+ constructor(columns, row, parent = null, rangeVars = /* @__PURE__ */ new Set()) {
13307
+ this.columns = columns;
13308
+ this.row = row;
13309
+ this.parent = parent;
13310
+ this.rangeVars = rangeVars;
13311
+ }
13312
+ columns;
13313
+ row;
13314
+ parent;
13315
+ rangeVars;
13316
+ /** Resolve a (possibly qualified) column reference. undefined = not found. */
13317
+ lookup(parts) {
13318
+ if (parts.length === 1) {
13319
+ const name2 = parts[0];
13320
+ let found2;
13321
+ let count2 = 0;
13322
+ for (let i = 0; i < this.columns.length; i++) {
13323
+ const c = this.columns[i];
13324
+ if (c.name === name2 && !c.hidden) {
13325
+ count2++;
13326
+ if (count2 > 1) {
13327
+ throw pgError("ambiguous_column", `column reference "${name2}" is ambiguous`);
13328
+ }
13329
+ found2 = tv(c.type, this.row[i] ?? null);
13330
+ }
13331
+ }
13332
+ if (found2 !== void 0) return found2;
13333
+ if (this.rangeVars.has(name2)) {
13334
+ return this.wholeRow(name2);
13335
+ }
13336
+ return this.parent?.lookup(parts);
13337
+ }
13338
+ const table = parts[parts.length - 2];
13339
+ const name = parts[parts.length - 1];
13340
+ let found;
13341
+ let count = 0;
13342
+ for (let i = 0; i < this.columns.length; i++) {
13343
+ const c = this.columns[i];
13344
+ if (c.table === table && c.name === name) {
13345
+ count++;
13346
+ if (count > 1) {
13347
+ throw pgError("ambiguous_column", `column reference "${name}" is ambiguous`);
13348
+ }
13349
+ found = tv(c.type, this.row[i] ?? null);
13350
+ }
13351
+ }
13352
+ if (found !== void 0) return found;
13353
+ return this.parent?.lookup(parts);
13354
+ }
13355
+ /** Build a record value from all columns belonging to a range variable. */
13356
+ wholeRow(rangeVar) {
13357
+ const types = [];
13358
+ const values = [];
13359
+ const names = [];
13360
+ for (let i = 0; i < this.columns.length; i++) {
13361
+ const c = this.columns[i];
13362
+ if (c.table === rangeVar) {
13363
+ types.push(c.type);
13364
+ values.push(this.row[i] ?? null);
13365
+ names.push(c.name);
13366
+ }
13367
+ }
13368
+ if (types.length === 0) return void 0;
13369
+ return tv("record", { kind: "pgrecord", types, values, names });
13370
+ }
13371
+ hasRangeVar(name) {
13372
+ if (this.rangeVars.has(name)) return true;
13373
+ return this.parent?.hasRangeVar(name) ?? false;
13374
+ }
13375
+ };
13376
+ function childEnv(env, outer) {
13377
+ return {
13378
+ ctx: env.ctx,
13379
+ params: env.params,
13380
+ ctes: env.ctes,
13381
+ outer,
13382
+ allowUnpopulatedMatviews: env.allowUnpopulatedMatviews
13383
+ };
13384
+ }
13385
+ function relationResult(rel3, command2) {
13386
+ const visible = rel3.columns.map((c, i) => ({ i, c })).filter(({ c }) => !c.hidden);
13387
+ return {
13388
+ columns: visible.map(({ c }) => ({ name: c.name, type: c.type })),
13389
+ rows: rel3.rows.map((r) => visible.map(({ i }) => r[i] ?? null)),
13390
+ command: command2,
13391
+ rowCount: rel3.rows.length
13417
13392
  };
13418
13393
  }
13419
- SRFS.set("json_object_keys", jsonKeysSrf());
13420
- SRFS.set("jsonb_object_keys", jsonKeysSrf());
13421
- function getSrfFunctions() {
13422
- return SRFS;
13394
+ function commandResult(command2, rowCount) {
13395
+ return { columns: [], rows: [], command: command2, rowCount };
13423
13396
  }
13424
- function isSrfName(name) {
13425
- return SRFS.has(name);
13397
+ function inferColumnName(e) {
13398
+ switch (e.type) {
13399
+ case "colref":
13400
+ return e.parts[e.parts.length - 1];
13401
+ case "func":
13402
+ return e.name[e.name.length - 1];
13403
+ case "cast": {
13404
+ const inner = inferColumnName(e.expr);
13405
+ if (inner !== "?column?") return inner;
13406
+ return e.target.parts[e.target.parts.length - 1];
13407
+ }
13408
+ case "collate":
13409
+ return inferColumnName(e.expr);
13410
+ case "case":
13411
+ return "case";
13412
+ case "array_ctor":
13413
+ case "array_query":
13414
+ return "array";
13415
+ case "row":
13416
+ return "row";
13417
+ case "subquery_expr":
13418
+ return e.kind === "exists" ? "exists" : "?column?";
13419
+ case "bool_lit":
13420
+ return "bool";
13421
+ case "extract":
13422
+ return "extract";
13423
+ case "position":
13424
+ return "position";
13425
+ case "substring_sql":
13426
+ return "substring";
13427
+ case "overlay":
13428
+ return "overlay";
13429
+ case "trim":
13430
+ return e.side === "leading" ? "ltrim" : e.side === "trailing" ? "rtrim" : "btrim";
13431
+ case "field_select":
13432
+ return e.field === "*" ? "?column?" : e.field;
13433
+ case "at_time_zone":
13434
+ return "timezone";
13435
+ case "grouping_func":
13436
+ return "grouping";
13437
+ case "subscript":
13438
+ return inferColumnName(e.base);
13439
+ default:
13440
+ return "?column?";
13441
+ }
13426
13442
  }
13427
13443
 
13428
13444
  // src/indexes/index.ts
@@ -13536,6 +13552,181 @@ function rebuildTableIndexes(env, table) {
13536
13552
  ensureIndexStores(env, table);
13537
13553
  }
13538
13554
 
13555
+ // src/constraints/enforce.ts
13556
+ function tableScope(table, row) {
13557
+ const cols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: table.name }));
13558
+ return new RowScope(cols, row, null, /* @__PURE__ */ new Set([table.name]));
13559
+ }
13560
+ function checkNotNull(env, table, row) {
13561
+ void env;
13562
+ for (let i = 0; i < table.columns.length; i++) {
13563
+ const c = table.columns[i];
13564
+ if (c.notNull && (row[i] ?? null) === null) {
13565
+ throw pgError(
13566
+ "not_null_violation",
13567
+ `null value in column "${c.name}" of relation "${table.name}" violates not-null constraint`,
13568
+ "23502"
13569
+ );
13570
+ }
13571
+ }
13572
+ }
13573
+ function checkChecks(env, table, row) {
13574
+ for (const con of table.constraints) {
13575
+ if (con.kind !== "check") continue;
13576
+ const scope = makeEvalScope(env, tableScope(table, row));
13577
+ const v = evalExpr(env.ctx, scope, con.expr);
13578
+ if (v.v === null) continue;
13579
+ const b = castTo(env.ctx, v, "bool", {});
13580
+ if (b.v !== true) {
13581
+ throw pgError(
13582
+ "check_violation",
13583
+ `new row for relation "${table.name}" violates check constraint "${con.name}"`,
13584
+ "23514"
13585
+ );
13586
+ }
13587
+ }
13588
+ }
13589
+ function uniqueSpecsFor(env, table) {
13590
+ const specs = [];
13591
+ for (const con of table.constraints) {
13592
+ if (con.kind !== "primary_key" && con.kind !== "unique") continue;
13593
+ specs.push({
13594
+ name: con.name,
13595
+ keys: con.columns.map((c) => ({ colIdx: table.columnIndex(c), expr: null })),
13596
+ columnNames: con.columns,
13597
+ nullsNotDistinct: con.kind === "unique" ? con.nullsNotDistinct : false,
13598
+ where: null,
13599
+ isPrimary: con.kind === "primary_key"
13600
+ });
13601
+ }
13602
+ const schema = env.ctx.state.schemas.get(table.schema);
13603
+ if (schema) {
13604
+ for (const idx of schema.indexes.values()) {
13605
+ if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
13606
+ specs.push(uniqueSpecOfIndex(table, idx));
13607
+ }
13608
+ }
13609
+ return specs;
13610
+ }
13611
+ function uniqueSpecOfIndex(table, idx) {
13612
+ return {
13613
+ name: idx.name,
13614
+ keys: idx.columns.map((c) => ({
13615
+ colIdx: c.column !== null ? table.columnIndex(c.column) : -1,
13616
+ expr: c.expr
13617
+ })),
13618
+ columnNames: idx.columns.map((c) => c.column ?? "expr"),
13619
+ nullsNotDistinct: idx.nullsNotDistinct,
13620
+ where: idx.where,
13621
+ isPrimary: false
13622
+ };
13623
+ }
13624
+ function uniqueKeyOf(env, table, spec, row) {
13625
+ if (spec.where) {
13626
+ const scope = makeEvalScope(env, tableScope(table, row));
13627
+ const v = evalExpr(env.ctx, scope, spec.where);
13628
+ if (v.v !== true) return null;
13629
+ }
13630
+ const parts = [];
13631
+ let hasNull = false;
13632
+ for (const k of spec.keys) {
13633
+ let value;
13634
+ let type;
13635
+ if (k.expr) {
13636
+ const scope = makeEvalScope(env, tableScope(table, row));
13637
+ const v = evalExpr(env.ctx, scope, k.expr);
13638
+ value = v.v;
13639
+ type = v.t === "unknown" ? "text" : v.t;
13640
+ } else {
13641
+ value = row[k.colIdx] ?? null;
13642
+ type = table.columns[k.colIdx].type.id;
13643
+ }
13644
+ if (value === null) {
13645
+ hasNull = true;
13646
+ parts.push("\0N");
13647
+ } else {
13648
+ parts.push(datumKey(type, value));
13649
+ }
13650
+ }
13651
+ if (hasNull && !spec.nullsNotDistinct) return null;
13652
+ return parts.join("");
13653
+ }
13654
+ function checkUnique(env, table, row, selfIdx) {
13655
+ for (const spec of uniqueSpecsFor(env, table)) {
13656
+ const key = uniqueKeyOf(env, table, spec, row);
13657
+ if (key === null) continue;
13658
+ indexStoreFor(env, table, spec).checkUnique(key, selfIdx);
13659
+ }
13660
+ }
13661
+ function findConflict(env, table, spec, row) {
13662
+ const key = uniqueKeyOf(env, table, spec, row);
13663
+ if (key === null) return null;
13664
+ const hits = indexStoreFor(env, table, spec).lookup(key);
13665
+ return hits.length > 0 ? hits[0] : null;
13666
+ }
13667
+ function checkForeignKeys(env, table, row) {
13668
+ const state = env.ctx.state;
13669
+ for (const con of table.constraints) {
13670
+ if (con.kind !== "foreign_key") continue;
13671
+ const values = con.columns.map((c) => row[table.columnIndex(c)] ?? null);
13672
+ const nulls = values.filter((v) => v === null).length;
13673
+ if (con.match === "simple" && nulls > 0) continue;
13674
+ if (con.match === "full") {
13675
+ if (nulls === values.length) continue;
13676
+ if (nulls > 0) {
13677
+ throw pgError(
13678
+ "constraint_foreign_key",
13679
+ `insert or update on table "${table.name}" violates foreign key constraint "${con.name}"`,
13680
+ "23503"
13681
+ );
13682
+ }
13683
+ }
13684
+ const refTable = state.schemas.get(con.refSchema)?.tables.get(con.refTable);
13685
+ if (!refTable) {
13686
+ throw pgError("undefined_table", `relation "${con.refSchema}.${con.refTable}" does not exist`, "42P01");
13687
+ }
13688
+ const refIdxs = con.refColumns.map((c) => refTable.columnIndex(c));
13689
+ const keyTypes = con.refColumns.map((_c, i) => refTable.columns[refIdxs[i]].type.id);
13690
+ const wanted = values.map((v, i) => {
13691
+ const localT = table.columns[table.columnIndex(con.columns[i])].type.id;
13692
+ const cast = castTo(env.ctx, tv(localT, v), keyTypes[i], {});
13693
+ return cast.v === null ? "\0N" : datumKey(keyTypes[i], cast.v);
13694
+ }).join("");
13695
+ let found = false;
13696
+ for (let i = 0; i < refTable.rowCount(); i++) {
13697
+ const r = refTable.rowAt(i);
13698
+ const key = refIdxs.map((ri, j) => {
13699
+ const v = r[ri] ?? null;
13700
+ return v === null ? "\0N" : datumKey(keyTypes[j], v);
13701
+ }).join("");
13702
+ if (key === wanted) {
13703
+ found = true;
13704
+ break;
13705
+ }
13706
+ }
13707
+ if (!found) {
13708
+ throw pgError(
13709
+ "constraint_foreign_key",
13710
+ `insert or update on table "${table.name}" violates foreign key constraint "${con.name}"`,
13711
+ "23503"
13712
+ );
13713
+ }
13714
+ }
13715
+ }
13716
+ function referencingConstraints(env, table) {
13717
+ const out = [];
13718
+ for (const schema of env.ctx.state.schemas.values()) {
13719
+ for (const t of schema.tables.values()) {
13720
+ for (const con of t.constraints) {
13721
+ if (con.kind === "foreign_key" && con.refSchema === table.schema && con.refTable === table.name) {
13722
+ out.push({ table: t, constraint: con });
13723
+ }
13724
+ }
13725
+ }
13726
+ }
13727
+ return out;
13728
+ }
13729
+
13539
13730
  // src/planner/access.ts
13540
13731
  function isRowIndependentExpr(expr) {
13541
13732
  return expr.type !== "colref";
@@ -15419,10 +15610,9 @@ var Parser = class {
15419
15610
  return this.parseCreateFunction(orReplace);
15420
15611
  }
15421
15612
  if (this.atKw("trigger") || this.atKw("constraint") && this.atKw("trigger", 1)) {
15422
- if (this.eatKw("constraint")) {
15423
- }
15613
+ const constraint = this.eatKw("constraint");
15424
15614
  this.expectKw("trigger");
15425
- return this.parseCreateTrigger(orReplace);
15615
+ return this.parseCreateTrigger(orReplace, constraint);
15426
15616
  }
15427
15617
  if (this.atKw("extension")) {
15428
15618
  this.pos++;
@@ -16243,7 +16433,7 @@ var Parser = class {
16243
16433
  strict: strict2
16244
16434
  };
16245
16435
  }
16246
- parseCreateTrigger(orReplace) {
16436
+ parseCreateTrigger(orReplace, constraint) {
16247
16437
  const name = this.ident();
16248
16438
  let timing;
16249
16439
  if (this.eatKw("before")) timing = "before";
@@ -16273,9 +16463,18 @@ var Parser = class {
16273
16463
  this.expectKw("on");
16274
16464
  const table = this.qualifiedName();
16275
16465
  if (this.eatKw("referencing")) throw unsupported("trigger transition tables");
16276
- while (this.atKw("not") || this.atKw("deferrable") || this.atKw("initially")) {
16277
- this.pos++;
16278
- }
16466
+ let deferrable = false;
16467
+ let initiallyDeferred = false;
16468
+ if (this.eatKw("not")) this.expectKw("deferrable");
16469
+ else if (this.eatKw("deferrable")) deferrable = true;
16470
+ if (this.eatKw("initially")) {
16471
+ if (this.eatKw("deferred")) initiallyDeferred = true;
16472
+ else this.expectKw("immediate");
16473
+ }
16474
+ if ((deferrable || initiallyDeferred) && !constraint)
16475
+ this.errorAt(this.peek(), "only constraint triggers can be deferred");
16476
+ if (initiallyDeferred && !deferrable)
16477
+ this.errorAt(this.peek(), "constraint declared INITIALLY DEFERRED must be DEFERRABLE");
16279
16478
  let forEachRow = false;
16280
16479
  if (this.eatKw("for")) {
16281
16480
  this.eatKw("each");
@@ -16300,7 +16499,21 @@ var Parser = class {
16300
16499
  } while (this.eatPunct(","));
16301
16500
  }
16302
16501
  this.expectPunct(")");
16303
- return { type: "create_trigger", orReplace, name, timing, events, table, forEachRow, when, funcName, funcArgs };
16502
+ return {
16503
+ type: "create_trigger",
16504
+ constraint,
16505
+ deferrable,
16506
+ initiallyDeferred,
16507
+ orReplace,
16508
+ name,
16509
+ timing,
16510
+ events,
16511
+ table,
16512
+ forEachRow,
16513
+ when,
16514
+ funcName,
16515
+ funcArgs
16516
+ };
16304
16517
  }
16305
16518
  // --- ALTER --------------------------------------------------------------------
16306
16519
  parseAlter() {
@@ -16645,9 +16858,15 @@ var Parser = class {
16645
16858
  this.skipToStatementEnd();
16646
16859
  return { type: "no_op", what: "SET TRANSACTION" };
16647
16860
  }
16648
- if (this.atKw("constraints")) {
16649
- this.skipToStatementEnd();
16650
- return { type: "no_op", what: "SET CONSTRAINTS" };
16861
+ if (this.eatKw("constraints")) {
16862
+ const names = this.eatKw("all") ? null : [];
16863
+ if (names)
16864
+ do {
16865
+ names.push(this.qualifiedName());
16866
+ } while (this.eatPunct(","));
16867
+ const deferred = this.eatKw("deferred");
16868
+ if (!deferred) this.expectKw("immediate");
16869
+ return { type: "set_constraints", names, deferred };
16651
16870
  }
16652
16871
  if (this.atKw("role") || this.atKw("session", 0) && this.atKw("authorization", 1)) {
16653
16872
  this.skipToStatementEnd();
@@ -19022,12 +19241,12 @@ function executeRecursiveCte(env, cte, query) {
19022
19241
  table: null
19023
19242
  }));
19024
19243
  const seen = /* @__PURE__ */ new Set();
19025
- const keyOf = (row) => row.map((v, i) => v === null ? "\0N" : datumKey(columns[i].type, v)).join("");
19244
+ const keyOf2 = (row) => row.map((v, i) => v === null ? "\0N" : datumKey(columns[i].type, v)).join("");
19026
19245
  let result = [];
19027
19246
  let working = [];
19028
19247
  for (const row of base.rows) {
19029
19248
  if (!body.all) {
19030
- const k = keyOf(row);
19249
+ const k = keyOf2(row);
19031
19250
  if (seen.has(k)) continue;
19032
19251
  seen.add(k);
19033
19252
  }
@@ -19060,7 +19279,7 @@ function executeRecursiveCte(env, cte, query) {
19060
19279
  return castTo(env.ctx, tv(srcT === UNKNOWN ? "text" : srcT, v), columns[i].type, {}).v;
19061
19280
  });
19062
19281
  if (!body.all) {
19063
- const k = keyOf(row);
19282
+ const k = keyOf2(row);
19064
19283
  if (seen.has(k)) continue;
19065
19284
  seen.add(k);
19066
19285
  }
@@ -20240,7 +20459,7 @@ function setOpRelation(env, op) {
20240
20459
  );
20241
20460
  const lrows = normalize2(left);
20242
20461
  const rrows = normalize2(right);
20243
- const keyOf = (row) => row.map((v, i) => v === null ? "\0N" : datumKey(types[i], v)).join("");
20462
+ const keyOf2 = (row) => row.map((v, i) => v === null ? "\0N" : datumKey(types[i], v)).join("");
20244
20463
  let rows;
20245
20464
  switch (op.op) {
20246
20465
  case "union": {
@@ -20248,7 +20467,7 @@ function setOpRelation(env, op) {
20248
20467
  if (!op.all) {
20249
20468
  const seen = /* @__PURE__ */ new Set();
20250
20469
  rows = rows.filter((r) => {
20251
- const k = keyOf(r);
20470
+ const k = keyOf2(r);
20252
20471
  if (seen.has(k)) return false;
20253
20472
  seen.add(k);
20254
20473
  return true;
@@ -20259,13 +20478,13 @@ function setOpRelation(env, op) {
20259
20478
  case "intersect": {
20260
20479
  const rightCounts = /* @__PURE__ */ new Map();
20261
20480
  for (const r of rrows) {
20262
- const k = keyOf(r);
20481
+ const k = keyOf2(r);
20263
20482
  rightCounts.set(k, (rightCounts.get(k) ?? 0) + 1);
20264
20483
  }
20265
20484
  rows = [];
20266
20485
  const emitted = /* @__PURE__ */ new Set();
20267
20486
  for (const r of lrows) {
20268
- const k = keyOf(r);
20487
+ const k = keyOf2(r);
20269
20488
  const avail = rightCounts.get(k) ?? 0;
20270
20489
  if (avail <= 0) continue;
20271
20490
  if (op.all) {
@@ -20281,13 +20500,13 @@ function setOpRelation(env, op) {
20281
20500
  case "except": {
20282
20501
  const rightCounts = /* @__PURE__ */ new Map();
20283
20502
  for (const r of rrows) {
20284
- const k = keyOf(r);
20503
+ const k = keyOf2(r);
20285
20504
  rightCounts.set(k, (rightCounts.get(k) ?? 0) + 1);
20286
20505
  }
20287
20506
  rows = [];
20288
20507
  const emitted = /* @__PURE__ */ new Set();
20289
20508
  for (const r of lrows) {
20290
- const k = keyOf(r);
20509
+ const k = keyOf2(r);
20291
20510
  if (op.all) {
20292
20511
  const avail = rightCounts.get(k) ?? 0;
20293
20512
  if (avail > 0) {
@@ -20763,202 +20982,222 @@ function executeSelectStmt(env0, stmt) {
20763
20982
  return applyLimitOffset(env, rel3, stmt.limit, stmt.offset);
20764
20983
  }
20765
20984
 
20766
- // src/constraints/enforce.ts
20767
- function tableScope(table, row) {
20768
- const cols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: table.name }));
20769
- return new RowScope(cols, row, null, /* @__PURE__ */ new Set([table.name]));
20770
- }
20771
- function checkNotNull(env, table, row) {
20772
- void env;
20773
- for (let i = 0; i < table.columns.length; i++) {
20774
- const c = table.columns[i];
20775
- if (c.notNull && (row[i] ?? null) === null) {
20776
- throw pgError(
20777
- "not_null_violation",
20778
- `null value in column "${c.name}" of relation "${table.name}" violates not-null constraint`,
20779
- "23502"
20780
- );
20985
+ // src/executor/triggers-scope.ts
20986
+ function triggerScope(table, newRow, oldRow) {
20987
+ const cols = [];
20988
+ const row = [];
20989
+ const rangeVars = /* @__PURE__ */ new Set();
20990
+ if (newRow) {
20991
+ rangeVars.add("new");
20992
+ for (let i = 0; i < table.columns.length; i++) {
20993
+ cols.push({ name: table.columns[i].name, type: table.columns[i].type.id, table: "new" });
20994
+ row.push(newRow[i] ?? null);
20781
20995
  }
20782
20996
  }
20783
- }
20784
- function checkChecks(env, table, row) {
20785
- for (const con of table.constraints) {
20786
- if (con.kind !== "check") continue;
20787
- const scope = makeEvalScope(env, tableScope(table, row));
20788
- const v = evalExpr(env.ctx, scope, con.expr);
20789
- if (v.v === null) continue;
20790
- const b = castTo(env.ctx, v, "bool", {});
20791
- if (b.v !== true) {
20792
- throw pgError(
20793
- "check_violation",
20794
- `new row for relation "${table.name}" violates check constraint "${con.name}"`,
20795
- "23514"
20796
- );
20997
+ if (oldRow) {
20998
+ rangeVars.add("old");
20999
+ for (let i = 0; i < table.columns.length; i++) {
21000
+ cols.push({ name: table.columns[i].name, type: table.columns[i].type.id, table: "old" });
21001
+ row.push(oldRow[i] ?? null);
20797
21002
  }
20798
21003
  }
21004
+ return new RowScope(cols, row, null, rangeVars);
20799
21005
  }
20800
- function uniqueSpecsFor(env, table) {
20801
- const specs = [];
20802
- for (const con of table.constraints) {
20803
- if (con.kind !== "primary_key" && con.kind !== "unique") continue;
20804
- specs.push({
20805
- name: con.name,
20806
- keys: con.columns.map((c) => ({ colIdx: table.columnIndex(c), expr: null })),
20807
- columnNames: con.columns,
20808
- nullsNotDistinct: con.kind === "unique" ? con.nullsNotDistinct : false,
20809
- where: null,
20810
- isPrimary: con.kind === "primary_key"
20811
- });
20812
- }
20813
- const schema = env.ctx.state.schemas.get(table.schema);
20814
- if (schema) {
20815
- for (const idx of schema.indexes.values()) {
20816
- if (idx.table !== table.name || !idx.unique || idx.isConstraint || idx.valid === false) continue;
20817
- specs.push(uniqueSpecOfIndex(table, idx));
20818
- }
20819
- }
20820
- return specs;
21006
+
21007
+ // src/executor/triggers.ts
21008
+ function matches(t, event) {
21009
+ return t.events.some((e) => e.event === event);
20821
21010
  }
20822
- function uniqueSpecOfIndex(table, idx) {
20823
- return {
20824
- name: idx.name,
20825
- keys: idx.columns.map((c) => ({
20826
- colIdx: c.column !== null ? table.columnIndex(c.column) : -1,
20827
- expr: c.expr
20828
- })),
20829
- columnNames: idx.columns.map((c) => c.column ?? "expr"),
20830
- nullsNotDistinct: idx.nullsNotDistinct,
20831
- where: idx.where,
20832
- isPrimary: false
20833
- };
21011
+ var executor = null;
21012
+ function setTriggerExecutor(e) {
21013
+ executor = e;
20834
21014
  }
20835
- function uniqueKeyOf(env, table, spec, row) {
20836
- if (spec.where) {
20837
- const scope = makeEvalScope(env, tableScope(table, row));
20838
- const v = evalExpr(env.ctx, scope, spec.where);
20839
- if (v.v !== true) return null;
20840
- }
20841
- const parts = [];
20842
- let hasNull = false;
20843
- for (const k of spec.keys) {
20844
- let value;
20845
- let type;
20846
- if (k.expr) {
20847
- const scope = makeEvalScope(env, tableScope(table, row));
20848
- const v = evalExpr(env.ctx, scope, k.expr);
20849
- value = v.v;
20850
- type = v.t === "unknown" ? "text" : v.t;
20851
- } else {
20852
- value = row[k.colIdx] ?? null;
20853
- type = table.columns[k.colIdx].type.id;
21015
+ function fireRowTriggers(env, table, timing, event, oldRow, newRow) {
21016
+ let row = newRow ?? oldRow;
21017
+ for (const t of table.triggers) {
21018
+ if (t.timing !== timing || !matches(t, event) || !t.forEachRow) continue;
21019
+ if (!executor) throw unsupported(`trigger "${t.name}" execution`);
21020
+ if (t.constraint && timing === "after") {
21021
+ if (t.when && !evalPredicate(env, triggerScope(table, newRow, oldRow), t.when)) continue;
21022
+ env.ctx.state.pendingTriggers.push({
21023
+ schema: table.schema,
21024
+ table: table.name,
21025
+ trigger: t.name,
21026
+ event,
21027
+ oldRow: oldRow?.slice() ?? null,
21028
+ newRow: newRow?.slice() ?? null
21029
+ });
21030
+ continue;
20854
21031
  }
20855
- if (value === null) {
20856
- hasNull = true;
20857
- parts.push("\0N");
20858
- } else {
20859
- parts.push(datumKey(type, value));
21032
+ const result = executor(env, table, t, event, oldRow, timing === "before" ? row : newRow);
21033
+ if (timing === "before") {
21034
+ if (result === null) return { row: null };
21035
+ row = result;
20860
21036
  }
20861
21037
  }
20862
- if (hasNull && !spec.nullsNotDistinct) return null;
20863
- return parts.join("");
21038
+ return { row };
20864
21039
  }
20865
- function checkUnique(env, table, row, selfIdx) {
20866
- for (const spec of uniqueSpecsFor(env, table)) {
20867
- const key = uniqueKeyOf(env, table, spec, row);
20868
- if (key === null) continue;
20869
- indexStoreFor(env, table, spec).checkUnique(key, selfIdx);
21040
+ var keyOf = (schema, name) => JSON.stringify([schema, name]);
21041
+ function flushConstraintTriggers(state, all = false) {
21042
+ const env = { ctx: new EngineCtx(state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
21043
+ for (let i = 0; i < state.pendingTriggers.length; ) {
21044
+ const pending = state.pendingTriggers[i];
21045
+ const table = state.schemas.get(pending.schema)?.tables.get(pending.table);
21046
+ const trigger = table?.triggers.find((t) => t.name === pending.trigger);
21047
+ if (!table || !trigger) {
21048
+ state.pendingTriggers.splice(i, 1);
21049
+ continue;
21050
+ }
21051
+ const deferred = trigger.deferrable && (state.constraintModes.get(keyOf(table.schema, trigger.name)) ?? trigger.initiallyDeferred);
21052
+ if (!all && state.inTransaction && deferred) {
21053
+ i++;
21054
+ continue;
21055
+ }
21056
+ if (!executor) throw unsupported(`trigger "${trigger.name}" execution`);
21057
+ state.pendingTriggers.splice(i, 1);
21058
+ const changes = state.changes;
21059
+ try {
21060
+ executor(env, table, { ...trigger, when: null }, pending.event, pending.oldRow, pending.newRow);
21061
+ } catch (error) {
21062
+ state.pendingTriggers.splice(i, 0, pending);
21063
+ throw error;
21064
+ } finally {
21065
+ state.changes = changes;
21066
+ }
20870
21067
  }
20871
21068
  }
20872
- function findConflict(env, table, spec, row) {
20873
- const key = uniqueKeyOf(env, table, spec, row);
20874
- if (key === null) return null;
20875
- const hits = indexStoreFor(env, table, spec).lookup(key);
20876
- return hits.length > 0 ? hits[0] : null;
20877
- }
20878
- function checkForeignKeys(env, table, row) {
20879
- const state = env.ctx.state;
20880
- for (const con of table.constraints) {
20881
- if (con.kind !== "foreign_key") continue;
20882
- const values = con.columns.map((c) => row[table.columnIndex(c)] ?? null);
20883
- const nulls = values.filter((v) => v === null).length;
20884
- if (con.match === "simple" && nulls > 0) continue;
20885
- if (con.match === "full") {
20886
- if (nulls === values.length) continue;
20887
- if (nulls > 0) {
20888
- throw pgError(
20889
- "constraint_foreign_key",
20890
- `insert or update on table "${table.name}" violates foreign key constraint "${con.name}"`,
20891
- "23503"
21069
+ function setConstraints(state, stmt) {
21070
+ if (!state.inTransaction) return;
21071
+ const matches2 = [];
21072
+ if (stmt.names) {
21073
+ for (const name of stmt.names) {
21074
+ const schemas = name.length > 1 ? [name[name.length - 2]] : state.effectiveSearchPath();
21075
+ let found = false;
21076
+ for (const schema of schemas) {
21077
+ const items = [...state.schemas.get(schema)?.tables.values() ?? []].flatMap(
21078
+ (table) => table.triggers.filter((t) => t.constraint && t.name === name[name.length - 1])
20892
21079
  );
21080
+ if (items.length) {
21081
+ for (const trigger of items) matches2.push({ schema, trigger });
21082
+ found = true;
21083
+ break;
21084
+ }
20893
21085
  }
21086
+ if (!found) throw pgError("undefined_object", `constraint "${name.join(".")}" does not exist`, "42704");
20894
21087
  }
20895
- const refTable = state.schemas.get(con.refSchema)?.tables.get(con.refTable);
20896
- if (!refTable) {
20897
- throw pgError("undefined_table", `relation "${con.refSchema}.${con.refTable}" does not exist`, "42P01");
21088
+ } else {
21089
+ for (const schema of state.schemas.values())
21090
+ for (const table of schema.tables.values())
21091
+ for (const trigger of table.triggers)
21092
+ if (trigger.constraint && trigger.deferrable) matches2.push({ schema: schema.name, trigger });
21093
+ }
21094
+ for (const { trigger } of matches2)
21095
+ if (stmt.deferred && !trigger.deferrable)
21096
+ throw pgError("misuse", `constraint "${trigger.name}" is not deferrable`, "55000");
21097
+ const previous = new Map(state.constraintModes);
21098
+ for (const { schema, trigger } of matches2) state.constraintModes.set(keyOf(schema, trigger.name), stmt.deferred);
21099
+ try {
21100
+ if (!stmt.deferred) flushConstraintTriggers(state);
21101
+ } catch (error) {
21102
+ state.constraintModes = previous;
21103
+ throw error;
21104
+ }
21105
+ }
21106
+
21107
+ // src/transactions/manager.ts
21108
+ var TransactionManager = class {
21109
+ constructor(state) {
21110
+ this.state = state;
21111
+ }
21112
+ state;
21113
+ base = null;
21114
+ savepoints = [];
21115
+ freezeDepth = 0;
21116
+ get inTransaction() {
21117
+ return this.base !== null;
21118
+ }
21119
+ takeSnapshot() {
21120
+ this.state.freezeShared();
21121
+ this.freezeDepth++;
21122
+ return { state: this.state.cloneShallow(), prngState: this.state.prng.getState() };
21123
+ }
21124
+ restore(snap) {
21125
+ this.state.restoreFrom(snap.state);
21126
+ this.state.prng.setState(snap.prngState);
21127
+ }
21128
+ thawOnce() {
21129
+ if (this.freezeDepth > 0) {
21130
+ this.state.thawShared();
21131
+ this.freezeDepth--;
20898
21132
  }
20899
- const refIdxs = con.refColumns.map((c) => refTable.columnIndex(c));
20900
- const keyTypes = con.refColumns.map((_c, i) => refTable.columns[refIdxs[i]].type.id);
20901
- const wanted = values.map((v, i) => {
20902
- const localT = table.columns[table.columnIndex(con.columns[i])].type.id;
20903
- const cast = castTo(env.ctx, tv(localT, v), keyTypes[i], {});
20904
- return cast.v === null ? "\0N" : datumKey(keyTypes[i], cast.v);
20905
- }).join("");
20906
- let found = false;
20907
- for (let i = 0; i < refTable.rowCount(); i++) {
20908
- const r = refTable.rowAt(i);
20909
- const key = refIdxs.map((ri, j) => {
20910
- const v = r[ri] ?? null;
20911
- return v === null ? "\0N" : datumKey(keyTypes[j], v);
20912
- }).join("");
20913
- if (key === wanted) {
20914
- found = true;
20915
- break;
20916
- }
21133
+ }
21134
+ begin() {
21135
+ if (this.base !== null) {
21136
+ return;
20917
21137
  }
20918
- if (!found) {
20919
- throw pgError(
20920
- "constraint_foreign_key",
20921
- `insert or update on table "${table.name}" violates foreign key constraint "${con.name}"`,
20922
- "23503"
20923
- );
21138
+ this.base = this.takeSnapshot();
21139
+ this.state.inTransaction = true;
21140
+ assert(this.freezeDepth === 1, "BEGIN freeze depth");
21141
+ }
21142
+ commit() {
21143
+ try {
21144
+ flushConstraintTriggers(this.state, true);
21145
+ } catch (error) {
21146
+ this.rollback();
21147
+ throw error;
21148
+ }
21149
+ this.base = null;
21150
+ this.savepoints = [];
21151
+ this.state.inTransaction = false;
21152
+ this.state.localSettings.clear();
21153
+ this.state.pendingTriggers = [];
21154
+ this.state.constraintModes.clear();
21155
+ while (this.freezeDepth > 0) this.thawOnce();
21156
+ }
21157
+ rollback() {
21158
+ if (this.base !== null) {
21159
+ this.restore(this.base);
20924
21160
  }
21161
+ this.base = null;
21162
+ this.savepoints = [];
21163
+ this.state.inTransaction = false;
21164
+ this.state.localSettings.clear();
21165
+ this.state.pendingTriggers = [];
21166
+ this.state.constraintModes.clear();
21167
+ while (this.freezeDepth > 0) this.thawOnce();
20925
21168
  }
20926
- }
20927
- function referencingConstraints(env, table) {
20928
- const out = [];
20929
- for (const schema of env.ctx.state.schemas.values()) {
20930
- for (const t of schema.tables.values()) {
20931
- for (const con of t.constraints) {
20932
- if (con.kind === "foreign_key" && con.refSchema === table.schema && con.refTable === table.name) {
20933
- out.push({ table: t, constraint: con });
20934
- }
20935
- }
21169
+ savepoint(name) {
21170
+ if (this.base === null) {
21171
+ throw pgError("transaction_state", "SAVEPOINT can only be used in transaction blocks", "25P01");
20936
21172
  }
21173
+ this.savepoints.push({ name, snap: this.takeSnapshot() });
20937
21174
  }
20938
- return out;
20939
- }
20940
-
20941
- // src/executor/triggers.ts
20942
- function matches(t, event) {
20943
- return t.events.some((e) => e.event === event);
20944
- }
20945
- var executor = null;
20946
- function setTriggerExecutor(e) {
20947
- executor = e;
20948
- }
20949
- function fireRowTriggers(env, table, timing, event, oldRow, newRow) {
20950
- let row = newRow ?? oldRow;
20951
- for (const t of table.triggers) {
20952
- if (t.timing !== timing || !matches(t, event) || !t.forEachRow) continue;
20953
- if (!executor) throw unsupported(`trigger "${t.name}" execution`);
20954
- const result = executor(env, table, t, event, oldRow, timing === "before" ? row : newRow);
20955
- if (timing === "before") {
20956
- if (result === null) return { row: null };
20957
- row = result;
21175
+ releaseSavepoint(name) {
21176
+ const idx = this.findSavepoint(name);
21177
+ this.savepoints.splice(idx);
21178
+ }
21179
+ rollbackToSavepoint(name) {
21180
+ const idx = this.findSavepoint(name);
21181
+ const sp = this.savepoints[idx];
21182
+ this.restore(sp.snap);
21183
+ this.savepoints.splice(idx + 1);
21184
+ }
21185
+ findSavepoint(name) {
21186
+ if (this.base === null) {
21187
+ throw pgError("transaction_state", `SAVEPOINT can only be used in transaction blocks`, "25P01");
21188
+ }
21189
+ for (let i = this.savepoints.length - 1; i >= 0; i--) {
21190
+ if (this.savepoints[i].name === name) return i;
20958
21191
  }
21192
+ throw pgError("invalid_savepoint_specification", `savepoint "${name}" does not exist`, "3B001");
20959
21193
  }
20960
- return { row };
20961
- }
21194
+ reset() {
21195
+ this.base = null;
21196
+ this.savepoints = [];
21197
+ this.state.inTransaction = false;
21198
+ this.freezeDepth = 0;
21199
+ }
21200
+ };
20962
21201
 
20963
21202
  // src/executor/dml.ts
20964
21203
  function withStatementRollback(env, fn) {
@@ -21648,6 +21887,7 @@ function executeSet(env, stmt) {
21648
21887
  else target.delete(name);
21649
21888
  return commandResult("SET", 0);
21650
21889
  }
21890
+ validateStorageSetting(name, stmt.value);
21651
21891
  target.set(name, stmt.value);
21652
21892
  return commandResult("SET", 0);
21653
21893
  }
@@ -21953,64 +22193,6 @@ function executeCopyFromData(env, stmt, data) {
21953
22193
  };
21954
22194
  }
21955
22195
 
21956
- // src/expressions/context.ts
21957
- var EngineCtx = class {
21958
- state;
21959
- /** transaction timestamp (now(), current_timestamp) — UTC micros since PG epoch */
21960
- txNow;
21961
- /** statement timestamp */
21962
- stmtNow;
21963
- constructor(state, txNow) {
21964
- this.state = state;
21965
- const nowMs = state.clock().getTime();
21966
- const nowMicros = BigInt(Math.round(nowMs)) * 1000n + UNIX_EPOCH_MICROS_FROM_PG;
21967
- this.txNow = txNow ?? nowMicros;
21968
- this.stmtNow = nowMicros;
21969
- }
21970
- timezone() {
21971
- return this.state.getSetting("timezone") ?? "UTC";
21972
- }
21973
- namespaceOid(name) {
21974
- const oid = builtinNamespaceOid(name) ?? this.state.schemas.get(name)?.oid;
21975
- if (oid === void 0) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
21976
- return oid;
21977
- }
21978
- namespaceName(oid) {
21979
- const builtin = builtinNamespaceName(oid);
21980
- if (builtin !== void 0) return builtin;
21981
- for (const schema of this.state.schemas.values()) {
21982
- if (schema.oid === oid) return schema.name;
21983
- }
21984
- return null;
21985
- }
21986
- zoneOffsetAt(utcMicros) {
21987
- return zoneOffsetAtUtc(this.timezone(), utcMicros);
21988
- }
21989
- zoneOffsetForNaive(naiveMicros) {
21990
- return zoneOffsetForNaive(this.timezone(), naiveMicros);
21991
- }
21992
- enumLabels(enumType) {
21993
- if (!isEnumType(enumType)) return null;
21994
- const e = this.state.findEnumByKey(enumType.slice(5));
21995
- return e?.labels ?? null;
21996
- }
21997
- enumHasLabel(enumType, label) {
21998
- const labels = this.enumLabels(enumType);
21999
- return labels === null ? true : labels.includes(label);
22000
- }
22001
- enumOrder(enumType, label) {
22002
- const labels = this.enumLabels(enumType);
22003
- if (!labels) return 0;
22004
- const idx = labels.indexOf(label);
22005
- return idx === -1 ? labels.length : idx;
22006
- }
22007
- requireEnum(enumType) {
22008
- const labels = this.enumLabels(enumType);
22009
- if (!labels) throw pgError("undefined_object", `type "${enumType.slice(5)}" does not exist`);
22010
- return labels;
22011
- }
22012
- };
22013
-
22014
22196
  // src/runtime/clock.ts
22015
22197
  var DEFAULT_NOW = /* @__PURE__ */ new Date("2000-01-01T00:00:00.000Z");
22016
22198
  function fixedClock(instant = DEFAULT_NOW) {
@@ -22978,7 +23160,7 @@ function pgClass(ctx) {
22978
23160
  v.materialized ? "m" : "v",
22979
23161
  v.columns?.length ?? 0,
22980
23162
  v.matRows?.length ?? 0,
22981
- false,
23163
+ v.materialized && [...schemaData.indexes.values()].some((i) => i.tableOid === v.oid),
22982
23164
  !v.materialized || v.matRows !== null,
22983
23165
  relPersistence(v)
22984
23166
  );
@@ -23244,7 +23426,7 @@ function pgIndex(ctx) {
23244
23426
  const valid = i.valid !== false;
23245
23427
  rows.push([
23246
23428
  i.oid ?? 0,
23247
- table?.oid ?? 0,
23429
+ table?.oid ?? i.tableOid ?? 0,
23248
23430
  indexAttributeCount(i),
23249
23431
  i.columns.length,
23250
23432
  i.unique,
@@ -23416,7 +23598,7 @@ function pgStatUserIndexes(ctx) {
23416
23598
  for (const index of schema.indexes.values()) {
23417
23599
  const table = schema.tables.get(index.table);
23418
23600
  rows.push([
23419
- table?.oid ?? 0,
23601
+ table?.oid ?? index.tableOid ?? 0,
23420
23602
  index.oid ?? 0,
23421
23603
  schema.name,
23422
23604
  index.table,
@@ -24011,26 +24193,6 @@ function runTriggerBody(env, table, stmts, vars) {
24011
24193
  }
24012
24194
  }
24013
24195
  }
24014
- function triggerScope(table, newRow, oldRow) {
24015
- const cols = [];
24016
- const row = [];
24017
- const rangeVars = /* @__PURE__ */ new Set();
24018
- if (newRow) {
24019
- rangeVars.add("new");
24020
- for (let i = 0; i < table.columns.length; i++) {
24021
- cols.push({ name: table.columns[i].name, type: table.columns[i].type.id, table: "new" });
24022
- row.push(newRow[i] ?? null);
24023
- }
24024
- }
24025
- if (oldRow) {
24026
- rangeVars.add("old");
24027
- for (let i = 0; i < table.columns.length; i++) {
24028
- cols.push({ name: table.columns[i].name, type: table.columns[i].type.id, table: "old" });
24029
- row.push(oldRow[i] ?? null);
24030
- }
24031
- }
24032
- return new RowScope(cols, row, null, rangeVars);
24033
- }
24034
24196
  function executeTrigger(env, table, trigger, _event, oldRow, newRow) {
24035
24197
  const state = env.ctx.state;
24036
24198
  if (trigger.when !== null && !evalPredicate(env, triggerScope(table, newRow, oldRow), trigger.when)) {
@@ -25000,7 +25162,8 @@ function executeCreateTableAs(env, stmt) {
25000
25162
  }
25001
25163
  function executeCreateIndex(env, stmt) {
25002
25164
  const state = env.ctx.state;
25003
- const table = state.findTable(stmt.table);
25165
+ const view = state.findView(stmt.table);
25166
+ const table = state.findTable(stmt.table) ?? (view?.materialized ? materializedTable(view) : void 0);
25004
25167
  if (!table) {
25005
25168
  throw pgError("undefined_table", `relation "${stmt.table.join(".")}" does not exist`, "42P01");
25006
25169
  }
@@ -25112,13 +25275,22 @@ function executeRefreshMatView(env, stmt) {
25112
25275
  if (!found?.materialized) {
25113
25276
  throw pgError("undefined_table", `materialized view "${stmt.name.join(".")}" does not exist`, "42P01");
25114
25277
  }
25278
+ const rel3 = executeSelectStmt(matviewQueryEnv(env, !stmt.withData), found.query);
25279
+ const next = {
25280
+ ...found,
25281
+ matColumns: rel3.columns.map((c, i) => ({
25282
+ name: found.columns?.[i] ?? c.name,
25283
+ type: c.type === UNKNOWN ? "text" : c.type
25284
+ })),
25285
+ matRows: stmt.withData ? rel3.rows : null
25286
+ };
25287
+ const table = materializedTable(next);
25288
+ for (const index of env.ctx.state.getSchema(found.schema).indexes.values()) {
25289
+ if (index.tableOid === found.oid && index.valid !== false) assertUniqueIndexBuildable(env, table, index);
25290
+ }
25115
25291
  const view = env.ctx.state.ensureWritableView(found);
25116
- const rel3 = executeSelectStmt(matviewQueryEnv(env, !stmt.withData), view.query);
25117
- view.matColumns = rel3.columns.map((c, i) => ({
25118
- name: view.columns?.[i] ?? c.name,
25119
- type: c.type === UNKNOWN ? "text" : c.type
25120
- }));
25121
- view.matRows = stmt.withData ? rel3.rows : null;
25292
+ view.matColumns = next.matColumns;
25293
+ view.matRows = next.matRows;
25122
25294
  return commandResult("REFRESH MATERIALIZED VIEW", 0);
25123
25295
  }
25124
25296
  function executeCreateSchema(env, stmt) {
@@ -25260,6 +25432,8 @@ function executeCreateFunction(env, stmt) {
25260
25432
  return commandResult("CREATE FUNCTION", 0);
25261
25433
  }
25262
25434
  function executeCreateTrigger(env, stmt) {
25435
+ if (stmt.constraint && (stmt.timing !== "after" || !stmt.forEachRow))
25436
+ throw pgError("syntax", "constraint triggers must be AFTER ROW triggers", "42601");
25263
25437
  const state = env.ctx.state;
25264
25438
  const found = state.findTable(stmt.table);
25265
25439
  if (!found) {
@@ -25278,6 +25452,9 @@ function executeCreateTrigger(env, stmt) {
25278
25452
  }
25279
25453
  const meta = {
25280
25454
  name: stmt.name,
25455
+ constraint: stmt.constraint,
25456
+ deferrable: stmt.deferrable,
25457
+ initiallyDeferred: stmt.initiallyDeferred,
25281
25458
  timing: stmt.timing,
25282
25459
  events: stmt.events,
25283
25460
  forEachRow: stmt.forEachRow,
@@ -26046,7 +26223,16 @@ function alterTableIsSelfContained(stmt) {
26046
26223
  return stmt.actions.length === 1 && action !== void 0 && SELF_CONTAINED_ALTER_ACTIONS.has(action.kind);
26047
26224
  }
26048
26225
  function executeStatement(env, stmt) {
26049
- if (!RESHAPES_TABLES.has(stmt.type)) return dispatchStatement(env, stmt);
26226
+ if (!RESHAPES_TABLES.has(stmt.type)) {
26227
+ if ((stmt.type === "insert" || stmt.type === "update" || stmt.type === "delete") && (env.ctx.state.pendingTriggers.length > 0 || env.ctx.state.findTable(stmt.table)?.triggers.some((t) => t.constraint))) {
26228
+ return withStatementRollback(env, () => {
26229
+ const result = dispatchStatement(env, stmt);
26230
+ flushConstraintTriggers(env.ctx.state);
26231
+ return result;
26232
+ });
26233
+ }
26234
+ return dispatchStatement(env, stmt);
26235
+ }
26050
26236
  try {
26051
26237
  return dispatchStatement(env, stmt);
26052
26238
  } finally {
@@ -26105,6 +26291,9 @@ function dispatchStatement(env, stmt) {
26105
26291
  return executeRefreshMatView(env, stmt);
26106
26292
  case "transaction":
26107
26293
  return executeTransaction(env, stmt);
26294
+ case "set_constraints":
26295
+ setConstraints(env.ctx.state, stmt);
26296
+ return commandResult("SET CONSTRAINTS", 0);
26108
26297
  case "set":
26109
26298
  return executeSet(env, stmt);
26110
26299
  case "show":
@@ -28543,11 +28732,13 @@ var ScramServer = class {
28543
28732
  var splitStatements = (script) => {
28544
28733
  const out = [];
28545
28734
  let start2 = 0;
28735
+ let hasSql = false;
28546
28736
  let i = 0;
28547
28737
  const n = script.length;
28548
28738
  const push = (end) => {
28549
28739
  const text = script.slice(start2, end).trim();
28550
- if (text.length > 0) out.push(text);
28740
+ if (hasSql) out.push(text);
28741
+ hasSql = false;
28551
28742
  };
28552
28743
  while (i < n) {
28553
28744
  const c = script[i];
@@ -28569,9 +28760,11 @@ var splitStatements = (script) => {
28569
28760
  i += 2;
28570
28761
  } else i++;
28571
28762
  }
28763
+ if (depth > 0) hasSql = true;
28572
28764
  continue;
28573
28765
  }
28574
28766
  if (c === "'" || c === '"') {
28767
+ hasSql = true;
28575
28768
  const escaped = c === "'" && /[eE]/.test(script[i - 1] ?? "") && !/[\w$]/.test(script[i - 2] ?? "");
28576
28769
  i++;
28577
28770
  while (i < n) {
@@ -28594,6 +28787,7 @@ var splitStatements = (script) => {
28594
28787
  if (c === "$") {
28595
28788
  const m = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(script.slice(i, i + 64));
28596
28789
  if (m) {
28790
+ hasSql = true;
28597
28791
  const tag = m[0];
28598
28792
  const end = script.indexOf(tag, i + tag.length);
28599
28793
  i = end === -1 ? n : end + tag.length;
@@ -28604,6 +28798,7 @@ var splitStatements = (script) => {
28604
28798
  push(i);
28605
28799
  start2 = i + 1;
28606
28800
  }
28801
+ if (c !== ";" && !/\s/.test(c)) hasSql = true;
28607
28802
  i++;
28608
28803
  }
28609
28804
  push(n);
@@ -29036,7 +29231,16 @@ var binaryValue = (type, text) => {
29036
29231
  }
29037
29232
  };
29038
29233
  var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
29039
- var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "no_op"].includes(stmt.type));
29234
+ var setsSessionConfig = (node) => {
29235
+ if (Array.isArray(node)) return node.some(setsSessionConfig);
29236
+ if (typeof node !== "object" || node === null) return false;
29237
+ const call = node;
29238
+ if (call.type === "func" && call.name?.at(-1) === "set_config") return true;
29239
+ return Object.values(node).some(setsSessionConfig);
29240
+ };
29241
+ var mutatesDatabase = (sql) => parse(sql).some(
29242
+ (stmt) => !["select", "show", "explain", "transaction", "no_op"].includes(stmt.type) || stmt.type === "select" && setsSessionConfig(stmt)
29243
+ );
29040
29244
  var NEVER_WRITES = /* @__PURE__ */ new Set(["show", "set", "reset", "transaction", "no_op", "prepare", "deallocate"]);
29041
29245
  var WRITE_NODES = /* @__PURE__ */ new Set(["insert", "update", "delete"]);
29042
29246
  var containsWrite = (db, node) => {
@@ -29661,6 +29865,7 @@ var Connection = class {
29661
29865
  }
29662
29866
  return outcome;
29663
29867
  } finally {
29868
+ workspace?.transactions.rollback();
29664
29869
  workspace?.close();
29665
29870
  }
29666
29871
  }
@@ -29735,8 +29940,9 @@ var Connection = class {
29735
29940
  });
29736
29941
  workspace.state.settings = new Map(this.settings);
29737
29942
  workspace.state.settingDefaults = this.settingDefaults;
29943
+ workspace.transactions.begin();
29738
29944
  for (const logged of this.transaction?.statements ?? []) {
29739
- workspace.transaction(() => this.replay(workspace, logged));
29945
+ this.replay(workspace, logged);
29740
29946
  }
29741
29947
  return workspace;
29742
29948
  }
@@ -30533,6 +30739,9 @@ var start = async (options) => {
30533
30739
  });
30534
30740
  };
30535
30741
 
30742
+ // src/wire/fleet.ts
30743
+ import { createHash as createHash4 } from "node:crypto";
30744
+
30536
30745
  // ../core/dist/clock.js
30537
30746
  var createClock = (source = Date.now) => {
30538
30747
  let offsetMs = 0;
@@ -30574,6 +30783,310 @@ var createClock = (source = Date.now) => {
30574
30783
  };
30575
30784
  };
30576
30785
 
30786
+ // ../core/dist/controls.js
30787
+ var ControlError = class extends RangeError {
30788
+ constructor(message2) {
30789
+ super(message2);
30790
+ this.name = "ControlError";
30791
+ }
30792
+ };
30793
+ var UNITS = {
30794
+ ms: 1,
30795
+ s: 1e3,
30796
+ m: 6e4,
30797
+ h: 36e5,
30798
+ d: 864e5
30799
+ };
30800
+ var parseDuration = (value) => {
30801
+ if (typeof value === "number")
30802
+ return Number.isFinite(value) ? value : void 0;
30803
+ if (typeof value !== "string")
30804
+ return void 0;
30805
+ const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/.exec(value.trim());
30806
+ if (!match)
30807
+ return void 0;
30808
+ return Number(match[1]) * UNITS[match[2] ?? "ms"];
30809
+ };
30810
+ var parseInstant = (value) => {
30811
+ if (typeof value === "number")
30812
+ return Number.isFinite(value) ? value : void 0;
30813
+ if (typeof value !== "string")
30814
+ return void 0;
30815
+ const text = value.trim();
30816
+ if (/^-?\d+$/.test(text))
30817
+ return Number(text);
30818
+ const parsed = Date.parse(text);
30819
+ return Number.isNaN(parsed) ? void 0 : parsed;
30820
+ };
30821
+ var durationMs = (value, label, negative = false) => {
30822
+ const parsed = parseDuration(value);
30823
+ if (parsed === void 0)
30824
+ throw new ControlError(`${label}: expected milliseconds or a duration like "250ms" or "15m"`);
30825
+ if (!negative && parsed < 0)
30826
+ throw new ControlError(`${label}: must not be negative`);
30827
+ return parsed;
30828
+ };
30829
+ var instantMs = (value, label) => {
30830
+ const parsed = parseInstant(value);
30831
+ if (parsed === void 0)
30832
+ throw new ControlError(`${label}: expected epoch ms or ISO-8601`);
30833
+ return parsed;
30834
+ };
30835
+ var normalizeSeed = (seed, label = "seed") => {
30836
+ if (typeof seed === "number" && Number.isFinite(seed))
30837
+ return seed;
30838
+ if (typeof seed === "string" && seed !== "")
30839
+ return /^\d+$/.test(seed) ? Number(seed) : seed;
30840
+ throw new ControlError(`${label}: expected a number or a non-empty string`);
30841
+ };
30842
+ var SNAPSHOT_FORMAT = "mockingbird.snapshot/v1";
30843
+ var NAMESPACE_KEYS = /* @__PURE__ */ new Set(["seed", "clock", "virtualTime", "latency", "faults", "snapshot"]);
30844
+ var MOCK_KEYS = /* @__PURE__ */ new Set([...NAMESPACE_KEYS, "credentials", "namespaces"]);
30845
+ var REPORTED_KEYS = /* @__PURE__ */ new Set(["namespace", "snapshots"]);
30846
+ var REPORTED_CLOCK_KEYS = /* @__PURE__ */ new Set(["iso", "offsetMs"]);
30847
+ var KEY_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
30848
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30849
+ var unknownKeys = (value, known, label, reported) => {
30850
+ for (const key of Object.keys(value)) {
30851
+ if (!known.has(key) && !reported?.has(key))
30852
+ throw new ControlError(`${label}: unknown control ${JSON.stringify(key)}; expected ${[...known].join(", ")}`);
30853
+ }
30854
+ };
30855
+ var latencyRule = (value, label) => {
30856
+ if (!isRecord2(value))
30857
+ return { delayMs: durationMs(value, label), jitterMs: 0 };
30858
+ unknownKeys(value, /* @__PURE__ */ new Set(["delay", "jitter", "delayMs", "jitterMs", "operations"]), label);
30859
+ return {
30860
+ delayMs: durationMs(value.delay ?? value.delayMs ?? 0, `${label}.delay`),
30861
+ jitterMs: durationMs(value.jitter ?? value.jitterMs ?? 0, `${label}.jitter`)
30862
+ };
30863
+ };
30864
+ var normalizeLatency = (value, label = "latency") => {
30865
+ const base = latencyRule(value, label);
30866
+ const operations = {};
30867
+ if (isRecord2(value) && value.operations !== void 0) {
30868
+ if (!isRecord2(value.operations))
30869
+ throw new ControlError(`${label}.operations: expected an object keyed by operationId`);
30870
+ for (const [operationId, rule] of Object.entries(value.operations)) {
30871
+ if (isRecord2(rule) && rule.operations !== void 0)
30872
+ throw new ControlError(`${label}.operations.${operationId}: cannot nest operations`);
30873
+ operations[operationId] = latencyRule(rule, `${label}.operations.${operationId}`);
30874
+ }
30875
+ }
30876
+ return { ...base, operations };
30877
+ };
30878
+ var normalizeClock = (value, label = "clock") => {
30879
+ if (!isRecord2(value))
30880
+ throw new ControlError(`${label}: expected an object`);
30881
+ unknownKeys(value, /* @__PURE__ */ new Set(["now", "set", "frozen", "freeze", "autoAdvance", "autoAdvanceMs"]), label, REPORTED_CLOCK_KEYS);
30882
+ const plan = {};
30883
+ const now = value.now ?? value.set;
30884
+ if (now !== void 0)
30885
+ plan.now = instantMs(now, `${label}.now`);
30886
+ const frozen = value.frozen ?? value.freeze;
30887
+ if (frozen !== void 0) {
30888
+ if (typeof frozen !== "boolean")
30889
+ throw new ControlError(`${label}.frozen: expected a boolean`);
30890
+ plan.frozen = frozen;
30891
+ }
30892
+ const auto = value.autoAdvance ?? value.autoAdvanceMs;
30893
+ if (auto !== void 0)
30894
+ plan.autoAdvanceMs = durationMs(auto, `${label}.autoAdvance`);
30895
+ return plan;
30896
+ };
30897
+ var assertSnapshotDocument = (value, label = "snapshot") => {
30898
+ if (!isRecord2(value))
30899
+ throw new ControlError(`${label}: expected a snapshot document`);
30900
+ if (value.format !== SNAPSHOT_FORMAT)
30901
+ throw new ControlError(`${label}.format: expected ${JSON.stringify(SNAPSHOT_FORMAT)}`);
30902
+ if (!Array.isArray(value.records))
30903
+ throw new ControlError(`${label}.records: expected a list`);
30904
+ value.records.forEach((row, index) => {
30905
+ if (!isRecord2(row) || typeof row.collection !== "string" || typeof row.id !== "string" || row.value === void 0 && typeof row.raw !== "string" || row.seq !== void 0 && !Number.isSafeInteger(row.seq))
30906
+ throw new ControlError(`${label}.records[${index}]: expected {collection, id, value, seq?}`);
30907
+ });
30908
+ if (value.sequences !== void 0) {
30909
+ if (!Array.isArray(value.sequences))
30910
+ throw new ControlError(`${label}.sequences: expected a list`);
30911
+ value.sequences.forEach((row, index) => {
30912
+ if (!isRecord2(row) || typeof row.name !== "string" || typeof row.kind !== "string" || !Number.isSafeInteger(row.value))
30913
+ throw new ControlError(`${label}.sequences[${index}]: expected {name, kind, value}`);
30914
+ });
30915
+ }
30916
+ if (value.clock !== void 0) {
30917
+ if (!isRecord2(value.clock) || typeof value.clock.now !== "number" || !Number.isFinite(value.clock.now) || typeof value.clock.frozen !== "boolean")
30918
+ throw new ControlError(`${label}.clock: expected {now, frozen}`);
30919
+ }
30920
+ return value;
30921
+ };
30922
+ var normalizeControls = (value, label = "controls") => {
30923
+ if (!isRecord2(value))
30924
+ throw new ControlError(`${label}: expected an object`);
30925
+ unknownKeys(value, NAMESPACE_KEYS, label, REPORTED_KEYS);
30926
+ const plan = {};
30927
+ if (value.seed !== void 0)
30928
+ plan.seed = normalizeSeed(value.seed, `${label}.seed`);
30929
+ if (value.clock !== void 0)
30930
+ plan.clock = normalizeClock(value.clock, `${label}.clock`);
30931
+ if (value.virtualTime !== void 0) {
30932
+ if (typeof value.virtualTime !== "boolean")
30933
+ throw new ControlError(`${label}.virtualTime: expected a boolean`);
30934
+ plan.virtualTime = value.virtualTime;
30935
+ }
30936
+ if (value.latency !== void 0)
30937
+ plan.latency = normalizeLatency(value.latency, `${label}.latency`);
30938
+ if (value.faults !== void 0) {
30939
+ if (!Array.isArray(value.faults) || !value.faults.every(isRecord2))
30940
+ throw new ControlError(`${label}.faults: expected a list of fault rules`);
30941
+ plan.faults = value.faults;
30942
+ }
30943
+ if (value.snapshot !== void 0)
30944
+ plan.snapshot = assertSnapshotDocument(value.snapshot, `${label}.snapshot`);
30945
+ return plan;
30946
+ };
30947
+ var normalizeBootControls = (value, label = "controls") => {
30948
+ if (value === void 0)
30949
+ return { shared: {}, namespaces: {}, credentials: {} };
30950
+ if (!isRecord2(value))
30951
+ throw new ControlError(`${label}: expected an object`);
30952
+ unknownKeys(value, MOCK_KEYS, label);
30953
+ const { credentials, namespaces, ...shared } = value;
30954
+ const plan = {
30955
+ shared: normalizeControls(shared, label),
30956
+ namespaces: {},
30957
+ credentials: {}
30958
+ };
30959
+ if (namespaces !== void 0) {
30960
+ if (!isRecord2(namespaces))
30961
+ throw new ControlError(`${label}.namespaces: expected an object`);
30962
+ for (const [name, each] of Object.entries(namespaces)) {
30963
+ if (!KEY_PATTERN.test(name))
30964
+ throw new ControlError(`${label}.namespaces: bad namespace ${JSON.stringify(name)}`);
30965
+ plan.namespaces[name] = normalizeControls(each, `${label}.namespaces.${name}`);
30966
+ }
30967
+ }
30968
+ if (credentials !== void 0) {
30969
+ if (!isRecord2(credentials))
30970
+ throw new ControlError(`${label}.credentials: expected an object`);
30971
+ for (const [credential, namespace] of Object.entries(credentials)) {
30972
+ if (typeof namespace !== "string" || !KEY_PATTERN.test(namespace))
30973
+ throw new ControlError(`${label}.credentials: bad namespace for a credential`);
30974
+ plan.credentials[credential] = namespace;
30975
+ }
30976
+ }
30977
+ return plan;
30978
+ };
30979
+ var CONTROL_HEADERS = {
30980
+ namespace: "x-mockingbird-namespace",
30981
+ adminKey: "x-mockingbird-admin-key",
30982
+ branch: "x-mockingbird-branch",
30983
+ at: "x-mockingbird-at",
30984
+ reset: "x-mockingbird-reset",
30985
+ restore: "x-mockingbird-restore",
30986
+ seed: "x-mockingbird-seed",
30987
+ time: "x-mockingbird-time",
30988
+ advance: "x-mockingbird-advance",
30989
+ virtualTime: "x-mockingbird-virtual-time",
30990
+ fault: "x-mockingbird-fault",
30991
+ delay: "x-mockingbird-delay",
30992
+ timeout: "x-mockingbird-timeout",
30993
+ snapshot: "x-mockingbird-snapshot"
30994
+ };
30995
+ var CONTROL_HEADER_REFERENCE = [
30996
+ {
30997
+ header: CONTROL_HEADERS.namespace,
30998
+ value: "<key>",
30999
+ scope: "select",
31000
+ guarded: false,
31001
+ summary: "Isolation key. Each key has its own records, clock, random streams, faults, latency, journal and snapshots."
31002
+ },
31003
+ {
31004
+ header: CONTROL_HEADERS.branch,
31005
+ value: "<name>",
31006
+ scope: "select",
31007
+ guarded: false,
31008
+ summary: "Read and write a named copy-on-write branch of the namespace's history."
31009
+ },
31010
+ {
31011
+ header: CONTROL_HEADERS.at,
31012
+ value: "<checkpoint>",
31013
+ scope: "select",
31014
+ guarded: false,
31015
+ summary: "Read the namespace as it was at a checkpoint. With a branch, start the branch there."
31016
+ },
31017
+ {
31018
+ header: CONTROL_HEADERS.reset,
31019
+ value: "true",
31020
+ scope: "namespace",
31021
+ guarded: true,
31022
+ summary: "Reset the namespace to its boot state before the request is handled."
31023
+ },
31024
+ {
31025
+ header: CONTROL_HEADERS.restore,
31026
+ value: "<snapshot>",
31027
+ scope: "namespace",
31028
+ guarded: true,
31029
+ summary: "Restore a snapshot before the request is handled."
31030
+ },
31031
+ {
31032
+ header: CONTROL_HEADERS.seed,
31033
+ value: "<seed>",
31034
+ scope: "namespace",
31035
+ guarded: true,
31036
+ summary: "Restart the namespace's random streams from this seed."
31037
+ },
31038
+ {
31039
+ header: CONTROL_HEADERS.time,
31040
+ value: "<epoch ms | ISO-8601>",
31041
+ scope: "namespace",
31042
+ guarded: true,
31043
+ summary: "Freeze the namespace clock at this instant."
31044
+ },
31045
+ {
31046
+ header: CONTROL_HEADERS.advance,
31047
+ value: "<duration>",
31048
+ scope: "namespace",
31049
+ guarded: true,
31050
+ summary: "Move the namespace clock by this much, forward or (negative) back."
31051
+ },
31052
+ {
31053
+ header: CONTROL_HEADERS.virtualTime,
31054
+ value: "true | false",
31055
+ scope: "namespace",
31056
+ guarded: true,
31057
+ summary: "Spend injected waits on the mock clock (true) or the wall clock (false)."
31058
+ },
31059
+ {
31060
+ header: CONTROL_HEADERS.delay,
31061
+ value: "<duration>",
31062
+ scope: "request",
31063
+ guarded: true,
31064
+ summary: "Hold this response back this long, on top of the namespace's latency."
31065
+ },
31066
+ {
31067
+ header: CONTROL_HEADERS.timeout,
31068
+ value: "<duration>",
31069
+ scope: "request",
31070
+ guarded: true,
31071
+ summary: "Never answer this request: hold it this long, then drop the connection."
31072
+ },
31073
+ {
31074
+ header: CONTROL_HEADERS.fault,
31075
+ value: '<status> | drop | <preset> | key=value; \u2026 | {"status": 503, \u2026}',
31076
+ scope: "request",
31077
+ guarded: true,
31078
+ summary: "Fault this request only. Stored fault rules are not consulted when it answers."
31079
+ },
31080
+ {
31081
+ header: CONTROL_HEADERS.snapshot,
31082
+ value: "<name>",
31083
+ scope: "namespace",
31084
+ guarded: true,
31085
+ summary: "Snapshot the namespace after the request is handled. Echoed on the response."
31086
+ }
31087
+ ];
31088
+ var GUARDED = CONTROL_HEADER_REFERENCE.filter((each) => each.guarded).map((each) => each.header);
31089
+
30577
31090
  // src/wire/probe.ts
30578
31091
  import { createHash as createHash3, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
30579
31092
  import { createConnection } from "node:net";
@@ -30678,26 +31191,38 @@ function probe(connectionString) {
30678
31191
  async function startProtocol(entry) {
30679
31192
  const namespaces = /* @__PURE__ */ new Map();
30680
31193
  const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
31194
+ if (entry.seed !== void 0 && (!String(entry.seed).trim() || !Number.isFinite(Number(entry.seed))))
31195
+ throw new Error("Postgres seed must be a finite number");
31196
+ let admission;
31197
+ const boot = normalizeBootControls(entry.adminControls);
31198
+ const create = async (name, databaseName, port = 0) => {
31199
+ if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string" || !databaseName.length)
31200
+ throw new Error("invalid Postgres namespace/database name");
31201
+ const clock = createClock();
31202
+ const control = { ...boot.shared.clock, ...boot.namespaces[name]?.clock };
31203
+ if (control.frozen) clock.freeze();
31204
+ if (control.now !== void 0) clock.set(control.now);
31205
+ const database = new Database({
31206
+ now: () => new Date(clock.now()),
31207
+ ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
31208
+ });
31209
+ const server = await serve({
31210
+ database,
31211
+ databaseName,
31212
+ allowDatabaseSelection: true,
31213
+ host: entry.host ?? "127.0.0.1",
31214
+ port,
31215
+ ...entry.password !== void 0 ? { password: entry.password } : {},
31216
+ ...entry.user !== void 0 ? { user: entry.user } : {}
31217
+ });
31218
+ server.server.prependListener("connection", (socket) => {
31219
+ if (admission?.(name) === false) socket.destroy();
31220
+ });
31221
+ namespaces.set(name, { server, clock });
31222
+ };
30681
31223
  try {
30682
- for (const [name, databaseName] of Object.entries(selectors)) {
30683
- if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
30684
- throw new Error("invalid Postgres namespace/database name");
30685
- const clock = createClock();
30686
- const database = new Database({
30687
- now: () => new Date(clock.now()),
30688
- ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
30689
- });
30690
- const server = await serve({
30691
- database,
30692
- databaseName,
30693
- allowDatabaseSelection: true,
30694
- host: entry.host ?? "127.0.0.1",
30695
- port: name === "default" ? entry.port ?? 0 : 0,
30696
- ...entry.password !== void 0 ? { password: entry.password } : {},
30697
- ...entry.user !== void 0 ? { user: entry.user } : {}
30698
- });
30699
- namespaces.set(name, { server, clock });
30700
- }
31224
+ for (const [name, databaseName] of Object.entries(selectors))
31225
+ await create(name, databaseName, name === "default" ? entry.port ?? 0 : 0);
30701
31226
  } catch (error) {
30702
31227
  await Promise.allSettled([...namespaces.values()].map(({ server }) => server.close()));
30703
31228
  throw error;
@@ -30719,17 +31244,143 @@ async function startProtocol(entry) {
30719
31244
  protocol: "postgres",
30720
31245
  url: publicUrl("default"),
30721
31246
  connection: get("default").server.connectionString,
30722
- connections: Object.fromEntries([...namespaces].map(([name, { server }]) => [name, server.connectionString])),
30723
- namespaces: {
30724
- mechanism: "endpoint",
30725
- endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl(name)]))
31247
+ get connections() {
31248
+ return Object.fromEntries([...namespaces].map(([name, { server }]) => [name, server.connectionString]));
31249
+ },
31250
+ get namespaces() {
31251
+ return {
31252
+ mechanism: "endpoint",
31253
+ endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl(name)]))
31254
+ };
31255
+ },
31256
+ async createNamespace(name) {
31257
+ if (namespaces.has(name)) throw new Error("namespace already exists");
31258
+ await create(name, entry.database ?? "postgres");
31259
+ origins.set(name, get(name).server.snapshotAll());
31260
+ },
31261
+ async deleteNamespace(name) {
31262
+ const { server } = get(name);
31263
+ if (server.connections) throw new Error("Postgres namespace has open sessions");
31264
+ await server.close();
31265
+ for (const database of server.databaseNames()) server.getDatabase(database).close();
31266
+ namespaces.delete(name);
31267
+ origins.delete(name);
31268
+ },
31269
+ async exportCheckpoint(namespace, handle) {
31270
+ const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
31271
+ if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
31272
+ return {
31273
+ format: "mockingbird.postgres/v1",
31274
+ inventory: Object.fromEntries(
31275
+ [...point.databases].map(([name, snapshot]) => {
31276
+ const database = snapshot.open();
31277
+ try {
31278
+ const tables = database.prepare("SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'public'").all();
31279
+ return [
31280
+ name,
31281
+ Object.fromEntries(
31282
+ tables.map((table) => {
31283
+ const identifier2 = table.table_name.replaceAll('"', '""');
31284
+ const rows = database.prepare(`SELECT * FROM public."${identifier2}"`).all();
31285
+ return [
31286
+ table.table_name,
31287
+ {
31288
+ count: rows.length,
31289
+ records: Object.fromEntries(
31290
+ rows.map((row, index) => [
31291
+ String(row.id ?? index),
31292
+ createHash4("sha256").update(
31293
+ JSON.stringify(
31294
+ row,
31295
+ (_key, value) => typeof value === "bigint" ? String(value) : value
31296
+ )
31297
+ ).digest("hex")
31298
+ ])
31299
+ )
31300
+ }
31301
+ ];
31302
+ })
31303
+ )
31304
+ ];
31305
+ } finally {
31306
+ database.close();
31307
+ }
31308
+ })
31309
+ ),
31310
+ databases: [...point.databases].map(([name, snapshot]) => ({
31311
+ name,
31312
+ bytes: Buffer.from(snapshot.encode()).toString("base64")
31313
+ })),
31314
+ clock: point.clock,
31315
+ faults: point.faults
31316
+ };
31317
+ },
31318
+ async importCheckpoint(namespace, document) {
31319
+ const doc = document;
31320
+ if (doc?.format !== "mockingbird.postgres/v1" || !Array.isArray(doc.databases) || !doc.databases.length || !Number.isFinite(doc.clock?.now) || typeof doc.clock?.frozen !== "boolean")
31321
+ throw new Error("invalid Postgres archive");
31322
+ const databases = /* @__PURE__ */ new Map();
31323
+ for (const row of doc.databases) {
31324
+ if (typeof row.name !== "string" || databases.has(row.name) || typeof row.bytes !== "string" || !/^[A-Za-z0-9+/]*={0,2}$/.test(row.bytes))
31325
+ throw new Error("invalid Postgres archive database");
31326
+ databases.set(row.name, Snapshot.decode(Buffer.from(row.bytes, "base64")));
31327
+ }
31328
+ const handle = {};
31329
+ snapshots.set(handle, { namespace, databases, clock: doc.clock, faults: doc.faults ?? {} });
31330
+ return handle;
31331
+ },
31332
+ async inspect(namespace, { offset, limit, collection }) {
31333
+ const { server } = get(namespace);
31334
+ const database = server.getDatabase(server.databaseNames()[0]);
31335
+ const tables = database.prepare("SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'public'").all();
31336
+ if (!collection) return { tables };
31337
+ const table = collection.replaceAll('"', '""');
31338
+ return {
31339
+ records: database.prepare(`SELECT * FROM public."${table}" LIMIT ${limit} OFFSET ${offset}`).all(),
31340
+ offset
31341
+ };
31342
+ },
31343
+ async initializationAccess() {
31344
+ const privateServers = [];
31345
+ const connections = {};
31346
+ try {
31347
+ for (const [name, { server }] of namespaces) {
31348
+ const databaseName = server.databaseNames()[0];
31349
+ const seedServer = await serve({
31350
+ database: server.getDatabase(databaseName),
31351
+ databaseName,
31352
+ port: 0,
31353
+ host: "127.0.0.1",
31354
+ ...entry.user ? { user: entry.user } : {},
31355
+ ...entry.password ? { password: entry.password } : {}
31356
+ });
31357
+ privateServers.push(seedServer);
31358
+ connections[name] = seedServer.connectionString;
31359
+ }
31360
+ } catch (error) {
31361
+ await Promise.allSettled(privateServers.map((s) => s.close()));
31362
+ throw error;
31363
+ }
31364
+ return {
31365
+ connections,
31366
+ close: async () => {
31367
+ await Promise.all(privateServers.map((s) => s.close()));
31368
+ }
31369
+ };
31370
+ },
31371
+ setAdmission(allow) {
31372
+ admission = allow;
30726
31373
  },
30727
31374
  async ready() {
31375
+ if (admission) return [...namespaces.values()].every(({ server }) => server.server.listening);
30728
31376
  const results = await Promise.all(
30729
31377
  [...namespaces.values()].map(
30730
31378
  ({ server }) => server.server.listening ? probe(server.connectionString) : false
30731
31379
  )
30732
31380
  );
31381
+ const deadline = Date.now() + 2e3;
31382
+ while ([...namespaces.values()].some(({ server }) => server.connections > 0) && Date.now() < deadline)
31383
+ await new Promise((resolve) => setTimeout(resolve, 1));
30733
31384
  return results.every(Boolean);
30734
31385
  },
30735
31386
  async close() {
@@ -30791,7 +31442,11 @@ async function startProtocol(entry) {
30791
31442
  for (const name of server.databaseNames()) server.getDatabase(name).now = () => new Date(clock.now());
30792
31443
  server.clearFaults();
30793
31444
  clock.reset();
31445
+ const control = { ...boot.shared.clock, ...boot.namespaces[namespace]?.clock };
31446
+ if (control.frozen) clock.freeze();
31447
+ if (control.now !== void 0) clock.set(control.now);
30794
31448
  },
31449
+ nextEvent: () => void 0,
30795
31450
  clock: (namespace) => get(namespace).clock
30796
31451
  };
30797
31452
  }