@crvouga/sqlite-mem 1.10.0 → 1.11.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/index.js CHANGED
@@ -2914,6 +2914,10 @@ var IndexStore = class _IndexStore {
2914
2914
  rememberKeyValues(key, values) {
2915
2915
  this.keyValues.set(key, [...values]);
2916
2916
  }
2917
+ /** Key component values for snapshot encode (binary keys). */
2918
+ keyValuesFor(key) {
2919
+ return [...this.keyValues.get(key) ?? []];
2920
+ }
2917
2921
  get size() {
2918
2922
  return this.entries.size;
2919
2923
  }
@@ -4645,6 +4649,8 @@ var Table = class _Table {
4645
4649
  strict;
4646
4650
  /** Clustered PK key string → row for WITHOUT ROWID tables. */
4647
4651
  clusteredRows;
4652
+ /** Frozen columnar storage after snapshot hydrate; `rows` stays empty until materialized. */
4653
+ slab = null;
4648
4654
  scanCache = null;
4649
4655
  /** Lazy covering hashes: column nameLower → serializeIndexKey → rowids. */
4650
4656
  equalityHashes = null;
@@ -4708,12 +4714,49 @@ var Table = class _Table {
4708
4714
  return this.integerPrimaryKeyAlias();
4709
4715
  }
4710
4716
  get(rowid) {
4717
+ if (this.slab) {
4718
+ const hit = this.slab.get(rowid);
4719
+ return hit ?? void 0;
4720
+ }
4711
4721
  try {
4712
4722
  return this.rows.get(canonicalRowid(rowid));
4713
4723
  } catch {
4714
4724
  return void 0;
4715
4725
  }
4716
4726
  }
4727
+ /** Row count (slab-backed or map-backed). */
4728
+ rowCount() {
4729
+ return this.slab ? this.slab.rowCount : this.rows.size;
4730
+ }
4731
+ /** Rows sorted by rowid for snapshot encode. */
4732
+ sortedRows() {
4733
+ if (this.slab) return [...this.slab.scan()].map((r) => ({ rowid: r.rowid, values: [...r.values] }));
4734
+ return [...this.rows.values()].sort((a, b) => compareRowids(a.rowid, b.rowid));
4735
+ }
4736
+ /** Attach decoded columnar slab (hydrate path). */
4737
+ attachSlab(slab) {
4738
+ this.slab = slab;
4739
+ this.invalidateScan();
4740
+ }
4741
+ /** Materialize slab into `rows` Map for mutation. */
4742
+ materializeSlab() {
4743
+ if (!this.slab) return;
4744
+ for (const row of this.slab.scan()) this.rows.set(row.rowid, { rowid: row.rowid, values: [...row.values] });
4745
+ this.slab = null;
4746
+ this.invalidateScan();
4747
+ }
4748
+ hasRow(rowid) {
4749
+ if (this.slab) return this.slab.get(rowid) !== void 0;
4750
+ try {
4751
+ return this.rows.has(canonicalRowid(rowid));
4752
+ } catch {
4753
+ return false;
4754
+ }
4755
+ }
4756
+ allRowids() {
4757
+ if (this.slab) return this.slab.rowids;
4758
+ return this.rows.keys();
4759
+ }
4717
4760
  getByKey(value) {
4718
4761
  if (value === null || this.withoutRowid) return void 0;
4719
4762
  try {
@@ -4757,20 +4800,21 @@ var Table = class _Table {
4757
4800
  if (!rowids || rowids.length === 0) return [];
4758
4801
  const rows = [];
4759
4802
  for (const rowid of rowids) {
4760
- const row = this.rows.get(rowid);
4803
+ const row = this.get(rowid);
4761
4804
  if (row) rows.push(row);
4762
4805
  }
4763
4806
  return rows;
4764
4807
  }
4765
4808
  ensureEqualityHash(columnLower) {
4766
- if (this.frozen || this.rows.size < EQUALITY_HASH_MIN_ROWS) return false;
4809
+ if (this.frozen || this.rowCount() < EQUALITY_HASH_MIN_ROWS) return false;
4767
4810
  const column = this.columns.find((item) => item.nameLower === columnLower);
4768
4811
  if (!column) return false;
4769
4812
  this.equalityHashes ??= /* @__PURE__ */ new Map();
4770
4813
  if (this.equalityHashes.has(columnLower)) return true;
4771
4814
  const map = /* @__PURE__ */ new Map();
4772
4815
  const collate = column.collate ?? "BINARY";
4773
- for (const row of this.rows.values()) {
4816
+ const iter = this.slab ? this.slab.scan() : this.rows.values();
4817
+ for (const row of iter) {
4774
4818
  const key = serializeIndexKey([normalizeForCollation(this.cell(row, columnLower), collate)]);
4775
4819
  if (key === null) continue;
4776
4820
  const bucket = map.get(key);
@@ -4836,7 +4880,7 @@ var Table = class _Table {
4836
4880
  if (rowid === void 0 && value !== null) rowid = asRowid(value, alias.name);
4837
4881
  }
4838
4882
  rowid = canonicalRowid(rowid ?? this.allocateRowid());
4839
- if (this.rows.has(rowid)) {
4883
+ if (this.hasRow(rowid)) {
4840
4884
  throw new SqliteError(
4841
4885
  `UNIQUE constraint failed: ${this.name}.rowid`,
4842
4886
  "constraint_primary",
@@ -4888,7 +4932,7 @@ var Table = class _Table {
4888
4932
  const alias = this.integerPrimaryKeyAlias();
4889
4933
  const aliasRow = { rowid: key, values };
4890
4934
  const targetKey = alias ? asRowid(this.cell(aliasRow, normalizeColumnName(alias.name)), alias.name) : key;
4891
- if (targetKey !== key && this.rows.has(targetKey)) {
4935
+ if (targetKey !== key && this.hasRow(targetKey)) {
4892
4936
  throw new SqliteError(
4893
4937
  `UNIQUE constraint failed: ${this.name}.${alias?.name ?? "rowid"}`,
4894
4938
  "constraint_unique",
@@ -4923,6 +4967,10 @@ var Table = class _Table {
4923
4967
  return this.rows.delete(key);
4924
4968
  }
4925
4969
  *scan() {
4970
+ if (this.slab) {
4971
+ yield* this.slab.scan();
4972
+ return;
4973
+ }
4926
4974
  if (!this.scanCache) {
4927
4975
  if (this.withoutRowid) {
4928
4976
  this.scanCache = [...this.clusteredRows.values()].sort((a, b) => this.comparePrimaryKeys(a, b));
@@ -4942,7 +4990,16 @@ var Table = class _Table {
4942
4990
  });
4943
4991
  copy.nextRowid = this.nextRowid;
4944
4992
  copy.maximumRowid = this.maximumRowid;
4945
- for (const [rowid, row] of this.rows) copy.rows.set(rowid, cloneRow(row));
4993
+ if (this.slab) {
4994
+ let max = null;
4995
+ for (const row of this.slab.scan()) {
4996
+ copy.rows.set(row.rowid, cloneRow(row));
4997
+ if (max === null || compareRowids(row.rowid, max) > 0) max = row.rowid;
4998
+ }
4999
+ copy.maximumRowid = max ?? void 0;
5000
+ } else {
5001
+ for (const [rowid, row] of this.rows) copy.rows.set(rowid, cloneRow(row));
5002
+ }
4946
5003
  for (const [clusterKey, row] of this.clusteredRows) copy.clusteredRows.set(clusterKey, cloneRow(row));
4947
5004
  return copy;
4948
5005
  }
@@ -4951,13 +5008,15 @@ var Table = class _Table {
4951
5008
  }
4952
5009
  assertMutable() {
4953
5010
  if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen table", "other");
5011
+ if (this.slab) this.materializeSlab();
4954
5012
  }
4955
5013
  /** Rebuild clustered storage after snapshot decode or bulk load. */
4956
5014
  rebuildClusteredRows() {
4957
5015
  this.maximumRowid = void 0;
4958
5016
  if (!this.withoutRowid) return;
4959
5017
  this.clusteredRows.clear();
4960
- for (const row of this.rows.values()) {
5018
+ const iter = this.slab ? this.slab.scan() : this.rows.values();
5019
+ for (const row of iter) {
4961
5020
  this.clusteredRows.set(this.makeClusterKey(row.values), row);
4962
5021
  }
4963
5022
  this.invalidateScan();
@@ -5081,7 +5140,11 @@ var Table = class _Table {
5081
5140
  if (!this.columns.some((column) => column.autoincrement)) {
5082
5141
  if (this.maximumRowid === void 0) {
5083
5142
  this.maximumRowid = null;
5084
- for (const rowid of this.rows.keys()) {
5143
+ for (const rowid of this.allRowids()) {
5144
+ if (this.maximumRowid === null || compareRowids(rowid, this.maximumRowid) > 0) this.maximumRowid = rowid;
5145
+ }
5146
+ } else if (this.maximumRowid === null && this.rowCount() > 0) {
5147
+ for (const rowid of this.allRowids()) {
5085
5148
  if (this.maximumRowid === null || compareRowids(rowid, this.maximumRowid) > 0) this.maximumRowid = rowid;
5086
5149
  }
5087
5150
  }
@@ -5089,7 +5152,7 @@ var Table = class _Table {
5089
5152
  }
5090
5153
  const maxSigned = 9223372036854775807n;
5091
5154
  let candidate = canonicalRowid(this.nextRowid);
5092
- while (this.rows.has(candidate)) {
5155
+ while (this.hasRow(candidate)) {
5093
5156
  if (BigInt(candidate) >= maxSigned) {
5094
5157
  throw new SqliteError("database or disk is full", "other", "SQLITE_FULL");
5095
5158
  }
@@ -9051,24 +9114,173 @@ function tableRowEvalContext(table, row) {
9051
9114
  };
9052
9115
  }
9053
9116
 
9054
- // src/serialization/codec.ts
9055
- var MAGIC = utf8Encode("SQLM");
9056
- var VERSION = 4;
9057
- var VERSION_V1 = 1;
9058
- var VERSION_V2 = 2;
9059
- var VERSION_V3 = 3;
9117
+ // src/storage/columnar-slab.ts
9118
+ var PACK_NULL = 0;
9119
+ var PACK_I32 = 1;
9120
+ var PACK_I64 = 2;
9121
+ var PACK_F64 = 3;
9122
+ var PACK_TEXT_INTERN = 4;
9123
+ var PACK_TEXT_INLINE = 5;
9124
+ var PACK_BLOB = 6;
9125
+ var PACK_TAGGED = 7;
9126
+ var ColumnarSlab = class {
9127
+ buffer;
9128
+ rowCount;
9129
+ rowids;
9130
+ columns;
9131
+ intern;
9132
+ rowidIndex = null;
9133
+ constructor(buffer, rowCount, rowids, columns, intern) {
9134
+ this.buffer = buffer;
9135
+ this.rowCount = rowCount;
9136
+ this.rowids = rowids;
9137
+ this.columns = columns;
9138
+ this.intern = intern;
9139
+ }
9140
+ rowIndex(rowid) {
9141
+ this.rowidIndex ??= new Map(this.rowids.map((id, i) => [canonicalRowid2(id), i]));
9142
+ const hit = this.rowidIndex.get(canonicalRowid2(rowid));
9143
+ if (hit === void 0) return -1;
9144
+ return hit;
9145
+ }
9146
+ cell(rowIndex, col) {
9147
+ if (rowIndex < 0 || rowIndex >= this.rowCount) return null;
9148
+ const column = this.columns[col];
9149
+ if (!column) return null;
9150
+ if (column.nullBitmap && isNull(column.nullBitmap, rowIndex)) return null;
9151
+ return readSlabCell(column, rowIndex, this.intern);
9152
+ }
9153
+ rowAt(rowIndex) {
9154
+ const values = new Array(this.columns.length);
9155
+ for (let c = 0; c < this.columns.length; c++) values[c] = this.cell(rowIndex, c);
9156
+ return { rowid: this.rowids[rowIndex], values };
9157
+ }
9158
+ get(rowid) {
9159
+ const i = this.rowIndex(rowid);
9160
+ if (i < 0) return void 0;
9161
+ return this.rowAt(i);
9162
+ }
9163
+ *scan() {
9164
+ for (let i = 0; i < this.rowCount; i++) yield this.rowAt(i);
9165
+ }
9166
+ materialize() {
9167
+ const map = /* @__PURE__ */ new Map();
9168
+ for (let i = 0; i < this.rowCount; i++) {
9169
+ const row = this.rowAt(i);
9170
+ map.set(row.rowid, row);
9171
+ }
9172
+ return map;
9173
+ }
9174
+ };
9175
+ function canonicalRowid2(rowid) {
9176
+ return typeof rowid === "bigint" ? rowid : rowid;
9177
+ }
9178
+ function isNull(bits, i) {
9179
+ return (bits[i >> 3] & 1 << (i & 7)) !== 0;
9180
+ }
9181
+ function readSlabCell(column, rowIndex, intern) {
9182
+ const pack2 = column.pack;
9183
+ if (pack2 === PACK_NULL) return null;
9184
+ const nonNullIndex = nonNullRowIndex(column, rowIndex);
9185
+ const view = new DataView(column.payload.buffer, column.payload.byteOffset, column.payload.byteLength);
9186
+ if (pack2 === PACK_I32) {
9187
+ return view.getInt32(nonNullIndex * 4, true);
9188
+ }
9189
+ if (pack2 === PACK_I64) {
9190
+ const integer = view.getBigInt64(nonNullIndex * 8, true);
9191
+ return integer <= BigInt(Number.MAX_SAFE_INTEGER) && integer >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(integer) : integer;
9192
+ }
9193
+ if (pack2 === PACK_F64) {
9194
+ const value = view.getFloat64(nonNullIndex * 8, true);
9195
+ return Number.isInteger(value) && Number.isFinite(value) ? asSqlReal(value) : value;
9196
+ }
9197
+ if (pack2 === PACK_TEXT_INTERN) {
9198
+ const id = view.getUint32(nonNullIndex * 4, true);
9199
+ return intern[id] ?? "";
9200
+ }
9201
+ if (pack2 === PACK_TEXT_INLINE) {
9202
+ const offsets = column.inlineOffsets;
9203
+ const start = offsets[nonNullIndex];
9204
+ const end = nonNullIndex + 1 < offsets.length ? offsets[nonNullIndex + 1] : column.payload.length;
9205
+ return new TextDecoder().decode(column.payload.subarray(start, end));
9206
+ }
9207
+ if (pack2 === PACK_BLOB) {
9208
+ const blob = column.blobChunks[nonNullIndex];
9209
+ return blob;
9210
+ }
9211
+ if (pack2 === PACK_TAGGED) {
9212
+ return column.tagged[nonNullIndex] ?? null;
9213
+ }
9214
+ return null;
9215
+ }
9216
+ function nonNullRowIndex(column, rowIndex) {
9217
+ if (!column.nullBitmap) return rowIndex;
9218
+ let idx = 0;
9219
+ for (let i = 0; i < rowIndex; i++) {
9220
+ if (!isNull(column.nullBitmap, i)) idx++;
9221
+ }
9222
+ return idx;
9223
+ }
9224
+ function packKindOf(value) {
9225
+ if (value instanceof SqlReal) return PACK_F64;
9226
+ if (typeof value === "bigint") return PACK_I64;
9227
+ if (typeof value === "number" && Number.isInteger(value)) {
9228
+ return value >= -2147483648 && value <= 2147483647 ? PACK_I32 : PACK_I64;
9229
+ }
9230
+ if (typeof value === "number") return PACK_F64;
9231
+ if (value instanceof SqlJsonText) return PACK_TAGGED;
9232
+ if (typeof value === "string") return PACK_TEXT_INTERN;
9233
+ if (value instanceof Uint8Array) return PACK_BLOB;
9234
+ return PACK_TAGGED;
9235
+ }
9236
+
9237
+ // src/serialization/wire.ts
9238
+ function writeVarintU32(w, value) {
9239
+ let v = value >>> 0;
9240
+ while (v >= 128) {
9241
+ w.u8(v & 127 | 128);
9242
+ v >>>= 7;
9243
+ }
9244
+ w.u8(v);
9245
+ }
9246
+ function readVarintU32(r) {
9247
+ let result = 0;
9248
+ let shift = 0;
9249
+ for (; ; ) {
9250
+ const byte = r.u8();
9251
+ result |= (byte & 127) << shift;
9252
+ if ((byte & 128) === 0) return result >>> 0;
9253
+ shift += 7;
9254
+ if (shift > 28) r.fail();
9255
+ }
9256
+ }
9060
9257
  var Writer = class {
9061
- buf = new Uint8Array(4096);
9062
- view = new DataView(this.buf.buffer);
9258
+ buf;
9259
+ view;
9063
9260
  len = 0;
9261
+ constructor(capacity = 4096) {
9262
+ this.buf = new Uint8Array(capacity);
9263
+ this.view = new DataView(this.buf.buffer);
9264
+ }
9265
+ get position() {
9266
+ return this.len;
9267
+ }
9268
+ reserve(capacity) {
9269
+ if (capacity <= this.buf.length) return;
9270
+ const next = new Uint8Array(capacity);
9271
+ next.set(this.buf.subarray(0, this.len));
9272
+ this.buf = next;
9273
+ this.view = new DataView(next.buffer);
9274
+ }
9064
9275
  ensure(needed) {
9065
9276
  if (this.len + needed <= this.buf.length) return;
9066
9277
  let cap = this.buf.length;
9067
9278
  while (cap < this.len + needed) cap *= 2;
9068
- const next = new Uint8Array(cap);
9069
- next.set(this.buf.subarray(0, this.len));
9070
- this.buf = next;
9071
- this.view = new DataView(next.buffer);
9279
+ this.reserve(cap);
9280
+ }
9281
+ align4() {
9282
+ const pad2 = 4 - (this.len & 3) & 3;
9283
+ for (let i = 0; i < pad2; i++) this.u8(0);
9072
9284
  }
9073
9285
  u8(value) {
9074
9286
  this.ensure(1);
@@ -9099,52 +9311,28 @@ var Writer = class {
9099
9311
  this.buf.set(value, this.len);
9100
9312
  this.len += value.length;
9101
9313
  }
9102
- text(value) {
9103
- const bytes = utf8Encode(value);
9104
- this.u32(bytes.length);
9105
- this.raw(bytes);
9314
+ rawAt(offset, value) {
9315
+ if (offset + value.length > this.len) this.fail();
9316
+ this.buf.set(value, offset);
9106
9317
  }
9107
- json(value) {
9108
- this.text(JSON.stringify(value, jsonReplacer));
9318
+ /** Length-prefixed UTF-8 via encodeInto when possible. */
9319
+ text(value) {
9320
+ const encoded = utf8Encode(value);
9321
+ writeVarintU32(this, encoded.length);
9322
+ this.raw(encoded);
9109
9323
  }
9110
- value(value) {
9111
- if (value === null) {
9112
- this.u8(0);
9113
- return;
9114
- }
9115
- if (value instanceof SqlReal) {
9116
- this.u8(2);
9117
- this.f64(value.value);
9118
- return;
9119
- }
9120
- if (typeof value === "bigint" || typeof value === "number" && Number.isInteger(value)) {
9121
- this.u8(1);
9122
- this.i64(BigInt(value));
9123
- return;
9124
- }
9125
- if (typeof value === "number") {
9126
- this.u8(2);
9127
- this.f64(value);
9128
- return;
9129
- }
9130
- if (typeof value === "string") {
9131
- this.u8(3);
9132
- this.text(value);
9133
- return;
9134
- }
9135
- if (value instanceof SqlJsonText) {
9136
- this.u8(3);
9137
- this.text(value.value);
9138
- return;
9139
- }
9140
- this.u8(4);
9141
- this.u32(value.length);
9142
- this.raw(value);
9324
+ /** Write UTF-8 at current position without length prefix (intern blob region). */
9325
+ textBytes(value) {
9326
+ const encoded = utf8Encode(value);
9327
+ this.raw(encoded);
9143
9328
  }
9144
9329
  finish() {
9145
9330
  if (this.len === this.buf.length) return this.buf;
9146
9331
  return this.buf.subarray(0, this.len).slice();
9147
9332
  }
9333
+ fail() {
9334
+ throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9335
+ }
9148
9336
  };
9149
9337
  var Reader = class {
9150
9338
  constructor(bytes) {
@@ -9154,6 +9342,13 @@ var Reader = class {
9154
9342
  bytes;
9155
9343
  offset = 0;
9156
9344
  view;
9345
+ get position() {
9346
+ return this.offset;
9347
+ }
9348
+ skipAlign4() {
9349
+ const pad2 = 4 - (this.offset & 3) & 3;
9350
+ this.offset += pad2;
9351
+ }
9157
9352
  u8() {
9158
9353
  if (this.offset >= this.bytes.length) this.fail();
9159
9354
  return this.bytes[this.offset++];
@@ -9188,33 +9383,9 @@ var Reader = class {
9188
9383
  this.offset += length;
9189
9384
  return value;
9190
9385
  }
9191
- owned(length) {
9192
- return this.raw(length).slice();
9193
- }
9194
9386
  text() {
9195
- return utf8Decode(this.raw(this.u32()));
9196
- }
9197
- json() {
9198
- try {
9199
- return JSON.parse(this.text(), jsonReviver);
9200
- } catch {
9201
- throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9202
- }
9203
- }
9204
- value() {
9205
- const tag = this.u8();
9206
- if (tag === 0) return null;
9207
- if (tag === 1) {
9208
- const integer = this.i64();
9209
- return integer <= BigInt(Number.MAX_SAFE_INTEGER) && integer >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(integer) : integer;
9210
- }
9211
- if (tag === 2) {
9212
- const value = this.f64();
9213
- return Number.isInteger(value) && Number.isFinite(value) ? asSqlReal(value) : value;
9214
- }
9215
- if (tag === 3) return this.text();
9216
- if (tag === 4) return this.owned(this.u32());
9217
- this.fail();
9387
+ const length = readVarintU32(this);
9388
+ return utf8Decode(this.raw(length));
9218
9389
  }
9219
9390
  remaining() {
9220
9391
  return this.bytes.length - this.offset;
@@ -9226,409 +9397,317 @@ var Reader = class {
9226
9397
  throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9227
9398
  }
9228
9399
  };
9229
- function encodeDatabaseState(state, runtime, formatVersion = VERSION) {
9230
- if (formatVersion >= 4) return encodeV4(state, runtime);
9231
- return encodeLegacy(state, runtime, formatVersion);
9232
- }
9233
- function encodeLegacy(state, runtime, formatVersion) {
9234
- const writer = new Writer();
9235
- writer.raw(MAGIC);
9236
- writer.u32(formatVersion);
9237
- writer.u8(state.foreignKeysEnabled ? 1 : 0);
9238
- writer.u32(state.schemaVersion);
9239
- writer.u32(state.changes);
9240
- writer.u32(state.totalChanges);
9241
- writer.value(state.lastInsertRowid);
9242
- writer.u32(state.tables.size);
9243
- for (const table of sortedValues(state.tables)) {
9244
- writer.json({
9245
- name: table.name,
9246
- columns: table.columns,
9247
- constraints: table.constraints,
9248
- indexes: [...table.indexes].sort(compareNames),
9249
- originalSql: table.originalSql,
9250
- withoutRowid: table.withoutRowid || void 0,
9251
- strict: table.strict || void 0
9252
- });
9253
- writer.value(table.nextRowid);
9254
- const rows = [...table.rows.values()].sort((a, b) => compareRowids2(a.rowid, b.rowid));
9255
- writer.u32(rows.length);
9256
- for (const row of rows) {
9257
- writer.value(row.rowid);
9258
- writer.u32(table.columns.length);
9259
- for (const column of table.columns) writer.value(table.cell(row, column.nameLower ?? column.name.toLowerCase()));
9400
+ var BJV_NULL = 0;
9401
+ var BJV_FALSE = 1;
9402
+ var BJV_TRUE = 2;
9403
+ var BJV_I32 = 3;
9404
+ var BJV_I64 = 4;
9405
+ var BJV_F64 = 5;
9406
+ var BJV_INTERN = 6;
9407
+ var BJV_STRING = 7;
9408
+ var BJV_BYTES = 8;
9409
+ var BJV_ARRAY = 9;
9410
+ var BJV_OBJECT = 10;
9411
+ function writeBjv(w, value, forceIntern) {
9412
+ if (value === null || value === void 0) {
9413
+ w.u8(BJV_NULL);
9414
+ return;
9415
+ }
9416
+ if (typeof value === "boolean") {
9417
+ w.u8(value ? BJV_TRUE : BJV_FALSE);
9418
+ return;
9419
+ }
9420
+ if (typeof value === "number") {
9421
+ if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
9422
+ w.u8(BJV_I32);
9423
+ w.u32(value | 0);
9424
+ return;
9260
9425
  }
9426
+ w.u8(BJV_F64);
9427
+ w.f64(value);
9428
+ return;
9261
9429
  }
9262
- const views = sortedValues(state.views);
9263
- writer.u32(views.length);
9264
- for (const view of views) writer.json(view);
9265
- const indexes = sortedValues(state.indexes);
9266
- writer.u32(indexes.length);
9267
- for (const index of indexes) {
9268
- writer.json({
9269
- name: index.name,
9270
- tableName: index.tableName,
9271
- unique: index.unique,
9272
- columns: index.columns,
9273
- where: index.where,
9274
- originalSql: index.originalSql
9275
- });
9430
+ if (typeof value === "bigint") {
9431
+ w.u8(BJV_I64);
9432
+ w.i64(value);
9433
+ return;
9276
9434
  }
9277
- if (formatVersion >= 3) {
9278
- for (const index of indexes) writeIndexStore(writer, index.store);
9435
+ if (typeof value === "string") {
9436
+ w.u8(BJV_INTERN);
9437
+ writeVarintU32(w, forceIntern(value));
9438
+ return;
9279
9439
  }
9280
- if (formatVersion >= VERSION_V2) {
9281
- writer.u64(runtime.prngState);
9282
- writer.i64(BigInt(Math.trunc(runtime.nowMs)));
9440
+ if (value instanceof Uint8Array) {
9441
+ w.u8(BJV_BYTES);
9442
+ writeVarintU32(w, value.length);
9443
+ w.raw(value);
9444
+ return;
9283
9445
  }
9284
- return writer.finish();
9285
- }
9286
- function decodeDatabaseState(snapshot) {
9287
- try {
9288
- return decodeDatabaseStateInner(snapshot);
9289
- } catch (error) {
9290
- if (error instanceof SqliteError) throw error;
9291
- throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9446
+ if (Array.isArray(value)) {
9447
+ w.u8(BJV_ARRAY);
9448
+ writeVarintU32(w, value.length);
9449
+ for (const item of value) writeBjv(w, item, forceIntern);
9450
+ return;
9292
9451
  }
9293
- }
9294
- function decodeDatabaseStateInner(snapshot) {
9295
- const reader = new Reader(snapshot);
9296
- const magic = reader.raw(4);
9297
- if (!magic.every((byte, index) => byte === MAGIC[index]))
9298
- throw new SqliteError("invalid sqlite-mem snapshot magic", "other");
9299
- const version = reader.u32();
9300
- if (version < VERSION_V1 || version > VERSION) {
9301
- throw new SqliteError(`unsupported sqlite-mem snapshot version: ${version}`, "snapshot_version", "SQLITE_FORMAT");
9452
+ if (typeof value === "object") {
9453
+ const entries = Object.entries(value);
9454
+ w.u8(BJV_OBJECT);
9455
+ writeVarintU32(w, entries.length);
9456
+ for (const [k, v] of entries) {
9457
+ w.u8(BJV_INTERN);
9458
+ writeVarintU32(w, forceIntern(k));
9459
+ writeBjv(w, v, forceIntern);
9460
+ }
9461
+ return;
9302
9462
  }
9303
- if (version > VERSION_V3) return decodeV4(reader);
9304
- const state = new DatabaseState();
9305
- state.foreignKeysEnabled = reader.u8() !== 0;
9306
- state.schemaVersion = reader.u32();
9307
- state.changes = reader.u32();
9308
- state.totalChanges = reader.u32();
9309
- state.lastInsertRowid = asRowid2(reader.value());
9310
- const tableCount = reader.u32();
9311
- for (let tableIndex = 0; tableIndex < tableCount; tableIndex++) {
9312
- const meta = reader.json();
9313
- const table = new Table(meta.name, meta.columns, {
9314
- constraints: meta.constraints,
9315
- indexes: meta.indexes,
9316
- originalSql: meta.originalSql,
9317
- withoutRowid: meta.withoutRowid ?? false,
9318
- strict: meta.strict ?? false
9319
- });
9320
- table.nextRowid = asRowid2(reader.value());
9321
- const rowCount = reader.u32();
9322
- for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
9323
- const rowid = asRowid2(reader.value());
9324
- const valueCount = reader.u32();
9325
- if (valueCount !== table.columns.length) throw new SqliteError("snapshot row column count mismatch", "other");
9326
- const values = [];
9327
- for (let i = 0; i < valueCount; i++) values.push(reader.value());
9328
- table.rows.set(rowid, { rowid, values });
9463
+ w.u8(BJV_NULL);
9464
+ }
9465
+ function readBjv(r, intern) {
9466
+ const tag = r.u8();
9467
+ switch (tag) {
9468
+ case BJV_NULL:
9469
+ return null;
9470
+ case BJV_FALSE:
9471
+ return false;
9472
+ case BJV_TRUE:
9473
+ return true;
9474
+ case BJV_I32:
9475
+ return r.u32() | 0;
9476
+ case BJV_I64:
9477
+ return r.i64();
9478
+ case BJV_F64:
9479
+ return r.f64();
9480
+ case BJV_INTERN:
9481
+ return intern[readVarintU32(r)] ?? "";
9482
+ case BJV_STRING:
9483
+ return r.text();
9484
+ case BJV_BYTES:
9485
+ return r.raw(readVarintU32(r)).slice();
9486
+ case BJV_ARRAY: {
9487
+ const count = readVarintU32(r);
9488
+ const out = new Array(count);
9489
+ for (let i = 0; i < count; i++) out[i] = readBjv(r, intern);
9490
+ return out;
9329
9491
  }
9330
- table.rebuildClusteredRows();
9331
- state.tables.set(table.name.toLowerCase(), table);
9492
+ case BJV_OBJECT: {
9493
+ const count = readVarintU32(r);
9494
+ const out = {};
9495
+ for (let i = 0; i < count; i++) {
9496
+ const key = readBjv(r, intern);
9497
+ out[key] = readBjv(r, intern);
9498
+ }
9499
+ return out;
9500
+ }
9501
+ default:
9502
+ return r.fail();
9332
9503
  }
9333
- const viewCount = reader.u32();
9334
- for (let index = 0; index < viewCount; index++) {
9335
- const view = reader.json();
9336
- state.views.set(view.name.toLowerCase(), view);
9504
+ }
9505
+ var InternPool = class {
9506
+ counts = /* @__PURE__ */ new Map();
9507
+ ids = /* @__PURE__ */ new Map();
9508
+ list = [];
9509
+ constructor() {
9510
+ this.list.push("");
9511
+ this.ids.set("", 0);
9337
9512
  }
9338
- const indexCount = reader.u32();
9339
- const loadedIndexes = [];
9340
- for (let indexPosition = 0; indexPosition < indexCount; indexPosition++) {
9341
- const meta = reader.json();
9342
- const info = { ...meta, store: new IndexStore(meta.name) };
9343
- state.indexes.set(info.name.toLowerCase(), info);
9344
- loadedIndexes.push(info);
9513
+ count(s) {
9514
+ this.counts.set(s, (this.counts.get(s) ?? 0) + 1);
9345
9515
  }
9346
- if (version >= 3) {
9347
- for (const info of loadedIndexes) {
9348
- info.store = readIndexStore(reader, info.name);
9516
+ /** Assign ids only to strings seen ≥2 times (plus empty string). */
9517
+ finalize() {
9518
+ for (const [s, c] of this.counts) {
9519
+ if (c >= 2 && !this.ids.has(s)) {
9520
+ const id = this.list.length;
9521
+ this.ids.set(s, id);
9522
+ this.list.push(s);
9523
+ }
9349
9524
  }
9350
- } else {
9351
- for (const info of loadedIndexes) rebuildIndexStore(state, info);
9352
- }
9353
- let runtime = null;
9354
- if (version >= VERSION_V2) {
9355
- if (reader.remaining() < 16) throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9356
- runtime = {
9357
- prngState: reader.u64(),
9358
- nowMs: finiteNowMs(Number(reader.i64()))
9359
- };
9360
9525
  }
9361
- if (!reader.done()) throw new SqliteError("snapshot has trailing data", "other");
9362
- return { state, runtime };
9363
- }
9364
- function writeIndexStore(writer, store) {
9365
- const entries = store.snapshotEntries();
9366
- writer.u32(entries.length);
9367
- for (const entry of entries) {
9368
- writer.text(entry.key);
9369
- writer.u32(entry.rowids.length);
9370
- for (const id of entry.rowids) writer.value(id);
9371
- writer.u32(entry.values.length);
9372
- for (const value of entry.values) writer.value(value);
9526
+ /** Always intern (schema / catalog strings). */
9527
+ forceId(s) {
9528
+ const hit = this.ids.get(s);
9529
+ if (hit !== void 0) return hit;
9530
+ const id = this.list.length;
9531
+ this.ids.set(s, id);
9532
+ this.list.push(s);
9533
+ return id;
9373
9534
  }
9374
- }
9375
- function readIndexStore(reader, name) {
9376
- const count = reader.u32();
9377
- const entries = /* @__PURE__ */ new Map();
9378
- const keyValues = /* @__PURE__ */ new Map();
9535
+ id(s) {
9536
+ const hit = this.ids.get(s);
9537
+ if (hit !== void 0) return hit;
9538
+ return -1;
9539
+ }
9540
+ has(s) {
9541
+ return this.ids.has(s);
9542
+ }
9543
+ };
9544
+ function writeInternTable(w, strings) {
9545
+ writeVarintU32(w, strings.length);
9546
+ if (strings.length === 0) return;
9547
+ const offsets = [];
9548
+ let total = 0;
9549
+ for (const s of strings) {
9550
+ offsets.push(total);
9551
+ total += utf8Encode(s).length;
9552
+ }
9553
+ writeVarintU32(w, total);
9554
+ const base = w.position + offsets.length * 4;
9555
+ for (const off of offsets) w.u32(base + off);
9556
+ for (const s of strings) w.textBytes(s);
9557
+ }
9558
+ function readInternTable(r) {
9559
+ const count = readVarintU32(r);
9560
+ if (count === 0) return [];
9561
+ const total = readVarintU32(r);
9562
+ const offsets = [];
9563
+ for (let i = 0; i < count; i++) offsets.push(r.u32());
9564
+ const blobStart = r.position;
9565
+ const blob = r.raw(total);
9566
+ const intern = new Array(count);
9379
9567
  for (let i = 0; i < count; i++) {
9380
- const key = reader.text();
9381
- const rowidCount = reader.u32();
9382
- const rowids = [];
9383
- for (let r = 0; r < rowidCount; r++) rowids.push(asRowid2(reader.value()));
9384
- const valueCount = reader.u32();
9385
- const values = [];
9386
- for (let v = 0; v < valueCount; v++) values.push(reader.value());
9387
- entries.set(key, rowids);
9388
- keyValues.set(key, values);
9568
+ const start = offsets[i] - blobStart;
9569
+ const end = i + 1 < count ? offsets[i + 1] - blobStart : total;
9570
+ intern[i] = utf8Decode(blob.subarray(start, end));
9389
9571
  }
9390
- return new IndexStore(name, entries, keyValues);
9391
- }
9392
- function rebuildIndexStore(state, info) {
9393
- const table = state.getTable(info.tableName);
9394
- rebuildIndexFromTable(info, table, (row) => tableRowEvalContext(table, row));
9395
- }
9396
- function asRowid2(value) {
9397
- if (typeof value === "number" && Number.isSafeInteger(value) || typeof value === "bigint") return value;
9398
- throw new SqliteError("invalid rowid in snapshot", "other");
9399
- }
9400
- function compareNames(a, b) {
9401
- return a < b ? -1 : a > b ? 1 : 0;
9402
- }
9403
- function finiteNowMs(value) {
9404
- if (!Number.isFinite(value)) return 0;
9405
- const max = 864e13;
9406
- if (value > max) return max;
9407
- if (value < -max) return -max;
9408
- return value;
9409
- }
9410
- function compareRowids2(a, b) {
9411
- const left = typeof a === "bigint" ? a : BigInt(a);
9412
- const right = typeof b === "bigint" ? b : BigInt(b);
9413
- if (left < right) return -1;
9414
- if (left > right) return 1;
9415
- return 0;
9416
- }
9417
- function sortedValues(map) {
9418
- return [...map.values()].sort((a, b) => compareNames(a.name, b.name));
9419
- }
9420
- function jsonReplacer(_key, value) {
9421
- if (typeof value === "bigint") return { $sqlm: "bigint", value: value.toString() };
9422
- if (value instanceof Uint8Array) return { $sqlm: "blob", value: Array.from(value) };
9423
- return value;
9424
- }
9425
- function jsonReviver(_key, value) {
9426
- if (!value || typeof value !== "object" || !("$sqlm" in value)) return value;
9427
- const tagged = value;
9428
- if (tagged.$sqlm === "bigint") return BigInt(tagged.value);
9429
- if (tagged.$sqlm === "blob") return Uint8Array.from(tagged.value);
9430
- return value;
9572
+ return intern;
9431
9573
  }
9432
- var PACK_NULL = 0;
9433
- var PACK_INT = 1;
9434
- var PACK_FLOAT = 2;
9435
- var PACK_TEXT = 3;
9436
- var PACK_BLOB = 4;
9437
- var PACK_TAGGED = 5;
9574
+
9575
+ // src/serialization/codec.ts
9576
+ var MAGIC = utf8Encode("SQLM");
9577
+ var VERSION = 5;
9438
9578
  var AFFINITIES = ["TEXT", "NUMERIC", "INTEGER", "REAL", "BLOB"];
9439
- function encodeV4(state, runtime) {
9440
- const intern = /* @__PURE__ */ new Map();
9441
- const internList = [];
9442
- const internId = (s) => {
9443
- const hit = intern.get(s);
9444
- if (hit !== void 0) return hit;
9445
- const id = internList.length;
9446
- intern.set(s, id);
9447
- internList.push(s);
9448
- return id;
9449
- };
9450
- internId("");
9579
+ function encodeDatabaseState(state, runtime) {
9580
+ const pool = new InternPool();
9451
9581
  const tables = sortedValues(state.tables);
9452
9582
  const views = sortedValues(state.views);
9453
9583
  const indexes = sortedValues(state.indexes);
9454
9584
  for (const table of tables) {
9455
- internId(table.name);
9456
- internId(table.originalSql ?? "");
9585
+ pool.count(table.name);
9586
+ pool.count(table.originalSql ?? "");
9457
9587
  for (const column of table.columns) {
9458
- internId(column.name);
9459
- internId(column.typeName ?? "");
9460
- internId(column.collate ?? "");
9588
+ pool.count(column.name);
9589
+ pool.count(column.typeName ?? "");
9590
+ pool.count(column.collate ?? "");
9461
9591
  }
9462
- for (const name of table.indexes) internId(name);
9463
- for (const row of table.rows.values()) {
9592
+ for (const name of table.indexes) pool.count(name);
9593
+ for (const row of table.sortedRows()) {
9464
9594
  for (const value of row.values) {
9465
- if (typeof value === "string") internId(value);
9466
- else if (value instanceof SqlJsonText) internId(value.value);
9595
+ if (typeof value === "string") pool.count(value);
9596
+ else if (value instanceof SqlJsonText) pool.count(value.value);
9467
9597
  }
9468
9598
  }
9469
9599
  }
9470
- const w = new Writer();
9600
+ for (const view of views) pool.count(JSON.stringify(view));
9601
+ for (const index of indexes) pool.count(index.name);
9602
+ pool.finalize();
9603
+ forceSchemaIntern(pool, tables, views, indexes);
9604
+ const internId = (s) => pool.id(s);
9605
+ const forceId = (s) => pool.forceId(s);
9606
+ const w = new Writer(64 * 1024);
9471
9607
  w.raw(MAGIC);
9472
9608
  w.u32(VERSION);
9473
9609
  w.u8(state.foreignKeysEnabled ? 1 : 0);
9474
9610
  w.u32(state.schemaVersion);
9475
9611
  w.u32(state.changes);
9476
9612
  w.u32(state.totalChanges);
9477
- w.value(state.lastInsertRowid);
9478
- w.u32(internList.length);
9479
- for (const s of internList) w.text(s);
9480
- w.u32(tables.length);
9613
+ writeTaggedValue(w, state.lastInsertRowid, internId);
9614
+ writeInternTable(w, pool.list);
9615
+ writeVarintU32(w, tables.length);
9481
9616
  for (const table of tables) {
9482
- w.u32(internId(table.name));
9483
- w.u32(table.columns.length);
9617
+ writeVarintU32(w, forceId(table.name));
9618
+ writeVarintU32(w, table.columns.length);
9484
9619
  for (const column of table.columns) {
9485
- w.u32(internId(column.name));
9486
- w.u32(internId(column.typeName ?? ""));
9620
+ writeVarintU32(w, forceId(column.name));
9621
+ writeVarintU32(w, forceId(column.typeName ?? ""));
9487
9622
  const aff = AFFINITIES.indexOf(column.affinity);
9488
9623
  w.u8(aff < 0 ? 2 : aff);
9489
9624
  w.u8(
9490
9625
  (column.notNull ? 1 : 0) | (column.primaryKey ? 2 : 0) | (column.autoincrement ? 4 : 0) | (column.unique ? 8 : 0)
9491
9626
  );
9492
- w.u32(internId(column.collate ?? ""));
9627
+ writeVarintU32(w, forceId(column.collate ?? ""));
9493
9628
  if (column.defaultExpr) {
9494
9629
  w.u8(1);
9495
- w.json(column.defaultExpr);
9630
+ writeBjv(w, column.defaultExpr, forceId);
9496
9631
  } else w.u8(0);
9497
9632
  if (column.generated) {
9498
9633
  w.u8(1);
9499
- w.json(column.generated);
9634
+ writeBjv(w, column.generated, forceId);
9500
9635
  } else w.u8(0);
9501
9636
  }
9502
- w.json(table.constraints);
9503
- w.u32(internId(table.originalSql ?? ""));
9637
+ writeBjv(w, table.constraints, forceId);
9638
+ writeVarintU32(w, forceId(table.originalSql ?? ""));
9504
9639
  w.u8((table.withoutRowid ? 1 : 0) | (table.strict ? 2 : 0));
9505
9640
  const indexNames = [...table.indexes].sort(compareNames);
9506
- w.u32(indexNames.length);
9507
- for (const name of indexNames) w.u32(internId(name));
9508
- w.value(table.nextRowid);
9509
- const rows = [...table.rows.values()].sort((a, b) => compareRowids2(a.rowid, b.rowid));
9510
- w.u32(rows.length);
9511
- for (const row of rows) w.value(row.rowid);
9512
- for (let c = 0; c < table.columns.length; c++) {
9513
- writePackedColumn(w, rows, c, internId);
9514
- }
9515
- }
9516
- w.u32(views.length);
9517
- for (const view of views) w.json(view);
9518
- w.u32(indexes.length);
9641
+ writeVarintU32(w, indexNames.length);
9642
+ for (const name of indexNames) writeVarintU32(w, forceId(name));
9643
+ writeTaggedValue(w, table.nextRowid, internId);
9644
+ const rows = table.sortedRows();
9645
+ writeVarintU32(w, rows.length);
9646
+ for (const row of rows) writeTaggedValue(w, row.rowid, internId);
9647
+ for (let c = 0; c < table.columns.length; c++) writePackedColumn(w, rows, c, internId);
9648
+ }
9649
+ writeVarintU32(w, views.length);
9650
+ for (const view of views) writeBjv(w, view, forceId);
9651
+ writeVarintU32(w, indexes.length);
9519
9652
  for (const index of indexes) {
9520
- w.json({
9521
- name: index.name,
9522
- tableName: index.tableName,
9523
- unique: index.unique,
9524
- columns: index.columns,
9525
- where: index.where,
9526
- originalSql: index.originalSql
9527
- });
9528
- }
9529
- for (const index of indexes) {
9530
- const entries = index.store.snapshotKeys();
9531
- w.u32(entries.length);
9532
- for (const entry of entries) {
9533
- w.text(entry.key);
9534
- w.u32(entry.rowids.length);
9535
- for (const id of entry.rowids) w.value(id);
9536
- }
9653
+ writeBjv(
9654
+ w,
9655
+ {
9656
+ name: index.name,
9657
+ tableName: index.tableName,
9658
+ unique: index.unique,
9659
+ columns: index.columns,
9660
+ where: index.where,
9661
+ originalSql: index.originalSql
9662
+ },
9663
+ forceId
9664
+ );
9537
9665
  }
9666
+ for (const index of indexes) writeIndexStoreBinary(w, index.store, internId);
9538
9667
  w.u64(runtime.prngState);
9539
9668
  w.i64(BigInt(Math.trunc(runtime.nowMs)));
9540
9669
  return w.finish();
9541
9670
  }
9542
- function writePackedColumn(w, rows, col, internId) {
9543
- const n = rows.length;
9544
- if (n === 0) {
9545
- w.u8(PACK_NULL);
9546
- return;
9547
- }
9548
- const bits = new Uint8Array(n + 7 >> 3);
9549
- let nulls = 0;
9550
- let kind = null;
9551
- for (let i = 0; i < n; i++) {
9552
- const value = rows[i].values[col] ?? null;
9553
- if (value === null) {
9554
- bits[i >> 3] = bits[i >> 3] | 1 << (i & 7);
9555
- nulls++;
9556
- continue;
9557
- }
9558
- const cellKind = packKindOf(value);
9559
- if (kind === null) kind = cellKind;
9560
- else if (kind !== cellKind) kind = PACK_TAGGED;
9561
- }
9562
- if (nulls === n) {
9563
- w.u8(PACK_NULL);
9564
- return;
9565
- }
9566
- const pack2 = kind ?? PACK_TAGGED;
9567
- w.u8(pack2);
9568
- w.raw(bits);
9569
- for (let i = 0; i < n; i++) {
9570
- if (bits[i >> 3] & 1 << (i & 7)) continue;
9571
- const value = rows[i].values[col];
9572
- writePackedCell(w, pack2, value, internId);
9671
+ function decodeDatabaseState(snapshot) {
9672
+ try {
9673
+ return decodeInner(snapshot);
9674
+ } catch (error) {
9675
+ if (error instanceof SqliteError) throw error;
9676
+ throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9573
9677
  }
9574
9678
  }
9575
- function packKindOf(value) {
9576
- if (value instanceof SqlReal) return PACK_FLOAT;
9577
- if (typeof value === "bigint" || typeof value === "number" && Number.isInteger(value)) return PACK_INT;
9578
- if (typeof value === "number") return PACK_FLOAT;
9579
- if (value instanceof SqlJsonText) return PACK_TAGGED;
9580
- if (typeof value === "string") return PACK_TEXT;
9581
- if (value instanceof Uint8Array) return PACK_BLOB;
9582
- return PACK_TAGGED;
9583
- }
9584
- function writePackedCell(w, pack2, value, internId) {
9585
- if (pack2 === PACK_INT) {
9586
- w.i64(typeof value === "bigint" ? value : BigInt(value));
9587
- return;
9588
- }
9589
- if (pack2 === PACK_FLOAT) {
9590
- w.f64(value instanceof SqlReal ? value.value : value);
9591
- return;
9592
- }
9593
- if (pack2 === PACK_TEXT) {
9594
- const text2 = value instanceof SqlJsonText ? value.value : value;
9595
- w.u32(internId(text2));
9596
- return;
9597
- }
9598
- if (pack2 === PACK_BLOB) {
9599
- const blob = value;
9600
- w.u32(blob.length);
9601
- w.raw(blob);
9602
- return;
9679
+ function decodeInner(snapshot) {
9680
+ const r = new Reader(snapshot);
9681
+ const magic = r.raw(4);
9682
+ if (!magic.every((byte, index) => byte === MAGIC[index]))
9683
+ throw new SqliteError("invalid sqlite-mem snapshot magic", "other");
9684
+ const version = r.u32();
9685
+ if (version !== VERSION) {
9686
+ throw new SqliteError(`unsupported sqlite-mem snapshot version: ${version}`, "snapshot_version", "SQLITE_FORMAT");
9603
9687
  }
9604
- w.value(value);
9605
- }
9606
- function decodeV4(reader) {
9607
9688
  const state = new DatabaseState();
9608
- state.foreignKeysEnabled = reader.u8() !== 0;
9609
- state.schemaVersion = reader.u32();
9610
- state.changes = reader.u32();
9611
- state.totalChanges = reader.u32();
9612
- state.lastInsertRowid = asRowid2(reader.value());
9613
- const internCount = reader.u32();
9614
- const intern = [];
9615
- for (let i = 0; i < internCount; i++) intern.push(reader.text());
9689
+ state.foreignKeysEnabled = r.u8() !== 0;
9690
+ state.schemaVersion = r.u32();
9691
+ state.changes = r.u32();
9692
+ state.totalChanges = r.u32();
9693
+ state.lastInsertRowid = asRowid2(readTaggedValue(r, []));
9694
+ const intern = readInternTable(r);
9616
9695
  const str = (id) => intern[id] ?? "";
9617
- const tableCount = reader.u32();
9696
+ const tableCount = readVarintU32(r);
9618
9697
  for (let t = 0; t < tableCount; t++) {
9619
- const name = str(reader.u32());
9620
- const colCount = reader.u32();
9698
+ const name = str(readVarintU32(r));
9699
+ const colCount = readVarintU32(r);
9621
9700
  const columns = [];
9622
9701
  for (let c = 0; c < colCount; c++) {
9623
- const colName = str(reader.u32());
9624
- const typeName = str(reader.u32()) || null;
9625
- const affIndex = reader.u8();
9626
- const flags = reader.u8();
9627
- const collate = str(reader.u32()) || null;
9628
- const hasDefault = reader.u8() === 1;
9629
- const defaultExpr = hasDefault ? reader.json() : null;
9630
- const hasGenerated = reader.u8() === 1;
9631
- const generated = hasGenerated ? reader.json() : null;
9702
+ const colName = str(readVarintU32(r));
9703
+ const typeName = str(readVarintU32(r)) || null;
9704
+ const affIndex = r.u8();
9705
+ const flags = r.u8();
9706
+ const collate = str(readVarintU32(r)) || null;
9707
+ const hasDefault = r.u8() === 1;
9708
+ const defaultExpr = hasDefault ? readBjv(r, intern) : null;
9709
+ const hasGenerated = r.u8() === 1;
9710
+ const generated = hasGenerated ? readBjv(r, intern) : null;
9632
9711
  columns.push({
9633
9712
  name: colName,
9634
9713
  nameLower: colName.toLowerCase(),
@@ -9643,12 +9722,12 @@ function decodeV4(reader) {
9643
9722
  generated
9644
9723
  });
9645
9724
  }
9646
- const constraints = reader.json();
9647
- const originalSql = str(reader.u32()) || null;
9648
- const tableFlags = reader.u8();
9649
- const indexCount2 = reader.u32();
9725
+ const constraints = readBjv(r, intern);
9726
+ const originalSql = str(readVarintU32(r)) || null;
9727
+ const tableFlags = r.u8();
9728
+ const indexNameCount = readVarintU32(r);
9650
9729
  const indexNames = [];
9651
- for (let i = 0; i < indexCount2; i++) indexNames.push(str(reader.u32()));
9730
+ for (let i = 0; i < indexNameCount; i++) indexNames.push(str(readVarintU32(r)));
9652
9731
  const table = new Table(name, columns, {
9653
9732
  constraints,
9654
9733
  indexes: indexNames,
@@ -9656,42 +9735,37 @@ function decodeV4(reader) {
9656
9735
  withoutRowid: (tableFlags & 1) !== 0,
9657
9736
  strict: (tableFlags & 2) !== 0
9658
9737
  });
9659
- table.nextRowid = asRowid2(reader.value());
9660
- const rowCount = reader.u32();
9738
+ table.nextRowid = asRowid2(readTaggedValue(r, intern));
9739
+ const rowCount = readVarintU32(r);
9661
9740
  const rowids = [];
9662
- for (let r = 0; r < rowCount; r++) rowids.push(asRowid2(reader.value()));
9663
- const cols = [];
9664
- for (let c = 0; c < colCount; c++) cols.push(readPackedColumn(reader, rowCount, intern));
9665
- for (let r = 0; r < rowCount; r++) {
9666
- const values = [];
9667
- for (let c = 0; c < colCount; c++) values.push(cols[c][r] ?? null);
9668
- const rowid = rowids[r];
9669
- table.rows.set(rowid, { rowid, values });
9670
- }
9671
- table.rebuildClusteredRows();
9741
+ for (let ri = 0; ri < rowCount; ri++) rowids.push(asRowid2(readTaggedValue(r, intern)));
9742
+ const slabColumns = [];
9743
+ for (let c = 0; c < colCount; c++) slabColumns.push(readPackedColumn(r, rowCount, intern));
9744
+ table.attachSlab(new ColumnarSlab(snapshot, rowCount, rowids, slabColumns, intern));
9745
+ if (table.withoutRowid) table.rebuildClusteredRows();
9672
9746
  state.tables.set(table.name.toLowerCase(), table);
9673
9747
  }
9674
- const viewCount = reader.u32();
9748
+ const viewCount = readVarintU32(r);
9675
9749
  for (let i = 0; i < viewCount; i++) {
9676
- const view = reader.json();
9750
+ const view = readBjv(r, intern);
9677
9751
  state.views.set(view.name.toLowerCase(), view);
9678
9752
  }
9679
- const indexCount = reader.u32();
9753
+ const indexCount = readVarintU32(r);
9680
9754
  const loadedIndexes = [];
9681
9755
  for (let i = 0; i < indexCount; i++) {
9682
- const meta = reader.json();
9756
+ const meta = readBjv(r, intern);
9683
9757
  const info = { ...meta, store: new IndexStore(meta.name) };
9684
9758
  state.indexes.set(info.name.toLowerCase(), info);
9685
9759
  loadedIndexes.push(info);
9686
9760
  }
9687
9761
  for (const info of loadedIndexes) {
9688
- info.store = readIndexStoreKeys(reader, info.name);
9762
+ info.store = readIndexStoreBinary(r, info.name, intern);
9689
9763
  const table = state.tables.get(info.tableName.toLowerCase());
9690
9764
  if (!table) continue;
9691
9765
  for (const entry of info.store.snapshotKeys()) {
9692
9766
  const rowid = entry.rowids[0];
9693
9767
  if (rowid === void 0) continue;
9694
- const row = table.rows.get(rowid);
9768
+ const row = table.get(rowid);
9695
9769
  if (!row) continue;
9696
9770
  info.store.rememberKeyValues(
9697
9771
  entry.key,
@@ -9699,52 +9773,299 @@ function decodeV4(reader) {
9699
9773
  );
9700
9774
  }
9701
9775
  }
9702
- if (reader.remaining() < 16) throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9703
- const runtime = { prngState: reader.u64(), nowMs: finiteNowMs(Number(reader.i64())) };
9704
- if (!reader.done()) throw new SqliteError("snapshot has trailing data", "other");
9776
+ if (r.remaining() < 16) throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
9777
+ const runtime = { prngState: r.u64(), nowMs: finiteNowMs(Number(r.i64())) };
9778
+ if (!r.done()) throw new SqliteError("snapshot has trailing data", "other");
9705
9779
  return { state, runtime };
9706
9780
  }
9707
- function readPackedColumn(reader, n, intern) {
9708
- const pack2 = reader.u8();
9709
- const out = new Array(n);
9710
- if (pack2 === PACK_NULL) {
9711
- for (let i = 0; i < n; i++) out[i] = null;
9712
- return out;
9781
+ function writeTaggedValue(w, value, internId) {
9782
+ if (value === null) {
9783
+ w.u8(0);
9784
+ return;
9713
9785
  }
9714
- const bits = reader.raw(n + 7 >> 3);
9715
- for (let i = 0; i < n; i++) {
9716
- if (bits[i >> 3] & 1 << (i & 7)) {
9717
- out[i] = null;
9718
- continue;
9786
+ if (value instanceof SqlReal) {
9787
+ w.u8(2);
9788
+ w.f64(value.value);
9789
+ return;
9790
+ }
9791
+ if (typeof value === "bigint" || typeof value === "number" && Number.isInteger(value)) {
9792
+ w.u8(1);
9793
+ w.i64(BigInt(value));
9794
+ return;
9795
+ }
9796
+ if (typeof value === "number") {
9797
+ w.u8(2);
9798
+ w.f64(value);
9799
+ return;
9800
+ }
9801
+ if (typeof value === "string") {
9802
+ const id = internId(value);
9803
+ if (id >= 0) {
9804
+ w.u8(3);
9805
+ writeVarintU32(w, id);
9806
+ return;
9719
9807
  }
9720
- out[i] = readPackedCell(reader, pack2, intern);
9808
+ w.u8(4);
9809
+ w.text(value);
9810
+ return;
9721
9811
  }
9722
- return out;
9812
+ if (value instanceof SqlJsonText) {
9813
+ w.u8(4);
9814
+ w.text(value.value);
9815
+ return;
9816
+ }
9817
+ w.u8(5);
9818
+ writeVarintU32(w, value.length);
9819
+ w.raw(value);
9723
9820
  }
9724
- function readPackedCell(reader, pack2, intern) {
9725
- if (pack2 === PACK_INT) {
9726
- const integer = reader.i64();
9821
+ function readTaggedValue(r, intern) {
9822
+ const tag = r.u8();
9823
+ if (tag === 0) return null;
9824
+ if (tag === 1) {
9825
+ const integer = r.i64();
9727
9826
  return integer <= BigInt(Number.MAX_SAFE_INTEGER) && integer >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(integer) : integer;
9728
9827
  }
9729
- if (pack2 === PACK_FLOAT) {
9730
- const value = reader.f64();
9828
+ if (tag === 2) {
9829
+ const value = r.f64();
9731
9830
  return Number.isInteger(value) && Number.isFinite(value) ? asSqlReal(value) : value;
9732
9831
  }
9733
- if (pack2 === PACK_TEXT) return intern[reader.u32()] ?? "";
9734
- if (pack2 === PACK_BLOB) return reader.owned(reader.u32());
9735
- return reader.value();
9832
+ if (tag === 3) return intern[readVarintU32(r)] ?? "";
9833
+ if (tag === 4) return r.text();
9834
+ return r.raw(readVarintU32(r));
9835
+ }
9836
+ function writePackedColumn(w, rows, col, internId) {
9837
+ const n = rows.length;
9838
+ if (n === 0) {
9839
+ w.u8(PACK_NULL);
9840
+ return;
9841
+ }
9842
+ const bits = new Uint8Array(n + 7 >> 3);
9843
+ let nulls = 0;
9844
+ let kind = null;
9845
+ let useInlineText = false;
9846
+ for (let i = 0; i < n; i++) {
9847
+ const value = rows[i].values[col] ?? null;
9848
+ if (value === null) {
9849
+ bits[i >> 3] = bits[i >> 3] | 1 << (i & 7);
9850
+ nulls++;
9851
+ continue;
9852
+ }
9853
+ const cellKind = packKindOf(value);
9854
+ if (kind === null) kind = cellKind;
9855
+ else if (kind !== cellKind) kind = PACK_TAGGED;
9856
+ if (cellKind === PACK_TEXT_INTERN && typeof value === "string" && internId(value) < 0) useInlineText = true;
9857
+ if (value instanceof SqlJsonText) kind = PACK_TAGGED;
9858
+ }
9859
+ if (nulls === n) {
9860
+ w.u8(PACK_NULL);
9861
+ return;
9862
+ }
9863
+ let pack2 = kind ?? PACK_TAGGED;
9864
+ if (pack2 === PACK_TEXT_INTERN && useInlineText) pack2 = PACK_TEXT_INLINE;
9865
+ w.u8(pack2);
9866
+ w.raw(bits);
9867
+ if (pack2 === PACK_I32) {
9868
+ w.align4();
9869
+ for (let i = 0; i < n; i++) {
9870
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9871
+ w.u32(rows[i].values[col]);
9872
+ }
9873
+ return;
9874
+ }
9875
+ if (pack2 === PACK_I64) {
9876
+ w.align4();
9877
+ for (let i = 0; i < n; i++) {
9878
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9879
+ const v = rows[i].values[col];
9880
+ w.i64(typeof v === "bigint" ? v : BigInt(v));
9881
+ }
9882
+ return;
9883
+ }
9884
+ if (pack2 === PACK_F64) {
9885
+ w.align4();
9886
+ for (let i = 0; i < n; i++) {
9887
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9888
+ const v = rows[i].values[col];
9889
+ w.f64(v instanceof SqlReal ? v.value : v);
9890
+ }
9891
+ return;
9892
+ }
9893
+ if (pack2 === PACK_TEXT_INTERN) {
9894
+ w.align4();
9895
+ for (let i = 0; i < n; i++) {
9896
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9897
+ const text2 = rows[i].values[col];
9898
+ w.u32(internId(text2));
9899
+ }
9900
+ return;
9901
+ }
9902
+ if (pack2 === PACK_TEXT_INLINE) {
9903
+ const offsets = [];
9904
+ let blobLen = 0;
9905
+ for (let i = 0; i < n; i++) {
9906
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9907
+ offsets.push(blobLen);
9908
+ const text2 = rows[i].values[col] instanceof SqlJsonText ? rows[i].values[col].value : rows[i].values[col];
9909
+ blobLen += utf8Encode(text2).length;
9910
+ }
9911
+ writeVarintU32(w, offsets.length);
9912
+ for (const off of offsets) writeVarintU32(w, off);
9913
+ writeVarintU32(w, blobLen);
9914
+ for (let i = 0; i < n; i++) {
9915
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9916
+ const text2 = rows[i].values[col] instanceof SqlJsonText ? rows[i].values[col].value : rows[i].values[col];
9917
+ w.textBytes(text2);
9918
+ }
9919
+ return;
9920
+ }
9921
+ if (pack2 === PACK_BLOB) {
9922
+ for (let i = 0; i < n; i++) {
9923
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9924
+ const blob = rows[i].values[col];
9925
+ writeVarintU32(w, blob.length);
9926
+ w.raw(blob);
9927
+ }
9928
+ return;
9929
+ }
9930
+ for (let i = 0; i < n; i++) {
9931
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9932
+ writeTaggedValue(w, rows[i].values[col], internId);
9933
+ }
9934
+ }
9935
+ function readPackedColumn(r, n, intern) {
9936
+ const pack2 = r.u8();
9937
+ if (pack2 === PACK_NULL) return { pack: pack2, nullBitmap: null, payload: new Uint8Array(0) };
9938
+ const bits = r.raw(n + 7 >> 3);
9939
+ const nonNull = countNonNull(bits, n);
9940
+ if (pack2 === PACK_I32 || pack2 === PACK_I64 || pack2 === PACK_F64 || pack2 === PACK_TEXT_INTERN) {
9941
+ r.skipAlign4();
9942
+ const width = pack2 === PACK_I32 || pack2 === PACK_TEXT_INTERN ? 4 : 8;
9943
+ const payload = r.raw(nonNull * width);
9944
+ return { pack: pack2, nullBitmap: bits, payload };
9945
+ }
9946
+ if (pack2 === PACK_TEXT_INLINE) {
9947
+ const count = readVarintU32(r);
9948
+ const offsets = new Uint32Array(count);
9949
+ for (let i = 0; i < count; i++) offsets[i] = readVarintU32(r);
9950
+ const total = readVarintU32(r);
9951
+ const payload = r.raw(total);
9952
+ return { pack: pack2, nullBitmap: bits, payload, inlineOffsets: offsets };
9953
+ }
9954
+ if (pack2 === PACK_BLOB) {
9955
+ const chunks = [];
9956
+ for (let i = 0; i < n; i++) {
9957
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9958
+ const len = readVarintU32(r);
9959
+ chunks.push(r.raw(len));
9960
+ }
9961
+ return { pack: pack2, nullBitmap: bits, payload: new Uint8Array(0), blobChunks: chunks };
9962
+ }
9963
+ const tagged = [];
9964
+ for (let i = 0; i < n; i++) {
9965
+ if (bits[i >> 3] & 1 << (i & 7)) continue;
9966
+ tagged.push(readTaggedValue(r, intern));
9967
+ }
9968
+ return { pack: PACK_TAGGED, nullBitmap: bits, payload: new Uint8Array(0), tagged };
9736
9969
  }
9737
- function readIndexStoreKeys(reader, name) {
9738
- const count = reader.u32();
9970
+ function countNonNull(bits, n) {
9971
+ let c = 0;
9972
+ for (let i = 0; i < n; i++) if ((bits[i >> 3] & 1 << (i & 7)) === 0) c++;
9973
+ return c;
9974
+ }
9975
+ function writeIndexStoreBinary(w, store, internId) {
9976
+ const entries = store.snapshotKeys();
9977
+ writeVarintU32(w, entries.length);
9978
+ for (const entry of entries) {
9979
+ const values = store.keyValuesFor(entry.key);
9980
+ writeVarintU32(w, values.length);
9981
+ for (const v of values) writeTaggedValue(w, v, internId);
9982
+ writeVarintU32(w, entry.rowids.length);
9983
+ for (const id of entry.rowids) writeTaggedValue(w, id, internId);
9984
+ }
9985
+ }
9986
+ function readIndexStoreBinary(r, name, intern) {
9987
+ const count = readVarintU32(r);
9739
9988
  const entries = /* @__PURE__ */ new Map();
9989
+ const keyValues = /* @__PURE__ */ new Map();
9740
9990
  for (let i = 0; i < count; i++) {
9741
- const key = reader.text();
9742
- const rowidCount = reader.u32();
9991
+ const valueCount = readVarintU32(r);
9992
+ const values = [];
9993
+ for (let v = 0; v < valueCount; v++) values.push(readTaggedValue(r, intern));
9994
+ const key = serializeIndexEntry(values);
9995
+ const rowidCount = readVarintU32(r);
9743
9996
  const rowids = [];
9744
- for (let r = 0; r < rowidCount; r++) rowids.push(asRowid2(reader.value()));
9997
+ for (let ri = 0; ri < rowidCount; ri++) rowids.push(asRowid2(readTaggedValue(r, intern)));
9745
9998
  entries.set(key, rowids);
9999
+ keyValues.set(key, values);
10000
+ }
10001
+ return new IndexStore(name, entries, keyValues);
10002
+ }
10003
+ function asRowid2(value) {
10004
+ if (typeof value === "number" && Number.isSafeInteger(value) || typeof value === "bigint") return value;
10005
+ throw new SqliteError("invalid rowid in snapshot", "other");
10006
+ }
10007
+ function compareNames(a, b) {
10008
+ return a < b ? -1 : a > b ? 1 : 0;
10009
+ }
10010
+ function finiteNowMs(value) {
10011
+ if (!Number.isFinite(value)) return 0;
10012
+ const max = 864e13;
10013
+ if (value > max) return max;
10014
+ if (value < -max) return -max;
10015
+ return value;
10016
+ }
10017
+ function sortedValues(map) {
10018
+ return [...map.values()].sort((a, b) => compareNames(a.name, b.name));
10019
+ }
10020
+ function forceSchemaIntern(pool, tables, views, indexes) {
10021
+ for (const table of tables) {
10022
+ pool.forceId(table.name);
10023
+ pool.forceId(table.originalSql ?? "");
10024
+ for (const column of table.columns) {
10025
+ pool.forceId(column.name);
10026
+ pool.forceId(column.typeName ?? "");
10027
+ pool.forceId(column.collate ?? "");
10028
+ }
10029
+ for (const name of table.indexes) pool.forceId(name);
10030
+ forceBjvStrings(pool, table.constraints);
10031
+ for (const column of table.columns) {
10032
+ if (column.defaultExpr) forceBjvStrings(pool, column.defaultExpr);
10033
+ if (column.generated) forceBjvStrings(pool, column.generated);
10034
+ }
10035
+ }
10036
+ for (const view of views) forceBjvStrings(pool, view);
10037
+ for (const index of indexes) {
10038
+ pool.forceId(index.name);
10039
+ pool.forceId(index.tableName);
10040
+ pool.forceId(index.originalSql ?? "");
10041
+ forceBjvStrings(pool, {
10042
+ name: index.name,
10043
+ tableName: index.tableName,
10044
+ unique: index.unique,
10045
+ columns: index.columns,
10046
+ where: index.where,
10047
+ originalSql: index.originalSql
10048
+ });
10049
+ }
10050
+ }
10051
+ function forceBjvStrings(pool, value) {
10052
+ if (value === null || value === void 0) return;
10053
+ if (typeof value === "string") {
10054
+ pool.forceId(value);
10055
+ return;
10056
+ }
10057
+ if (typeof value === "bigint" || typeof value === "boolean" || typeof value === "number") return;
10058
+ if (value instanceof Uint8Array) return;
10059
+ if (Array.isArray(value)) {
10060
+ for (const item of value) forceBjvStrings(pool, item);
10061
+ return;
10062
+ }
10063
+ if (typeof value === "object") {
10064
+ for (const [k, v] of Object.entries(value)) {
10065
+ pool.forceId(k);
10066
+ forceBjvStrings(pool, v);
10067
+ }
9746
10068
  }
9747
- return new IndexStore(name, entries);
9748
10069
  }
9749
10070
 
9750
10071
  // src/api/snapshot.ts
@@ -11098,7 +11419,7 @@ function lookupTableRows(from, where, env) {
11098
11419
  const row = table.get(rowid);
11099
11420
  if (row) rows.push(row);
11100
11421
  }
11101
- rows.sort((a, b) => compareRowids3(a.rowid, b.rowid));
11422
+ rows.sort((a, b) => compareRowids2(a.rowid, b.rowid));
11102
11423
  return { table, alias, rows, coveredColumns: new Set(best.columns) };
11103
11424
  }
11104
11425
  for (const eq of resolved) {
@@ -11106,7 +11427,7 @@ function lookupTableRows(from, where, env) {
11106
11427
  if (isRowidName(eq.column)) continue;
11107
11428
  const hashed = table.lookupEquality(col, eq.value);
11108
11429
  if (!hashed) continue;
11109
- hashed.sort((a, b) => compareRowids3(a.rowid, b.rowid));
11430
+ hashed.sort((a, b) => compareRowids2(a.rowid, b.rowid));
11110
11431
  return { table, alias, rows: hashed, coveredColumns: /* @__PURE__ */ new Set([col]) };
11111
11432
  }
11112
11433
  const range = rangeAgainstConst(where) ?? rangeFromBetween(where);
@@ -11188,7 +11509,7 @@ function considerIndexLookup(best, candidate) {
11188
11509
  if (!best || candidate.prefixLen > best.prefixLen) return { best: candidate, stop: false };
11189
11510
  return { best, stop: false };
11190
11511
  }
11191
- function compareRowids3(left, right) {
11512
+ function compareRowids2(left, right) {
11192
11513
  const a = typeof left === "bigint" ? left : BigInt(left);
11193
11514
  const b = typeof right === "bigint" ? right : BigInt(right);
11194
11515
  return a < b ? -1 : a > b ? 1 : 0;
@@ -11290,7 +11611,7 @@ function tryJoinProbe(right, on, env) {
11290
11611
  const row = table.get(rowid);
11291
11612
  if (row) rows.push(row);
11292
11613
  }
11293
- rows.sort((a, b) => compareRowids3(a.rowid, b.rowid));
11614
+ rows.sort((a, b) => compareRowids2(a.rowid, b.rowid));
11294
11615
  return rows;
11295
11616
  }
11296
11617
  };