document-dataply 0.0.6 → 0.0.7-alpha.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/cjs/index.js
CHANGED
|
@@ -1757,6 +1757,39 @@ var require_cjs = __commonJS({
|
|
|
1757
1757
|
}
|
|
1758
1758
|
return true;
|
|
1759
1759
|
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Inserts a key-value pair into an already-cloned leaf node in-place.
|
|
1762
|
+
* Unlike _insertAtLeaf, this does NOT clone or update the node via MVCC.
|
|
1763
|
+
* Used by batchInsert to batch multiple insertions with a single clone/update.
|
|
1764
|
+
* @returns true if the leaf was modified, false if the key already exists.
|
|
1765
|
+
*/
|
|
1766
|
+
_insertValueIntoLeaf(leaf, key, value) {
|
|
1767
|
+
if (leaf.values.length) {
|
|
1768
|
+
for (let i = 0, len = leaf.values.length; i < len; i++) {
|
|
1769
|
+
const nValue = leaf.values[i];
|
|
1770
|
+
if (this.comparator.isSame(value, nValue)) {
|
|
1771
|
+
if (leaf.keys[i].includes(key)) {
|
|
1772
|
+
return false;
|
|
1773
|
+
}
|
|
1774
|
+
leaf.keys[i].push(key);
|
|
1775
|
+
return true;
|
|
1776
|
+
} else if (this.comparator.isLower(value, nValue)) {
|
|
1777
|
+
leaf.values.splice(i, 0, value);
|
|
1778
|
+
leaf.keys.splice(i, 0, [key]);
|
|
1779
|
+
return true;
|
|
1780
|
+
} else if (i + 1 === leaf.values.length) {
|
|
1781
|
+
leaf.values.push(value);
|
|
1782
|
+
leaf.keys.push([key]);
|
|
1783
|
+
return true;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
} else {
|
|
1787
|
+
leaf.values = [value];
|
|
1788
|
+
leaf.keys = [[key]];
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
return false;
|
|
1792
|
+
}
|
|
1760
1793
|
_cloneNode(node) {
|
|
1761
1794
|
return JSON.parse(JSON.stringify(node));
|
|
1762
1795
|
}
|
|
@@ -2213,17 +2246,24 @@ var require_cjs = __commonJS({
|
|
|
2213
2246
|
}
|
|
2214
2247
|
return void 0;
|
|
2215
2248
|
}
|
|
2216
|
-
*keysStream(condition,
|
|
2217
|
-
const
|
|
2249
|
+
*keysStream(condition, options) {
|
|
2250
|
+
const { filterValues, limit, order = "asc" } = options ?? {};
|
|
2251
|
+
const stream = this.whereStream(condition, options);
|
|
2218
2252
|
const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
|
|
2253
|
+
let count = 0;
|
|
2219
2254
|
for (const [key] of stream) {
|
|
2220
2255
|
if (intersection && !intersection.has(key)) {
|
|
2221
2256
|
continue;
|
|
2222
2257
|
}
|
|
2223
2258
|
yield key;
|
|
2259
|
+
count++;
|
|
2260
|
+
if (limit !== void 0 && count >= limit) {
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2224
2263
|
}
|
|
2225
2264
|
}
|
|
2226
|
-
*whereStream(condition,
|
|
2265
|
+
*whereStream(condition, options) {
|
|
2266
|
+
const { filterValues, limit, order = "asc" } = options ?? {};
|
|
2227
2267
|
const driverKey = this.getDriverKey(condition);
|
|
2228
2268
|
if (!driverKey) return;
|
|
2229
2269
|
const value = condition[driverKey];
|
|
@@ -2246,8 +2286,12 @@ var require_cjs = __commonJS({
|
|
|
2246
2286
|
earlyTerminate
|
|
2247
2287
|
);
|
|
2248
2288
|
let count = 0;
|
|
2289
|
+
const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
|
|
2249
2290
|
for (const pair of generator) {
|
|
2250
2291
|
const [k, v] = pair;
|
|
2292
|
+
if (intersection && !intersection.has(k)) {
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2251
2295
|
let isMatch = true;
|
|
2252
2296
|
for (const key in condition) {
|
|
2253
2297
|
if (key === driverKey) continue;
|
|
@@ -2267,16 +2311,16 @@ var require_cjs = __commonJS({
|
|
|
2267
2311
|
}
|
|
2268
2312
|
}
|
|
2269
2313
|
}
|
|
2270
|
-
keys(condition,
|
|
2314
|
+
keys(condition, options) {
|
|
2271
2315
|
const set = /* @__PURE__ */ new Set();
|
|
2272
|
-
for (const key of this.keysStream(condition,
|
|
2316
|
+
for (const key of this.keysStream(condition, options)) {
|
|
2273
2317
|
set.add(key);
|
|
2274
2318
|
}
|
|
2275
2319
|
return set;
|
|
2276
2320
|
}
|
|
2277
|
-
where(condition,
|
|
2321
|
+
where(condition, options) {
|
|
2278
2322
|
const map = /* @__PURE__ */ new Map();
|
|
2279
|
-
for (const [key, value] of this.whereStream(condition,
|
|
2323
|
+
for (const [key, value] of this.whereStream(condition, options)) {
|
|
2280
2324
|
map.set(key, value);
|
|
2281
2325
|
}
|
|
2282
2326
|
return map;
|
|
@@ -2304,6 +2348,50 @@ var require_cjs = __commonJS({
|
|
|
2304
2348
|
this._insertInParent(before, after.values[0], after);
|
|
2305
2349
|
}
|
|
2306
2350
|
}
|
|
2351
|
+
batchInsert(entries) {
|
|
2352
|
+
if (entries.length === 0) return;
|
|
2353
|
+
const sorted = [...entries].sort((a, b) => this.comparator.asc(a[1], b[1]));
|
|
2354
|
+
let currentLeaf = null;
|
|
2355
|
+
let modified = false;
|
|
2356
|
+
for (const [key, value] of sorted) {
|
|
2357
|
+
const targetLeaf = this.insertableNode(value);
|
|
2358
|
+
if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
|
|
2359
|
+
} else {
|
|
2360
|
+
if (currentLeaf !== null && modified) {
|
|
2361
|
+
this._updateNode(currentLeaf);
|
|
2362
|
+
}
|
|
2363
|
+
currentLeaf = this._cloneNode(targetLeaf);
|
|
2364
|
+
modified = false;
|
|
2365
|
+
}
|
|
2366
|
+
const changed = this._insertValueIntoLeaf(currentLeaf, key, value);
|
|
2367
|
+
modified = modified || changed;
|
|
2368
|
+
if (currentLeaf.values.length === this.order) {
|
|
2369
|
+
this._updateNode(currentLeaf);
|
|
2370
|
+
let after = this._createNode(
|
|
2371
|
+
true,
|
|
2372
|
+
[],
|
|
2373
|
+
[],
|
|
2374
|
+
currentLeaf.parent,
|
|
2375
|
+
null,
|
|
2376
|
+
null
|
|
2377
|
+
);
|
|
2378
|
+
const mid = Math.ceil(this.order / 2) - 1;
|
|
2379
|
+
after = this._cloneNode(after);
|
|
2380
|
+
after.values = currentLeaf.values.slice(mid + 1);
|
|
2381
|
+
after.keys = currentLeaf.keys.slice(mid + 1);
|
|
2382
|
+
currentLeaf.values = currentLeaf.values.slice(0, mid + 1);
|
|
2383
|
+
currentLeaf.keys = currentLeaf.keys.slice(0, mid + 1);
|
|
2384
|
+
this._updateNode(currentLeaf);
|
|
2385
|
+
this._updateNode(after);
|
|
2386
|
+
this._insertInParent(currentLeaf, after.values[0], after);
|
|
2387
|
+
currentLeaf = null;
|
|
2388
|
+
modified = false;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (currentLeaf !== null && modified) {
|
|
2392
|
+
this._updateNode(currentLeaf);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2307
2395
|
_deleteEntry(node, key) {
|
|
2308
2396
|
if (!node.leaf) {
|
|
2309
2397
|
let keyIndex = -1;
|
|
@@ -2655,6 +2743,14 @@ var require_cjs = __commonJS({
|
|
|
2655
2743
|
throw new Error(`Transaction failed: ${result.error || "Commit failed due to conflict"}`);
|
|
2656
2744
|
}
|
|
2657
2745
|
}
|
|
2746
|
+
batchInsert(entries) {
|
|
2747
|
+
const tx = this.createTransaction();
|
|
2748
|
+
tx.batchInsert(entries);
|
|
2749
|
+
const result = tx.commit();
|
|
2750
|
+
if (!result.success) {
|
|
2751
|
+
throw new Error(`Transaction failed: ${result.error || "Commit failed due to conflict"}`);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2658
2754
|
};
|
|
2659
2755
|
var Ryoiki22 = class _Ryoiki2 {
|
|
2660
2756
|
readings;
|
|
@@ -3280,17 +3376,24 @@ var require_cjs = __commonJS({
|
|
|
3280
3376
|
}
|
|
3281
3377
|
return void 0;
|
|
3282
3378
|
}
|
|
3283
|
-
async *keysStream(condition,
|
|
3284
|
-
const
|
|
3379
|
+
async *keysStream(condition, options) {
|
|
3380
|
+
const { filterValues, limit, order = "asc" } = options ?? {};
|
|
3381
|
+
const stream = this.whereStream(condition, options);
|
|
3285
3382
|
const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
|
|
3383
|
+
let count = 0;
|
|
3286
3384
|
for await (const [key] of stream) {
|
|
3287
3385
|
if (intersection && !intersection.has(key)) {
|
|
3288
3386
|
continue;
|
|
3289
3387
|
}
|
|
3290
3388
|
yield key;
|
|
3389
|
+
count++;
|
|
3390
|
+
if (limit !== void 0 && count >= limit) {
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
3291
3393
|
}
|
|
3292
3394
|
}
|
|
3293
|
-
async *whereStream(condition,
|
|
3395
|
+
async *whereStream(condition, options) {
|
|
3396
|
+
const { filterValues, limit, order = "asc" } = options ?? {};
|
|
3294
3397
|
const driverKey = this.getDriverKey(condition);
|
|
3295
3398
|
if (!driverKey) return;
|
|
3296
3399
|
const value = condition[driverKey];
|
|
@@ -3313,8 +3416,12 @@ var require_cjs = __commonJS({
|
|
|
3313
3416
|
earlyTerminate
|
|
3314
3417
|
);
|
|
3315
3418
|
let count = 0;
|
|
3419
|
+
const intersection = filterValues && filterValues.size > 0 ? filterValues : null;
|
|
3316
3420
|
for await (const pair of generator) {
|
|
3317
3421
|
const [k, v] = pair;
|
|
3422
|
+
if (intersection && !intersection.has(k)) {
|
|
3423
|
+
continue;
|
|
3424
|
+
}
|
|
3318
3425
|
let isMatch = true;
|
|
3319
3426
|
for (const key in condition) {
|
|
3320
3427
|
if (key === driverKey) continue;
|
|
@@ -3334,16 +3441,16 @@ var require_cjs = __commonJS({
|
|
|
3334
3441
|
}
|
|
3335
3442
|
}
|
|
3336
3443
|
}
|
|
3337
|
-
async keys(condition,
|
|
3444
|
+
async keys(condition, options) {
|
|
3338
3445
|
const set = /* @__PURE__ */ new Set();
|
|
3339
|
-
for await (const key of this.keysStream(condition,
|
|
3446
|
+
for await (const key of this.keysStream(condition, options)) {
|
|
3340
3447
|
set.add(key);
|
|
3341
3448
|
}
|
|
3342
3449
|
return set;
|
|
3343
3450
|
}
|
|
3344
|
-
async where(condition,
|
|
3451
|
+
async where(condition, options) {
|
|
3345
3452
|
const map = /* @__PURE__ */ new Map();
|
|
3346
|
-
for await (const [key, value] of this.whereStream(condition,
|
|
3453
|
+
for await (const [key, value] of this.whereStream(condition, options)) {
|
|
3347
3454
|
map.set(key, value);
|
|
3348
3455
|
}
|
|
3349
3456
|
return map;
|
|
@@ -3373,6 +3480,52 @@ var require_cjs = __commonJS({
|
|
|
3373
3480
|
}
|
|
3374
3481
|
});
|
|
3375
3482
|
}
|
|
3483
|
+
async batchInsert(entries) {
|
|
3484
|
+
if (entries.length === 0) return;
|
|
3485
|
+
return this.writeLock(0, async () => {
|
|
3486
|
+
const sorted = [...entries].sort((a, b) => this.comparator.asc(a[1], b[1]));
|
|
3487
|
+
let currentLeaf = null;
|
|
3488
|
+
let modified = false;
|
|
3489
|
+
for (const [key, value] of sorted) {
|
|
3490
|
+
const targetLeaf = await this.insertableNode(value);
|
|
3491
|
+
if (currentLeaf !== null && currentLeaf.id === targetLeaf.id) {
|
|
3492
|
+
} else {
|
|
3493
|
+
if (currentLeaf !== null && modified) {
|
|
3494
|
+
await this._updateNode(currentLeaf);
|
|
3495
|
+
}
|
|
3496
|
+
currentLeaf = this._cloneNode(targetLeaf);
|
|
3497
|
+
modified = false;
|
|
3498
|
+
}
|
|
3499
|
+
const changed = this._insertValueIntoLeaf(currentLeaf, key, value);
|
|
3500
|
+
modified = modified || changed;
|
|
3501
|
+
if (currentLeaf.values.length === this.order) {
|
|
3502
|
+
await this._updateNode(currentLeaf);
|
|
3503
|
+
let after = await this._createNode(
|
|
3504
|
+
true,
|
|
3505
|
+
[],
|
|
3506
|
+
[],
|
|
3507
|
+
currentLeaf.parent,
|
|
3508
|
+
null,
|
|
3509
|
+
null
|
|
3510
|
+
);
|
|
3511
|
+
const mid = Math.ceil(this.order / 2) - 1;
|
|
3512
|
+
after = this._cloneNode(after);
|
|
3513
|
+
after.values = currentLeaf.values.slice(mid + 1);
|
|
3514
|
+
after.keys = currentLeaf.keys.slice(mid + 1);
|
|
3515
|
+
currentLeaf.values = currentLeaf.values.slice(0, mid + 1);
|
|
3516
|
+
currentLeaf.keys = currentLeaf.keys.slice(0, mid + 1);
|
|
3517
|
+
await this._updateNode(currentLeaf);
|
|
3518
|
+
await this._updateNode(after);
|
|
3519
|
+
await this._insertInParent(currentLeaf, after.values[0], after);
|
|
3520
|
+
currentLeaf = null;
|
|
3521
|
+
modified = false;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
if (currentLeaf !== null && modified) {
|
|
3525
|
+
await this._updateNode(currentLeaf);
|
|
3526
|
+
}
|
|
3527
|
+
});
|
|
3528
|
+
}
|
|
3376
3529
|
async _deleteEntry(node, key) {
|
|
3377
3530
|
if (!node.leaf) {
|
|
3378
3531
|
let keyIndex = -1;
|
|
@@ -3730,6 +3883,16 @@ var require_cjs = __commonJS({
|
|
|
3730
3883
|
}
|
|
3731
3884
|
});
|
|
3732
3885
|
}
|
|
3886
|
+
async batchInsert(entries) {
|
|
3887
|
+
return this.writeLock(1, async () => {
|
|
3888
|
+
const tx = await this.createTransaction();
|
|
3889
|
+
await tx.batchInsert(entries);
|
|
3890
|
+
const result = await tx.commit();
|
|
3891
|
+
if (!result.success) {
|
|
3892
|
+
throw new Error(`Transaction failed: ${result.error || "Commit failed due to conflict"}`);
|
|
3893
|
+
}
|
|
3894
|
+
});
|
|
3895
|
+
}
|
|
3733
3896
|
};
|
|
3734
3897
|
var SerializeStrategy = class {
|
|
3735
3898
|
order;
|
|
@@ -8704,6 +8867,7 @@ var require_cjs = __commonJS({
|
|
|
8704
8867
|
if (!this.factory.isDataPage(lastInsertDataPage)) {
|
|
8705
8868
|
throw new Error(`Last insert page is not data page`);
|
|
8706
8869
|
}
|
|
8870
|
+
const batchInsertData = [];
|
|
8707
8871
|
for (const data of dataList) {
|
|
8708
8872
|
const pk = ++lastPk;
|
|
8709
8873
|
const willRowSize = this.getRequiredRowSize(data);
|
|
@@ -8748,9 +8912,10 @@ var require_cjs = __commonJS({
|
|
|
8748
8912
|
await this.pfs.setPage(lastInsertDataPageId, lastInsertDataPage, tx);
|
|
8749
8913
|
}
|
|
8750
8914
|
}
|
|
8751
|
-
|
|
8915
|
+
batchInsertData.push([this.getRID(), pk]);
|
|
8752
8916
|
pks.push(pk);
|
|
8753
8917
|
}
|
|
8918
|
+
await btx.batchInsert(batchInsertData);
|
|
8754
8919
|
tx.__markBPTreeDirty();
|
|
8755
8920
|
const freshMetadataPage = await this.pfs.getMetadata(tx);
|
|
8756
8921
|
this.metadataPageManager.setLastInsertPageId(freshMetadataPage, lastInsertDataPageId);
|
|
@@ -9953,6 +10118,45 @@ var BinaryHeap = class {
|
|
|
9953
10118
|
}
|
|
9954
10119
|
};
|
|
9955
10120
|
|
|
10121
|
+
// src/utils/tokenizer.ts
|
|
10122
|
+
function whitespaceTokenize(text) {
|
|
10123
|
+
if (typeof text !== "string") return [];
|
|
10124
|
+
return Array.from(new Set(text.split(/\s+/).filter(Boolean)));
|
|
10125
|
+
}
|
|
10126
|
+
function ngramTokenize(text, gramSize) {
|
|
10127
|
+
if (typeof text !== "string") return [];
|
|
10128
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
10129
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
10130
|
+
for (const word of words) {
|
|
10131
|
+
if (word.length < gramSize) {
|
|
10132
|
+
if (word.length > 0) tokens.add(word);
|
|
10133
|
+
continue;
|
|
10134
|
+
}
|
|
10135
|
+
for (let i = 0; i <= word.length - gramSize; i++) {
|
|
10136
|
+
tokens.add(word.slice(i, i + gramSize));
|
|
10137
|
+
}
|
|
10138
|
+
}
|
|
10139
|
+
return Array.from(tokens);
|
|
10140
|
+
}
|
|
10141
|
+
function tokenize(text, options) {
|
|
10142
|
+
if (options.tokenizer === "whitespace") {
|
|
10143
|
+
return whitespaceTokenize(text);
|
|
10144
|
+
}
|
|
10145
|
+
if (options.tokenizer === "ngram") {
|
|
10146
|
+
return ngramTokenize(text, options.gramSize);
|
|
10147
|
+
}
|
|
10148
|
+
return [];
|
|
10149
|
+
}
|
|
10150
|
+
|
|
10151
|
+
// src/utils/hash.ts
|
|
10152
|
+
function fastStringHash(str) {
|
|
10153
|
+
let hash = 0;
|
|
10154
|
+
for (let i = 0; i < str.length; i++) {
|
|
10155
|
+
hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
|
|
10156
|
+
}
|
|
10157
|
+
return hash >>> 0;
|
|
10158
|
+
}
|
|
10159
|
+
|
|
9956
10160
|
// src/core/documentAPI.ts
|
|
9957
10161
|
var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
9958
10162
|
indices = {};
|
|
@@ -10008,7 +10212,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10008
10212
|
} else {
|
|
10009
10213
|
const [_pk, isMetaBackfillEnabled] = existingIndex;
|
|
10010
10214
|
if (isBackfillEnabled && !isMetaBackfillEnabled) {
|
|
10011
|
-
metadata.indices[field][1] =
|
|
10215
|
+
metadata.indices[field][1] = isBackfillEnabled;
|
|
10012
10216
|
isMetadataChanged = true;
|
|
10013
10217
|
backfillTargets.push(field);
|
|
10014
10218
|
} else if (!isBackfillEnabled && isMetaBackfillEnabled) {
|
|
@@ -10111,12 +10315,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10111
10315
|
}
|
|
10112
10316
|
const v = flatDoc[field];
|
|
10113
10317
|
const btx = fieldTxMap[field];
|
|
10114
|
-
const
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10318
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10319
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10320
|
+
let tokens = [v];
|
|
10321
|
+
if (isFts) {
|
|
10322
|
+
tokens = tokenize(v, indexConfig);
|
|
10323
|
+
}
|
|
10324
|
+
const batchInsertData = [];
|
|
10325
|
+
for (const token of tokens) {
|
|
10326
|
+
const keyToInsert = isFts ? this.getTokenKey(k, token) : k;
|
|
10327
|
+
const entry = { k, v: token };
|
|
10328
|
+
batchInsertData.push([keyToInsert, entry]);
|
|
10329
|
+
if (!fieldMap.has(btx)) {
|
|
10330
|
+
fieldMap.set(btx, []);
|
|
10331
|
+
}
|
|
10332
|
+
fieldMap.get(btx).push({ k: keyToInsert, v: entry });
|
|
10118
10333
|
}
|
|
10119
|
-
|
|
10334
|
+
await btx.batchInsert(batchInsertData);
|
|
10120
10335
|
}
|
|
10121
10336
|
backfilledCount++;
|
|
10122
10337
|
}
|
|
@@ -10210,6 +10425,11 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10210
10425
|
async updateDocumentInnerMetadata(metadata, tx) {
|
|
10211
10426
|
await this.update(1, JSON.stringify(metadata), tx);
|
|
10212
10427
|
}
|
|
10428
|
+
/**
|
|
10429
|
+
* Transforms a query object into a verbose query object
|
|
10430
|
+
* @param query The query object to transform
|
|
10431
|
+
* @returns The verbose query object
|
|
10432
|
+
*/
|
|
10213
10433
|
verboseQuery(query) {
|
|
10214
10434
|
const result = {};
|
|
10215
10435
|
for (const field in query) {
|
|
@@ -10223,7 +10443,12 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10223
10443
|
const before = operator;
|
|
10224
10444
|
const after = this.operatorConverters[before];
|
|
10225
10445
|
const v = conditions[before];
|
|
10226
|
-
if (!after)
|
|
10446
|
+
if (!after) {
|
|
10447
|
+
if (before === "match") {
|
|
10448
|
+
newConditions[before] = v;
|
|
10449
|
+
}
|
|
10450
|
+
continue;
|
|
10451
|
+
}
|
|
10227
10452
|
if (before === "or" && Array.isArray(v)) {
|
|
10228
10453
|
newConditions[after] = v.map((val) => ({ v: val }));
|
|
10229
10454
|
} else if (before === "like") {
|
|
@@ -10245,12 +10470,26 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10245
10470
|
*/
|
|
10246
10471
|
async getSelectivityCandidate(query, orderByField) {
|
|
10247
10472
|
const candidates = [];
|
|
10473
|
+
const metadata = await this.getDocumentInnerMetadata(this.txContext.get());
|
|
10248
10474
|
for (const field in query) {
|
|
10249
10475
|
const tree = this.trees.get(field);
|
|
10250
10476
|
if (!tree) continue;
|
|
10251
10477
|
const condition = query[field];
|
|
10252
10478
|
const treeTx = await tree.createTransaction();
|
|
10253
|
-
|
|
10479
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10480
|
+
let isFtsMatch = false;
|
|
10481
|
+
let matchTokens;
|
|
10482
|
+
if (typeof indexConfig === "object" && indexConfig?.type === "fts" && condition.match) {
|
|
10483
|
+
isFtsMatch = true;
|
|
10484
|
+
matchTokens = tokenize(condition.match, indexConfig);
|
|
10485
|
+
}
|
|
10486
|
+
candidates.push({
|
|
10487
|
+
tree: treeTx,
|
|
10488
|
+
condition,
|
|
10489
|
+
field,
|
|
10490
|
+
isFtsMatch,
|
|
10491
|
+
matchTokens
|
|
10492
|
+
});
|
|
10254
10493
|
}
|
|
10255
10494
|
const rollback = () => {
|
|
10256
10495
|
for (const { tree } of candidates) {
|
|
@@ -10293,36 +10532,72 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10293
10532
|
const smallChunkSize = safeLimit * 0.3;
|
|
10294
10533
|
return { verySmallChunkSize, smallChunkSize };
|
|
10295
10534
|
}
|
|
10535
|
+
getTokenKey(pk, token) {
|
|
10536
|
+
return fastStringHash(pk + ":" + token);
|
|
10537
|
+
}
|
|
10538
|
+
async applyCandidateByFTS(candidate, matchedTokens, filterValues, order) {
|
|
10539
|
+
const keys = /* @__PURE__ */ new Set();
|
|
10540
|
+
for (const token of matchedTokens) {
|
|
10541
|
+
const pairs = await candidate.tree.where(
|
|
10542
|
+
{ primaryEqual: { v: token } },
|
|
10543
|
+
{
|
|
10544
|
+
filterValues,
|
|
10545
|
+
order
|
|
10546
|
+
}
|
|
10547
|
+
);
|
|
10548
|
+
for (const c of pairs.values()) {
|
|
10549
|
+
if (!c || typeof c.k !== "number") continue;
|
|
10550
|
+
const dpk = c.k;
|
|
10551
|
+
if (filterValues === void 0 || filterValues.has(dpk)) {
|
|
10552
|
+
keys.add(dpk);
|
|
10553
|
+
}
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
return keys;
|
|
10557
|
+
}
|
|
10558
|
+
/**
|
|
10559
|
+
* 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
|
|
10560
|
+
*/
|
|
10561
|
+
async applyCandidate(candidate, filterValues, order) {
|
|
10562
|
+
return await candidate.tree.keys(
|
|
10563
|
+
candidate.condition,
|
|
10564
|
+
{
|
|
10565
|
+
filterValues,
|
|
10566
|
+
order
|
|
10567
|
+
}
|
|
10568
|
+
);
|
|
10569
|
+
}
|
|
10296
10570
|
/**
|
|
10297
|
-
*
|
|
10298
|
-
*
|
|
10571
|
+
* 쿼리와 인덱스 선택을 기반으로 기본 키(Primary Keys)를 가져옵니다.
|
|
10572
|
+
* 쿼리 최적화를 통합하기 위한 내부 공통 메서드입니다.
|
|
10299
10573
|
*/
|
|
10300
10574
|
async getKeys(query, orderBy, sortOrder = "asc") {
|
|
10301
10575
|
const isQueryEmpty = Object.keys(query).length === 0;
|
|
10302
10576
|
const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
|
|
10303
|
-
const verbose = this.verboseQuery(normalizedQuery);
|
|
10304
10577
|
const selectivity = await this.getSelectivityCandidate(
|
|
10305
|
-
|
|
10578
|
+
this.verboseQuery(normalizedQuery),
|
|
10306
10579
|
orderBy
|
|
10307
10580
|
);
|
|
10308
10581
|
if (!selectivity) return new Float64Array(0);
|
|
10309
10582
|
const { driver, others, rollback } = selectivity;
|
|
10310
|
-
const
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10583
|
+
const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
|
|
10584
|
+
const candidates = [driver, ...others];
|
|
10585
|
+
let keys = void 0;
|
|
10586
|
+
for (const candidate of candidates) {
|
|
10587
|
+
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10588
|
+
if (candidate.isFtsMatch && candidate.matchTokens && candidate.matchTokens.length > 0) {
|
|
10589
|
+
keys = await this.applyCandidateByFTS(
|
|
10590
|
+
candidate,
|
|
10591
|
+
candidate.matchTokens,
|
|
10592
|
+
keys,
|
|
10593
|
+
currentOrder
|
|
10594
|
+
);
|
|
10595
|
+
} else {
|
|
10596
|
+
keys = await this.applyCandidate(candidate, keys, currentOrder);
|
|
10322
10597
|
}
|
|
10323
|
-
rollback();
|
|
10324
|
-
return new Float64Array(keysSet);
|
|
10325
10598
|
}
|
|
10599
|
+
rollback();
|
|
10600
|
+
return new Float64Array(Array.from(keys || []));
|
|
10326
10601
|
}
|
|
10327
10602
|
async insertDocumentInternal(document, tx) {
|
|
10328
10603
|
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
@@ -10346,15 +10621,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10346
10621
|
*/
|
|
10347
10622
|
async insertSingleDocument(document, tx) {
|
|
10348
10623
|
return this.writeLock(() => this.runWithDefault(async (tx2) => {
|
|
10349
|
-
const { pk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10624
|
+
const { pk: dpk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10625
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10350
10626
|
const flattenDocument = this.flattenDocument(dataplyDocument);
|
|
10351
10627
|
for (const field in flattenDocument) {
|
|
10352
10628
|
const tree = this.trees.get(field);
|
|
10353
10629
|
if (!tree) continue;
|
|
10354
10630
|
const v = flattenDocument[field];
|
|
10355
|
-
const
|
|
10356
|
-
|
|
10357
|
-
|
|
10631
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10632
|
+
let tokens = [v];
|
|
10633
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10634
|
+
if (isFts) {
|
|
10635
|
+
tokens = tokenize(v, indexConfig);
|
|
10636
|
+
}
|
|
10637
|
+
for (const token of tokens) {
|
|
10638
|
+
const keyToInsert = isFts ? this.getTokenKey(dpk, token) : dpk;
|
|
10639
|
+
const [error] = await catchPromise(tree.insert(keyToInsert, { k: dpk, v: token }));
|
|
10640
|
+
if (error) {
|
|
10641
|
+
throw error;
|
|
10642
|
+
}
|
|
10358
10643
|
}
|
|
10359
10644
|
}
|
|
10360
10645
|
return dataplyDocument._id;
|
|
@@ -10392,15 +10677,26 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10392
10677
|
}
|
|
10393
10678
|
for (const [field, tree] of this.trees) {
|
|
10394
10679
|
const treeTx = await tree.createTransaction();
|
|
10680
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10681
|
+
const batchInsertData = [];
|
|
10395
10682
|
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
10396
10683
|
const item = flattenedData[i];
|
|
10397
10684
|
const v = item.data[field];
|
|
10398
10685
|
if (v === void 0) continue;
|
|
10399
|
-
const
|
|
10400
|
-
|
|
10401
|
-
|
|
10686
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10687
|
+
let tokens = [v];
|
|
10688
|
+
if (isFts) {
|
|
10689
|
+
tokens = tokenize(v, indexConfig);
|
|
10690
|
+
}
|
|
10691
|
+
for (const token of tokens) {
|
|
10692
|
+
const keyToInsert = isFts ? this.getTokenKey(item.pk, token) : item.pk;
|
|
10693
|
+
batchInsertData.push([keyToInsert, { k: item.pk, v: token }]);
|
|
10402
10694
|
}
|
|
10403
10695
|
}
|
|
10696
|
+
const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
|
|
10697
|
+
if (error) {
|
|
10698
|
+
throw error;
|
|
10699
|
+
}
|
|
10404
10700
|
const res = await treeTx.commit();
|
|
10405
10701
|
if (!res.success) {
|
|
10406
10702
|
throw res.error;
|
|
@@ -10431,15 +10727,34 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10431
10727
|
const updatedDoc = computeUpdatedDoc(doc);
|
|
10432
10728
|
const oldFlatDoc = this.flattenDocument(doc);
|
|
10433
10729
|
const newFlatDoc = this.flattenDocument(updatedDoc);
|
|
10730
|
+
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
10434
10731
|
for (const [field, treeTx] of treeTxs) {
|
|
10435
10732
|
const oldV = oldFlatDoc[field];
|
|
10436
10733
|
const newV = newFlatDoc[field];
|
|
10437
10734
|
if (oldV === newV) continue;
|
|
10735
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10736
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts";
|
|
10438
10737
|
if (field in oldFlatDoc) {
|
|
10439
|
-
|
|
10738
|
+
let oldTokens = [oldV];
|
|
10739
|
+
if (isFts && typeof oldV === "string") {
|
|
10740
|
+
oldTokens = tokenize(oldV, indexConfig);
|
|
10741
|
+
}
|
|
10742
|
+
for (const oldToken of oldTokens) {
|
|
10743
|
+
const keyToDelete = isFts ? this.getTokenKey(pk, oldToken) : pk;
|
|
10744
|
+
await treeTx.delete(keyToDelete, { k: pk, v: oldToken });
|
|
10745
|
+
}
|
|
10440
10746
|
}
|
|
10441
10747
|
if (field in newFlatDoc) {
|
|
10442
|
-
|
|
10748
|
+
let newTokens = [newV];
|
|
10749
|
+
if (isFts && typeof newV === "string") {
|
|
10750
|
+
newTokens = tokenize(newV, indexConfig);
|
|
10751
|
+
}
|
|
10752
|
+
const batchInsertData = [];
|
|
10753
|
+
for (const newToken of newTokens) {
|
|
10754
|
+
const keyToInsert = isFts ? this.getTokenKey(pk, newToken) : pk;
|
|
10755
|
+
batchInsertData.push([keyToInsert, { k: pk, v: newToken }]);
|
|
10756
|
+
}
|
|
10757
|
+
await treeTx.batchInsert(batchInsertData);
|
|
10443
10758
|
}
|
|
10444
10759
|
}
|
|
10445
10760
|
await this.update(pk, JSON.stringify(updatedDoc), tx);
|
|
@@ -10503,10 +10818,20 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10503
10818
|
const doc = await this.getDocument(pk, tx2);
|
|
10504
10819
|
if (!doc) continue;
|
|
10505
10820
|
const flatDoc = this.flattenDocument(doc);
|
|
10821
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10506
10822
|
for (const [field, tree] of this.trees) {
|
|
10507
10823
|
const v = flatDoc[field];
|
|
10508
10824
|
if (v === void 0) continue;
|
|
10509
|
-
|
|
10825
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10826
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10827
|
+
let tokens = [v];
|
|
10828
|
+
if (isFts) {
|
|
10829
|
+
tokens = tokenize(v, indexConfig);
|
|
10830
|
+
}
|
|
10831
|
+
for (const token of tokens) {
|
|
10832
|
+
const keyToDelete = isFts ? this.getTokenKey(pk, token) : pk;
|
|
10833
|
+
await tree.delete(keyToDelete, { k: pk, v: token });
|
|
10834
|
+
}
|
|
10510
10835
|
}
|
|
10511
10836
|
await super.delete(pk, true, tx2);
|
|
10512
10837
|
deletedCount++;
|
|
@@ -10526,6 +10851,63 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10526
10851
|
return pks.length;
|
|
10527
10852
|
}, tx));
|
|
10528
10853
|
}
|
|
10854
|
+
/**
|
|
10855
|
+
* FTS 조건에 대해 문서가 유효한지 검증합니다.
|
|
10856
|
+
*/
|
|
10857
|
+
verifyFts(doc, ftsConditions) {
|
|
10858
|
+
for (const { field, matchTokens } of ftsConditions) {
|
|
10859
|
+
const docValue = this.flattenDocument(doc)[field];
|
|
10860
|
+
if (typeof docValue !== "string") return false;
|
|
10861
|
+
for (const token of matchTokens) {
|
|
10862
|
+
if (!docValue.includes(token)) return false;
|
|
10863
|
+
}
|
|
10864
|
+
}
|
|
10865
|
+
return true;
|
|
10866
|
+
}
|
|
10867
|
+
/**
|
|
10868
|
+
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
10869
|
+
*/
|
|
10870
|
+
adjustChunkSize(currentChunkSize, chunkTotalSize) {
|
|
10871
|
+
if (chunkTotalSize <= 0) return currentChunkSize;
|
|
10872
|
+
const { verySmallChunkSize, smallChunkSize } = this.getFreeMemoryChunkSize();
|
|
10873
|
+
if (chunkTotalSize < verySmallChunkSize) return currentChunkSize * 2;
|
|
10874
|
+
if (chunkTotalSize > smallChunkSize) return Math.max(Math.floor(currentChunkSize / 2), 20);
|
|
10875
|
+
return currentChunkSize;
|
|
10876
|
+
}
|
|
10877
|
+
/**
|
|
10878
|
+
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
10879
|
+
* FTS 검증을 통과한 문서만 yield 합니다.
|
|
10880
|
+
*/
|
|
10881
|
+
async *processChunkedKeys(keys, startIdx, initialChunkSize, ftsConditions, tx) {
|
|
10882
|
+
let i = startIdx;
|
|
10883
|
+
const totalKeys = keys.length;
|
|
10884
|
+
let currentChunkSize = initialChunkSize;
|
|
10885
|
+
let nextChunkPromise = null;
|
|
10886
|
+
if (i < totalKeys) {
|
|
10887
|
+
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
10888
|
+
nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
|
|
10889
|
+
i = endIdx;
|
|
10890
|
+
}
|
|
10891
|
+
while (nextChunkPromise) {
|
|
10892
|
+
const rawResults = await nextChunkPromise;
|
|
10893
|
+
nextChunkPromise = null;
|
|
10894
|
+
if (i < totalKeys) {
|
|
10895
|
+
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
10896
|
+
nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
|
|
10897
|
+
i = endIdx;
|
|
10898
|
+
}
|
|
10899
|
+
let chunkTotalSize = 0;
|
|
10900
|
+
for (let j = 0, len = rawResults.length; j < len; j++) {
|
|
10901
|
+
const s = rawResults[j];
|
|
10902
|
+
if (!s) continue;
|
|
10903
|
+
const doc = JSON.parse(s);
|
|
10904
|
+
chunkTotalSize += s.length * 2;
|
|
10905
|
+
if (!this.verifyFts(doc, ftsConditions)) continue;
|
|
10906
|
+
yield { doc, rawSize: s.length * 2 };
|
|
10907
|
+
}
|
|
10908
|
+
currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
|
|
10909
|
+
}
|
|
10910
|
+
}
|
|
10529
10911
|
/**
|
|
10530
10912
|
* Select documents from the database
|
|
10531
10913
|
* @param query The query to use (only indexed fields + _id allowed)
|
|
@@ -10552,80 +10934,57 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10552
10934
|
} = options;
|
|
10553
10935
|
const self = this;
|
|
10554
10936
|
const stream = this.streamWithDefault(async function* (tx2) {
|
|
10937
|
+
const metadata = await self.getDocumentInnerMetadata(tx2);
|
|
10938
|
+
const ftsConditions = [];
|
|
10939
|
+
for (const field in query) {
|
|
10940
|
+
const q = query[field];
|
|
10941
|
+
if (q && typeof q === "object" && "match" in q && typeof q.match === "string") {
|
|
10942
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10943
|
+
if (typeof indexConfig === "object" && indexConfig?.type === "fts") {
|
|
10944
|
+
ftsConditions.push({ field, matchTokens: tokenize(q.match, indexConfig) });
|
|
10945
|
+
}
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10555
10948
|
const keys = await self.getKeys(query, orderByField, sortOrder);
|
|
10556
|
-
|
|
10557
|
-
if (totalKeys === 0) return;
|
|
10949
|
+
if (keys.length === 0) return;
|
|
10558
10950
|
const selectivity = await self.getSelectivityCandidate(
|
|
10559
10951
|
self.verboseQuery(query),
|
|
10560
10952
|
orderByField
|
|
10561
10953
|
);
|
|
10562
10954
|
const isDriverOrderByField = orderByField === void 0 || selectivity && selectivity.driver.field === orderByField;
|
|
10563
|
-
if (selectivity)
|
|
10564
|
-
selectivity.rollback();
|
|
10565
|
-
}
|
|
10566
|
-
let currentChunkSize = self.options.pageSize;
|
|
10955
|
+
if (selectivity) selectivity.rollback();
|
|
10567
10956
|
if (!isDriverOrderByField && orderByField) {
|
|
10568
10957
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
10569
10958
|
let heap = null;
|
|
10570
10959
|
if (topK !== Infinity) {
|
|
10571
|
-
|
|
10960
|
+
heap = new BinaryHeap((a, b) => {
|
|
10572
10961
|
const aVal = a[orderByField] ?? a._id;
|
|
10573
10962
|
const bVal = b[orderByField] ?? b._id;
|
|
10574
10963
|
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10575
10964
|
return sortOrder === "asc" ? -cmp : cmp;
|
|
10576
|
-
};
|
|
10577
|
-
heap = new BinaryHeap(heapComparator);
|
|
10965
|
+
});
|
|
10578
10966
|
}
|
|
10579
10967
|
const results = [];
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
i = endIdx;
|
|
10597
|
-
}
|
|
10598
|
-
let chunkTotalSize = 0;
|
|
10599
|
-
for (let j = 0, len = rawResults.length; j < len; j++) {
|
|
10600
|
-
const s = rawResults[j];
|
|
10601
|
-
if (s) {
|
|
10602
|
-
const doc = JSON.parse(s);
|
|
10603
|
-
chunkTotalSize += s.length * 2;
|
|
10604
|
-
if (heap) {
|
|
10605
|
-
if (heap.size < topK) heap.push(doc);
|
|
10606
|
-
else {
|
|
10607
|
-
const top = heap.peek();
|
|
10608
|
-
if (top) {
|
|
10609
|
-
const aVal = doc[orderByField] ?? doc._id;
|
|
10610
|
-
const bVal = top[orderByField] ?? top._id;
|
|
10611
|
-
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10612
|
-
const isBetter = sortOrder === "asc" ? cmp < 0 : cmp > 0;
|
|
10613
|
-
if (isBetter) {
|
|
10614
|
-
heap.replace(doc);
|
|
10615
|
-
}
|
|
10616
|
-
}
|
|
10617
|
-
}
|
|
10618
|
-
} else {
|
|
10619
|
-
results.push(doc);
|
|
10968
|
+
for await (const { doc } of self.processChunkedKeys(
|
|
10969
|
+
keys,
|
|
10970
|
+
0,
|
|
10971
|
+
self.options.pageSize,
|
|
10972
|
+
ftsConditions,
|
|
10973
|
+
tx2
|
|
10974
|
+
)) {
|
|
10975
|
+
if (heap) {
|
|
10976
|
+
if (heap.size < topK) heap.push(doc);
|
|
10977
|
+
else {
|
|
10978
|
+
const top = heap.peek();
|
|
10979
|
+
if (top) {
|
|
10980
|
+
const aVal = doc[orderByField] ?? doc._id;
|
|
10981
|
+
const bVal = top[orderByField] ?? top._id;
|
|
10982
|
+
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10983
|
+
if (sortOrder === "asc" ? cmp < 0 : cmp > 0) heap.replace(doc);
|
|
10620
10984
|
}
|
|
10621
10985
|
}
|
|
10622
|
-
}
|
|
10623
|
-
|
|
10624
|
-
if (chunkTotalSize < verySmallChunkSize) {
|
|
10625
|
-
currentChunkSize = currentChunkSize * 2;
|
|
10626
|
-
} else if (chunkTotalSize > smallChunkSize) {
|
|
10627
|
-
currentChunkSize = Math.max(Math.floor(currentChunkSize / 2), 20);
|
|
10628
|
-
}
|
|
10986
|
+
} else {
|
|
10987
|
+
results.push(doc);
|
|
10629
10988
|
}
|
|
10630
10989
|
}
|
|
10631
10990
|
const finalDocs = heap ? heap.toArray() : results;
|
|
@@ -10635,50 +10994,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10635
10994
|
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10636
10995
|
return sortOrder === "asc" ? cmp : -cmp;
|
|
10637
10996
|
});
|
|
10638
|
-
const
|
|
10639
|
-
const
|
|
10640
|
-
const limitedResults = finalDocs.slice(start, end);
|
|
10997
|
+
const end = limit === Infinity ? void 0 : offset + limit;
|
|
10998
|
+
const limitedResults = finalDocs.slice(offset, end);
|
|
10641
10999
|
for (let j = 0, len = limitedResults.length; j < len; j++) {
|
|
10642
11000
|
yield limitedResults[j];
|
|
10643
11001
|
}
|
|
10644
11002
|
} else {
|
|
10645
11003
|
let yieldedCount = 0;
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
nextChunkPromise = null;
|
|
10657
|
-
const { verySmallChunkSize, smallChunkSize } = self.getFreeMemoryChunkSize();
|
|
10658
|
-
if (yieldedCount < limit && i < totalKeys) {
|
|
10659
|
-
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
10660
|
-
const chunkKeys = keys.subarray(i, endIdx);
|
|
10661
|
-
nextChunkPromise = self.selectMany(chunkKeys, false, tx2);
|
|
10662
|
-
i = endIdx;
|
|
10663
|
-
}
|
|
10664
|
-
let chunkTotalSize = 0;
|
|
10665
|
-
for (let j = 0, len = rawResults.length; j < len; j++) {
|
|
10666
|
-
const s = rawResults[j];
|
|
10667
|
-
if (s) {
|
|
10668
|
-
if (yieldedCount < limit) {
|
|
10669
|
-
yield JSON.parse(s);
|
|
10670
|
-
yieldedCount++;
|
|
10671
|
-
}
|
|
10672
|
-
chunkTotalSize += s.length * 2;
|
|
10673
|
-
}
|
|
10674
|
-
}
|
|
10675
|
-
if (chunkTotalSize > 0) {
|
|
10676
|
-
if (chunkTotalSize < verySmallChunkSize) {
|
|
10677
|
-
currentChunkSize = currentChunkSize * 2;
|
|
10678
|
-
} else if (chunkTotalSize > smallChunkSize) {
|
|
10679
|
-
currentChunkSize = Math.max(Math.floor(currentChunkSize / 2), 20);
|
|
10680
|
-
}
|
|
10681
|
-
}
|
|
11004
|
+
for await (const { doc } of self.processChunkedKeys(
|
|
11005
|
+
keys,
|
|
11006
|
+
offset,
|
|
11007
|
+
self.options.pageSize,
|
|
11008
|
+
ftsConditions,
|
|
11009
|
+
tx2
|
|
11010
|
+
)) {
|
|
11011
|
+
if (yieldedCount >= limit) break;
|
|
11012
|
+
yield doc;
|
|
11013
|
+
yieldedCount++;
|
|
10682
11014
|
}
|
|
10683
11015
|
}
|
|
10684
11016
|
}, tx);
|
|
@@ -35,6 +35,11 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON, IC extends Index
|
|
|
35
35
|
getDocumentMetadata(tx: Transaction): Promise<DocumentDataplyMetadata>;
|
|
36
36
|
getDocumentInnerMetadata(tx: Transaction): Promise<DocumentDataplyInnerMetadata>;
|
|
37
37
|
updateDocumentInnerMetadata(metadata: DocumentDataplyInnerMetadata, tx: Transaction): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Transforms a query object into a verbose query object
|
|
40
|
+
* @param query The query object to transform
|
|
41
|
+
* @returns The verbose query object
|
|
42
|
+
*/
|
|
38
43
|
verboseQuery<U extends Partial<DocumentDataplyIndexedQuery<T, IC>>, V extends DataplyTreeValue<U>>(query: Partial<DocumentDataplyQuery<U>>): Partial<DocumentDataplyQuery<V>>;
|
|
39
44
|
/**
|
|
40
45
|
* Get the selectivity candidate for the given query
|
|
@@ -47,11 +52,15 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON, IC extends Index
|
|
|
47
52
|
tree: BPTreeAsync<number, V>;
|
|
48
53
|
condition: Partial<DocumentDataplyCondition<U>>;
|
|
49
54
|
field: string;
|
|
55
|
+
isFtsMatch?: boolean;
|
|
56
|
+
matchTokens?: string[];
|
|
50
57
|
};
|
|
51
58
|
others: {
|
|
52
59
|
tree: BPTreeAsync<number, V>;
|
|
53
60
|
condition: Partial<DocumentDataplyCondition<U>>;
|
|
54
61
|
field: string;
|
|
62
|
+
isFtsMatch?: boolean;
|
|
63
|
+
matchTokens?: string[];
|
|
55
64
|
}[];
|
|
56
65
|
rollback: () => void;
|
|
57
66
|
} | null>;
|
|
@@ -63,9 +72,15 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON, IC extends Index
|
|
|
63
72
|
verySmallChunkSize: number;
|
|
64
73
|
smallChunkSize: number;
|
|
65
74
|
};
|
|
75
|
+
private getTokenKey;
|
|
76
|
+
private applyCandidateByFTS;
|
|
66
77
|
/**
|
|
67
|
-
*
|
|
68
|
-
|
|
78
|
+
* 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
|
|
79
|
+
*/
|
|
80
|
+
private applyCandidate;
|
|
81
|
+
/**
|
|
82
|
+
* 쿼리와 인덱스 선택을 기반으로 기본 키(Primary Keys)를 가져옵니다.
|
|
83
|
+
* 쿼리 최적화를 통합하기 위한 내부 공통 메서드입니다.
|
|
69
84
|
*/
|
|
70
85
|
getKeys(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, orderBy?: keyof IC | '_id', sortOrder?: 'asc' | 'desc'): Promise<Float64Array>;
|
|
71
86
|
private insertDocumentInternal;
|
|
@@ -121,6 +136,19 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON, IC extends Index
|
|
|
121
136
|
* @returns The number of documents that match the query
|
|
122
137
|
*/
|
|
123
138
|
countDocuments(query: Partial<DocumentDataplyIndexedQuery<T, IC>>, tx?: Transaction): Promise<number>;
|
|
139
|
+
/**
|
|
140
|
+
* FTS 조건에 대해 문서가 유효한지 검증합니다.
|
|
141
|
+
*/
|
|
142
|
+
private verifyFts;
|
|
143
|
+
/**
|
|
144
|
+
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
145
|
+
*/
|
|
146
|
+
private adjustChunkSize;
|
|
147
|
+
/**
|
|
148
|
+
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
149
|
+
* FTS 검증을 통과한 문서만 yield 합니다.
|
|
150
|
+
*/
|
|
151
|
+
private processChunkedKeys;
|
|
124
152
|
/**
|
|
125
153
|
* Select documents from the database
|
|
126
154
|
* @param query The query to use (only indexed fields + _id allowed)
|
|
@@ -16,7 +16,17 @@ export interface DocumentDataplyInnerMetadata {
|
|
|
16
16
|
updatedAt: number;
|
|
17
17
|
lastId: number;
|
|
18
18
|
indices: {
|
|
19
|
-
[key: string]: [
|
|
19
|
+
[key: string]: [
|
|
20
|
+
number,
|
|
21
|
+
boolean | {
|
|
22
|
+
type: 'fts';
|
|
23
|
+
tokenizer: 'whitespace';
|
|
24
|
+
} | {
|
|
25
|
+
type: 'fts';
|
|
26
|
+
tokenizer: 'ngram';
|
|
27
|
+
gramSize: number;
|
|
28
|
+
}
|
|
29
|
+
];
|
|
20
30
|
};
|
|
21
31
|
}
|
|
22
32
|
export interface DocumentDataplyMetadata {
|
|
@@ -46,6 +56,7 @@ export type DocumentDataplyCondition<V> = {
|
|
|
46
56
|
notEqual?: Partial<V>;
|
|
47
57
|
or?: Partial<V>[];
|
|
48
58
|
like?: string;
|
|
59
|
+
match?: string;
|
|
49
60
|
};
|
|
50
61
|
export type DocumentDataplyQuery<T> = {
|
|
51
62
|
[key in keyof T]?: T[key] | DocumentDataplyCondition<T[key]>;
|
|
@@ -110,10 +121,18 @@ export type DocumentDataplyIndices<T extends DocumentJSON, IC extends IndexConfi
|
|
|
110
121
|
[key in keyof IC & keyof FinalFlatten<T>]: GetTypeByPath<T, key>;
|
|
111
122
|
};
|
|
112
123
|
/**
|
|
113
|
-
* Index configuration type
|
|
124
|
+
* Index configuration type
|
|
114
125
|
*/
|
|
126
|
+
export type FTSConfig = {
|
|
127
|
+
type: 'fts';
|
|
128
|
+
tokenizer: 'whitespace';
|
|
129
|
+
} | {
|
|
130
|
+
type: 'fts';
|
|
131
|
+
tokenizer: 'ngram';
|
|
132
|
+
gramSize: number;
|
|
133
|
+
};
|
|
115
134
|
export type IndexConfig<T> = Partial<{
|
|
116
|
-
[key in keyof FinalFlatten<T>]: boolean;
|
|
135
|
+
[key in keyof FinalFlatten<T>]: boolean | FTSConfig;
|
|
117
136
|
}>;
|
|
118
137
|
/**
|
|
119
138
|
* Extract index keys from IndexConfig
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fastStringHash(str: string): number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-dataply",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7-alpha.0",
|
|
4
4
|
"description": "Simple and powerful JSON document database supporting complex queries and flexible indexing policies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "izure <admin@izure.org>",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dataply"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"dataply": "^0.0.
|
|
45
|
+
"dataply": "^0.0.23-alpha.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/jest": "^30.0.0",
|