document-dataply 0.0.9-alpha.0 → 0.0.9-alpha.2
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 +625 -235
- package/dist/types/core/bptree/documentStrategy.d.ts +8 -3
- package/dist/types/core/document.d.ts +44 -18
- package/dist/types/core/documentAPI.d.ts +110 -23
- package/dist/types/types/index.d.ts +46 -37
- package/package.json +1 -1
- package/readme.md +69 -20
package/dist/cjs/index.js
CHANGED
|
@@ -9990,6 +9990,11 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
|
|
|
9990
9990
|
this.txContext = txContext;
|
|
9991
9991
|
this.treeKey = treeKey;
|
|
9992
9992
|
}
|
|
9993
|
+
/**
|
|
9994
|
+
* readHead에서 할당된 headPk를 캐싱하여
|
|
9995
|
+
* writeHead에서 AsyncLocalStorage 컨텍스트 유실 시에도 사용할 수 있도록 함
|
|
9996
|
+
*/
|
|
9997
|
+
cachedHeadPk = null;
|
|
9993
9998
|
async id(isLeaf) {
|
|
9994
9999
|
const tx = this.txContext.get();
|
|
9995
10000
|
const pk = await this.api.insertAsOverflow("__BPTREE_NODE_PLACEHOLDER__", false, tx);
|
|
@@ -10022,20 +10027,25 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
|
|
|
10022
10027
|
const pk = await this.api.insertAsOverflow("__BPTREE_HEAD_PLACEHOLDER__", false, tx);
|
|
10023
10028
|
metadata.indices[this.treeKey][0] = pk;
|
|
10024
10029
|
await this.api.updateDocumentInnerMetadata(metadata, tx);
|
|
10030
|
+
this.cachedHeadPk = pk;
|
|
10025
10031
|
return null;
|
|
10026
10032
|
}
|
|
10033
|
+
this.cachedHeadPk = headPk;
|
|
10027
10034
|
const row = await this.api.select(headPk, false, tx);
|
|
10028
10035
|
if (row === null || row === "" || row.startsWith("__BPTREE_")) return null;
|
|
10029
10036
|
return JSON.parse(row);
|
|
10030
10037
|
}
|
|
10031
10038
|
async writeHead(head) {
|
|
10032
10039
|
const tx = this.txContext.get();
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10040
|
+
let headPk = this.cachedHeadPk;
|
|
10041
|
+
if (headPk === null) {
|
|
10042
|
+
const metadata = await this.api.getDocumentInnerMetadata(tx);
|
|
10043
|
+
const indexInfo = metadata.indices[this.treeKey];
|
|
10044
|
+
if (!indexInfo) {
|
|
10045
|
+
throw new Error(`Index info not found for tree: ${this.treeKey}. Initialization should be handled outside.`);
|
|
10046
|
+
}
|
|
10047
|
+
headPk = indexInfo[0];
|
|
10037
10048
|
}
|
|
10038
|
-
const headPk = indexInfo[0];
|
|
10039
10049
|
const json = JSON.stringify(head);
|
|
10040
10050
|
await this.api.update(headPk, json, tx);
|
|
10041
10051
|
}
|
|
@@ -10043,22 +10053,46 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
|
|
|
10043
10053
|
|
|
10044
10054
|
// src/core/bptree/documentComparator.ts
|
|
10045
10055
|
var import_dataply2 = __toESM(require_cjs());
|
|
10056
|
+
function comparePrimitive(a, b) {
|
|
10057
|
+
if (a === b) return 0;
|
|
10058
|
+
if (a === null) return -1;
|
|
10059
|
+
if (b === null) return 1;
|
|
10060
|
+
if (typeof a !== typeof b) {
|
|
10061
|
+
const typeOrder = (v) => typeof v === "boolean" ? 0 : typeof v === "number" ? 1 : 2;
|
|
10062
|
+
return typeOrder(a) - typeOrder(b);
|
|
10063
|
+
}
|
|
10064
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
10065
|
+
return a.localeCompare(b);
|
|
10066
|
+
}
|
|
10067
|
+
return +a - +b;
|
|
10068
|
+
}
|
|
10069
|
+
function compareValue(a, b) {
|
|
10070
|
+
const aArr = Array.isArray(a);
|
|
10071
|
+
const bArr = Array.isArray(b);
|
|
10072
|
+
if (!aArr && !bArr) {
|
|
10073
|
+
return comparePrimitive(a, b);
|
|
10074
|
+
}
|
|
10075
|
+
const aList = aArr ? a : [a];
|
|
10076
|
+
const bList = bArr ? b : [b];
|
|
10077
|
+
const len = Math.min(aList.length, bList.length);
|
|
10078
|
+
for (let i = 0; i < len; i++) {
|
|
10079
|
+
const diff = comparePrimitive(aList[i], bList[i]);
|
|
10080
|
+
if (diff !== 0) return diff;
|
|
10081
|
+
}
|
|
10082
|
+
return aList.length - bList.length;
|
|
10083
|
+
}
|
|
10046
10084
|
var DocumentValueComparator = class extends import_dataply2.ValueComparator {
|
|
10047
10085
|
primaryAsc(a, b) {
|
|
10048
|
-
|
|
10049
|
-
return +a.v - +b.v;
|
|
10050
|
-
}
|
|
10051
|
-
return a.v.localeCompare(b.v);
|
|
10086
|
+
return compareValue(a.v, b.v);
|
|
10052
10087
|
}
|
|
10053
10088
|
asc(a, b) {
|
|
10054
|
-
|
|
10055
|
-
const diff2 = +a.v - +b.v;
|
|
10056
|
-
return diff2 === 0 ? a.k - b.k : diff2;
|
|
10057
|
-
}
|
|
10058
|
-
const diff = a.v.localeCompare(b.v);
|
|
10089
|
+
const diff = compareValue(a.v, b.v);
|
|
10059
10090
|
return diff === 0 ? a.k - b.k : diff;
|
|
10060
10091
|
}
|
|
10061
10092
|
match(value) {
|
|
10093
|
+
if (Array.isArray(value.v)) {
|
|
10094
|
+
return value.v[0] + "";
|
|
10095
|
+
}
|
|
10062
10096
|
return value.v + "";
|
|
10063
10097
|
}
|
|
10064
10098
|
};
|
|
@@ -10171,7 +10205,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10171
10205
|
comparator = new DocumentValueComparator();
|
|
10172
10206
|
pendingBackfillFields = [];
|
|
10173
10207
|
lock;
|
|
10208
|
+
_initialized = false;
|
|
10174
10209
|
indexedFields;
|
|
10210
|
+
/**
|
|
10211
|
+
* Registered indices via createIndex() (before init)
|
|
10212
|
+
* Key: index name, Value: index configuration
|
|
10213
|
+
*/
|
|
10214
|
+
pendingCreateIndices = /* @__PURE__ */ new Map();
|
|
10215
|
+
/**
|
|
10216
|
+
* Resolved index configurations after init.
|
|
10217
|
+
* Key: index name, Value: index config (from metadata)
|
|
10218
|
+
*/
|
|
10219
|
+
registeredIndices = /* @__PURE__ */ new Map();
|
|
10220
|
+
/**
|
|
10221
|
+
* Maps field name → index names that cover this field.
|
|
10222
|
+
* Used for query resolution.
|
|
10223
|
+
*/
|
|
10224
|
+
fieldToIndices = /* @__PURE__ */ new Map();
|
|
10175
10225
|
operatorConverters = {
|
|
10176
10226
|
equal: "primaryEqual",
|
|
10177
10227
|
notEqual: "primaryNotEqual",
|
|
@@ -10187,11 +10237,6 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10187
10237
|
this.trees = /* @__PURE__ */ new Map();
|
|
10188
10238
|
this.lock = new import_dataply3.Ryoiki();
|
|
10189
10239
|
this.indexedFields = /* @__PURE__ */ new Set(["_id"]);
|
|
10190
|
-
if (options?.indices) {
|
|
10191
|
-
for (const field of Object.keys(options.indices)) {
|
|
10192
|
-
this.indexedFields.add(field);
|
|
10193
|
-
}
|
|
10194
|
-
}
|
|
10195
10240
|
this.hook.onceAfter("init", async (tx, isNewlyCreated) => {
|
|
10196
10241
|
if (isNewlyCreated) {
|
|
10197
10242
|
await this.initializeDocumentFile(tx);
|
|
@@ -10200,31 +10245,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10200
10245
|
throw new Error("Document metadata verification failed");
|
|
10201
10246
|
}
|
|
10202
10247
|
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
10203
|
-
const
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10248
|
+
const targetIndices = /* @__PURE__ */ new Map();
|
|
10249
|
+
targetIndices.set("_id", { type: "btree", fields: ["_id"] });
|
|
10250
|
+
for (const [name, option] of this.pendingCreateIndices) {
|
|
10251
|
+
const config = this.toIndexMetaConfig(option);
|
|
10252
|
+
targetIndices.set(name, config);
|
|
10253
|
+
}
|
|
10208
10254
|
const backfillTargets = [];
|
|
10209
10255
|
let isMetadataChanged = false;
|
|
10210
|
-
for (const
|
|
10211
|
-
const
|
|
10212
|
-
const existingIndex = metadata.indices[field];
|
|
10256
|
+
for (const [indexName, config] of targetIndices) {
|
|
10257
|
+
const existingIndex = metadata.indices[indexName];
|
|
10213
10258
|
if (!existingIndex) {
|
|
10214
|
-
metadata.indices[
|
|
10259
|
+
metadata.indices[indexName] = [-1, config];
|
|
10215
10260
|
isMetadataChanged = true;
|
|
10216
|
-
if (
|
|
10217
|
-
backfillTargets.push(
|
|
10261
|
+
if (!isNewlyCreated) {
|
|
10262
|
+
backfillTargets.push(indexName);
|
|
10218
10263
|
}
|
|
10219
10264
|
} else {
|
|
10220
|
-
const [_pk,
|
|
10221
|
-
if (
|
|
10222
|
-
metadata.indices[
|
|
10223
|
-
isMetadataChanged = true;
|
|
10224
|
-
backfillTargets.push(field);
|
|
10225
|
-
} else if (!isBackfillEnabled && isMetaBackfillEnabled) {
|
|
10226
|
-
metadata.indices[field][1] = false;
|
|
10265
|
+
const [_pk, existingConfig] = existingIndex;
|
|
10266
|
+
if (JSON.stringify(existingConfig) !== JSON.stringify(config)) {
|
|
10267
|
+
metadata.indices[indexName] = [_pk, config];
|
|
10227
10268
|
isMetadataChanged = true;
|
|
10269
|
+
if (!isNewlyCreated) {
|
|
10270
|
+
backfillTargets.push(indexName);
|
|
10271
|
+
}
|
|
10228
10272
|
}
|
|
10229
10273
|
}
|
|
10230
10274
|
}
|
|
@@ -10232,25 +10276,220 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10232
10276
|
await this.updateDocumentInnerMetadata(metadata, tx);
|
|
10233
10277
|
}
|
|
10234
10278
|
this.indices = metadata.indices;
|
|
10235
|
-
|
|
10236
|
-
|
|
10279
|
+
this.registeredIndices = /* @__PURE__ */ new Map();
|
|
10280
|
+
this.fieldToIndices = /* @__PURE__ */ new Map();
|
|
10281
|
+
for (const [indexName, config] of targetIndices) {
|
|
10282
|
+
this.registeredIndices.set(indexName, config);
|
|
10283
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10284
|
+
for (const field of fields) {
|
|
10285
|
+
this.indexedFields.add(field);
|
|
10286
|
+
if (!this.fieldToIndices.has(field)) {
|
|
10287
|
+
this.fieldToIndices.set(field, []);
|
|
10288
|
+
}
|
|
10289
|
+
this.fieldToIndices.get(field).push(indexName);
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
for (const indexName of targetIndices.keys()) {
|
|
10293
|
+
if (metadata.indices[indexName]) {
|
|
10237
10294
|
const tree = new import_dataply3.BPTreeAsync(
|
|
10238
10295
|
new DocumentSerializeStrategyAsync(
|
|
10239
10296
|
this.rowTableEngine.order,
|
|
10240
10297
|
this,
|
|
10241
10298
|
this.txContext,
|
|
10242
|
-
|
|
10299
|
+
indexName
|
|
10243
10300
|
),
|
|
10244
10301
|
this.comparator
|
|
10245
10302
|
);
|
|
10246
10303
|
await tree.init();
|
|
10247
|
-
this.trees.set(
|
|
10304
|
+
this.trees.set(indexName, tree);
|
|
10248
10305
|
}
|
|
10249
10306
|
}
|
|
10250
10307
|
this.pendingBackfillFields = backfillTargets;
|
|
10308
|
+
this._initialized = true;
|
|
10251
10309
|
return tx;
|
|
10252
10310
|
});
|
|
10253
10311
|
}
|
|
10312
|
+
/**
|
|
10313
|
+
* Whether the document database has been initialized.
|
|
10314
|
+
*/
|
|
10315
|
+
get isDocInitialized() {
|
|
10316
|
+
return this._initialized;
|
|
10317
|
+
}
|
|
10318
|
+
/**
|
|
10319
|
+
* Register an index. If called before init(), queues it for processing during init.
|
|
10320
|
+
* If called after init(), immediately creates the tree, updates metadata, and backfills.
|
|
10321
|
+
*/
|
|
10322
|
+
async registerIndex(name, option, tx) {
|
|
10323
|
+
if (!this._initialized) {
|
|
10324
|
+
this.pendingCreateIndices.set(name, option);
|
|
10325
|
+
return;
|
|
10326
|
+
}
|
|
10327
|
+
await this.registerIndexRuntime(name, option, tx);
|
|
10328
|
+
}
|
|
10329
|
+
/**
|
|
10330
|
+
* Register an index at runtime (after init).
|
|
10331
|
+
* Creates the tree, updates metadata, and backfills existing data.
|
|
10332
|
+
*/
|
|
10333
|
+
async registerIndexRuntime(name, option, tx) {
|
|
10334
|
+
const config = this.toIndexMetaConfig(option);
|
|
10335
|
+
if (this.registeredIndices.has(name)) {
|
|
10336
|
+
const existing = this.registeredIndices.get(name);
|
|
10337
|
+
if (JSON.stringify(existing) === JSON.stringify(config)) return;
|
|
10338
|
+
}
|
|
10339
|
+
await this.runWithDefault(async (tx2) => {
|
|
10340
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10341
|
+
metadata.indices[name] = [-1, config];
|
|
10342
|
+
await this.updateDocumentInnerMetadata(metadata, tx2);
|
|
10343
|
+
this.indices = metadata.indices;
|
|
10344
|
+
this.registeredIndices.set(name, config);
|
|
10345
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10346
|
+
for (const field of fields) {
|
|
10347
|
+
this.indexedFields.add(field);
|
|
10348
|
+
if (!this.fieldToIndices.has(field)) {
|
|
10349
|
+
this.fieldToIndices.set(field, []);
|
|
10350
|
+
}
|
|
10351
|
+
this.fieldToIndices.get(field).push(name);
|
|
10352
|
+
}
|
|
10353
|
+
const tree = new import_dataply3.BPTreeAsync(
|
|
10354
|
+
new DocumentSerializeStrategyAsync(
|
|
10355
|
+
this.rowTableEngine.order,
|
|
10356
|
+
this,
|
|
10357
|
+
this.txContext,
|
|
10358
|
+
name
|
|
10359
|
+
),
|
|
10360
|
+
this.comparator
|
|
10361
|
+
);
|
|
10362
|
+
await tree.init();
|
|
10363
|
+
this.trees.set(name, tree);
|
|
10364
|
+
if (metadata.lastId > 0) {
|
|
10365
|
+
this.pendingBackfillFields = [name];
|
|
10366
|
+
await this.backfillIndices(tx2);
|
|
10367
|
+
}
|
|
10368
|
+
}, tx);
|
|
10369
|
+
}
|
|
10370
|
+
/**
|
|
10371
|
+
* Drop (remove) a named index.
|
|
10372
|
+
* Removes the index from metadata, in-memory maps, and trees.
|
|
10373
|
+
* The '_id' index cannot be dropped.
|
|
10374
|
+
* @param name The name of the index to drop
|
|
10375
|
+
*/
|
|
10376
|
+
async dropIndex(name, tx) {
|
|
10377
|
+
if (name === "_id") {
|
|
10378
|
+
throw new Error("Cannot drop the _id index");
|
|
10379
|
+
}
|
|
10380
|
+
if (!this._initialized) {
|
|
10381
|
+
this.pendingCreateIndices.delete(name);
|
|
10382
|
+
return;
|
|
10383
|
+
}
|
|
10384
|
+
if (!this.registeredIndices.has(name)) {
|
|
10385
|
+
throw new Error(`Index '${name}' does not exist`);
|
|
10386
|
+
}
|
|
10387
|
+
await this.runWithDefault(async (tx2) => {
|
|
10388
|
+
const config = this.registeredIndices.get(name);
|
|
10389
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10390
|
+
delete metadata.indices[name];
|
|
10391
|
+
await this.updateDocumentInnerMetadata(metadata, tx2);
|
|
10392
|
+
this.indices = metadata.indices;
|
|
10393
|
+
this.registeredIndices.delete(name);
|
|
10394
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10395
|
+
for (const field of fields) {
|
|
10396
|
+
const indexNames = this.fieldToIndices.get(field);
|
|
10397
|
+
if (indexNames) {
|
|
10398
|
+
const filtered = indexNames.filter((n) => n !== name);
|
|
10399
|
+
if (filtered.length === 0) {
|
|
10400
|
+
this.fieldToIndices.delete(field);
|
|
10401
|
+
if (field !== "_id") {
|
|
10402
|
+
this.indexedFields.delete(field);
|
|
10403
|
+
}
|
|
10404
|
+
} else {
|
|
10405
|
+
this.fieldToIndices.set(field, filtered);
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
this.trees.delete(name);
|
|
10410
|
+
}, tx);
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* Convert CreateIndexOption to IndexMetaConfig for metadata storage.
|
|
10414
|
+
*/
|
|
10415
|
+
toIndexMetaConfig(option) {
|
|
10416
|
+
if (option.type === "btree") {
|
|
10417
|
+
return {
|
|
10418
|
+
type: "btree",
|
|
10419
|
+
fields: option.fields
|
|
10420
|
+
};
|
|
10421
|
+
}
|
|
10422
|
+
if (option.type === "fts") {
|
|
10423
|
+
if (option.tokenizer === "ngram") {
|
|
10424
|
+
return {
|
|
10425
|
+
type: "fts",
|
|
10426
|
+
fields: option.fields,
|
|
10427
|
+
tokenizer: "ngram",
|
|
10428
|
+
gramSize: option.ngram
|
|
10429
|
+
};
|
|
10430
|
+
}
|
|
10431
|
+
return {
|
|
10432
|
+
type: "fts",
|
|
10433
|
+
fields: option.fields,
|
|
10434
|
+
tokenizer: "whitespace"
|
|
10435
|
+
};
|
|
10436
|
+
}
|
|
10437
|
+
throw new Error(`Unknown index type: ${option.type}`);
|
|
10438
|
+
}
|
|
10439
|
+
/**
|
|
10440
|
+
* Get all field names from an IndexMetaConfig.
|
|
10441
|
+
*/
|
|
10442
|
+
getFieldsFromConfig(config) {
|
|
10443
|
+
if (config.type === "btree") {
|
|
10444
|
+
return config.fields;
|
|
10445
|
+
}
|
|
10446
|
+
if (config.type === "fts") {
|
|
10447
|
+
return [config.fields];
|
|
10448
|
+
}
|
|
10449
|
+
return [];
|
|
10450
|
+
}
|
|
10451
|
+
/**
|
|
10452
|
+
* Get the primary field of an index (the field used as tree key).
|
|
10453
|
+
* For btree: first field in fields array.
|
|
10454
|
+
* For fts: the single field.
|
|
10455
|
+
*/
|
|
10456
|
+
getPrimaryField(config) {
|
|
10457
|
+
if (config.type === "btree") {
|
|
10458
|
+
return config.fields[0];
|
|
10459
|
+
}
|
|
10460
|
+
return config.fields;
|
|
10461
|
+
}
|
|
10462
|
+
/**
|
|
10463
|
+
* 인덱스 config에 따라 B+tree에 저장할 v 값을 생성합니다.
|
|
10464
|
+
* - 단일 필드 btree: Primitive (단일 값)
|
|
10465
|
+
* - 복합 필드 btree: Primitive[] (필드 순서대로 배열)
|
|
10466
|
+
* - fts: 별도 처리 (이 메서드 사용 안 함)
|
|
10467
|
+
* @returns undefined면 해당 문서에 필수 필드가 없으므로 인덱싱 스킵
|
|
10468
|
+
*/
|
|
10469
|
+
getIndexValue(config, flatDoc) {
|
|
10470
|
+
if (config.type !== "btree") return void 0;
|
|
10471
|
+
if (config.fields.length === 1) {
|
|
10472
|
+
const v = flatDoc[config.fields[0]];
|
|
10473
|
+
return v === void 0 ? void 0 : v;
|
|
10474
|
+
}
|
|
10475
|
+
const values = [];
|
|
10476
|
+
for (let i = 0, len = config.fields.length; i < len; i++) {
|
|
10477
|
+
const v = flatDoc[config.fields[i]];
|
|
10478
|
+
if (v === void 0) return void 0;
|
|
10479
|
+
values.push(v);
|
|
10480
|
+
}
|
|
10481
|
+
return values;
|
|
10482
|
+
}
|
|
10483
|
+
/**
|
|
10484
|
+
* Get FTSConfig from IndexMetaConfig (for tokenizer compatibility).
|
|
10485
|
+
*/
|
|
10486
|
+
getFtsConfig(config) {
|
|
10487
|
+
if (config.type !== "fts") return null;
|
|
10488
|
+
if (config.tokenizer === "ngram") {
|
|
10489
|
+
return { type: "fts", tokenizer: "ngram", gramSize: config.gramSize };
|
|
10490
|
+
}
|
|
10491
|
+
return { type: "fts", tokenizer: "whitespace" };
|
|
10492
|
+
}
|
|
10254
10493
|
async getDocument(pk, tx) {
|
|
10255
10494
|
return this.runWithDefault(async (tx2) => {
|
|
10256
10495
|
const row = await this.select(pk, false, tx2);
|
|
@@ -10279,10 +10518,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10279
10518
|
});
|
|
10280
10519
|
}
|
|
10281
10520
|
/**
|
|
10282
|
-
* Backfill indices for
|
|
10283
|
-
* This method should be called after `init()
|
|
10284
|
-
*
|
|
10285
|
-
*
|
|
10521
|
+
* Backfill indices for newly created indices after data was inserted.
|
|
10522
|
+
* This method should be called after `init()`.
|
|
10523
|
+
*
|
|
10286
10524
|
* @returns Number of documents that were backfilled
|
|
10287
10525
|
*/
|
|
10288
10526
|
async backfillIndices(tx) {
|
|
@@ -10295,12 +10533,12 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10295
10533
|
if (metadata.lastId === 0) {
|
|
10296
10534
|
return 0;
|
|
10297
10535
|
}
|
|
10298
|
-
const
|
|
10299
|
-
const
|
|
10300
|
-
for (const
|
|
10301
|
-
const tree = this.trees.get(
|
|
10302
|
-
if (tree &&
|
|
10303
|
-
|
|
10536
|
+
const indexTxMap = {};
|
|
10537
|
+
const indexEntryMap = /* @__PURE__ */ new Map();
|
|
10538
|
+
for (const indexName of backfillTargets) {
|
|
10539
|
+
const tree = this.trees.get(indexName);
|
|
10540
|
+
if (tree && indexName !== "_id") {
|
|
10541
|
+
indexTxMap[indexName] = await tree.createTransaction();
|
|
10304
10542
|
}
|
|
10305
10543
|
}
|
|
10306
10544
|
let backfilledCount = 0;
|
|
@@ -10315,35 +10553,44 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10315
10553
|
const doc = await this.getDocument(k, tx2);
|
|
10316
10554
|
if (!doc) continue;
|
|
10317
10555
|
const flatDoc = this.flattenDocument(doc);
|
|
10318
|
-
for (const
|
|
10319
|
-
if (!(
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10556
|
+
for (const indexName of backfillTargets) {
|
|
10557
|
+
if (!(indexName in indexTxMap)) continue;
|
|
10558
|
+
const config = this.registeredIndices.get(indexName);
|
|
10559
|
+
if (!config) continue;
|
|
10560
|
+
const btx = indexTxMap[indexName];
|
|
10561
|
+
if (config.type === "fts") {
|
|
10562
|
+
const primaryField = this.getPrimaryField(config);
|
|
10563
|
+
const v = flatDoc[primaryField];
|
|
10564
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
10565
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10566
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
10567
|
+
const batchInsertData = [];
|
|
10568
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
10569
|
+
const token = tokens[i];
|
|
10570
|
+
const keyToInsert = this.getTokenKey(k, token);
|
|
10571
|
+
const entry = { k, v: token };
|
|
10572
|
+
batchInsertData.push([keyToInsert, entry]);
|
|
10573
|
+
if (!indexEntryMap.has(btx)) {
|
|
10574
|
+
indexEntryMap.set(btx, []);
|
|
10575
|
+
}
|
|
10576
|
+
indexEntryMap.get(btx).push({ k: keyToInsert, v: entry });
|
|
10577
|
+
}
|
|
10578
|
+
await btx.batchInsert(batchInsertData);
|
|
10579
|
+
} else {
|
|
10580
|
+
const indexVal = this.getIndexValue(config, flatDoc);
|
|
10581
|
+
if (indexVal === void 0) continue;
|
|
10582
|
+
const entry = { k, v: indexVal };
|
|
10583
|
+
const batchInsertData = [[k, entry]];
|
|
10584
|
+
if (!indexEntryMap.has(btx)) {
|
|
10585
|
+
indexEntryMap.set(btx, []);
|
|
10339
10586
|
}
|
|
10340
|
-
|
|
10587
|
+
indexEntryMap.get(btx).push(entry);
|
|
10588
|
+
await btx.batchInsert(batchInsertData);
|
|
10341
10589
|
}
|
|
10342
|
-
await btx.batchInsert(batchInsertData);
|
|
10343
10590
|
}
|
|
10344
10591
|
backfilledCount++;
|
|
10345
10592
|
}
|
|
10346
|
-
const btxs = Object.values(
|
|
10593
|
+
const btxs = Object.values(indexTxMap);
|
|
10347
10594
|
const success = [];
|
|
10348
10595
|
try {
|
|
10349
10596
|
for (const btx of btxs) {
|
|
@@ -10355,7 +10602,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10355
10602
|
await btx.rollback();
|
|
10356
10603
|
}
|
|
10357
10604
|
for (const btx of success) {
|
|
10358
|
-
const entries =
|
|
10605
|
+
const entries = indexEntryMap.get(btx);
|
|
10359
10606
|
if (!entries) continue;
|
|
10360
10607
|
for (const entry of entries) {
|
|
10361
10608
|
await btx.delete(entry.k, entry);
|
|
@@ -10374,6 +10621,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10374
10621
|
createdAt: Date.now(),
|
|
10375
10622
|
updatedAt: Date.now(),
|
|
10376
10623
|
lastId: 0,
|
|
10624
|
+
schemeVersion: 0,
|
|
10377
10625
|
indices
|
|
10378
10626
|
};
|
|
10379
10627
|
}
|
|
@@ -10383,7 +10631,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10383
10631
|
throw new Error("Document metadata already exists");
|
|
10384
10632
|
}
|
|
10385
10633
|
const metaObj = this.createDocumentInnerMetadata({
|
|
10386
|
-
_id: [-1,
|
|
10634
|
+
_id: [-1, { type: "btree", fields: ["_id"] }]
|
|
10387
10635
|
});
|
|
10388
10636
|
await this.insertAsOverflow(JSON.stringify(metaObj), false, tx);
|
|
10389
10637
|
}
|
|
@@ -10408,18 +10656,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10408
10656
|
}
|
|
10409
10657
|
/**
|
|
10410
10658
|
* returns flattened document
|
|
10411
|
-
* @param document
|
|
10412
|
-
* @returns
|
|
10659
|
+
* @param document
|
|
10660
|
+
* @returns
|
|
10413
10661
|
*/
|
|
10414
10662
|
flattenDocument(document) {
|
|
10415
10663
|
return this.flatten(document, "", {});
|
|
10416
10664
|
}
|
|
10417
10665
|
async getDocumentMetadata(tx) {
|
|
10418
10666
|
const metadata = await this.getMetadata(tx);
|
|
10667
|
+
const innerMetadata = await this.getDocumentInnerMetadata(tx);
|
|
10668
|
+
const indices = [];
|
|
10669
|
+
for (const name of this.registeredIndices.keys()) {
|
|
10670
|
+
if (name !== "_id") {
|
|
10671
|
+
indices.push(name);
|
|
10672
|
+
}
|
|
10673
|
+
}
|
|
10419
10674
|
return {
|
|
10420
10675
|
pageSize: metadata.pageSize,
|
|
10421
10676
|
pageCount: metadata.pageCount,
|
|
10422
|
-
rowCount: metadata.rowCount
|
|
10677
|
+
rowCount: metadata.rowCount,
|
|
10678
|
+
indices,
|
|
10679
|
+
schemeVersion: innerMetadata.schemeVersion ?? 0
|
|
10423
10680
|
};
|
|
10424
10681
|
}
|
|
10425
10682
|
async getDocumentInnerMetadata(tx) {
|
|
@@ -10432,6 +10689,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10432
10689
|
async updateDocumentInnerMetadata(metadata, tx) {
|
|
10433
10690
|
await this.update(1, JSON.stringify(metadata), tx);
|
|
10434
10691
|
}
|
|
10692
|
+
/**
|
|
10693
|
+
* Run a migration if the current schemeVersion is lower than the target version.
|
|
10694
|
+
* After the callback completes, schemeVersion is updated to the target version.
|
|
10695
|
+
* @param version The target scheme version
|
|
10696
|
+
* @param callback The migration callback
|
|
10697
|
+
* @param tx Optional transaction
|
|
10698
|
+
*/
|
|
10699
|
+
async migration(version, callback, tx) {
|
|
10700
|
+
await this.runWithDefault(async (tx2) => {
|
|
10701
|
+
const innerMetadata = await this.getDocumentInnerMetadata(tx2);
|
|
10702
|
+
const currentVersion = innerMetadata.schemeVersion ?? 0;
|
|
10703
|
+
if (currentVersion < version) {
|
|
10704
|
+
await callback(tx2);
|
|
10705
|
+
innerMetadata.schemeVersion = version;
|
|
10706
|
+
innerMetadata.updatedAt = Date.now();
|
|
10707
|
+
await this.updateDocumentInnerMetadata(innerMetadata, tx2);
|
|
10708
|
+
}
|
|
10709
|
+
}, tx);
|
|
10710
|
+
}
|
|
10435
10711
|
/**
|
|
10436
10712
|
* Transforms a query object into a verbose query object
|
|
10437
10713
|
* @param query The query object to transform
|
|
@@ -10470,33 +10746,74 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10470
10746
|
return result;
|
|
10471
10747
|
}
|
|
10472
10748
|
/**
|
|
10473
|
-
*
|
|
10474
|
-
*
|
|
10749
|
+
* Choose the best index (driver) for the given query.
|
|
10750
|
+
* Scores each index based on field coverage and condition type.
|
|
10751
|
+
*
|
|
10752
|
+
* @param query The verbose query conditions
|
|
10475
10753
|
* @param orderByField Optional field name for orderBy optimization
|
|
10476
10754
|
* @returns Driver and other candidates for query execution
|
|
10477
10755
|
*/
|
|
10478
10756
|
async getSelectivityCandidate(query, orderByField) {
|
|
10757
|
+
const queryFields = new Set(Object.keys(query));
|
|
10479
10758
|
const candidates = [];
|
|
10480
|
-
const
|
|
10481
|
-
|
|
10482
|
-
const tree = this.trees.get(field);
|
|
10759
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10760
|
+
const tree = this.trees.get(indexName);
|
|
10483
10761
|
if (!tree) continue;
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10762
|
+
if (config.type === "btree") {
|
|
10763
|
+
const primaryField = config.fields[0];
|
|
10764
|
+
if (!queryFields.has(primaryField)) continue;
|
|
10765
|
+
const condition = query[primaryField];
|
|
10766
|
+
const treeTx = await tree.createTransaction();
|
|
10767
|
+
let score = 0;
|
|
10768
|
+
const coveredFields = config.fields.filter((f) => queryFields.has(f));
|
|
10769
|
+
score += coveredFields.length;
|
|
10770
|
+
if (condition) {
|
|
10771
|
+
if (typeof condition !== "object" || condition === null) {
|
|
10772
|
+
score += 100;
|
|
10773
|
+
} else if ("primaryEqual" in condition || "equal" in condition) {
|
|
10774
|
+
score += 100;
|
|
10775
|
+
} else if ("primaryGte" in condition || "primaryLte" in condition || "primaryGt" in condition || "primaryLt" in condition || "gte" in condition || "lte" in condition || "gt" in condition || "lt" in condition) {
|
|
10776
|
+
score += 50;
|
|
10777
|
+
} else if ("primaryOr" in condition || "or" in condition) {
|
|
10778
|
+
score += 20;
|
|
10779
|
+
} else if ("like" in condition) {
|
|
10780
|
+
score += 15;
|
|
10781
|
+
} else {
|
|
10782
|
+
score += 10;
|
|
10783
|
+
}
|
|
10784
|
+
}
|
|
10785
|
+
if (orderByField && primaryField === orderByField) {
|
|
10786
|
+
score += 200;
|
|
10787
|
+
}
|
|
10788
|
+
const compositeVerifyFields = coveredFields.filter((f) => f !== primaryField);
|
|
10789
|
+
candidates.push({
|
|
10790
|
+
tree: treeTx,
|
|
10791
|
+
condition,
|
|
10792
|
+
field: primaryField,
|
|
10793
|
+
indexName,
|
|
10794
|
+
isFtsMatch: false,
|
|
10795
|
+
score,
|
|
10796
|
+
compositeVerifyFields
|
|
10797
|
+
});
|
|
10798
|
+
} else if (config.type === "fts") {
|
|
10799
|
+
const field = config.fields;
|
|
10800
|
+
if (!queryFields.has(field)) continue;
|
|
10801
|
+
const condition = query[field];
|
|
10802
|
+
if (!condition || typeof condition !== "object" || !("match" in condition)) continue;
|
|
10803
|
+
const treeTx = await tree.createTransaction();
|
|
10804
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10805
|
+
const matchTokens = ftsConfig ? tokenize(condition.match, ftsConfig) : [];
|
|
10806
|
+
candidates.push({
|
|
10807
|
+
tree: treeTx,
|
|
10808
|
+
condition,
|
|
10809
|
+
field,
|
|
10810
|
+
indexName,
|
|
10811
|
+
isFtsMatch: true,
|
|
10812
|
+
matchTokens,
|
|
10813
|
+
score: 90,
|
|
10814
|
+
compositeVerifyFields: []
|
|
10815
|
+
});
|
|
10816
|
+
}
|
|
10500
10817
|
}
|
|
10501
10818
|
const rollback = () => {
|
|
10502
10819
|
for (const { tree } of candidates) {
|
|
@@ -10507,41 +10824,19 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10507
10824
|
rollback();
|
|
10508
10825
|
return null;
|
|
10509
10826
|
}
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
};
|
|
10518
|
-
}
|
|
10519
|
-
}
|
|
10520
|
-
const ftsCandidate = candidates.find(
|
|
10521
|
-
(c) => c.isFtsMatch && c.matchTokens && c.matchTokens.length > 0
|
|
10522
|
-
);
|
|
10523
|
-
if (ftsCandidate) {
|
|
10524
|
-
const hasHigherPriority = candidates.some((c) => {
|
|
10525
|
-
if (c === ftsCandidate) return false;
|
|
10526
|
-
const cond = c.condition;
|
|
10527
|
-
return "equal" in cond || "primaryEqual" in cond;
|
|
10528
|
-
});
|
|
10529
|
-
if (!hasHigherPriority) {
|
|
10530
|
-
return {
|
|
10531
|
-
driver: ftsCandidate,
|
|
10532
|
-
others: candidates.filter((c) => c !== ftsCandidate),
|
|
10533
|
-
rollback
|
|
10534
|
-
};
|
|
10827
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
10828
|
+
const driver = candidates[0];
|
|
10829
|
+
const others = candidates.slice(1);
|
|
10830
|
+
const compositeVerifyConditions = [];
|
|
10831
|
+
for (const field of driver.compositeVerifyFields) {
|
|
10832
|
+
if (query[field]) {
|
|
10833
|
+
compositeVerifyConditions.push({ field, condition: query[field] });
|
|
10535
10834
|
}
|
|
10536
10835
|
}
|
|
10537
|
-
let res = import_dataply3.BPTreeAsync.ChooseDriver(candidates);
|
|
10538
|
-
if (!res && candidates.length > 0) {
|
|
10539
|
-
res = candidates[0];
|
|
10540
|
-
}
|
|
10541
|
-
if (!res) return null;
|
|
10542
10836
|
return {
|
|
10543
|
-
driver
|
|
10544
|
-
others
|
|
10837
|
+
driver,
|
|
10838
|
+
others,
|
|
10839
|
+
compositeVerifyConditions,
|
|
10545
10840
|
rollback
|
|
10546
10841
|
};
|
|
10547
10842
|
}
|
|
@@ -10634,7 +10929,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10634
10929
|
orderBy
|
|
10635
10930
|
);
|
|
10636
10931
|
if (!selectivity) return null;
|
|
10637
|
-
const { driver, others, rollback } = selectivity;
|
|
10932
|
+
const { driver, others, compositeVerifyConditions, rollback } = selectivity;
|
|
10638
10933
|
const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
|
|
10639
10934
|
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10640
10935
|
let keys;
|
|
@@ -10651,6 +10946,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10651
10946
|
return {
|
|
10652
10947
|
keys: new Float64Array(Array.from(keys)),
|
|
10653
10948
|
others,
|
|
10949
|
+
compositeVerifyConditions,
|
|
10950
|
+
isDriverOrderByField: useIndexOrder,
|
|
10654
10951
|
rollback
|
|
10655
10952
|
};
|
|
10656
10953
|
}
|
|
@@ -10677,25 +10974,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10677
10974
|
async insertSingleDocument(document, tx) {
|
|
10678
10975
|
return this.writeLock(() => this.runWithDefault(async (tx2) => {
|
|
10679
10976
|
const { pk: dpk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10680
|
-
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10681
10977
|
const flattenDocument = this.flattenDocument(dataplyDocument);
|
|
10682
|
-
for (const
|
|
10683
|
-
const tree = this.trees.get(
|
|
10978
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10979
|
+
const tree = this.trees.get(indexName);
|
|
10684
10980
|
if (!tree) continue;
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
tokens = tokenize(v,
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
if (error) {
|
|
10697
|
-
throw error;
|
|
10981
|
+
if (config.type === "fts") {
|
|
10982
|
+
const primaryField = this.getPrimaryField(config);
|
|
10983
|
+
const v = flattenDocument[primaryField];
|
|
10984
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
10985
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10986
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
10987
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
10988
|
+
const token = tokens[i];
|
|
10989
|
+
const keyToInsert = this.getTokenKey(dpk, token);
|
|
10990
|
+
const [error] = await catchPromise(tree.insert(keyToInsert, { k: dpk, v: token }));
|
|
10991
|
+
if (error) throw error;
|
|
10698
10992
|
}
|
|
10993
|
+
} else {
|
|
10994
|
+
const indexVal = this.getIndexValue(config, flattenDocument);
|
|
10995
|
+
if (indexVal === void 0) continue;
|
|
10996
|
+
const [error] = await catchPromise(tree.insert(dpk, { k: dpk, v: indexVal }));
|
|
10997
|
+
if (error) throw error;
|
|
10699
10998
|
}
|
|
10700
10999
|
}
|
|
10701
11000
|
return dataplyDocument._id;
|
|
@@ -10731,23 +11030,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10731
11030
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
10732
11031
|
flattenedData[i].pk = pks[i];
|
|
10733
11032
|
}
|
|
10734
|
-
for (const [
|
|
11033
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
11034
|
+
const tree = this.trees.get(indexName);
|
|
11035
|
+
if (!tree) continue;
|
|
10735
11036
|
const treeTx = await tree.createTransaction();
|
|
10736
|
-
const indexConfig = metadata.indices[field]?.[1];
|
|
10737
11037
|
const batchInsertData = [];
|
|
10738
|
-
|
|
10739
|
-
const
|
|
10740
|
-
const
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
tokens = tokenize(v,
|
|
11038
|
+
if (config.type === "fts") {
|
|
11039
|
+
const primaryField = this.getPrimaryField(config);
|
|
11040
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11041
|
+
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
11042
|
+
const item = flattenedData[i];
|
|
11043
|
+
const v = item.data[primaryField];
|
|
11044
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
11045
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
11046
|
+
for (let j = 0, tLen = tokens.length; j < tLen; j++) {
|
|
11047
|
+
const token = tokens[j];
|
|
11048
|
+
batchInsertData.push([this.getTokenKey(item.pk, token), { k: item.pk, v: token }]);
|
|
11049
|
+
}
|
|
10746
11050
|
}
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
const
|
|
10750
|
-
|
|
11051
|
+
} else {
|
|
11052
|
+
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
11053
|
+
const item = flattenedData[i];
|
|
11054
|
+
const indexVal = this.getIndexValue(config, item.data);
|
|
11055
|
+
if (indexVal === void 0) continue;
|
|
11056
|
+
batchInsertData.push([item.pk, { k: item.pk, v: indexVal }]);
|
|
10751
11057
|
}
|
|
10752
11058
|
}
|
|
10753
11059
|
const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
|
|
@@ -10773,8 +11079,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10773
11079
|
const pks = await this.getKeys(query);
|
|
10774
11080
|
let updatedCount = 0;
|
|
10775
11081
|
const treeTxs = /* @__PURE__ */ new Map();
|
|
10776
|
-
for (const [
|
|
10777
|
-
treeTxs.set(
|
|
11082
|
+
for (const [indexName, tree] of this.trees) {
|
|
11083
|
+
treeTxs.set(indexName, await tree.createTransaction());
|
|
10778
11084
|
}
|
|
10779
11085
|
treeTxs.delete("_id");
|
|
10780
11086
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
@@ -10784,42 +11090,45 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10784
11090
|
const updatedDoc = computeUpdatedDoc(doc);
|
|
10785
11091
|
const oldFlatDoc = this.flattenDocument(doc);
|
|
10786
11092
|
const newFlatDoc = this.flattenDocument(updatedDoc);
|
|
10787
|
-
const
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
if (
|
|
10797
|
-
oldTokens = tokenize(oldV,
|
|
11093
|
+
for (const [indexName, treeTx] of treeTxs) {
|
|
11094
|
+
const config = this.registeredIndices.get(indexName);
|
|
11095
|
+
if (!config) continue;
|
|
11096
|
+
if (config.type === "fts") {
|
|
11097
|
+
const primaryField = this.getPrimaryField(config);
|
|
11098
|
+
const oldV = oldFlatDoc[primaryField];
|
|
11099
|
+
const newV = newFlatDoc[primaryField];
|
|
11100
|
+
if (oldV === newV) continue;
|
|
11101
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11102
|
+
if (typeof oldV === "string") {
|
|
11103
|
+
const oldTokens = ftsConfig ? tokenize(oldV, ftsConfig) : [oldV];
|
|
11104
|
+
for (let j = 0, jLen = oldTokens.length; j < jLen; j++) {
|
|
11105
|
+
await treeTx.delete(this.getTokenKey(pk, oldTokens[j]), { k: pk, v: oldTokens[j] });
|
|
11106
|
+
}
|
|
10798
11107
|
}
|
|
10799
|
-
|
|
10800
|
-
const
|
|
10801
|
-
const
|
|
10802
|
-
|
|
11108
|
+
if (typeof newV === "string") {
|
|
11109
|
+
const newTokens = ftsConfig ? tokenize(newV, ftsConfig) : [newV];
|
|
11110
|
+
const batchInsertData = [];
|
|
11111
|
+
for (let j = 0, jLen = newTokens.length; j < jLen; j++) {
|
|
11112
|
+
batchInsertData.push([this.getTokenKey(pk, newTokens[j]), { k: pk, v: newTokens[j] }]);
|
|
11113
|
+
}
|
|
11114
|
+
await treeTx.batchInsert(batchInsertData);
|
|
10803
11115
|
}
|
|
10804
|
-
}
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
if (
|
|
10808
|
-
|
|
11116
|
+
} else {
|
|
11117
|
+
const oldIndexVal = this.getIndexValue(config, oldFlatDoc);
|
|
11118
|
+
const newIndexVal = this.getIndexValue(config, newFlatDoc);
|
|
11119
|
+
if (JSON.stringify(oldIndexVal) === JSON.stringify(newIndexVal)) continue;
|
|
11120
|
+
if (oldIndexVal !== void 0) {
|
|
11121
|
+
await treeTx.delete(pk, { k: pk, v: oldIndexVal });
|
|
10809
11122
|
}
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
const newToken = newTokens[j];
|
|
10813
|
-
const keyToInsert = isFts ? this.getTokenKey(pk, newToken) : pk;
|
|
10814
|
-
batchInsertData.push([keyToInsert, { k: pk, v: newToken }]);
|
|
11123
|
+
if (newIndexVal !== void 0) {
|
|
11124
|
+
await treeTx.batchInsert([[pk, { k: pk, v: newIndexVal }]]);
|
|
10815
11125
|
}
|
|
10816
|
-
await treeTx.batchInsert(batchInsertData);
|
|
10817
11126
|
}
|
|
10818
11127
|
}
|
|
10819
11128
|
await this.update(pk, JSON.stringify(updatedDoc), tx);
|
|
10820
11129
|
updatedCount++;
|
|
10821
11130
|
}
|
|
10822
|
-
for (const [
|
|
11131
|
+
for (const [indexName, treeTx] of treeTxs) {
|
|
10823
11132
|
const result = await treeTx.commit();
|
|
10824
11133
|
if (!result.success) {
|
|
10825
11134
|
for (const rollbackTx of treeTxs.values()) {
|
|
@@ -10832,7 +11141,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10832
11141
|
}
|
|
10833
11142
|
/**
|
|
10834
11143
|
* Fully update documents from the database that match the query
|
|
10835
|
-
* @param query The query to use
|
|
11144
|
+
* @param query The query to use
|
|
10836
11145
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
10837
11146
|
* @param tx The transaction to use
|
|
10838
11147
|
* @returns The number of updated documents
|
|
@@ -10847,7 +11156,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10847
11156
|
}
|
|
10848
11157
|
/**
|
|
10849
11158
|
* Partially update documents from the database that match the query
|
|
10850
|
-
* @param query The query to use
|
|
11159
|
+
* @param query The query to use
|
|
10851
11160
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
10852
11161
|
* @param tx The transaction to use
|
|
10853
11162
|
* @returns The number of updated documents
|
|
@@ -10864,7 +11173,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10864
11173
|
}
|
|
10865
11174
|
/**
|
|
10866
11175
|
* Delete documents from the database that match the query
|
|
10867
|
-
* @param query The query to use
|
|
11176
|
+
* @param query The query to use
|
|
10868
11177
|
* @param tx The transaction to use
|
|
10869
11178
|
* @returns The number of deleted documents
|
|
10870
11179
|
*/
|
|
@@ -10877,20 +11186,22 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10877
11186
|
const doc = await this.getDocument(pk, tx2);
|
|
10878
11187
|
if (!doc) continue;
|
|
10879
11188
|
const flatDoc = this.flattenDocument(doc);
|
|
10880
|
-
const
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
if (
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
tokens = tokenize(v,
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
11189
|
+
for (const [indexName, tree] of this.trees) {
|
|
11190
|
+
const config = this.registeredIndices.get(indexName);
|
|
11191
|
+
if (!config) continue;
|
|
11192
|
+
if (config.type === "fts") {
|
|
11193
|
+
const primaryField = this.getPrimaryField(config);
|
|
11194
|
+
const v = flatDoc[primaryField];
|
|
11195
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
11196
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11197
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
11198
|
+
for (let j = 0, jLen = tokens.length; j < jLen; j++) {
|
|
11199
|
+
await tree.delete(this.getTokenKey(pk, tokens[j]), { k: pk, v: tokens[j] });
|
|
11200
|
+
}
|
|
11201
|
+
} else {
|
|
11202
|
+
const indexVal = this.getIndexValue(config, flatDoc);
|
|
11203
|
+
if (indexVal === void 0) continue;
|
|
11204
|
+
await tree.delete(pk, { k: pk, v: indexVal });
|
|
10894
11205
|
}
|
|
10895
11206
|
}
|
|
10896
11207
|
await super.delete(pk, true, tx2);
|
|
@@ -10901,7 +11212,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10901
11212
|
}
|
|
10902
11213
|
/**
|
|
10903
11214
|
* Count documents from the database that match the query
|
|
10904
|
-
* @param query The query to use
|
|
11215
|
+
* @param query The query to use
|
|
10905
11216
|
* @param tx The transaction to use
|
|
10906
11217
|
* @returns The number of documents that match the query
|
|
10907
11218
|
*/
|
|
@@ -10927,6 +11238,51 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10927
11238
|
}
|
|
10928
11239
|
return true;
|
|
10929
11240
|
}
|
|
11241
|
+
/**
|
|
11242
|
+
* 복합 인덱스의 non-primary 필드에 대해 문서가 유효한지 검증합니다.
|
|
11243
|
+
*/
|
|
11244
|
+
verifyCompositeConditions(doc, conditions) {
|
|
11245
|
+
if (conditions.length === 0) return true;
|
|
11246
|
+
const flatDoc = this.flattenDocument(doc);
|
|
11247
|
+
for (let i = 0, len = conditions.length; i < len; i++) {
|
|
11248
|
+
const { field, condition } = conditions[i];
|
|
11249
|
+
const docValue = flatDoc[field];
|
|
11250
|
+
if (docValue === void 0) return false;
|
|
11251
|
+
const treeValue = { k: doc._id, v: docValue };
|
|
11252
|
+
if (!this.verifyValue(docValue, condition)) return false;
|
|
11253
|
+
}
|
|
11254
|
+
return true;
|
|
11255
|
+
}
|
|
11256
|
+
/**
|
|
11257
|
+
* 단일 값에 대해 verbose 조건을 검증합니다.
|
|
11258
|
+
*/
|
|
11259
|
+
verifyValue(value, condition) {
|
|
11260
|
+
if (typeof condition !== "object" || condition === null) {
|
|
11261
|
+
return value === condition;
|
|
11262
|
+
}
|
|
11263
|
+
if ("primaryEqual" in condition) {
|
|
11264
|
+
return value === condition.primaryEqual?.v;
|
|
11265
|
+
}
|
|
11266
|
+
if ("primaryNotEqual" in condition) {
|
|
11267
|
+
return value !== condition.primaryNotEqual?.v;
|
|
11268
|
+
}
|
|
11269
|
+
if ("primaryLt" in condition) {
|
|
11270
|
+
return value !== null && condition.primaryLt?.v !== void 0 && value < condition.primaryLt.v;
|
|
11271
|
+
}
|
|
11272
|
+
if ("primaryLte" in condition) {
|
|
11273
|
+
return value !== null && condition.primaryLte?.v !== void 0 && value <= condition.primaryLte.v;
|
|
11274
|
+
}
|
|
11275
|
+
if ("primaryGt" in condition) {
|
|
11276
|
+
return value !== null && condition.primaryGt?.v !== void 0 && value > condition.primaryGt.v;
|
|
11277
|
+
}
|
|
11278
|
+
if ("primaryGte" in condition) {
|
|
11279
|
+
return value !== null && condition.primaryGte?.v !== void 0 && value >= condition.primaryGte.v;
|
|
11280
|
+
}
|
|
11281
|
+
if ("primaryOr" in condition && Array.isArray(condition.primaryOr)) {
|
|
11282
|
+
return condition.primaryOr.some((c) => value === c?.v);
|
|
11283
|
+
}
|
|
11284
|
+
return true;
|
|
11285
|
+
}
|
|
10930
11286
|
/**
|
|
10931
11287
|
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
10932
11288
|
*/
|
|
@@ -10939,10 +11295,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10939
11295
|
}
|
|
10940
11296
|
/**
|
|
10941
11297
|
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
10942
|
-
* FTS
|
|
10943
|
-
* 교집합 대신 스트리밍 중 검증하여 첫 결과 반환 시간을 단축합니다.
|
|
11298
|
+
* FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
|
|
10944
11299
|
*/
|
|
10945
|
-
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, others, tx) {
|
|
11300
|
+
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
|
|
10946
11301
|
const verifyOthers = others.filter((o) => !o.isFtsMatch);
|
|
10947
11302
|
let i = startIdx;
|
|
10948
11303
|
const totalKeys = keys.length;
|
|
@@ -10968,6 +11323,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10968
11323
|
const doc = JSON.parse(s);
|
|
10969
11324
|
chunkTotalSize += s.length * 2;
|
|
10970
11325
|
if (ftsConditions.length > 0 && !this.verifyFts(doc, ftsConditions)) continue;
|
|
11326
|
+
if (compositeVerifyConditions.length > 0 && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
|
|
10971
11327
|
if (verifyOthers.length > 0) {
|
|
10972
11328
|
const flatDoc = this.flattenDocument(doc);
|
|
10973
11329
|
let passed = true;
|
|
@@ -10993,7 +11349,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10993
11349
|
}
|
|
10994
11350
|
/**
|
|
10995
11351
|
* Select documents from the database
|
|
10996
|
-
* @param query The query to use
|
|
11352
|
+
* @param query The query to use
|
|
10997
11353
|
* @param options The options to use
|
|
10998
11354
|
* @param tx The transaction to use
|
|
10999
11355
|
* @returns The documents that match the query
|
|
@@ -11017,32 +11373,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11017
11373
|
} = options;
|
|
11018
11374
|
const self = this;
|
|
11019
11375
|
const stream = this.streamWithDefault(async function* (tx2) {
|
|
11020
|
-
const metadata = await self.getDocumentInnerMetadata(tx2);
|
|
11021
11376
|
const ftsConditions = [];
|
|
11022
11377
|
for (const field in query) {
|
|
11023
11378
|
const q = query[field];
|
|
11024
11379
|
if (q && typeof q === "object" && "match" in q && typeof q.match === "string") {
|
|
11025
|
-
const
|
|
11026
|
-
|
|
11027
|
-
|
|
11380
|
+
const indexNames = self.fieldToIndices.get(field) || [];
|
|
11381
|
+
for (const indexName of indexNames) {
|
|
11382
|
+
const config = self.registeredIndices.get(indexName);
|
|
11383
|
+
if (config && config.type === "fts") {
|
|
11384
|
+
const ftsConfig = self.getFtsConfig(config);
|
|
11385
|
+
if (ftsConfig) {
|
|
11386
|
+
ftsConditions.push({ field, matchTokens: tokenize(q.match, ftsConfig) });
|
|
11387
|
+
}
|
|
11388
|
+
break;
|
|
11389
|
+
}
|
|
11028
11390
|
}
|
|
11029
11391
|
}
|
|
11030
11392
|
}
|
|
11031
11393
|
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
|
|
11032
11394
|
if (!driverResult) return;
|
|
11033
|
-
const { keys, others, rollback } = driverResult;
|
|
11395
|
+
const { keys, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
|
|
11034
11396
|
if (keys.length === 0) {
|
|
11035
11397
|
rollback();
|
|
11036
11398
|
return;
|
|
11037
11399
|
}
|
|
11038
|
-
const isQueryEmpty = Object.keys(query).length === 0;
|
|
11039
|
-
const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
|
|
11040
|
-
const selectivity = await self.getSelectivityCandidate(
|
|
11041
|
-
self.verboseQuery(normalizedQuery),
|
|
11042
|
-
orderByField
|
|
11043
|
-
);
|
|
11044
|
-
const isDriverOrderByField = orderByField === void 0 || selectivity && selectivity.driver.field === orderByField;
|
|
11045
|
-
if (selectivity) selectivity.rollback();
|
|
11046
11400
|
try {
|
|
11047
11401
|
if (!isDriverOrderByField && orderByField) {
|
|
11048
11402
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
@@ -11061,6 +11415,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11061
11415
|
0,
|
|
11062
11416
|
self.options.pageSize,
|
|
11063
11417
|
ftsConditions,
|
|
11418
|
+
compositeVerifyConditions,
|
|
11064
11419
|
others,
|
|
11065
11420
|
tx2
|
|
11066
11421
|
)) {
|
|
@@ -11098,6 +11453,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11098
11453
|
offset,
|
|
11099
11454
|
self.options.pageSize,
|
|
11100
11455
|
ftsConditions,
|
|
11456
|
+
compositeVerifyConditions,
|
|
11101
11457
|
others,
|
|
11102
11458
|
tx2
|
|
11103
11459
|
)) {
|
|
@@ -11131,8 +11487,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11131
11487
|
static Define() {
|
|
11132
11488
|
return {
|
|
11133
11489
|
/**
|
|
11134
|
-
* Sets the options for the database, such as
|
|
11135
|
-
* @template IC The configuration of indices.
|
|
11490
|
+
* Sets the options for the database, such as WAL settings.
|
|
11136
11491
|
* @param options The database initialization options.
|
|
11137
11492
|
*/
|
|
11138
11493
|
Options: (options) => _DocumentDataply.Options(options)
|
|
@@ -11160,6 +11515,30 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11160
11515
|
constructor(file, options) {
|
|
11161
11516
|
this.api = new DocumentDataplyAPI(file, options ?? {});
|
|
11162
11517
|
}
|
|
11518
|
+
/**
|
|
11519
|
+
* Create a named index on the database.
|
|
11520
|
+
* Can be called before or after init().
|
|
11521
|
+
* If called after init(), the index is immediately created and backfilled.
|
|
11522
|
+
* @param name The name of the index
|
|
11523
|
+
* @param option The index configuration (btree or fts)
|
|
11524
|
+
* @param tx Optional transaction
|
|
11525
|
+
* @returns Promise<this> for chaining
|
|
11526
|
+
*/
|
|
11527
|
+
async createIndex(name, option, tx) {
|
|
11528
|
+
await this.api.registerIndex(name, option, tx);
|
|
11529
|
+
return this;
|
|
11530
|
+
}
|
|
11531
|
+
/**
|
|
11532
|
+
* Drop (remove) a named index from the database.
|
|
11533
|
+
* The '_id' index cannot be dropped.
|
|
11534
|
+
* @param name The name of the index to drop
|
|
11535
|
+
* @param tx Optional transaction
|
|
11536
|
+
* @returns Promise<this> for chaining
|
|
11537
|
+
*/
|
|
11538
|
+
async dropIndex(name, tx) {
|
|
11539
|
+
await this.api.dropIndex(name, tx);
|
|
11540
|
+
return this;
|
|
11541
|
+
}
|
|
11163
11542
|
/**
|
|
11164
11543
|
* Initialize the document database
|
|
11165
11544
|
*/
|
|
@@ -11167,6 +11546,17 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11167
11546
|
await this.api.init();
|
|
11168
11547
|
await this.api.backfillIndices();
|
|
11169
11548
|
}
|
|
11549
|
+
/**
|
|
11550
|
+
* Run a migration if the current schemeVersion is lower than the target version.
|
|
11551
|
+
* The callback is only executed when the database's schemeVersion is below the given version.
|
|
11552
|
+
* After the callback completes, schemeVersion is updated to the target version.
|
|
11553
|
+
* @param version The target scheme version
|
|
11554
|
+
* @param callback The migration callback receiving a transaction
|
|
11555
|
+
* @param tx Optional transaction
|
|
11556
|
+
*/
|
|
11557
|
+
async migration(version, callback, tx) {
|
|
11558
|
+
await this.api.migration(version, callback, tx);
|
|
11559
|
+
}
|
|
11170
11560
|
/**
|
|
11171
11561
|
* Get the metadata of the document database
|
|
11172
11562
|
*/
|
|
@@ -11199,7 +11589,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11199
11589
|
}
|
|
11200
11590
|
/**
|
|
11201
11591
|
* Fully update documents from the database that match the query
|
|
11202
|
-
* @param query The query to use
|
|
11592
|
+
* @param query The query to use
|
|
11203
11593
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
11204
11594
|
* @param tx The transaction to use
|
|
11205
11595
|
* @returns The number of updated documents
|
|
@@ -11209,7 +11599,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11209
11599
|
}
|
|
11210
11600
|
/**
|
|
11211
11601
|
* Partially update documents from the database that match the query
|
|
11212
|
-
* @param query The query to use
|
|
11602
|
+
* @param query The query to use
|
|
11213
11603
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
11214
11604
|
* @param tx The transaction to use
|
|
11215
11605
|
* @returns The number of updated documents
|
|
@@ -11219,7 +11609,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11219
11609
|
}
|
|
11220
11610
|
/**
|
|
11221
11611
|
* Delete documents from the database that match the query
|
|
11222
|
-
* @param query The query to use
|
|
11612
|
+
* @param query The query to use
|
|
11223
11613
|
* @param tx The transaction to use
|
|
11224
11614
|
* @returns The number of deleted documents
|
|
11225
11615
|
*/
|
|
@@ -11228,7 +11618,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11228
11618
|
}
|
|
11229
11619
|
/**
|
|
11230
11620
|
* Count documents from the database that match the query
|
|
11231
|
-
* @param query The query to use
|
|
11621
|
+
* @param query The query to use
|
|
11232
11622
|
* @param tx The transaction to use
|
|
11233
11623
|
* @returns The number of documents that match the query
|
|
11234
11624
|
*/
|
|
@@ -11237,7 +11627,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11237
11627
|
}
|
|
11238
11628
|
/**
|
|
11239
11629
|
* Select documents from the database
|
|
11240
|
-
* @param query The query to use
|
|
11630
|
+
* @param query The query to use
|
|
11241
11631
|
* @param options The options to use
|
|
11242
11632
|
* @param tx The transaction to use
|
|
11243
11633
|
* @returns The documents that match the query
|