document-dataply 0.0.9-alpha.3 → 0.0.9-alpha.5
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 +191 -104
- package/dist/types/core/documentAPI.d.ts +9 -5
- package/dist/types/types/index.d.ts +1 -1
- package/package.json +2 -2
- package/readme.md +12 -7
package/dist/cjs/index.js
CHANGED
|
@@ -10081,9 +10081,24 @@ function compareValue(a, b) {
|
|
|
10081
10081
|
}
|
|
10082
10082
|
return aList.length - bList.length;
|
|
10083
10083
|
}
|
|
10084
|
+
function comparePrimaryValue(a, b) {
|
|
10085
|
+
const aArr = Array.isArray(a);
|
|
10086
|
+
const bArr = Array.isArray(b);
|
|
10087
|
+
if (!aArr && !bArr) {
|
|
10088
|
+
return comparePrimitive(a, b);
|
|
10089
|
+
}
|
|
10090
|
+
const aList = aArr ? a : [a];
|
|
10091
|
+
const bList = bArr ? b : [b];
|
|
10092
|
+
const len = Math.min(aList.length, bList.length);
|
|
10093
|
+
for (let i = 0; i < len; i++) {
|
|
10094
|
+
const diff = comparePrimitive(aList[i], bList[i]);
|
|
10095
|
+
if (diff !== 0) return diff;
|
|
10096
|
+
}
|
|
10097
|
+
return 0;
|
|
10098
|
+
}
|
|
10084
10099
|
var DocumentValueComparator = class extends import_dataply2.ValueComparator {
|
|
10085
10100
|
primaryAsc(a, b) {
|
|
10086
|
-
return
|
|
10101
|
+
return comparePrimaryValue(a.v, b.v);
|
|
10087
10102
|
}
|
|
10088
10103
|
asc(a, b) {
|
|
10089
10104
|
const diff = compareValue(a.v, b.v);
|
|
@@ -10417,19 +10432,39 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10417
10432
|
* Convert CreateIndexOption to IndexMetaConfig for metadata storage.
|
|
10418
10433
|
*/
|
|
10419
10434
|
toIndexMetaConfig(option) {
|
|
10435
|
+
if (!option || typeof option !== "object") {
|
|
10436
|
+
throw new Error("Index option must be a non-null object");
|
|
10437
|
+
}
|
|
10438
|
+
if (!option.type) {
|
|
10439
|
+
throw new Error('Index option must have a "type" property ("btree" or "fts")');
|
|
10440
|
+
}
|
|
10420
10441
|
if (option.type === "btree") {
|
|
10442
|
+
if (!Array.isArray(option.fields) || option.fields.length === 0) {
|
|
10443
|
+
throw new Error('btree index requires a non-empty "fields" array');
|
|
10444
|
+
}
|
|
10445
|
+
for (let i = 0; i < option.fields.length; i++) {
|
|
10446
|
+
if (typeof option.fields[i] !== "string" || option.fields[i].length === 0) {
|
|
10447
|
+
throw new Error(`btree index "fields[${i}]" must be a non-empty string, got: ${JSON.stringify(option.fields[i])}`);
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10421
10450
|
return {
|
|
10422
10451
|
type: "btree",
|
|
10423
10452
|
fields: option.fields
|
|
10424
10453
|
};
|
|
10425
10454
|
}
|
|
10426
10455
|
if (option.type === "fts") {
|
|
10456
|
+
if (typeof option.fields !== "string" || option.fields.length === 0) {
|
|
10457
|
+
throw new Error(`fts index requires a non-empty string "fields", got: ${JSON.stringify(option.fields)}`);
|
|
10458
|
+
}
|
|
10427
10459
|
if (option.tokenizer === "ngram") {
|
|
10460
|
+
if (typeof option.gramSize !== "number" || option.gramSize < 1) {
|
|
10461
|
+
throw new Error(`fts ngram index requires a positive "gramSize" number, got: ${JSON.stringify(option.gramSize)}`);
|
|
10462
|
+
}
|
|
10428
10463
|
return {
|
|
10429
10464
|
type: "fts",
|
|
10430
10465
|
fields: option.fields,
|
|
10431
10466
|
tokenizer: "ngram",
|
|
10432
|
-
gramSize: option.
|
|
10467
|
+
gramSize: option.gramSize
|
|
10433
10468
|
};
|
|
10434
10469
|
}
|
|
10435
10470
|
return {
|
|
@@ -10537,8 +10572,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10537
10572
|
if (metadata.lastId === 0) {
|
|
10538
10573
|
return 0;
|
|
10539
10574
|
}
|
|
10540
|
-
|
|
10541
|
-
const indexEntryMap = /* @__PURE__ */ new Map();
|
|
10575
|
+
let indexTxMap = {};
|
|
10542
10576
|
for (const indexName of backfillTargets) {
|
|
10543
10577
|
const tree = this.trees.get(indexName);
|
|
10544
10578
|
if (tree && indexName !== "_id") {
|
|
@@ -10546,6 +10580,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10546
10580
|
}
|
|
10547
10581
|
}
|
|
10548
10582
|
let backfilledCount = 0;
|
|
10583
|
+
let chunkCount = 0;
|
|
10584
|
+
const CHUNK_SIZE = 1e3;
|
|
10549
10585
|
const idTree = this.trees.get("_id");
|
|
10550
10586
|
if (!idTree) {
|
|
10551
10587
|
throw new Error("ID tree not found");
|
|
@@ -10574,10 +10610,6 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10574
10610
|
const keyToInsert = this.getTokenKey(k, token);
|
|
10575
10611
|
const entry = { k, v: token };
|
|
10576
10612
|
batchInsertData.push([keyToInsert, entry]);
|
|
10577
|
-
if (!indexEntryMap.has(btx)) {
|
|
10578
|
-
indexEntryMap.set(btx, []);
|
|
10579
|
-
}
|
|
10580
|
-
indexEntryMap.get(btx).push({ k: keyToInsert, v: entry });
|
|
10581
10613
|
}
|
|
10582
10614
|
await btx.batchInsert(batchInsertData);
|
|
10583
10615
|
} else {
|
|
@@ -10585,34 +10617,42 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10585
10617
|
if (indexVal === void 0) continue;
|
|
10586
10618
|
const entry = { k, v: indexVal };
|
|
10587
10619
|
const batchInsertData = [[k, entry]];
|
|
10588
|
-
if (!indexEntryMap.has(btx)) {
|
|
10589
|
-
indexEntryMap.set(btx, []);
|
|
10590
|
-
}
|
|
10591
|
-
indexEntryMap.get(btx).push(entry);
|
|
10592
10620
|
await btx.batchInsert(batchInsertData);
|
|
10593
10621
|
}
|
|
10594
10622
|
}
|
|
10595
10623
|
backfilledCount++;
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10624
|
+
chunkCount++;
|
|
10625
|
+
if (chunkCount >= CHUNK_SIZE) {
|
|
10626
|
+
try {
|
|
10627
|
+
for (const btx of Object.values(indexTxMap)) {
|
|
10628
|
+
await btx.commit();
|
|
10629
|
+
}
|
|
10630
|
+
} catch (err) {
|
|
10631
|
+
for (const btx of Object.values(indexTxMap)) {
|
|
10632
|
+
await btx.rollback();
|
|
10633
|
+
}
|
|
10634
|
+
throw err;
|
|
10635
|
+
}
|
|
10636
|
+
for (const indexName of backfillTargets) {
|
|
10637
|
+
const tree = this.trees.get(indexName);
|
|
10638
|
+
if (tree && indexName !== "_id") {
|
|
10639
|
+
indexTxMap[indexName] = await tree.createTransaction();
|
|
10640
|
+
}
|
|
10641
|
+
}
|
|
10642
|
+
chunkCount = 0;
|
|
10607
10643
|
}
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
for (const
|
|
10612
|
-
await btx.
|
|
10644
|
+
}
|
|
10645
|
+
if (chunkCount > 0) {
|
|
10646
|
+
try {
|
|
10647
|
+
for (const btx of Object.values(indexTxMap)) {
|
|
10648
|
+
await btx.commit();
|
|
10613
10649
|
}
|
|
10650
|
+
} catch (err) {
|
|
10651
|
+
for (const btx of Object.values(indexTxMap)) {
|
|
10652
|
+
await btx.rollback();
|
|
10653
|
+
}
|
|
10654
|
+
throw err;
|
|
10614
10655
|
}
|
|
10615
|
-
throw err;
|
|
10616
10656
|
}
|
|
10617
10657
|
this.pendingBackfillFields = [];
|
|
10618
10658
|
return backfilledCount;
|
|
@@ -10706,9 +10746,10 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10706
10746
|
const currentVersion = innerMetadata.schemeVersion ?? 0;
|
|
10707
10747
|
if (currentVersion < version) {
|
|
10708
10748
|
await callback(tx2);
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10749
|
+
const freshMetadata = await this.getDocumentInnerMetadata(tx2);
|
|
10750
|
+
freshMetadata.schemeVersion = version;
|
|
10751
|
+
freshMetadata.updatedAt = Date.now();
|
|
10752
|
+
await this.updateDocumentInnerMetadata(freshMetadata, tx2);
|
|
10712
10753
|
}
|
|
10713
10754
|
}, tx);
|
|
10714
10755
|
}
|
|
@@ -10769,27 +10810,61 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10769
10810
|
const condition = query[primaryField];
|
|
10770
10811
|
const treeTx = await tree.createTransaction();
|
|
10771
10812
|
let score = 0;
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
}
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
}
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10813
|
+
let isConsecutive = true;
|
|
10814
|
+
const coveredFields = [];
|
|
10815
|
+
const compositeVerifyFields = [];
|
|
10816
|
+
for (const field of config.fields) {
|
|
10817
|
+
if (!queryFields.has(field)) {
|
|
10818
|
+
isConsecutive = false;
|
|
10819
|
+
continue;
|
|
10820
|
+
}
|
|
10821
|
+
coveredFields.push(field);
|
|
10822
|
+
if (field !== primaryField) {
|
|
10823
|
+
compositeVerifyFields.push(field);
|
|
10824
|
+
}
|
|
10825
|
+
score += 1;
|
|
10826
|
+
if (isConsecutive) {
|
|
10827
|
+
const cond = query[field];
|
|
10828
|
+
if (cond !== void 0) {
|
|
10829
|
+
if (typeof cond !== "object" || cond === null) {
|
|
10830
|
+
score += 100;
|
|
10831
|
+
} else if ("primaryEqual" in cond || "equal" in cond) {
|
|
10832
|
+
score += 100;
|
|
10833
|
+
} else if ("primaryGte" in cond || "primaryLte" in cond || "primaryGt" in cond || "primaryLt" in cond || "gte" in cond || "lte" in cond || "gt" in cond || "lt" in cond) {
|
|
10834
|
+
score += 50;
|
|
10835
|
+
isConsecutive = false;
|
|
10836
|
+
} else if ("primaryOr" in cond || "or" in cond) {
|
|
10837
|
+
score += 20;
|
|
10838
|
+
isConsecutive = false;
|
|
10839
|
+
} else if ("like" in cond) {
|
|
10840
|
+
score += 15;
|
|
10841
|
+
isConsecutive = false;
|
|
10842
|
+
} else {
|
|
10843
|
+
score += 10;
|
|
10844
|
+
isConsecutive = false;
|
|
10845
|
+
}
|
|
10846
|
+
}
|
|
10787
10847
|
}
|
|
10788
10848
|
}
|
|
10789
|
-
|
|
10790
|
-
|
|
10849
|
+
let isIndexOrderSupported = false;
|
|
10850
|
+
if (orderByField) {
|
|
10851
|
+
for (const field of config.fields) {
|
|
10852
|
+
if (field === orderByField) {
|
|
10853
|
+
isIndexOrderSupported = true;
|
|
10854
|
+
break;
|
|
10855
|
+
}
|
|
10856
|
+
const cond = query[field];
|
|
10857
|
+
let isExactMatch = false;
|
|
10858
|
+
if (cond !== void 0) {
|
|
10859
|
+
if (typeof cond !== "object" || cond === null) isExactMatch = true;
|
|
10860
|
+
else if ("primaryEqual" in cond || "equal" in cond) isExactMatch = true;
|
|
10861
|
+
}
|
|
10862
|
+
if (!isExactMatch) break;
|
|
10863
|
+
}
|
|
10864
|
+
if (isIndexOrderSupported) {
|
|
10865
|
+
score += 200;
|
|
10866
|
+
}
|
|
10791
10867
|
}
|
|
10792
|
-
const compositeVerifyFields = coveredFields.filter((f) => f !== primaryField);
|
|
10793
10868
|
candidates.push({
|
|
10794
10869
|
tree: treeTx,
|
|
10795
10870
|
condition,
|
|
@@ -10797,7 +10872,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10797
10872
|
indexName,
|
|
10798
10873
|
isFtsMatch: false,
|
|
10799
10874
|
score,
|
|
10800
|
-
compositeVerifyFields
|
|
10875
|
+
compositeVerifyFields,
|
|
10876
|
+
isIndexOrderSupported
|
|
10801
10877
|
});
|
|
10802
10878
|
} else if (config.type === "fts") {
|
|
10803
10879
|
const field = config.fields;
|
|
@@ -10815,7 +10891,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10815
10891
|
isFtsMatch: true,
|
|
10816
10892
|
matchTokens,
|
|
10817
10893
|
score: 90,
|
|
10818
|
-
compositeVerifyFields: []
|
|
10894
|
+
compositeVerifyFields: [],
|
|
10895
|
+
isIndexOrderSupported: false
|
|
10819
10896
|
});
|
|
10820
10897
|
}
|
|
10821
10898
|
}
|
|
@@ -10858,33 +10935,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10858
10935
|
getTokenKey(pk, token) {
|
|
10859
10936
|
return pk + ":" + token;
|
|
10860
10937
|
}
|
|
10861
|
-
async
|
|
10938
|
+
async *applyCandidateByFTSStream(candidate, matchedTokens, filterValues, order) {
|
|
10862
10939
|
const keys = /* @__PURE__ */ new Set();
|
|
10863
10940
|
for (let i = 0, len = matchedTokens.length; i < len; i++) {
|
|
10864
10941
|
const token = matchedTokens[i];
|
|
10865
|
-
const
|
|
10942
|
+
for await (const pair of candidate.tree.whereStream(
|
|
10866
10943
|
{ primaryEqual: { v: token } },
|
|
10867
|
-
{
|
|
10868
|
-
|
|
10944
|
+
{ order }
|
|
10945
|
+
)) {
|
|
10946
|
+
const pk = pair[1].k;
|
|
10947
|
+
if (filterValues && !filterValues.has(pk)) continue;
|
|
10948
|
+
if (!keys.has(pk)) {
|
|
10949
|
+
keys.add(pk);
|
|
10950
|
+
yield pk;
|
|
10869
10951
|
}
|
|
10870
|
-
);
|
|
10871
|
-
for (const pair of pairs.values()) {
|
|
10872
|
-
if (filterValues && !filterValues.has(pair.k)) continue;
|
|
10873
|
-
keys.add(pair.k);
|
|
10874
10952
|
}
|
|
10875
10953
|
}
|
|
10876
|
-
return keys;
|
|
10877
10954
|
}
|
|
10878
10955
|
/**
|
|
10879
10956
|
* 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
|
|
10880
10957
|
*/
|
|
10881
|
-
|
|
10882
|
-
return
|
|
10958
|
+
applyCandidateStream(candidate, filterValues, order) {
|
|
10959
|
+
return candidate.tree.keysStream(
|
|
10883
10960
|
candidate.condition,
|
|
10884
|
-
{
|
|
10885
|
-
filterValues,
|
|
10886
|
-
order
|
|
10887
|
-
}
|
|
10961
|
+
{ filterValues, order }
|
|
10888
10962
|
);
|
|
10889
10963
|
}
|
|
10890
10964
|
/**
|
|
@@ -10900,30 +10974,34 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10900
10974
|
);
|
|
10901
10975
|
if (!selectivity) return new Float64Array(0);
|
|
10902
10976
|
const { driver, others, rollback } = selectivity;
|
|
10903
|
-
const useIndexOrder = orderBy === void 0 || driver.
|
|
10977
|
+
const useIndexOrder = orderBy === void 0 || driver.isIndexOrderSupported;
|
|
10904
10978
|
const candidates = [driver, ...others];
|
|
10905
10979
|
let keys = void 0;
|
|
10906
10980
|
for (let i = 0, len = candidates.length; i < len; i++) {
|
|
10907
10981
|
const candidate = candidates[i];
|
|
10908
10982
|
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10909
10983
|
if (candidate.isFtsMatch && candidate.matchTokens && candidate.matchTokens.length > 0) {
|
|
10910
|
-
|
|
10984
|
+
const stream = this.applyCandidateByFTSStream(
|
|
10911
10985
|
candidate,
|
|
10912
10986
|
candidate.matchTokens,
|
|
10913
10987
|
keys,
|
|
10914
10988
|
currentOrder
|
|
10915
10989
|
);
|
|
10990
|
+
keys = /* @__PURE__ */ new Set();
|
|
10991
|
+
for await (const pk of stream) keys.add(pk);
|
|
10916
10992
|
} else {
|
|
10917
|
-
|
|
10993
|
+
const stream = this.applyCandidateStream(candidate, keys, currentOrder);
|
|
10994
|
+
keys = /* @__PURE__ */ new Set();
|
|
10995
|
+
for await (const pk of stream) keys.add(pk);
|
|
10918
10996
|
}
|
|
10919
10997
|
}
|
|
10920
10998
|
rollback();
|
|
10921
10999
|
return new Float64Array(Array.from(keys || []));
|
|
10922
11000
|
}
|
|
10923
11001
|
/**
|
|
10924
|
-
* 드라이버 인덱스만으로 PK
|
|
11002
|
+
* 드라이버 인덱스만으로 PK 스트림을 가져옵니다. (교집합 없이)
|
|
10925
11003
|
* selectDocuments에서 사용하며, 나머지 조건(others)은 스트리밍 중 tree.verify()로 검증합니다.
|
|
10926
|
-
* @returns 드라이버 키
|
|
11004
|
+
* @returns 드라이버 키 스트림, others 후보 목록, rollback 함수. 또는 null.
|
|
10927
11005
|
*/
|
|
10928
11006
|
async getDriverKeys(query, orderBy, sortOrder = "asc") {
|
|
10929
11007
|
const isQueryEmpty = Object.keys(query).length === 0;
|
|
@@ -10934,21 +11012,21 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10934
11012
|
);
|
|
10935
11013
|
if (!selectivity) return null;
|
|
10936
11014
|
const { driver, others, compositeVerifyConditions, rollback } = selectivity;
|
|
10937
|
-
const useIndexOrder = orderBy === void 0 || driver.
|
|
11015
|
+
const useIndexOrder = orderBy === void 0 || driver.isIndexOrderSupported;
|
|
10938
11016
|
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10939
|
-
let
|
|
11017
|
+
let keysStream;
|
|
10940
11018
|
if (driver.isFtsMatch && driver.matchTokens && driver.matchTokens.length > 0) {
|
|
10941
|
-
|
|
11019
|
+
keysStream = this.applyCandidateByFTSStream(
|
|
10942
11020
|
driver,
|
|
10943
11021
|
driver.matchTokens,
|
|
10944
11022
|
void 0,
|
|
10945
11023
|
currentOrder
|
|
10946
11024
|
);
|
|
10947
11025
|
} else {
|
|
10948
|
-
|
|
11026
|
+
keysStream = this.applyCandidateStream(driver, void 0, currentOrder);
|
|
10949
11027
|
}
|
|
10950
11028
|
return {
|
|
10951
|
-
|
|
11029
|
+
keysStream,
|
|
10952
11030
|
others,
|
|
10953
11031
|
compositeVerifyConditions,
|
|
10954
11032
|
isDriverOrderByField: useIndexOrder,
|
|
@@ -11298,28 +11376,17 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11298
11376
|
return currentChunkSize;
|
|
11299
11377
|
}
|
|
11300
11378
|
/**
|
|
11301
|
-
* Prefetch 방식으로 키
|
|
11379
|
+
* Prefetch 방식으로 키 스트림을 청크 단위로 조회하여 문서를 순회합니다.
|
|
11302
11380
|
* FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
|
|
11303
11381
|
*/
|
|
11304
|
-
async *processChunkedKeysWithVerify(
|
|
11382
|
+
async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
|
|
11305
11383
|
const verifyOthers = others.filter((o) => !o.isFtsMatch);
|
|
11306
|
-
let i = startIdx;
|
|
11307
|
-
const totalKeys = keys.length;
|
|
11308
11384
|
let currentChunkSize = initialChunkSize;
|
|
11309
|
-
let
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
}
|
|
11315
|
-
while (nextChunkPromise) {
|
|
11316
|
-
const rawResults = await nextChunkPromise;
|
|
11317
|
-
nextChunkPromise = null;
|
|
11318
|
-
if (i < totalKeys) {
|
|
11319
|
-
const endIdx = Math.min(i + currentChunkSize, totalKeys);
|
|
11320
|
-
nextChunkPromise = this.selectMany(keys.subarray(i, endIdx), false, tx);
|
|
11321
|
-
i = endIdx;
|
|
11322
|
-
}
|
|
11385
|
+
let chunk = [];
|
|
11386
|
+
let dropped = 0;
|
|
11387
|
+
const processChunk = async (pks) => {
|
|
11388
|
+
const docs = [];
|
|
11389
|
+
const rawResults = await this.selectMany(new Float64Array(pks), false, tx);
|
|
11323
11390
|
let chunkTotalSize = 0;
|
|
11324
11391
|
for (let j = 0, len = rawResults.length; j < len; j++) {
|
|
11325
11392
|
const s = rawResults[j];
|
|
@@ -11346,9 +11413,26 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11346
11413
|
}
|
|
11347
11414
|
if (!passed) continue;
|
|
11348
11415
|
}
|
|
11349
|
-
|
|
11416
|
+
docs.push(doc);
|
|
11350
11417
|
}
|
|
11351
11418
|
currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
|
|
11419
|
+
return docs;
|
|
11420
|
+
};
|
|
11421
|
+
for await (const pk of keysStream) {
|
|
11422
|
+
if (dropped < startIdx) {
|
|
11423
|
+
dropped++;
|
|
11424
|
+
continue;
|
|
11425
|
+
}
|
|
11426
|
+
chunk.push(pk);
|
|
11427
|
+
if (chunk.length >= currentChunkSize) {
|
|
11428
|
+
const docs = await processChunk(chunk);
|
|
11429
|
+
for (let j = 0; j < docs.length; j++) yield docs[j];
|
|
11430
|
+
chunk = [];
|
|
11431
|
+
}
|
|
11432
|
+
}
|
|
11433
|
+
if (chunk.length > 0) {
|
|
11434
|
+
const docs = await processChunk(chunk);
|
|
11435
|
+
for (let j = 0; j < docs.length; j++) yield docs[j];
|
|
11352
11436
|
}
|
|
11353
11437
|
}
|
|
11354
11438
|
/**
|
|
@@ -11396,11 +11480,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11396
11480
|
}
|
|
11397
11481
|
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
|
|
11398
11482
|
if (!driverResult) return;
|
|
11399
|
-
const {
|
|
11400
|
-
if (keys.length === 0) {
|
|
11401
|
-
rollback();
|
|
11402
|
-
return;
|
|
11403
|
-
}
|
|
11483
|
+
const { keysStream, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
|
|
11404
11484
|
try {
|
|
11405
11485
|
if (!isDriverOrderByField && orderByField) {
|
|
11406
11486
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
@@ -11415,7 +11495,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11415
11495
|
}
|
|
11416
11496
|
const results = [];
|
|
11417
11497
|
for await (const doc of self.processChunkedKeysWithVerify(
|
|
11418
|
-
|
|
11498
|
+
keysStream,
|
|
11419
11499
|
0,
|
|
11420
11500
|
self.options.pageSize,
|
|
11421
11501
|
ftsConditions,
|
|
@@ -11451,16 +11531,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11451
11531
|
yield limitedResults[j];
|
|
11452
11532
|
}
|
|
11453
11533
|
} else {
|
|
11534
|
+
const hasFilters = ftsConditions.length > 0 || compositeVerifyConditions.length > 0 || others.length > 0;
|
|
11535
|
+
const startIdx = hasFilters ? 0 : offset;
|
|
11454
11536
|
let yieldedCount = 0;
|
|
11537
|
+
let skippedCount = hasFilters ? 0 : offset;
|
|
11455
11538
|
for await (const doc of self.processChunkedKeysWithVerify(
|
|
11456
|
-
|
|
11457
|
-
|
|
11539
|
+
keysStream,
|
|
11540
|
+
startIdx,
|
|
11458
11541
|
self.options.pageSize,
|
|
11459
11542
|
ftsConditions,
|
|
11460
11543
|
compositeVerifyConditions,
|
|
11461
11544
|
others,
|
|
11462
11545
|
tx2
|
|
11463
11546
|
)) {
|
|
11547
|
+
if (skippedCount < offset) {
|
|
11548
|
+
skippedCount++;
|
|
11549
|
+
continue;
|
|
11550
|
+
}
|
|
11464
11551
|
if (yieldedCount >= limit) break;
|
|
11465
11552
|
yield doc;
|
|
11466
11553
|
yieldedCount++;
|
|
@@ -127,6 +127,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
127
127
|
field: string;
|
|
128
128
|
indexName: string;
|
|
129
129
|
isFtsMatch: false;
|
|
130
|
+
isIndexOrderSupported: boolean;
|
|
130
131
|
} | {
|
|
131
132
|
tree: BPTreeAsync<string, V>;
|
|
132
133
|
condition: Partial<DocumentDataplyCondition<U>>;
|
|
@@ -134,6 +135,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
134
135
|
indexName: string;
|
|
135
136
|
isFtsMatch: true;
|
|
136
137
|
matchTokens: string[];
|
|
138
|
+
isIndexOrderSupported: boolean;
|
|
137
139
|
});
|
|
138
140
|
others: ({
|
|
139
141
|
tree: BPTreeAsync<number, V>;
|
|
@@ -141,6 +143,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
141
143
|
field: string;
|
|
142
144
|
indexName: string;
|
|
143
145
|
isFtsMatch: false;
|
|
146
|
+
isIndexOrderSupported: boolean;
|
|
144
147
|
} | {
|
|
145
148
|
tree: BPTreeAsync<string, V>;
|
|
146
149
|
condition: Partial<DocumentDataplyCondition<U>>;
|
|
@@ -148,6 +151,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
148
151
|
indexName: string;
|
|
149
152
|
isFtsMatch: true;
|
|
150
153
|
matchTokens: string[];
|
|
154
|
+
isIndexOrderSupported: boolean;
|
|
151
155
|
})[];
|
|
152
156
|
compositeVerifyConditions: {
|
|
153
157
|
field: string;
|
|
@@ -164,20 +168,20 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
164
168
|
smallChunkSize: number;
|
|
165
169
|
};
|
|
166
170
|
private getTokenKey;
|
|
167
|
-
private
|
|
171
|
+
private applyCandidateByFTSStream;
|
|
168
172
|
/**
|
|
169
173
|
* 특정 인덱스 후보를 조회하여 PK 집합을 필터링합니다.
|
|
170
174
|
*/
|
|
171
|
-
private
|
|
175
|
+
private applyCandidateStream;
|
|
172
176
|
/**
|
|
173
177
|
* 쿼리와 인덱스 선택을 기반으로 기본 키(Primary Keys)를 가져옵니다.
|
|
174
178
|
* 쿼리 최적화를 통합하기 위한 내부 공통 메서드입니다.
|
|
175
179
|
*/
|
|
176
180
|
getKeys(query: Partial<DocumentDataplyQuery<T>>, orderBy?: string, sortOrder?: 'asc' | 'desc'): Promise<Float64Array>;
|
|
177
181
|
/**
|
|
178
|
-
* 드라이버 인덱스만으로 PK
|
|
182
|
+
* 드라이버 인덱스만으로 PK 스트림을 가져옵니다. (교집합 없이)
|
|
179
183
|
* selectDocuments에서 사용하며, 나머지 조건(others)은 스트리밍 중 tree.verify()로 검증합니다.
|
|
180
|
-
* @returns 드라이버 키
|
|
184
|
+
* @returns 드라이버 키 스트림, others 후보 목록, rollback 함수. 또는 null.
|
|
181
185
|
*/
|
|
182
186
|
private getDriverKeys;
|
|
183
187
|
private insertDocumentInternal;
|
|
@@ -250,7 +254,7 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
|
|
|
250
254
|
*/
|
|
251
255
|
private adjustChunkSize;
|
|
252
256
|
/**
|
|
253
|
-
* Prefetch 방식으로 키
|
|
257
|
+
* Prefetch 방식으로 키 스트림을 청크 단위로 조회하여 문서를 순회합니다.
|
|
254
258
|
* FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
|
|
255
259
|
*/
|
|
256
260
|
private processChunkedKeysWithVerify;
|
|
@@ -151,7 +151,7 @@ export type CreateIndexFTSOption<T extends DocumentJSON> = {
|
|
|
151
151
|
type: 'fts';
|
|
152
152
|
fields: DeepFlattenKeys<DataplyDocument<T>> & string;
|
|
153
153
|
tokenizer: 'ngram';
|
|
154
|
-
|
|
154
|
+
gramSize: number;
|
|
155
155
|
};
|
|
156
156
|
export type CreateIndexOption<T extends DocumentJSON> = CreateIndexBTreeOption<T> | CreateIndexFTSOption<T>;
|
|
157
157
|
export interface DocumentDataplyOptions extends DataplyOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-dataply",
|
|
3
|
-
"version": "0.0.9-alpha.
|
|
3
|
+
"version": "0.0.9-alpha.5",
|
|
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.24-alpha.
|
|
45
|
+
"dataply": "^0.0.24-alpha.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/jest": "^30.0.0",
|
package/readme.md
CHANGED
|
@@ -54,16 +54,21 @@ async function main() {
|
|
|
54
54
|
.Options({ wal: 'my-database.wal' })
|
|
55
55
|
.Open('my-database.db');
|
|
56
56
|
|
|
57
|
-
// Register indices before init (Recommended)
|
|
58
|
-
await db.createIndex('name', { type: 'btree', fields: ['name'] });
|
|
59
|
-
await db.createIndex('tags_0', { type: 'btree', fields: ['tags.0'] });
|
|
60
|
-
|
|
61
|
-
// Composite Index support
|
|
62
|
-
await db.createIndex('idx_name_age', { type: 'btree', fields: ['name', 'age'] });
|
|
63
|
-
|
|
64
57
|
// Initialize database
|
|
65
58
|
await db.init();
|
|
66
59
|
|
|
60
|
+
// Register indices
|
|
61
|
+
// use transaction to ensure atomicity
|
|
62
|
+
await db.migration(1, async (tx) => {
|
|
63
|
+
await db.createIndex('name', { type: 'btree', fields: ['name'] }, tx);
|
|
64
|
+
await db.createIndex('tags_0', { type: 'btree', fields: ['tags.0'] }, tx);
|
|
65
|
+
|
|
66
|
+
// Composite Index support
|
|
67
|
+
await db.createIndex('idx_name_age', { type: 'btree', fields: ['name', 'age'] }, tx);
|
|
68
|
+
|
|
69
|
+
console.log('Migration completed successfully');
|
|
70
|
+
});
|
|
71
|
+
|
|
67
72
|
// Insert document
|
|
68
73
|
const id = await db.insert({
|
|
69
74
|
name: 'John Doe',
|