bun-sqlite-for-rxdb 1.1.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +374 -0
- package/dist/index.js +607 -267
- package/dist/instance.d.ts +1 -0
- package/dist/query/builder.d.ts +2 -1
- package/dist/query/operators.d.ts +12 -5
- package/dist/query/regex-matcher.d.ts +2 -0
- package/dist/query/smart-regex.d.ts +2 -1
- package/dist/src/connection-pool.d.ts +4 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/instance.d.ts +44 -0
- package/dist/src/query/builder.d.ts +6 -0
- package/dist/src/query/operators.d.ts +27 -0
- package/dist/src/query/regex-matcher.d.ts +2 -0
- package/dist/src/query/schema-mapper.d.ts +8 -0
- package/dist/src/query/smart-regex.d.ts +7 -0
- package/dist/src/rxdb-helpers.d.ts +28 -0
- package/dist/src/statement-manager.d.ts +20 -0
- package/dist/src/storage.d.ts +4 -0
- package/dist/src/transaction-queue.d.ts +3 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/utils/stable-stringify.d.ts +16 -0
- package/package.json +8 -7
- package/dist/connection-pool.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/instance.d.ts.map +0 -1
- package/dist/query/builder.d.ts.map +0 -1
- package/dist/query/operators.d.ts.map +0 -1
- package/dist/query/schema-mapper.d.ts.map +0 -1
- package/dist/query/smart-regex.d.ts.map +0 -1
- package/dist/rxdb-helpers.d.ts.map +0 -1
- package/dist/statement-manager.d.ts.map +0 -1
- package/dist/storage.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -10341,80 +10341,6 @@ var require_dist = __commonJS((exports) => {
|
|
|
10341
10341
|
exports.default = PQueue;
|
|
10342
10342
|
});
|
|
10343
10343
|
|
|
10344
|
-
// node_modules/fast-stable-stringify/index.js
|
|
10345
|
-
var require_fast_stable_stringify = __commonJS((exports, module) => {
|
|
10346
|
-
var objToString = Object.prototype.toString;
|
|
10347
|
-
var objKeys = Object.keys || function(obj) {
|
|
10348
|
-
var keys = [];
|
|
10349
|
-
for (var name in obj) {
|
|
10350
|
-
keys.push(name);
|
|
10351
|
-
}
|
|
10352
|
-
return keys;
|
|
10353
|
-
};
|
|
10354
|
-
function stringify(val, isArrayProp) {
|
|
10355
|
-
var i, max, str, keys, key, propVal, toStr;
|
|
10356
|
-
if (val === true) {
|
|
10357
|
-
return "true";
|
|
10358
|
-
}
|
|
10359
|
-
if (val === false) {
|
|
10360
|
-
return "false";
|
|
10361
|
-
}
|
|
10362
|
-
switch (typeof val) {
|
|
10363
|
-
case "object":
|
|
10364
|
-
if (val === null) {
|
|
10365
|
-
return null;
|
|
10366
|
-
} else if (val.toJSON && typeof val.toJSON === "function") {
|
|
10367
|
-
return stringify(val.toJSON(), isArrayProp);
|
|
10368
|
-
} else {
|
|
10369
|
-
toStr = objToString.call(val);
|
|
10370
|
-
if (toStr === "[object Array]") {
|
|
10371
|
-
str = "[";
|
|
10372
|
-
max = val.length - 1;
|
|
10373
|
-
for (i = 0;i < max; i++) {
|
|
10374
|
-
str += stringify(val[i], true) + ",";
|
|
10375
|
-
}
|
|
10376
|
-
if (max > -1) {
|
|
10377
|
-
str += stringify(val[i], true);
|
|
10378
|
-
}
|
|
10379
|
-
return str + "]";
|
|
10380
|
-
} else if (toStr === "[object Object]") {
|
|
10381
|
-
keys = objKeys(val).sort();
|
|
10382
|
-
max = keys.length;
|
|
10383
|
-
str = "";
|
|
10384
|
-
i = 0;
|
|
10385
|
-
while (i < max) {
|
|
10386
|
-
key = keys[i];
|
|
10387
|
-
propVal = stringify(val[key], false);
|
|
10388
|
-
if (propVal !== undefined) {
|
|
10389
|
-
if (str) {
|
|
10390
|
-
str += ",";
|
|
10391
|
-
}
|
|
10392
|
-
str += JSON.stringify(key) + ":" + propVal;
|
|
10393
|
-
}
|
|
10394
|
-
i++;
|
|
10395
|
-
}
|
|
10396
|
-
return "{" + str + "}";
|
|
10397
|
-
} else {
|
|
10398
|
-
return JSON.stringify(val);
|
|
10399
|
-
}
|
|
10400
|
-
}
|
|
10401
|
-
case "function":
|
|
10402
|
-
case "undefined":
|
|
10403
|
-
return isArrayProp ? null : undefined;
|
|
10404
|
-
case "string":
|
|
10405
|
-
return JSON.stringify(val);
|
|
10406
|
-
default:
|
|
10407
|
-
return isFinite(val) ? val : null;
|
|
10408
|
-
}
|
|
10409
|
-
}
|
|
10410
|
-
module.exports = function(val) {
|
|
10411
|
-
var returnVal = stringify(val, false);
|
|
10412
|
-
if (returnVal !== undefined) {
|
|
10413
|
-
return "" + returnVal;
|
|
10414
|
-
}
|
|
10415
|
-
};
|
|
10416
|
-
});
|
|
10417
|
-
|
|
10418
10344
|
// node_modules/rxdb/dist/esm/rx-storage-multiinstance.js
|
|
10419
10345
|
var import_rxjs = __toESM(require_cjs(), 1);
|
|
10420
10346
|
var import_operators = __toESM(require_operators(), 1);
|
|
@@ -12284,6 +12210,36 @@ function addRxStorageMultiInstanceSupport(storageName, instanceCreationParams, i
|
|
|
12284
12210
|
// src/instance.ts
|
|
12285
12211
|
var import_rxjs2 = __toESM(require_cjs(), 1);
|
|
12286
12212
|
|
|
12213
|
+
// src/query/regex-matcher.ts
|
|
12214
|
+
var REGEX_CACHE = new Map;
|
|
12215
|
+
var MAX_REGEX_CACHE_SIZE = 100;
|
|
12216
|
+
function compileRegex(pattern, options) {
|
|
12217
|
+
const cacheKey = `${pattern}::${options || ""}`;
|
|
12218
|
+
const cached = REGEX_CACHE.get(cacheKey);
|
|
12219
|
+
if (cached) {
|
|
12220
|
+
return cached.regex;
|
|
12221
|
+
}
|
|
12222
|
+
const regex = new RegExp(pattern, options);
|
|
12223
|
+
if (REGEX_CACHE.size >= MAX_REGEX_CACHE_SIZE) {
|
|
12224
|
+
const firstKey = REGEX_CACHE.keys().next().value;
|
|
12225
|
+
if (firstKey) {
|
|
12226
|
+
REGEX_CACHE.delete(firstKey);
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
REGEX_CACHE.set(cacheKey, { regex });
|
|
12230
|
+
return regex;
|
|
12231
|
+
}
|
|
12232
|
+
function matchesRegex(value, pattern, options) {
|
|
12233
|
+
const regex = compileRegex(pattern, options);
|
|
12234
|
+
if (typeof value === "string") {
|
|
12235
|
+
return regex.test(value);
|
|
12236
|
+
}
|
|
12237
|
+
if (Array.isArray(value)) {
|
|
12238
|
+
return value.some((v) => typeof v === "string" && regex.test(v));
|
|
12239
|
+
}
|
|
12240
|
+
return false;
|
|
12241
|
+
}
|
|
12242
|
+
|
|
12287
12243
|
// src/query/schema-mapper.ts
|
|
12288
12244
|
function getColumnInfo(path2, schema) {
|
|
12289
12245
|
if (path2 === "_deleted") {
|
|
@@ -12302,39 +12258,87 @@ function getColumnInfo(path2, schema) {
|
|
|
12302
12258
|
}
|
|
12303
12259
|
|
|
12304
12260
|
// src/query/smart-regex.ts
|
|
12305
|
-
|
|
12306
|
-
|
|
12261
|
+
var INDEX_CACHE = new Map;
|
|
12262
|
+
var MAX_INDEX_CACHE_SIZE = 1000;
|
|
12263
|
+
function hasExpressionIndex(fieldName, schema) {
|
|
12264
|
+
const indexKey = schema.indexes ? JSON.stringify(schema.indexes) : "none";
|
|
12265
|
+
const cacheKey = `${schema.version}_${fieldName}_${indexKey}`;
|
|
12266
|
+
const cached = INDEX_CACHE.get(cacheKey);
|
|
12267
|
+
if (cached !== undefined) {
|
|
12268
|
+
INDEX_CACHE.delete(cacheKey);
|
|
12269
|
+
INDEX_CACHE.set(cacheKey, cached);
|
|
12270
|
+
return cached;
|
|
12271
|
+
}
|
|
12272
|
+
if (!schema.indexes) {
|
|
12273
|
+
if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
|
|
12274
|
+
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12275
|
+
if (firstKey)
|
|
12276
|
+
INDEX_CACHE.delete(firstKey);
|
|
12277
|
+
}
|
|
12278
|
+
INDEX_CACHE.set(cacheKey, false);
|
|
12279
|
+
return false;
|
|
12280
|
+
}
|
|
12281
|
+
const hasLowerIndex = schema.indexes.some((idx) => {
|
|
12282
|
+
const fields = Array.isArray(idx) ? idx : [idx];
|
|
12283
|
+
return fields.some((f) => {
|
|
12284
|
+
if (typeof f !== "string")
|
|
12285
|
+
return false;
|
|
12286
|
+
const normalized = f.toLowerCase().replace(/\s/g, "");
|
|
12287
|
+
return normalized === `lower(${fieldName})`;
|
|
12288
|
+
});
|
|
12289
|
+
});
|
|
12290
|
+
if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
|
|
12291
|
+
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12292
|
+
if (firstKey)
|
|
12293
|
+
INDEX_CACHE.delete(firstKey);
|
|
12294
|
+
}
|
|
12295
|
+
INDEX_CACHE.set(cacheKey, hasLowerIndex);
|
|
12296
|
+
return hasLowerIndex;
|
|
12297
|
+
}
|
|
12298
|
+
function isComplexRegex(pattern) {
|
|
12299
|
+
return /[*+?()[\]{}|]/.test(pattern.replace(/\\\./g, ""));
|
|
12300
|
+
}
|
|
12301
|
+
function escapeForLike(str) {
|
|
12302
|
+
return str.replace(/[\\%_]/g, "\\$&");
|
|
12303
|
+
}
|
|
12304
|
+
function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
12305
|
+
if (typeof pattern !== "string")
|
|
12306
|
+
return null;
|
|
12307
|
+
const caseInsensitive = options?.includes("i") ?? false;
|
|
12308
|
+
const hasLowerIndex = hasExpressionIndex(fieldName, schema);
|
|
12307
12309
|
const startsWithAnchor = pattern.startsWith("^");
|
|
12308
12310
|
const endsWithAnchor = pattern.endsWith("$");
|
|
12309
12311
|
let cleanPattern = pattern.replace(/^\^/, "").replace(/\$$/, "");
|
|
12310
|
-
if (
|
|
12311
|
-
|
|
12312
|
+
if (isComplexRegex(cleanPattern)) {
|
|
12313
|
+
return null;
|
|
12314
|
+
}
|
|
12315
|
+
const unescaped = cleanPattern.replace(/\\\./g, ".");
|
|
12316
|
+
const escaped = escapeForLike(unescaped);
|
|
12317
|
+
if (startsWithAnchor && endsWithAnchor) {
|
|
12312
12318
|
if (caseInsensitive) {
|
|
12313
|
-
|
|
12314
|
-
return { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped] };
|
|
12319
|
+
return hasLowerIndex ? { sql: `LOWER(${field}) = ?`, args: [unescaped.toLowerCase()] } : { sql: `${field} = ? COLLATE NOCASE`, args: [unescaped] };
|
|
12315
12320
|
}
|
|
12316
|
-
return { sql: `${field} = ?`, args: [
|
|
12321
|
+
return { sql: `${field} = ?`, args: [unescaped] };
|
|
12317
12322
|
}
|
|
12318
12323
|
if (startsWithAnchor) {
|
|
12319
|
-
const
|
|
12320
|
-
if (
|
|
12321
|
-
|
|
12322
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: [escaped + "%"] };
|
|
12324
|
+
const suffix = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12325
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12326
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: [suffix + "%"] };
|
|
12323
12327
|
}
|
|
12328
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: [suffix + "%"] };
|
|
12324
12329
|
}
|
|
12325
12330
|
if (endsWithAnchor) {
|
|
12326
|
-
const
|
|
12327
|
-
if (
|
|
12328
|
-
|
|
12329
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped] };
|
|
12331
|
+
const prefix = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12332
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12333
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + prefix] };
|
|
12330
12334
|
}
|
|
12335
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + prefix] };
|
|
12331
12336
|
}
|
|
12332
|
-
|
|
12333
|
-
if (
|
|
12334
|
-
|
|
12335
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped + "%"] };
|
|
12337
|
+
const middle = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12338
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12339
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + middle + "%"] };
|
|
12336
12340
|
}
|
|
12337
|
-
return
|
|
12341
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + middle + "%"] };
|
|
12338
12342
|
}
|
|
12339
12343
|
|
|
12340
12344
|
// src/query/operators.ts
|
|
@@ -12406,40 +12410,82 @@ function translateExists(field, exists) {
|
|
|
12406
12410
|
args: []
|
|
12407
12411
|
};
|
|
12408
12412
|
}
|
|
12409
|
-
function translateRegex(field, pattern, options) {
|
|
12410
|
-
const smartResult = smartRegexToLike(field, pattern, options);
|
|
12413
|
+
function translateRegex(field, pattern, options, schema, fieldName) {
|
|
12414
|
+
const smartResult = smartRegexToLike(field, pattern, options, schema, fieldName);
|
|
12411
12415
|
if (smartResult)
|
|
12412
12416
|
return smartResult;
|
|
12413
12417
|
return null;
|
|
12414
12418
|
}
|
|
12415
|
-
function
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12419
|
+
function buildElemMatchConditions(criteria) {
|
|
12420
|
+
const conditions = [];
|
|
12421
|
+
const args = [];
|
|
12422
|
+
for (const [key, value] of Object.entries(criteria)) {
|
|
12423
|
+
if (key.startsWith("$")) {
|
|
12424
|
+
const fragment = processOperatorValue("json_each.value", { [key]: value });
|
|
12425
|
+
conditions.push(fragment.sql);
|
|
12426
|
+
args.push(...fragment.args);
|
|
12427
|
+
} else {
|
|
12428
|
+
const propertyField = `json_extract(json_each.value, '$.${key}')`;
|
|
12429
|
+
const fragment = processOperatorValue(propertyField, value);
|
|
12430
|
+
conditions.push(fragment.sql);
|
|
12431
|
+
args.push(...fragment.args);
|
|
12432
|
+
}
|
|
12433
|
+
}
|
|
12420
12434
|
return {
|
|
12421
|
-
sql:
|
|
12422
|
-
args
|
|
12435
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
12436
|
+
args
|
|
12423
12437
|
};
|
|
12424
12438
|
}
|
|
12425
|
-
function
|
|
12426
|
-
if (
|
|
12427
|
-
return {
|
|
12439
|
+
function translateElemMatch(field, criteria) {
|
|
12440
|
+
if (typeof criteria !== "object" || criteria === null) {
|
|
12441
|
+
return {
|
|
12442
|
+
sql: `EXISTS (SELECT 1 FROM json_each(${field}) WHERE json_each.value = ?)`,
|
|
12443
|
+
args: [criteria]
|
|
12444
|
+
};
|
|
12445
|
+
}
|
|
12446
|
+
if (criteria.$and && Array.isArray(criteria.$and)) {
|
|
12447
|
+
const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond));
|
|
12448
|
+
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12449
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12450
|
+
return {
|
|
12451
|
+
sql: `EXISTS (SELECT 1 FROM json_each(${field}) WHERE ${sql})`,
|
|
12452
|
+
args
|
|
12453
|
+
};
|
|
12454
|
+
}
|
|
12455
|
+
if (criteria.$or && Array.isArray(criteria.$or)) {
|
|
12456
|
+
const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond));
|
|
12457
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12458
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12459
|
+
return {
|
|
12460
|
+
sql: `EXISTS (SELECT 1 FROM json_each(${field}) WHERE ${sql})`,
|
|
12461
|
+
args
|
|
12462
|
+
};
|
|
12463
|
+
}
|
|
12464
|
+
if (criteria.$nor && Array.isArray(criteria.$nor)) {
|
|
12465
|
+
const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond));
|
|
12466
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12467
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12468
|
+
return {
|
|
12469
|
+
sql: `EXISTS (SELECT 1 FROM json_each(${field}) WHERE NOT (${sql}))`,
|
|
12470
|
+
args
|
|
12471
|
+
};
|
|
12472
|
+
}
|
|
12473
|
+
const fragment = buildElemMatchConditions(criteria);
|
|
12474
|
+
if (fragment.sql === "1=1") {
|
|
12475
|
+
return null;
|
|
12428
12476
|
}
|
|
12429
|
-
const fragments = conditions.map((condition) => {
|
|
12430
|
-
const [[field, value]] = Object.entries(condition);
|
|
12431
|
-
return processOperatorValue(field, value);
|
|
12432
|
-
});
|
|
12433
|
-
const sql = fragments.map((f) => `(${f.sql})`).join(" OR ");
|
|
12434
|
-
const args = fragments.flatMap((f) => f.args);
|
|
12435
12477
|
return {
|
|
12436
|
-
sql: `
|
|
12437
|
-
args
|
|
12478
|
+
sql: `EXISTS (SELECT 1 FROM json_each(${field}) WHERE ${fragment.sql})`,
|
|
12479
|
+
args: fragment.args
|
|
12438
12480
|
};
|
|
12439
12481
|
}
|
|
12440
12482
|
function processOperatorValue(field, value) {
|
|
12441
12483
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12442
12484
|
const [[op, opValue]] = Object.entries(value);
|
|
12485
|
+
if (!op.startsWith("$")) {
|
|
12486
|
+
const jsonPath = `json_extract(${field}, '$.${op}')`;
|
|
12487
|
+
return translateEq(jsonPath, opValue);
|
|
12488
|
+
}
|
|
12443
12489
|
switch (op) {
|
|
12444
12490
|
case "$eq":
|
|
12445
12491
|
return translateEq(field, opValue);
|
|
@@ -12457,27 +12503,68 @@ function processOperatorValue(field, value) {
|
|
|
12457
12503
|
return translateIn(field, opValue);
|
|
12458
12504
|
case "$nin":
|
|
12459
12505
|
return translateNin(field, opValue);
|
|
12506
|
+
case "$exists":
|
|
12507
|
+
return translateExists(field, opValue);
|
|
12508
|
+
case "$size":
|
|
12509
|
+
return translateSize(field, opValue);
|
|
12510
|
+
case "$mod": {
|
|
12511
|
+
const result = translateMod(field, opValue);
|
|
12512
|
+
if (!result)
|
|
12513
|
+
return translateEq(field, opValue);
|
|
12514
|
+
return result;
|
|
12515
|
+
}
|
|
12516
|
+
case "$not": {
|
|
12517
|
+
const result = translateNot(field, opValue);
|
|
12518
|
+
if (!result)
|
|
12519
|
+
return translateEq(field, opValue);
|
|
12520
|
+
return result;
|
|
12521
|
+
}
|
|
12522
|
+
case "$and": {
|
|
12523
|
+
if (!Array.isArray(opValue))
|
|
12524
|
+
return translateEq(field, opValue);
|
|
12525
|
+
const fragments = opValue.map((v) => processOperatorValue(field, v));
|
|
12526
|
+
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12527
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12528
|
+
return { sql: `(${sql})`, args };
|
|
12529
|
+
}
|
|
12530
|
+
case "$or": {
|
|
12531
|
+
if (!Array.isArray(opValue))
|
|
12532
|
+
return translateEq(field, opValue);
|
|
12533
|
+
const fragments = opValue.map((v) => processOperatorValue(field, v));
|
|
12534
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12535
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12536
|
+
return { sql: `(${sql})`, args };
|
|
12537
|
+
}
|
|
12460
12538
|
default:
|
|
12461
12539
|
return translateEq(field, opValue);
|
|
12462
12540
|
}
|
|
12463
12541
|
}
|
|
12464
12542
|
return translateEq(field, value);
|
|
12465
12543
|
}
|
|
12466
|
-
function
|
|
12544
|
+
function translateNot(field, criteria) {
|
|
12545
|
+
if (!criteria || typeof criteria === "object" && Object.keys(criteria).length === 0)
|
|
12546
|
+
return null;
|
|
12547
|
+
const inner = processOperatorValue(field, criteria);
|
|
12548
|
+
return {
|
|
12549
|
+
sql: `NOT(${inner.sql})`,
|
|
12550
|
+
args: inner.args
|
|
12551
|
+
};
|
|
12552
|
+
}
|
|
12553
|
+
function translateType(jsonColumn, fieldName, type6) {
|
|
12554
|
+
const jsonPath = `$.${fieldName}`;
|
|
12467
12555
|
switch (type6) {
|
|
12468
|
-
case "number":
|
|
12469
|
-
return {
|
|
12470
|
-
sql: `(typeof(${field}) = 'integer' OR typeof(${field}) = 'real')`,
|
|
12471
|
-
args: []
|
|
12472
|
-
};
|
|
12473
|
-
case "string":
|
|
12474
|
-
return { sql: `typeof(${field}) = 'text'`, args: [] };
|
|
12475
12556
|
case "null":
|
|
12476
|
-
return { sql: `
|
|
12557
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'null'`, args: [] };
|
|
12477
12558
|
case "boolean":
|
|
12559
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('true', 'false')`, args: [] };
|
|
12560
|
+
case "number":
|
|
12561
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('integer', 'real')`, args: [] };
|
|
12562
|
+
case "string":
|
|
12563
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'text'`, args: [] };
|
|
12478
12564
|
case "array":
|
|
12565
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'array'`, args: [] };
|
|
12479
12566
|
case "object":
|
|
12480
|
-
|
|
12567
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'object'`, args: [] };
|
|
12481
12568
|
default:
|
|
12482
12569
|
return null;
|
|
12483
12570
|
}
|
|
@@ -12488,19 +12575,120 @@ function translateSize(field, size) {
|
|
|
12488
12575
|
args: [size]
|
|
12489
12576
|
};
|
|
12490
12577
|
}
|
|
12491
|
-
function translateMod(field,
|
|
12578
|
+
function translateMod(field, value) {
|
|
12579
|
+
if (!Array.isArray(value) || value.length !== 2)
|
|
12580
|
+
return null;
|
|
12581
|
+
const [divisor, remainder] = value;
|
|
12492
12582
|
return {
|
|
12493
12583
|
sql: `${field} % ? = ?`,
|
|
12494
12584
|
args: [divisor, remainder]
|
|
12495
12585
|
};
|
|
12496
12586
|
}
|
|
12497
12587
|
|
|
12588
|
+
// src/utils/stable-stringify.ts
|
|
12589
|
+
var strEscapeRegex = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/;
|
|
12590
|
+
function strEscape(str) {
|
|
12591
|
+
if (str.length < 5000 && !strEscapeRegex.test(str)) {
|
|
12592
|
+
return `"${str}"`;
|
|
12593
|
+
}
|
|
12594
|
+
return JSON.stringify(str);
|
|
12595
|
+
}
|
|
12596
|
+
function callSafe(method, thisArg) {
|
|
12597
|
+
try {
|
|
12598
|
+
return method.call(thisArg);
|
|
12599
|
+
} catch (error) {
|
|
12600
|
+
return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
|
|
12601
|
+
}
|
|
12602
|
+
}
|
|
12603
|
+
function sortKeys(keys) {
|
|
12604
|
+
if (keys.length > 100) {
|
|
12605
|
+
return keys.sort();
|
|
12606
|
+
}
|
|
12607
|
+
for (let i = 1;i < keys.length; i++) {
|
|
12608
|
+
const current = keys[i];
|
|
12609
|
+
let pos = i;
|
|
12610
|
+
while (pos !== 0 && keys[pos - 1] > current) {
|
|
12611
|
+
keys[pos] = keys[pos - 1];
|
|
12612
|
+
pos--;
|
|
12613
|
+
}
|
|
12614
|
+
keys[pos] = current;
|
|
12615
|
+
}
|
|
12616
|
+
return keys;
|
|
12617
|
+
}
|
|
12618
|
+
function stableStringify(value) {
|
|
12619
|
+
return _stringify(value, []);
|
|
12620
|
+
}
|
|
12621
|
+
function _stringify(value, stack) {
|
|
12622
|
+
if (value === null)
|
|
12623
|
+
return "null";
|
|
12624
|
+
if (value === true)
|
|
12625
|
+
return "true";
|
|
12626
|
+
if (value === false)
|
|
12627
|
+
return "false";
|
|
12628
|
+
const type6 = typeof value;
|
|
12629
|
+
if (type6 === "string")
|
|
12630
|
+
return strEscape(value);
|
|
12631
|
+
if (type6 === "number")
|
|
12632
|
+
return isFinite(value) ? String(value) : "null";
|
|
12633
|
+
if (type6 === "undefined")
|
|
12634
|
+
return "null";
|
|
12635
|
+
if (type6 === "bigint")
|
|
12636
|
+
return String(value);
|
|
12637
|
+
if (Array.isArray(value)) {
|
|
12638
|
+
if (stack.indexOf(value) !== -1)
|
|
12639
|
+
return '"[Circular]"';
|
|
12640
|
+
stack.push(value);
|
|
12641
|
+
if (value.length === 0) {
|
|
12642
|
+
stack.pop();
|
|
12643
|
+
return "[]";
|
|
12644
|
+
}
|
|
12645
|
+
let res = "[" + _stringify(value[0], stack);
|
|
12646
|
+
for (let i = 1;i < value.length; i++) {
|
|
12647
|
+
res += "," + _stringify(value[i], stack);
|
|
12648
|
+
}
|
|
12649
|
+
stack.pop();
|
|
12650
|
+
return res + "]";
|
|
12651
|
+
}
|
|
12652
|
+
if (type6 === "object") {
|
|
12653
|
+
const obj = value;
|
|
12654
|
+
if (stack.indexOf(value) !== -1)
|
|
12655
|
+
return '"[Circular]"';
|
|
12656
|
+
stack.push(value);
|
|
12657
|
+
if ("toJSON" in obj && typeof obj.toJSON === "function") {
|
|
12658
|
+
const result = _stringify(callSafe(obj.toJSON, obj), stack);
|
|
12659
|
+
stack.pop();
|
|
12660
|
+
return result;
|
|
12661
|
+
}
|
|
12662
|
+
const objType = Object.prototype.toString.call(obj);
|
|
12663
|
+
if (objType !== "[object Object]") {
|
|
12664
|
+
const result = JSON.stringify(obj);
|
|
12665
|
+
stack.pop();
|
|
12666
|
+
return result;
|
|
12667
|
+
}
|
|
12668
|
+
const keys = sortKeys(Object.keys(obj));
|
|
12669
|
+
let res = "{";
|
|
12670
|
+
let separator = "";
|
|
12671
|
+
for (let i = 0;i < keys.length; i++) {
|
|
12672
|
+
const key = keys[i];
|
|
12673
|
+
const val = obj[key];
|
|
12674
|
+
if (val === undefined)
|
|
12675
|
+
continue;
|
|
12676
|
+
res += separator + strEscape(key) + ":" + _stringify(val, stack);
|
|
12677
|
+
separator = ",";
|
|
12678
|
+
}
|
|
12679
|
+
stack.pop();
|
|
12680
|
+
return res + "}";
|
|
12681
|
+
}
|
|
12682
|
+
return "null";
|
|
12683
|
+
}
|
|
12684
|
+
|
|
12498
12685
|
// src/query/builder.ts
|
|
12499
|
-
var import_fast_stable_stringify = __toESM(require_fast_stable_stringify(), 1);
|
|
12500
12686
|
var QUERY_CACHE = new Map;
|
|
12501
|
-
var MAX_CACHE_SIZE =
|
|
12502
|
-
function buildWhereClause(selector, schema) {
|
|
12503
|
-
|
|
12687
|
+
var MAX_CACHE_SIZE = 1000;
|
|
12688
|
+
function buildWhereClause(selector, schema, collectionName) {
|
|
12689
|
+
if (!selector || typeof selector !== "object")
|
|
12690
|
+
return null;
|
|
12691
|
+
const cacheKey = `v${schema.version}_${collectionName}_${stableStringify(selector)}`;
|
|
12504
12692
|
const cached = QUERY_CACHE.get(cacheKey);
|
|
12505
12693
|
if (cached) {
|
|
12506
12694
|
QUERY_CACHE.delete(cacheKey);
|
|
@@ -12508,6 +12696,8 @@ function buildWhereClause(selector, schema) {
|
|
|
12508
12696
|
return cached;
|
|
12509
12697
|
}
|
|
12510
12698
|
const result = processSelector(selector, schema, 0);
|
|
12699
|
+
if (!result)
|
|
12700
|
+
return null;
|
|
12511
12701
|
if (QUERY_CACHE.size >= MAX_CACHE_SIZE) {
|
|
12512
12702
|
const firstKey = QUERY_CACHE.keys().next().value;
|
|
12513
12703
|
if (firstKey)
|
|
@@ -12516,12 +12706,27 @@ function buildWhereClause(selector, schema) {
|
|
|
12516
12706
|
QUERY_CACHE.set(cacheKey, result);
|
|
12517
12707
|
return result;
|
|
12518
12708
|
}
|
|
12709
|
+
function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
|
|
12710
|
+
if (conditions.length === 0) {
|
|
12711
|
+
return { sql: "1=1", args: [] };
|
|
12712
|
+
}
|
|
12713
|
+
const fragments = conditions.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
|
|
12714
|
+
if (fragments.some((f) => f === null))
|
|
12715
|
+
return null;
|
|
12716
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12717
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12718
|
+
return operator === "nor" ? { sql: `NOT(${sql})`, args } : { sql, args };
|
|
12719
|
+
}
|
|
12519
12720
|
function processSelector(selector, schema, logicalDepth) {
|
|
12721
|
+
if (!selector || typeof selector !== "object")
|
|
12722
|
+
return null;
|
|
12520
12723
|
const conditions = [];
|
|
12521
12724
|
const args = [];
|
|
12522
12725
|
for (const [field, value] of Object.entries(selector)) {
|
|
12523
12726
|
if (field === "$and" && Array.isArray(value)) {
|
|
12524
|
-
const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth));
|
|
12727
|
+
const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
|
|
12728
|
+
if (andFragments.some((f) => f === null))
|
|
12729
|
+
return null;
|
|
12525
12730
|
const andConditions = andFragments.map((f) => f.sql);
|
|
12526
12731
|
const needsParens = logicalDepth > 0 && andConditions.length > 1;
|
|
12527
12732
|
const joined = andConditions.join(" AND ");
|
|
@@ -12530,22 +12735,25 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12530
12735
|
continue;
|
|
12531
12736
|
}
|
|
12532
12737
|
if (field === "$or" && Array.isArray(value)) {
|
|
12533
|
-
const
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
const
|
|
12537
|
-
conditions.push(needsParens ? `(${
|
|
12538
|
-
|
|
12738
|
+
const orFragment = buildLogicalOperator("or", value, schema, logicalDepth);
|
|
12739
|
+
if (!orFragment)
|
|
12740
|
+
return null;
|
|
12741
|
+
const needsParens = logicalDepth > 0;
|
|
12742
|
+
conditions.push(needsParens ? `(${orFragment.sql})` : orFragment.sql);
|
|
12743
|
+
args.push(...orFragment.args);
|
|
12539
12744
|
continue;
|
|
12540
12745
|
}
|
|
12541
12746
|
if (field === "$nor" && Array.isArray(value)) {
|
|
12542
|
-
const norFragment =
|
|
12747
|
+
const norFragment = buildLogicalOperator("nor", value, schema, logicalDepth);
|
|
12748
|
+
if (!norFragment)
|
|
12749
|
+
return null;
|
|
12543
12750
|
conditions.push(norFragment.sql);
|
|
12544
12751
|
args.push(...norFragment.args);
|
|
12545
12752
|
continue;
|
|
12546
12753
|
}
|
|
12547
12754
|
const columnInfo = getColumnInfo(field, schema);
|
|
12548
12755
|
const fieldName = columnInfo.column || `json_extract(data, '${columnInfo.jsonPath}')`;
|
|
12756
|
+
const actualFieldName = columnInfo.jsonPath?.replace(/^\$\./, "") || columnInfo.column || field;
|
|
12549
12757
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12550
12758
|
for (const [op, opValue] of Object.entries(value)) {
|
|
12551
12759
|
let fragment;
|
|
@@ -12579,32 +12787,40 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12579
12787
|
break;
|
|
12580
12788
|
case "$regex":
|
|
12581
12789
|
const options = value.$options;
|
|
12582
|
-
const regexFragment = translateRegex(fieldName, opValue, options);
|
|
12790
|
+
const regexFragment = translateRegex(fieldName, opValue, options, schema, actualFieldName);
|
|
12583
12791
|
if (!regexFragment)
|
|
12584
|
-
|
|
12792
|
+
return null;
|
|
12585
12793
|
fragment = regexFragment;
|
|
12586
12794
|
break;
|
|
12587
12795
|
case "$elemMatch":
|
|
12588
12796
|
const elemMatchFragment = translateElemMatch(fieldName, opValue);
|
|
12589
12797
|
if (!elemMatchFragment)
|
|
12590
|
-
|
|
12798
|
+
return null;
|
|
12591
12799
|
fragment = elemMatchFragment;
|
|
12592
12800
|
break;
|
|
12593
|
-
case "$not":
|
|
12594
|
-
|
|
12801
|
+
case "$not": {
|
|
12802
|
+
const notResult = translateNot(fieldName, opValue);
|
|
12803
|
+
if (!notResult)
|
|
12804
|
+
return null;
|
|
12805
|
+
fragment = notResult;
|
|
12595
12806
|
break;
|
|
12807
|
+
}
|
|
12596
12808
|
case "$type":
|
|
12597
|
-
const typeFragment = translateType(
|
|
12809
|
+
const typeFragment = translateType("data", actualFieldName, opValue);
|
|
12598
12810
|
if (!typeFragment)
|
|
12599
|
-
|
|
12811
|
+
return null;
|
|
12600
12812
|
fragment = typeFragment;
|
|
12601
12813
|
break;
|
|
12602
12814
|
case "$size":
|
|
12603
12815
|
fragment = translateSize(fieldName, opValue);
|
|
12604
12816
|
break;
|
|
12605
|
-
case "$mod":
|
|
12606
|
-
|
|
12817
|
+
case "$mod": {
|
|
12818
|
+
const modResult = translateMod(fieldName, opValue);
|
|
12819
|
+
if (!modResult)
|
|
12820
|
+
return null;
|
|
12821
|
+
fragment = modResult;
|
|
12607
12822
|
break;
|
|
12823
|
+
}
|
|
12608
12824
|
default:
|
|
12609
12825
|
continue;
|
|
12610
12826
|
}
|
|
@@ -12828,32 +13044,64 @@ function ensureRxStorageInstanceParamsAreCorrect(params) {
|
|
|
12828
13044
|
class StatementManager {
|
|
12829
13045
|
db;
|
|
12830
13046
|
staticStatements = new Map;
|
|
13047
|
+
static MAX_STATEMENTS = 500;
|
|
13048
|
+
closed = false;
|
|
12831
13049
|
constructor(db) {
|
|
12832
13050
|
this.db = db;
|
|
12833
13051
|
}
|
|
13052
|
+
checkClosed() {
|
|
13053
|
+
if (this.closed) {
|
|
13054
|
+
throw new Error("StatementManager is closed");
|
|
13055
|
+
}
|
|
13056
|
+
}
|
|
13057
|
+
evictOldest() {
|
|
13058
|
+
if (this.staticStatements.size >= StatementManager.MAX_STATEMENTS) {
|
|
13059
|
+
const firstKey = this.staticStatements.keys().next().value;
|
|
13060
|
+
if (firstKey) {
|
|
13061
|
+
const stmt = this.staticStatements.get(firstKey);
|
|
13062
|
+
stmt?.finalize();
|
|
13063
|
+
this.staticStatements.delete(firstKey);
|
|
13064
|
+
}
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
12834
13067
|
all(queryWithParams) {
|
|
13068
|
+
this.checkClosed();
|
|
12835
13069
|
const { query, params } = queryWithParams;
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
this.staticStatements.set(query, stmt);
|
|
12841
|
-
}
|
|
12842
|
-
return stmt.all(...params);
|
|
13070
|
+
let stmt = this.staticStatements.get(query);
|
|
13071
|
+
if (stmt) {
|
|
13072
|
+
this.staticStatements.delete(query);
|
|
13073
|
+
this.staticStatements.set(query, stmt);
|
|
12843
13074
|
} else {
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
13075
|
+
this.evictOldest();
|
|
13076
|
+
stmt = this.db.query(query);
|
|
13077
|
+
this.staticStatements.set(query, stmt);
|
|
13078
|
+
}
|
|
13079
|
+
return stmt.all(...params);
|
|
13080
|
+
}
|
|
13081
|
+
get(queryWithParams) {
|
|
13082
|
+
this.checkClosed();
|
|
13083
|
+
const { query, params } = queryWithParams;
|
|
13084
|
+
let stmt = this.staticStatements.get(query);
|
|
13085
|
+
if (stmt) {
|
|
13086
|
+
this.staticStatements.delete(query);
|
|
13087
|
+
this.staticStatements.set(query, stmt);
|
|
13088
|
+
} else {
|
|
13089
|
+
this.evictOldest();
|
|
13090
|
+
stmt = this.db.query(query);
|
|
13091
|
+
this.staticStatements.set(query, stmt);
|
|
12850
13092
|
}
|
|
13093
|
+
return stmt.get(...params);
|
|
12851
13094
|
}
|
|
12852
13095
|
run(queryWithParams) {
|
|
13096
|
+
this.checkClosed();
|
|
12853
13097
|
const { query, params } = queryWithParams;
|
|
12854
13098
|
if (this.isStaticSQL(query)) {
|
|
12855
13099
|
let stmt = this.staticStatements.get(query);
|
|
12856
|
-
if (
|
|
13100
|
+
if (stmt) {
|
|
13101
|
+
this.staticStatements.delete(query);
|
|
13102
|
+
this.staticStatements.set(query, stmt);
|
|
13103
|
+
} else {
|
|
13104
|
+
this.evictOldest();
|
|
12857
13105
|
stmt = this.db.query(query);
|
|
12858
13106
|
this.staticStatements.set(query, stmt);
|
|
12859
13107
|
}
|
|
@@ -12872,6 +13120,7 @@ class StatementManager {
|
|
|
12872
13120
|
stmt.finalize();
|
|
12873
13121
|
}
|
|
12874
13122
|
this.staticStatements.clear();
|
|
13123
|
+
this.closed = true;
|
|
12875
13124
|
}
|
|
12876
13125
|
isStaticSQL(query) {
|
|
12877
13126
|
if (query.includes("WHERE (")) {
|
|
@@ -12912,6 +13161,28 @@ function releaseDatabase(databaseName) {
|
|
|
12912
13161
|
}
|
|
12913
13162
|
}
|
|
12914
13163
|
|
|
13164
|
+
// src/transaction-queue.ts
|
|
13165
|
+
var TX_QUEUE_BY_DATABASE = new WeakMap;
|
|
13166
|
+
async function sqliteTransaction(database, handler) {
|
|
13167
|
+
let queue = TX_QUEUE_BY_DATABASE.get(database);
|
|
13168
|
+
if (!queue) {
|
|
13169
|
+
queue = Promise.resolve();
|
|
13170
|
+
}
|
|
13171
|
+
const result = queue.then(async () => {
|
|
13172
|
+
database.run("BEGIN IMMEDIATE");
|
|
13173
|
+
try {
|
|
13174
|
+
const handlerResult = await handler();
|
|
13175
|
+
database.run("COMMIT");
|
|
13176
|
+
return handlerResult;
|
|
13177
|
+
} catch (error) {
|
|
13178
|
+
database.run("ROLLBACK");
|
|
13179
|
+
throw error;
|
|
13180
|
+
}
|
|
13181
|
+
});
|
|
13182
|
+
TX_QUEUE_BY_DATABASE.set(database, result.then(() => {}).catch(() => {}));
|
|
13183
|
+
return result;
|
|
13184
|
+
}
|
|
13185
|
+
|
|
12915
13186
|
// src/instance.ts
|
|
12916
13187
|
class BunSQLiteStorageInstance {
|
|
12917
13188
|
db;
|
|
@@ -12947,6 +13218,15 @@ class BunSQLiteStorageInstance {
|
|
|
12947
13218
|
if (filename !== ":memory:") {
|
|
12948
13219
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
12949
13220
|
this.db.run("PRAGMA synchronous = NORMAL");
|
|
13221
|
+
this.db.run("PRAGMA wal_autocheckpoint = 1000");
|
|
13222
|
+
this.db.run("PRAGMA cache_size = -32000");
|
|
13223
|
+
this.db.run("PRAGMA analysis_limit = 400");
|
|
13224
|
+
const mmapSize = this.options.mmapSize ?? 268435456;
|
|
13225
|
+
if (mmapSize > 0) {
|
|
13226
|
+
this.db.run(`PRAGMA mmap_size = ${mmapSize}`);
|
|
13227
|
+
}
|
|
13228
|
+
this.db.run("PRAGMA temp_store = MEMORY");
|
|
13229
|
+
this.db.run("PRAGMA locking_mode = NORMAL");
|
|
12950
13230
|
}
|
|
12951
13231
|
this.db.run(`
|
|
12952
13232
|
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
@@ -12962,8 +13242,17 @@ class BunSQLiteStorageInstance {
|
|
|
12962
13242
|
if (this.schema.indexes) {
|
|
12963
13243
|
for (const index of this.schema.indexes) {
|
|
12964
13244
|
const fields = Array.isArray(index) ? index : [index];
|
|
12965
|
-
const indexName = `idx_${this.tableName}_${fields.join("_")}`;
|
|
12966
|
-
const columns = fields.map((field) =>
|
|
13245
|
+
const indexName = `idx_${this.tableName}_${fields.join("_").replace(/[()]/g, "_")}`;
|
|
13246
|
+
const columns = fields.map((field) => {
|
|
13247
|
+
if (typeof field !== "string")
|
|
13248
|
+
return `json_extract(data, '$.${field}')`;
|
|
13249
|
+
const funcMatch = field.match(/^(\w+)\((.+)\)$/);
|
|
13250
|
+
if (funcMatch) {
|
|
13251
|
+
const [, func, fieldName] = funcMatch;
|
|
13252
|
+
return `${func}(json_extract(data, '$.${fieldName}'))`;
|
|
13253
|
+
}
|
|
13254
|
+
return `json_extract(data, '$.${field}')`;
|
|
13255
|
+
}).join(", ");
|
|
12967
13256
|
this.db.run(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${this.tableName}"(${columns})`);
|
|
12968
13257
|
}
|
|
12969
13258
|
}
|
|
@@ -12976,69 +13265,85 @@ class BunSQLiteStorageInstance {
|
|
|
12976
13265
|
`);
|
|
12977
13266
|
}
|
|
12978
13267
|
async bulkWrite(documentWrites, context) {
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
const
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13268
|
+
return sqliteTransaction(this.db, async () => {
|
|
13269
|
+
if (documentWrites.length === 0) {
|
|
13270
|
+
return { error: [] };
|
|
13271
|
+
}
|
|
13272
|
+
const ids = documentWrites.map((w) => w.document[this.primaryPath]);
|
|
13273
|
+
const docsInDb = await this.findDocumentsById(ids, true);
|
|
13274
|
+
const docsInDbMap = new Map(docsInDb.map((d) => [d[this.primaryPath], d]));
|
|
13275
|
+
const categorized = categorizeBulkWriteRows(this, this.primaryPath, docsInDbMap, documentWrites, context);
|
|
13276
|
+
const updateQuery = `UPDATE "${this.tableName}" SET data = jsonb(?), deleted = ?, rev = ?, mtime_ms = ? WHERE id = ?`;
|
|
13277
|
+
const BATCH_SIZE = 100;
|
|
13278
|
+
for (let i = 0;i < categorized.bulkInsertDocs.length; i += BATCH_SIZE) {
|
|
13279
|
+
const batch = categorized.bulkInsertDocs.slice(i, i + BATCH_SIZE);
|
|
13280
|
+
const placeholders = batch.map(() => "(?, jsonb(?), ?, ?, ?)").join(", ");
|
|
13281
|
+
const insertQuery = `INSERT INTO "${this.tableName}" (id, data, deleted, rev, mtime_ms) VALUES ${placeholders}`;
|
|
13282
|
+
const params = [];
|
|
13283
|
+
for (const row of batch) {
|
|
13284
|
+
const doc = row.document;
|
|
13285
|
+
const id = doc[this.primaryPath];
|
|
13286
|
+
params.push(id, JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt);
|
|
13287
|
+
}
|
|
13288
|
+
try {
|
|
13289
|
+
this.stmtManager.run({ query: insertQuery, params });
|
|
13290
|
+
} catch (err) {
|
|
13291
|
+
if (err && typeof err === "object" && "code" in err && (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || err.code === "SQLITE_CONSTRAINT_UNIQUE")) {
|
|
13292
|
+
for (const row of batch) {
|
|
13293
|
+
const doc = row.document;
|
|
13294
|
+
const id = doc[this.primaryPath];
|
|
13295
|
+
const documentInDb = docsInDbMap.get(id);
|
|
13296
|
+
categorized.errors.push({
|
|
13297
|
+
isError: true,
|
|
13298
|
+
status: 409,
|
|
13299
|
+
documentId: id,
|
|
13300
|
+
writeRow: row,
|
|
13301
|
+
documentInDb: documentInDb || doc
|
|
13302
|
+
});
|
|
13303
|
+
}
|
|
13304
|
+
} else {
|
|
13305
|
+
throw err;
|
|
13306
|
+
}
|
|
13005
13307
|
}
|
|
13006
13308
|
}
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
this.
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
categorized.eventBulk.
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13309
|
+
for (let i = 0;i < categorized.bulkUpdateDocs.length; i += BATCH_SIZE) {
|
|
13310
|
+
const batch = categorized.bulkUpdateDocs.slice(i, i + BATCH_SIZE);
|
|
13311
|
+
for (const row of batch) {
|
|
13312
|
+
const doc = row.document;
|
|
13313
|
+
const id = doc[this.primaryPath];
|
|
13314
|
+
this.stmtManager.run({ query: updateQuery, params: [JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt, id] });
|
|
13315
|
+
}
|
|
13316
|
+
}
|
|
13317
|
+
const insertAttQuery = `INSERT OR REPLACE INTO "${this.tableName}_attachments" (id, data, digest) VALUES (?, ?, ?)`;
|
|
13318
|
+
const deleteAttQuery = `DELETE FROM "${this.tableName}_attachments" WHERE id = ?`;
|
|
13319
|
+
for (const att of [...categorized.attachmentsAdd, ...categorized.attachmentsUpdate]) {
|
|
13320
|
+
this.stmtManager.run({
|
|
13321
|
+
query: insertAttQuery,
|
|
13322
|
+
params: [
|
|
13323
|
+
this.attachmentMapKey(att.documentId, att.attachmentId),
|
|
13324
|
+
att.attachmentData.data,
|
|
13325
|
+
att.digest
|
|
13326
|
+
]
|
|
13327
|
+
});
|
|
13328
|
+
}
|
|
13329
|
+
for (const att of categorized.attachmentsRemove) {
|
|
13330
|
+
this.stmtManager.run({
|
|
13331
|
+
query: deleteAttQuery,
|
|
13332
|
+
params: [this.attachmentMapKey(att.documentId, att.attachmentId)]
|
|
13333
|
+
});
|
|
13334
|
+
}
|
|
13335
|
+
const failedDocIds = new Set(categorized.errors.map((e) => e.documentId));
|
|
13336
|
+
categorized.eventBulk.events = categorized.eventBulk.events.filter((event) => !failedDocIds.has(event.documentId));
|
|
13337
|
+
if (categorized.eventBulk.events.length > 0 && categorized.newestRow) {
|
|
13338
|
+
const lastState = categorized.newestRow.document;
|
|
13339
|
+
categorized.eventBulk.checkpoint = {
|
|
13340
|
+
id: lastState[this.primaryPath],
|
|
13341
|
+
lwt: lastState._meta.lwt
|
|
13342
|
+
};
|
|
13343
|
+
this.changeStream$.next(categorized.eventBulk);
|
|
13344
|
+
}
|
|
13345
|
+
return { error: categorized.errors };
|
|
13346
|
+
});
|
|
13042
13347
|
}
|
|
13043
13348
|
async findDocumentsById(ids, withDeleted) {
|
|
13044
13349
|
if (ids.length === 0)
|
|
@@ -13050,47 +13355,39 @@ class BunSQLiteStorageInstance {
|
|
|
13050
13355
|
return rows.map((row) => JSON.parse(row.data));
|
|
13051
13356
|
}
|
|
13052
13357
|
async query(preparedQuery) {
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
}
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
if (preparedQuery.query.skip) {
|
|
13087
|
-
documents = documents.slice(preparedQuery.query.skip);
|
|
13088
|
-
}
|
|
13089
|
-
if (preparedQuery.query.limit) {
|
|
13090
|
-
documents = documents.slice(0, preparedQuery.query.limit);
|
|
13091
|
-
}
|
|
13092
|
-
return { documents };
|
|
13093
|
-
}
|
|
13358
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13359
|
+
if (!whereResult) {
|
|
13360
|
+
return this.queryWithOurMemory(preparedQuery);
|
|
13361
|
+
}
|
|
13362
|
+
const { sql: whereClause, args } = whereResult;
|
|
13363
|
+
let sql = `SELECT json(data) as data FROM "${this.tableName}" WHERE (${whereClause})`;
|
|
13364
|
+
const queryArgs = [...args];
|
|
13365
|
+
if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
|
|
13366
|
+
const orderBy = preparedQuery.query.sort.map((sortField) => {
|
|
13367
|
+
const [field, direction] = Object.entries(sortField)[0];
|
|
13368
|
+
const dir = direction === "asc" ? "ASC" : "DESC";
|
|
13369
|
+
return `json_extract(data, '$.${field}') ${dir}`;
|
|
13370
|
+
}).join(", ");
|
|
13371
|
+
sql += ` ORDER BY ${orderBy}`;
|
|
13372
|
+
}
|
|
13373
|
+
if (preparedQuery.query.limit) {
|
|
13374
|
+
sql += ` LIMIT ?`;
|
|
13375
|
+
queryArgs.push(preparedQuery.query.limit);
|
|
13376
|
+
}
|
|
13377
|
+
if (preparedQuery.query.skip) {
|
|
13378
|
+
sql += ` OFFSET ?`;
|
|
13379
|
+
queryArgs.push(preparedQuery.query.skip);
|
|
13380
|
+
}
|
|
13381
|
+
if (process.env.DEBUG_QUERIES) {
|
|
13382
|
+
const explainSql = `EXPLAIN QUERY PLAN ${sql}`;
|
|
13383
|
+
const plan = this.stmtManager.all({ query: explainSql, params: queryArgs });
|
|
13384
|
+
console.log("[DEBUG_QUERIES] Query plan:", JSON.stringify(plan, null, 2));
|
|
13385
|
+
console.log("[DEBUG_QUERIES] SQL:", sql);
|
|
13386
|
+
console.log("[DEBUG_QUERIES] Args:", queryArgs);
|
|
13387
|
+
}
|
|
13388
|
+
const rows = this.stmtManager.all({ query: sql, params: queryArgs });
|
|
13389
|
+
const documents = rows.map((row) => JSON.parse(row.data));
|
|
13390
|
+
return { documents };
|
|
13094
13391
|
}
|
|
13095
13392
|
matchesSelector(doc, selector) {
|
|
13096
13393
|
for (const [key, value] of Object.entries(selector)) {
|
|
@@ -13135,9 +13432,18 @@ class BunSQLiteStorageInstance {
|
|
|
13135
13432
|
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13136
13433
|
}
|
|
13137
13434
|
async count(preparedQuery) {
|
|
13138
|
-
const
|
|
13435
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13436
|
+
if (!whereResult) {
|
|
13437
|
+
const allDocs = await this.queryWithOurMemory(preparedQuery);
|
|
13438
|
+
return {
|
|
13439
|
+
count: allDocs.documents.length,
|
|
13440
|
+
mode: "fast"
|
|
13441
|
+
};
|
|
13442
|
+
}
|
|
13443
|
+
const { sql, args } = whereResult;
|
|
13444
|
+
const result = this.db.query(`SELECT COUNT(*) as count FROM "${this.tableName}" WHERE (${sql})`).get(...args);
|
|
13139
13445
|
return {
|
|
13140
|
-
count: result
|
|
13446
|
+
count: result?.count ?? 0,
|
|
13141
13447
|
mode: "fast"
|
|
13142
13448
|
};
|
|
13143
13449
|
}
|
|
@@ -13198,9 +13504,43 @@ class BunSQLiteStorageInstance {
|
|
|
13198
13504
|
const rows = this.stmtManager.all({ query: sql, params: [checkpointLwt, checkpointLwt, checkpointId, limit] });
|
|
13199
13505
|
const documents = rows.map((row) => JSON.parse(row.data));
|
|
13200
13506
|
const lastDoc = documents[documents.length - 1];
|
|
13201
|
-
const newCheckpoint = lastDoc ? {
|
|
13507
|
+
const newCheckpoint = lastDoc ? {
|
|
13508
|
+
id: String(lastDoc[this.primaryPath]),
|
|
13509
|
+
lwt: lastDoc._meta.lwt
|
|
13510
|
+
} : checkpoint ?? null;
|
|
13202
13511
|
return { documents, checkpoint: newCheckpoint };
|
|
13203
13512
|
}
|
|
13513
|
+
queryWithOurMemory(preparedQuery) {
|
|
13514
|
+
const query = `SELECT json(data) as data FROM "${this.tableName}"`;
|
|
13515
|
+
const rows = this.stmtManager.all({ query, params: [] });
|
|
13516
|
+
let documents = rows.map((row) => JSON.parse(row.data));
|
|
13517
|
+
documents = documents.filter((doc) => this.matchesRegexSelector(doc, preparedQuery.query.selector));
|
|
13518
|
+
if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
|
|
13519
|
+
documents = this.sortDocuments(documents, preparedQuery.query.sort);
|
|
13520
|
+
}
|
|
13521
|
+
if (preparedQuery.query.skip) {
|
|
13522
|
+
documents = documents.slice(preparedQuery.query.skip);
|
|
13523
|
+
}
|
|
13524
|
+
if (preparedQuery.query.limit) {
|
|
13525
|
+
documents = documents.slice(0, preparedQuery.query.limit);
|
|
13526
|
+
}
|
|
13527
|
+
return { documents };
|
|
13528
|
+
}
|
|
13529
|
+
matchesRegexSelector(doc, selector) {
|
|
13530
|
+
for (const [field, value] of Object.entries(selector)) {
|
|
13531
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
13532
|
+
const ops = value;
|
|
13533
|
+
if (ops.$regex) {
|
|
13534
|
+
const fieldValue = this.getNestedValue(doc, field);
|
|
13535
|
+
const pattern = ops.$regex;
|
|
13536
|
+
const options = ops.$options;
|
|
13537
|
+
if (!matchesRegex(fieldValue, pattern, options))
|
|
13538
|
+
return false;
|
|
13539
|
+
}
|
|
13540
|
+
}
|
|
13541
|
+
}
|
|
13542
|
+
return true;
|
|
13543
|
+
}
|
|
13204
13544
|
}
|
|
13205
13545
|
|
|
13206
13546
|
// src/storage.ts
|