bun-sqlite-for-rxdb 1.3.1 → 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 +642 -232
- package/dist/src/instance.d.ts +2 -1
- package/dist/src/query/builder.d.ts +2 -1
- package/dist/src/query/lightweight-matcher.d.ts +3 -0
- package/dist/src/query/operators.d.ts +12 -11
- package/dist/src/query/schema-mapper.d.ts +1 -1
- package/dist/src/query/smart-regex.d.ts +1 -0
- package/package.json +10 -8
- package/CHANGELOG.md +0 -722
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;
|
|
@@ -12254,6 +12283,13 @@ function getColumnInfo(path2, schema) {
|
|
|
12254
12283
|
if (path2 === schema.primaryKey) {
|
|
12255
12284
|
return { column: "id", type: "string" };
|
|
12256
12285
|
}
|
|
12286
|
+
const properties = schema.properties;
|
|
12287
|
+
const fieldSchema = properties?.[path2];
|
|
12288
|
+
if (fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema) {
|
|
12289
|
+
if (fieldSchema.type === "array") {
|
|
12290
|
+
return { jsonPath: `$.${path2}`, type: "array" };
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12257
12293
|
return { jsonPath: `$.${path2}`, type: "unknown" };
|
|
12258
12294
|
}
|
|
12259
12295
|
|
|
@@ -12295,6 +12331,14 @@ function hasExpressionIndex(fieldName, schema) {
|
|
|
12295
12331
|
INDEX_CACHE.set(cacheKey, hasLowerIndex);
|
|
12296
12332
|
return hasLowerIndex;
|
|
12297
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
|
+
}
|
|
12298
12342
|
function isComplexRegex(pattern) {
|
|
12299
12343
|
return /[*+?()[\]{}|]/.test(pattern.replace(/\\\./g, ""));
|
|
12300
12344
|
}
|
|
@@ -12304,6 +12348,9 @@ function escapeForLike(str) {
|
|
|
12304
12348
|
function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
12305
12349
|
if (typeof pattern !== "string")
|
|
12306
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
|
+
}
|
|
12307
12354
|
const caseInsensitive = options?.includes("i") ?? false;
|
|
12308
12355
|
const hasLowerIndex = hasExpressionIndex(fieldName, schema);
|
|
12309
12356
|
const startsWithAnchor = pattern.startsWith("^");
|
|
@@ -12342,67 +12389,161 @@ function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
|
12342
12389
|
}
|
|
12343
12390
|
|
|
12344
12391
|
// src/query/operators.ts
|
|
12345
|
-
function
|
|
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
|
+
}
|
|
12404
|
+
function translateEq(field, value, schema, actualFieldName) {
|
|
12346
12405
|
if (value === null) {
|
|
12347
12406
|
return { sql: `${field} IS NULL`, args: [] };
|
|
12348
12407
|
}
|
|
12349
|
-
|
|
12408
|
+
if (schema && actualFieldName) {
|
|
12409
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12410
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12411
|
+
return {
|
|
12412
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12413
|
+
args: [normalizeValueForSQLite(value)]
|
|
12414
|
+
};
|
|
12415
|
+
}
|
|
12416
|
+
}
|
|
12417
|
+
return { sql: `${field} = ?`, args: [normalizeValueForSQLite(value)] };
|
|
12350
12418
|
}
|
|
12351
|
-
function translateNe(field, value) {
|
|
12419
|
+
function translateNe(field, value, schema, actualFieldName) {
|
|
12352
12420
|
if (value === null) {
|
|
12353
12421
|
return { sql: `${field} IS NOT NULL`, args: [] };
|
|
12354
12422
|
}
|
|
12355
|
-
|
|
12423
|
+
if (schema && actualFieldName) {
|
|
12424
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12425
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12426
|
+
return {
|
|
12427
|
+
sql: `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12428
|
+
args: [normalizeValueForSQLite(value)]
|
|
12429
|
+
};
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12432
|
+
return { sql: `(${field} <> ? OR ${field} IS NULL)`, args: [normalizeValueForSQLite(value)] };
|
|
12356
12433
|
}
|
|
12357
|
-
function translateGt(field, value) {
|
|
12358
|
-
|
|
12434
|
+
function translateGt(field, value, schema, actualFieldName) {
|
|
12435
|
+
if (schema && actualFieldName) {
|
|
12436
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12437
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12438
|
+
return {
|
|
12439
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value > ?)`,
|
|
12440
|
+
args: [normalizeValueForSQLite(value)]
|
|
12441
|
+
};
|
|
12442
|
+
}
|
|
12443
|
+
}
|
|
12444
|
+
return { sql: `${field} > ?`, args: [normalizeValueForSQLite(value)] };
|
|
12359
12445
|
}
|
|
12360
|
-
function translateGte(field, value) {
|
|
12361
|
-
|
|
12446
|
+
function translateGte(field, value, schema, actualFieldName) {
|
|
12447
|
+
if (schema && actualFieldName) {
|
|
12448
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12449
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12450
|
+
return {
|
|
12451
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value >= ?)`,
|
|
12452
|
+
args: [normalizeValueForSQLite(value)]
|
|
12453
|
+
};
|
|
12454
|
+
}
|
|
12455
|
+
}
|
|
12456
|
+
return { sql: `${field} >= ?`, args: [normalizeValueForSQLite(value)] };
|
|
12362
12457
|
}
|
|
12363
|
-
function translateLt(field, value) {
|
|
12364
|
-
|
|
12458
|
+
function translateLt(field, value, schema, actualFieldName) {
|
|
12459
|
+
if (schema && actualFieldName) {
|
|
12460
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12461
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12462
|
+
return {
|
|
12463
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value < ?)`,
|
|
12464
|
+
args: [normalizeValueForSQLite(value)]
|
|
12465
|
+
};
|
|
12466
|
+
}
|
|
12467
|
+
}
|
|
12468
|
+
return { sql: `${field} < ?`, args: [normalizeValueForSQLite(value)] };
|
|
12365
12469
|
}
|
|
12366
|
-
function translateLte(field, value) {
|
|
12367
|
-
|
|
12470
|
+
function translateLte(field, value, schema, actualFieldName) {
|
|
12471
|
+
if (schema && actualFieldName) {
|
|
12472
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12473
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12474
|
+
return {
|
|
12475
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value <= ?)`,
|
|
12476
|
+
args: [normalizeValueForSQLite(value)]
|
|
12477
|
+
};
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12480
|
+
return { sql: `${field} <= ?`, args: [normalizeValueForSQLite(value)] };
|
|
12368
12481
|
}
|
|
12369
|
-
function translateIn(field, values) {
|
|
12482
|
+
function translateIn(field, values, schema, actualFieldName) {
|
|
12370
12483
|
if (!Array.isArray(values) || values.length === 0) {
|
|
12371
12484
|
return { sql: "1=0", args: [] };
|
|
12372
12485
|
}
|
|
12373
12486
|
const hasNull = values.includes(null);
|
|
12374
|
-
const nonNullValues = values.filter((v) => v !== null);
|
|
12487
|
+
const nonNullValues = values.filter((v) => v !== null).map((v) => normalizeValueForSQLite(v));
|
|
12375
12488
|
if (nonNullValues.length === 0) {
|
|
12376
12489
|
return { sql: `${field} IS NULL`, args: [] };
|
|
12377
12490
|
}
|
|
12378
|
-
|
|
12379
|
-
|
|
12491
|
+
if (schema && actualFieldName) {
|
|
12492
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12493
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12494
|
+
const inClause2 = `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
|
|
12495
|
+
const args2 = [JSON.stringify(nonNullValues)];
|
|
12496
|
+
if (hasNull) {
|
|
12497
|
+
return {
|
|
12498
|
+
sql: `(${inClause2} OR ${field} IS NULL)`,
|
|
12499
|
+
args: args2
|
|
12500
|
+
};
|
|
12501
|
+
}
|
|
12502
|
+
return { sql: inClause2, args: args2 };
|
|
12503
|
+
}
|
|
12504
|
+
}
|
|
12505
|
+
const inClause = `${field} IN (SELECT value FROM json_each(?))`;
|
|
12506
|
+
const args = [JSON.stringify(nonNullValues)];
|
|
12380
12507
|
if (hasNull) {
|
|
12381
12508
|
return {
|
|
12382
12509
|
sql: `(${inClause} OR ${field} IS NULL)`,
|
|
12383
|
-
args
|
|
12510
|
+
args
|
|
12384
12511
|
};
|
|
12385
12512
|
}
|
|
12386
|
-
return { sql: inClause, args
|
|
12513
|
+
return { sql: inClause, args };
|
|
12387
12514
|
}
|
|
12388
|
-
function translateNin(field, values) {
|
|
12515
|
+
function translateNin(field, values, schema, actualFieldName) {
|
|
12389
12516
|
if (!Array.isArray(values) || values.length === 0) {
|
|
12390
12517
|
return { sql: "1=1", args: [] };
|
|
12391
12518
|
}
|
|
12392
12519
|
const hasNull = values.includes(null);
|
|
12393
|
-
const nonNullValues = values.filter((v) => v !== null);
|
|
12520
|
+
const nonNullValues = values.filter((v) => v !== null).map((v) => normalizeValueForSQLite(v));
|
|
12394
12521
|
if (nonNullValues.length === 0) {
|
|
12395
12522
|
return { sql: `${field} IS NOT NULL`, args: [] };
|
|
12396
12523
|
}
|
|
12397
|
-
|
|
12398
|
-
|
|
12524
|
+
if (schema && actualFieldName) {
|
|
12525
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12526
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12527
|
+
const ninClause2 = `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
|
|
12528
|
+
const args2 = [JSON.stringify(nonNullValues)];
|
|
12529
|
+
if (hasNull) {
|
|
12530
|
+
return {
|
|
12531
|
+
sql: `(${ninClause2} AND ${field} IS NOT NULL)`,
|
|
12532
|
+
args: args2
|
|
12533
|
+
};
|
|
12534
|
+
}
|
|
12535
|
+
return { sql: `(${field} IS NULL OR ${ninClause2})`, args: args2 };
|
|
12536
|
+
}
|
|
12537
|
+
}
|
|
12538
|
+
const ninClause = `${field} NOT IN (SELECT value FROM json_each(?))`;
|
|
12539
|
+
const args = [JSON.stringify(nonNullValues)];
|
|
12399
12540
|
if (hasNull) {
|
|
12400
12541
|
return {
|
|
12401
12542
|
sql: `(${ninClause} AND ${field} IS NOT NULL)`,
|
|
12402
|
-
args
|
|
12543
|
+
args
|
|
12403
12544
|
};
|
|
12404
12545
|
}
|
|
12405
|
-
return { sql: ninClause
|
|
12546
|
+
return { sql: `(${field} IS NULL OR ${ninClause})`, args };
|
|
12406
12547
|
}
|
|
12407
12548
|
function translateExists(field, exists) {
|
|
12408
12549
|
return {
|
|
@@ -12416,142 +12557,259 @@ function translateRegex(field, pattern, options, schema, fieldName) {
|
|
|
12416
12557
|
return smartResult;
|
|
12417
12558
|
return null;
|
|
12418
12559
|
}
|
|
12419
|
-
|
|
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
|
+
}
|
|
12645
|
+
function buildElemMatchConditions(criteria, schema, baseFieldName) {
|
|
12420
12646
|
const conditions = [];
|
|
12421
12647
|
const args = [];
|
|
12422
12648
|
for (const [key, value] of Object.entries(criteria)) {
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
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);
|
|
12427
12654
|
} else {
|
|
12428
|
-
|
|
12429
|
-
const fragment = processOperatorValue(propertyField, value);
|
|
12430
|
-
conditions.push(fragment.sql);
|
|
12431
|
-
args.push(...fragment.args);
|
|
12655
|
+
fragment = handleFieldCondition(key, value, schema, baseFieldName);
|
|
12432
12656
|
}
|
|
12657
|
+
if (!fragment)
|
|
12658
|
+
return null;
|
|
12659
|
+
conditions.push(fragment.sql);
|
|
12660
|
+
args.push(...fragment.args);
|
|
12433
12661
|
}
|
|
12434
12662
|
return {
|
|
12435
12663
|
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
12436
12664
|
args
|
|
12437
12665
|
};
|
|
12438
12666
|
}
|
|
12439
|
-
function translateElemMatch(field, criteria) {
|
|
12667
|
+
function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
12668
|
+
if (typeof criteria === "object" && criteria !== null && !Array.isArray(criteria) && Object.keys(criteria).length === 0) {
|
|
12669
|
+
return { sql: "1=0", args: [] };
|
|
12670
|
+
}
|
|
12440
12671
|
if (typeof criteria !== "object" || criteria === null) {
|
|
12441
12672
|
return {
|
|
12442
|
-
sql: `EXISTS (SELECT 1 FROM
|
|
12673
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12443
12674
|
args: [criteria]
|
|
12444
12675
|
};
|
|
12445
12676
|
}
|
|
12446
12677
|
if (criteria.$and && Array.isArray(criteria.$and)) {
|
|
12447
|
-
const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond));
|
|
12678
|
+
const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12679
|
+
if (fragments.some((f) => f === null))
|
|
12680
|
+
return null;
|
|
12448
12681
|
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12449
12682
|
const args = fragments.flatMap((f) => f.args);
|
|
12450
12683
|
return {
|
|
12451
|
-
sql: `EXISTS (SELECT 1 FROM
|
|
12684
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
|
|
12452
12685
|
args
|
|
12453
12686
|
};
|
|
12454
12687
|
}
|
|
12455
12688
|
if (criteria.$or && Array.isArray(criteria.$or)) {
|
|
12456
|
-
const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond));
|
|
12689
|
+
const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12690
|
+
if (fragments.some((f) => f === null))
|
|
12691
|
+
return null;
|
|
12457
12692
|
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12458
12693
|
const args = fragments.flatMap((f) => f.args);
|
|
12459
12694
|
return {
|
|
12460
|
-
sql: `EXISTS (SELECT 1 FROM
|
|
12695
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
|
|
12461
12696
|
args
|
|
12462
12697
|
};
|
|
12463
12698
|
}
|
|
12464
12699
|
if (criteria.$nor && Array.isArray(criteria.$nor)) {
|
|
12465
|
-
const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond));
|
|
12700
|
+
const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12701
|
+
if (fragments.some((f) => f === null))
|
|
12702
|
+
return null;
|
|
12466
12703
|
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12467
12704
|
const args = fragments.flatMap((f) => f.args);
|
|
12468
12705
|
return {
|
|
12469
|
-
sql: `EXISTS (SELECT 1 FROM
|
|
12706
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE NOT (${sql}))`,
|
|
12470
12707
|
args
|
|
12471
12708
|
};
|
|
12472
12709
|
}
|
|
12473
|
-
const fragment = buildElemMatchConditions(criteria);
|
|
12474
|
-
if (fragment
|
|
12710
|
+
const fragment = buildElemMatchConditions(criteria, schema, actualFieldName);
|
|
12711
|
+
if (!fragment)
|
|
12475
12712
|
return null;
|
|
12476
|
-
}
|
|
12477
12713
|
return {
|
|
12478
|
-
sql: `EXISTS (SELECT 1 FROM
|
|
12714
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${fragment.sql})`,
|
|
12479
12715
|
args: fragment.args
|
|
12480
12716
|
};
|
|
12481
12717
|
}
|
|
12482
|
-
function
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
return
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
return
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
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 };
|
|
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;
|
|
12529
12760
|
}
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
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: [] };
|
|
12792
|
+
}
|
|
12793
|
+
if (value === "number") {
|
|
12794
|
+
return { sql: `(type IN ('integer', 'real'))`, args: [] };
|
|
12795
|
+
}
|
|
12796
|
+
return { sql: `type = '${sqlType}'`, args: [] };
|
|
12537
12797
|
}
|
|
12538
|
-
|
|
12539
|
-
|
|
12798
|
+
const typeFragment = translateType(jsonCol, path2, value, true);
|
|
12799
|
+
return typeFragment || { sql: "1=0", args: [] };
|
|
12540
12800
|
}
|
|
12801
|
+
default:
|
|
12802
|
+
return translateEq(field, value, schema, actualFieldName);
|
|
12541
12803
|
}
|
|
12542
|
-
return translateEq(field, value);
|
|
12543
12804
|
}
|
|
12544
|
-
function
|
|
12545
|
-
if (!criteria || typeof criteria === "object" && Object.keys(criteria).length === 0)
|
|
12546
|
-
return null;
|
|
12547
|
-
const inner = processOperatorValue(field, criteria);
|
|
12805
|
+
function wrapWithNot(innerFragment) {
|
|
12548
12806
|
return {
|
|
12549
|
-
sql: `NOT(${
|
|
12550
|
-
args:
|
|
12807
|
+
sql: `NOT (${innerFragment.sql})`,
|
|
12808
|
+
args: innerFragment.args
|
|
12551
12809
|
};
|
|
12552
12810
|
}
|
|
12553
|
-
function translateType(jsonColumn, fieldName, type6) {
|
|
12554
|
-
const jsonPath = `$.${fieldName}`;
|
|
12811
|
+
function translateType(jsonColumn, fieldName, type6, isDirectPath = false) {
|
|
12812
|
+
const jsonPath = isDirectPath ? fieldName : `$.${fieldName}`;
|
|
12555
12813
|
switch (type6) {
|
|
12556
12814
|
case "null":
|
|
12557
12815
|
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'null'`, args: [] };
|
|
@@ -12566,7 +12824,7 @@ function translateType(jsonColumn, fieldName, type6) {
|
|
|
12566
12824
|
case "object":
|
|
12567
12825
|
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'object'`, args: [] };
|
|
12568
12826
|
default:
|
|
12569
|
-
return
|
|
12827
|
+
return { sql: "1=0", args: [] };
|
|
12570
12828
|
}
|
|
12571
12829
|
}
|
|
12572
12830
|
function translateSize(field, size) {
|
|
@@ -12577,11 +12835,11 @@ function translateSize(field, size) {
|
|
|
12577
12835
|
}
|
|
12578
12836
|
function translateMod(field, value) {
|
|
12579
12837
|
if (!Array.isArray(value) || value.length !== 2)
|
|
12580
|
-
return
|
|
12838
|
+
return { sql: "1=0", args: [] };
|
|
12581
12839
|
const [divisor, remainder] = value;
|
|
12582
12840
|
return {
|
|
12583
|
-
sql:
|
|
12584
|
-
args: [divisor, remainder]
|
|
12841
|
+
sql: `(${field} - (CAST(${field} / ? AS INTEGER) * ?)) = ?`,
|
|
12842
|
+
args: [divisor, divisor, remainder]
|
|
12585
12843
|
};
|
|
12586
12844
|
}
|
|
12587
12845
|
|
|
@@ -12661,6 +12919,14 @@ function _stringify(value, stack) {
|
|
|
12661
12919
|
}
|
|
12662
12920
|
const objType = Object.prototype.toString.call(obj);
|
|
12663
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
|
+
}
|
|
12664
12930
|
const result = JSON.stringify(obj);
|
|
12665
12931
|
stack.pop();
|
|
12666
12932
|
return result;
|
|
@@ -12683,37 +12949,37 @@ function _stringify(value, stack) {
|
|
|
12683
12949
|
}
|
|
12684
12950
|
|
|
12685
12951
|
// src/query/builder.ts
|
|
12686
|
-
var QUERY_CACHE = new Map;
|
|
12687
12952
|
var MAX_CACHE_SIZE = 1000;
|
|
12688
|
-
|
|
12953
|
+
var GLOBAL_CACHE = new Map;
|
|
12954
|
+
function buildWhereClause(selector, schema, collectionName, cache = GLOBAL_CACHE) {
|
|
12689
12955
|
if (!selector || typeof selector !== "object")
|
|
12690
12956
|
return null;
|
|
12691
12957
|
const cacheKey = `v${schema.version}_${collectionName}_${stableStringify(selector)}`;
|
|
12692
|
-
const cached =
|
|
12693
|
-
if (cached) {
|
|
12694
|
-
|
|
12695
|
-
|
|
12958
|
+
const cached = cache.get(cacheKey);
|
|
12959
|
+
if (cached !== undefined) {
|
|
12960
|
+
cache.delete(cacheKey);
|
|
12961
|
+
cache.set(cacheKey, cached);
|
|
12696
12962
|
return cached;
|
|
12697
12963
|
}
|
|
12698
12964
|
const result = processSelector(selector, schema, 0);
|
|
12699
12965
|
if (!result)
|
|
12700
12966
|
return null;
|
|
12701
|
-
if (
|
|
12702
|
-
const firstKey =
|
|
12967
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
12968
|
+
const firstKey = cache.keys().next().value;
|
|
12703
12969
|
if (firstKey)
|
|
12704
|
-
|
|
12970
|
+
cache.delete(firstKey);
|
|
12705
12971
|
}
|
|
12706
|
-
|
|
12972
|
+
cache.set(cacheKey, result);
|
|
12707
12973
|
return result;
|
|
12708
12974
|
}
|
|
12709
12975
|
function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
|
|
12710
12976
|
if (conditions.length === 0) {
|
|
12711
|
-
return { sql: "1=1", args: [] };
|
|
12977
|
+
return { sql: operator === "or" ? "1=0" : "1=1", args: [] };
|
|
12712
12978
|
}
|
|
12713
12979
|
const fragments = conditions.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
|
|
12714
12980
|
if (fragments.some((f) => f === null))
|
|
12715
12981
|
return null;
|
|
12716
|
-
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12982
|
+
const sql = fragments.map((f) => `(${f.sql})`).join(" OR ");
|
|
12717
12983
|
const args = fragments.flatMap((f) => f.args);
|
|
12718
12984
|
return operator === "nor" ? { sql: `NOT(${sql})`, args } : { sql, args };
|
|
12719
12985
|
}
|
|
@@ -12738,8 +13004,7 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12738
13004
|
const orFragment = buildLogicalOperator("or", value, schema, logicalDepth);
|
|
12739
13005
|
if (!orFragment)
|
|
12740
13006
|
return null;
|
|
12741
|
-
|
|
12742
|
-
conditions.push(needsParens ? `(${orFragment.sql})` : orFragment.sql);
|
|
13007
|
+
conditions.push(`(${orFragment.sql})`);
|
|
12743
13008
|
args.push(...orFragment.args);
|
|
12744
13009
|
continue;
|
|
12745
13010
|
}
|
|
@@ -12755,80 +13020,127 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12755
13020
|
const fieldName = columnInfo.column || `json_extract(data, '${columnInfo.jsonPath}')`;
|
|
12756
13021
|
const actualFieldName = columnInfo.jsonPath?.replace(/^\$\./, "") || columnInfo.column || field;
|
|
12757
13022
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
13023
|
+
if (Object.keys(value).length === 0) {
|
|
13024
|
+
return { sql: "1=0", args: [] };
|
|
13025
|
+
}
|
|
13026
|
+
const fieldFragments = [];
|
|
12758
13027
|
for (const [op, opValue] of Object.entries(value)) {
|
|
12759
13028
|
let fragment;
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
case "$ne":
|
|
12765
|
-
fragment = translateNe(fieldName, opValue);
|
|
12766
|
-
break;
|
|
12767
|
-
case "$gt":
|
|
12768
|
-
fragment = translateGt(fieldName, opValue);
|
|
12769
|
-
break;
|
|
12770
|
-
case "$gte":
|
|
12771
|
-
fragment = translateGte(fieldName, opValue);
|
|
12772
|
-
break;
|
|
12773
|
-
case "$lt":
|
|
12774
|
-
fragment = translateLt(fieldName, opValue);
|
|
12775
|
-
break;
|
|
12776
|
-
case "$lte":
|
|
12777
|
-
fragment = translateLte(fieldName, opValue);
|
|
12778
|
-
break;
|
|
12779
|
-
case "$in":
|
|
12780
|
-
fragment = translateIn(fieldName, opValue);
|
|
12781
|
-
break;
|
|
12782
|
-
case "$nin":
|
|
12783
|
-
fragment = translateNin(fieldName, opValue);
|
|
12784
|
-
break;
|
|
12785
|
-
case "$exists":
|
|
12786
|
-
fragment = translateExists(fieldName, opValue);
|
|
12787
|
-
break;
|
|
12788
|
-
case "$regex":
|
|
12789
|
-
const options = value.$options;
|
|
12790
|
-
const regexFragment = translateRegex(fieldName, opValue, options, schema, actualFieldName);
|
|
12791
|
-
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)
|
|
12792
13033
|
return null;
|
|
12793
|
-
fragment =
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
if (!elemMatchFragment)
|
|
13034
|
+
fragment = wrapWithNot(eqFrag);
|
|
13035
|
+
} else if (opValue instanceof Date) {
|
|
13036
|
+
const eqFrag = translateLeafOperator("$eq", fieldName, opValue, schema, actualFieldName);
|
|
13037
|
+
if (!eqFrag)
|
|
12798
13038
|
return null;
|
|
12799
|
-
fragment =
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
if (!notResult)
|
|
13039
|
+
fragment = wrapWithNot(eqFrag);
|
|
13040
|
+
} else if (opValue instanceof RegExp) {
|
|
13041
|
+
const regexFrag = translateLeafOperator("$regex", fieldName, opValue, schema, actualFieldName);
|
|
13042
|
+
if (!regexFrag)
|
|
12804
13043
|
return null;
|
|
12805
|
-
fragment =
|
|
12806
|
-
|
|
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
|
+
}
|
|
12807
13092
|
}
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
fragment = modResult;
|
|
12822
|
-
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;
|
|
12823
13106
|
}
|
|
12824
|
-
|
|
12825
|
-
|
|
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);
|
|
12826
13134
|
}
|
|
12827
|
-
conditions.push(fragment.sql);
|
|
12828
|
-
args.push(...fragment.args);
|
|
12829
13135
|
}
|
|
13136
|
+
fieldFragments.forEach((f) => {
|
|
13137
|
+
conditions.push(f.sql);
|
|
13138
|
+
args.push(...f.args);
|
|
13139
|
+
});
|
|
12830
13140
|
} else {
|
|
12831
|
-
const fragment =
|
|
13141
|
+
const fragment = translateLeafOperator("$eq", fieldName, value, schema, actualFieldName);
|
|
13142
|
+
if (!fragment)
|
|
13143
|
+
return null;
|
|
12832
13144
|
conditions.push(fragment.sql);
|
|
12833
13145
|
args.push(...fragment.args);
|
|
12834
13146
|
}
|
|
@@ -12837,6 +13149,100 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12837
13149
|
return { sql: where, args };
|
|
12838
13150
|
}
|
|
12839
13151
|
|
|
13152
|
+
// src/query/lightweight-matcher.ts
|
|
13153
|
+
var operators = {
|
|
13154
|
+
$eq: (a, b) => a === b,
|
|
13155
|
+
$ne: (a, b) => a !== b,
|
|
13156
|
+
$gt: (a, b) => a > b,
|
|
13157
|
+
$gte: (a, b) => a >= b,
|
|
13158
|
+
$lt: (a, b) => a < b,
|
|
13159
|
+
$lte: (a, b) => a <= b,
|
|
13160
|
+
$in: (a, b) => Array.isArray(b) && b.some((v) => v === a),
|
|
13161
|
+
$nin: (a, b) => Array.isArray(b) && !b.some((v) => v === a),
|
|
13162
|
+
$exists: (a, b) => a !== undefined === b,
|
|
13163
|
+
$type: (a, b) => {
|
|
13164
|
+
const type6 = Array.isArray(a) ? "array" : typeof a;
|
|
13165
|
+
return type6 === b;
|
|
13166
|
+
},
|
|
13167
|
+
$mod: (a, b) => {
|
|
13168
|
+
if (!Array.isArray(b) || b.length !== 2)
|
|
13169
|
+
return false;
|
|
13170
|
+
const [divisor, remainder] = b;
|
|
13171
|
+
return typeof a === "number" && a % divisor === remainder;
|
|
13172
|
+
},
|
|
13173
|
+
$size: (a, b) => Array.isArray(a) && a.length === b
|
|
13174
|
+
};
|
|
13175
|
+
function getNestedValue(obj, path2) {
|
|
13176
|
+
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13177
|
+
}
|
|
13178
|
+
function matchesSelector(doc, selector) {
|
|
13179
|
+
if (!selector || typeof selector !== "object")
|
|
13180
|
+
return true;
|
|
13181
|
+
if (selector.$and) {
|
|
13182
|
+
return Array.isArray(selector.$and) && selector.$and.every((s) => matchesSelector(doc, s));
|
|
13183
|
+
}
|
|
13184
|
+
if (selector.$or) {
|
|
13185
|
+
return Array.isArray(selector.$or) && selector.$or.some((s) => matchesSelector(doc, s));
|
|
13186
|
+
}
|
|
13187
|
+
if (selector.$nor) {
|
|
13188
|
+
return Array.isArray(selector.$nor) && !selector.$nor.some((s) => matchesSelector(doc, s));
|
|
13189
|
+
}
|
|
13190
|
+
for (const [field, condition] of Object.entries(selector)) {
|
|
13191
|
+
const value = getNestedValue(doc, field);
|
|
13192
|
+
if (typeof condition !== "object" || condition === null || Array.isArray(condition)) {
|
|
13193
|
+
if (value !== condition)
|
|
13194
|
+
return false;
|
|
13195
|
+
continue;
|
|
13196
|
+
}
|
|
13197
|
+
for (const [op, opValue] of Object.entries(condition)) {
|
|
13198
|
+
if (op === "$regex") {
|
|
13199
|
+
const options = condition.$options;
|
|
13200
|
+
if (!matchesRegex(value, opValue, options))
|
|
13201
|
+
return false;
|
|
13202
|
+
continue;
|
|
13203
|
+
}
|
|
13204
|
+
if (op === "$not") {
|
|
13205
|
+
if (matchesOperator(value, opValue))
|
|
13206
|
+
return false;
|
|
13207
|
+
continue;
|
|
13208
|
+
}
|
|
13209
|
+
if (op === "$elemMatch") {
|
|
13210
|
+
if (!Array.isArray(value))
|
|
13211
|
+
return false;
|
|
13212
|
+
const hasMatch = value.some((item) => matchesSelector(item, opValue));
|
|
13213
|
+
if (!hasMatch)
|
|
13214
|
+
return false;
|
|
13215
|
+
continue;
|
|
13216
|
+
}
|
|
13217
|
+
if (op === "$options")
|
|
13218
|
+
continue;
|
|
13219
|
+
const operator = operators[op];
|
|
13220
|
+
if (!operator)
|
|
13221
|
+
return false;
|
|
13222
|
+
if (!operator(value, opValue))
|
|
13223
|
+
return false;
|
|
13224
|
+
}
|
|
13225
|
+
}
|
|
13226
|
+
return true;
|
|
13227
|
+
}
|
|
13228
|
+
function matchesOperator(value, operator) {
|
|
13229
|
+
if (typeof operator !== "object" || operator === null) {
|
|
13230
|
+
return value === operator;
|
|
13231
|
+
}
|
|
13232
|
+
for (const [op, opValue] of Object.entries(operator)) {
|
|
13233
|
+
if (op === "$regex") {
|
|
13234
|
+
const options = operator.$options;
|
|
13235
|
+
return matchesRegex(value, opValue, options);
|
|
13236
|
+
}
|
|
13237
|
+
const operatorFn = operators[op];
|
|
13238
|
+
if (!operatorFn)
|
|
13239
|
+
return false;
|
|
13240
|
+
if (operatorFn(value, opValue))
|
|
13241
|
+
return true;
|
|
13242
|
+
}
|
|
13243
|
+
return false;
|
|
13244
|
+
}
|
|
13245
|
+
|
|
12840
13246
|
// src/rxdb-helpers.ts
|
|
12841
13247
|
function randomToken2(length) {
|
|
12842
13248
|
return Math.random().toString(36).substring(2, 2 + length);
|
|
@@ -13188,6 +13594,7 @@ class BunSQLiteStorageInstance {
|
|
|
13188
13594
|
db;
|
|
13189
13595
|
stmtManager;
|
|
13190
13596
|
changeStream$ = new import_rxjs2.Subject;
|
|
13597
|
+
queryCache = new Map;
|
|
13191
13598
|
databaseName;
|
|
13192
13599
|
collectionName;
|
|
13193
13600
|
schema;
|
|
@@ -13355,7 +13762,7 @@ class BunSQLiteStorageInstance {
|
|
|
13355
13762
|
return rows.map((row) => JSON.parse(row.data));
|
|
13356
13763
|
}
|
|
13357
13764
|
async query(preparedQuery) {
|
|
13358
|
-
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13765
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13359
13766
|
if (!whereResult) {
|
|
13360
13767
|
return this.queryWithOurMemory(preparedQuery);
|
|
13361
13768
|
}
|
|
@@ -13375,6 +13782,9 @@ class BunSQLiteStorageInstance {
|
|
|
13375
13782
|
queryArgs.push(preparedQuery.query.limit);
|
|
13376
13783
|
}
|
|
13377
13784
|
if (preparedQuery.query.skip) {
|
|
13785
|
+
if (!preparedQuery.query.limit) {
|
|
13786
|
+
sql += ` LIMIT -1`;
|
|
13787
|
+
}
|
|
13378
13788
|
sql += ` OFFSET ?`;
|
|
13379
13789
|
queryArgs.push(preparedQuery.query.skip);
|
|
13380
13790
|
}
|
|
@@ -13389,31 +13799,6 @@ class BunSQLiteStorageInstance {
|
|
|
13389
13799
|
const documents = rows.map((row) => JSON.parse(row.data));
|
|
13390
13800
|
return { documents };
|
|
13391
13801
|
}
|
|
13392
|
-
matchesSelector(doc, selector) {
|
|
13393
|
-
for (const [key, value] of Object.entries(selector)) {
|
|
13394
|
-
const docValue = this.getNestedValue(doc, key);
|
|
13395
|
-
if (typeof value === "object" && value !== null) {
|
|
13396
|
-
for (const [op, opValue] of Object.entries(value)) {
|
|
13397
|
-
if (op === "$eq" && docValue !== opValue)
|
|
13398
|
-
return false;
|
|
13399
|
-
if (op === "$ne" && docValue === opValue)
|
|
13400
|
-
return false;
|
|
13401
|
-
if (op === "$gt" && !(docValue > opValue))
|
|
13402
|
-
return false;
|
|
13403
|
-
if (op === "$gte" && !(docValue >= opValue))
|
|
13404
|
-
return false;
|
|
13405
|
-
if (op === "$lt" && !(docValue < opValue))
|
|
13406
|
-
return false;
|
|
13407
|
-
if (op === "$lte" && !(docValue <= opValue))
|
|
13408
|
-
return false;
|
|
13409
|
-
}
|
|
13410
|
-
} else {
|
|
13411
|
-
if (docValue !== value)
|
|
13412
|
-
return false;
|
|
13413
|
-
}
|
|
13414
|
-
}
|
|
13415
|
-
return true;
|
|
13416
|
-
}
|
|
13417
13802
|
sortDocuments(docs, sort) {
|
|
13418
13803
|
return docs.sort((a, b) => {
|
|
13419
13804
|
for (const sortField of sort) {
|
|
@@ -13432,7 +13817,7 @@ class BunSQLiteStorageInstance {
|
|
|
13432
13817
|
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13433
13818
|
}
|
|
13434
13819
|
async count(preparedQuery) {
|
|
13435
|
-
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13820
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName, this.queryCache);
|
|
13436
13821
|
if (!whereResult) {
|
|
13437
13822
|
const allDocs = await this.queryWithOurMemory(preparedQuery);
|
|
13438
13823
|
return {
|
|
@@ -13467,12 +13852,16 @@ class BunSQLiteStorageInstance {
|
|
|
13467
13852
|
if (this.closed)
|
|
13468
13853
|
return this.closed;
|
|
13469
13854
|
this.closed = (async () => {
|
|
13855
|
+
this.queryCache.clear();
|
|
13470
13856
|
this.changeStream$.complete();
|
|
13471
13857
|
this.stmtManager.close();
|
|
13472
13858
|
releaseDatabase(this.databaseName);
|
|
13473
13859
|
})();
|
|
13474
13860
|
return this.closed;
|
|
13475
13861
|
}
|
|
13862
|
+
getCacheSize() {
|
|
13863
|
+
return this.queryCache.size;
|
|
13864
|
+
}
|
|
13476
13865
|
async remove() {
|
|
13477
13866
|
if (this.closed)
|
|
13478
13867
|
throw new Error("already closed");
|
|
@@ -13512,17 +13901,38 @@ class BunSQLiteStorageInstance {
|
|
|
13512
13901
|
}
|
|
13513
13902
|
queryWithOurMemory(preparedQuery) {
|
|
13514
13903
|
const query = `SELECT json(data) as data FROM "${this.tableName}"`;
|
|
13515
|
-
const
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13904
|
+
const selector = preparedQuery.query.selector;
|
|
13905
|
+
const hasSort = preparedQuery.query.sort && preparedQuery.query.sort.length > 0;
|
|
13906
|
+
if (hasSort) {
|
|
13907
|
+
const rows = this.stmtManager.all({ query, params: [] });
|
|
13908
|
+
let documents2 = rows.map((row) => JSON.parse(row.data));
|
|
13909
|
+
documents2 = documents2.filter((doc) => matchesSelector(doc, selector));
|
|
13910
|
+
documents2 = this.sortDocuments(documents2, preparedQuery.query.sort);
|
|
13911
|
+
if (preparedQuery.query.skip) {
|
|
13912
|
+
documents2 = documents2.slice(preparedQuery.query.skip);
|
|
13913
|
+
}
|
|
13914
|
+
if (preparedQuery.query.limit) {
|
|
13915
|
+
documents2 = documents2.slice(0, preparedQuery.query.limit);
|
|
13916
|
+
}
|
|
13917
|
+
return { documents: documents2 };
|
|
13918
|
+
}
|
|
13919
|
+
const stmt = this.db.prepare(query);
|
|
13920
|
+
const documents = [];
|
|
13921
|
+
const skip = preparedQuery.query.skip || 0;
|
|
13922
|
+
const limit = preparedQuery.query.limit;
|
|
13923
|
+
let skipped = 0;
|
|
13924
|
+
for (const row of stmt.iterate()) {
|
|
13925
|
+
const doc = JSON.parse(row.data);
|
|
13926
|
+
if (matchesSelector(doc, selector)) {
|
|
13927
|
+
if (skipped < skip) {
|
|
13928
|
+
skipped++;
|
|
13929
|
+
continue;
|
|
13930
|
+
}
|
|
13931
|
+
documents.push(doc);
|
|
13932
|
+
if (limit && documents.length >= limit) {
|
|
13933
|
+
break;
|
|
13934
|
+
}
|
|
13935
|
+
}
|
|
13526
13936
|
}
|
|
13527
13937
|
return { documents };
|
|
13528
13938
|
}
|