bun-sqlite-for-rxdb 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -22
- package/dist/index.js +389 -213
- package/dist/src/instance.d.ts +2 -0
- package/dist/src/query/builder.d.ts +1 -1
- package/dist/src/query/operators.d.ts +2 -1
- package/dist/src/query/smart-regex.d.ts +1 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -4,33 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
RxDB storage adapter that translates Mango queries directly to bun:sqlite (except of $regex), bypassing slow in-memory filtering.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- ✅ **
|
|
11
|
-
- ✅
|
|
9
|
+
## Features
|
|
10
|
+
- ✅ **17 Mango operators** directly using SQL ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $or, $and, $exists, $elemMatch, $not, $nor, $type, $size, $mod)
|
|
11
|
+
- ✅ **1 Mango operator optimized in memory** (complex $regex)
|
|
12
|
+
- ✅ **Smart regex** - converts simple patterns to SQL LIKE (uses indexes)
|
|
12
13
|
- ✅ **Attachments support** (base64 storage with digest validation)
|
|
13
|
-
|
|
14
|
-
- ✅ **18 Mango operators** ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $or, $and, $exists, $regex, $elemMatch, $not, $nor, $type, $size, $mod)
|
|
14
|
+
|
|
15
15
|
- ✅ **Multi-instance support** with connection pooling
|
|
16
|
-
- ✅ **
|
|
16
|
+
- ✅ **Dual LRU caching** - prepared statements + query AST parsing
|
|
17
|
+
- ✅ **Property-based testing** with fast-check (3000+ assertions vs Mingo and Sift.js)
|
|
17
18
|
- ✅ MIT licensed
|
|
18
19
|
|
|
19
20
|
## Performance
|
|
20
21
|
|
|
22
|
+
### Database Performance
|
|
21
23
|
Benchmarked against better-sqlite3 (1M documents, WAL mode + PRAGMA synchronous = 1):
|
|
22
24
|
|
|
23
25
|
| Operation | Bun SQLite | better-sqlite3 | Speedup |
|
|
24
26
|
|-----------|------------|----------------|---------|
|
|
25
27
|
| Bulk INSERT (1M docs) | 7.42s | 7.90s | **1.06x faster** |
|
|
26
28
|
| SELECT by ID (10K lookups) | 170ms | 170ms | Equal |
|
|
27
|
-
| Complex WHERE query | 484ms | 814ms | **1.68x faster** |
|
|
28
|
-
|
|
29
|
-
**Requirements for optimal performance:**
|
|
30
|
-
```typescript
|
|
31
|
-
db.run("PRAGMA journal_mode = WAL");
|
|
32
|
-
db.run("PRAGMA synchronous = 1");
|
|
33
|
-
```
|
|
29
|
+
| Complex WHERE query | 484ms | 814ms | **1.68x faster** |
|
|
34
30
|
|
|
35
31
|
## Installation
|
|
36
32
|
|
|
@@ -66,14 +62,6 @@ bun test
|
|
|
66
62
|
bun run typecheck
|
|
67
63
|
```
|
|
68
64
|
|
|
69
|
-
## Architecture
|
|
70
|
-
|
|
71
|
-
**Key components:**
|
|
72
|
-
- `RxStorage` factory
|
|
73
|
-
- `RxStorageInstance` implementation (11 required methods)
|
|
74
|
-
- Mango query → SQL translator
|
|
75
|
-
- Change stream observables
|
|
76
|
-
|
|
77
65
|
## License
|
|
78
66
|
|
|
79
67
|
MIT © adam2am
|
package/dist/index.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
|
|
@@ -12213,12 +12231,23 @@ var import_rxjs2 = __toESM(require_cjs(), 1);
|
|
|
12213
12231
|
// src/query/regex-matcher.ts
|
|
12214
12232
|
var REGEX_CACHE = new Map;
|
|
12215
12233
|
var MAX_REGEX_CACHE_SIZE = 100;
|
|
12234
|
+
function isValidRegexOptions(options) {
|
|
12235
|
+
for (let i = 0;i < options.length; i++) {
|
|
12236
|
+
const c = options[i];
|
|
12237
|
+
if (c !== "i" && c !== "m" && c !== "s" && c !== "x" && c !== "u")
|
|
12238
|
+
return false;
|
|
12239
|
+
}
|
|
12240
|
+
return true;
|
|
12241
|
+
}
|
|
12216
12242
|
function compileRegex(pattern, options) {
|
|
12217
12243
|
const cacheKey = `${pattern}::${options || ""}`;
|
|
12218
12244
|
const cached = REGEX_CACHE.get(cacheKey);
|
|
12219
12245
|
if (cached) {
|
|
12220
12246
|
return cached.regex;
|
|
12221
12247
|
}
|
|
12248
|
+
if (options && !isValidRegexOptions(options)) {
|
|
12249
|
+
throw new Error(`Invalid regex options: "${options}". Valid options are: i, m, s, x, u`);
|
|
12250
|
+
}
|
|
12222
12251
|
const regex = new RegExp(pattern, options);
|
|
12223
12252
|
if (REGEX_CACHE.size >= MAX_REGEX_CACHE_SIZE) {
|
|
12224
12253
|
const firstKey = REGEX_CACHE.keys().next().value;
|
|
@@ -12302,6 +12331,14 @@ function hasExpressionIndex(fieldName, schema) {
|
|
|
12302
12331
|
INDEX_CACHE.set(cacheKey, hasLowerIndex);
|
|
12303
12332
|
return hasLowerIndex;
|
|
12304
12333
|
}
|
|
12334
|
+
function isValidRegexOptions2(options) {
|
|
12335
|
+
for (let i = 0;i < options.length; i++) {
|
|
12336
|
+
const c = options[i];
|
|
12337
|
+
if (c !== "i" && c !== "m" && c !== "s" && c !== "x" && c !== "u")
|
|
12338
|
+
return false;
|
|
12339
|
+
}
|
|
12340
|
+
return true;
|
|
12341
|
+
}
|
|
12305
12342
|
function isComplexRegex(pattern) {
|
|
12306
12343
|
return /[*+?()[\]{}|]/.test(pattern.replace(/\\\./g, ""));
|
|
12307
12344
|
}
|
|
@@ -12311,6 +12348,9 @@ function escapeForLike(str) {
|
|
|
12311
12348
|
function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
12312
12349
|
if (typeof pattern !== "string")
|
|
12313
12350
|
return null;
|
|
12351
|
+
if (options && !isValidRegexOptions2(options)) {
|
|
12352
|
+
throw new Error(`Invalid regex options: "${options}". Valid options are: i, m, s, x, u`);
|
|
12353
|
+
}
|
|
12314
12354
|
const caseInsensitive = options?.includes("i") ?? false;
|
|
12315
12355
|
const hasLowerIndex = hasExpressionIndex(fieldName, schema);
|
|
12316
12356
|
const startsWithAnchor = pattern.startsWith("^");
|
|
@@ -12349,6 +12389,18 @@ function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
|
12349
12389
|
}
|
|
12350
12390
|
|
|
12351
12391
|
// src/query/operators.ts
|
|
12392
|
+
function normalizeValueForSQLite(value) {
|
|
12393
|
+
if (value instanceof Date) {
|
|
12394
|
+
return value.toISOString();
|
|
12395
|
+
}
|
|
12396
|
+
if (value instanceof RegExp) {
|
|
12397
|
+
return JSON.stringify({ source: value.source, flags: value.flags });
|
|
12398
|
+
}
|
|
12399
|
+
if (value === undefined) {
|
|
12400
|
+
return null;
|
|
12401
|
+
}
|
|
12402
|
+
return value;
|
|
12403
|
+
}
|
|
12352
12404
|
function translateEq(field, value, schema, actualFieldName) {
|
|
12353
12405
|
if (value === null) {
|
|
12354
12406
|
return { sql: `${field} IS NULL`, args: [] };
|
|
@@ -12358,11 +12410,11 @@ function translateEq(field, value, schema, actualFieldName) {
|
|
|
12358
12410
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12359
12411
|
return {
|
|
12360
12412
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12361
|
-
args: [value]
|
|
12413
|
+
args: [normalizeValueForSQLite(value)]
|
|
12362
12414
|
};
|
|
12363
12415
|
}
|
|
12364
12416
|
}
|
|
12365
|
-
return { sql: `${field} = ?`, args: [value] };
|
|
12417
|
+
return { sql: `${field} = ?`, args: [normalizeValueForSQLite(value)] };
|
|
12366
12418
|
}
|
|
12367
12419
|
function translateNe(field, value, schema, actualFieldName) {
|
|
12368
12420
|
if (value === null) {
|
|
@@ -12373,11 +12425,11 @@ function translateNe(field, value, schema, actualFieldName) {
|
|
|
12373
12425
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12374
12426
|
return {
|
|
12375
12427
|
sql: `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12376
|
-
args: [value]
|
|
12428
|
+
args: [normalizeValueForSQLite(value)]
|
|
12377
12429
|
};
|
|
12378
12430
|
}
|
|
12379
12431
|
}
|
|
12380
|
-
return { sql: `(${field} <> ? OR ${field} IS NULL)`, args: [value] };
|
|
12432
|
+
return { sql: `(${field} <> ? OR ${field} IS NULL)`, args: [normalizeValueForSQLite(value)] };
|
|
12381
12433
|
}
|
|
12382
12434
|
function translateGt(field, value, schema, actualFieldName) {
|
|
12383
12435
|
if (schema && actualFieldName) {
|
|
@@ -12385,11 +12437,11 @@ function translateGt(field, value, schema, actualFieldName) {
|
|
|
12385
12437
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12386
12438
|
return {
|
|
12387
12439
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value > ?)`,
|
|
12388
|
-
args: [value]
|
|
12440
|
+
args: [normalizeValueForSQLite(value)]
|
|
12389
12441
|
};
|
|
12390
12442
|
}
|
|
12391
12443
|
}
|
|
12392
|
-
return { sql: `${field} > ?`, args: [value] };
|
|
12444
|
+
return { sql: `${field} > ?`, args: [normalizeValueForSQLite(value)] };
|
|
12393
12445
|
}
|
|
12394
12446
|
function translateGte(field, value, schema, actualFieldName) {
|
|
12395
12447
|
if (schema && actualFieldName) {
|
|
@@ -12397,11 +12449,11 @@ function translateGte(field, value, schema, actualFieldName) {
|
|
|
12397
12449
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12398
12450
|
return {
|
|
12399
12451
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value >= ?)`,
|
|
12400
|
-
args: [value]
|
|
12452
|
+
args: [normalizeValueForSQLite(value)]
|
|
12401
12453
|
};
|
|
12402
12454
|
}
|
|
12403
12455
|
}
|
|
12404
|
-
return { sql: `${field} >= ?`, args: [value] };
|
|
12456
|
+
return { sql: `${field} >= ?`, args: [normalizeValueForSQLite(value)] };
|
|
12405
12457
|
}
|
|
12406
12458
|
function translateLt(field, value, schema, actualFieldName) {
|
|
12407
12459
|
if (schema && actualFieldName) {
|
|
@@ -12409,11 +12461,11 @@ function translateLt(field, value, schema, actualFieldName) {
|
|
|
12409
12461
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12410
12462
|
return {
|
|
12411
12463
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value < ?)`,
|
|
12412
|
-
args: [value]
|
|
12464
|
+
args: [normalizeValueForSQLite(value)]
|
|
12413
12465
|
};
|
|
12414
12466
|
}
|
|
12415
12467
|
}
|
|
12416
|
-
return { sql: `${field} < ?`, args: [value] };
|
|
12468
|
+
return { sql: `${field} < ?`, args: [normalizeValueForSQLite(value)] };
|
|
12417
12469
|
}
|
|
12418
12470
|
function translateLte(field, value, schema, actualFieldName) {
|
|
12419
12471
|
if (schema && actualFieldName) {
|
|
@@ -12421,18 +12473,18 @@ function translateLte(field, value, schema, actualFieldName) {
|
|
|
12421
12473
|
if (field !== "value" && columnInfo.type === "array") {
|
|
12422
12474
|
return {
|
|
12423
12475
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value <= ?)`,
|
|
12424
|
-
args: [value]
|
|
12476
|
+
args: [normalizeValueForSQLite(value)]
|
|
12425
12477
|
};
|
|
12426
12478
|
}
|
|
12427
12479
|
}
|
|
12428
|
-
return { sql: `${field} <= ?`, args: [value] };
|
|
12480
|
+
return { sql: `${field} <= ?`, args: [normalizeValueForSQLite(value)] };
|
|
12429
12481
|
}
|
|
12430
12482
|
function translateIn(field, values, schema, actualFieldName) {
|
|
12431
12483
|
if (!Array.isArray(values) || values.length === 0) {
|
|
12432
12484
|
return { sql: "1=0", args: [] };
|
|
12433
12485
|
}
|
|
12434
12486
|
const hasNull = values.includes(null);
|
|
12435
|
-
const nonNullValues = values.filter((v) => v !== null);
|
|
12487
|
+
const nonNullValues = values.filter((v) => v !== null).map((v) => normalizeValueForSQLite(v));
|
|
12436
12488
|
if (nonNullValues.length === 0) {
|
|
12437
12489
|
return { sql: `${field} IS NULL`, args: [] };
|
|
12438
12490
|
}
|
|
@@ -12465,7 +12517,7 @@ function translateNin(field, values, schema, actualFieldName) {
|
|
|
12465
12517
|
return { sql: "1=1", args: [] };
|
|
12466
12518
|
}
|
|
12467
12519
|
const hasNull = values.includes(null);
|
|
12468
|
-
const nonNullValues = values.filter((v) => v !== null);
|
|
12520
|
+
const nonNullValues = values.filter((v) => v !== null).map((v) => normalizeValueForSQLite(v));
|
|
12469
12521
|
if (nonNullValues.length === 0) {
|
|
12470
12522
|
return { sql: `${field} IS NOT NULL`, args: [] };
|
|
12471
12523
|
}
|
|
@@ -12480,7 +12532,7 @@ function translateNin(field, values, schema, actualFieldName) {
|
|
|
12480
12532
|
args: args2
|
|
12481
12533
|
};
|
|
12482
12534
|
}
|
|
12483
|
-
return { sql: ninClause2
|
|
12535
|
+
return { sql: `(${field} IS NULL OR ${ninClause2})`, args: args2 };
|
|
12484
12536
|
}
|
|
12485
12537
|
}
|
|
12486
12538
|
const ninClause = `${field} NOT IN (SELECT value FROM json_each(?))`;
|
|
@@ -12491,7 +12543,7 @@ function translateNin(field, values, schema, actualFieldName) {
|
|
|
12491
12543
|
args
|
|
12492
12544
|
};
|
|
12493
12545
|
}
|
|
12494
|
-
return { sql: ninClause
|
|
12546
|
+
return { sql: `(${field} IS NULL OR ${ninClause})`, args };
|
|
12495
12547
|
}
|
|
12496
12548
|
function translateExists(field, exists) {
|
|
12497
12549
|
return {
|
|
@@ -12505,21 +12557,107 @@ function translateRegex(field, pattern, options, schema, fieldName) {
|
|
|
12505
12557
|
return smartResult;
|
|
12506
12558
|
return null;
|
|
12507
12559
|
}
|
|
12560
|
+
var LOGICAL_OPERATORS = new Set(["$and", "$or", "$nor", "$not"]);
|
|
12561
|
+
var LEAF_OPERATORS = new Set(["$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$nin", "$exists", "$regex", "$type", "$size", "$mod", "$elemMatch"]);
|
|
12562
|
+
function isLogicalOperator(key) {
|
|
12563
|
+
return LOGICAL_OPERATORS.has(key);
|
|
12564
|
+
}
|
|
12565
|
+
function isOperatorObject(obj) {
|
|
12566
|
+
let hasKeys = false;
|
|
12567
|
+
for (const k in obj) {
|
|
12568
|
+
hasKeys = true;
|
|
12569
|
+
if (!k.startsWith("$"))
|
|
12570
|
+
return false;
|
|
12571
|
+
}
|
|
12572
|
+
return hasKeys;
|
|
12573
|
+
}
|
|
12574
|
+
function handleLogicalOperator(operator, value, schema, baseFieldName) {
|
|
12575
|
+
if (operator === "$not") {
|
|
12576
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12577
|
+
const innerFragment = buildElemMatchConditions(value, schema, baseFieldName);
|
|
12578
|
+
if (!innerFragment)
|
|
12579
|
+
return null;
|
|
12580
|
+
return { sql: `NOT (${innerFragment.sql})`, args: innerFragment.args };
|
|
12581
|
+
}
|
|
12582
|
+
return { sql: "1=1", args: [] };
|
|
12583
|
+
}
|
|
12584
|
+
if (!Array.isArray(value))
|
|
12585
|
+
return { sql: "1=0", args: [] };
|
|
12586
|
+
const nestedConditions = value.map((cond) => buildElemMatchConditions(cond, schema, baseFieldName));
|
|
12587
|
+
if (nestedConditions.some((f) => f === null))
|
|
12588
|
+
return null;
|
|
12589
|
+
const joiner = operator === "$and" ? " AND " : operator === "$or" ? " OR " : " AND NOT ";
|
|
12590
|
+
const sql = nestedConditions.map((f) => `(${f.sql})`).join(joiner);
|
|
12591
|
+
return {
|
|
12592
|
+
sql: `(${sql})`,
|
|
12593
|
+
args: nestedConditions.flatMap((f) => f.args)
|
|
12594
|
+
};
|
|
12595
|
+
}
|
|
12596
|
+
function handleFieldCondition(fieldName, value, schema, baseFieldName) {
|
|
12597
|
+
const propertyField = `json_extract(value, '$.${fieldName}')`;
|
|
12598
|
+
const nestedFieldName = `${baseFieldName}.${fieldName}`;
|
|
12599
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12600
|
+
if (value instanceof RegExp) {
|
|
12601
|
+
return translateLeafOperator("$regex", propertyField, value, schema, nestedFieldName);
|
|
12602
|
+
}
|
|
12603
|
+
if (value instanceof Date) {
|
|
12604
|
+
return translateLeafOperator("$eq", propertyField, value, schema, nestedFieldName);
|
|
12605
|
+
}
|
|
12606
|
+
const valueObj = value;
|
|
12607
|
+
if (Object.keys(valueObj).length === 0) {
|
|
12608
|
+
return { sql: "1=0", args: [] };
|
|
12609
|
+
}
|
|
12610
|
+
if (isOperatorObject(valueObj)) {
|
|
12611
|
+
const fragments2 = [];
|
|
12612
|
+
for (const [op, opValue] of Object.entries(valueObj)) {
|
|
12613
|
+
if (op === "$not") {
|
|
12614
|
+
const innerFragment = handleFieldCondition(fieldName, opValue, schema, baseFieldName);
|
|
12615
|
+
if (!innerFragment)
|
|
12616
|
+
return null;
|
|
12617
|
+
fragments2.push({ sql: `NOT (${innerFragment.sql})`, args: innerFragment.args });
|
|
12618
|
+
} else {
|
|
12619
|
+
const frag = translateLeafOperator(op, propertyField, opValue, schema, nestedFieldName);
|
|
12620
|
+
if (!frag)
|
|
12621
|
+
return null;
|
|
12622
|
+
fragments2.push(frag);
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
if (fragments2.some((f) => f === null))
|
|
12626
|
+
return null;
|
|
12627
|
+
return {
|
|
12628
|
+
sql: fragments2.map((f) => f.sql).join(" AND "),
|
|
12629
|
+
args: fragments2.flatMap((f) => f.args)
|
|
12630
|
+
};
|
|
12631
|
+
}
|
|
12632
|
+
const fragments = Object.entries(valueObj).map(([nestedKey, nestedValue]) => {
|
|
12633
|
+
const nestedField = `json_extract(value, '$.${fieldName}.${nestedKey}')`;
|
|
12634
|
+
return translateLeafOperator("$eq", nestedField, nestedValue, schema, `${nestedFieldName}.${nestedKey}`);
|
|
12635
|
+
});
|
|
12636
|
+
if (fragments.some((f) => f === null))
|
|
12637
|
+
return null;
|
|
12638
|
+
return {
|
|
12639
|
+
sql: fragments.map((f) => f.sql).join(" AND "),
|
|
12640
|
+
args: fragments.flatMap((f) => f.args)
|
|
12641
|
+
};
|
|
12642
|
+
}
|
|
12643
|
+
return translateLeafOperator("$eq", propertyField, value, schema, nestedFieldName);
|
|
12644
|
+
}
|
|
12508
12645
|
function buildElemMatchConditions(criteria, schema, baseFieldName) {
|
|
12509
12646
|
const conditions = [];
|
|
12510
12647
|
const args = [];
|
|
12511
12648
|
for (const [key, value] of Object.entries(criteria)) {
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12649
|
+
let fragment;
|
|
12650
|
+
if (isLogicalOperator(key)) {
|
|
12651
|
+
fragment = handleLogicalOperator(key, value, schema, baseFieldName);
|
|
12652
|
+
} else if (key.startsWith("$")) {
|
|
12653
|
+
fragment = translateLeafOperator(key, "value", value, schema, baseFieldName);
|
|
12516
12654
|
} else {
|
|
12517
|
-
|
|
12518
|
-
const nestedFieldName = `${baseFieldName}.${key}`;
|
|
12519
|
-
const fragment = processOperatorValue(propertyField, value, schema, nestedFieldName);
|
|
12520
|
-
conditions.push(fragment.sql);
|
|
12521
|
-
args.push(...fragment.args);
|
|
12655
|
+
fragment = handleFieldCondition(key, value, schema, baseFieldName);
|
|
12522
12656
|
}
|
|
12657
|
+
if (!fragment)
|
|
12658
|
+
return null;
|
|
12659
|
+
conditions.push(fragment.sql);
|
|
12660
|
+
args.push(...fragment.args);
|
|
12523
12661
|
}
|
|
12524
12662
|
return {
|
|
12525
12663
|
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
@@ -12538,6 +12676,8 @@ function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
|
12538
12676
|
}
|
|
12539
12677
|
if (criteria.$and && Array.isArray(criteria.$and)) {
|
|
12540
12678
|
const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12679
|
+
if (fragments.some((f) => f === null))
|
|
12680
|
+
return null;
|
|
12541
12681
|
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12542
12682
|
const args = fragments.flatMap((f) => f.args);
|
|
12543
12683
|
return {
|
|
@@ -12547,6 +12687,8 @@ function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
|
12547
12687
|
}
|
|
12548
12688
|
if (criteria.$or && Array.isArray(criteria.$or)) {
|
|
12549
12689
|
const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12690
|
+
if (fragments.some((f) => f === null))
|
|
12691
|
+
return null;
|
|
12550
12692
|
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12551
12693
|
const args = fragments.flatMap((f) => f.args);
|
|
12552
12694
|
return {
|
|
@@ -12556,6 +12698,8 @@ function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
|
12556
12698
|
}
|
|
12557
12699
|
if (criteria.$nor && Array.isArray(criteria.$nor)) {
|
|
12558
12700
|
const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12701
|
+
if (fragments.some((f) => f === null))
|
|
12702
|
+
return null;
|
|
12559
12703
|
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12560
12704
|
const args = fragments.flatMap((f) => f.args);
|
|
12561
12705
|
return {
|
|
@@ -12564,129 +12708,104 @@ function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
|
12564
12708
|
};
|
|
12565
12709
|
}
|
|
12566
12710
|
const fragment = buildElemMatchConditions(criteria, schema, actualFieldName);
|
|
12711
|
+
if (!fragment)
|
|
12712
|
+
return null;
|
|
12567
12713
|
return {
|
|
12568
12714
|
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${fragment.sql})`,
|
|
12569
12715
|
args: fragment.args
|
|
12570
12716
|
};
|
|
12571
12717
|
}
|
|
12572
|
-
function
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12718
|
+
function translateLeafOperator(op, field, value, schema, actualFieldName) {
|
|
12719
|
+
switch (op) {
|
|
12720
|
+
case "$eq":
|
|
12721
|
+
return translateEq(field, value, schema, actualFieldName);
|
|
12722
|
+
case "$ne":
|
|
12723
|
+
return translateNe(field, value, schema, actualFieldName);
|
|
12724
|
+
case "$gt":
|
|
12725
|
+
return translateGt(field, value, schema, actualFieldName);
|
|
12726
|
+
case "$gte":
|
|
12727
|
+
return translateGte(field, value, schema, actualFieldName);
|
|
12728
|
+
case "$lt":
|
|
12729
|
+
return translateLt(field, value, schema, actualFieldName);
|
|
12730
|
+
case "$lte":
|
|
12731
|
+
return translateLte(field, value, schema, actualFieldName);
|
|
12732
|
+
case "$in":
|
|
12733
|
+
return translateIn(field, value, schema, actualFieldName);
|
|
12734
|
+
case "$nin":
|
|
12735
|
+
return translateNin(field, value, schema, actualFieldName);
|
|
12736
|
+
case "$exists":
|
|
12737
|
+
return translateExists(field, value);
|
|
12738
|
+
case "$size":
|
|
12739
|
+
return translateSize(field, value);
|
|
12740
|
+
case "$mod": {
|
|
12741
|
+
const result = translateMod(field, value);
|
|
12742
|
+
if (!result)
|
|
12743
|
+
return translateEq(field, value, schema, actualFieldName);
|
|
12744
|
+
return result;
|
|
12745
|
+
}
|
|
12746
|
+
case "$regex": {
|
|
12747
|
+
let pattern;
|
|
12748
|
+
let options;
|
|
12749
|
+
if (value instanceof RegExp) {
|
|
12750
|
+
pattern = value.source;
|
|
12751
|
+
options = value.flags;
|
|
12752
|
+
} else if (typeof value === "string") {
|
|
12753
|
+
pattern = value;
|
|
12754
|
+
} else if (typeof value === "object" && value !== null) {
|
|
12755
|
+
const regexObj = value;
|
|
12756
|
+
pattern = regexObj.pattern || regexObj.$regex;
|
|
12757
|
+
options = regexObj.$options;
|
|
12758
|
+
} else {
|
|
12759
|
+
return null;
|
|
12606
12760
|
}
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
}
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12761
|
+
return translateRegex(field, pattern, options, schema, actualFieldName);
|
|
12762
|
+
}
|
|
12763
|
+
case "$type": {
|
|
12764
|
+
let jsonCol = "data";
|
|
12765
|
+
let path2 = `$.${actualFieldName}`;
|
|
12766
|
+
let useDirectType = false;
|
|
12767
|
+
if (field === "value") {
|
|
12768
|
+
jsonCol = "value";
|
|
12769
|
+
path2 = "";
|
|
12770
|
+
useDirectType = true;
|
|
12771
|
+
} else if (field.startsWith("json_extract(")) {
|
|
12772
|
+
const match = field.match(/json_extract\(([^,]+),\s*'([^']+)'\)/);
|
|
12773
|
+
if (match && match[1] && match[2]) {
|
|
12774
|
+
jsonCol = match[1];
|
|
12775
|
+
path2 = match[2];
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12778
|
+
if (useDirectType) {
|
|
12779
|
+
const typeMap = {
|
|
12780
|
+
null: "null",
|
|
12781
|
+
boolean: "true",
|
|
12782
|
+
number: "integer",
|
|
12783
|
+
string: "text",
|
|
12784
|
+
array: "array",
|
|
12785
|
+
object: "object"
|
|
12786
|
+
};
|
|
12787
|
+
const sqlType = typeMap[value];
|
|
12788
|
+
if (!sqlType)
|
|
12789
|
+
return { sql: "1=0", args: [] };
|
|
12790
|
+
if (value === "boolean") {
|
|
12791
|
+
return { sql: `(type IN ('true', 'false'))`, args: [] };
|
|
12626
12792
|
}
|
|
12627
|
-
if (
|
|
12628
|
-
|
|
12629
|
-
null: "null",
|
|
12630
|
-
boolean: "true",
|
|
12631
|
-
number: "integer",
|
|
12632
|
-
string: "text",
|
|
12633
|
-
array: "array",
|
|
12634
|
-
object: "object"
|
|
12635
|
-
};
|
|
12636
|
-
const sqlType = typeMap[opValue];
|
|
12637
|
-
if (!sqlType)
|
|
12638
|
-
return { sql: "1=0", args: [] };
|
|
12639
|
-
if (opValue === "boolean") {
|
|
12640
|
-
return { sql: `(type IN ('true', 'false'))`, args: [] };
|
|
12641
|
-
}
|
|
12642
|
-
if (opValue === "number") {
|
|
12643
|
-
return { sql: `(type IN ('integer', 'real'))`, args: [] };
|
|
12644
|
-
}
|
|
12645
|
-
return { sql: `type = '${sqlType}'`, args: [] };
|
|
12793
|
+
if (value === "number") {
|
|
12794
|
+
return { sql: `(type IN ('integer', 'real'))`, args: [] };
|
|
12646
12795
|
}
|
|
12647
|
-
|
|
12648
|
-
return typeFragment || { sql: "1=0", args: [] };
|
|
12649
|
-
}
|
|
12650
|
-
case "$elemMatch": {
|
|
12651
|
-
const elemMatchFragment = translateElemMatch(field, opValue, schema, actualFieldName);
|
|
12652
|
-
return elemMatchFragment || { sql: "1=0", args: [] };
|
|
12653
|
-
}
|
|
12654
|
-
case "$not": {
|
|
12655
|
-
const result = translateNot(field, opValue, schema, actualFieldName);
|
|
12656
|
-
if (!result)
|
|
12657
|
-
return translateEq(field, opValue, schema, actualFieldName);
|
|
12658
|
-
return result;
|
|
12796
|
+
return { sql: `type = '${sqlType}'`, args: [] };
|
|
12659
12797
|
}
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
return translateEq(field, opValue, schema, actualFieldName);
|
|
12663
|
-
const fragments = opValue.map((v) => processOperatorValue(field, v, schema, actualFieldName));
|
|
12664
|
-
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12665
|
-
const args = fragments.flatMap((f) => f.args);
|
|
12666
|
-
return { sql: `(${sql})`, args };
|
|
12667
|
-
}
|
|
12668
|
-
case "$or": {
|
|
12669
|
-
if (!Array.isArray(opValue))
|
|
12670
|
-
return translateEq(field, opValue, schema, actualFieldName);
|
|
12671
|
-
const fragments = opValue.map((v) => processOperatorValue(field, v, schema, actualFieldName));
|
|
12672
|
-
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12673
|
-
const args = fragments.flatMap((f) => f.args);
|
|
12674
|
-
return { sql: `(${sql})`, args };
|
|
12675
|
-
}
|
|
12676
|
-
default:
|
|
12677
|
-
return translateEq(field, opValue, schema, actualFieldName);
|
|
12798
|
+
const typeFragment = translateType(jsonCol, path2, value, true);
|
|
12799
|
+
return typeFragment || { sql: "1=0", args: [] };
|
|
12678
12800
|
}
|
|
12801
|
+
default:
|
|
12802
|
+
return translateEq(field, value, schema, actualFieldName);
|
|
12679
12803
|
}
|
|
12680
|
-
return translateEq(field, value, schema, actualFieldName);
|
|
12681
12804
|
}
|
|
12682
|
-
function
|
|
12683
|
-
if (criteria === undefined || criteria === null || typeof criteria !== "object" || Array.isArray(criteria) || Object.keys(criteria).length === 0) {
|
|
12684
|
-
return { sql: "1=0", args: [] };
|
|
12685
|
-
}
|
|
12686
|
-
const inner = processOperatorValue(field, criteria, schema, actualFieldName);
|
|
12805
|
+
function wrapWithNot(innerFragment) {
|
|
12687
12806
|
return {
|
|
12688
|
-
sql: `NOT (${
|
|
12689
|
-
args:
|
|
12807
|
+
sql: `NOT (${innerFragment.sql})`,
|
|
12808
|
+
args: innerFragment.args
|
|
12690
12809
|
};
|
|
12691
12810
|
}
|
|
12692
12811
|
function translateType(jsonColumn, fieldName, type6, isDirectPath = false) {
|
|
@@ -12800,6 +12919,14 @@ function _stringify(value, stack) {
|
|
|
12800
12919
|
}
|
|
12801
12920
|
const objType = Object.prototype.toString.call(obj);
|
|
12802
12921
|
if (objType !== "[object Object]") {
|
|
12922
|
+
if (obj instanceof RegExp) {
|
|
12923
|
+
stack.pop();
|
|
12924
|
+
return `{"$regex":${strEscape(obj.source)},"$options":${strEscape(obj.flags)}}`;
|
|
12925
|
+
}
|
|
12926
|
+
if (obj instanceof Date) {
|
|
12927
|
+
stack.pop();
|
|
12928
|
+
return strEscape(obj.toISOString());
|
|
12929
|
+
}
|
|
12803
12930
|
const result = JSON.stringify(obj);
|
|
12804
12931
|
stack.pop();
|
|
12805
12932
|
return result;
|
|
@@ -12822,27 +12949,27 @@ function _stringify(value, stack) {
|
|
|
12822
12949
|
}
|
|
12823
12950
|
|
|
12824
12951
|
// src/query/builder.ts
|
|
12825
|
-
var QUERY_CACHE = new Map;
|
|
12826
12952
|
var MAX_CACHE_SIZE = 1000;
|
|
12827
|
-
|
|
12953
|
+
var GLOBAL_CACHE = new Map;
|
|
12954
|
+
function buildWhereClause(selector, schema, collectionName, cache = GLOBAL_CACHE) {
|
|
12828
12955
|
if (!selector || typeof selector !== "object")
|
|
12829
12956
|
return null;
|
|
12830
12957
|
const cacheKey = `v${schema.version}_${collectionName}_${stableStringify(selector)}`;
|
|
12831
|
-
const cached =
|
|
12832
|
-
if (cached) {
|
|
12833
|
-
|
|
12834
|
-
|
|
12958
|
+
const cached = cache.get(cacheKey);
|
|
12959
|
+
if (cached !== undefined) {
|
|
12960
|
+
cache.delete(cacheKey);
|
|
12961
|
+
cache.set(cacheKey, cached);
|
|
12835
12962
|
return cached;
|
|
12836
12963
|
}
|
|
12837
12964
|
const result = processSelector(selector, schema, 0);
|
|
12838
12965
|
if (!result)
|
|
12839
12966
|
return null;
|
|
12840
|
-
if (
|
|
12841
|
-
const firstKey =
|
|
12967
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
12968
|
+
const firstKey = cache.keys().next().value;
|
|
12842
12969
|
if (firstKey)
|
|
12843
|
-
|
|
12970
|
+
cache.delete(firstKey);
|
|
12844
12971
|
}
|
|
12845
|
-
|
|
12972
|
+
cache.set(cacheKey, result);
|
|
12846
12973
|
return result;
|
|
12847
12974
|
}
|
|
12848
12975
|
function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
|
|
@@ -12896,80 +13023,124 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12896
13023
|
if (Object.keys(value).length === 0) {
|
|
12897
13024
|
return { sql: "1=0", args: [] };
|
|
12898
13025
|
}
|
|
13026
|
+
const fieldFragments = [];
|
|
12899
13027
|
for (const [op, opValue] of Object.entries(value)) {
|
|
12900
13028
|
let fragment;
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
case "$ne":
|
|
12906
|
-
fragment = translateNe(fieldName, opValue, schema, actualFieldName);
|
|
12907
|
-
break;
|
|
12908
|
-
case "$gt":
|
|
12909
|
-
fragment = translateGt(fieldName, opValue, schema, actualFieldName);
|
|
12910
|
-
break;
|
|
12911
|
-
case "$gte":
|
|
12912
|
-
fragment = translateGte(fieldName, opValue, schema, actualFieldName);
|
|
12913
|
-
break;
|
|
12914
|
-
case "$lt":
|
|
12915
|
-
fragment = translateLt(fieldName, opValue, schema, actualFieldName);
|
|
12916
|
-
break;
|
|
12917
|
-
case "$lte":
|
|
12918
|
-
fragment = translateLte(fieldName, opValue, schema, actualFieldName);
|
|
12919
|
-
break;
|
|
12920
|
-
case "$in":
|
|
12921
|
-
fragment = translateIn(fieldName, opValue, schema, actualFieldName);
|
|
12922
|
-
break;
|
|
12923
|
-
case "$nin":
|
|
12924
|
-
fragment = translateNin(fieldName, opValue, schema, actualFieldName);
|
|
12925
|
-
break;
|
|
12926
|
-
case "$exists":
|
|
12927
|
-
fragment = translateExists(fieldName, opValue);
|
|
12928
|
-
break;
|
|
12929
|
-
case "$regex":
|
|
12930
|
-
const options = value.$options;
|
|
12931
|
-
const regexFragment = translateRegex(fieldName, opValue, options, schema, actualFieldName);
|
|
12932
|
-
if (!regexFragment)
|
|
13029
|
+
if (op === "$not") {
|
|
13030
|
+
if (typeof opValue !== "object" || opValue === null || Array.isArray(opValue)) {
|
|
13031
|
+
const eqFrag = translateLeafOperator("$eq", fieldName, opValue, schema, actualFieldName);
|
|
13032
|
+
if (!eqFrag)
|
|
12933
13033
|
return null;
|
|
12934
|
-
fragment =
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
if (!elemMatchFragment)
|
|
13034
|
+
fragment = wrapWithNot(eqFrag);
|
|
13035
|
+
} else if (opValue instanceof Date) {
|
|
13036
|
+
const eqFrag = translateLeafOperator("$eq", fieldName, opValue, schema, actualFieldName);
|
|
13037
|
+
if (!eqFrag)
|
|
12939
13038
|
return null;
|
|
12940
|
-
fragment =
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
if (!notResult)
|
|
13039
|
+
fragment = wrapWithNot(eqFrag);
|
|
13040
|
+
} else if (opValue instanceof RegExp) {
|
|
13041
|
+
const regexFrag = translateLeafOperator("$regex", fieldName, opValue, schema, actualFieldName);
|
|
13042
|
+
if (!regexFrag)
|
|
12945
13043
|
return null;
|
|
12946
|
-
fragment =
|
|
12947
|
-
|
|
13044
|
+
fragment = wrapWithNot(regexFrag);
|
|
13045
|
+
} else {
|
|
13046
|
+
const opValueObj = opValue;
|
|
13047
|
+
const innerKeys = Object.keys(opValueObj);
|
|
13048
|
+
if (innerKeys.length === 0) {
|
|
13049
|
+
fragment = { sql: "1=0", args: [] };
|
|
13050
|
+
} else if (innerKeys.some((k) => k === "$and" || k === "$or" || k === "$nor")) {
|
|
13051
|
+
let recursivelyWrapLeafOperators = function(items2, field2) {
|
|
13052
|
+
return items2.map((item) => {
|
|
13053
|
+
const keys = Object.keys(item);
|
|
13054
|
+
if (keys.some((k) => !k.startsWith("$")))
|
|
13055
|
+
return item;
|
|
13056
|
+
if (keys.length === 1 && LOGICAL_OPS.has(keys[0])) {
|
|
13057
|
+
const logicalOp2 = keys[0];
|
|
13058
|
+
return { [logicalOp2]: recursivelyWrapLeafOperators(item[logicalOp2], field2) };
|
|
13059
|
+
}
|
|
13060
|
+
return { [field2]: item };
|
|
13061
|
+
});
|
|
13062
|
+
};
|
|
13063
|
+
const logicalOp = innerKeys[0];
|
|
13064
|
+
const nestedSelector = opValueObj;
|
|
13065
|
+
const items = nestedSelector[logicalOp];
|
|
13066
|
+
const LOGICAL_OPS = new Set(["$and", "$or", "$nor"]);
|
|
13067
|
+
const wrappedItems = recursivelyWrapLeafOperators(items, field);
|
|
13068
|
+
const innerFragment = processSelector({ [logicalOp]: wrappedItems }, schema, logicalDepth + 1);
|
|
13069
|
+
if (!innerFragment)
|
|
13070
|
+
return null;
|
|
13071
|
+
fragment = wrapWithNot(innerFragment);
|
|
13072
|
+
} else if (innerKeys.length === 1 && innerKeys[0] === "$elemMatch") {
|
|
13073
|
+
const elemMatchFragment = translateElemMatch(fieldName, opValueObj.$elemMatch, schema, actualFieldName);
|
|
13074
|
+
if (!elemMatchFragment)
|
|
13075
|
+
return null;
|
|
13076
|
+
fragment = wrapWithNot(elemMatchFragment);
|
|
13077
|
+
} else {
|
|
13078
|
+
const hasOperators = innerKeys.some((k) => k.startsWith("$"));
|
|
13079
|
+
if (!hasOperators) {
|
|
13080
|
+
const eqFrag = translateLeafOperator("$eq", fieldName, opValueObj, schema, actualFieldName);
|
|
13081
|
+
if (!eqFrag)
|
|
13082
|
+
return null;
|
|
13083
|
+
fragment = wrapWithNot(eqFrag);
|
|
13084
|
+
} else {
|
|
13085
|
+
const [[innerOp, innerVal]] = Object.entries(opValueObj);
|
|
13086
|
+
const innerFrag = translateLeafOperator(innerOp, fieldName, innerVal, schema, actualFieldName);
|
|
13087
|
+
if (!innerFrag)
|
|
13088
|
+
return null;
|
|
13089
|
+
fragment = wrapWithNot(innerFrag);
|
|
13090
|
+
}
|
|
13091
|
+
}
|
|
12948
13092
|
}
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
fragment = modResult;
|
|
12963
|
-
break;
|
|
13093
|
+
} else if (op === "$elemMatch") {
|
|
13094
|
+
const elemMatchFragment = translateElemMatch(fieldName, opValue, schema, actualFieldName);
|
|
13095
|
+
if (!elemMatchFragment)
|
|
13096
|
+
return null;
|
|
13097
|
+
fragment = elemMatchFragment;
|
|
13098
|
+
} else if (op === "$regex") {
|
|
13099
|
+
const regexValue = opValue;
|
|
13100
|
+
const optionsValue = value.$options;
|
|
13101
|
+
let combinedValue;
|
|
13102
|
+
if (typeof regexValue === "string" && optionsValue) {
|
|
13103
|
+
combinedValue = { $regex: regexValue, $options: optionsValue };
|
|
13104
|
+
} else {
|
|
13105
|
+
combinedValue = regexValue;
|
|
12964
13106
|
}
|
|
12965
|
-
|
|
12966
|
-
|
|
13107
|
+
const leafFrag = translateLeafOperator("$regex", fieldName, combinedValue, schema, actualFieldName);
|
|
13108
|
+
if (!leafFrag)
|
|
13109
|
+
return null;
|
|
13110
|
+
fragment = leafFrag;
|
|
13111
|
+
} else if (op === "$options") {
|
|
13112
|
+
continue;
|
|
13113
|
+
} else if (!op.startsWith("$")) {
|
|
13114
|
+
const jsonPath = `json_extract(${fieldName}, '$.${op}')`;
|
|
13115
|
+
const nestedFieldName = `${actualFieldName}.${op}`;
|
|
13116
|
+
const leafFrag = translateLeafOperator("$eq", jsonPath, opValue, schema, nestedFieldName);
|
|
13117
|
+
if (!leafFrag)
|
|
13118
|
+
return null;
|
|
13119
|
+
fragment = leafFrag;
|
|
13120
|
+
} else {
|
|
13121
|
+
const leafFrag = translateLeafOperator(op, fieldName, opValue, schema, actualFieldName);
|
|
13122
|
+
if (!leafFrag)
|
|
13123
|
+
return null;
|
|
13124
|
+
fragment = leafFrag;
|
|
13125
|
+
}
|
|
13126
|
+
if (fieldFragments.length > 0) {
|
|
13127
|
+
const prev = fieldFragments.pop();
|
|
13128
|
+
fieldFragments.push({
|
|
13129
|
+
sql: `(${prev.sql} AND ${fragment.sql})`,
|
|
13130
|
+
args: [...prev.args, ...fragment.args]
|
|
13131
|
+
});
|
|
13132
|
+
} else {
|
|
13133
|
+
fieldFragments.push(fragment);
|
|
12967
13134
|
}
|
|
12968
|
-
conditions.push(fragment.sql);
|
|
12969
|
-
args.push(...fragment.args);
|
|
12970
13135
|
}
|
|
13136
|
+
fieldFragments.forEach((f) => {
|
|
13137
|
+
conditions.push(f.sql);
|
|
13138
|
+
args.push(...f.args);
|
|
13139
|
+
});
|
|
12971
13140
|
} else {
|
|
12972
|
-
const fragment =
|
|
13141
|
+
const fragment = translateLeafOperator("$eq", fieldName, value, schema, actualFieldName);
|
|
13142
|
+
if (!fragment)
|
|
13143
|
+
return null;
|
|
12973
13144
|
conditions.push(fragment.sql);
|
|
12974
13145
|
args.push(...fragment.args);
|
|
12975
13146
|
}
|
|
@@ -13423,6 +13594,7 @@ class BunSQLiteStorageInstance {
|
|
|
13423
13594
|
db;
|
|
13424
13595
|
stmtManager;
|
|
13425
13596
|
changeStream$ = new import_rxjs2.Subject;
|
|
13597
|
+
queryCache = new Map;
|
|
13426
13598
|
databaseName;
|
|
13427
13599
|
collectionName;
|
|
13428
13600
|
schema;
|
|
@@ -13590,7 +13762,7 @@ class BunSQLiteStorageInstance {
|
|
|
13590
13762
|
return rows.map((row) => JSON.parse(row.data));
|
|
13591
13763
|
}
|
|
13592
13764
|
async query(preparedQuery) {
|
|
13593
|
-
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13765
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13594
13766
|
if (!whereResult) {
|
|
13595
13767
|
return this.queryWithOurMemory(preparedQuery);
|
|
13596
13768
|
}
|
|
@@ -13645,7 +13817,7 @@ class BunSQLiteStorageInstance {
|
|
|
13645
13817
|
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13646
13818
|
}
|
|
13647
13819
|
async count(preparedQuery) {
|
|
13648
|
-
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13820
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13649
13821
|
if (!whereResult) {
|
|
13650
13822
|
const allDocs = await this.queryWithOurMemory(preparedQuery);
|
|
13651
13823
|
return {
|
|
@@ -13680,12 +13852,16 @@ class BunSQLiteStorageInstance {
|
|
|
13680
13852
|
if (this.closed)
|
|
13681
13853
|
return this.closed;
|
|
13682
13854
|
this.closed = (async () => {
|
|
13855
|
+
this.queryCache.clear();
|
|
13683
13856
|
this.changeStream$.complete();
|
|
13684
13857
|
this.stmtManager.close();
|
|
13685
13858
|
releaseDatabase(this.databaseName);
|
|
13686
13859
|
})();
|
|
13687
13860
|
return this.closed;
|
|
13688
13861
|
}
|
|
13862
|
+
getCacheSize() {
|
|
13863
|
+
return this.queryCache.size;
|
|
13864
|
+
}
|
|
13689
13865
|
async remove() {
|
|
13690
13866
|
if (this.closed)
|
|
13691
13867
|
throw new Error("already closed");
|
package/dist/src/instance.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare class BunSQLiteStorageInstance<RxDocType> implements RxStorageIns
|
|
|
5
5
|
private db;
|
|
6
6
|
private stmtManager;
|
|
7
7
|
private changeStream$;
|
|
8
|
+
private queryCache;
|
|
8
9
|
readonly databaseName: string;
|
|
9
10
|
readonly collectionName: string;
|
|
10
11
|
readonly schema: Readonly<RxJsonSchema<RxDocumentData<RxDocType>>>;
|
|
@@ -24,6 +25,7 @@ export declare class BunSQLiteStorageInstance<RxDocType> implements RxStorageIns
|
|
|
24
25
|
changeStream(): Observable<EventBulk<RxStorageChangeEvent<RxDocumentData<RxDocType>>, RxStorageDefaultCheckpoint>>;
|
|
25
26
|
cleanup(minimumDeletedTime: number): Promise<boolean>;
|
|
26
27
|
close(): Promise<void>;
|
|
28
|
+
getCacheSize(): number;
|
|
27
29
|
remove(): Promise<void>;
|
|
28
30
|
private attachmentMapKey;
|
|
29
31
|
getAttachmentData(documentId: string, attachmentId: string, digest: string): Promise<string>;
|
|
@@ -2,6 +2,6 @@ import type { RxJsonSchema, MangoQuerySelector, RxDocumentData } from 'rxdb';
|
|
|
2
2
|
import type { SqlFragment } from './operators';
|
|
3
3
|
export declare function getCacheSize(): number;
|
|
4
4
|
export declare function clearCache(): void;
|
|
5
|
-
export declare function buildWhereClause<RxDocType>(selector: MangoQuerySelector<RxDocumentData<RxDocType>>, schema: RxJsonSchema<RxDocumentData<RxDocType>>, collectionName: string): SqlFragment | null;
|
|
5
|
+
export declare function buildWhereClause<RxDocType>(selector: MangoQuerySelector<RxDocumentData<RxDocType>>, schema: RxJsonSchema<RxDocumentData<RxDocType>>, collectionName: string, cache?: Map<string, SqlFragment | null>): SqlFragment | null;
|
|
6
6
|
export declare function buildLogicalOperator<RxDocType>(operator: 'or' | 'nor' | 'and', conditions: MangoQuerySelector<RxDocumentData<RxDocType>>[], schema: RxJsonSchema<RxDocumentData<RxDocType>>, logicalDepth: number): SqlFragment | null;
|
|
7
7
|
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -19,7 +19,8 @@ export declare function translateNin<RxDocType>(field: string, values: unknown[]
|
|
|
19
19
|
export declare function translateExists(field: string, exists: boolean): SqlFragment;
|
|
20
20
|
export declare function translateRegex<RxDocType>(field: string, pattern: string, options: string | undefined, schema: RxJsonSchema<RxDocumentData<RxDocType>>, fieldName: string): SqlFragment | null;
|
|
21
21
|
export declare function translateElemMatch<RxDocType>(field: string, criteria: ElemMatchCriteria, schema: RxJsonSchema<RxDocumentData<RxDocType>>, actualFieldName: string): SqlFragment | null;
|
|
22
|
-
export declare function
|
|
22
|
+
export declare function translateLeafOperator<RxDocType>(op: string, field: string, value: unknown, schema: RxJsonSchema<RxDocumentData<RxDocType>>, actualFieldName: string): SqlFragment | null;
|
|
23
|
+
export declare function wrapWithNot(innerFragment: SqlFragment): SqlFragment;
|
|
23
24
|
export declare function translateType(jsonColumn: string, fieldName: string, type: string, isDirectPath?: boolean): SqlFragment | null;
|
|
24
25
|
export declare function translateSize(field: string, size: number): SqlFragment;
|
|
25
26
|
export declare function translateMod(field: string, value: unknown): SqlFragment | null;
|
|
@@ -3,5 +3,6 @@ export interface SqlFragment {
|
|
|
3
3
|
sql: string;
|
|
4
4
|
args: (string | number | boolean)[];
|
|
5
5
|
}
|
|
6
|
+
export declare function clearRegexCache(): void;
|
|
6
7
|
export declare function smartRegexToLike<RxDocType>(field: string, pattern: string, options: string | undefined, schema: RxJsonSchema<RxDocumentData<RxDocType>>, fieldName: string): SqlFragment | null;
|
|
7
8
|
//# sourceMappingURL=smart-regex.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-sqlite-for-rxdb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"author": "adam2am",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/adam2am/bun-sqlite-for-rxdb.git"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "bun build src/index.ts --outdir dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11
|
+
"test": "bun test test/",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
9
14
|
"main": "dist/index.js",
|
|
10
15
|
"dependencies": {},
|
|
11
16
|
"devDependencies": {
|
|
@@ -45,11 +50,6 @@
|
|
|
45
50
|
"README.md",
|
|
46
51
|
"LICENSE"
|
|
47
52
|
],
|
|
48
|
-
"scripts": {
|
|
49
|
-
"build": "bun build src/index.ts --outdir dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
50
|
-
"test": "bun test src/",
|
|
51
|
-
"typecheck": "tsc --noEmit"
|
|
52
|
-
},
|
|
53
53
|
"type": "module",
|
|
54
54
|
"types": "dist/index.d.ts"
|
|
55
55
|
}
|