bun-sqlite-for-rxdb 1.5.2 → 1.5.4
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 +379 -127
- package/dist/src/instance.d.ts +1 -0
- package/dist/src/query/builder.d.ts +8 -3
- package/dist/src/query/cache.d.ts +18 -0
- package/dist/src/query/no-op-cache.d.ts +10 -0
- package/dist/src/query/sieve-cache.d.ts +17 -0
- package/dist/src/query/smart-regex.d.ts +0 -1
- package/dist/src/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12228,9 +12228,191 @@ function addRxStorageMultiInstanceSupport(storageName, instanceCreationParams, i
|
|
|
12228
12228
|
// src/instance.ts
|
|
12229
12229
|
var import_rxjs2 = __toESM(require_cjs(), 1);
|
|
12230
12230
|
|
|
12231
|
-
// src/query/
|
|
12232
|
-
|
|
12231
|
+
// src/query/sieve-cache.ts
|
|
12232
|
+
class SieveCache {
|
|
12233
|
+
#capacity;
|
|
12234
|
+
#map;
|
|
12235
|
+
#keys;
|
|
12236
|
+
#values;
|
|
12237
|
+
#visited;
|
|
12238
|
+
#newer;
|
|
12239
|
+
#older;
|
|
12240
|
+
#head = 0;
|
|
12241
|
+
#tail = 0;
|
|
12242
|
+
#hand = 0;
|
|
12243
|
+
#freeHead = 0;
|
|
12244
|
+
#nextFreeIndex = 1;
|
|
12245
|
+
constructor(capacity) {
|
|
12246
|
+
if (capacity < 1)
|
|
12247
|
+
throw new RangeError("Capacity must be at least 1");
|
|
12248
|
+
const arraySize = capacity + 1;
|
|
12249
|
+
this.#capacity = capacity;
|
|
12250
|
+
this.#map = new Map;
|
|
12251
|
+
this.#keys = new Array(arraySize);
|
|
12252
|
+
this.#values = new Array(arraySize);
|
|
12253
|
+
this.#visited = new Uint8Array(arraySize);
|
|
12254
|
+
this.#newer = new Uint32Array(arraySize);
|
|
12255
|
+
this.#older = new Uint32Array(arraySize);
|
|
12256
|
+
}
|
|
12257
|
+
get size() {
|
|
12258
|
+
return this.#map.size;
|
|
12259
|
+
}
|
|
12260
|
+
has(key) {
|
|
12261
|
+
return this.#map.has(key);
|
|
12262
|
+
}
|
|
12263
|
+
get(key) {
|
|
12264
|
+
const index = this.#map.get(key);
|
|
12265
|
+
if (index !== undefined) {
|
|
12266
|
+
this.#visited[index] = 1;
|
|
12267
|
+
return this.#values[index];
|
|
12268
|
+
}
|
|
12269
|
+
return;
|
|
12270
|
+
}
|
|
12271
|
+
set(key, value) {
|
|
12272
|
+
let index = this.#map.get(key);
|
|
12273
|
+
if (index !== undefined) {
|
|
12274
|
+
this.#values[index] = value;
|
|
12275
|
+
this.#visited[index] = 1;
|
|
12276
|
+
return this;
|
|
12277
|
+
}
|
|
12278
|
+
index = this.#getFreeIndex();
|
|
12279
|
+
this.#keys[index] = key;
|
|
12280
|
+
this.#values[index] = value;
|
|
12281
|
+
this.#visited[index] = 0;
|
|
12282
|
+
this.#map.set(key, index);
|
|
12283
|
+
if (this.#head === 0) {
|
|
12284
|
+
this.#head = this.#tail = index;
|
|
12285
|
+
} else {
|
|
12286
|
+
this.#newer[this.#head] = index;
|
|
12287
|
+
this.#older[index] = this.#head;
|
|
12288
|
+
this.#head = index;
|
|
12289
|
+
}
|
|
12290
|
+
return this;
|
|
12291
|
+
}
|
|
12292
|
+
delete(key) {
|
|
12293
|
+
const index = this.#map.get(key);
|
|
12294
|
+
if (index !== undefined) {
|
|
12295
|
+
this.#map.delete(key);
|
|
12296
|
+
this.#removeNode(index);
|
|
12297
|
+
this.#keys[index] = undefined;
|
|
12298
|
+
this.#values[index] = undefined;
|
|
12299
|
+
this.#newer[index] = this.#freeHead;
|
|
12300
|
+
this.#freeHead = index;
|
|
12301
|
+
return true;
|
|
12302
|
+
}
|
|
12303
|
+
return false;
|
|
12304
|
+
}
|
|
12305
|
+
clear() {
|
|
12306
|
+
this.#map.clear();
|
|
12307
|
+
this.#head = 0;
|
|
12308
|
+
this.#tail = 0;
|
|
12309
|
+
this.#hand = 0;
|
|
12310
|
+
this.#freeHead = 0;
|
|
12311
|
+
this.#nextFreeIndex = 1;
|
|
12312
|
+
this.#keys.fill(undefined);
|
|
12313
|
+
this.#values.fill(undefined);
|
|
12314
|
+
this.#newer.fill(0);
|
|
12315
|
+
this.#older.fill(0);
|
|
12316
|
+
this.#visited.fill(0);
|
|
12317
|
+
}
|
|
12318
|
+
#getFreeIndex() {
|
|
12319
|
+
if (this.#freeHead !== 0) {
|
|
12320
|
+
const index = this.#freeHead;
|
|
12321
|
+
this.#freeHead = this.#newer[index];
|
|
12322
|
+
this.#newer[index] = 0;
|
|
12323
|
+
return index;
|
|
12324
|
+
}
|
|
12325
|
+
if (this.#nextFreeIndex <= this.#capacity) {
|
|
12326
|
+
return this.#nextFreeIndex++;
|
|
12327
|
+
}
|
|
12328
|
+
return this.#evict();
|
|
12329
|
+
}
|
|
12330
|
+
#evict() {
|
|
12331
|
+
let hand = this.#hand === 0 ? this.#tail : this.#hand;
|
|
12332
|
+
while (this.#visited[hand] === 1) {
|
|
12333
|
+
this.#visited[hand] = 0;
|
|
12334
|
+
hand = this.#newer[hand] === 0 ? this.#tail : this.#newer[hand];
|
|
12335
|
+
}
|
|
12336
|
+
this.#hand = this.#newer[hand];
|
|
12337
|
+
const victimIndex = hand;
|
|
12338
|
+
this.#map.delete(this.#keys[victimIndex]);
|
|
12339
|
+
this.#removeNode(victimIndex);
|
|
12340
|
+
return victimIndex;
|
|
12341
|
+
}
|
|
12342
|
+
#removeNode(index) {
|
|
12343
|
+
const newer = this.#newer[index];
|
|
12344
|
+
const older = this.#older[index];
|
|
12345
|
+
if (newer !== 0) {
|
|
12346
|
+
this.#older[newer] = older;
|
|
12347
|
+
} else {
|
|
12348
|
+
this.#head = older;
|
|
12349
|
+
}
|
|
12350
|
+
if (older !== 0) {
|
|
12351
|
+
this.#newer[older] = newer;
|
|
12352
|
+
} else {
|
|
12353
|
+
this.#tail = newer;
|
|
12354
|
+
}
|
|
12355
|
+
if (this.#hand === index) {
|
|
12356
|
+
this.#hand = newer;
|
|
12357
|
+
}
|
|
12358
|
+
this.#newer[index] = 0;
|
|
12359
|
+
this.#older[index] = 0;
|
|
12360
|
+
}
|
|
12361
|
+
forEach(callbackfn, thisArg) {
|
|
12362
|
+
this.#map.forEach((index, key) => {
|
|
12363
|
+
callbackfn.call(thisArg, this.#values[index], key, this);
|
|
12364
|
+
});
|
|
12365
|
+
}
|
|
12366
|
+
*entries() {
|
|
12367
|
+
for (const [key, index] of this.#map.entries()) {
|
|
12368
|
+
yield [key, this.#values[index]];
|
|
12369
|
+
}
|
|
12370
|
+
}
|
|
12371
|
+
*keys() {
|
|
12372
|
+
for (const key of this.#map.keys()) {
|
|
12373
|
+
yield key;
|
|
12374
|
+
}
|
|
12375
|
+
}
|
|
12376
|
+
*values() {
|
|
12377
|
+
for (const index of this.#map.values()) {
|
|
12378
|
+
yield this.#values[index];
|
|
12379
|
+
}
|
|
12380
|
+
}
|
|
12381
|
+
[Symbol.iterator]() {
|
|
12382
|
+
return this.entries();
|
|
12383
|
+
}
|
|
12384
|
+
get [Symbol.toStringTag]() {
|
|
12385
|
+
return `SieveCache(${this.#capacity})`;
|
|
12386
|
+
}
|
|
12387
|
+
}
|
|
12388
|
+
|
|
12389
|
+
// src/query/cache.ts
|
|
12390
|
+
var MAX_QUERY_CACHE_SIZE = 5000;
|
|
12233
12391
|
var MAX_REGEX_CACHE_SIZE = 100;
|
|
12392
|
+
var MAX_INDEX_CACHE_SIZE = 1000;
|
|
12393
|
+
var queryCacheByDatabase = new WeakMap;
|
|
12394
|
+
var GLOBAL_QUERY_CACHE = new SieveCache(MAX_QUERY_CACHE_SIZE);
|
|
12395
|
+
function getQueryCache(database) {
|
|
12396
|
+
let cache = queryCacheByDatabase.get(database);
|
|
12397
|
+
if (!cache) {
|
|
12398
|
+
cache = new SieveCache(MAX_QUERY_CACHE_SIZE);
|
|
12399
|
+
queryCacheByDatabase.set(database, cache);
|
|
12400
|
+
}
|
|
12401
|
+
return cache;
|
|
12402
|
+
}
|
|
12403
|
+
function getGlobalCache() {
|
|
12404
|
+
return GLOBAL_QUERY_CACHE;
|
|
12405
|
+
}
|
|
12406
|
+
var REGEX_CACHE = new SieveCache(MAX_REGEX_CACHE_SIZE);
|
|
12407
|
+
function getRegexCache() {
|
|
12408
|
+
return REGEX_CACHE;
|
|
12409
|
+
}
|
|
12410
|
+
var INDEX_CACHE = new SieveCache(MAX_INDEX_CACHE_SIZE);
|
|
12411
|
+
function getIndexCache() {
|
|
12412
|
+
return INDEX_CACHE;
|
|
12413
|
+
}
|
|
12414
|
+
|
|
12415
|
+
// src/query/regex-matcher.ts
|
|
12234
12416
|
function isValidRegexOptions(options) {
|
|
12235
12417
|
for (let i = 0;i < options.length; i++) {
|
|
12236
12418
|
const c = options[i];
|
|
@@ -12240,22 +12422,17 @@ function isValidRegexOptions(options) {
|
|
|
12240
12422
|
return true;
|
|
12241
12423
|
}
|
|
12242
12424
|
function compileRegex(pattern, options) {
|
|
12243
|
-
const
|
|
12244
|
-
const
|
|
12425
|
+
const cache = getRegexCache();
|
|
12426
|
+
const cacheKey = `${pattern}:${options || ""}`;
|
|
12427
|
+
const cached = cache.get(cacheKey);
|
|
12245
12428
|
if (cached) {
|
|
12246
12429
|
return cached.regex;
|
|
12247
12430
|
}
|
|
12248
12431
|
if (options && !isValidRegexOptions(options)) {
|
|
12249
|
-
throw new Error(`Invalid regex options:
|
|
12432
|
+
throw new Error(`Invalid regex options: ${options}`);
|
|
12250
12433
|
}
|
|
12251
12434
|
const regex = new RegExp(pattern, options);
|
|
12252
|
-
|
|
12253
|
-
const firstKey = REGEX_CACHE.keys().next().value;
|
|
12254
|
-
if (firstKey) {
|
|
12255
|
-
REGEX_CACHE.delete(firstKey);
|
|
12256
|
-
}
|
|
12257
|
-
}
|
|
12258
|
-
REGEX_CACHE.set(cacheKey, { regex });
|
|
12435
|
+
cache.set(cacheKey, { regex });
|
|
12259
12436
|
return regex;
|
|
12260
12437
|
}
|
|
12261
12438
|
function matchesRegex(value, pattern, options) {
|
|
@@ -12294,24 +12471,15 @@ function getColumnInfo(path2, schema) {
|
|
|
12294
12471
|
}
|
|
12295
12472
|
|
|
12296
12473
|
// src/query/smart-regex.ts
|
|
12297
|
-
var INDEX_CACHE = new Map;
|
|
12298
|
-
var MAX_INDEX_CACHE_SIZE = 1000;
|
|
12299
12474
|
function hasExpressionIndex(fieldName, schema) {
|
|
12300
|
-
const
|
|
12301
|
-
const cacheKey = `${
|
|
12302
|
-
const cached =
|
|
12475
|
+
const cache = getIndexCache();
|
|
12476
|
+
const cacheKey = `${fieldName}:${JSON.stringify(schema.indexes || [])}`;
|
|
12477
|
+
const cached = cache.get(cacheKey);
|
|
12303
12478
|
if (cached !== undefined) {
|
|
12304
|
-
INDEX_CACHE.delete(cacheKey);
|
|
12305
|
-
INDEX_CACHE.set(cacheKey, cached);
|
|
12306
12479
|
return cached;
|
|
12307
12480
|
}
|
|
12308
12481
|
if (!schema.indexes) {
|
|
12309
|
-
|
|
12310
|
-
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12311
|
-
if (firstKey)
|
|
12312
|
-
INDEX_CACHE.delete(firstKey);
|
|
12313
|
-
}
|
|
12314
|
-
INDEX_CACHE.set(cacheKey, false);
|
|
12482
|
+
cache.set(cacheKey, false);
|
|
12315
12483
|
return false;
|
|
12316
12484
|
}
|
|
12317
12485
|
const hasLowerIndex = schema.indexes.some((idx) => {
|
|
@@ -12323,12 +12491,7 @@ function hasExpressionIndex(fieldName, schema) {
|
|
|
12323
12491
|
return normalized === `lower(${fieldName})`;
|
|
12324
12492
|
});
|
|
12325
12493
|
});
|
|
12326
|
-
|
|
12327
|
-
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12328
|
-
if (firstKey)
|
|
12329
|
-
INDEX_CACHE.delete(firstKey);
|
|
12330
|
-
}
|
|
12331
|
-
INDEX_CACHE.set(cacheKey, hasLowerIndex);
|
|
12494
|
+
cache.set(cacheKey, hasLowerIndex);
|
|
12332
12495
|
return hasLowerIndex;
|
|
12333
12496
|
}
|
|
12334
12497
|
function isValidRegexOptions2(options) {
|
|
@@ -12949,29 +13112,47 @@ function _stringify(value, stack) {
|
|
|
12949
13112
|
}
|
|
12950
13113
|
|
|
12951
13114
|
// src/query/builder.ts
|
|
12952
|
-
|
|
12953
|
-
var GLOBAL_CACHE = new Map;
|
|
12954
|
-
function buildWhereClause(selector, schema, collectionName, cache = GLOBAL_CACHE) {
|
|
13115
|
+
function buildWhereClause(selector, schema, collectionName, cache) {
|
|
12955
13116
|
if (!selector || typeof selector !== "object")
|
|
12956
13117
|
return null;
|
|
12957
|
-
const
|
|
12958
|
-
const
|
|
13118
|
+
const actualCache = cache ?? getGlobalCache();
|
|
13119
|
+
const cacheKey = `v${schema.version}_${stableStringify(selector)}`;
|
|
13120
|
+
const cached = actualCache.get(cacheKey);
|
|
12959
13121
|
if (cached !== undefined) {
|
|
12960
|
-
cache.delete(cacheKey);
|
|
12961
|
-
cache.set(cacheKey, cached);
|
|
12962
13122
|
return cached;
|
|
12963
13123
|
}
|
|
12964
13124
|
const result = processSelector(selector, schema, 0);
|
|
12965
|
-
|
|
12966
|
-
return null;
|
|
12967
|
-
if (cache.size >= MAX_CACHE_SIZE) {
|
|
12968
|
-
const firstKey = cache.keys().next().value;
|
|
12969
|
-
if (firstKey)
|
|
12970
|
-
cache.delete(firstKey);
|
|
12971
|
-
}
|
|
12972
|
-
cache.set(cacheKey, result);
|
|
13125
|
+
actualCache.set(cacheKey, result);
|
|
12973
13126
|
return result;
|
|
12974
13127
|
}
|
|
13128
|
+
function buildWhereClauseWithFallback(selector, schema, collectionName, cache) {
|
|
13129
|
+
if (!selector || typeof selector !== "object") {
|
|
13130
|
+
return { sqlWhere: null, jsSelector: null };
|
|
13131
|
+
}
|
|
13132
|
+
const sqlResult = buildWhereClause(selector, schema, collectionName, cache);
|
|
13133
|
+
if (sqlResult) {
|
|
13134
|
+
return { sqlWhere: sqlResult, jsSelector: null };
|
|
13135
|
+
}
|
|
13136
|
+
const splitResult = splitSelector(selector, schema);
|
|
13137
|
+
return splitResult;
|
|
13138
|
+
}
|
|
13139
|
+
function splitSelector(selector, schema) {
|
|
13140
|
+
const sqlConditions = [];
|
|
13141
|
+
const jsConditions = [];
|
|
13142
|
+
const entries = Object.entries(selector);
|
|
13143
|
+
for (const [field, value] of entries) {
|
|
13144
|
+
const testSelector = { [field]: value };
|
|
13145
|
+
const sqlFragment = processSelector(testSelector, schema, 0);
|
|
13146
|
+
if (sqlFragment) {
|
|
13147
|
+
sqlConditions.push(testSelector);
|
|
13148
|
+
} else {
|
|
13149
|
+
jsConditions.push(testSelector);
|
|
13150
|
+
}
|
|
13151
|
+
}
|
|
13152
|
+
const sqlWhere = sqlConditions.length > 0 ? processSelector({ $and: sqlConditions }, schema, 0) : null;
|
|
13153
|
+
const jsSelector = jsConditions.length > 0 ? jsConditions.length === 1 ? jsConditions[0] : { $and: jsConditions } : null;
|
|
13154
|
+
return { sqlWhere, jsSelector };
|
|
13155
|
+
}
|
|
12975
13156
|
function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
|
|
12976
13157
|
if (conditions.length === 0) {
|
|
12977
13158
|
return { sql: operator === "or" ? "1=0" : "1=1", args: [] };
|
|
@@ -13016,6 +13197,14 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
13016
13197
|
args.push(...norFragment.args);
|
|
13017
13198
|
continue;
|
|
13018
13199
|
}
|
|
13200
|
+
if (field === "$not" && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
13201
|
+
const innerFragment = processSelector(value, schema, logicalDepth + 1);
|
|
13202
|
+
if (!innerFragment)
|
|
13203
|
+
return null;
|
|
13204
|
+
conditions.push(`NOT (${innerFragment.sql})`);
|
|
13205
|
+
args.push(...innerFragment.args);
|
|
13206
|
+
continue;
|
|
13207
|
+
}
|
|
13019
13208
|
const columnInfo = getColumnInfo(field, schema);
|
|
13020
13209
|
const fieldName = columnInfo.column || `json_extract(data, '${columnInfo.jsonPath}')`;
|
|
13021
13210
|
const actualFieldName = columnInfo.jsonPath?.replace(/^\$\./, "") || columnInfo.column || field;
|
|
@@ -13529,9 +13718,6 @@ class StatementManager {
|
|
|
13529
13718
|
this.closed = true;
|
|
13530
13719
|
}
|
|
13531
13720
|
isStaticSQL(query) {
|
|
13532
|
-
if (query.includes("WHERE (")) {
|
|
13533
|
-
return false;
|
|
13534
|
-
}
|
|
13535
13721
|
return true;
|
|
13536
13722
|
}
|
|
13537
13723
|
}
|
|
@@ -13594,7 +13780,7 @@ class BunSQLiteStorageInstance {
|
|
|
13594
13780
|
db;
|
|
13595
13781
|
stmtManager;
|
|
13596
13782
|
changeStream$ = new import_rxjs2.Subject;
|
|
13597
|
-
queryCache
|
|
13783
|
+
queryCache;
|
|
13598
13784
|
databaseName;
|
|
13599
13785
|
collectionName;
|
|
13600
13786
|
schema;
|
|
@@ -13602,6 +13788,7 @@ class BunSQLiteStorageInstance {
|
|
|
13602
13788
|
options;
|
|
13603
13789
|
primaryPath;
|
|
13604
13790
|
tableName;
|
|
13791
|
+
useStoredColumns;
|
|
13605
13792
|
closed;
|
|
13606
13793
|
constructor(params, settings = {}) {
|
|
13607
13794
|
ensureRxStorageInstanceParamsAreCorrect(params);
|
|
@@ -13612,9 +13799,11 @@ class BunSQLiteStorageInstance {
|
|
|
13612
13799
|
const primaryKey = params.schema.primaryKey;
|
|
13613
13800
|
this.primaryPath = typeof primaryKey === "string" ? primaryKey : primaryKey.key;
|
|
13614
13801
|
this.tableName = `${params.collectionName}_v${params.schema.version}`;
|
|
13802
|
+
this.useStoredColumns = this.options?.useStoredColumns ?? "virtual";
|
|
13615
13803
|
const filename = settings.filename || ":memory:";
|
|
13616
13804
|
this.db = getDatabase(this.databaseName, filename);
|
|
13617
13805
|
this.stmtManager = new StatementManager(this.db);
|
|
13806
|
+
this.queryCache = getQueryCache(this.db);
|
|
13618
13807
|
this.internals = {
|
|
13619
13808
|
db: this.db,
|
|
13620
13809
|
primaryPath: this.primaryPath
|
|
@@ -13635,15 +13824,37 @@ class BunSQLiteStorageInstance {
|
|
|
13635
13824
|
this.db.run("PRAGMA temp_store = MEMORY");
|
|
13636
13825
|
this.db.run("PRAGMA locking_mode = NORMAL");
|
|
13637
13826
|
}
|
|
13638
|
-
this.
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13827
|
+
if (this.useStoredColumns === "virtual") {
|
|
13828
|
+
this.db.run(`
|
|
13829
|
+
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
13830
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
13831
|
+
data BLOB NOT NULL,
|
|
13832
|
+
deleted INTEGER GENERATED ALWAYS AS (json_extract(data, '$._deleted')) VIRTUAL,
|
|
13833
|
+
rev TEXT GENERATED ALWAYS AS (json_extract(data, '$._rev')) VIRTUAL,
|
|
13834
|
+
mtime_ms REAL GENERATED ALWAYS AS (json_extract(data, '$._meta.lwt')) VIRTUAL
|
|
13835
|
+
)
|
|
13836
|
+
`);
|
|
13837
|
+
} else if (this.useStoredColumns === "stored") {
|
|
13838
|
+
this.db.run(`
|
|
13839
|
+
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
13840
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
13841
|
+
data BLOB NOT NULL,
|
|
13842
|
+
deleted INTEGER GENERATED ALWAYS AS (json_extract(data, '$._deleted')) STORED,
|
|
13843
|
+
rev TEXT GENERATED ALWAYS AS (json_extract(data, '$._rev')) STORED,
|
|
13844
|
+
mtime_ms REAL GENERATED ALWAYS AS (json_extract(data, '$._meta.lwt')) STORED
|
|
13845
|
+
)
|
|
13846
|
+
`);
|
|
13847
|
+
} else {
|
|
13848
|
+
this.db.run(`
|
|
13849
|
+
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
13850
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
13851
|
+
data BLOB NOT NULL,
|
|
13852
|
+
deleted INTEGER NOT NULL DEFAULT 0,
|
|
13853
|
+
rev TEXT NOT NULL,
|
|
13854
|
+
mtime_ms REAL NOT NULL
|
|
13855
|
+
)
|
|
13856
|
+
`);
|
|
13857
|
+
}
|
|
13647
13858
|
this.db.run(`CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_deleted_id" ON "${this.tableName}"(deleted, id)`);
|
|
13648
13859
|
this.db.run(`CREATE INDEX IF NOT EXISTS "idx_${this.tableName}_mtime_ms_id" ON "${this.tableName}"(mtime_ms, id)`);
|
|
13649
13860
|
if (this.schema.indexes) {
|
|
@@ -13677,49 +13888,70 @@ class BunSQLiteStorageInstance {
|
|
|
13677
13888
|
return { error: [] };
|
|
13678
13889
|
}
|
|
13679
13890
|
const ids = documentWrites.map((w) => w.document[this.primaryPath]);
|
|
13680
|
-
const
|
|
13891
|
+
const LOOKUP_BATCH_SIZE = 500;
|
|
13892
|
+
const docsInDb = [];
|
|
13893
|
+
for (let i = 0;i < ids.length; i += LOOKUP_BATCH_SIZE) {
|
|
13894
|
+
const batch = ids.slice(i, i + LOOKUP_BATCH_SIZE);
|
|
13895
|
+
const batchDocs = await this.findDocumentsById(batch, true);
|
|
13896
|
+
docsInDb.push(...batchDocs);
|
|
13897
|
+
}
|
|
13681
13898
|
const docsInDbMap = new Map(docsInDb.map((d) => [d[this.primaryPath], d]));
|
|
13682
13899
|
const categorized = categorizeBulkWriteRows(this, this.primaryPath, docsInDbMap, documentWrites, context);
|
|
13683
|
-
const
|
|
13684
|
-
const
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
const
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13900
|
+
const CHUNK_SIZE = 50;
|
|
13901
|
+
const updateStmt = this.useStoredColumns ? this.db.prepare(`UPDATE "${this.tableName}" SET data = jsonb(?) WHERE id = ?`) : this.db.prepare(`UPDATE "${this.tableName}" SET data = jsonb(?), deleted = ?, rev = ?, mtime_ms = ? WHERE id = ?`);
|
|
13902
|
+
const insertBatch = this.db.transaction((docs) => {
|
|
13903
|
+
for (let i = 0;i < docs.length; i += CHUNK_SIZE) {
|
|
13904
|
+
const chunk = docs.slice(i, i + CHUNK_SIZE);
|
|
13905
|
+
const placeholders = this.useStoredColumns ? chunk.map(() => "(?, jsonb(?))").join(", ") : chunk.map(() => "(?, jsonb(?), ?, ?, ?)").join(", ");
|
|
13906
|
+
const insertQuery = this.useStoredColumns ? `INSERT INTO "${this.tableName}" (id, data) VALUES ${placeholders}` : `INSERT INTO "${this.tableName}" (id, data, deleted, rev, mtime_ms) VALUES ${placeholders}`;
|
|
13907
|
+
const params = [];
|
|
13908
|
+
for (const row of chunk) {
|
|
13909
|
+
const doc = row.document;
|
|
13910
|
+
const id = doc[this.primaryPath];
|
|
13911
|
+
if (this.useStoredColumns) {
|
|
13912
|
+
params.push(id, JSON.stringify(doc));
|
|
13913
|
+
} else {
|
|
13914
|
+
params.push(id, JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt);
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
try {
|
|
13918
|
+
this.stmtManager.run({ query: insertQuery, params });
|
|
13919
|
+
} catch (err) {
|
|
13920
|
+
if (err && typeof err === "object" && "code" in err && (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || err.code === "SQLITE_CONSTRAINT_UNIQUE")) {
|
|
13921
|
+
for (const row of chunk) {
|
|
13922
|
+
const doc = row.document;
|
|
13923
|
+
const id = doc[this.primaryPath];
|
|
13924
|
+
const documentInDb = docsInDbMap.get(id);
|
|
13925
|
+
categorized.errors.push({
|
|
13926
|
+
isError: true,
|
|
13927
|
+
status: 409,
|
|
13928
|
+
documentId: id,
|
|
13929
|
+
writeRow: row,
|
|
13930
|
+
documentInDb: documentInDb || doc
|
|
13931
|
+
});
|
|
13932
|
+
}
|
|
13933
|
+
} else {
|
|
13934
|
+
throw err;
|
|
13710
13935
|
}
|
|
13711
|
-
} else {
|
|
13712
|
-
throw err;
|
|
13713
13936
|
}
|
|
13714
13937
|
}
|
|
13715
|
-
}
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
for (const row of batch) {
|
|
13938
|
+
});
|
|
13939
|
+
const updateBatch = this.db.transaction((docs) => {
|
|
13940
|
+
for (const row of docs) {
|
|
13719
13941
|
const doc = row.document;
|
|
13720
13942
|
const id = doc[this.primaryPath];
|
|
13721
|
-
this.
|
|
13943
|
+
if (this.useStoredColumns) {
|
|
13944
|
+
updateStmt.run(JSON.stringify(doc), id);
|
|
13945
|
+
} else {
|
|
13946
|
+
updateStmt.run(JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt, id);
|
|
13947
|
+
}
|
|
13722
13948
|
}
|
|
13949
|
+
});
|
|
13950
|
+
if (categorized.bulkInsertDocs.length > 0) {
|
|
13951
|
+
insertBatch(categorized.bulkInsertDocs);
|
|
13952
|
+
}
|
|
13953
|
+
if (categorized.bulkUpdateDocs.length > 0) {
|
|
13954
|
+
updateBatch(categorized.bulkUpdateDocs);
|
|
13723
13955
|
}
|
|
13724
13956
|
const insertAttQuery = `INSERT OR REPLACE INTO "${this.tableName}_attachments" (id, data, digest) VALUES (?, ?, ?)`;
|
|
13725
13957
|
const deleteAttQuery = `DELETE FROM "${this.tableName}_attachments" WHERE id = ?`;
|
|
@@ -13762,31 +13994,37 @@ class BunSQLiteStorageInstance {
|
|
|
13762
13994
|
return rows.map((row) => JSON.parse(row.data));
|
|
13763
13995
|
}
|
|
13764
13996
|
async query(preparedQuery) {
|
|
13765
|
-
const
|
|
13766
|
-
|
|
13767
|
-
|
|
13997
|
+
const { sqlWhere, jsSelector } = buildWhereClauseWithFallback(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13998
|
+
let sql = `SELECT json(data) as data FROM "${this.tableName}"`;
|
|
13999
|
+
const queryArgs = [];
|
|
14000
|
+
if (sqlWhere) {
|
|
14001
|
+
sql += ` WHERE (${sqlWhere.sql})`;
|
|
14002
|
+
queryArgs.push(...sqlWhere.args);
|
|
13768
14003
|
}
|
|
13769
|
-
const { sql: whereClause, args } = whereResult;
|
|
13770
|
-
let sql = `SELECT json(data) as data FROM "${this.tableName}" WHERE (${whereClause})`;
|
|
13771
|
-
const queryArgs = [...args];
|
|
13772
14004
|
if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
|
|
13773
14005
|
const orderBy = preparedQuery.query.sort.map((sortField) => {
|
|
13774
14006
|
const [field, direction] = Object.entries(sortField)[0];
|
|
13775
14007
|
const dir = direction === "asc" ? "ASC" : "DESC";
|
|
13776
|
-
|
|
14008
|
+
const colInfo = getColumnInfo(field, this.schema);
|
|
14009
|
+
const colName = colInfo.column || `json_extract(data, '${colInfo.jsonPath}')`;
|
|
14010
|
+
return `${colName} ${dir}`;
|
|
13777
14011
|
}).join(", ");
|
|
13778
14012
|
sql += ` ORDER BY ${orderBy}`;
|
|
13779
14013
|
}
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
14014
|
+
const skip = preparedQuery.query.skip || 0;
|
|
14015
|
+
const limit = preparedQuery.query.limit;
|
|
14016
|
+
if (!jsSelector) {
|
|
14017
|
+
if (limit !== undefined) {
|
|
14018
|
+
sql += ` LIMIT ?`;
|
|
14019
|
+
queryArgs.push(limit);
|
|
14020
|
+
}
|
|
14021
|
+
if (skip > 0) {
|
|
14022
|
+
if (limit === undefined) {
|
|
14023
|
+
sql += ` LIMIT -1`;
|
|
14024
|
+
}
|
|
14025
|
+
sql += ` OFFSET ?`;
|
|
14026
|
+
queryArgs.push(skip);
|
|
13787
14027
|
}
|
|
13788
|
-
sql += ` OFFSET ?`;
|
|
13789
|
-
queryArgs.push(preparedQuery.query.skip);
|
|
13790
14028
|
}
|
|
13791
14029
|
if (process.env.DEBUG_QUERIES) {
|
|
13792
14030
|
const explainSql = `EXPLAIN QUERY PLAN ${sql}`;
|
|
@@ -13795,9 +14033,29 @@ class BunSQLiteStorageInstance {
|
|
|
13795
14033
|
console.log("[DEBUG_QUERIES] SQL:", sql);
|
|
13796
14034
|
console.log("[DEBUG_QUERIES] Args:", queryArgs);
|
|
13797
14035
|
}
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
14036
|
+
if (jsSelector) {
|
|
14037
|
+
const stmt = this.db.prepare(sql);
|
|
14038
|
+
const documents = [];
|
|
14039
|
+
let skipped = 0;
|
|
14040
|
+
for (const row of stmt.all(...queryArgs)) {
|
|
14041
|
+
const doc = JSON.parse(row.data);
|
|
14042
|
+
if (matchesSelector(doc, jsSelector)) {
|
|
14043
|
+
if (skip > 0 && skipped < skip) {
|
|
14044
|
+
skipped++;
|
|
14045
|
+
continue;
|
|
14046
|
+
}
|
|
14047
|
+
documents.push(doc);
|
|
14048
|
+
if (limit !== undefined && documents.length >= limit) {
|
|
14049
|
+
break;
|
|
14050
|
+
}
|
|
14051
|
+
}
|
|
14052
|
+
}
|
|
14053
|
+
return { documents };
|
|
14054
|
+
} else {
|
|
14055
|
+
const rows = this.stmtManager.all({ query: sql, params: queryArgs });
|
|
14056
|
+
const documents = rows.map((row) => JSON.parse(row.data));
|
|
14057
|
+
return { documents };
|
|
14058
|
+
}
|
|
13801
14059
|
}
|
|
13802
14060
|
sortDocuments(docs, sort) {
|
|
13803
14061
|
return docs.sort((a, b) => {
|
|
@@ -13819,7 +14077,7 @@ class BunSQLiteStorageInstance {
|
|
|
13819
14077
|
async count(preparedQuery) {
|
|
13820
14078
|
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13821
14079
|
if (!whereResult) {
|
|
13822
|
-
const allDocs =
|
|
14080
|
+
const allDocs = this.queryWithOurMemory(preparedQuery);
|
|
13823
14081
|
return {
|
|
13824
14082
|
count: allDocs.documents.length,
|
|
13825
14083
|
mode: "fast"
|
|
@@ -13852,7 +14110,6 @@ class BunSQLiteStorageInstance {
|
|
|
13852
14110
|
if (this.closed)
|
|
13853
14111
|
return this.closed;
|
|
13854
14112
|
this.closed = (async () => {
|
|
13855
|
-
this.queryCache.clear();
|
|
13856
14113
|
this.changeStream$.complete();
|
|
13857
14114
|
this.stmtManager.close();
|
|
13858
14115
|
releaseDatabase(this.databaseName);
|
|
@@ -13900,21 +14157,16 @@ class BunSQLiteStorageInstance {
|
|
|
13900
14157
|
return { documents, checkpoint: newCheckpoint };
|
|
13901
14158
|
}
|
|
13902
14159
|
queryWithOurMemory(preparedQuery) {
|
|
13903
|
-
|
|
14160
|
+
let query = `SELECT json(data) as data FROM "${this.tableName}"`;
|
|
13904
14161
|
const selector = preparedQuery.query.selector;
|
|
13905
14162
|
const hasSort = preparedQuery.query.sort && preparedQuery.query.sort.length > 0;
|
|
13906
14163
|
if (hasSort) {
|
|
13907
|
-
const
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
}
|
|
13914
|
-
if (preparedQuery.query.limit) {
|
|
13915
|
-
documents2 = documents2.slice(0, preparedQuery.query.limit);
|
|
13916
|
-
}
|
|
13917
|
-
return { documents: documents2 };
|
|
14164
|
+
const orderBy = preparedQuery.query.sort.map((sortField) => {
|
|
14165
|
+
const [field, direction] = Object.entries(sortField)[0];
|
|
14166
|
+
const dir = direction === "asc" ? "ASC" : "DESC";
|
|
14167
|
+
return `json_extract(data, '$.${field}') ${dir}`;
|
|
14168
|
+
}).join(", ");
|
|
14169
|
+
query += ` ORDER BY ${orderBy}`;
|
|
13918
14170
|
}
|
|
13919
14171
|
const stmt = this.db.prepare(query);
|
|
13920
14172
|
const documents = [];
|
package/dist/src/instance.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class BunSQLiteStorageInstance<RxDocType> implements RxStorageIns
|
|
|
13
13
|
readonly options: Readonly<BunSQLiteStorageSettings>;
|
|
14
14
|
private primaryPath;
|
|
15
15
|
private tableName;
|
|
16
|
+
private useStoredColumns;
|
|
16
17
|
closed?: Promise<void>;
|
|
17
18
|
constructor(params: RxStorageInstanceCreationParams<RxDocType, BunSQLiteStorageSettings>, settings?: BunSQLiteStorageSettings);
|
|
18
19
|
private initTable;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { RxJsonSchema, MangoQuerySelector, RxDocumentData } from 'rxdb';
|
|
2
2
|
import type { SqlFragment } from './operators';
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export
|
|
3
|
+
import type { SieveCache } from './sieve-cache';
|
|
4
|
+
export { getCacheSize, clearCache } from './cache';
|
|
5
|
+
export interface BipartiteQuery<RxDocType> {
|
|
6
|
+
sqlWhere: SqlFragment | null;
|
|
7
|
+
jsSelector: MangoQuerySelector<RxDocumentData<RxDocType>> | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildWhereClause<RxDocType>(selector: MangoQuerySelector<RxDocumentData<RxDocType>>, schema: RxJsonSchema<RxDocumentData<RxDocType>>, collectionName: string, cache?: Map<string, SqlFragment | null> | SieveCache<string, SqlFragment | null>): SqlFragment | null;
|
|
10
|
+
export declare function buildWhereClauseWithFallback<RxDocType>(selector: MangoQuerySelector<RxDocumentData<RxDocType>>, schema: RxJsonSchema<RxDocumentData<RxDocType>>, collectionName: string, cache?: Map<string, SqlFragment | null> | SieveCache<string, SqlFragment | null>): BipartiteQuery<RxDocType>;
|
|
6
11
|
export declare function buildLogicalOperator<RxDocType>(operator: 'or' | 'nor' | 'and', conditions: MangoQuerySelector<RxDocumentData<RxDocType>>[], schema: RxJsonSchema<RxDocumentData<RxDocType>>, logicalDepth: number): SqlFragment | null;
|
|
7
12
|
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type { SqlFragment } from './operators';
|
|
3
|
+
import { SieveCache } from './sieve-cache';
|
|
4
|
+
export declare const MAX_QUERY_CACHE_SIZE = 5000;
|
|
5
|
+
export declare const MAX_REGEX_CACHE_SIZE = 100;
|
|
6
|
+
export declare const MAX_INDEX_CACHE_SIZE = 1000;
|
|
7
|
+
export declare function getQueryCache(database: Database): SieveCache<string, SqlFragment | null>;
|
|
8
|
+
export declare function getGlobalCache(): SieveCache<string, SqlFragment | null>;
|
|
9
|
+
export declare function getCacheSize(): number;
|
|
10
|
+
export declare function clearCache(): void;
|
|
11
|
+
export interface RegexCacheEntry {
|
|
12
|
+
regex: RegExp;
|
|
13
|
+
}
|
|
14
|
+
export declare function getRegexCache(): SieveCache<string, RegexCacheEntry>;
|
|
15
|
+
export declare function clearRegexCache(): void;
|
|
16
|
+
export declare function getIndexCache(): SieveCache<string, boolean>;
|
|
17
|
+
export declare function clearIndexCache(): void;
|
|
18
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SqlFragment } from './operators';
|
|
2
|
+
export declare class NoOpCache<K, V> {
|
|
3
|
+
get size(): number;
|
|
4
|
+
has(_key: K): boolean;
|
|
5
|
+
get(_key: K): V | undefined;
|
|
6
|
+
set(_key: K, _value: V): this;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const NO_CACHE: NoOpCache<string, SqlFragment | null>;
|
|
10
|
+
//# sourceMappingURL=no-op-cache.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class SieveCache<K, V> {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(capacity: number);
|
|
4
|
+
get size(): number;
|
|
5
|
+
has(key: K): boolean;
|
|
6
|
+
get(key: K): V | undefined;
|
|
7
|
+
set(key: K, value: V): this;
|
|
8
|
+
delete(key: K): boolean;
|
|
9
|
+
clear(): void;
|
|
10
|
+
forEach(callbackfn: (value: V, key: K, map: SieveCache<K, V>) => void, thisArg?: any): void;
|
|
11
|
+
entries(): IterableIterator<[K, V]>;
|
|
12
|
+
keys(): IterableIterator<K>;
|
|
13
|
+
values(): IterableIterator<V>;
|
|
14
|
+
[Symbol.iterator](): IterableIterator<[K, V]>;
|
|
15
|
+
get [Symbol.toStringTag](): string;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=sieve-cache.d.ts.map
|
|
@@ -3,6 +3,5 @@ export interface SqlFragment {
|
|
|
3
3
|
sql: string;
|
|
4
4
|
args: (string | number | boolean)[];
|
|
5
5
|
}
|
|
6
|
-
export declare function clearRegexCache(): void;
|
|
7
6
|
export declare function smartRegexToLike<RxDocType>(field: string, pattern: string, options: string | undefined, schema: RxJsonSchema<RxDocumentData<RxDocType>>, fieldName: string): SqlFragment | null;
|
|
8
7
|
//# sourceMappingURL=smart-regex.d.ts.map
|
package/dist/src/types.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export interface BunSQLiteStorageSettings {
|
|
|
12
12
|
* @default 268435456 (256MB)
|
|
13
13
|
*/
|
|
14
14
|
mmapSize?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Use generated columns for _deleted and _meta.lwt fields.
|
|
17
|
+
* - false: Regular columns with manual extraction (baseline)
|
|
18
|
+
* - 'virtual': VIRTUAL generated columns (computed on-the-fly, no storage overhead) - RECOMMENDED
|
|
19
|
+
* - 'stored': STORED generated columns (pre-computed, +11% storage, 58% faster queries)
|
|
20
|
+
* Requires SQLite 3.31.0+ (Bun 1.0+ includes SQLite 3.42+).
|
|
21
|
+
* @default 'virtual'
|
|
22
|
+
* @experimental Alpha feature - opt-in for testing
|
|
23
|
+
*/
|
|
24
|
+
useStoredColumns?: false | 'virtual' | 'stored';
|
|
15
25
|
}
|
|
16
26
|
export interface BunSQLiteInternals {
|
|
17
27
|
db: Database;
|