document-dataply 0.0.10-alpha.5 → 0.0.10-alpha.7
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 +115 -41
- package/dist/types/core/Optimizer.d.ts +24 -14
- package/dist/types/core/QueryManager.d.ts +10 -2
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -10466,6 +10466,26 @@ function tokenize(text, options) {
|
|
|
10466
10466
|
}
|
|
10467
10467
|
|
|
10468
10468
|
// src/core/Optimizer.ts
|
|
10469
|
+
var SELECTIVITY = {
|
|
10470
|
+
/** O(log N) 포인트 룩업 */
|
|
10471
|
+
EQUAL: 0.01,
|
|
10472
|
+
/** 양쪽 바운드(gte+lte) 범위 스캔 */
|
|
10473
|
+
BOUNDED_RANGE: 0.33,
|
|
10474
|
+
/** 한쪽 바운드(gte 또는 lte)만 있을 때, 중간부터 풀스캔 */
|
|
10475
|
+
HALF_RANGE: 0.5,
|
|
10476
|
+
/** Or 조건: B+Tree 내부 풀스캔 */
|
|
10477
|
+
OR: 0.9,
|
|
10478
|
+
/** Like 조건: B+Tree 내부 풀스캔 */
|
|
10479
|
+
LIKE: 0.9,
|
|
10480
|
+
/** 알 수 없는 조건 */
|
|
10481
|
+
UNKNOWN: 0.9,
|
|
10482
|
+
/** FTS 통계 없을 때 보수적 추정 */
|
|
10483
|
+
FTS_DEFAULT: 0.5,
|
|
10484
|
+
/** 정렬 비용 가중치 (orderBy 미지원 시) */
|
|
10485
|
+
SORT_PENALTY: 0.5,
|
|
10486
|
+
/** 인메모리 정렬이 유의미해지는 임계 문서 수 */
|
|
10487
|
+
SORT_THRESHOLD: 1e4
|
|
10488
|
+
};
|
|
10469
10489
|
var Optimizer = class {
|
|
10470
10490
|
constructor(api) {
|
|
10471
10491
|
this.api = api;
|
|
@@ -10477,7 +10497,7 @@ var Optimizer = class {
|
|
|
10477
10497
|
const primaryField = config.fields[0];
|
|
10478
10498
|
if (!queryFields.has(primaryField)) return null;
|
|
10479
10499
|
const builtCondition = {};
|
|
10480
|
-
let
|
|
10500
|
+
let selectivity = 1;
|
|
10481
10501
|
let isConsecutive = true;
|
|
10482
10502
|
const coveredFields = [];
|
|
10483
10503
|
const compositeVerifyFields = [];
|
|
@@ -10492,13 +10512,12 @@ var Optimizer = class {
|
|
|
10492
10512
|
continue;
|
|
10493
10513
|
}
|
|
10494
10514
|
coveredFields.push(field);
|
|
10495
|
-
score += 1;
|
|
10496
10515
|
if (isConsecutive) {
|
|
10497
10516
|
const cond = query[field];
|
|
10498
10517
|
if (cond !== void 0) {
|
|
10499
10518
|
let isBounded = false;
|
|
10500
10519
|
if (typeof cond !== "object" || cond === null) {
|
|
10501
|
-
|
|
10520
|
+
selectivity *= SELECTIVITY.EQUAL;
|
|
10502
10521
|
startValues.push(cond);
|
|
10503
10522
|
endValues.push(cond);
|
|
10504
10523
|
startOperator = "primaryGte";
|
|
@@ -10506,7 +10525,7 @@ var Optimizer = class {
|
|
|
10506
10525
|
isBounded = true;
|
|
10507
10526
|
} else if ("primaryEqual" in cond || "equal" in cond) {
|
|
10508
10527
|
const val = cond.primaryEqual?.v ?? cond.equal?.v ?? cond.primaryEqual ?? cond.equal;
|
|
10509
|
-
|
|
10528
|
+
selectivity *= SELECTIVITY.EQUAL;
|
|
10510
10529
|
startValues.push(val);
|
|
10511
10530
|
endValues.push(val);
|
|
10512
10531
|
startOperator = "primaryGte";
|
|
@@ -10514,7 +10533,7 @@ var Optimizer = class {
|
|
|
10514
10533
|
isBounded = true;
|
|
10515
10534
|
} else if ("primaryGte" in cond || "gte" in cond) {
|
|
10516
10535
|
const val = cond.primaryGte?.v ?? cond.gte?.v ?? cond.primaryGte ?? cond.gte;
|
|
10517
|
-
|
|
10536
|
+
selectivity *= SELECTIVITY.HALF_RANGE;
|
|
10518
10537
|
isConsecutive = false;
|
|
10519
10538
|
startValues.push(val);
|
|
10520
10539
|
startOperator = "primaryGte";
|
|
@@ -10522,7 +10541,7 @@ var Optimizer = class {
|
|
|
10522
10541
|
isBounded = true;
|
|
10523
10542
|
} else if ("primaryGt" in cond || "gt" in cond) {
|
|
10524
10543
|
const val = cond.primaryGt?.v ?? cond.gt?.v ?? cond.primaryGt ?? cond.gt;
|
|
10525
|
-
|
|
10544
|
+
selectivity *= SELECTIVITY.HALF_RANGE;
|
|
10526
10545
|
isConsecutive = false;
|
|
10527
10546
|
startValues.push(val);
|
|
10528
10547
|
startOperator = "primaryGt";
|
|
@@ -10530,7 +10549,7 @@ var Optimizer = class {
|
|
|
10530
10549
|
isBounded = true;
|
|
10531
10550
|
} else if ("primaryLte" in cond || "lte" in cond) {
|
|
10532
10551
|
const val = cond.primaryLte?.v ?? cond.lte?.v ?? cond.primaryLte ?? cond.lte;
|
|
10533
|
-
|
|
10552
|
+
selectivity *= SELECTIVITY.HALF_RANGE;
|
|
10534
10553
|
isConsecutive = false;
|
|
10535
10554
|
endValues.push(val);
|
|
10536
10555
|
endOperator = "primaryLte";
|
|
@@ -10538,20 +10557,20 @@ var Optimizer = class {
|
|
|
10538
10557
|
isBounded = true;
|
|
10539
10558
|
} else if ("primaryLt" in cond || "lt" in cond) {
|
|
10540
10559
|
const val = cond.primaryLt?.v ?? cond.lt?.v ?? cond.primaryLt ?? cond.lt;
|
|
10541
|
-
|
|
10560
|
+
selectivity *= SELECTIVITY.HALF_RANGE;
|
|
10542
10561
|
isConsecutive = false;
|
|
10543
10562
|
endValues.push(val);
|
|
10544
10563
|
endOperator = "primaryLt";
|
|
10545
10564
|
if (startValues.length > 0) startOperator = "primaryGte";
|
|
10546
10565
|
isBounded = true;
|
|
10547
10566
|
} else if ("primaryOr" in cond || "or" in cond) {
|
|
10548
|
-
|
|
10567
|
+
selectivity *= SELECTIVITY.OR;
|
|
10549
10568
|
isConsecutive = false;
|
|
10550
10569
|
} else if ("like" in cond) {
|
|
10551
|
-
|
|
10570
|
+
selectivity *= SELECTIVITY.LIKE;
|
|
10552
10571
|
isConsecutive = false;
|
|
10553
10572
|
} else {
|
|
10554
|
-
|
|
10573
|
+
selectivity *= SELECTIVITY.UNKNOWN;
|
|
10555
10574
|
isConsecutive = false;
|
|
10556
10575
|
}
|
|
10557
10576
|
if (!isBounded && field !== primaryField) {
|
|
@@ -10598,9 +10617,6 @@ var Optimizer = class {
|
|
|
10598
10617
|
}
|
|
10599
10618
|
if (!isExactMatch) break;
|
|
10600
10619
|
}
|
|
10601
|
-
if (isIndexOrderSupported) {
|
|
10602
|
-
score += 200;
|
|
10603
|
-
}
|
|
10604
10620
|
}
|
|
10605
10621
|
return {
|
|
10606
10622
|
tree: treeTx,
|
|
@@ -10608,7 +10624,7 @@ var Optimizer = class {
|
|
|
10608
10624
|
field: primaryField,
|
|
10609
10625
|
indexName,
|
|
10610
10626
|
isFtsMatch: false,
|
|
10611
|
-
|
|
10627
|
+
selectivity,
|
|
10612
10628
|
compositeVerifyFields,
|
|
10613
10629
|
coveredFields,
|
|
10614
10630
|
isIndexOrderSupported
|
|
@@ -10616,7 +10632,7 @@ var Optimizer = class {
|
|
|
10616
10632
|
}
|
|
10617
10633
|
/**
|
|
10618
10634
|
* FTS 타입 인덱스의 선택도를 평가합니다.
|
|
10619
|
-
* FTSTermCount 통계가 있으면
|
|
10635
|
+
* FTSTermCount 통계가 있으면 실측 데이터 기반으로 선택도를 산출합니다.
|
|
10620
10636
|
*/
|
|
10621
10637
|
evaluateFTSCandidate(indexName, config, query, queryFields, treeTx) {
|
|
10622
10638
|
const field = config.fields;
|
|
@@ -10625,18 +10641,14 @@ var Optimizer = class {
|
|
|
10625
10641
|
if (!condition || typeof condition !== "object" || !("match" in condition)) return null;
|
|
10626
10642
|
const ftsConfig = this.api.indexManager.getFtsConfig(config);
|
|
10627
10643
|
const matchTokens = ftsConfig ? tokenize(condition.match, ftsConfig) : [];
|
|
10628
|
-
|
|
10629
|
-
const MIN_FTS_SCORE = 10;
|
|
10630
|
-
const DEFAULT_FTS_SCORE = 90;
|
|
10631
|
-
let score = DEFAULT_FTS_SCORE;
|
|
10644
|
+
let selectivity = SELECTIVITY.FTS_DEFAULT;
|
|
10632
10645
|
const termCountProvider = this.api.analysisManager.getProvider("fts_term_count");
|
|
10633
10646
|
if (termCountProvider && termCountProvider.hasSampleData && ftsConfig && matchTokens.length > 0) {
|
|
10634
10647
|
const strategy = ftsConfig.tokenizer === "ngram" ? `${ftsConfig.gramSize}gram` : ftsConfig.tokenizer;
|
|
10635
10648
|
const minCount = termCountProvider.getMinTokenCount(field, strategy, matchTokens);
|
|
10636
10649
|
if (minCount >= 0) {
|
|
10637
10650
|
const sampleSize = termCountProvider.getSampleSize();
|
|
10638
|
-
|
|
10639
|
-
score = Math.round(MAX_FTS_SCORE * (1 - selectivityRatio) + MIN_FTS_SCORE);
|
|
10651
|
+
selectivity = Math.min(minCount / sampleSize, 1);
|
|
10640
10652
|
}
|
|
10641
10653
|
}
|
|
10642
10654
|
return {
|
|
@@ -10646,18 +10658,36 @@ var Optimizer = class {
|
|
|
10646
10658
|
indexName,
|
|
10647
10659
|
isFtsMatch: true,
|
|
10648
10660
|
matchTokens,
|
|
10649
|
-
|
|
10661
|
+
selectivity,
|
|
10650
10662
|
compositeVerifyFields: [],
|
|
10651
10663
|
coveredFields: [field],
|
|
10652
10664
|
isIndexOrderSupported: false
|
|
10653
10665
|
};
|
|
10654
10666
|
}
|
|
10655
10667
|
/**
|
|
10656
|
-
*
|
|
10668
|
+
* 비용 계산: effectiveScanCost + sortPenalty
|
|
10669
|
+
* - effectiveScanCost: 인덱스 순서 지원 + limit 존재 시 조기 종료 이점 반영
|
|
10670
|
+
* - sortPenalty: 인메모리 정렬의 절대 문서 수 기반 비용
|
|
10671
|
+
* - hasUncoveredFilters: 드라이버가 커버하지 못하는 비-FTS 필터 존재 여부
|
|
10672
|
+
* true일 경우 topK/N 조기 종료를 적용하지 않음
|
|
10673
|
+
* (uncovered 필터가 행을 탈락시킬 수 있어 topK개 이상 스캔 필요)
|
|
10657
10674
|
*/
|
|
10658
|
-
|
|
10675
|
+
calculateCost(selectivity, isIndexOrderSupported, orderByField, N, topK, hasUncoveredFilters = false) {
|
|
10676
|
+
const effectiveScanCost = isIndexOrderSupported && isFinite(topK) && N > 0 && !hasUncoveredFilters ? Math.min(topK / N, selectivity) : selectivity;
|
|
10677
|
+
const estimatedSortDocs = selectivity * N;
|
|
10678
|
+
const sortPenalty = orderByField && !isIndexOrderSupported ? Math.min(estimatedSortDocs / SELECTIVITY.SORT_THRESHOLD, 1) * SELECTIVITY.SORT_PENALTY : 0;
|
|
10679
|
+
return effectiveScanCost + sortPenalty;
|
|
10680
|
+
}
|
|
10681
|
+
/**
|
|
10682
|
+
* 실행할 최적의 인덱스를 선택합니다. (비용 기반 최적 드라이버 선택)
|
|
10683
|
+
* cost = selectivity + sortPenalty (낮을수록 좋음)
|
|
10684
|
+
*/
|
|
10685
|
+
async getSelectivityCandidate(query, orderByField, limit = Infinity, offset = 0) {
|
|
10659
10686
|
const queryFields = new Set(Object.keys(query));
|
|
10660
10687
|
const candidates = [];
|
|
10688
|
+
const metadata = await this.api.getMetadata();
|
|
10689
|
+
const N = metadata.rowCount;
|
|
10690
|
+
const topK = isFinite(limit) ? offset + limit : Infinity;
|
|
10661
10691
|
for (const [indexName, config] of this.api.indexManager.registeredIndices) {
|
|
10662
10692
|
const tree = this.api.trees.get(indexName);
|
|
10663
10693
|
if (!tree) continue;
|
|
@@ -10671,7 +10701,22 @@ var Optimizer = class {
|
|
|
10671
10701
|
treeTx,
|
|
10672
10702
|
orderByField
|
|
10673
10703
|
);
|
|
10674
|
-
if (candidate)
|
|
10704
|
+
if (candidate) {
|
|
10705
|
+
const hasUncoveredFilters = ![...queryFields].every(
|
|
10706
|
+
(f) => candidate.coveredFields.includes(f)
|
|
10707
|
+
);
|
|
10708
|
+
candidates.push({
|
|
10709
|
+
...candidate,
|
|
10710
|
+
cost: this.calculateCost(
|
|
10711
|
+
candidate.selectivity,
|
|
10712
|
+
candidate.isIndexOrderSupported,
|
|
10713
|
+
orderByField,
|
|
10714
|
+
N,
|
|
10715
|
+
topK,
|
|
10716
|
+
hasUncoveredFilters
|
|
10717
|
+
)
|
|
10718
|
+
});
|
|
10719
|
+
}
|
|
10675
10720
|
} else if (config.type === "fts") {
|
|
10676
10721
|
const treeTx = await tree.createTransaction();
|
|
10677
10722
|
const candidate = this.evaluateFTSCandidate(
|
|
@@ -10681,7 +10726,19 @@ var Optimizer = class {
|
|
|
10681
10726
|
queryFields,
|
|
10682
10727
|
treeTx
|
|
10683
10728
|
);
|
|
10684
|
-
if (candidate)
|
|
10729
|
+
if (candidate) {
|
|
10730
|
+
candidates.push({
|
|
10731
|
+
...candidate,
|
|
10732
|
+
cost: this.calculateCost(
|
|
10733
|
+
candidate.selectivity,
|
|
10734
|
+
candidate.isIndexOrderSupported,
|
|
10735
|
+
orderByField,
|
|
10736
|
+
N,
|
|
10737
|
+
topK,
|
|
10738
|
+
true
|
|
10739
|
+
)
|
|
10740
|
+
});
|
|
10741
|
+
}
|
|
10685
10742
|
}
|
|
10686
10743
|
}
|
|
10687
10744
|
const rollback = () => {
|
|
@@ -10694,7 +10751,7 @@ var Optimizer = class {
|
|
|
10694
10751
|
return null;
|
|
10695
10752
|
}
|
|
10696
10753
|
candidates.sort((a, b) => {
|
|
10697
|
-
if (
|
|
10754
|
+
if (a.cost !== b.cost) return a.cost - b.cost;
|
|
10698
10755
|
const aConfig = this.api.indexManager.registeredIndices.get(a.indexName);
|
|
10699
10756
|
const bConfig = this.api.indexManager.registeredIndices.get(b.indexName);
|
|
10700
10757
|
const aFieldCount = aConfig ? Array.isArray(aConfig.fields) ? aConfig.fields.length : 1 : 0;
|
|
@@ -10709,8 +10766,8 @@ var Optimizer = class {
|
|
|
10709
10766
|
const candidate = nonDriverCandidates[i];
|
|
10710
10767
|
let isSubset = false;
|
|
10711
10768
|
for (let j = 0, oLen = others.length; j < oLen; j++) {
|
|
10712
|
-
const
|
|
10713
|
-
if (candidate.coveredFields.every((f) =>
|
|
10769
|
+
const better = others[j];
|
|
10770
|
+
if (candidate.coveredFields.every((f) => better.coveredFields.includes(f))) {
|
|
10714
10771
|
isSubset = true;
|
|
10715
10772
|
break;
|
|
10716
10773
|
}
|
|
@@ -10915,12 +10972,14 @@ var QueryManager = class {
|
|
|
10915
10972
|
rollback();
|
|
10916
10973
|
return new Float64Array(Array.from(keys || []));
|
|
10917
10974
|
}
|
|
10918
|
-
async getDriverKeys(query, orderBy, sortOrder = "asc") {
|
|
10975
|
+
async getDriverKeys(query, orderBy, sortOrder = "asc", limit = Infinity, offset = 0) {
|
|
10919
10976
|
const isQueryEmpty = Object.keys(query).length === 0;
|
|
10920
10977
|
const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
|
|
10921
10978
|
const selectivity = await this.optimizer.getSelectivityCandidate(
|
|
10922
10979
|
this.verboseQuery(normalizedQuery),
|
|
10923
|
-
orderBy
|
|
10980
|
+
orderBy,
|
|
10981
|
+
limit,
|
|
10982
|
+
offset
|
|
10924
10983
|
);
|
|
10925
10984
|
if (!selectivity) return null;
|
|
10926
10985
|
const { driver, others, compositeVerifyConditions, rollback } = selectivity;
|
|
@@ -10996,12 +11055,24 @@ var QueryManager = class {
|
|
|
10996
11055
|
}
|
|
10997
11056
|
return true;
|
|
10998
11057
|
}
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11058
|
+
/**
|
|
11059
|
+
* 최적화 공식: x = x * Math.sqrt(z / n)
|
|
11060
|
+
* @param currentChunkSize 현재 청크 크기
|
|
11061
|
+
* @param matchedCount 매칭된 문서 개수
|
|
11062
|
+
* @param limit 최대 문서 개수
|
|
11063
|
+
* @param chunkTotalSize 청크 내 문서 총 크기
|
|
11064
|
+
* @returns
|
|
11065
|
+
*/
|
|
11066
|
+
adjustChunkSize(currentChunkSize, matchedCount, limit, chunkTotalSize) {
|
|
11067
|
+
if (matchedCount <= 0 || chunkTotalSize <= 0) return currentChunkSize;
|
|
11068
|
+
const n = Math.max(matchedCount, 1);
|
|
11069
|
+
const z = isFinite(limit) ? limit : currentChunkSize * 10;
|
|
11070
|
+
const nextChunkSize = Math.ceil(currentChunkSize * Math.sqrt(z / n));
|
|
11071
|
+
const { smallChunkSize } = this.getFreeMemoryChunkSize();
|
|
11072
|
+
const avgDocSize = chunkTotalSize / currentChunkSize;
|
|
11073
|
+
const maxSafeChunkSize = Math.max(Math.floor(smallChunkSize / avgDocSize), 20);
|
|
11074
|
+
const finalChunkSize = Math.max(Math.min(nextChunkSize, maxSafeChunkSize), 20);
|
|
11075
|
+
return finalChunkSize;
|
|
11005
11076
|
}
|
|
11006
11077
|
async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, limit, ftsConditions, compositeVerifyConditions, others, tx) {
|
|
11007
11078
|
const verifyOthers = others.filter((o) => !o.isFtsMatch);
|
|
@@ -11017,6 +11088,7 @@ var QueryManager = class {
|
|
|
11017
11088
|
let chunk = [];
|
|
11018
11089
|
let chunkSize = 0;
|
|
11019
11090
|
let dropped = 0;
|
|
11091
|
+
let nAccumulated = 0;
|
|
11020
11092
|
const processChunk = async (pks) => {
|
|
11021
11093
|
const docs = [];
|
|
11022
11094
|
const rawResults = await this.api.selectMany(new Float64Array(pks), false, tx);
|
|
@@ -11068,8 +11140,9 @@ var QueryManager = class {
|
|
|
11068
11140
|
}
|
|
11069
11141
|
docs.push(doc);
|
|
11070
11142
|
}
|
|
11143
|
+
nAccumulated += docs.length;
|
|
11071
11144
|
if (!isReadQuotaLimited) {
|
|
11072
|
-
currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
|
|
11145
|
+
currentChunkSize = this.adjustChunkSize(currentChunkSize, nAccumulated, limit, chunkTotalSize);
|
|
11073
11146
|
}
|
|
11074
11147
|
return docs;
|
|
11075
11148
|
};
|
|
@@ -11139,12 +11212,13 @@ var QueryManager = class {
|
|
|
11139
11212
|
}
|
|
11140
11213
|
}
|
|
11141
11214
|
}
|
|
11142
|
-
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
|
|
11215
|
+
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder, limit, offset);
|
|
11143
11216
|
if (!driverResult) return;
|
|
11144
11217
|
const { keysStream, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
|
|
11145
11218
|
const initialChunkSize = self.api.options.pageSize;
|
|
11219
|
+
const isInMemorySort = !isDriverOrderByField && orderByField;
|
|
11146
11220
|
try {
|
|
11147
|
-
if (
|
|
11221
|
+
if (isInMemorySort) {
|
|
11148
11222
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
11149
11223
|
let heap = null;
|
|
11150
11224
|
if (topK !== Infinity) {
|
|
@@ -13,31 +13,41 @@ export declare class Optimizer<T extends Record<string, any>> {
|
|
|
13
13
|
readonly field: any;
|
|
14
14
|
readonly indexName: string;
|
|
15
15
|
readonly isFtsMatch: false;
|
|
16
|
-
readonly
|
|
16
|
+
readonly selectivity: number;
|
|
17
17
|
readonly compositeVerifyFields: string[];
|
|
18
18
|
readonly coveredFields: string[];
|
|
19
19
|
readonly isIndexOrderSupported: boolean;
|
|
20
20
|
} | null;
|
|
21
21
|
/**
|
|
22
22
|
* FTS 타입 인덱스의 선택도를 평가합니다.
|
|
23
|
-
* FTSTermCount 통계가 있으면
|
|
23
|
+
* FTSTermCount 통계가 있으면 실측 데이터 기반으로 선택도를 산출합니다.
|
|
24
24
|
*/
|
|
25
25
|
evaluateFTSCandidate<U extends Partial<DocumentDataplyQuery<T>>, V extends DataplyTreeValue<U>>(indexName: string, config: any, query: Partial<DocumentDataplyQuery<V>>, queryFields: Set<string>, treeTx: BPTreeAsync<string | number, V>): {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
tree: BPTreeAsync<string | number, V>;
|
|
27
|
+
condition: any;
|
|
28
|
+
field: any;
|
|
29
|
+
indexName: string;
|
|
30
|
+
isFtsMatch: boolean;
|
|
31
|
+
matchTokens: string[];
|
|
32
|
+
selectivity: number;
|
|
33
|
+
compositeVerifyFields: never[];
|
|
34
|
+
coveredFields: any[];
|
|
35
|
+
isIndexOrderSupported: boolean;
|
|
36
36
|
} | null;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* 비용 계산: effectiveScanCost + sortPenalty
|
|
39
|
+
* - effectiveScanCost: 인덱스 순서 지원 + limit 존재 시 조기 종료 이점 반영
|
|
40
|
+
* - sortPenalty: 인메모리 정렬의 절대 문서 수 기반 비용
|
|
41
|
+
* - hasUncoveredFilters: 드라이버가 커버하지 못하는 비-FTS 필터 존재 여부
|
|
42
|
+
* true일 경우 topK/N 조기 종료를 적용하지 않음
|
|
43
|
+
* (uncovered 필터가 행을 탈락시킬 수 있어 topK개 이상 스캔 필요)
|
|
44
|
+
*/
|
|
45
|
+
private calculateCost;
|
|
46
|
+
/**
|
|
47
|
+
* 실행할 최적의 인덱스를 선택합니다. (비용 기반 최적 드라이버 선택)
|
|
48
|
+
* cost = selectivity + sortPenalty (낮을수록 좋음)
|
|
39
49
|
*/
|
|
40
|
-
getSelectivityCandidate<U extends Partial<DocumentDataplyQuery<T>>, V extends DataplyTreeValue<U>>(query: Partial<DocumentDataplyQuery<V>>, orderByField?: string): Promise<{
|
|
50
|
+
getSelectivityCandidate<U extends Partial<DocumentDataplyQuery<T>>, V extends DataplyTreeValue<U>>(query: Partial<DocumentDataplyQuery<V>>, orderByField?: string, limit?: number, offset?: number): Promise<{
|
|
41
51
|
driver: ({
|
|
42
52
|
tree: BPTreeAsync<number, V>;
|
|
43
53
|
condition: Partial<DocumentDataplyCondition<U>>;
|
|
@@ -18,7 +18,7 @@ export declare class QueryManager<T extends DocumentJSON> {
|
|
|
18
18
|
private applyCandidateByFTSStream;
|
|
19
19
|
private applyCandidateStream;
|
|
20
20
|
getKeys(query: Partial<DocumentDataplyQuery<T>>, orderBy?: string, sortOrder?: 'asc' | 'desc'): Promise<Float64Array>;
|
|
21
|
-
getDriverKeys(query: Partial<DocumentDataplyQuery<T>>, orderBy?: string, sortOrder?: 'asc' | 'desc'): Promise<{
|
|
21
|
+
getDriverKeys(query: Partial<DocumentDataplyQuery<T>>, orderBy?: string, sortOrder?: 'asc' | 'desc', limit?: number, offset?: number): Promise<{
|
|
22
22
|
keysStream: AsyncIterableIterator<number>;
|
|
23
23
|
others: {
|
|
24
24
|
tree: BPTreeAsync<string | number, DataplyTreeValue<Primitive>>;
|
|
@@ -45,7 +45,15 @@ export declare class QueryManager<T extends DocumentJSON> {
|
|
|
45
45
|
condition: any;
|
|
46
46
|
}[]): boolean;
|
|
47
47
|
verifyValue(value: Primitive, condition: any): boolean;
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* 최적화 공식: x = x * Math.sqrt(z / n)
|
|
50
|
+
* @param currentChunkSize 현재 청크 크기
|
|
51
|
+
* @param matchedCount 매칭된 문서 개수
|
|
52
|
+
* @param limit 최대 문서 개수
|
|
53
|
+
* @param chunkTotalSize 청크 내 문서 총 크기
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
adjustChunkSize(currentChunkSize: number, matchedCount: number, limit: number, chunkTotalSize: number): number;
|
|
49
57
|
processChunkedKeysWithVerify(keysStream: AsyncIterableIterator<number>, startIdx: number, initialChunkSize: number, limit: number, ftsConditions: {
|
|
50
58
|
field: string;
|
|
51
59
|
matchTokens: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-dataply",
|
|
3
|
-
"version": "0.0.10-alpha.
|
|
3
|
+
"version": "0.0.10-alpha.7",
|
|
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>",
|