document-dataply 0.0.6 → 0.0.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,69 @@ 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
|
+
{ order }
|
|
10544
|
+
);
|
|
10545
|
+
for (const c of pairs.values()) {
|
|
10546
|
+
if (!c || typeof c.k !== "number") continue;
|
|
10547
|
+
const dpk = c.k;
|
|
10548
|
+
if (filterValues === void 0 || filterValues.has(dpk)) {
|
|
10549
|
+
keys.add(dpk);
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
return keys;
|
|
10554
|
+
}
|
|
10555
|
+
/**
|
|
10556
|
+
* 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
|
|
10557
|
+
*/
|
|
10558
|
+
async applyCandidate(candidate, filterValues, order) {
|
|
10559
|
+
return await candidate.tree.keys(
|
|
10560
|
+
candidate.condition,
|
|
10561
|
+
{
|
|
10562
|
+
filterValues,
|
|
10563
|
+
order
|
|
10564
|
+
}
|
|
10565
|
+
);
|
|
10566
|
+
}
|
|
10296
10567
|
/**
|
|
10297
|
-
*
|
|
10298
|
-
*
|
|
10568
|
+
* 쿼리와 인덱스 선택을 기반으로 기본 키(Primary Keys)를 가져옵니다.
|
|
10569
|
+
* 쿼리 최적화를 통합하기 위한 내부 공통 메서드입니다.
|
|
10299
10570
|
*/
|
|
10300
10571
|
async getKeys(query, orderBy, sortOrder = "asc") {
|
|
10301
10572
|
const isQueryEmpty = Object.keys(query).length === 0;
|
|
10302
10573
|
const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
|
|
10303
|
-
const verbose = this.verboseQuery(normalizedQuery);
|
|
10304
10574
|
const selectivity = await this.getSelectivityCandidate(
|
|
10305
|
-
|
|
10575
|
+
this.verboseQuery(normalizedQuery),
|
|
10306
10576
|
orderBy
|
|
10307
10577
|
);
|
|
10308
10578
|
if (!selectivity) return new Float64Array(0);
|
|
10309
10579
|
const { driver, others, rollback } = selectivity;
|
|
10310
|
-
const
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10580
|
+
const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
|
|
10581
|
+
const candidates = [driver, ...others];
|
|
10582
|
+
let keys = void 0;
|
|
10583
|
+
for (const candidate of candidates) {
|
|
10584
|
+
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10585
|
+
if (candidate.isFtsMatch && candidate.matchTokens && candidate.matchTokens.length > 0) {
|
|
10586
|
+
keys = await this.applyCandidateByFTS(
|
|
10587
|
+
candidate,
|
|
10588
|
+
candidate.matchTokens,
|
|
10589
|
+
keys,
|
|
10590
|
+
currentOrder
|
|
10591
|
+
);
|
|
10592
|
+
} else {
|
|
10593
|
+
keys = await this.applyCandidate(candidate, keys, currentOrder);
|
|
10322
10594
|
}
|
|
10323
|
-
rollback();
|
|
10324
|
-
return new Float64Array(keysSet);
|
|
10325
10595
|
}
|
|
10596
|
+
rollback();
|
|
10597
|
+
return new Float64Array(Array.from(keys || []));
|
|
10326
10598
|
}
|
|
10327
10599
|
async insertDocumentInternal(document, tx) {
|
|
10328
10600
|
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
@@ -10346,15 +10618,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10346
10618
|
*/
|
|
10347
10619
|
async insertSingleDocument(document, tx) {
|
|
10348
10620
|
return this.writeLock(() => this.runWithDefault(async (tx2) => {
|
|
10349
|
-
const { pk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10621
|
+
const { pk: dpk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10622
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10350
10623
|
const flattenDocument = this.flattenDocument(dataplyDocument);
|
|
10351
10624
|
for (const field in flattenDocument) {
|
|
10352
10625
|
const tree = this.trees.get(field);
|
|
10353
10626
|
if (!tree) continue;
|
|
10354
10627
|
const v = flattenDocument[field];
|
|
10355
|
-
const
|
|
10356
|
-
|
|
10357
|
-
|
|
10628
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10629
|
+
let tokens = [v];
|
|
10630
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10631
|
+
if (isFts) {
|
|
10632
|
+
tokens = tokenize(v, indexConfig);
|
|
10633
|
+
}
|
|
10634
|
+
for (const token of tokens) {
|
|
10635
|
+
const keyToInsert = isFts ? this.getTokenKey(dpk, token) : dpk;
|
|
10636
|
+
const [error] = await catchPromise(tree.insert(keyToInsert, { k: dpk, v: token }));
|
|
10637
|
+
if (error) {
|
|
10638
|
+
throw error;
|
|
10639
|
+
}
|
|
10358
10640
|
}
|
|
10359
10641
|
}
|
|
10360
10642
|
return dataplyDocument._id;
|
|
@@ -10392,14 +10674,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10392
10674
|
}
|
|
10393
10675
|
for (const [field, tree] of this.trees) {
|
|
10394
10676
|
const treeTx = await tree.createTransaction();
|
|
10677
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10678
|
+
const batchInsertData = [];
|
|
10395
10679
|
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
10396
10680
|
const item = flattenedData[i];
|
|
10397
10681
|
const v = item.data[field];
|
|
10398
10682
|
if (v === void 0) continue;
|
|
10399
|
-
const
|
|
10400
|
-
|
|
10401
|
-
|
|
10683
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10684
|
+
let tokens = [v];
|
|
10685
|
+
if (isFts) {
|
|
10686
|
+
tokens = tokenize(v, indexConfig);
|
|
10402
10687
|
}
|
|
10688
|
+
for (const token of tokens) {
|
|
10689
|
+
const keyToInsert = isFts ? this.getTokenKey(item.pk, token) : item.pk;
|
|
10690
|
+
batchInsertData.push([keyToInsert, { k: item.pk, v: token }]);
|
|
10691
|
+
}
|
|
10692
|
+
}
|
|
10693
|
+
const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
|
|
10694
|
+
if (error) {
|
|
10695
|
+
throw error;
|
|
10403
10696
|
}
|
|
10404
10697
|
const res = await treeTx.commit();
|
|
10405
10698
|
if (!res.success) {
|
|
@@ -10431,15 +10724,34 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10431
10724
|
const updatedDoc = computeUpdatedDoc(doc);
|
|
10432
10725
|
const oldFlatDoc = this.flattenDocument(doc);
|
|
10433
10726
|
const newFlatDoc = this.flattenDocument(updatedDoc);
|
|
10727
|
+
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
10434
10728
|
for (const [field, treeTx] of treeTxs) {
|
|
10435
10729
|
const oldV = oldFlatDoc[field];
|
|
10436
10730
|
const newV = newFlatDoc[field];
|
|
10437
10731
|
if (oldV === newV) continue;
|
|
10732
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10733
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts";
|
|
10438
10734
|
if (field in oldFlatDoc) {
|
|
10439
|
-
|
|
10735
|
+
let oldTokens = [oldV];
|
|
10736
|
+
if (isFts && typeof oldV === "string") {
|
|
10737
|
+
oldTokens = tokenize(oldV, indexConfig);
|
|
10738
|
+
}
|
|
10739
|
+
for (const oldToken of oldTokens) {
|
|
10740
|
+
const keyToDelete = isFts ? this.getTokenKey(pk, oldToken) : pk;
|
|
10741
|
+
await treeTx.delete(keyToDelete, { k: pk, v: oldToken });
|
|
10742
|
+
}
|
|
10440
10743
|
}
|
|
10441
10744
|
if (field in newFlatDoc) {
|
|
10442
|
-
|
|
10745
|
+
let newTokens = [newV];
|
|
10746
|
+
if (isFts && typeof newV === "string") {
|
|
10747
|
+
newTokens = tokenize(newV, indexConfig);
|
|
10748
|
+
}
|
|
10749
|
+
const batchInsertData = [];
|
|
10750
|
+
for (const newToken of newTokens) {
|
|
10751
|
+
const keyToInsert = isFts ? this.getTokenKey(pk, newToken) : pk;
|
|
10752
|
+
batchInsertData.push([keyToInsert, { k: pk, v: newToken }]);
|
|
10753
|
+
}
|
|
10754
|
+
await treeTx.batchInsert(batchInsertData);
|
|
10443
10755
|
}
|
|
10444
10756
|
}
|
|
10445
10757
|
await this.update(pk, JSON.stringify(updatedDoc), tx);
|
|
@@ -10503,10 +10815,20 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10503
10815
|
const doc = await this.getDocument(pk, tx2);
|
|
10504
10816
|
if (!doc) continue;
|
|
10505
10817
|
const flatDoc = this.flattenDocument(doc);
|
|
10818
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10506
10819
|
for (const [field, tree] of this.trees) {
|
|
10507
10820
|
const v = flatDoc[field];
|
|
10508
10821
|
if (v === void 0) continue;
|
|
10509
|
-
|
|
10822
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10823
|
+
const isFts = typeof indexConfig === "object" && indexConfig?.type === "fts" && typeof v === "string";
|
|
10824
|
+
let tokens = [v];
|
|
10825
|
+
if (isFts) {
|
|
10826
|
+
tokens = tokenize(v, indexConfig);
|
|
10827
|
+
}
|
|
10828
|
+
for (const token of tokens) {
|
|
10829
|
+
const keyToDelete = isFts ? this.getTokenKey(pk, token) : pk;
|
|
10830
|
+
await tree.delete(keyToDelete, { k: pk, v: token });
|
|
10831
|
+
}
|
|
10510
10832
|
}
|
|
10511
10833
|
await super.delete(pk, true, tx2);
|
|
10512
10834
|
deletedCount++;
|
|
@@ -10526,6 +10848,63 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10526
10848
|
return pks.length;
|
|
10527
10849
|
}, tx));
|
|
10528
10850
|
}
|
|
10851
|
+
/**
|
|
10852
|
+
* FTS 조건에 대해 문서가 유효한지 검증합니다.
|
|
10853
|
+
*/
|
|
10854
|
+
verifyFts(doc, ftsConditions) {
|
|
10855
|
+
for (const { field, matchTokens } of ftsConditions) {
|
|
10856
|
+
const docValue = this.flattenDocument(doc)[field];
|
|
10857
|
+
if (typeof docValue !== "string") return false;
|
|
10858
|
+
for (const token of matchTokens) {
|
|
10859
|
+
if (!docValue.includes(token)) return false;
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
return true;
|
|
10863
|
+
}
|
|
10864
|
+
/**
|
|
10865
|
+
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
10866
|
+
*/
|
|
10867
|
+
adjustChunkSize(currentChunkSize, chunkTotalSize) {
|
|
10868
|
+
if (chunkTotalSize <= 0) return currentChunkSize;
|
|
10869
|
+
const { verySmallChunkSize, smallChunkSize } = this.getFreeMemoryChunkSize();
|
|
10870
|
+
if (chunkTotalSize < verySmallChunkSize) return currentChunkSize * 2;
|
|
10871
|
+
if (chunkTotalSize > smallChunkSize) return Math.max(Math.floor(currentChunkSize / 2), 20);
|
|
10872
|
+
return currentChunkSize;
|
|
10873
|
+
}
|
|
10874
|
+
/**
|
|
10875
|
+
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
10876
|
+
* FTS 검증을 통과한 문서만 yield 합니다.
|
|
10877
|
+
*/
|
|
10878
|
+
async *processChunkedKeys(keys, startIdx, initialChunkSize, ftsConditions, tx) {
|
|
10879
|
+
let i = startIdx;
|
|
10880
|
+
const totalKeys = keys.length;
|
|
10881
|
+
let currentChunkSize = initialChunkSize;
|
|
10882
|
+
let nextChunkPromise = null;
|
|
10883
|
+
if (i < totalKeys) {
|
|
10884
|
+
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
10885
|
+
nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
|
|
10886
|
+
i = endIdx;
|
|
10887
|
+
}
|
|
10888
|
+
while (nextChunkPromise) {
|
|
10889
|
+
const rawResults = await nextChunkPromise;
|
|
10890
|
+
nextChunkPromise = null;
|
|
10891
|
+
if (i < totalKeys) {
|
|
10892
|
+
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
10893
|
+
nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
|
|
10894
|
+
i = endIdx;
|
|
10895
|
+
}
|
|
10896
|
+
let chunkTotalSize = 0;
|
|
10897
|
+
for (let j = 0, len = rawResults.length; j < len; j++) {
|
|
10898
|
+
const s = rawResults[j];
|
|
10899
|
+
if (!s) continue;
|
|
10900
|
+
const doc = JSON.parse(s);
|
|
10901
|
+
chunkTotalSize += s.length * 2;
|
|
10902
|
+
if (!this.verifyFts(doc, ftsConditions)) continue;
|
|
10903
|
+
yield { doc, rawSize: s.length * 2 };
|
|
10904
|
+
}
|
|
10905
|
+
currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
|
|
10906
|
+
}
|
|
10907
|
+
}
|
|
10529
10908
|
/**
|
|
10530
10909
|
* Select documents from the database
|
|
10531
10910
|
* @param query The query to use (only indexed fields + _id allowed)
|
|
@@ -10552,80 +10931,57 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10552
10931
|
} = options;
|
|
10553
10932
|
const self = this;
|
|
10554
10933
|
const stream = this.streamWithDefault(async function* (tx2) {
|
|
10934
|
+
const metadata = await self.getDocumentInnerMetadata(tx2);
|
|
10935
|
+
const ftsConditions = [];
|
|
10936
|
+
for (const field in query) {
|
|
10937
|
+
const q = query[field];
|
|
10938
|
+
if (q && typeof q === "object" && "match" in q && typeof q.match === "string") {
|
|
10939
|
+
const indexConfig = metadata.indices[field]?.[1];
|
|
10940
|
+
if (typeof indexConfig === "object" && indexConfig?.type === "fts") {
|
|
10941
|
+
ftsConditions.push({ field, matchTokens: tokenize(q.match, indexConfig) });
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10555
10945
|
const keys = await self.getKeys(query, orderByField, sortOrder);
|
|
10556
|
-
|
|
10557
|
-
if (totalKeys === 0) return;
|
|
10946
|
+
if (keys.length === 0) return;
|
|
10558
10947
|
const selectivity = await self.getSelectivityCandidate(
|
|
10559
10948
|
self.verboseQuery(query),
|
|
10560
10949
|
orderByField
|
|
10561
10950
|
);
|
|
10562
10951
|
const isDriverOrderByField = orderByField === void 0 || selectivity && selectivity.driver.field === orderByField;
|
|
10563
|
-
if (selectivity)
|
|
10564
|
-
selectivity.rollback();
|
|
10565
|
-
}
|
|
10566
|
-
let currentChunkSize = self.options.pageSize;
|
|
10952
|
+
if (selectivity) selectivity.rollback();
|
|
10567
10953
|
if (!isDriverOrderByField && orderByField) {
|
|
10568
10954
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
10569
10955
|
let heap = null;
|
|
10570
10956
|
if (topK !== Infinity) {
|
|
10571
|
-
|
|
10957
|
+
heap = new BinaryHeap((a, b) => {
|
|
10572
10958
|
const aVal = a[orderByField] ?? a._id;
|
|
10573
10959
|
const bVal = b[orderByField] ?? b._id;
|
|
10574
10960
|
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10575
10961
|
return sortOrder === "asc" ? -cmp : cmp;
|
|
10576
|
-
};
|
|
10577
|
-
heap = new BinaryHeap(heapComparator);
|
|
10962
|
+
});
|
|
10578
10963
|
}
|
|
10579
10964
|
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);
|
|
10965
|
+
for await (const { doc } of self.processChunkedKeys(
|
|
10966
|
+
keys,
|
|
10967
|
+
0,
|
|
10968
|
+
self.options.pageSize,
|
|
10969
|
+
ftsConditions,
|
|
10970
|
+
tx2
|
|
10971
|
+
)) {
|
|
10972
|
+
if (heap) {
|
|
10973
|
+
if (heap.size < topK) heap.push(doc);
|
|
10974
|
+
else {
|
|
10975
|
+
const top = heap.peek();
|
|
10976
|
+
if (top) {
|
|
10977
|
+
const aVal = doc[orderByField] ?? doc._id;
|
|
10978
|
+
const bVal = top[orderByField] ?? top._id;
|
|
10979
|
+
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10980
|
+
if (sortOrder === "asc" ? cmp < 0 : cmp > 0) heap.replace(doc);
|
|
10620
10981
|
}
|
|
10621
10982
|
}
|
|
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
|
-
}
|
|
10983
|
+
} else {
|
|
10984
|
+
results.push(doc);
|
|
10629
10985
|
}
|
|
10630
10986
|
}
|
|
10631
10987
|
const finalDocs = heap ? heap.toArray() : results;
|
|
@@ -10635,50 +10991,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10635
10991
|
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
10636
10992
|
return sortOrder === "asc" ? cmp : -cmp;
|
|
10637
10993
|
});
|
|
10638
|
-
const
|
|
10639
|
-
const
|
|
10640
|
-
const limitedResults = finalDocs.slice(start, end);
|
|
10994
|
+
const end = limit === Infinity ? void 0 : offset + limit;
|
|
10995
|
+
const limitedResults = finalDocs.slice(offset, end);
|
|
10641
10996
|
for (let j = 0, len = limitedResults.length; j < len; j++) {
|
|
10642
10997
|
yield limitedResults[j];
|
|
10643
10998
|
}
|
|
10644
10999
|
} else {
|
|
10645
11000
|
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
|
-
}
|
|
11001
|
+
for await (const { doc } of self.processChunkedKeys(
|
|
11002
|
+
keys,
|
|
11003
|
+
offset,
|
|
11004
|
+
self.options.pageSize,
|
|
11005
|
+
ftsConditions,
|
|
11006
|
+
tx2
|
|
11007
|
+
)) {
|
|
11008
|
+
if (yieldedCount >= limit) break;
|
|
11009
|
+
yield doc;
|
|
11010
|
+
yieldedCount++;
|
|
10682
11011
|
}
|
|
10683
11012
|
}
|
|
10684
11013
|
}, 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.1",
|
|
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",
|