@crvouga/sqlite-mem 1.13.0 → 1.14.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.
@@ -5,6 +5,7 @@ export declare class IndexStore {
5
5
  private entries;
6
6
  private keyValues;
7
7
  private sortedKeys;
8
+ private mapsShared;
8
9
  frozen: boolean;
9
10
  constructor(name?: string, entries?: ReadonlyMap<string, readonly Rowid[]>, keyValues?: ReadonlyMap<string, readonly SqlValue[]>);
10
11
  checkUnique(values: readonly SqlValue[], rowid?: Rowid): void;
@@ -39,6 +40,7 @@ export declare class IndexStore {
39
40
  get size(): number;
40
41
  private orderedKeys;
41
42
  private assertMutable;
43
+ private forkMaps;
42
44
  }
43
45
  /** Hash-join / covering-hash key: NULL components never match. */
44
46
  export declare function serializeIndexKey(values: readonly SqlValue[]): string | null;
@@ -0,0 +1,11 @@
1
+ import type { Row } from "../storage/row.js";
2
+ import type { Table } from "../storage/table.js";
3
+ /** Always-on invariant check (Tiger Style — not stripped in production). */
4
+ export declare function assert(condition: boolean, message: string): asserts condition;
5
+ /** Exhaustive switch helper — call when a union case should be impossible. */
6
+ export declare function assertUnreachable(value: never, message?: string): never;
7
+ /** Row payload must align with table column count after insert/update. */
8
+ export declare function assertRowShape(table: Table, row: Row): void;
9
+ /** Maximum blob/string length enforced before allocation (matches bun:sqlite TOOBIG). */
10
+ export declare const SQLITE_MAX_LENGTH = 2147483647;
11
+ export declare function assertBlobLength(length: number, _feature: string): void;
@@ -0,0 +1,3 @@
1
+ import type { ErrorCategory } from "../errors/index.js";
2
+ /** True when an error is an expected fast-path miss (fall through to full executor). */
3
+ export declare function isExpectedFastPathMiss(error: unknown, categories?: readonly ErrorCategory[]): boolean;
package/dist/unstable.js CHANGED
@@ -2219,6 +2219,23 @@ var mathFunctions = {
2219
2219
  }
2220
2220
  };
2221
2221
 
2222
+ // src/runtime/assert.ts
2223
+ function assert(condition, message) {
2224
+ if (!condition) {
2225
+ throw new SqliteError(`internal assertion failed: ${message}`, "other");
2226
+ }
2227
+ }
2228
+ function assertRowShape(table, row) {
2229
+ assert(row.values.length === table.columns.length, `row.values.length !== table.columns.length for ${table.name}`);
2230
+ }
2231
+ var SQLITE_MAX_LENGTH = 2147483647;
2232
+ function assertBlobLength(length, _feature) {
2233
+ if (!Number.isFinite(length) || length < 0) return;
2234
+ if (length >= SQLITE_MAX_LENGTH) {
2235
+ throw new SqliteError(`string or blob too big`, "other", "SQLITE_TOOBIG");
2236
+ }
2237
+ }
2238
+
2222
2239
  // src/expressions/like.ts
2223
2240
  function escapeRegexChar(char) {
2224
2241
  return /[\\^$.*+?()[\]{}|]/.test(char) ? `\\${char}` : char;
@@ -2478,6 +2495,7 @@ var scalarFunctions = {
2478
2495
  requireArgs3("randomblob", args, 1);
2479
2496
  if (args[0] === null) return null;
2480
2497
  const length = Math.max(0, Math.trunc(numeric(args[0])));
2498
+ assertBlobLength(length, "randomblob");
2481
2499
  const out = new Uint8Array(length);
2482
2500
  let offset = 0;
2483
2501
  while (offset < length) {
@@ -2492,6 +2510,7 @@ var scalarFunctions = {
2492
2510
  requireArgs3("zeroblob", args, 1);
2493
2511
  if (args[0] === null) return null;
2494
2512
  const length = Math.max(0, Math.trunc(numeric(args[0])));
2513
+ assertBlobLength(length, "zeroblob");
2495
2514
  return new Uint8Array(length);
2496
2515
  },
2497
2516
  hex(args) {
@@ -2974,10 +2993,19 @@ function evalBinary(op, leftExpr, rightExpr, ctx) {
2974
2993
  if (left === null || right === null) return null;
2975
2994
  switch (op) {
2976
2995
  case "+":
2996
+ if (storageClassOf(left) === "integer" && storageClassOf(right) === "integer") {
2997
+ return integerArithmetic("+", left, right);
2998
+ }
2977
2999
  return asNumber(numberValue(left) + numberValue(right));
2978
3000
  case "-":
3001
+ if (storageClassOf(left) === "integer" && storageClassOf(right) === "integer") {
3002
+ return integerArithmetic("-", left, right);
3003
+ }
2979
3004
  return asNumber(numberValue(left) - numberValue(right));
2980
3005
  case "*":
3006
+ if (storageClassOf(left) === "integer" && storageClassOf(right) === "integer") {
3007
+ return integerArithmetic("*", left, right);
3008
+ }
2981
3009
  return asNumber(numberValue(left) * numberValue(right));
2982
3010
  case "/": {
2983
3011
  const divisor = numberValue(right);
@@ -3014,6 +3042,17 @@ function evalBinary(op, leftExpr, rightExpr, ctx) {
3014
3042
  function safeIntegerResult(value) {
3015
3043
  return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(value) : value;
3016
3044
  }
3045
+ var I64_MIN = -(2n ** 63n);
3046
+ var I64_MAX = 2n ** 63n - 1n;
3047
+ function integerArithmetic(op, left, right) {
3048
+ const a = integerValue(left);
3049
+ const b = integerValue(right);
3050
+ const result = op === "+" ? a + b : op === "-" ? a - b : a * b;
3051
+ if (result < I64_MIN || result > I64_MAX) {
3052
+ return asSqlReal(Number(result));
3053
+ }
3054
+ return safeIntegerResult(result);
3055
+ }
3017
3056
  function compareRowValues(op, leftExprs, rightExprs, ctx) {
3018
3057
  if (leftExprs.length !== rightExprs.length) {
3019
3058
  throw new SqliteError("row value misused", "misuse");
@@ -5815,6 +5854,7 @@ var IndexStore = class _IndexStore {
5815
5854
  entries;
5816
5855
  keyValues;
5817
5856
  sortedKeys;
5857
+ mapsShared = false;
5818
5858
  frozen = false;
5819
5859
  constructor(name = "index", entries, keyValues) {
5820
5860
  this.name = name;
@@ -5868,10 +5908,22 @@ var IndexStore = class _IndexStore {
5868
5908
  if (values.some((value) => value === null)) return [];
5869
5909
  const prefix = serializeIndexEntry(values);
5870
5910
  const exact = this.entries.get(prefix);
5871
- const needle = `${prefix}|`;
5872
5911
  const rowids = exact ? [...exact] : [];
5873
- for (const [key, ids] of this.entries) {
5874
- if (key.startsWith(needle)) rowids.push(...ids);
5912
+ const keys = this.orderedKeys();
5913
+ let start = 0;
5914
+ let end = keys.length;
5915
+ while (start < end) {
5916
+ const mid = start + end >>> 1;
5917
+ const kv = this.keyValues.get(keys[mid]);
5918
+ if (!kv || prefixKeyLess(kv, values)) start = mid + 1;
5919
+ else end = mid;
5920
+ }
5921
+ for (let i = start; i < keys.length; i++) {
5922
+ const kv = this.keyValues.get(keys[i]);
5923
+ if (!kv || !prefixKeyMatches(kv, values)) break;
5924
+ const key = keys[i];
5925
+ if (key === prefix) continue;
5926
+ rowids.push(...this.entries.get(key) ?? []);
5875
5927
  }
5876
5928
  return rowids;
5877
5929
  }
@@ -5880,9 +5932,12 @@ var IndexStore = class _IndexStore {
5880
5932
  * `op` applies to the first key component.
5881
5933
  */
5882
5934
  rangeLookup(op, bound, bound2) {
5883
- const rowids = [];
5884
5935
  const keys = this.orderedKeys();
5885
- for (const key of keys) {
5936
+ const rowids = [];
5937
+ const start = lowerBoundKeyValues(keys, this.keyValues, bound, op === ">" ? "gt" : op === ">=" ? "ge" : "any");
5938
+ const end = op === "<" || op === "<=" ? upperBoundKeyValues(keys, this.keyValues, bound, op === "<" ? "lt" : "le") : op === "between" && bound2 !== void 0 ? upperBoundKeyValues(keys, this.keyValues, bound2, "le") : keys.length;
5939
+ for (let i = start; i < end; i++) {
5940
+ const key = keys[i];
5886
5941
  const values = this.keyValues.get(key);
5887
5942
  if (!values || values[0] === void 0 || values[0] === null) continue;
5888
5943
  const cmp = compareSerializedOrder(values[0], bound);
@@ -5939,7 +5994,12 @@ var IndexStore = class _IndexStore {
5939
5994
  this.sortedKeys = null;
5940
5995
  }
5941
5996
  clone() {
5942
- return new _IndexStore(this.name, this.entries, this.keyValues);
5997
+ const copy = new _IndexStore(this.name);
5998
+ copy.entries = this.entries;
5999
+ copy.keyValues = this.keyValues;
6000
+ copy.sortedKeys = this.sortedKeys;
6001
+ copy.mapsShared = true;
6002
+ return copy;
5943
6003
  }
5944
6004
  freeze() {
5945
6005
  this.frozen = true;
@@ -5995,6 +6055,18 @@ var IndexStore = class _IndexStore {
5995
6055
  }
5996
6056
  assertMutable() {
5997
6057
  if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen index", "other");
6058
+ this.forkMaps();
6059
+ }
6060
+ forkMaps() {
6061
+ if (!this.mapsShared) return;
6062
+ const entries = /* @__PURE__ */ new Map();
6063
+ for (const [key, rowids] of this.entries) entries.set(key, [...rowids]);
6064
+ const keyValues = /* @__PURE__ */ new Map();
6065
+ for (const [key, values] of this.keyValues) keyValues.set(key, [...values]);
6066
+ this.entries = entries;
6067
+ this.keyValues = keyValues;
6068
+ this.mapsShared = false;
6069
+ this.sortedKeys = null;
5998
6070
  }
5999
6071
  };
6000
6072
  function serializeIndexKey(values) {
@@ -6033,6 +6105,53 @@ function serializeValue(value) {
6033
6105
  function compareSerializedOrder(left, right) {
6034
6106
  return compareSql(left, right) ?? 0;
6035
6107
  }
6108
+ function prefixKeyLess(keyValues, prefix) {
6109
+ for (let i = 0; i < prefix.length; i++) {
6110
+ const cmp = compareSerializedOrder(keyValues[i] ?? null, prefix[i]);
6111
+ if (cmp !== 0) return cmp < 0;
6112
+ }
6113
+ return false;
6114
+ }
6115
+ function prefixKeyMatches(keyValues, prefix) {
6116
+ for (let i = 0; i < prefix.length; i++) {
6117
+ if (compareSerializedOrder(keyValues[i] ?? null, prefix[i]) !== 0) return false;
6118
+ }
6119
+ return true;
6120
+ }
6121
+ function lowerBoundKeyValues(keys, keyValues, bound, mode) {
6122
+ let lo = 0;
6123
+ let hi = keys.length;
6124
+ while (lo < hi) {
6125
+ const mid = lo + hi >>> 1;
6126
+ const value = keyValues.get(keys[mid])?.[0];
6127
+ if (value === void 0 || value === null) {
6128
+ lo = mid + 1;
6129
+ continue;
6130
+ }
6131
+ const cmp = compareSerializedOrder(value, bound);
6132
+ const before = mode === "gt" ? cmp <= 0 : mode === "ge" ? cmp < 0 : false;
6133
+ if (before) lo = mid + 1;
6134
+ else hi = mid;
6135
+ }
6136
+ return lo;
6137
+ }
6138
+ function upperBoundKeyValues(keys, keyValues, bound, mode) {
6139
+ let lo = 0;
6140
+ let hi = keys.length;
6141
+ while (lo < hi) {
6142
+ const mid = lo + hi >>> 1;
6143
+ const value = keyValues.get(keys[mid])?.[0];
6144
+ if (value === void 0 || value === null) {
6145
+ lo = mid + 1;
6146
+ continue;
6147
+ }
6148
+ const cmp = compareSerializedOrder(value, bound);
6149
+ const after = mode === "lt" ? cmp >= 0 : cmp > 0;
6150
+ if (after) hi = mid;
6151
+ else lo = mid + 1;
6152
+ }
6153
+ return lo;
6154
+ }
6036
6155
  function sameRowid(left, right) {
6037
6156
  return typeof left === "bigint" || typeof right === "bigint" ? BigInt(left) === BigInt(right) : left === right;
6038
6157
  }
@@ -6553,6 +6672,9 @@ function parseFts5Query(input) {
6553
6672
  }
6554
6673
  if (p.kind === "TERM") {
6555
6674
  next();
6675
+ if (p.prefix && (p.value === "" || p.value.includes("*"))) {
6676
+ throw new SqliteError(`unknown special query: ${p.value}`, "other");
6677
+ }
6556
6678
  return { type: "term", value: p.value, prefix: p.prefix, column: null, columns: null };
6557
6679
  }
6558
6680
  throw new SqliteError(`fts5: syntax error near "${displayTok(p)}"`, "syntax");
@@ -6719,17 +6841,7 @@ function displayTok(t) {
6719
6841
  }
6720
6842
  }
6721
6843
  function parseFts3Query(input) {
6722
- try {
6723
- return parseFts5Query(input);
6724
- } catch {
6725
- const terms = input.split(/\s+/).filter(Boolean).filter((t) => !/^(AND|OR|NOT|NEAR)$/i.test(t));
6726
- if (terms.length === 0) return { type: "true" };
6727
- const nodes = terms.map((t) => {
6728
- const prefix = t.endsWith("*");
6729
- return { type: "term", value: prefix ? t.slice(0, -1) : t, prefix, column: null, columns: null };
6730
- });
6731
- return nodes.length === 1 ? nodes[0] : { type: "and", children: nodes };
6732
- }
6844
+ return parseFts5Query(input);
6733
6845
  }
6734
6846
 
6735
6847
  // src/vtable/fts/porter.ts
@@ -7792,11 +7904,6 @@ function rowValues(values) {
7792
7904
  }
7793
7905
  return result;
7794
7906
  }
7795
- function cloneRow(row) {
7796
- const values = new Array(row.values.length);
7797
- for (let i = 0; i < row.values.length; i++) values[i] = cloneSqlValue(row.values[i]);
7798
- return { rowid: row.rowid, values };
7799
- }
7800
7907
  function isValueArray(values) {
7801
7908
  return Array.isArray(values);
7802
7909
  }
@@ -7935,6 +8042,7 @@ var Table = class _Table {
7935
8042
  this.scanCache = null;
7936
8043
  }
7937
8044
  commitRow(row) {
8045
+ assertRowShape(this, row);
7938
8046
  this.indexEquality(row);
7939
8047
  this.invalidateScan();
7940
8048
  }
@@ -8157,16 +8265,11 @@ var Table = class _Table {
8157
8265
  copy.nextRowid = this.nextRowid;
8158
8266
  copy.maximumRowid = this.maximumRowid;
8159
8267
  if (this.slab) {
8160
- let max = null;
8161
- for (const row of this.slab.scan()) {
8162
- copy.rows.set(row.rowid, cloneRow(row));
8163
- if (max === null || compareRowids(row.rowid, max) > 0) max = row.rowid;
8164
- }
8165
- copy.maximumRowid = max ?? void 0;
8268
+ for (const row of this.slab.scan()) copy.rows.set(row.rowid, row);
8166
8269
  } else {
8167
- for (const [rowid, row] of this.rows) copy.rows.set(rowid, cloneRow(row));
8270
+ for (const [rowid, row] of this.rows) copy.rows.set(rowid, row);
8168
8271
  }
8169
- for (const [clusterKey, row] of this.clusteredRows) copy.clusteredRows.set(clusterKey, cloneRow(row));
8272
+ for (const [clusterKey, row] of this.clusteredRows) copy.clusteredRows.set(clusterKey, row);
8170
8273
  return copy;
8171
8274
  }
8172
8275
  freeze() {