@crvouga/mockingbird-service-postgres 3.11.0 → 3.12.1

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/admin.js CHANGED
@@ -1,3 +1,249 @@
1
+ // ../../core/dist/fork-map.js
2
+ var ForkMap = class _ForkMap {
3
+ owner = {};
4
+ index = { owner: this.owner, groups: [] };
5
+ order = { owner: this.owner, cells: [] };
6
+ shift = 0;
7
+ length = 0;
8
+ count = 0;
9
+ nextSequence = 0;
10
+ layout = 0;
11
+ cleared = 0;
12
+ version = 0;
13
+ constructor(entries) {
14
+ if (entries)
15
+ for (const [key, value] of entries)
16
+ this.set(key, value);
17
+ }
18
+ get size() {
19
+ return this.count;
20
+ }
21
+ get [Symbol.toStringTag]() {
22
+ return "Map";
23
+ }
24
+ fork() {
25
+ const copy2 = new _ForkMap();
26
+ copy2.index = this.index;
27
+ copy2.order = this.order;
28
+ copy2.shift = this.shift;
29
+ copy2.length = this.length;
30
+ copy2.count = this.count;
31
+ copy2.nextSequence = this.nextSequence;
32
+ this.owner = {};
33
+ return copy2;
34
+ }
35
+ /** Visit changed hash buckets only. Values must be replaced, rather than mutated in place. */
36
+ *changes(previous) {
37
+ if (previous?.index === this.index)
38
+ return;
39
+ for (let group = 0; group < 32; group++) {
40
+ const currentGroup = this.index.groups[group];
41
+ const oldGroup = previous?.index.groups[group];
42
+ if (currentGroup === oldGroup)
43
+ continue;
44
+ for (let bucket = 0; bucket < 32; bucket++) {
45
+ const current = currentGroup?.buckets[bucket]?.data;
46
+ const old = oldGroup?.buckets[bucket]?.data;
47
+ if (current === old)
48
+ continue;
49
+ if (current)
50
+ for (const [key, entry] of current) {
51
+ const before = old?.get(key);
52
+ if (!before || !Object.is(before.value, entry.value) || before.sequence !== entry.sequence)
53
+ yield { key, value: entry.value, deleted: false };
54
+ }
55
+ if (old) {
56
+ for (const key of old.keys())
57
+ if (!current?.has(key))
58
+ yield { key, deleted: true };
59
+ }
60
+ }
61
+ }
62
+ }
63
+ get(key) {
64
+ return this.find(key)?.value;
65
+ }
66
+ has(key) {
67
+ return this.find(key) !== void 0;
68
+ }
69
+ set(key, value) {
70
+ if (typeof key === "number" && key === 0)
71
+ key = 0;
72
+ const hash = hashKey(key);
73
+ const existing = this.bucket(hash)?.get(key);
74
+ if (existing && Object.is(existing.value, value))
75
+ return this;
76
+ const entry = existing ? { ...existing, value } : { key, value, position: this.length++, sequence: this.nextSequence++ };
77
+ this.writableBucket(hash).set(key, entry);
78
+ this.writeOrder(entry.position, entry);
79
+ if (!existing)
80
+ this.count++;
81
+ this.version++;
82
+ return this;
83
+ }
84
+ delete(key) {
85
+ const hash = hashKey(key);
86
+ const entry = this.bucket(hash)?.get(key);
87
+ if (!entry)
88
+ return false;
89
+ this.writableBucket(hash).delete(key);
90
+ this.writeOrder(entry.position, void 0);
91
+ this.count--;
92
+ this.version++;
93
+ if (this.length > 1024 && this.length > this.count * 2)
94
+ this.compact();
95
+ return true;
96
+ }
97
+ clear() {
98
+ this.index = { owner: this.owner, groups: [] };
99
+ this.order = { owner: this.owner, cells: [] };
100
+ this.shift = this.length = this.count = this.nextSequence = 0;
101
+ this.layout++;
102
+ this.cleared++;
103
+ this.version++;
104
+ }
105
+ entries() {
106
+ return this.iterate(0);
107
+ }
108
+ keys() {
109
+ return this.iterate(1);
110
+ }
111
+ values() {
112
+ return this.iterate(2);
113
+ }
114
+ [Symbol.iterator]() {
115
+ return this.entries();
116
+ }
117
+ forEach(callback, thisArg) {
118
+ for (const [key, value] of this)
119
+ callback.call(thisArg, value, key, this);
120
+ }
121
+ find(key) {
122
+ return this.bucket(hashKey(key))?.get(key);
123
+ }
124
+ bucket(hash) {
125
+ return this.index.groups[hash >>> 5 & 31]?.buckets[hash & 31]?.data;
126
+ }
127
+ writableBucket(hash) {
128
+ if (this.index.owner !== this.owner)
129
+ this.index = { owner: this.owner, groups: this.index.groups.slice() };
130
+ const groupIndex = hash >>> 5 & 31;
131
+ let group = this.index.groups[groupIndex];
132
+ if (!group || group.owner !== this.owner) {
133
+ group = { owner: this.owner, buckets: group?.buckets.slice() ?? [] };
134
+ this.index.groups[groupIndex] = group;
135
+ }
136
+ const at = hash & 31;
137
+ let bucket = group.buckets[at];
138
+ if (!bucket || bucket.owner !== this.owner) {
139
+ bucket = { owner: this.owner, data: bucket ? this.copyBucket(bucket.data) : /* @__PURE__ */ new Map() };
140
+ group.buckets[at] = bucket;
141
+ }
142
+ return bucket.data;
143
+ }
144
+ copyBucket(bucket) {
145
+ return new Map(bucket);
146
+ }
147
+ leaf(position) {
148
+ let node = this.order;
149
+ for (let shift = this.shift; shift > 0; shift -= 5)
150
+ node = node.cells[Math.floor(position / 2 ** shift) & 31];
151
+ return node;
152
+ }
153
+ writeOrder(position, entry) {
154
+ while (position >= 2 ** (this.shift + 5)) {
155
+ this.order = { owner: this.owner, cells: [this.order] };
156
+ this.shift += 5;
157
+ }
158
+ if (this.order.owner !== this.owner)
159
+ this.order = { owner: this.owner, cells: this.order.cells.slice() };
160
+ let node = this.order;
161
+ for (let shift = this.shift; shift > 0; shift -= 5) {
162
+ const at = Math.floor(position / 2 ** shift) & 31;
163
+ let child = node.cells[at];
164
+ if (!child || child.owner !== this.owner) {
165
+ child = { owner: this.owner, cells: child?.cells.slice() ?? [] };
166
+ node.cells[at] = child;
167
+ }
168
+ node = child;
169
+ }
170
+ node.cells[position & 31] = entry;
171
+ }
172
+ compact() {
173
+ const entries = [];
174
+ for (let position = 0; position < this.length; position++) {
175
+ const entry = this.leaf(position).cells[position & 31];
176
+ if (entry)
177
+ entries.push(entry);
178
+ }
179
+ this.index = { owner: this.owner, groups: [] };
180
+ this.order = { owner: this.owner, cells: [] };
181
+ this.shift = this.length = this.count = 0;
182
+ for (const original of entries) {
183
+ const entry = { ...original, position: this.length++ };
184
+ this.writableBucket(hashKey(entry.key)).set(entry.key, entry);
185
+ this.writeOrder(entry.position, entry);
186
+ this.count++;
187
+ }
188
+ this.layout++;
189
+ }
190
+ *iterate(kind) {
191
+ let position = 0;
192
+ let layout = this.layout;
193
+ let cleared = this.cleared;
194
+ let lastSequence = -1;
195
+ let version = -1;
196
+ let leaf;
197
+ let page = -1;
198
+ for (; ; ) {
199
+ if (cleared !== this.cleared) {
200
+ position = 0;
201
+ lastSequence = -1;
202
+ cleared = this.cleared;
203
+ }
204
+ if (layout !== this.layout) {
205
+ position = 0;
206
+ while (position < this.length) {
207
+ const entry2 = this.leaf(position).cells[position & 31];
208
+ if (entry2 && entry2.sequence > lastSequence)
209
+ break;
210
+ position++;
211
+ }
212
+ layout = this.layout;
213
+ }
214
+ if (position >= this.length)
215
+ return;
216
+ const nextPage = Math.floor(position / 32);
217
+ if (page !== nextPage || version !== this.version) {
218
+ leaf = this.leaf(position);
219
+ page = nextPage;
220
+ version = this.version;
221
+ }
222
+ const entry = leaf?.cells[position++ & 31];
223
+ if (!entry)
224
+ continue;
225
+ lastSequence = entry.sequence;
226
+ yield kind === 0 ? [entry.key, entry.value] : kind === 1 ? entry.key : entry.value;
227
+ }
228
+ }
229
+ };
230
+ function hashKey(key) {
231
+ if (typeof key === "number" && Number.isInteger(key))
232
+ return mix((key | 0) ^ (key / 4294967296 | 0));
233
+ if (typeof key === "bigint")
234
+ return mix(Number(BigInt.asIntN(32, key ^ key >> 32n)));
235
+ const text = String(key);
236
+ let hash = 2166136261;
237
+ for (let i = 0; i < text.length; i++)
238
+ hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
239
+ return hash;
240
+ }
241
+ function mix(value) {
242
+ value = Math.imul(value ^ value >>> 16, 73244475);
243
+ value = Math.imul(value ^ value >>> 16, 73244475);
244
+ return value ^ value >>> 16;
245
+ }
246
+
1
247
  // ../../core/dist/mount.js
2
248
  var prefixes = /* @__PURE__ */ new WeakMap();
3
249
  var requestBaseUrl = (request) => new URL(request.url).origin + (prefixes.get(request) ?? "");
@@ -7091,10 +7337,81 @@ function parseArrayText(t, text, ctx) {
7091
7337
 
7092
7338
  // src/storage/database-state.ts
7093
7339
  var TableData = class _TableData {
7340
+ /** @internal Optional physical row journal; IDs remain stable across heap splices. */
7341
+ durableChanges = null;
7342
+ durableNextId = 0;
7343
+ durableEpoch;
7344
+ durableUnsafe = false;
7345
+ durableIds;
7346
+ enableDurabilityTracking(ids, nextId) {
7347
+ if (this.durableChanges && !this.durableUnsafe && !ids) return;
7348
+ this.materializeSlab();
7349
+ this.durableIds = /* @__PURE__ */ new WeakMap();
7350
+ this.durableChanges = new ForkMap();
7351
+ this.durableNextId = nextId ?? this.#rows.length;
7352
+ for (let i = 0; i < this.#rows.length; i++) this.durableIds.set(this.#rows[i], ids?.[i] ?? i);
7353
+ this.durableEpoch = {};
7354
+ this.durableUnsafe = false;
7355
+ }
7356
+ durableRowId(row) {
7357
+ const id = this.durableIds?.get(row);
7358
+ if (id === void 0) throw new Error("Untracked durable row");
7359
+ return id;
7360
+ }
7361
+ disableDurabilityTracking() {
7362
+ this.durableChanges = null;
7363
+ this.durableIds = void 0;
7364
+ this.durableEpoch = void 0;
7365
+ this.durableUnsafe = false;
7366
+ }
7367
+ recordDurableRow(row, previous) {
7368
+ if (!this.durableChanges) return;
7369
+ const id = (previous && this.durableIds?.get(previous)) ?? this.durableIds?.get(row) ?? this.durableNextId++;
7370
+ this.durableIds.set(row, id);
7371
+ this.durableChanges.set(id, { row, xmin: this.rowXmins.get(row) ?? 2 });
7372
+ }
7373
+ recordDurableDelete(row) {
7374
+ if (this.durableChanges) this.durableChanges.set(this.durableRowId(row), { row: null, xmin: 0 });
7375
+ }
7376
+ /** @internal Metadata twin with no visits/copies of existing rows or index entries. */
7377
+ emptyForDurability() {
7378
+ const copy2 = new _TableData(this.schema, this.name, this.columns, this.oid, this.temp);
7379
+ copy2.constraints = this.constraints;
7380
+ copy2.triggers = this.triggers;
7381
+ copy2.unlogged = this.unlogged;
7382
+ return copy2;
7383
+ }
7384
+ /** @internal Serialization/SQL scans must not count as public native-array exposure. */
7385
+ serializationRows() {
7386
+ this.materializeSlab();
7387
+ return this.materializedRows();
7388
+ }
7389
+ /** @internal Internal mutable array access, with ordinary public mutableRows kept intact. */
7390
+ writeRows() {
7391
+ return this.serializationRows();
7392
+ }
7393
+ /** @internal Ordinary snapshot branches must not inherit a persistence journal. */
7394
+ untrackedFork() {
7395
+ const copy2 = this.emptyForDurability();
7396
+ copy2.rowXmins = this.rowXmins;
7397
+ copy2.slabXmins = this.slabXmins;
7398
+ if (this.slab) copy2.slab = this.slab;
7399
+ else {
7400
+ copy2.#rows = this.#rows;
7401
+ copy2.#rowPatches = this.#rowPatches?.fork() ?? null;
7402
+ copy2.#rowsShared = this.#rowsShared = true;
7403
+ copy2.tuplesShared = this.tuplesShared = true;
7404
+ }
7405
+ copy2.indexStores = this.indexStores ? new Map([...this.indexStores].map(([name, store]) => [name, store.clone()])) : null;
7406
+ return copy2;
7407
+ }
7094
7408
  name;
7095
7409
  schema;
7096
7410
  columns;
7097
- rows;
7411
+ #rows = [];
7412
+ #rowPatches = null;
7413
+ #rowsShared = false;
7414
+ #rowsExposed = false;
7098
7415
  rowXmins = /* @__PURE__ */ new WeakMap();
7099
7416
  slabXmins = null;
7100
7417
  /** Frozen columnar storage after snapshot hydrate; `rows` stays empty until materialized. */
@@ -7110,22 +7427,54 @@ var TableData = class _TableData {
7110
7427
  shareCount = 0;
7111
7428
  /** Derived unique/btree index maps; null until built or invalidated. */
7112
7429
  indexStores = null;
7430
+ /** DML replaces tuples; DDL must detach them before changing cells in place. */
7431
+ tuplesShared = false;
7113
7432
  constructor(schema, name, columns, oid, temp = false) {
7114
7433
  this.schema = schema;
7115
7434
  this.name = name;
7116
7435
  this.columns = columns;
7117
- this.rows = [];
7436
+ Object.defineProperty(this, "rows", {
7437
+ enumerable: true,
7438
+ configurable: true,
7439
+ get: this.nativeRows,
7440
+ set: this.setNativeRows
7441
+ });
7118
7442
  this.constraints = [];
7119
7443
  this.triggers = [];
7120
7444
  this.temp = temp;
7121
7445
  this.oid = oid;
7122
7446
  }
7123
7447
  rowCount() {
7124
- return this.slab ? this.slab.rowCount : this.rows.length;
7448
+ return this.slab ? this.slab.rowCount : this.#rows.length;
7125
7449
  }
7126
7450
  rowAt(index) {
7127
7451
  if (this.slab) return this.slab.rowAt(index);
7128
- return this.rows[index] ?? [];
7452
+ return this.#rowPatches?.get(index) ?? this.#rows[index] ?? [];
7453
+ }
7454
+ /** Replace an existing tuple without copying the array of unchanged row references. */
7455
+ replaceRow(index, row) {
7456
+ if (this.durableChanges) this.recordDurableRow(row, this.rowAt(index));
7457
+ if (this.#rowPatches) this.#rowPatches.set(index, row);
7458
+ else this.#rows[index] = row;
7459
+ }
7460
+ materializedRows() {
7461
+ if (this.#rowsShared) this.#rows = this.#rows.slice();
7462
+ if (this.#rowPatches) for (const [index, row] of this.#rowPatches) this.#rows[index] = row;
7463
+ this.#rowPatches = null;
7464
+ this.#rowsShared = false;
7465
+ return this.#rows;
7466
+ }
7467
+ nativeRows() {
7468
+ if (this.durableChanges) this.durableUnsafe = true;
7469
+ this.#rowsExposed = true;
7470
+ return this.materializedRows();
7471
+ }
7472
+ setNativeRows(rows) {
7473
+ if (this.durableChanges) this.durableUnsafe = true;
7474
+ this.#rows = rows;
7475
+ this.#rowPatches = null;
7476
+ this.#rowsShared = false;
7477
+ this.#rowsExposed = true;
7129
7478
  }
7130
7479
  /** All rows for scans; materializes slab once into `rows` when needed. */
7131
7480
  allRows() {
@@ -7134,13 +7483,15 @@ var TableData = class _TableData {
7134
7483
  }
7135
7484
  attachSlab(slab) {
7136
7485
  this.slab = slab;
7137
- this.rows = [];
7486
+ this.#rows = [];
7487
+ this.#rowPatches = null;
7488
+ this.#rowsShared = false;
7138
7489
  }
7139
7490
  materializeSlab() {
7140
7491
  if (!this.slab) return;
7141
- this.rows = this.slab.materialize();
7492
+ this.#rows = this.slab.materialize();
7142
7493
  if (this.slabXmins)
7143
- this.rows.forEach((row, index) => {
7494
+ this.#rows.forEach((row, index) => {
7144
7495
  this.rowXmins.set(row, this.slabXmins[index] ?? 2);
7145
7496
  });
7146
7497
  this.slabXmins = null;
@@ -7164,7 +7515,7 @@ var TableData = class _TableData {
7164
7515
  columnIndex(name) {
7165
7516
  return this.columns.findIndex((c) => c.name === name);
7166
7517
  }
7167
- clone() {
7518
+ cloneMetadata() {
7168
7519
  const t = new _TableData(
7169
7520
  this.schema,
7170
7521
  this.name,
@@ -7172,21 +7523,68 @@ var TableData = class _TableData {
7172
7523
  this.oid,
7173
7524
  this.temp
7174
7525
  );
7526
+ t.unlogged = this.unlogged;
7527
+ t.durableChanges = this.durableChanges?.fork() ?? null;
7528
+ t.durableIds = this.durableIds;
7529
+ t.durableNextId = this.durableNextId;
7530
+ t.durableEpoch = this.durableEpoch;
7531
+ t.durableUnsafe = this.durableUnsafe;
7532
+ t.constraints = this.constraints.map((c) => ({ ...c }));
7533
+ t.triggers = this.triggers.map((tr) => ({ ...tr }));
7534
+ t.indexStores = this.indexStores ? new Map([...this.indexStores].map(([k, v]) => [k, v.clone()])) : null;
7535
+ return t;
7536
+ }
7537
+ clone() {
7538
+ const t = this.cloneMetadata();
7539
+ if (t.durableChanges) t.durableUnsafe = true;
7175
7540
  if (this.slab) {
7176
- t.rows = this.slab.materialize().map((r) => r.slice());
7541
+ t.#rows = this.slab.materialize().map((r) => r.slice());
7177
7542
  } else {
7178
- t.rows = this.rows.map((r) => r.slice());
7543
+ t.#rows = this.materializedRows().map((r) => r.slice());
7179
7544
  }
7180
- t.rows.forEach((row, index) => {
7181
- const xmin = this.slabXmins?.[index] ?? this.rowXmins.get(this.rows[index]);
7545
+ t.#rows.forEach((row, index) => {
7546
+ const xmin = this.slabXmins?.[index] ?? this.rowXmins.get(this.#rows[index]);
7182
7547
  if (xmin !== void 0) t.rowXmins.set(row, xmin);
7183
7548
  });
7184
- t.unlogged = this.unlogged;
7185
- t.constraints = this.constraints.map((c) => ({ ...c }));
7186
- t.triggers = this.triggers.map((tr) => ({ ...tr }));
7187
- t.indexStores = this.indexStores ? new Map([...this.indexStores].map(([k, v]) => [k, v.clone()])) : null;
7188
7549
  return t;
7189
7550
  }
7551
+ /** DML creates new tuple arrays; retain existing tuples and xmin bindings until DDL. */
7552
+ cloneForDml() {
7553
+ if (this.triggers.length > 0) return this.cloneForWrite();
7554
+ const t = this.cloneMetadata();
7555
+ t.rowXmins = this.rowXmins;
7556
+ if (this.slab) {
7557
+ t.#rows = this.slab.materialize();
7558
+ if (this.slabXmins) {
7559
+ for (let index = 0; index < t.#rows.length; index++)
7560
+ t.rowXmins.set(t.#rows[index], this.slabXmins[index] ?? 2);
7561
+ }
7562
+ } else {
7563
+ if (this.#rows.length >= 4096 && !this.#rowsExposed) {
7564
+ this.#rowPatches ??= new ForkMap();
7565
+ t.#rowPatches = this.#rowPatches.fork();
7566
+ t.#rows = this.#rows;
7567
+ t.#rowsShared = this.#rowsShared = true;
7568
+ } else t.#rows = this.materializedRows().slice();
7569
+ this.tuplesShared = true;
7570
+ }
7571
+ t.tuplesShared = true;
7572
+ return t;
7573
+ }
7574
+ /** A live DML fork can be unfrozen while its unchanged tuples still belong to history. */
7575
+ ownTuples() {
7576
+ if (this.durableChanges) this.durableUnsafe = true;
7577
+ if (!this.tuplesShared) return;
7578
+ const xmins = /* @__PURE__ */ new WeakMap();
7579
+ this.rows = this.rows.map((row) => {
7580
+ const copy2 = row.slice();
7581
+ const xmin = this.rowXmins.get(row);
7582
+ if (xmin !== void 0) xmins.set(copy2, xmin);
7583
+ return copy2;
7584
+ });
7585
+ this.rowXmins = xmins;
7586
+ this.tuplesShared = false;
7587
+ }
7190
7588
  /** Independent writable copy (row tuples copied so ALTER COLUMN is isolated). */
7191
7589
  cloneForWrite() {
7192
7590
  const t = this.clone();
@@ -7540,12 +7938,21 @@ var DatabaseState = class _DatabaseState {
7540
7938
  }
7541
7939
  }
7542
7940
  ensureWritableTable(table) {
7543
- if (!table.frozen) return table;
7941
+ if (!table.frozen) {
7942
+ table.ownTuples();
7943
+ return table;
7944
+ }
7544
7945
  const schema = this.schemas.get(table.schema);
7545
7946
  const copy2 = table.cloneForWrite();
7546
7947
  schema?.tables.set(table.name, copy2);
7547
7948
  return copy2;
7548
7949
  }
7950
+ ensureWritableTableForDml(table) {
7951
+ if (!table.frozen) return table;
7952
+ const copy2 = table.cloneForDml();
7953
+ this.schemas.get(table.schema)?.tables.set(table.name, copy2);
7954
+ return copy2;
7955
+ }
7549
7956
  ensureWritableSequence(seq) {
7550
7957
  if ((seq.shareCount ?? 0) === 0) return seq;
7551
7958
  const schema = this.schemas.get(seq.schema);
@@ -10655,7 +11062,7 @@ function evalPgcryptoFunction(ctx, fn, args) {
10655
11062
  // src/functions/hash-fns.ts
10656
11063
  var utf83 = new TextEncoder();
10657
11064
  var rot = (x, k) => (x << k | x >>> 32 - k) >>> 0;
10658
- function mix(s) {
11065
+ function mix2(s) {
10659
11066
  s[0] = s[0] - s[2] >>> 0;
10660
11067
  s[0] = (s[0] ^ rot(s[2], 4)) >>> 0;
10661
11068
  s[2] = s[2] + s[1] >>> 0;
@@ -10699,7 +11106,7 @@ function hashAny(k, seed) {
10699
11106
  const bits = BigInt.asUintN(64, seed);
10700
11107
  s[0] = s[0] + Number(bits >> 32n) >>> 0;
10701
11108
  s[1] = s[1] + Number(bits & 0xffffffffn) >>> 0;
10702
- mix(s);
11109
+ mix2(s);
10703
11110
  }
10704
11111
  let at = 0;
10705
11112
  let len = k.length;
@@ -10707,7 +11114,7 @@ function hashAny(k, seed) {
10707
11114
  s[0] = s[0] + word(k, at) >>> 0;
10708
11115
  s[1] = s[1] + word(k, at + 4) >>> 0;
10709
11116
  s[2] = s[2] + word(k, at + 8) >>> 0;
10710
- mix(s);
11117
+ mix2(s);
10711
11118
  }
10712
11119
  for (let i = len - 1; i >= 0; i--) {
10713
11120
  const byte = k[at + i];
@@ -17279,6 +17686,21 @@ var IndexStore = class _IndexStore {
17279
17686
  name;
17280
17687
  unique;
17281
17688
  entries = /* @__PURE__ */ new Map();
17689
+ integerKeys = null;
17690
+ shared = false;
17691
+ ownedBuckets = /* @__PURE__ */ new WeakSet();
17692
+ writableBucket(key, bucket) {
17693
+ if (this.ownedBuckets.has(bucket)) return bucket;
17694
+ const copy2 = bucket.slice();
17695
+ this.entries.set(key, copy2);
17696
+ this.ownedBuckets.add(copy2);
17697
+ return copy2;
17698
+ }
17699
+ detach() {
17700
+ if (!this.shared) return;
17701
+ if (!(this.entries instanceof ForkMap)) this.entries = new Map(this.entries);
17702
+ this.shared = false;
17703
+ }
17282
17704
  constructor(name, unique = false) {
17283
17705
  this.name = name;
17284
17706
  this.unique = unique;
@@ -17297,36 +17719,78 @@ var IndexStore = class _IndexStore {
17297
17719
  }
17298
17720
  insert(key, rowIdx) {
17299
17721
  if (this.unique) this.checkUnique(key, rowIdx);
17722
+ this.detach();
17723
+ this.integerKeys = null;
17300
17724
  const existing = this.entries.get(key);
17301
17725
  if (!existing) {
17302
- this.entries.set(key, [rowIdx]);
17726
+ const bucket = [rowIdx];
17727
+ this.ownedBuckets.add(bucket);
17728
+ this.entries.set(key, bucket);
17303
17729
  return;
17304
17730
  }
17305
17731
  if (existing.includes(rowIdx)) return;
17306
- existing.push(rowIdx);
17732
+ this.writableBucket(key, existing).push(rowIdx);
17307
17733
  }
17308
17734
  remove(key, rowIdx) {
17309
17735
  const existing = this.entries.get(key);
17310
17736
  if (!existing) return;
17311
17737
  const at = existing.indexOf(rowIdx);
17312
17738
  if (at < 0) return;
17313
- existing.splice(at, 1);
17314
- if (existing.length === 0) this.entries.delete(key);
17739
+ this.detach();
17740
+ this.integerKeys = null;
17741
+ if (existing.length === 1) this.entries.delete(key);
17742
+ else {
17743
+ const remaining = this.writableBucket(key, existing);
17744
+ remaining.splice(at, 1);
17745
+ this.entries.set(key, remaining);
17746
+ }
17315
17747
  }
17316
17748
  lookup(key) {
17317
17749
  return this.entries.get(key) ?? [];
17318
17750
  }
17751
+ /** Ordered access for a single int2/int4 key (the planner validates the key type). */
17752
+ integerRange(lower, upper, includeLower, includeUpper) {
17753
+ const keys = this.integerKeys ??= [...this.entries].map(([key, rows]) => ({ value: Number(key.slice(1)), rows })).sort((a, b) => a.value - b.value);
17754
+ let lo = 0;
17755
+ let hi = keys.length;
17756
+ while (lo < hi) {
17757
+ const mid = lo + hi >>> 1;
17758
+ const value = keys[mid].value;
17759
+ if (value < lower || !includeLower && value === lower) lo = mid + 1;
17760
+ else hi = mid;
17761
+ }
17762
+ const result = [];
17763
+ for (let i = lo; i < keys.length; i++) {
17764
+ const key = keys[i];
17765
+ if (key.value > upper || !includeUpper && key.value === upper) break;
17766
+ result.push(...key.rows);
17767
+ }
17768
+ return result.sort((a, b) => a - b);
17769
+ }
17319
17770
  clear() {
17320
- this.entries.clear();
17771
+ this.entries = /* @__PURE__ */ new Map();
17772
+ this.shared = false;
17773
+ this.integerKeys = null;
17321
17774
  }
17322
17775
  clone() {
17323
17776
  const copy2 = new _IndexStore(this.name, this.unique);
17324
- for (const [key, rowids] of this.entries) copy2.entries.set(key, [...rowids]);
17777
+ if (this.entries.size >= 4096 && !(this.entries instanceof ForkMap)) this.entries = new ForkMap(this.entries);
17778
+ copy2.entries = this.entries instanceof ForkMap ? this.entries.fork() : this.entries;
17779
+ copy2.integerKeys = this.integerKeys;
17780
+ copy2.shared = true;
17781
+ this.shared = true;
17782
+ this.ownedBuckets = /* @__PURE__ */ new WeakSet();
17325
17783
  return copy2;
17326
17784
  }
17327
17785
  };
17328
17786
 
17329
17787
  // src/indexes/maintain.ts
17788
+ function prepareDmlIndexStores(env, table) {
17789
+ if (table.indexStores || !table.frozen || table.triggers.length > 0) return;
17790
+ const specs = uniqueSpecsFor(env, table);
17791
+ if (specs.some((spec) => spec.where || spec.keys.some((key) => key.expr))) return;
17792
+ ensureIndexStores(env, table);
17793
+ }
17330
17794
  function ensureIndexStores(env, table) {
17331
17795
  if (table.indexStores) return table.indexStores;
17332
17796
  const stores = /* @__PURE__ */ new Map();
@@ -17377,8 +17841,19 @@ function indexRemoveRow(env, table, rowIdx, row) {
17377
17841
  }
17378
17842
  }
17379
17843
  function indexUpdateRow(env, table, rowIdx, oldRow, newRow) {
17380
- indexRemoveRow(env, table, rowIdx, oldRow);
17381
- indexInsertRow(env, table, rowIdx, newRow);
17844
+ const specs = uniqueSpecsFor(env, table);
17845
+ if (specs.some((spec) => spec.where || spec.keys.some((key) => key.expr))) {
17846
+ indexRemoveRow(env, table, rowIdx, oldRow);
17847
+ indexInsertRow(env, table, rowIdx, newRow);
17848
+ return;
17849
+ }
17850
+ for (const spec of specs) {
17851
+ const before = uniqueKeyOf(env, table, spec, oldRow);
17852
+ const after = uniqueKeyOf(env, table, spec, newRow);
17853
+ if (before === after) continue;
17854
+ if (before !== null) table.indexStores?.get(spec.name)?.remove(before, rowIdx);
17855
+ if (after !== null) indexStoreFor(env, table, spec).insert(after, rowIdx);
17856
+ }
17382
17857
  }
17383
17858
  function rebuildTableIndexes(env, table) {
17384
17859
  table.indexStores = null;
@@ -17595,6 +18070,82 @@ function conjunctions(expr) {
17595
18070
  }
17596
18071
  return [expr];
17597
18072
  }
18073
+ function tryPointRowIndices(env, table, alias, where) {
18074
+ const eqs = [];
18075
+ for (const part of conjunctions(where)) {
18076
+ const eq = equalityAgainstConst(part);
18077
+ if (!eq || !["param", "number_lit", "string_lit", "bool_lit", "null_lit"].includes(eq.valueExpr.type)) return null;
18078
+ if (part.type !== "binop") return null;
18079
+ const column = part.left.type === "colref" ? part.left : part.right;
18080
+ if (column.type !== "colref") return null;
18081
+ if (column.parts.length > 1 && column.parts[column.parts.length - 2] !== alias) return null;
18082
+ eqs.push(eq);
18083
+ }
18084
+ for (const spec of uniqueSpecsFor(env, table)) {
18085
+ if (spec.where || spec.keys.length !== eqs.length || spec.keys.some((key2) => key2.expr) || !eqs.every((eq, index) => eq.column === spec.columnNames[index]))
18086
+ continue;
18087
+ const values = table.columns.map(() => null);
18088
+ let matches3 = true;
18089
+ for (let index = 0; index < spec.keys.length; index++) {
18090
+ const key2 = spec.keys[index];
18091
+ const column = table.columns[key2.colIdx];
18092
+ const eq = eqs[index];
18093
+ if (!column || !eq || column.domain) {
18094
+ matches3 = false;
18095
+ break;
18096
+ }
18097
+ const value = evalExpr(env.ctx, makeEvalScope(env, null), eq.valueExpr);
18098
+ if (value.t !== column.type.id && !(column.type.id === "text" && value.t === "unknown" && typeof value.v === "string")) {
18099
+ matches3 = false;
18100
+ break;
18101
+ }
18102
+ values[key2.colIdx] = value.v;
18103
+ }
18104
+ if (!matches3) continue;
18105
+ const key = uniqueKeyOf(env, table, spec, values);
18106
+ return { indices: key === null ? [] : indexStoreFor(env, table, spec).lookup(key), indexName: spec.name };
18107
+ }
18108
+ return null;
18109
+ }
18110
+ function recordIndexScan(env, table, name) {
18111
+ const schema = env.ctx.state.schemas.get(table.schema);
18112
+ const index = schema?.indexes.get(name);
18113
+ if (schema && index) {
18114
+ const lastScan = env.ctx.state.clock().getTime();
18115
+ schema.indexes.set(name, { ...index, scans: (index.scans ?? 0n) + 1n, lastScan });
18116
+ }
18117
+ }
18118
+ function tryRangeRowIndices(env, table, alias, where) {
18119
+ const parts = conjunctions(where);
18120
+ if (parts.length !== 2) return null;
18121
+ const bounds = [];
18122
+ for (const part of parts) {
18123
+ if (part.type !== "binop" || ![">", ">=", "<", "<="].includes(part.op)) return null;
18124
+ const column2 = part.left.type === "colref" ? part.left : part.right;
18125
+ const value = part.left.type === "colref" ? part.right : part.left;
18126
+ if (column2.type !== "colref" || column2.parts.length > 2 || column2.parts.length === 2 && column2.parts[0] !== alias || !["param", "number_lit"].includes(value.type))
18127
+ return null;
18128
+ const op = part.left === column2 ? part.op : { ">": "<", ">=": "<=", "<": ">", "<=": ">=" }[part.op];
18129
+ bounds.push({ column: column2.parts.at(-1), op, value });
18130
+ }
18131
+ const lower = bounds.find((bound) => bound.op === ">" || bound.op === ">=");
18132
+ const upper = bounds.find((bound) => bound.op === "<" || bound.op === "<=");
18133
+ if (!lower || !upper || lower.column !== upper.column) return null;
18134
+ const column = table.columns.find((column2) => column2.name === lower.column);
18135
+ if (!column || column.domain || !["int2", "int4"].includes(column.type.id)) return null;
18136
+ for (const spec of uniqueSpecsFor(env, table)) {
18137
+ if (spec.where || spec.keys.length !== 1 || spec.keys[0].expr || spec.columnNames[0] !== lower.column) continue;
18138
+ const low = evalExpr(env.ctx, makeEvalScope(env, null), lower.value);
18139
+ const high = evalExpr(env.ctx, makeEvalScope(env, null), upper.value);
18140
+ if (low.t !== column.type.id || high.t !== column.type.id || typeof low.v !== "number" || typeof high.v !== "number")
18141
+ return null;
18142
+ return {
18143
+ indices: indexStoreFor(env, table, spec).integerRange(low.v, high.v, lower.op === ">=", upper.op === "<="),
18144
+ indexName: spec.name
18145
+ };
18146
+ }
18147
+ return null;
18148
+ }
17598
18149
  function tryIndexedTableRows(env, table, alias, where) {
17599
18150
  const parts = conjunctions(where);
17600
18151
  const eqs = [];
@@ -17631,12 +18182,7 @@ function tryIndexedTableRows(env, table, alias, where) {
17631
18182
  const key = uniqueKeyOf(env, table, spec, probeRow);
17632
18183
  if (key === null) return [];
17633
18184
  const hits = indexStoreFor(env, table, spec).lookup(key);
17634
- const schema = env.ctx.state.schemas.get(table.schema);
17635
- const index = schema?.indexes.get(spec.name);
17636
- if (schema && index) {
17637
- const lastScan = env.ctx.state.clock().getTime();
17638
- schema.indexes.set(spec.name, { ...index, scans: (index.scans ?? 0n) + 1n, lastScan });
17639
- }
18185
+ recordIndexScan(env, table, spec.name);
17640
18186
  return hits.map((i) => table.rowAt(i));
17641
18187
  }
17642
18188
  function tryIndexedFromItem(env, item, where) {
@@ -23612,7 +24158,7 @@ function renameWithAliases(rel4, label, colAliases, what) {
23612
24158
  }));
23613
24159
  return { columns, rows: rel4.rows };
23614
24160
  }
23615
- function materializeItem(env, item, scope) {
24161
+ function materializeItem(env, item, scope, shapeOnly = false) {
23616
24162
  switch (item.type) {
23617
24163
  case "from_table": {
23618
24164
  const label = item.alias ?? item.name[item.name.length - 1];
@@ -23631,7 +24177,7 @@ function materializeItem(env, item, scope) {
23631
24177
  ...table.columns.map((c) => ({ name: c.name, type: c.type.id, mod: c.type.mod, table: label })),
23632
24178
  { name: "xmin", type: "xid", table: label, hidden: true, system: true }
23633
24179
  ],
23634
- rows: table.rows.map((row) => [...row, table.rowXmins.get(row) ?? 2])
24180
+ rows: shapeOnly ? [] : table.serializationRows().map((row) => [...row, table.rowXmins.get(row) ?? 2])
23635
24181
  };
23636
24182
  return { rel: renameWithAliases(rel4, label, item.colAliases, label), rangeVars: /* @__PURE__ */ new Set([label]) };
23637
24183
  }
@@ -24904,14 +25450,30 @@ function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
24904
25450
  function executeCore(env0, core, orderBy, opts) {
24905
25451
  const env = env0;
24906
25452
  const ctx = env.ctx;
24907
- const source = buildFrom(env, core.from, core.where);
25453
+ const item = core.from.length === 1 ? core.from[0] : null;
25454
+ const pointTable = item?.type === "from_table" && !item.colAliases?.length && !(item.name.length === 1 && env.ctes.has(item.name[0])) ? env.ctx.state.findTable(item.name) : null;
25455
+ const point2 = pointTable && core.where && item?.type === "from_table" ? tryPointRowIndices(env, pointTable, item.alias ?? item.name[item.name.length - 1], core.where) : null;
25456
+ const range = !point2 && pointTable && core.where && item?.type === "from_table" ? tryRangeRowIndices(env, pointTable, item.alias ?? item.name[item.name.length - 1], core.where) : null;
25457
+ const access = point2 ?? range;
25458
+ const source = access && item ? materializeItem(env, item, env.outer, true) : buildFrom(env, core.from, core.where);
24908
25459
  const srcCols = source.rel.columns;
24909
25460
  const rangeVars = source.rangeVars;
24910
25461
  const rowScope = (row) => new RowScope(srcCols, row, env.outer, rangeVars);
24911
25462
  let srcRows = source.rel.rows;
24912
25463
  if (core.where) {
24913
25464
  checkPredicateType(env, srcCols, rangeVars, core.where, "WHERE");
24914
- if (core.from.length === 1) {
25465
+ if (access && pointTable) {
25466
+ pointTable.materializeSlab();
25467
+ srcRows = access.indices.map((index) => {
25468
+ const row = pointTable.rowAt(index);
25469
+ return [...row, pointTable.rowXmins.get(row) ?? 2];
25470
+ });
25471
+ if (range) {
25472
+ const where = core.where;
25473
+ srcRows = srcRows.filter((row) => evalPredicate2(env, rowScope(row), where));
25474
+ }
25475
+ recordIndexScan(env, pointTable, access.indexName);
25476
+ } else if (core.from.length === 1) {
24915
25477
  const indexed = tryIndexedFromItem(env, core.from[0], core.where);
24916
25478
  if (indexed !== null) {
24917
25479
  srcRows = indexed;
@@ -25092,11 +25654,11 @@ function executeCore(env0, core, orderBy, opts) {
25092
25654
  }
25093
25655
  }
25094
25656
  }
25095
- const item = { name: `__ord${orderExprs.length}`, kind: "expr", colIdx: -1, expr: ob.expr, hidden: true };
25657
+ const item2 = { name: `__ord${orderExprs.length}`, kind: "expr", colIdx: -1, expr: ob.expr, hidden: true };
25096
25658
  const spec = { colIdx: projItems.length, dir, nullsFirst };
25097
- projItems.push(item);
25659
+ projItems.push(item2);
25098
25660
  sortSpecs.push(spec);
25099
- orderExprs.push({ item, spec });
25661
+ orderExprs.push({ item: item2, spec });
25100
25662
  }
25101
25663
  if (core.distinct && !core.distinct.on && sortSpecs.length > 0 && !opts?.hasLimit) {
25102
25664
  const used = new Set(sortSpecs.map((s) => s.colIdx));
@@ -25470,7 +26032,7 @@ var TransactionManager = class {
25470
26032
  return { state: this.state.cloneShallow(), prngState: this.state.prng.getState() };
25471
26033
  }
25472
26034
  restore(snap) {
25473
- this.state.restoreFrom(snap.state);
26035
+ this.state.restoreFrom(snap.state.cloneShallow());
25474
26036
  this.state.prng.setState(snap.prngState);
25475
26037
  }
25476
26038
  thawOnce() {
@@ -25570,7 +26132,10 @@ function withStatementRollback(env, fn) {
25570
26132
  function requireTargetTable(env, parts, verb) {
25571
26133
  const state = env.ctx.state;
25572
26134
  const table = state.findTable(parts);
25573
- if (table) return state.ensureWritableTable(table);
26135
+ if (table) {
26136
+ prepareDmlIndexStores(env, table);
26137
+ return state.ensureWritableTableForDml(table);
26138
+ }
25574
26139
  const view = state.findView(parts);
25575
26140
  if (view) {
25576
26141
  throw pgError("wrong_object_type", `cannot ${verb} view "${view.name}"`, "42809");
@@ -25738,7 +26303,7 @@ function resolveArbiters(env, table, clause) {
25738
26303
  return matches3.map((spec) => ({ spec }));
25739
26304
  }
25740
26305
  function applyOnConflictUpdate(env, table, label, existingIdx, newRow, sets, where) {
25741
- const rows = table.mutableRows();
26306
+ const rows = table.writeRows();
25742
26307
  const existing = rows[existingIdx];
25743
26308
  const cols = [
25744
26309
  ...table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label })),
@@ -25750,7 +26315,7 @@ function applyOnConflictUpdate(env, table, label, existingIdx, newRow, sets, whe
25750
26315
  applyUpdateSets(env, table, sets, scope, updated);
25751
26316
  computeGeneratedColumns(env, table, updated);
25752
26317
  table.rowXmins.set(updated, env.ctx.rowVersionXid());
25753
- rows[existingIdx] = updated;
26318
+ table.replaceRow(existingIdx, updated);
25754
26319
  indexUpdateRow(env, table, existingIdx, existing, updated);
25755
26320
  checkNotNull(env, table, updated);
25756
26321
  checkChecks(env, table, updated);
@@ -25762,7 +26327,7 @@ function applyOnConflictUpdate(env, table, label, existingIdx, newRow, sets, whe
25762
26327
  function executeInsert(env0, stmt) {
25763
26328
  const env = applyWith(env0, stmt.with);
25764
26329
  const table = requireTargetTable(env, stmt.table, "insert into");
25765
- const rows = table.mutableRows();
26330
+ const rows = table.writeRows();
25766
26331
  const label = stmt.alias ?? table.name;
25767
26332
  const colIdxs = insertTargetColumns(env, table, stmt);
25768
26333
  const sourceRows = insertSourceRows(env, stmt, colIdxs);
@@ -25846,6 +26411,7 @@ function executeInsert(env0, stmt) {
25846
26411
  checkForeignKeys(env, table, newRow);
25847
26412
  table.rowXmins.set(newRow, env.ctx.rowVersionXid());
25848
26413
  rows.push(newRow);
26414
+ table.recordDurableRow(newRow);
25849
26415
  indexInsertRow(env, table, rows.length - 1, newRow);
25850
26416
  insertedCount++;
25851
26417
  insertedRows.push(newRow);
@@ -25938,13 +26504,17 @@ function executeUpdate(env0, stmt) {
25938
26504
  const label = stmt.alias ?? table.name;
25939
26505
  fireStatementTriggers(env, table, "before", "update");
25940
26506
  const fromSource = stmt.from.length > 0 ? buildFrom(env, stmt.from) : null;
25941
- const rows = table.mutableRows();
25942
26507
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
25943
26508
  const updatedRows = [];
25944
26509
  const replacedRows = [];
25945
26510
  let updateCount = 0;
25946
- for (let ri = 0; ri < rows.length; ri++) {
25947
- const oldRow = rows[ri];
26511
+ const point2 = !fromSource && stmt.where && table.triggers.length === 0 && !table.columns.some((column) => column.generated || column.domain) && !table.constraints.some((constraint) => constraint.kind === "check") && stmt.sets.every(
26512
+ (set) => set.columns.every((column) => column.subscripts === null && column.fields.length === 0) && "type" in set.value && isPureUpdateValue(set.value)
26513
+ ) && referencingConstraints(env, table).length === 0 ? tryPointRowIndices(env, table, label, stmt.where) : null;
26514
+ const rows = point2 ? null : table.writeRows();
26515
+ for (let candidate = 0; candidate < (point2 ? point2.indices.length : rows.length); candidate++) {
26516
+ const ri = point2 ? point2.indices[candidate] : candidate;
26517
+ const oldRow = point2 ? table.rowAt(ri) : rows[ri];
25948
26518
  let matchScope = null;
25949
26519
  if (fromSource) {
25950
26520
  let found = false;
@@ -25970,14 +26540,14 @@ function executeUpdate(env0, stmt) {
25970
26540
  const finalRow = fired.row;
25971
26541
  computeGeneratedColumns(env, table, finalRow);
25972
26542
  table.rowXmins.set(finalRow, env.ctx.rowVersionXid());
25973
- rows[ri] = finalRow;
26543
+ table.replaceRow(ri, finalRow);
25974
26544
  try {
25975
26545
  checkNotNull(env, table, finalRow);
25976
26546
  checkChecks(env, table, finalRow);
25977
26547
  checkUnique(env, table, finalRow, ri);
25978
26548
  checkForeignKeys(env, table, finalRow);
25979
26549
  } catch (err) {
25980
- rows[ri] = oldRow;
26550
+ table.replaceRow(ri, oldRow);
25981
26551
  throw err;
25982
26552
  }
25983
26553
  indexUpdateRow(env, table, ri, oldRow, finalRow);
@@ -25995,12 +26565,29 @@ function executeUpdate(env0, stmt) {
25995
26565
  }
25996
26566
  return commandResult("UPDATE", updateCount);
25997
26567
  }
26568
+ function isPureUpdateValue(expr) {
26569
+ switch (expr.type) {
26570
+ case "param":
26571
+ case "number_lit":
26572
+ case "string_lit":
26573
+ case "bool_lit":
26574
+ case "null_lit":
26575
+ case "colref":
26576
+ return true;
26577
+ case "binop":
26578
+ return isPureUpdateValue(expr.left) && isPureUpdateValue(expr.right);
26579
+ case "unop":
26580
+ return isPureUpdateValue(expr.operand);
26581
+ default:
26582
+ return false;
26583
+ }
26584
+ }
25998
26585
  function executeDelete(env0, stmt) {
25999
26586
  const env = applyWith(env0, stmt.with);
26000
26587
  const table = requireTargetTable(env, stmt.table, "delete from");
26001
26588
  const label = stmt.alias ?? table.name;
26002
26589
  fireStatementTriggers(env, table, "before", "delete");
26003
- const rows = table.mutableRows();
26590
+ const rows = table.writeRows();
26004
26591
  const usingSource = stmt.using.length > 0 ? buildFrom(env, stmt.using) : null;
26005
26592
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
26006
26593
  const toDelete = [];
@@ -26030,6 +26617,7 @@ function executeDelete(env0, stmt) {
26030
26617
  if (skipped.has(ri)) continue;
26031
26618
  const row = rows[ri];
26032
26619
  handleReferencedDelete(env, table, row, 0);
26620
+ table.recordDurableDelete(row);
26033
26621
  rows.splice(ri, 1);
26034
26622
  deletedRows.unshift(row);
26035
26623
  }
@@ -26076,13 +26664,14 @@ function handleReferencedDelete(env, table, row, depth) {
26076
26664
  const idxs = referencingRowIdxs(ref, key);
26077
26665
  if (idxs.length === 0) continue;
26078
26666
  const action = con.onDelete ?? "no_action";
26079
- const child = env.ctx.state.ensureWritableTable(ref.table);
26080
- const childRows = child.mutableRows();
26667
+ const child = env.ctx.state.ensureWritableTableForDml(ref.table);
26668
+ const childRows = child.writeRows();
26081
26669
  switch (action) {
26082
26670
  case "cascade": {
26083
26671
  for (let k = idxs.length - 1; k >= 0; k--) {
26084
26672
  const childRow = childRows[idxs[k]];
26085
26673
  handleReferencedDelete(env, child, childRow, depth + 1);
26674
+ child.recordDurableDelete(childRow);
26086
26675
  childRows.splice(idxs[k], 1);
26087
26676
  }
26088
26677
  break;
@@ -26094,7 +26683,7 @@ function handleReferencedDelete(env, table, row, depth) {
26094
26683
  newRow[child.columnIndex(c)] = null;
26095
26684
  }
26096
26685
  child.rowXmins.set(newRow, env.ctx.rowVersionXid());
26097
- childRows[i] = newRow;
26686
+ child.replaceRow(i, newRow);
26098
26687
  checkNotNull(env, child, newRow);
26099
26688
  checkChecks(env, child, newRow);
26100
26689
  }
@@ -26108,7 +26697,7 @@ function handleReferencedDelete(env, table, row, depth) {
26108
26697
  newRow[ci] = columnDefault(env, child, child.columns[ci]);
26109
26698
  }
26110
26699
  child.rowXmins.set(newRow, env.ctx.rowVersionXid());
26111
- childRows[i] = newRow;
26700
+ child.replaceRow(i, newRow);
26112
26701
  checkNotNull(env, child, newRow);
26113
26702
  checkChecks(env, child, newRow);
26114
26703
  checkForeignKeys(env, child, newRow);
@@ -26134,8 +26723,8 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
26134
26723
  const idxs = referencingRowIdxs(ref, oldKey);
26135
26724
  if (idxs.length === 0) continue;
26136
26725
  const action = con.onUpdate ?? "no_action";
26137
- const child = env.ctx.state.ensureWritableTable(ref.table);
26138
- const childRows = child.mutableRows();
26726
+ const child = env.ctx.state.ensureWritableTableForDml(ref.table);
26727
+ const childRows = child.writeRows();
26139
26728
  switch (action) {
26140
26729
  case "cascade": {
26141
26730
  for (const i of idxs) {
@@ -26146,7 +26735,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
26146
26735
  newRow[localIdx] = newRows[r][refIdx] ?? null;
26147
26736
  }
26148
26737
  child.rowXmins.set(newRow, env.ctx.rowVersionXid());
26149
- childRows[i] = newRow;
26738
+ child.replaceRow(i, newRow);
26150
26739
  checkChecks(env, child, newRow);
26151
26740
  }
26152
26741
  break;
@@ -26156,7 +26745,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
26156
26745
  const newRow = childRows[i].slice();
26157
26746
  for (const c of con.columns) newRow[child.columnIndex(c)] = null;
26158
26747
  child.rowXmins.set(newRow, env.ctx.rowVersionXid());
26159
- childRows[i] = newRow;
26748
+ child.replaceRow(i, newRow);
26160
26749
  checkNotNull(env, child, newRow);
26161
26750
  }
26162
26751
  break;
@@ -26169,7 +26758,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
26169
26758
  newRow[ci] = columnDefault(env, child, child.columns[ci]);
26170
26759
  }
26171
26760
  child.rowXmins.set(newRow, env.ctx.rowVersionXid());
26172
- childRows[i] = newRow;
26761
+ child.replaceRow(i, newRow);
26173
26762
  checkForeignKeys(env, child, newRow);
26174
26763
  }
26175
26764
  break;
@@ -26692,6 +27281,8 @@ function openSnapshot(snapshot, options = {}) {
26692
27281
  now = () => new Date(ms);
26693
27282
  }
26694
27283
  const state = snapshot.state.cloneShallow();
27284
+ for (const schema of state.schemas.values())
27285
+ for (const [name, table] of schema.tables) if (table.durableChanges) schema.tables.set(name, table.untrackedFork());
26695
27286
  state.prng = prng;
26696
27287
  state.clock = now;
26697
27288
  return createAdoptedDatabase({
@@ -31374,6 +31965,24 @@ var Statement = class _Statement {
31374
31965
  this.database = database;
31375
31966
  this.sql = sql;
31376
31967
  this.statements = statements;
31968
+ const observer = database.durabilityObserver;
31969
+ if (observer) {
31970
+ const execute = this.execute;
31971
+ this.execute = (params) => {
31972
+ observer.assertWritable();
31973
+ if (params.some((value) => value instanceof Uint8Array)) observer.exposed();
31974
+ try {
31975
+ const result = execute.call(this, params);
31976
+ if (result.columns.some(
31977
+ (column, index) => column.type === "bytea" && result.rows.some((row) => row[index] !== null && row[index] !== void 0)
31978
+ ))
31979
+ observer.exposed();
31980
+ return result;
31981
+ } finally {
31982
+ observer.changed();
31983
+ }
31984
+ };
31985
+ }
31377
31986
  }
31378
31987
  database;
31379
31988
  statements;
@@ -31406,7 +32015,7 @@ var Statement = class _Statement {
31406
32015
  */
31407
32016
  textResult(...params) {
31408
32017
  const res = this.execute(params);
31409
- const ctx = new EngineCtx(this.database.state);
32018
+ const ctx = new EngineCtx(this.database.engineState);
31410
32019
  return {
31411
32020
  columns: res.columns.map((c) => c.name),
31412
32021
  columnTypes: res.columns.map((c) => c.type === UNKNOWN ? "text" : c.type),
@@ -31443,7 +32052,7 @@ var Statement = class _Statement {
31443
32052
  const refused = this.database.transactions.inTransaction ? refusedInTransactionBlock(stmt) : null;
31444
32053
  if (refused) throw pgError("misuse", `${refused} cannot run inside a transaction block`, "25001");
31445
32054
  const env = {
31446
- ctx: new EngineCtx(this.database.state),
32055
+ ctx: new EngineCtx(this.database.engineState),
31447
32056
  params: typed,
31448
32057
  ctes: /* @__PURE__ */ new Map(),
31449
32058
  outer: null
@@ -31453,12 +32062,13 @@ var Statement = class _Statement {
31453
32062
  return last;
31454
32063
  }
31455
32064
  toRows(res) {
31456
- const ctx = new EngineCtx(this.database.state);
32065
+ const ctx = new EngineCtx(this.database.engineState);
31457
32066
  return res.rows.map((row) => {
31458
32067
  const obj = {};
31459
32068
  for (let i = 0; i < res.columns.length; i++) {
31460
32069
  const c = res.columns[i];
31461
- obj[c.name] = datumToJs(c.type, row[i] ?? null, ctx, this.database.int8Mode);
32070
+ const value = datumToJs(c.type, row[i] ?? null, ctx, this.database.int8Mode);
32071
+ obj[c.name] = value;
31462
32072
  }
31463
32073
  return obj;
31464
32074
  });
@@ -31476,6 +32086,10 @@ function project(point2) {
31476
32086
  var Database = class {
31477
32087
  /** @internal Engine catalog, tables, session settings. */
31478
32088
  state;
32089
+ /** @internal Engine-only access; buffered mode observes public state exposure. */
32090
+ engineState;
32091
+ /** @internal Installed only by buffered durability. */
32092
+ durabilityObserver;
31479
32093
  /** Seed used to construct the PRNG. Ignored when {@link randomMode} is `"os"`. */
31480
32094
  seed;
31481
32095
  /** Entropy mode for `random()` / `gen_random_uuid()`. */
@@ -31505,10 +32119,10 @@ var Database = class {
31505
32119
  this.int8Mode = options.int8Mode;
31506
32120
  this.prng = options.prng;
31507
32121
  this.now = options.now;
31508
- this.state = options.state;
31509
- this.state.prng = this.prng;
31510
- this.state.clock = () => this.now();
31511
- this.transactions = txManagerFor(this.state);
32122
+ this.engineState = options.state;
32123
+ this.engineState.prng = this.prng;
32124
+ this.engineState.clock = () => this.now();
32125
+ this.transactions = txManagerFor(this.engineState);
31512
32126
  } else {
31513
32127
  this.seed = options.seed ?? DEFAULT_DATABASE_SEED;
31514
32128
  this.randomMode = options.random ?? "deterministic";
@@ -31516,9 +32130,10 @@ var Database = class {
31516
32130
  this.int8Mode = options.int8 ?? "bigint";
31517
32131
  this.prng = this.randomMode === "os" ? new OsEntropy() : new Prng(this.seed);
31518
32132
  this.now = resolveClock(options.now);
31519
- this.state = new DatabaseState(this.prng, () => this.now());
31520
- this.transactions = txManagerFor(this.state);
32133
+ this.engineState = new DatabaseState(this.prng, () => this.now());
32134
+ this.transactions = txManagerFor(this.engineState);
31521
32135
  }
32136
+ this.state = this.engineState;
31522
32137
  this.timeline = new Timeline({
31523
32138
  now: () => this.now().getTime(),
31524
32139
  ...options.maxCheckpoints !== void 0 ? { maxCheckpoints: options.maxCheckpoints } : {}
@@ -31545,14 +32160,15 @@ var Database = class {
31545
32160
  * a live in-memory snapshot.
31546
32161
  */
31547
32162
  registerFunction(spec) {
32163
+ this.durabilityObserver?.assertWritable();
31548
32164
  this.assertOpen();
31549
32165
  const parts = spec.name.split(".").map((p) => p.trim()).filter((p) => p.length > 0);
31550
32166
  if (parts.length === 0) throw pgError("syntax", "function name is required", "42601");
31551
- const schemaName = (parts.length >= 2 ? parts[0] : this.state.currentSchema()).toLowerCase();
32167
+ const schemaName = (parts.length >= 2 ? parts[0] : this.engineState.currentSchema()).toLowerCase();
31552
32168
  const name = parts[parts.length - 1].toLowerCase();
31553
- const functions = schemaName === "pg_catalog" ? this.state.systemFunctions : this.state.getSchema(schemaName).functions;
31554
- const argTypes = spec.args.map((a) => resolveTypeName(this.state, parseTypeName(a)).column.id);
31555
- const returns = resolveTypeName(this.state, parseTypeName(spec.returns)).column.id;
32169
+ const functions = schemaName === "pg_catalog" ? this.engineState.systemFunctions : this.engineState.getSchema(schemaName).functions;
32170
+ const argTypes = spec.args.map((a) => resolveTypeName(this.engineState, parseTypeName(a)).column.id);
32171
+ const returns = resolveTypeName(this.engineState, parseTypeName(spec.returns)).column.id;
31556
32172
  const fn = {
31557
32173
  name,
31558
32174
  schema: schemaName,
@@ -31566,7 +32182,7 @@ var Database = class {
31566
32182
  body: null,
31567
32183
  rawBody: null,
31568
32184
  strict: spec.strict ?? true,
31569
- oid: this.state.nextOid(),
32185
+ oid: this.engineState.nextOid(),
31570
32186
  jsImpl: spec.fn
31571
32187
  };
31572
32188
  const existing = functions.get(name) ?? [];
@@ -31576,6 +32192,7 @@ var Database = class {
31576
32192
  if (sameSig !== -1) existing[sameSig] = fn;
31577
32193
  else existing.push(fn);
31578
32194
  functions.set(name, existing);
32195
+ this.durabilityObserver?.exposed();
31579
32196
  }
31580
32197
  /** Execute a single-statement query and return all rows keyed by column name. */
31581
32198
  query(sql, params = [], options = {}) {
@@ -31602,6 +32219,7 @@ var Database = class {
31602
32219
  */
31603
32220
  transaction(fn) {
31604
32221
  this.assertOpen();
32222
+ this.durabilityObserver?.assertWritable();
31605
32223
  this.apiTransactionDepth++;
31606
32224
  try {
31607
32225
  if (!this.transactions.inTransaction) {
@@ -31628,6 +32246,7 @@ var Database = class {
31628
32246
  }
31629
32247
  } finally {
31630
32248
  this.apiTransactionDepth--;
32249
+ this.durabilityObserver?.changed();
31631
32250
  }
31632
32251
  }
31633
32252
  /**
@@ -31722,7 +32341,7 @@ var Database = class {
31722
32341
  */
31723
32342
  fault(faults) {
31724
32343
  this.assertOpen();
31725
- Object.assign(this.state.faults, faults);
32344
+ Object.assign(this.engineState.faults, faults);
31726
32345
  }
31727
32346
  /** Close the database. Further SQL throws. Idempotent. */
31728
32347
  close() {
@@ -31732,6 +32351,7 @@ var Database = class {
31732
32351
  }
31733
32352
  if (this.transactions.inTransaction) this.transactions.rollback();
31734
32353
  this.closed = true;
32354
+ this.durabilityObserver?.closed?.();
31735
32355
  }
31736
32356
  /**
31737
32357
  * Execute `COPY table [(cols)] FROM STDIN` with the given text/csv payload
@@ -31750,13 +32370,18 @@ var Database = class {
31750
32370
  if (statements.length !== 1 || !stmt || stmt.type !== "copy" || stmt.direction !== "from") {
31751
32371
  throw pgError("misuse", "copyFrom() requires a single COPY ... FROM STDIN statement", "XX000");
31752
32372
  }
31753
- const env = { ctx: new EngineCtx(this.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
31754
- return executeCopyFromData(env, stmt, data).rowCount;
32373
+ this.durabilityObserver?.assertWritable();
32374
+ try {
32375
+ const env = { ctx: new EngineCtx(this.engineState), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
32376
+ return executeCopyFromData(env, stmt, data).rowCount;
32377
+ } finally {
32378
+ this.durabilityObserver?.changed();
32379
+ }
31755
32380
  }
31756
32381
  /** Rows affected by the most recent INSERT / UPDATE / DELETE. */
31757
32382
  get changes() {
31758
32383
  this.assertOpen();
31759
- return this.state.changes;
32384
+ return this.engineState.changes;
31760
32385
  }
31761
32386
  /** @internal */
31762
32387
  assertOpen() {
@@ -31768,7 +32393,7 @@ var Database = class {
31768
32393
  }
31769
32394
  captureLive() {
31770
32395
  return captureSnapshot(
31771
- this.state,
32396
+ this.engineState,
31772
32397
  this.prng,
31773
32398
  this.now,
31774
32399
  this.seed,
@@ -31778,7 +32403,8 @@ var Database = class {
31778
32403
  );
31779
32404
  }
31780
32405
  install(snapshot) {
31781
- this.state.restoreFrom(snapshot.state.cloneShallow());
32406
+ this.durabilityObserver?.exposed();
32407
+ this.engineState.restoreFrom(snapshot.state.cloneShallow());
31782
32408
  this.prng.setState(snapshot.prngState);
31783
32409
  if (!this.systemClock) this.now = fixedClock(new Date(snapshot.nowMs));
31784
32410
  }