bun-sqlite-for-rxdb 1.1.3 → 1.4.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/dist/index.js +907 -333
- package/dist/instance.d.ts +1 -0
- package/dist/query/builder.d.ts +2 -1
- package/dist/query/operators.d.ts +12 -5
- package/dist/query/regex-matcher.d.ts +2 -0
- package/dist/query/smart-regex.d.ts +2 -1
- package/dist/src/connection-pool.d.ts +4 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/instance.d.ts +43 -0
- package/dist/src/query/builder.d.ts +7 -0
- package/dist/src/query/lightweight-matcher.d.ts +3 -0
- package/dist/src/query/operators.d.ts +27 -0
- package/dist/src/query/regex-matcher.d.ts +2 -0
- package/dist/src/query/schema-mapper.d.ts +8 -0
- package/dist/src/query/smart-regex.d.ts +7 -0
- package/dist/src/rxdb-helpers.d.ts +28 -0
- package/dist/src/statement-manager.d.ts +20 -0
- package/dist/src/storage.d.ts +4 -0
- package/dist/src/transaction-queue.d.ts +3 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/utils/stable-stringify.d.ts +16 -0
- package/package.json +12 -9
- package/CHANGELOG.md +0 -348
- package/dist/connection-pool.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/instance.d.ts.map +0 -1
- package/dist/query/builder.d.ts.map +0 -1
- package/dist/query/operators.d.ts.map +0 -1
- package/dist/query/schema-mapper.d.ts.map +0 -1
- package/dist/query/smart-regex.d.ts.map +0 -1
- package/dist/rxdb-helpers.d.ts.map +0 -1
- package/dist/statement-manager.d.ts.map +0 -1
- package/dist/storage.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -10341,80 +10341,6 @@ var require_dist = __commonJS((exports) => {
|
|
|
10341
10341
|
exports.default = PQueue;
|
|
10342
10342
|
});
|
|
10343
10343
|
|
|
10344
|
-
// node_modules/fast-stable-stringify/index.js
|
|
10345
|
-
var require_fast_stable_stringify = __commonJS((exports, module) => {
|
|
10346
|
-
var objToString = Object.prototype.toString;
|
|
10347
|
-
var objKeys = Object.keys || function(obj) {
|
|
10348
|
-
var keys = [];
|
|
10349
|
-
for (var name in obj) {
|
|
10350
|
-
keys.push(name);
|
|
10351
|
-
}
|
|
10352
|
-
return keys;
|
|
10353
|
-
};
|
|
10354
|
-
function stringify(val, isArrayProp) {
|
|
10355
|
-
var i, max, str, keys, key, propVal, toStr;
|
|
10356
|
-
if (val === true) {
|
|
10357
|
-
return "true";
|
|
10358
|
-
}
|
|
10359
|
-
if (val === false) {
|
|
10360
|
-
return "false";
|
|
10361
|
-
}
|
|
10362
|
-
switch (typeof val) {
|
|
10363
|
-
case "object":
|
|
10364
|
-
if (val === null) {
|
|
10365
|
-
return null;
|
|
10366
|
-
} else if (val.toJSON && typeof val.toJSON === "function") {
|
|
10367
|
-
return stringify(val.toJSON(), isArrayProp);
|
|
10368
|
-
} else {
|
|
10369
|
-
toStr = objToString.call(val);
|
|
10370
|
-
if (toStr === "[object Array]") {
|
|
10371
|
-
str = "[";
|
|
10372
|
-
max = val.length - 1;
|
|
10373
|
-
for (i = 0;i < max; i++) {
|
|
10374
|
-
str += stringify(val[i], true) + ",";
|
|
10375
|
-
}
|
|
10376
|
-
if (max > -1) {
|
|
10377
|
-
str += stringify(val[i], true);
|
|
10378
|
-
}
|
|
10379
|
-
return str + "]";
|
|
10380
|
-
} else if (toStr === "[object Object]") {
|
|
10381
|
-
keys = objKeys(val).sort();
|
|
10382
|
-
max = keys.length;
|
|
10383
|
-
str = "";
|
|
10384
|
-
i = 0;
|
|
10385
|
-
while (i < max) {
|
|
10386
|
-
key = keys[i];
|
|
10387
|
-
propVal = stringify(val[key], false);
|
|
10388
|
-
if (propVal !== undefined) {
|
|
10389
|
-
if (str) {
|
|
10390
|
-
str += ",";
|
|
10391
|
-
}
|
|
10392
|
-
str += JSON.stringify(key) + ":" + propVal;
|
|
10393
|
-
}
|
|
10394
|
-
i++;
|
|
10395
|
-
}
|
|
10396
|
-
return "{" + str + "}";
|
|
10397
|
-
} else {
|
|
10398
|
-
return JSON.stringify(val);
|
|
10399
|
-
}
|
|
10400
|
-
}
|
|
10401
|
-
case "function":
|
|
10402
|
-
case "undefined":
|
|
10403
|
-
return isArrayProp ? null : undefined;
|
|
10404
|
-
case "string":
|
|
10405
|
-
return JSON.stringify(val);
|
|
10406
|
-
default:
|
|
10407
|
-
return isFinite(val) ? val : null;
|
|
10408
|
-
}
|
|
10409
|
-
}
|
|
10410
|
-
module.exports = function(val) {
|
|
10411
|
-
var returnVal = stringify(val, false);
|
|
10412
|
-
if (returnVal !== undefined) {
|
|
10413
|
-
return "" + returnVal;
|
|
10414
|
-
}
|
|
10415
|
-
};
|
|
10416
|
-
});
|
|
10417
|
-
|
|
10418
10344
|
// node_modules/rxdb/dist/esm/rx-storage-multiinstance.js
|
|
10419
10345
|
var import_rxjs = __toESM(require_cjs(), 1);
|
|
10420
10346
|
var import_operators = __toESM(require_operators(), 1);
|
|
@@ -12284,6 +12210,36 @@ function addRxStorageMultiInstanceSupport(storageName, instanceCreationParams, i
|
|
|
12284
12210
|
// src/instance.ts
|
|
12285
12211
|
var import_rxjs2 = __toESM(require_cjs(), 1);
|
|
12286
12212
|
|
|
12213
|
+
// src/query/regex-matcher.ts
|
|
12214
|
+
var REGEX_CACHE = new Map;
|
|
12215
|
+
var MAX_REGEX_CACHE_SIZE = 100;
|
|
12216
|
+
function compileRegex(pattern, options) {
|
|
12217
|
+
const cacheKey = `${pattern}::${options || ""}`;
|
|
12218
|
+
const cached = REGEX_CACHE.get(cacheKey);
|
|
12219
|
+
if (cached) {
|
|
12220
|
+
return cached.regex;
|
|
12221
|
+
}
|
|
12222
|
+
const regex = new RegExp(pattern, options);
|
|
12223
|
+
if (REGEX_CACHE.size >= MAX_REGEX_CACHE_SIZE) {
|
|
12224
|
+
const firstKey = REGEX_CACHE.keys().next().value;
|
|
12225
|
+
if (firstKey) {
|
|
12226
|
+
REGEX_CACHE.delete(firstKey);
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
REGEX_CACHE.set(cacheKey, { regex });
|
|
12230
|
+
return regex;
|
|
12231
|
+
}
|
|
12232
|
+
function matchesRegex(value, pattern, options) {
|
|
12233
|
+
const regex = compileRegex(pattern, options);
|
|
12234
|
+
if (typeof value === "string") {
|
|
12235
|
+
return regex.test(value);
|
|
12236
|
+
}
|
|
12237
|
+
if (Array.isArray(value)) {
|
|
12238
|
+
return value.some((v) => typeof v === "string" && regex.test(v));
|
|
12239
|
+
}
|
|
12240
|
+
return false;
|
|
12241
|
+
}
|
|
12242
|
+
|
|
12287
12243
|
// src/query/schema-mapper.ts
|
|
12288
12244
|
function getColumnInfo(path2, schema) {
|
|
12289
12245
|
if (path2 === "_deleted") {
|
|
@@ -12298,71 +12254,180 @@ function getColumnInfo(path2, schema) {
|
|
|
12298
12254
|
if (path2 === schema.primaryKey) {
|
|
12299
12255
|
return { column: "id", type: "string" };
|
|
12300
12256
|
}
|
|
12257
|
+
const properties = schema.properties;
|
|
12258
|
+
const fieldSchema = properties?.[path2];
|
|
12259
|
+
if (fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema) {
|
|
12260
|
+
if (fieldSchema.type === "array") {
|
|
12261
|
+
return { jsonPath: `$.${path2}`, type: "array" };
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12301
12264
|
return { jsonPath: `$.${path2}`, type: "unknown" };
|
|
12302
12265
|
}
|
|
12303
12266
|
|
|
12304
12267
|
// src/query/smart-regex.ts
|
|
12305
|
-
|
|
12306
|
-
|
|
12268
|
+
var INDEX_CACHE = new Map;
|
|
12269
|
+
var MAX_INDEX_CACHE_SIZE = 1000;
|
|
12270
|
+
function hasExpressionIndex(fieldName, schema) {
|
|
12271
|
+
const indexKey = schema.indexes ? JSON.stringify(schema.indexes) : "none";
|
|
12272
|
+
const cacheKey = `${schema.version}_${fieldName}_${indexKey}`;
|
|
12273
|
+
const cached = INDEX_CACHE.get(cacheKey);
|
|
12274
|
+
if (cached !== undefined) {
|
|
12275
|
+
INDEX_CACHE.delete(cacheKey);
|
|
12276
|
+
INDEX_CACHE.set(cacheKey, cached);
|
|
12277
|
+
return cached;
|
|
12278
|
+
}
|
|
12279
|
+
if (!schema.indexes) {
|
|
12280
|
+
if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
|
|
12281
|
+
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12282
|
+
if (firstKey)
|
|
12283
|
+
INDEX_CACHE.delete(firstKey);
|
|
12284
|
+
}
|
|
12285
|
+
INDEX_CACHE.set(cacheKey, false);
|
|
12286
|
+
return false;
|
|
12287
|
+
}
|
|
12288
|
+
const hasLowerIndex = schema.indexes.some((idx) => {
|
|
12289
|
+
const fields = Array.isArray(idx) ? idx : [idx];
|
|
12290
|
+
return fields.some((f) => {
|
|
12291
|
+
if (typeof f !== "string")
|
|
12292
|
+
return false;
|
|
12293
|
+
const normalized = f.toLowerCase().replace(/\s/g, "");
|
|
12294
|
+
return normalized === `lower(${fieldName})`;
|
|
12295
|
+
});
|
|
12296
|
+
});
|
|
12297
|
+
if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
|
|
12298
|
+
const firstKey = INDEX_CACHE.keys().next().value;
|
|
12299
|
+
if (firstKey)
|
|
12300
|
+
INDEX_CACHE.delete(firstKey);
|
|
12301
|
+
}
|
|
12302
|
+
INDEX_CACHE.set(cacheKey, hasLowerIndex);
|
|
12303
|
+
return hasLowerIndex;
|
|
12304
|
+
}
|
|
12305
|
+
function isComplexRegex(pattern) {
|
|
12306
|
+
return /[*+?()[\]{}|]/.test(pattern.replace(/\\\./g, ""));
|
|
12307
|
+
}
|
|
12308
|
+
function escapeForLike(str) {
|
|
12309
|
+
return str.replace(/[\\%_]/g, "\\$&");
|
|
12310
|
+
}
|
|
12311
|
+
function smartRegexToLike(field, pattern, options, schema, fieldName) {
|
|
12312
|
+
if (typeof pattern !== "string")
|
|
12313
|
+
return null;
|
|
12314
|
+
const caseInsensitive = options?.includes("i") ?? false;
|
|
12315
|
+
const hasLowerIndex = hasExpressionIndex(fieldName, schema);
|
|
12307
12316
|
const startsWithAnchor = pattern.startsWith("^");
|
|
12308
12317
|
const endsWithAnchor = pattern.endsWith("$");
|
|
12309
12318
|
let cleanPattern = pattern.replace(/^\^/, "").replace(/\$$/, "");
|
|
12310
|
-
if (
|
|
12311
|
-
|
|
12319
|
+
if (isComplexRegex(cleanPattern)) {
|
|
12320
|
+
return null;
|
|
12321
|
+
}
|
|
12322
|
+
const unescaped = cleanPattern.replace(/\\\./g, ".");
|
|
12323
|
+
const escaped = escapeForLike(unescaped);
|
|
12324
|
+
if (startsWithAnchor && endsWithAnchor) {
|
|
12312
12325
|
if (caseInsensitive) {
|
|
12313
|
-
|
|
12314
|
-
return { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped] };
|
|
12326
|
+
return hasLowerIndex ? { sql: `LOWER(${field}) = ?`, args: [unescaped.toLowerCase()] } : { sql: `${field} = ? COLLATE NOCASE`, args: [unescaped] };
|
|
12315
12327
|
}
|
|
12316
|
-
return { sql: `${field} = ?`, args: [
|
|
12328
|
+
return { sql: `${field} = ?`, args: [unescaped] };
|
|
12317
12329
|
}
|
|
12318
12330
|
if (startsWithAnchor) {
|
|
12319
|
-
const
|
|
12320
|
-
if (
|
|
12321
|
-
|
|
12322
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: [escaped + "%"] };
|
|
12331
|
+
const suffix = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12332
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12333
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: [suffix + "%"] };
|
|
12323
12334
|
}
|
|
12335
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: [suffix + "%"] };
|
|
12324
12336
|
}
|
|
12325
12337
|
if (endsWithAnchor) {
|
|
12326
|
-
const
|
|
12327
|
-
if (
|
|
12328
|
-
|
|
12329
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped] };
|
|
12338
|
+
const prefix = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12339
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12340
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + prefix] };
|
|
12330
12341
|
}
|
|
12342
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + prefix] };
|
|
12331
12343
|
}
|
|
12332
|
-
|
|
12333
|
-
if (
|
|
12334
|
-
|
|
12335
|
-
return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped + "%"] };
|
|
12344
|
+
const middle = caseInsensitive ? escaped.toLowerCase() : escaped;
|
|
12345
|
+
if (caseInsensitive && hasLowerIndex) {
|
|
12346
|
+
return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + middle + "%"] };
|
|
12336
12347
|
}
|
|
12337
|
-
return
|
|
12348
|
+
return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + middle + "%"] };
|
|
12338
12349
|
}
|
|
12339
12350
|
|
|
12340
12351
|
// src/query/operators.ts
|
|
12341
|
-
function translateEq(field, value) {
|
|
12352
|
+
function translateEq(field, value, schema, actualFieldName) {
|
|
12342
12353
|
if (value === null) {
|
|
12343
12354
|
return { sql: `${field} IS NULL`, args: [] };
|
|
12344
12355
|
}
|
|
12356
|
+
if (schema && actualFieldName) {
|
|
12357
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12358
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12359
|
+
return {
|
|
12360
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12361
|
+
args: [value]
|
|
12362
|
+
};
|
|
12363
|
+
}
|
|
12364
|
+
}
|
|
12345
12365
|
return { sql: `${field} = ?`, args: [value] };
|
|
12346
12366
|
}
|
|
12347
|
-
function translateNe(field, value) {
|
|
12367
|
+
function translateNe(field, value, schema, actualFieldName) {
|
|
12348
12368
|
if (value === null) {
|
|
12349
12369
|
return { sql: `${field} IS NOT NULL`, args: [] };
|
|
12350
12370
|
}
|
|
12351
|
-
|
|
12371
|
+
if (schema && actualFieldName) {
|
|
12372
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12373
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12374
|
+
return {
|
|
12375
|
+
sql: `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12376
|
+
args: [value]
|
|
12377
|
+
};
|
|
12378
|
+
}
|
|
12379
|
+
}
|
|
12380
|
+
return { sql: `(${field} <> ? OR ${field} IS NULL)`, args: [value] };
|
|
12352
12381
|
}
|
|
12353
|
-
function translateGt(field, value) {
|
|
12382
|
+
function translateGt(field, value, schema, actualFieldName) {
|
|
12383
|
+
if (schema && actualFieldName) {
|
|
12384
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12385
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12386
|
+
return {
|
|
12387
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value > ?)`,
|
|
12388
|
+
args: [value]
|
|
12389
|
+
};
|
|
12390
|
+
}
|
|
12391
|
+
}
|
|
12354
12392
|
return { sql: `${field} > ?`, args: [value] };
|
|
12355
12393
|
}
|
|
12356
|
-
function translateGte(field, value) {
|
|
12394
|
+
function translateGte(field, value, schema, actualFieldName) {
|
|
12395
|
+
if (schema && actualFieldName) {
|
|
12396
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12397
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12398
|
+
return {
|
|
12399
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value >= ?)`,
|
|
12400
|
+
args: [value]
|
|
12401
|
+
};
|
|
12402
|
+
}
|
|
12403
|
+
}
|
|
12357
12404
|
return { sql: `${field} >= ?`, args: [value] };
|
|
12358
12405
|
}
|
|
12359
|
-
function translateLt(field, value) {
|
|
12406
|
+
function translateLt(field, value, schema, actualFieldName) {
|
|
12407
|
+
if (schema && actualFieldName) {
|
|
12408
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12409
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12410
|
+
return {
|
|
12411
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value < ?)`,
|
|
12412
|
+
args: [value]
|
|
12413
|
+
};
|
|
12414
|
+
}
|
|
12415
|
+
}
|
|
12360
12416
|
return { sql: `${field} < ?`, args: [value] };
|
|
12361
12417
|
}
|
|
12362
|
-
function translateLte(field, value) {
|
|
12418
|
+
function translateLte(field, value, schema, actualFieldName) {
|
|
12419
|
+
if (schema && actualFieldName) {
|
|
12420
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12421
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12422
|
+
return {
|
|
12423
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value <= ?)`,
|
|
12424
|
+
args: [value]
|
|
12425
|
+
};
|
|
12426
|
+
}
|
|
12427
|
+
}
|
|
12363
12428
|
return { sql: `${field} <= ?`, args: [value] };
|
|
12364
12429
|
}
|
|
12365
|
-
function translateIn(field, values) {
|
|
12430
|
+
function translateIn(field, values, schema, actualFieldName) {
|
|
12366
12431
|
if (!Array.isArray(values) || values.length === 0) {
|
|
12367
12432
|
return { sql: "1=0", args: [] };
|
|
12368
12433
|
}
|
|
@@ -12371,17 +12436,31 @@ function translateIn(field, values) {
|
|
|
12371
12436
|
if (nonNullValues.length === 0) {
|
|
12372
12437
|
return { sql: `${field} IS NULL`, args: [] };
|
|
12373
12438
|
}
|
|
12374
|
-
|
|
12375
|
-
|
|
12439
|
+
if (schema && actualFieldName) {
|
|
12440
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12441
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12442
|
+
const inClause2 = `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
|
|
12443
|
+
const args2 = [JSON.stringify(nonNullValues)];
|
|
12444
|
+
if (hasNull) {
|
|
12445
|
+
return {
|
|
12446
|
+
sql: `(${inClause2} OR ${field} IS NULL)`,
|
|
12447
|
+
args: args2
|
|
12448
|
+
};
|
|
12449
|
+
}
|
|
12450
|
+
return { sql: inClause2, args: args2 };
|
|
12451
|
+
}
|
|
12452
|
+
}
|
|
12453
|
+
const inClause = `${field} IN (SELECT value FROM json_each(?))`;
|
|
12454
|
+
const args = [JSON.stringify(nonNullValues)];
|
|
12376
12455
|
if (hasNull) {
|
|
12377
12456
|
return {
|
|
12378
12457
|
sql: `(${inClause} OR ${field} IS NULL)`,
|
|
12379
|
-
args
|
|
12458
|
+
args
|
|
12380
12459
|
};
|
|
12381
12460
|
}
|
|
12382
|
-
return { sql: inClause, args
|
|
12461
|
+
return { sql: inClause, args };
|
|
12383
12462
|
}
|
|
12384
|
-
function translateNin(field, values) {
|
|
12463
|
+
function translateNin(field, values, schema, actualFieldName) {
|
|
12385
12464
|
if (!Array.isArray(values) || values.length === 0) {
|
|
12386
12465
|
return { sql: "1=1", args: [] };
|
|
12387
12466
|
}
|
|
@@ -12390,15 +12469,29 @@ function translateNin(field, values) {
|
|
|
12390
12469
|
if (nonNullValues.length === 0) {
|
|
12391
12470
|
return { sql: `${field} IS NOT NULL`, args: [] };
|
|
12392
12471
|
}
|
|
12393
|
-
|
|
12394
|
-
|
|
12472
|
+
if (schema && actualFieldName) {
|
|
12473
|
+
const columnInfo = getColumnInfo(actualFieldName, schema);
|
|
12474
|
+
if (field !== "value" && columnInfo.type === "array") {
|
|
12475
|
+
const ninClause2 = `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
|
|
12476
|
+
const args2 = [JSON.stringify(nonNullValues)];
|
|
12477
|
+
if (hasNull) {
|
|
12478
|
+
return {
|
|
12479
|
+
sql: `(${ninClause2} AND ${field} IS NOT NULL)`,
|
|
12480
|
+
args: args2
|
|
12481
|
+
};
|
|
12482
|
+
}
|
|
12483
|
+
return { sql: ninClause2, args: args2 };
|
|
12484
|
+
}
|
|
12485
|
+
}
|
|
12486
|
+
const ninClause = `${field} NOT IN (SELECT value FROM json_each(?))`;
|
|
12487
|
+
const args = [JSON.stringify(nonNullValues)];
|
|
12395
12488
|
if (hasNull) {
|
|
12396
12489
|
return {
|
|
12397
12490
|
sql: `(${ninClause} AND ${field} IS NOT NULL)`,
|
|
12398
|
-
args
|
|
12491
|
+
args
|
|
12399
12492
|
};
|
|
12400
12493
|
}
|
|
12401
|
-
return { sql: ninClause, args
|
|
12494
|
+
return { sql: ninClause, args };
|
|
12402
12495
|
}
|
|
12403
12496
|
function translateExists(field, exists) {
|
|
12404
12497
|
return {
|
|
@@ -12406,80 +12499,213 @@ function translateExists(field, exists) {
|
|
|
12406
12499
|
args: []
|
|
12407
12500
|
};
|
|
12408
12501
|
}
|
|
12409
|
-
function translateRegex(field, pattern, options) {
|
|
12410
|
-
const smartResult = smartRegexToLike(field, pattern, options);
|
|
12502
|
+
function translateRegex(field, pattern, options, schema, fieldName) {
|
|
12503
|
+
const smartResult = smartRegexToLike(field, pattern, options, schema, fieldName);
|
|
12411
12504
|
if (smartResult)
|
|
12412
12505
|
return smartResult;
|
|
12413
12506
|
return null;
|
|
12414
12507
|
}
|
|
12415
|
-
function
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12508
|
+
function buildElemMatchConditions(criteria, schema, baseFieldName) {
|
|
12509
|
+
const conditions = [];
|
|
12510
|
+
const args = [];
|
|
12511
|
+
for (const [key, value] of Object.entries(criteria)) {
|
|
12512
|
+
if (key.startsWith("$")) {
|
|
12513
|
+
const fragment = processOperatorValue("value", { [key]: value }, schema, baseFieldName);
|
|
12514
|
+
conditions.push(fragment.sql);
|
|
12515
|
+
args.push(...fragment.args);
|
|
12516
|
+
} else {
|
|
12517
|
+
const propertyField = `json_extract(value, '$.${key}')`;
|
|
12518
|
+
const nestedFieldName = `${baseFieldName}.${key}`;
|
|
12519
|
+
const fragment = processOperatorValue(propertyField, value, schema, nestedFieldName);
|
|
12520
|
+
conditions.push(fragment.sql);
|
|
12521
|
+
args.push(...fragment.args);
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
12420
12524
|
return {
|
|
12421
|
-
sql:
|
|
12422
|
-
args
|
|
12525
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
12526
|
+
args
|
|
12423
12527
|
};
|
|
12424
12528
|
}
|
|
12425
|
-
function
|
|
12426
|
-
if (
|
|
12427
|
-
return { sql: "1=
|
|
12529
|
+
function translateElemMatch(field, criteria, schema, actualFieldName) {
|
|
12530
|
+
if (typeof criteria === "object" && criteria !== null && !Array.isArray(criteria) && Object.keys(criteria).length === 0) {
|
|
12531
|
+
return { sql: "1=0", args: [] };
|
|
12428
12532
|
}
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12533
|
+
if (typeof criteria !== "object" || criteria === null) {
|
|
12534
|
+
return {
|
|
12535
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
|
|
12536
|
+
args: [criteria]
|
|
12537
|
+
};
|
|
12538
|
+
}
|
|
12539
|
+
if (criteria.$and && Array.isArray(criteria.$and)) {
|
|
12540
|
+
const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12541
|
+
const sql = fragments.map((f) => f.sql).join(" AND ");
|
|
12542
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12543
|
+
return {
|
|
12544
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
|
|
12545
|
+
args
|
|
12546
|
+
};
|
|
12547
|
+
}
|
|
12548
|
+
if (criteria.$or && Array.isArray(criteria.$or)) {
|
|
12549
|
+
const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12550
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12551
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12552
|
+
return {
|
|
12553
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
|
|
12554
|
+
args
|
|
12555
|
+
};
|
|
12556
|
+
}
|
|
12557
|
+
if (criteria.$nor && Array.isArray(criteria.$nor)) {
|
|
12558
|
+
const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
|
|
12559
|
+
const sql = fragments.map((f) => f.sql).join(" OR ");
|
|
12560
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12561
|
+
return {
|
|
12562
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE NOT (${sql}))`,
|
|
12563
|
+
args
|
|
12564
|
+
};
|
|
12565
|
+
}
|
|
12566
|
+
const fragment = buildElemMatchConditions(criteria, schema, actualFieldName);
|
|
12435
12567
|
return {
|
|
12436
|
-
sql: `
|
|
12437
|
-
args
|
|
12568
|
+
sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${fragment.sql})`,
|
|
12569
|
+
args: fragment.args
|
|
12438
12570
|
};
|
|
12439
12571
|
}
|
|
12440
|
-
function processOperatorValue(field, value) {
|
|
12572
|
+
function processOperatorValue(field, value, schema, actualFieldName) {
|
|
12441
12573
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12442
12574
|
const [[op, opValue]] = Object.entries(value);
|
|
12575
|
+
if (!op.startsWith("$")) {
|
|
12576
|
+
const jsonPath = `json_extract(${field}, '$.${op}')`;
|
|
12577
|
+
const nestedFieldName = `${actualFieldName}.${op}`;
|
|
12578
|
+
return translateEq(jsonPath, opValue, schema, nestedFieldName);
|
|
12579
|
+
}
|
|
12443
12580
|
switch (op) {
|
|
12444
12581
|
case "$eq":
|
|
12445
|
-
return translateEq(field, opValue);
|
|
12582
|
+
return translateEq(field, opValue, schema, actualFieldName);
|
|
12446
12583
|
case "$ne":
|
|
12447
|
-
return translateNe(field, opValue);
|
|
12584
|
+
return translateNe(field, opValue, schema, actualFieldName);
|
|
12448
12585
|
case "$gt":
|
|
12449
|
-
return translateGt(field, opValue);
|
|
12586
|
+
return translateGt(field, opValue, schema, actualFieldName);
|
|
12450
12587
|
case "$gte":
|
|
12451
|
-
return translateGte(field, opValue);
|
|
12588
|
+
return translateGte(field, opValue, schema, actualFieldName);
|
|
12452
12589
|
case "$lt":
|
|
12453
|
-
return translateLt(field, opValue);
|
|
12590
|
+
return translateLt(field, opValue, schema, actualFieldName);
|
|
12454
12591
|
case "$lte":
|
|
12455
|
-
return translateLte(field, opValue);
|
|
12592
|
+
return translateLte(field, opValue, schema, actualFieldName);
|
|
12456
12593
|
case "$in":
|
|
12457
|
-
return translateIn(field, opValue);
|
|
12594
|
+
return translateIn(field, opValue, schema, actualFieldName);
|
|
12458
12595
|
case "$nin":
|
|
12459
|
-
return translateNin(field, opValue);
|
|
12596
|
+
return translateNin(field, opValue, schema, actualFieldName);
|
|
12597
|
+
case "$exists":
|
|
12598
|
+
return translateExists(field, opValue);
|
|
12599
|
+
case "$size":
|
|
12600
|
+
return translateSize(field, opValue);
|
|
12601
|
+
case "$mod": {
|
|
12602
|
+
const result = translateMod(field, opValue);
|
|
12603
|
+
if (!result)
|
|
12604
|
+
return translateEq(field, opValue, schema, actualFieldName);
|
|
12605
|
+
return result;
|
|
12606
|
+
}
|
|
12607
|
+
case "$regex": {
|
|
12608
|
+
const options = value.$options;
|
|
12609
|
+
const regexFragment = translateRegex(field, opValue, options, schema, actualFieldName);
|
|
12610
|
+
return regexFragment || { sql: "1=0", args: [] };
|
|
12611
|
+
}
|
|
12612
|
+
case "$type": {
|
|
12613
|
+
let jsonCol = "data";
|
|
12614
|
+
let path2 = `$.${actualFieldName}`;
|
|
12615
|
+
let useDirectType = false;
|
|
12616
|
+
if (field === "value") {
|
|
12617
|
+
jsonCol = "value";
|
|
12618
|
+
path2 = "";
|
|
12619
|
+
useDirectType = true;
|
|
12620
|
+
} else if (field.startsWith("json_extract(")) {
|
|
12621
|
+
const match = field.match(/json_extract\(([^,]+),\s*'([^']+)'\)/);
|
|
12622
|
+
if (match) {
|
|
12623
|
+
jsonCol = match[1];
|
|
12624
|
+
path2 = match[2];
|
|
12625
|
+
}
|
|
12626
|
+
}
|
|
12627
|
+
if (useDirectType) {
|
|
12628
|
+
const typeMap = {
|
|
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: [] };
|
|
12646
|
+
}
|
|
12647
|
+
const typeFragment = translateType(jsonCol, path2, opValue, true);
|
|
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;
|
|
12659
|
+
}
|
|
12660
|
+
case "$and": {
|
|
12661
|
+
if (!Array.isArray(opValue))
|
|
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
|
+
}
|
|
12460
12676
|
default:
|
|
12461
|
-
return translateEq(field, opValue);
|
|
12677
|
+
return translateEq(field, opValue, schema, actualFieldName);
|
|
12462
12678
|
}
|
|
12463
12679
|
}
|
|
12464
|
-
return translateEq(field, value);
|
|
12680
|
+
return translateEq(field, value, schema, actualFieldName);
|
|
12681
|
+
}
|
|
12682
|
+
function translateNot(field, criteria, schema, actualFieldName) {
|
|
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);
|
|
12687
|
+
return {
|
|
12688
|
+
sql: `NOT (${inner.sql})`,
|
|
12689
|
+
args: inner.args
|
|
12690
|
+
};
|
|
12465
12691
|
}
|
|
12466
|
-
function translateType(
|
|
12692
|
+
function translateType(jsonColumn, fieldName, type6, isDirectPath = false) {
|
|
12693
|
+
const jsonPath = isDirectPath ? fieldName : `$.${fieldName}`;
|
|
12467
12694
|
switch (type6) {
|
|
12468
|
-
case "number":
|
|
12469
|
-
return {
|
|
12470
|
-
sql: `(typeof(${field}) = 'integer' OR typeof(${field}) = 'real')`,
|
|
12471
|
-
args: []
|
|
12472
|
-
};
|
|
12473
|
-
case "string":
|
|
12474
|
-
return { sql: `typeof(${field}) = 'text'`, args: [] };
|
|
12475
12695
|
case "null":
|
|
12476
|
-
return { sql: `
|
|
12696
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'null'`, args: [] };
|
|
12477
12697
|
case "boolean":
|
|
12698
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('true', 'false')`, args: [] };
|
|
12699
|
+
case "number":
|
|
12700
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('integer', 'real')`, args: [] };
|
|
12701
|
+
case "string":
|
|
12702
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'text'`, args: [] };
|
|
12478
12703
|
case "array":
|
|
12704
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'array'`, args: [] };
|
|
12479
12705
|
case "object":
|
|
12480
|
-
|
|
12706
|
+
return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'object'`, args: [] };
|
|
12481
12707
|
default:
|
|
12482
|
-
return
|
|
12708
|
+
return { sql: "1=0", args: [] };
|
|
12483
12709
|
}
|
|
12484
12710
|
}
|
|
12485
12711
|
function translateSize(field, size) {
|
|
@@ -12488,19 +12714,120 @@ function translateSize(field, size) {
|
|
|
12488
12714
|
args: [size]
|
|
12489
12715
|
};
|
|
12490
12716
|
}
|
|
12491
|
-
function translateMod(field,
|
|
12717
|
+
function translateMod(field, value) {
|
|
12718
|
+
if (!Array.isArray(value) || value.length !== 2)
|
|
12719
|
+
return { sql: "1=0", args: [] };
|
|
12720
|
+
const [divisor, remainder] = value;
|
|
12492
12721
|
return {
|
|
12493
|
-
sql:
|
|
12494
|
-
args: [divisor, remainder]
|
|
12722
|
+
sql: `(${field} - (CAST(${field} / ? AS INTEGER) * ?)) = ?`,
|
|
12723
|
+
args: [divisor, divisor, remainder]
|
|
12495
12724
|
};
|
|
12496
12725
|
}
|
|
12497
12726
|
|
|
12727
|
+
// src/utils/stable-stringify.ts
|
|
12728
|
+
var strEscapeRegex = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/;
|
|
12729
|
+
function strEscape(str) {
|
|
12730
|
+
if (str.length < 5000 && !strEscapeRegex.test(str)) {
|
|
12731
|
+
return `"${str}"`;
|
|
12732
|
+
}
|
|
12733
|
+
return JSON.stringify(str);
|
|
12734
|
+
}
|
|
12735
|
+
function callSafe(method, thisArg) {
|
|
12736
|
+
try {
|
|
12737
|
+
return method.call(thisArg);
|
|
12738
|
+
} catch (error) {
|
|
12739
|
+
return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
|
|
12740
|
+
}
|
|
12741
|
+
}
|
|
12742
|
+
function sortKeys(keys) {
|
|
12743
|
+
if (keys.length > 100) {
|
|
12744
|
+
return keys.sort();
|
|
12745
|
+
}
|
|
12746
|
+
for (let i = 1;i < keys.length; i++) {
|
|
12747
|
+
const current = keys[i];
|
|
12748
|
+
let pos = i;
|
|
12749
|
+
while (pos !== 0 && keys[pos - 1] > current) {
|
|
12750
|
+
keys[pos] = keys[pos - 1];
|
|
12751
|
+
pos--;
|
|
12752
|
+
}
|
|
12753
|
+
keys[pos] = current;
|
|
12754
|
+
}
|
|
12755
|
+
return keys;
|
|
12756
|
+
}
|
|
12757
|
+
function stableStringify(value) {
|
|
12758
|
+
return _stringify(value, []);
|
|
12759
|
+
}
|
|
12760
|
+
function _stringify(value, stack) {
|
|
12761
|
+
if (value === null)
|
|
12762
|
+
return "null";
|
|
12763
|
+
if (value === true)
|
|
12764
|
+
return "true";
|
|
12765
|
+
if (value === false)
|
|
12766
|
+
return "false";
|
|
12767
|
+
const type6 = typeof value;
|
|
12768
|
+
if (type6 === "string")
|
|
12769
|
+
return strEscape(value);
|
|
12770
|
+
if (type6 === "number")
|
|
12771
|
+
return isFinite(value) ? String(value) : "null";
|
|
12772
|
+
if (type6 === "undefined")
|
|
12773
|
+
return "null";
|
|
12774
|
+
if (type6 === "bigint")
|
|
12775
|
+
return String(value);
|
|
12776
|
+
if (Array.isArray(value)) {
|
|
12777
|
+
if (stack.indexOf(value) !== -1)
|
|
12778
|
+
return '"[Circular]"';
|
|
12779
|
+
stack.push(value);
|
|
12780
|
+
if (value.length === 0) {
|
|
12781
|
+
stack.pop();
|
|
12782
|
+
return "[]";
|
|
12783
|
+
}
|
|
12784
|
+
let res = "[" + _stringify(value[0], stack);
|
|
12785
|
+
for (let i = 1;i < value.length; i++) {
|
|
12786
|
+
res += "," + _stringify(value[i], stack);
|
|
12787
|
+
}
|
|
12788
|
+
stack.pop();
|
|
12789
|
+
return res + "]";
|
|
12790
|
+
}
|
|
12791
|
+
if (type6 === "object") {
|
|
12792
|
+
const obj = value;
|
|
12793
|
+
if (stack.indexOf(value) !== -1)
|
|
12794
|
+
return '"[Circular]"';
|
|
12795
|
+
stack.push(value);
|
|
12796
|
+
if ("toJSON" in obj && typeof obj.toJSON === "function") {
|
|
12797
|
+
const result = _stringify(callSafe(obj.toJSON, obj), stack);
|
|
12798
|
+
stack.pop();
|
|
12799
|
+
return result;
|
|
12800
|
+
}
|
|
12801
|
+
const objType = Object.prototype.toString.call(obj);
|
|
12802
|
+
if (objType !== "[object Object]") {
|
|
12803
|
+
const result = JSON.stringify(obj);
|
|
12804
|
+
stack.pop();
|
|
12805
|
+
return result;
|
|
12806
|
+
}
|
|
12807
|
+
const keys = sortKeys(Object.keys(obj));
|
|
12808
|
+
let res = "{";
|
|
12809
|
+
let separator = "";
|
|
12810
|
+
for (let i = 0;i < keys.length; i++) {
|
|
12811
|
+
const key = keys[i];
|
|
12812
|
+
const val = obj[key];
|
|
12813
|
+
if (val === undefined)
|
|
12814
|
+
continue;
|
|
12815
|
+
res += separator + strEscape(key) + ":" + _stringify(val, stack);
|
|
12816
|
+
separator = ",";
|
|
12817
|
+
}
|
|
12818
|
+
stack.pop();
|
|
12819
|
+
return res + "}";
|
|
12820
|
+
}
|
|
12821
|
+
return "null";
|
|
12822
|
+
}
|
|
12823
|
+
|
|
12498
12824
|
// src/query/builder.ts
|
|
12499
|
-
var import_fast_stable_stringify = __toESM(require_fast_stable_stringify(), 1);
|
|
12500
12825
|
var QUERY_CACHE = new Map;
|
|
12501
|
-
var MAX_CACHE_SIZE =
|
|
12502
|
-
function buildWhereClause(selector, schema) {
|
|
12503
|
-
|
|
12826
|
+
var MAX_CACHE_SIZE = 1000;
|
|
12827
|
+
function buildWhereClause(selector, schema, collectionName) {
|
|
12828
|
+
if (!selector || typeof selector !== "object")
|
|
12829
|
+
return null;
|
|
12830
|
+
const cacheKey = `v${schema.version}_${collectionName}_${stableStringify(selector)}`;
|
|
12504
12831
|
const cached = QUERY_CACHE.get(cacheKey);
|
|
12505
12832
|
if (cached) {
|
|
12506
12833
|
QUERY_CACHE.delete(cacheKey);
|
|
@@ -12508,6 +12835,8 @@ function buildWhereClause(selector, schema) {
|
|
|
12508
12835
|
return cached;
|
|
12509
12836
|
}
|
|
12510
12837
|
const result = processSelector(selector, schema, 0);
|
|
12838
|
+
if (!result)
|
|
12839
|
+
return null;
|
|
12511
12840
|
if (QUERY_CACHE.size >= MAX_CACHE_SIZE) {
|
|
12512
12841
|
const firstKey = QUERY_CACHE.keys().next().value;
|
|
12513
12842
|
if (firstKey)
|
|
@@ -12516,12 +12845,27 @@ function buildWhereClause(selector, schema) {
|
|
|
12516
12845
|
QUERY_CACHE.set(cacheKey, result);
|
|
12517
12846
|
return result;
|
|
12518
12847
|
}
|
|
12848
|
+
function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
|
|
12849
|
+
if (conditions.length === 0) {
|
|
12850
|
+
return { sql: operator === "or" ? "1=0" : "1=1", args: [] };
|
|
12851
|
+
}
|
|
12852
|
+
const fragments = conditions.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
|
|
12853
|
+
if (fragments.some((f) => f === null))
|
|
12854
|
+
return null;
|
|
12855
|
+
const sql = fragments.map((f) => `(${f.sql})`).join(" OR ");
|
|
12856
|
+
const args = fragments.flatMap((f) => f.args);
|
|
12857
|
+
return operator === "nor" ? { sql: `NOT(${sql})`, args } : { sql, args };
|
|
12858
|
+
}
|
|
12519
12859
|
function processSelector(selector, schema, logicalDepth) {
|
|
12860
|
+
if (!selector || typeof selector !== "object")
|
|
12861
|
+
return null;
|
|
12520
12862
|
const conditions = [];
|
|
12521
12863
|
const args = [];
|
|
12522
12864
|
for (const [field, value] of Object.entries(selector)) {
|
|
12523
12865
|
if (field === "$and" && Array.isArray(value)) {
|
|
12524
|
-
const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth));
|
|
12866
|
+
const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
|
|
12867
|
+
if (andFragments.some((f) => f === null))
|
|
12868
|
+
return null;
|
|
12525
12869
|
const andConditions = andFragments.map((f) => f.sql);
|
|
12526
12870
|
const needsParens = logicalDepth > 0 && andConditions.length > 1;
|
|
12527
12871
|
const joined = andConditions.join(" AND ");
|
|
@@ -12530,81 +12874,94 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12530
12874
|
continue;
|
|
12531
12875
|
}
|
|
12532
12876
|
if (field === "$or" && Array.isArray(value)) {
|
|
12533
|
-
const
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
orFragments.forEach((f) => args.push(...f.args));
|
|
12877
|
+
const orFragment = buildLogicalOperator("or", value, schema, logicalDepth);
|
|
12878
|
+
if (!orFragment)
|
|
12879
|
+
return null;
|
|
12880
|
+
conditions.push(`(${orFragment.sql})`);
|
|
12881
|
+
args.push(...orFragment.args);
|
|
12539
12882
|
continue;
|
|
12540
12883
|
}
|
|
12541
12884
|
if (field === "$nor" && Array.isArray(value)) {
|
|
12542
|
-
const norFragment =
|
|
12885
|
+
const norFragment = buildLogicalOperator("nor", value, schema, logicalDepth);
|
|
12886
|
+
if (!norFragment)
|
|
12887
|
+
return null;
|
|
12543
12888
|
conditions.push(norFragment.sql);
|
|
12544
12889
|
args.push(...norFragment.args);
|
|
12545
12890
|
continue;
|
|
12546
12891
|
}
|
|
12547
12892
|
const columnInfo = getColumnInfo(field, schema);
|
|
12548
12893
|
const fieldName = columnInfo.column || `json_extract(data, '${columnInfo.jsonPath}')`;
|
|
12894
|
+
const actualFieldName = columnInfo.jsonPath?.replace(/^\$\./, "") || columnInfo.column || field;
|
|
12549
12895
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
12896
|
+
if (Object.keys(value).length === 0) {
|
|
12897
|
+
return { sql: "1=0", args: [] };
|
|
12898
|
+
}
|
|
12550
12899
|
for (const [op, opValue] of Object.entries(value)) {
|
|
12551
12900
|
let fragment;
|
|
12552
12901
|
switch (op) {
|
|
12553
12902
|
case "$eq":
|
|
12554
|
-
fragment = translateEq(fieldName, opValue);
|
|
12903
|
+
fragment = translateEq(fieldName, opValue, schema, actualFieldName);
|
|
12555
12904
|
break;
|
|
12556
12905
|
case "$ne":
|
|
12557
|
-
fragment = translateNe(fieldName, opValue);
|
|
12906
|
+
fragment = translateNe(fieldName, opValue, schema, actualFieldName);
|
|
12558
12907
|
break;
|
|
12559
12908
|
case "$gt":
|
|
12560
|
-
fragment = translateGt(fieldName, opValue);
|
|
12909
|
+
fragment = translateGt(fieldName, opValue, schema, actualFieldName);
|
|
12561
12910
|
break;
|
|
12562
12911
|
case "$gte":
|
|
12563
|
-
fragment = translateGte(fieldName, opValue);
|
|
12912
|
+
fragment = translateGte(fieldName, opValue, schema, actualFieldName);
|
|
12564
12913
|
break;
|
|
12565
12914
|
case "$lt":
|
|
12566
|
-
fragment = translateLt(fieldName, opValue);
|
|
12915
|
+
fragment = translateLt(fieldName, opValue, schema, actualFieldName);
|
|
12567
12916
|
break;
|
|
12568
12917
|
case "$lte":
|
|
12569
|
-
fragment = translateLte(fieldName, opValue);
|
|
12918
|
+
fragment = translateLte(fieldName, opValue, schema, actualFieldName);
|
|
12570
12919
|
break;
|
|
12571
12920
|
case "$in":
|
|
12572
|
-
fragment = translateIn(fieldName, opValue);
|
|
12921
|
+
fragment = translateIn(fieldName, opValue, schema, actualFieldName);
|
|
12573
12922
|
break;
|
|
12574
12923
|
case "$nin":
|
|
12575
|
-
fragment = translateNin(fieldName, opValue);
|
|
12924
|
+
fragment = translateNin(fieldName, opValue, schema, actualFieldName);
|
|
12576
12925
|
break;
|
|
12577
12926
|
case "$exists":
|
|
12578
12927
|
fragment = translateExists(fieldName, opValue);
|
|
12579
12928
|
break;
|
|
12580
12929
|
case "$regex":
|
|
12581
12930
|
const options = value.$options;
|
|
12582
|
-
const regexFragment = translateRegex(fieldName, opValue, options);
|
|
12931
|
+
const regexFragment = translateRegex(fieldName, opValue, options, schema, actualFieldName);
|
|
12583
12932
|
if (!regexFragment)
|
|
12584
|
-
|
|
12933
|
+
return null;
|
|
12585
12934
|
fragment = regexFragment;
|
|
12586
12935
|
break;
|
|
12587
12936
|
case "$elemMatch":
|
|
12588
|
-
const elemMatchFragment = translateElemMatch(fieldName, opValue);
|
|
12937
|
+
const elemMatchFragment = translateElemMatch(fieldName, opValue, schema, actualFieldName);
|
|
12589
12938
|
if (!elemMatchFragment)
|
|
12590
|
-
|
|
12939
|
+
return null;
|
|
12591
12940
|
fragment = elemMatchFragment;
|
|
12592
12941
|
break;
|
|
12593
|
-
case "$not":
|
|
12594
|
-
|
|
12942
|
+
case "$not": {
|
|
12943
|
+
const notResult = translateNot(fieldName, opValue, schema, actualFieldName);
|
|
12944
|
+
if (!notResult)
|
|
12945
|
+
return null;
|
|
12946
|
+
fragment = notResult;
|
|
12595
12947
|
break;
|
|
12948
|
+
}
|
|
12596
12949
|
case "$type":
|
|
12597
|
-
const typeFragment = translateType(
|
|
12950
|
+
const typeFragment = translateType("data", actualFieldName, opValue);
|
|
12598
12951
|
if (!typeFragment)
|
|
12599
|
-
|
|
12952
|
+
return null;
|
|
12600
12953
|
fragment = typeFragment;
|
|
12601
12954
|
break;
|
|
12602
12955
|
case "$size":
|
|
12603
12956
|
fragment = translateSize(fieldName, opValue);
|
|
12604
12957
|
break;
|
|
12605
|
-
case "$mod":
|
|
12606
|
-
|
|
12958
|
+
case "$mod": {
|
|
12959
|
+
const modResult = translateMod(fieldName, opValue);
|
|
12960
|
+
if (!modResult)
|
|
12961
|
+
return null;
|
|
12962
|
+
fragment = modResult;
|
|
12607
12963
|
break;
|
|
12964
|
+
}
|
|
12608
12965
|
default:
|
|
12609
12966
|
continue;
|
|
12610
12967
|
}
|
|
@@ -12612,7 +12969,7 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12612
12969
|
args.push(...fragment.args);
|
|
12613
12970
|
}
|
|
12614
12971
|
} else {
|
|
12615
|
-
const fragment = translateEq(fieldName, value);
|
|
12972
|
+
const fragment = translateEq(fieldName, value, schema, actualFieldName);
|
|
12616
12973
|
conditions.push(fragment.sql);
|
|
12617
12974
|
args.push(...fragment.args);
|
|
12618
12975
|
}
|
|
@@ -12621,6 +12978,100 @@ function processSelector(selector, schema, logicalDepth) {
|
|
|
12621
12978
|
return { sql: where, args };
|
|
12622
12979
|
}
|
|
12623
12980
|
|
|
12981
|
+
// src/query/lightweight-matcher.ts
|
|
12982
|
+
var operators = {
|
|
12983
|
+
$eq: (a, b) => a === b,
|
|
12984
|
+
$ne: (a, b) => a !== b,
|
|
12985
|
+
$gt: (a, b) => a > b,
|
|
12986
|
+
$gte: (a, b) => a >= b,
|
|
12987
|
+
$lt: (a, b) => a < b,
|
|
12988
|
+
$lte: (a, b) => a <= b,
|
|
12989
|
+
$in: (a, b) => Array.isArray(b) && b.some((v) => v === a),
|
|
12990
|
+
$nin: (a, b) => Array.isArray(b) && !b.some((v) => v === a),
|
|
12991
|
+
$exists: (a, b) => a !== undefined === b,
|
|
12992
|
+
$type: (a, b) => {
|
|
12993
|
+
const type6 = Array.isArray(a) ? "array" : typeof a;
|
|
12994
|
+
return type6 === b;
|
|
12995
|
+
},
|
|
12996
|
+
$mod: (a, b) => {
|
|
12997
|
+
if (!Array.isArray(b) || b.length !== 2)
|
|
12998
|
+
return false;
|
|
12999
|
+
const [divisor, remainder] = b;
|
|
13000
|
+
return typeof a === "number" && a % divisor === remainder;
|
|
13001
|
+
},
|
|
13002
|
+
$size: (a, b) => Array.isArray(a) && a.length === b
|
|
13003
|
+
};
|
|
13004
|
+
function getNestedValue(obj, path2) {
|
|
13005
|
+
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13006
|
+
}
|
|
13007
|
+
function matchesSelector(doc, selector) {
|
|
13008
|
+
if (!selector || typeof selector !== "object")
|
|
13009
|
+
return true;
|
|
13010
|
+
if (selector.$and) {
|
|
13011
|
+
return Array.isArray(selector.$and) && selector.$and.every((s) => matchesSelector(doc, s));
|
|
13012
|
+
}
|
|
13013
|
+
if (selector.$or) {
|
|
13014
|
+
return Array.isArray(selector.$or) && selector.$or.some((s) => matchesSelector(doc, s));
|
|
13015
|
+
}
|
|
13016
|
+
if (selector.$nor) {
|
|
13017
|
+
return Array.isArray(selector.$nor) && !selector.$nor.some((s) => matchesSelector(doc, s));
|
|
13018
|
+
}
|
|
13019
|
+
for (const [field, condition] of Object.entries(selector)) {
|
|
13020
|
+
const value = getNestedValue(doc, field);
|
|
13021
|
+
if (typeof condition !== "object" || condition === null || Array.isArray(condition)) {
|
|
13022
|
+
if (value !== condition)
|
|
13023
|
+
return false;
|
|
13024
|
+
continue;
|
|
13025
|
+
}
|
|
13026
|
+
for (const [op, opValue] of Object.entries(condition)) {
|
|
13027
|
+
if (op === "$regex") {
|
|
13028
|
+
const options = condition.$options;
|
|
13029
|
+
if (!matchesRegex(value, opValue, options))
|
|
13030
|
+
return false;
|
|
13031
|
+
continue;
|
|
13032
|
+
}
|
|
13033
|
+
if (op === "$not") {
|
|
13034
|
+
if (matchesOperator(value, opValue))
|
|
13035
|
+
return false;
|
|
13036
|
+
continue;
|
|
13037
|
+
}
|
|
13038
|
+
if (op === "$elemMatch") {
|
|
13039
|
+
if (!Array.isArray(value))
|
|
13040
|
+
return false;
|
|
13041
|
+
const hasMatch = value.some((item) => matchesSelector(item, opValue));
|
|
13042
|
+
if (!hasMatch)
|
|
13043
|
+
return false;
|
|
13044
|
+
continue;
|
|
13045
|
+
}
|
|
13046
|
+
if (op === "$options")
|
|
13047
|
+
continue;
|
|
13048
|
+
const operator = operators[op];
|
|
13049
|
+
if (!operator)
|
|
13050
|
+
return false;
|
|
13051
|
+
if (!operator(value, opValue))
|
|
13052
|
+
return false;
|
|
13053
|
+
}
|
|
13054
|
+
}
|
|
13055
|
+
return true;
|
|
13056
|
+
}
|
|
13057
|
+
function matchesOperator(value, operator) {
|
|
13058
|
+
if (typeof operator !== "object" || operator === null) {
|
|
13059
|
+
return value === operator;
|
|
13060
|
+
}
|
|
13061
|
+
for (const [op, opValue] of Object.entries(operator)) {
|
|
13062
|
+
if (op === "$regex") {
|
|
13063
|
+
const options = operator.$options;
|
|
13064
|
+
return matchesRegex(value, opValue, options);
|
|
13065
|
+
}
|
|
13066
|
+
const operatorFn = operators[op];
|
|
13067
|
+
if (!operatorFn)
|
|
13068
|
+
return false;
|
|
13069
|
+
if (operatorFn(value, opValue))
|
|
13070
|
+
return true;
|
|
13071
|
+
}
|
|
13072
|
+
return false;
|
|
13073
|
+
}
|
|
13074
|
+
|
|
12624
13075
|
// src/rxdb-helpers.ts
|
|
12625
13076
|
function randomToken2(length) {
|
|
12626
13077
|
return Math.random().toString(36).substring(2, 2 + length);
|
|
@@ -12828,32 +13279,64 @@ function ensureRxStorageInstanceParamsAreCorrect(params) {
|
|
|
12828
13279
|
class StatementManager {
|
|
12829
13280
|
db;
|
|
12830
13281
|
staticStatements = new Map;
|
|
13282
|
+
static MAX_STATEMENTS = 500;
|
|
13283
|
+
closed = false;
|
|
12831
13284
|
constructor(db) {
|
|
12832
13285
|
this.db = db;
|
|
12833
13286
|
}
|
|
13287
|
+
checkClosed() {
|
|
13288
|
+
if (this.closed) {
|
|
13289
|
+
throw new Error("StatementManager is closed");
|
|
13290
|
+
}
|
|
13291
|
+
}
|
|
13292
|
+
evictOldest() {
|
|
13293
|
+
if (this.staticStatements.size >= StatementManager.MAX_STATEMENTS) {
|
|
13294
|
+
const firstKey = this.staticStatements.keys().next().value;
|
|
13295
|
+
if (firstKey) {
|
|
13296
|
+
const stmt = this.staticStatements.get(firstKey);
|
|
13297
|
+
stmt?.finalize();
|
|
13298
|
+
this.staticStatements.delete(firstKey);
|
|
13299
|
+
}
|
|
13300
|
+
}
|
|
13301
|
+
}
|
|
12834
13302
|
all(queryWithParams) {
|
|
13303
|
+
this.checkClosed();
|
|
12835
13304
|
const { query, params } = queryWithParams;
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
this.staticStatements.set(query, stmt);
|
|
12841
|
-
}
|
|
12842
|
-
return stmt.all(...params);
|
|
13305
|
+
let stmt = this.staticStatements.get(query);
|
|
13306
|
+
if (stmt) {
|
|
13307
|
+
this.staticStatements.delete(query);
|
|
13308
|
+
this.staticStatements.set(query, stmt);
|
|
12843
13309
|
} else {
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
} finally {
|
|
12848
|
-
stmt.finalize();
|
|
12849
|
-
}
|
|
13310
|
+
this.evictOldest();
|
|
13311
|
+
stmt = this.db.query(query);
|
|
13312
|
+
this.staticStatements.set(query, stmt);
|
|
12850
13313
|
}
|
|
13314
|
+
return stmt.all(...params);
|
|
13315
|
+
}
|
|
13316
|
+
get(queryWithParams) {
|
|
13317
|
+
this.checkClosed();
|
|
13318
|
+
const { query, params } = queryWithParams;
|
|
13319
|
+
let stmt = this.staticStatements.get(query);
|
|
13320
|
+
if (stmt) {
|
|
13321
|
+
this.staticStatements.delete(query);
|
|
13322
|
+
this.staticStatements.set(query, stmt);
|
|
13323
|
+
} else {
|
|
13324
|
+
this.evictOldest();
|
|
13325
|
+
stmt = this.db.query(query);
|
|
13326
|
+
this.staticStatements.set(query, stmt);
|
|
13327
|
+
}
|
|
13328
|
+
return stmt.get(...params);
|
|
12851
13329
|
}
|
|
12852
13330
|
run(queryWithParams) {
|
|
13331
|
+
this.checkClosed();
|
|
12853
13332
|
const { query, params } = queryWithParams;
|
|
12854
13333
|
if (this.isStaticSQL(query)) {
|
|
12855
13334
|
let stmt = this.staticStatements.get(query);
|
|
12856
|
-
if (
|
|
13335
|
+
if (stmt) {
|
|
13336
|
+
this.staticStatements.delete(query);
|
|
13337
|
+
this.staticStatements.set(query, stmt);
|
|
13338
|
+
} else {
|
|
13339
|
+
this.evictOldest();
|
|
12857
13340
|
stmt = this.db.query(query);
|
|
12858
13341
|
this.staticStatements.set(query, stmt);
|
|
12859
13342
|
}
|
|
@@ -12872,6 +13355,7 @@ class StatementManager {
|
|
|
12872
13355
|
stmt.finalize();
|
|
12873
13356
|
}
|
|
12874
13357
|
this.staticStatements.clear();
|
|
13358
|
+
this.closed = true;
|
|
12875
13359
|
}
|
|
12876
13360
|
isStaticSQL(query) {
|
|
12877
13361
|
if (query.includes("WHERE (")) {
|
|
@@ -12912,6 +13396,28 @@ function releaseDatabase(databaseName) {
|
|
|
12912
13396
|
}
|
|
12913
13397
|
}
|
|
12914
13398
|
|
|
13399
|
+
// src/transaction-queue.ts
|
|
13400
|
+
var TX_QUEUE_BY_DATABASE = new WeakMap;
|
|
13401
|
+
async function sqliteTransaction(database, handler) {
|
|
13402
|
+
let queue = TX_QUEUE_BY_DATABASE.get(database);
|
|
13403
|
+
if (!queue) {
|
|
13404
|
+
queue = Promise.resolve();
|
|
13405
|
+
}
|
|
13406
|
+
const result = queue.then(async () => {
|
|
13407
|
+
database.run("BEGIN IMMEDIATE");
|
|
13408
|
+
try {
|
|
13409
|
+
const handlerResult = await handler();
|
|
13410
|
+
database.run("COMMIT");
|
|
13411
|
+
return handlerResult;
|
|
13412
|
+
} catch (error) {
|
|
13413
|
+
database.run("ROLLBACK");
|
|
13414
|
+
throw error;
|
|
13415
|
+
}
|
|
13416
|
+
});
|
|
13417
|
+
TX_QUEUE_BY_DATABASE.set(database, result.then(() => {}).catch(() => {}));
|
|
13418
|
+
return result;
|
|
13419
|
+
}
|
|
13420
|
+
|
|
12915
13421
|
// src/instance.ts
|
|
12916
13422
|
class BunSQLiteStorageInstance {
|
|
12917
13423
|
db;
|
|
@@ -12947,6 +13453,15 @@ class BunSQLiteStorageInstance {
|
|
|
12947
13453
|
if (filename !== ":memory:") {
|
|
12948
13454
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
12949
13455
|
this.db.run("PRAGMA synchronous = NORMAL");
|
|
13456
|
+
this.db.run("PRAGMA wal_autocheckpoint = 1000");
|
|
13457
|
+
this.db.run("PRAGMA cache_size = -32000");
|
|
13458
|
+
this.db.run("PRAGMA analysis_limit = 400");
|
|
13459
|
+
const mmapSize = this.options.mmapSize ?? 268435456;
|
|
13460
|
+
if (mmapSize > 0) {
|
|
13461
|
+
this.db.run(`PRAGMA mmap_size = ${mmapSize}`);
|
|
13462
|
+
}
|
|
13463
|
+
this.db.run("PRAGMA temp_store = MEMORY");
|
|
13464
|
+
this.db.run("PRAGMA locking_mode = NORMAL");
|
|
12950
13465
|
}
|
|
12951
13466
|
this.db.run(`
|
|
12952
13467
|
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
@@ -12962,8 +13477,17 @@ class BunSQLiteStorageInstance {
|
|
|
12962
13477
|
if (this.schema.indexes) {
|
|
12963
13478
|
for (const index of this.schema.indexes) {
|
|
12964
13479
|
const fields = Array.isArray(index) ? index : [index];
|
|
12965
|
-
const indexName = `idx_${this.tableName}_${fields.join("_")}`;
|
|
12966
|
-
const columns = fields.map((field) =>
|
|
13480
|
+
const indexName = `idx_${this.tableName}_${fields.join("_").replace(/[()]/g, "_")}`;
|
|
13481
|
+
const columns = fields.map((field) => {
|
|
13482
|
+
if (typeof field !== "string")
|
|
13483
|
+
return `json_extract(data, '$.${field}')`;
|
|
13484
|
+
const funcMatch = field.match(/^(\w+)\((.+)\)$/);
|
|
13485
|
+
if (funcMatch) {
|
|
13486
|
+
const [, func, fieldName] = funcMatch;
|
|
13487
|
+
return `${func}(json_extract(data, '$.${fieldName}'))`;
|
|
13488
|
+
}
|
|
13489
|
+
return `json_extract(data, '$.${field}')`;
|
|
13490
|
+
}).join(", ");
|
|
12967
13491
|
this.db.run(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${this.tableName}"(${columns})`);
|
|
12968
13492
|
}
|
|
12969
13493
|
}
|
|
@@ -12976,69 +13500,85 @@ class BunSQLiteStorageInstance {
|
|
|
12976
13500
|
`);
|
|
12977
13501
|
}
|
|
12978
13502
|
async bulkWrite(documentWrites, context) {
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
const
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13503
|
+
return sqliteTransaction(this.db, async () => {
|
|
13504
|
+
if (documentWrites.length === 0) {
|
|
13505
|
+
return { error: [] };
|
|
13506
|
+
}
|
|
13507
|
+
const ids = documentWrites.map((w) => w.document[this.primaryPath]);
|
|
13508
|
+
const docsInDb = await this.findDocumentsById(ids, true);
|
|
13509
|
+
const docsInDbMap = new Map(docsInDb.map((d) => [d[this.primaryPath], d]));
|
|
13510
|
+
const categorized = categorizeBulkWriteRows(this, this.primaryPath, docsInDbMap, documentWrites, context);
|
|
13511
|
+
const updateQuery = `UPDATE "${this.tableName}" SET data = jsonb(?), deleted = ?, rev = ?, mtime_ms = ? WHERE id = ?`;
|
|
13512
|
+
const BATCH_SIZE = 100;
|
|
13513
|
+
for (let i = 0;i < categorized.bulkInsertDocs.length; i += BATCH_SIZE) {
|
|
13514
|
+
const batch = categorized.bulkInsertDocs.slice(i, i + BATCH_SIZE);
|
|
13515
|
+
const placeholders = batch.map(() => "(?, jsonb(?), ?, ?, ?)").join(", ");
|
|
13516
|
+
const insertQuery = `INSERT INTO "${this.tableName}" (id, data, deleted, rev, mtime_ms) VALUES ${placeholders}`;
|
|
13517
|
+
const params = [];
|
|
13518
|
+
for (const row of batch) {
|
|
13519
|
+
const doc = row.document;
|
|
13520
|
+
const id = doc[this.primaryPath];
|
|
13521
|
+
params.push(id, JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt);
|
|
13522
|
+
}
|
|
13523
|
+
try {
|
|
13524
|
+
this.stmtManager.run({ query: insertQuery, params });
|
|
13525
|
+
} catch (err) {
|
|
13526
|
+
if (err && typeof err === "object" && "code" in err && (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || err.code === "SQLITE_CONSTRAINT_UNIQUE")) {
|
|
13527
|
+
for (const row of batch) {
|
|
13528
|
+
const doc = row.document;
|
|
13529
|
+
const id = doc[this.primaryPath];
|
|
13530
|
+
const documentInDb = docsInDbMap.get(id);
|
|
13531
|
+
categorized.errors.push({
|
|
13532
|
+
isError: true,
|
|
13533
|
+
status: 409,
|
|
13534
|
+
documentId: id,
|
|
13535
|
+
writeRow: row,
|
|
13536
|
+
documentInDb: documentInDb || doc
|
|
13537
|
+
});
|
|
13538
|
+
}
|
|
13539
|
+
} else {
|
|
13540
|
+
throw err;
|
|
13541
|
+
}
|
|
13005
13542
|
}
|
|
13006
13543
|
}
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
this.
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
categorized.eventBulk.
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13544
|
+
for (let i = 0;i < categorized.bulkUpdateDocs.length; i += BATCH_SIZE) {
|
|
13545
|
+
const batch = categorized.bulkUpdateDocs.slice(i, i + BATCH_SIZE);
|
|
13546
|
+
for (const row of batch) {
|
|
13547
|
+
const doc = row.document;
|
|
13548
|
+
const id = doc[this.primaryPath];
|
|
13549
|
+
this.stmtManager.run({ query: updateQuery, params: [JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt, id] });
|
|
13550
|
+
}
|
|
13551
|
+
}
|
|
13552
|
+
const insertAttQuery = `INSERT OR REPLACE INTO "${this.tableName}_attachments" (id, data, digest) VALUES (?, ?, ?)`;
|
|
13553
|
+
const deleteAttQuery = `DELETE FROM "${this.tableName}_attachments" WHERE id = ?`;
|
|
13554
|
+
for (const att of [...categorized.attachmentsAdd, ...categorized.attachmentsUpdate]) {
|
|
13555
|
+
this.stmtManager.run({
|
|
13556
|
+
query: insertAttQuery,
|
|
13557
|
+
params: [
|
|
13558
|
+
this.attachmentMapKey(att.documentId, att.attachmentId),
|
|
13559
|
+
att.attachmentData.data,
|
|
13560
|
+
att.digest
|
|
13561
|
+
]
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13564
|
+
for (const att of categorized.attachmentsRemove) {
|
|
13565
|
+
this.stmtManager.run({
|
|
13566
|
+
query: deleteAttQuery,
|
|
13567
|
+
params: [this.attachmentMapKey(att.documentId, att.attachmentId)]
|
|
13568
|
+
});
|
|
13569
|
+
}
|
|
13570
|
+
const failedDocIds = new Set(categorized.errors.map((e) => e.documentId));
|
|
13571
|
+
categorized.eventBulk.events = categorized.eventBulk.events.filter((event) => !failedDocIds.has(event.documentId));
|
|
13572
|
+
if (categorized.eventBulk.events.length > 0 && categorized.newestRow) {
|
|
13573
|
+
const lastState = categorized.newestRow.document;
|
|
13574
|
+
categorized.eventBulk.checkpoint = {
|
|
13575
|
+
id: lastState[this.primaryPath],
|
|
13576
|
+
lwt: lastState._meta.lwt
|
|
13577
|
+
};
|
|
13578
|
+
this.changeStream$.next(categorized.eventBulk);
|
|
13579
|
+
}
|
|
13580
|
+
return { error: categorized.errors };
|
|
13581
|
+
});
|
|
13042
13582
|
}
|
|
13043
13583
|
async findDocumentsById(ids, withDeleted) {
|
|
13044
13584
|
if (ids.length === 0)
|
|
@@ -13050,72 +13590,42 @@ class BunSQLiteStorageInstance {
|
|
|
13050
13590
|
return rows.map((row) => JSON.parse(row.data));
|
|
13051
13591
|
}
|
|
13052
13592
|
async query(preparedQuery) {
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
}
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
}
|
|
13079
|
-
|
|
13080
|
-
const
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
if (preparedQuery.query.limit) {
|
|
13090
|
-
documents = documents.slice(0, preparedQuery.query.limit);
|
|
13091
|
-
}
|
|
13092
|
-
return { documents };
|
|
13093
|
-
}
|
|
13094
|
-
}
|
|
13095
|
-
matchesSelector(doc, selector) {
|
|
13096
|
-
for (const [key, value] of Object.entries(selector)) {
|
|
13097
|
-
const docValue = this.getNestedValue(doc, key);
|
|
13098
|
-
if (typeof value === "object" && value !== null) {
|
|
13099
|
-
for (const [op, opValue] of Object.entries(value)) {
|
|
13100
|
-
if (op === "$eq" && docValue !== opValue)
|
|
13101
|
-
return false;
|
|
13102
|
-
if (op === "$ne" && docValue === opValue)
|
|
13103
|
-
return false;
|
|
13104
|
-
if (op === "$gt" && !(docValue > opValue))
|
|
13105
|
-
return false;
|
|
13106
|
-
if (op === "$gte" && !(docValue >= opValue))
|
|
13107
|
-
return false;
|
|
13108
|
-
if (op === "$lt" && !(docValue < opValue))
|
|
13109
|
-
return false;
|
|
13110
|
-
if (op === "$lte" && !(docValue <= opValue))
|
|
13111
|
-
return false;
|
|
13112
|
-
}
|
|
13113
|
-
} else {
|
|
13114
|
-
if (docValue !== value)
|
|
13115
|
-
return false;
|
|
13116
|
-
}
|
|
13117
|
-
}
|
|
13118
|
-
return true;
|
|
13593
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13594
|
+
if (!whereResult) {
|
|
13595
|
+
return this.queryWithOurMemory(preparedQuery);
|
|
13596
|
+
}
|
|
13597
|
+
const { sql: whereClause, args } = whereResult;
|
|
13598
|
+
let sql = `SELECT json(data) as data FROM "${this.tableName}" WHERE (${whereClause})`;
|
|
13599
|
+
const queryArgs = [...args];
|
|
13600
|
+
if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
|
|
13601
|
+
const orderBy = preparedQuery.query.sort.map((sortField) => {
|
|
13602
|
+
const [field, direction] = Object.entries(sortField)[0];
|
|
13603
|
+
const dir = direction === "asc" ? "ASC" : "DESC";
|
|
13604
|
+
return `json_extract(data, '$.${field}') ${dir}`;
|
|
13605
|
+
}).join(", ");
|
|
13606
|
+
sql += ` ORDER BY ${orderBy}`;
|
|
13607
|
+
}
|
|
13608
|
+
if (preparedQuery.query.limit) {
|
|
13609
|
+
sql += ` LIMIT ?`;
|
|
13610
|
+
queryArgs.push(preparedQuery.query.limit);
|
|
13611
|
+
}
|
|
13612
|
+
if (preparedQuery.query.skip) {
|
|
13613
|
+
if (!preparedQuery.query.limit) {
|
|
13614
|
+
sql += ` LIMIT -1`;
|
|
13615
|
+
}
|
|
13616
|
+
sql += ` OFFSET ?`;
|
|
13617
|
+
queryArgs.push(preparedQuery.query.skip);
|
|
13618
|
+
}
|
|
13619
|
+
if (process.env.DEBUG_QUERIES) {
|
|
13620
|
+
const explainSql = `EXPLAIN QUERY PLAN ${sql}`;
|
|
13621
|
+
const plan = this.stmtManager.all({ query: explainSql, params: queryArgs });
|
|
13622
|
+
console.log("[DEBUG_QUERIES] Query plan:", JSON.stringify(plan, null, 2));
|
|
13623
|
+
console.log("[DEBUG_QUERIES] SQL:", sql);
|
|
13624
|
+
console.log("[DEBUG_QUERIES] Args:", queryArgs);
|
|
13625
|
+
}
|
|
13626
|
+
const rows = this.stmtManager.all({ query: sql, params: queryArgs });
|
|
13627
|
+
const documents = rows.map((row) => JSON.parse(row.data));
|
|
13628
|
+
return { documents };
|
|
13119
13629
|
}
|
|
13120
13630
|
sortDocuments(docs, sort) {
|
|
13121
13631
|
return docs.sort((a, b) => {
|
|
@@ -13135,9 +13645,18 @@ class BunSQLiteStorageInstance {
|
|
|
13135
13645
|
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
13136
13646
|
}
|
|
13137
13647
|
async count(preparedQuery) {
|
|
13138
|
-
const
|
|
13648
|
+
const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
|
|
13649
|
+
if (!whereResult) {
|
|
13650
|
+
const allDocs = await this.queryWithOurMemory(preparedQuery);
|
|
13651
|
+
return {
|
|
13652
|
+
count: allDocs.documents.length,
|
|
13653
|
+
mode: "fast"
|
|
13654
|
+
};
|
|
13655
|
+
}
|
|
13656
|
+
const { sql, args } = whereResult;
|
|
13657
|
+
const result = this.db.query(`SELECT COUNT(*) as count FROM "${this.tableName}" WHERE (${sql})`).get(...args);
|
|
13139
13658
|
return {
|
|
13140
|
-
count: result
|
|
13659
|
+
count: result?.count ?? 0,
|
|
13141
13660
|
mode: "fast"
|
|
13142
13661
|
};
|
|
13143
13662
|
}
|
|
@@ -13198,9 +13717,64 @@ class BunSQLiteStorageInstance {
|
|
|
13198
13717
|
const rows = this.stmtManager.all({ query: sql, params: [checkpointLwt, checkpointLwt, checkpointId, limit] });
|
|
13199
13718
|
const documents = rows.map((row) => JSON.parse(row.data));
|
|
13200
13719
|
const lastDoc = documents[documents.length - 1];
|
|
13201
|
-
const newCheckpoint = lastDoc ? {
|
|
13720
|
+
const newCheckpoint = lastDoc ? {
|
|
13721
|
+
id: String(lastDoc[this.primaryPath]),
|
|
13722
|
+
lwt: lastDoc._meta.lwt
|
|
13723
|
+
} : checkpoint ?? null;
|
|
13202
13724
|
return { documents, checkpoint: newCheckpoint };
|
|
13203
13725
|
}
|
|
13726
|
+
queryWithOurMemory(preparedQuery) {
|
|
13727
|
+
const query = `SELECT json(data) as data FROM "${this.tableName}"`;
|
|
13728
|
+
const selector = preparedQuery.query.selector;
|
|
13729
|
+
const hasSort = preparedQuery.query.sort && preparedQuery.query.sort.length > 0;
|
|
13730
|
+
if (hasSort) {
|
|
13731
|
+
const rows = this.stmtManager.all({ query, params: [] });
|
|
13732
|
+
let documents2 = rows.map((row) => JSON.parse(row.data));
|
|
13733
|
+
documents2 = documents2.filter((doc) => matchesSelector(doc, selector));
|
|
13734
|
+
documents2 = this.sortDocuments(documents2, preparedQuery.query.sort);
|
|
13735
|
+
if (preparedQuery.query.skip) {
|
|
13736
|
+
documents2 = documents2.slice(preparedQuery.query.skip);
|
|
13737
|
+
}
|
|
13738
|
+
if (preparedQuery.query.limit) {
|
|
13739
|
+
documents2 = documents2.slice(0, preparedQuery.query.limit);
|
|
13740
|
+
}
|
|
13741
|
+
return { documents: documents2 };
|
|
13742
|
+
}
|
|
13743
|
+
const stmt = this.db.prepare(query);
|
|
13744
|
+
const documents = [];
|
|
13745
|
+
const skip = preparedQuery.query.skip || 0;
|
|
13746
|
+
const limit = preparedQuery.query.limit;
|
|
13747
|
+
let skipped = 0;
|
|
13748
|
+
for (const row of stmt.iterate()) {
|
|
13749
|
+
const doc = JSON.parse(row.data);
|
|
13750
|
+
if (matchesSelector(doc, selector)) {
|
|
13751
|
+
if (skipped < skip) {
|
|
13752
|
+
skipped++;
|
|
13753
|
+
continue;
|
|
13754
|
+
}
|
|
13755
|
+
documents.push(doc);
|
|
13756
|
+
if (limit && documents.length >= limit) {
|
|
13757
|
+
break;
|
|
13758
|
+
}
|
|
13759
|
+
}
|
|
13760
|
+
}
|
|
13761
|
+
return { documents };
|
|
13762
|
+
}
|
|
13763
|
+
matchesRegexSelector(doc, selector) {
|
|
13764
|
+
for (const [field, value] of Object.entries(selector)) {
|
|
13765
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
13766
|
+
const ops = value;
|
|
13767
|
+
if (ops.$regex) {
|
|
13768
|
+
const fieldValue = this.getNestedValue(doc, field);
|
|
13769
|
+
const pattern = ops.$regex;
|
|
13770
|
+
const options = ops.$options;
|
|
13771
|
+
if (!matchesRegex(fieldValue, pattern, options))
|
|
13772
|
+
return false;
|
|
13773
|
+
}
|
|
13774
|
+
}
|
|
13775
|
+
}
|
|
13776
|
+
return true;
|
|
13777
|
+
}
|
|
13204
13778
|
}
|
|
13205
13779
|
|
|
13206
13780
|
// src/storage.ts
|