document-dataply 0.0.9-alpha.0 → 0.0.9-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +610 -230
- package/dist/types/core/bptree/documentStrategy.d.ts +3 -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
|
@@ -10043,22 +10043,46 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
|
|
|
10043
10043
|
|
|
10044
10044
|
// src/core/bptree/documentComparator.ts
|
|
10045
10045
|
var import_dataply2 = __toESM(require_cjs());
|
|
10046
|
+
function comparePrimitive(a, b) {
|
|
10047
|
+
if (a === b) return 0;
|
|
10048
|
+
if (a === null) return -1;
|
|
10049
|
+
if (b === null) return 1;
|
|
10050
|
+
if (typeof a !== typeof b) {
|
|
10051
|
+
const typeOrder = (v) => typeof v === "boolean" ? 0 : typeof v === "number" ? 1 : 2;
|
|
10052
|
+
return typeOrder(a) - typeOrder(b);
|
|
10053
|
+
}
|
|
10054
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
10055
|
+
return a.localeCompare(b);
|
|
10056
|
+
}
|
|
10057
|
+
return +a - +b;
|
|
10058
|
+
}
|
|
10059
|
+
function compareValue(a, b) {
|
|
10060
|
+
const aArr = Array.isArray(a);
|
|
10061
|
+
const bArr = Array.isArray(b);
|
|
10062
|
+
if (!aArr && !bArr) {
|
|
10063
|
+
return comparePrimitive(a, b);
|
|
10064
|
+
}
|
|
10065
|
+
const aList = aArr ? a : [a];
|
|
10066
|
+
const bList = bArr ? b : [b];
|
|
10067
|
+
const len = Math.min(aList.length, bList.length);
|
|
10068
|
+
for (let i = 0; i < len; i++) {
|
|
10069
|
+
const diff = comparePrimitive(aList[i], bList[i]);
|
|
10070
|
+
if (diff !== 0) return diff;
|
|
10071
|
+
}
|
|
10072
|
+
return aList.length - bList.length;
|
|
10073
|
+
}
|
|
10046
10074
|
var DocumentValueComparator = class extends import_dataply2.ValueComparator {
|
|
10047
10075
|
primaryAsc(a, b) {
|
|
10048
|
-
|
|
10049
|
-
return +a.v - +b.v;
|
|
10050
|
-
}
|
|
10051
|
-
return a.v.localeCompare(b.v);
|
|
10076
|
+
return compareValue(a.v, b.v);
|
|
10052
10077
|
}
|
|
10053
10078
|
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);
|
|
10079
|
+
const diff = compareValue(a.v, b.v);
|
|
10059
10080
|
return diff === 0 ? a.k - b.k : diff;
|
|
10060
10081
|
}
|
|
10061
10082
|
match(value) {
|
|
10083
|
+
if (Array.isArray(value.v)) {
|
|
10084
|
+
return value.v[0] + "";
|
|
10085
|
+
}
|
|
10062
10086
|
return value.v + "";
|
|
10063
10087
|
}
|
|
10064
10088
|
};
|
|
@@ -10171,7 +10195,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10171
10195
|
comparator = new DocumentValueComparator();
|
|
10172
10196
|
pendingBackfillFields = [];
|
|
10173
10197
|
lock;
|
|
10198
|
+
_initialized = false;
|
|
10174
10199
|
indexedFields;
|
|
10200
|
+
/**
|
|
10201
|
+
* Registered indices via createIndex() (before init)
|
|
10202
|
+
* Key: index name, Value: index configuration
|
|
10203
|
+
*/
|
|
10204
|
+
pendingCreateIndices = /* @__PURE__ */ new Map();
|
|
10205
|
+
/**
|
|
10206
|
+
* Resolved index configurations after init.
|
|
10207
|
+
* Key: index name, Value: index config (from metadata)
|
|
10208
|
+
*/
|
|
10209
|
+
registeredIndices = /* @__PURE__ */ new Map();
|
|
10210
|
+
/**
|
|
10211
|
+
* Maps field name → index names that cover this field.
|
|
10212
|
+
* Used for query resolution.
|
|
10213
|
+
*/
|
|
10214
|
+
fieldToIndices = /* @__PURE__ */ new Map();
|
|
10175
10215
|
operatorConverters = {
|
|
10176
10216
|
equal: "primaryEqual",
|
|
10177
10217
|
notEqual: "primaryNotEqual",
|
|
@@ -10187,11 +10227,6 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10187
10227
|
this.trees = /* @__PURE__ */ new Map();
|
|
10188
10228
|
this.lock = new import_dataply3.Ryoiki();
|
|
10189
10229
|
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
10230
|
this.hook.onceAfter("init", async (tx, isNewlyCreated) => {
|
|
10196
10231
|
if (isNewlyCreated) {
|
|
10197
10232
|
await this.initializeDocumentFile(tx);
|
|
@@ -10200,31 +10235,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10200
10235
|
throw new Error("Document metadata verification failed");
|
|
10201
10236
|
}
|
|
10202
10237
|
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
10203
|
-
const
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10238
|
+
const targetIndices = /* @__PURE__ */ new Map();
|
|
10239
|
+
targetIndices.set("_id", { type: "btree", fields: ["_id"] });
|
|
10240
|
+
for (const [name, option] of this.pendingCreateIndices) {
|
|
10241
|
+
const config = this.toIndexMetaConfig(option);
|
|
10242
|
+
targetIndices.set(name, config);
|
|
10243
|
+
}
|
|
10208
10244
|
const backfillTargets = [];
|
|
10209
10245
|
let isMetadataChanged = false;
|
|
10210
|
-
for (const
|
|
10211
|
-
const
|
|
10212
|
-
const existingIndex = metadata.indices[field];
|
|
10246
|
+
for (const [indexName, config] of targetIndices) {
|
|
10247
|
+
const existingIndex = metadata.indices[indexName];
|
|
10213
10248
|
if (!existingIndex) {
|
|
10214
|
-
metadata.indices[
|
|
10249
|
+
metadata.indices[indexName] = [-1, config];
|
|
10215
10250
|
isMetadataChanged = true;
|
|
10216
|
-
if (
|
|
10217
|
-
backfillTargets.push(
|
|
10251
|
+
if (!isNewlyCreated) {
|
|
10252
|
+
backfillTargets.push(indexName);
|
|
10218
10253
|
}
|
|
10219
10254
|
} 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;
|
|
10255
|
+
const [_pk, existingConfig] = existingIndex;
|
|
10256
|
+
if (JSON.stringify(existingConfig) !== JSON.stringify(config)) {
|
|
10257
|
+
metadata.indices[indexName] = [_pk, config];
|
|
10227
10258
|
isMetadataChanged = true;
|
|
10259
|
+
if (!isNewlyCreated) {
|
|
10260
|
+
backfillTargets.push(indexName);
|
|
10261
|
+
}
|
|
10228
10262
|
}
|
|
10229
10263
|
}
|
|
10230
10264
|
}
|
|
@@ -10232,25 +10266,220 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10232
10266
|
await this.updateDocumentInnerMetadata(metadata, tx);
|
|
10233
10267
|
}
|
|
10234
10268
|
this.indices = metadata.indices;
|
|
10235
|
-
|
|
10236
|
-
|
|
10269
|
+
this.registeredIndices = /* @__PURE__ */ new Map();
|
|
10270
|
+
this.fieldToIndices = /* @__PURE__ */ new Map();
|
|
10271
|
+
for (const [indexName, config] of targetIndices) {
|
|
10272
|
+
this.registeredIndices.set(indexName, config);
|
|
10273
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10274
|
+
for (const field of fields) {
|
|
10275
|
+
this.indexedFields.add(field);
|
|
10276
|
+
if (!this.fieldToIndices.has(field)) {
|
|
10277
|
+
this.fieldToIndices.set(field, []);
|
|
10278
|
+
}
|
|
10279
|
+
this.fieldToIndices.get(field).push(indexName);
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
for (const indexName of targetIndices.keys()) {
|
|
10283
|
+
if (metadata.indices[indexName]) {
|
|
10237
10284
|
const tree = new import_dataply3.BPTreeAsync(
|
|
10238
10285
|
new DocumentSerializeStrategyAsync(
|
|
10239
10286
|
this.rowTableEngine.order,
|
|
10240
10287
|
this,
|
|
10241
10288
|
this.txContext,
|
|
10242
|
-
|
|
10289
|
+
indexName
|
|
10243
10290
|
),
|
|
10244
10291
|
this.comparator
|
|
10245
10292
|
);
|
|
10246
10293
|
await tree.init();
|
|
10247
|
-
this.trees.set(
|
|
10294
|
+
this.trees.set(indexName, tree);
|
|
10248
10295
|
}
|
|
10249
10296
|
}
|
|
10250
10297
|
this.pendingBackfillFields = backfillTargets;
|
|
10298
|
+
this._initialized = true;
|
|
10251
10299
|
return tx;
|
|
10252
10300
|
});
|
|
10253
10301
|
}
|
|
10302
|
+
/**
|
|
10303
|
+
* Whether the document database has been initialized.
|
|
10304
|
+
*/
|
|
10305
|
+
get isDocInitialized() {
|
|
10306
|
+
return this._initialized;
|
|
10307
|
+
}
|
|
10308
|
+
/**
|
|
10309
|
+
* Register an index. If called before init(), queues it for processing during init.
|
|
10310
|
+
* If called after init(), immediately creates the tree, updates metadata, and backfills.
|
|
10311
|
+
*/
|
|
10312
|
+
async registerIndex(name, option, tx) {
|
|
10313
|
+
if (!this._initialized) {
|
|
10314
|
+
this.pendingCreateIndices.set(name, option);
|
|
10315
|
+
return;
|
|
10316
|
+
}
|
|
10317
|
+
await this.registerIndexRuntime(name, option, tx);
|
|
10318
|
+
}
|
|
10319
|
+
/**
|
|
10320
|
+
* Register an index at runtime (after init).
|
|
10321
|
+
* Creates the tree, updates metadata, and backfills existing data.
|
|
10322
|
+
*/
|
|
10323
|
+
async registerIndexRuntime(name, option, tx) {
|
|
10324
|
+
const config = this.toIndexMetaConfig(option);
|
|
10325
|
+
if (this.registeredIndices.has(name)) {
|
|
10326
|
+
const existing = this.registeredIndices.get(name);
|
|
10327
|
+
if (JSON.stringify(existing) === JSON.stringify(config)) return;
|
|
10328
|
+
}
|
|
10329
|
+
await this.runWithDefault(async (tx2) => {
|
|
10330
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10331
|
+
metadata.indices[name] = [-1, config];
|
|
10332
|
+
await this.updateDocumentInnerMetadata(metadata, tx2);
|
|
10333
|
+
this.indices = metadata.indices;
|
|
10334
|
+
this.registeredIndices.set(name, config);
|
|
10335
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10336
|
+
for (const field of fields) {
|
|
10337
|
+
this.indexedFields.add(field);
|
|
10338
|
+
if (!this.fieldToIndices.has(field)) {
|
|
10339
|
+
this.fieldToIndices.set(field, []);
|
|
10340
|
+
}
|
|
10341
|
+
this.fieldToIndices.get(field).push(name);
|
|
10342
|
+
}
|
|
10343
|
+
const tree = new import_dataply3.BPTreeAsync(
|
|
10344
|
+
new DocumentSerializeStrategyAsync(
|
|
10345
|
+
this.rowTableEngine.order,
|
|
10346
|
+
this,
|
|
10347
|
+
this.txContext,
|
|
10348
|
+
name
|
|
10349
|
+
),
|
|
10350
|
+
this.comparator
|
|
10351
|
+
);
|
|
10352
|
+
await tree.init();
|
|
10353
|
+
this.trees.set(name, tree);
|
|
10354
|
+
if (metadata.lastId > 0) {
|
|
10355
|
+
this.pendingBackfillFields = [name];
|
|
10356
|
+
await this.backfillIndices(tx2);
|
|
10357
|
+
}
|
|
10358
|
+
}, tx);
|
|
10359
|
+
}
|
|
10360
|
+
/**
|
|
10361
|
+
* Drop (remove) a named index.
|
|
10362
|
+
* Removes the index from metadata, in-memory maps, and trees.
|
|
10363
|
+
* The '_id' index cannot be dropped.
|
|
10364
|
+
* @param name The name of the index to drop
|
|
10365
|
+
*/
|
|
10366
|
+
async dropIndex(name, tx) {
|
|
10367
|
+
if (name === "_id") {
|
|
10368
|
+
throw new Error("Cannot drop the _id index");
|
|
10369
|
+
}
|
|
10370
|
+
if (!this._initialized) {
|
|
10371
|
+
this.pendingCreateIndices.delete(name);
|
|
10372
|
+
return;
|
|
10373
|
+
}
|
|
10374
|
+
if (!this.registeredIndices.has(name)) {
|
|
10375
|
+
throw new Error(`Index '${name}' does not exist`);
|
|
10376
|
+
}
|
|
10377
|
+
await this.runWithDefault(async (tx2) => {
|
|
10378
|
+
const config = this.registeredIndices.get(name);
|
|
10379
|
+
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10380
|
+
delete metadata.indices[name];
|
|
10381
|
+
await this.updateDocumentInnerMetadata(metadata, tx2);
|
|
10382
|
+
this.indices = metadata.indices;
|
|
10383
|
+
this.registeredIndices.delete(name);
|
|
10384
|
+
const fields = this.getFieldsFromConfig(config);
|
|
10385
|
+
for (const field of fields) {
|
|
10386
|
+
const indexNames = this.fieldToIndices.get(field);
|
|
10387
|
+
if (indexNames) {
|
|
10388
|
+
const filtered = indexNames.filter((n) => n !== name);
|
|
10389
|
+
if (filtered.length === 0) {
|
|
10390
|
+
this.fieldToIndices.delete(field);
|
|
10391
|
+
if (field !== "_id") {
|
|
10392
|
+
this.indexedFields.delete(field);
|
|
10393
|
+
}
|
|
10394
|
+
} else {
|
|
10395
|
+
this.fieldToIndices.set(field, filtered);
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
this.trees.delete(name);
|
|
10400
|
+
}, tx);
|
|
10401
|
+
}
|
|
10402
|
+
/**
|
|
10403
|
+
* Convert CreateIndexOption to IndexMetaConfig for metadata storage.
|
|
10404
|
+
*/
|
|
10405
|
+
toIndexMetaConfig(option) {
|
|
10406
|
+
if (option.type === "btree") {
|
|
10407
|
+
return {
|
|
10408
|
+
type: "btree",
|
|
10409
|
+
fields: option.fields
|
|
10410
|
+
};
|
|
10411
|
+
}
|
|
10412
|
+
if (option.type === "fts") {
|
|
10413
|
+
if (option.tokenizer === "ngram") {
|
|
10414
|
+
return {
|
|
10415
|
+
type: "fts",
|
|
10416
|
+
fields: option.fields,
|
|
10417
|
+
tokenizer: "ngram",
|
|
10418
|
+
gramSize: option.ngram
|
|
10419
|
+
};
|
|
10420
|
+
}
|
|
10421
|
+
return {
|
|
10422
|
+
type: "fts",
|
|
10423
|
+
fields: option.fields,
|
|
10424
|
+
tokenizer: "whitespace"
|
|
10425
|
+
};
|
|
10426
|
+
}
|
|
10427
|
+
throw new Error(`Unknown index type: ${option.type}`);
|
|
10428
|
+
}
|
|
10429
|
+
/**
|
|
10430
|
+
* Get all field names from an IndexMetaConfig.
|
|
10431
|
+
*/
|
|
10432
|
+
getFieldsFromConfig(config) {
|
|
10433
|
+
if (config.type === "btree") {
|
|
10434
|
+
return config.fields;
|
|
10435
|
+
}
|
|
10436
|
+
if (config.type === "fts") {
|
|
10437
|
+
return [config.fields];
|
|
10438
|
+
}
|
|
10439
|
+
return [];
|
|
10440
|
+
}
|
|
10441
|
+
/**
|
|
10442
|
+
* Get the primary field of an index (the field used as tree key).
|
|
10443
|
+
* For btree: first field in fields array.
|
|
10444
|
+
* For fts: the single field.
|
|
10445
|
+
*/
|
|
10446
|
+
getPrimaryField(config) {
|
|
10447
|
+
if (config.type === "btree") {
|
|
10448
|
+
return config.fields[0];
|
|
10449
|
+
}
|
|
10450
|
+
return config.fields;
|
|
10451
|
+
}
|
|
10452
|
+
/**
|
|
10453
|
+
* 인덱스 config에 따라 B+tree에 저장할 v 값을 생성합니다.
|
|
10454
|
+
* - 단일 필드 btree: Primitive (단일 값)
|
|
10455
|
+
* - 복합 필드 btree: Primitive[] (필드 순서대로 배열)
|
|
10456
|
+
* - fts: 별도 처리 (이 메서드 사용 안 함)
|
|
10457
|
+
* @returns undefined면 해당 문서에 필수 필드가 없으므로 인덱싱 스킵
|
|
10458
|
+
*/
|
|
10459
|
+
getIndexValue(config, flatDoc) {
|
|
10460
|
+
if (config.type !== "btree") return void 0;
|
|
10461
|
+
if (config.fields.length === 1) {
|
|
10462
|
+
const v = flatDoc[config.fields[0]];
|
|
10463
|
+
return v === void 0 ? void 0 : v;
|
|
10464
|
+
}
|
|
10465
|
+
const values = [];
|
|
10466
|
+
for (let i = 0, len = config.fields.length; i < len; i++) {
|
|
10467
|
+
const v = flatDoc[config.fields[i]];
|
|
10468
|
+
if (v === void 0) return void 0;
|
|
10469
|
+
values.push(v);
|
|
10470
|
+
}
|
|
10471
|
+
return values;
|
|
10472
|
+
}
|
|
10473
|
+
/**
|
|
10474
|
+
* Get FTSConfig from IndexMetaConfig (for tokenizer compatibility).
|
|
10475
|
+
*/
|
|
10476
|
+
getFtsConfig(config) {
|
|
10477
|
+
if (config.type !== "fts") return null;
|
|
10478
|
+
if (config.tokenizer === "ngram") {
|
|
10479
|
+
return { type: "fts", tokenizer: "ngram", gramSize: config.gramSize };
|
|
10480
|
+
}
|
|
10481
|
+
return { type: "fts", tokenizer: "whitespace" };
|
|
10482
|
+
}
|
|
10254
10483
|
async getDocument(pk, tx) {
|
|
10255
10484
|
return this.runWithDefault(async (tx2) => {
|
|
10256
10485
|
const row = await this.select(pk, false, tx2);
|
|
@@ -10279,10 +10508,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10279
10508
|
});
|
|
10280
10509
|
}
|
|
10281
10510
|
/**
|
|
10282
|
-
* Backfill indices for
|
|
10283
|
-
* This method should be called after `init()
|
|
10284
|
-
*
|
|
10285
|
-
*
|
|
10511
|
+
* Backfill indices for newly created indices after data was inserted.
|
|
10512
|
+
* This method should be called after `init()`.
|
|
10513
|
+
*
|
|
10286
10514
|
* @returns Number of documents that were backfilled
|
|
10287
10515
|
*/
|
|
10288
10516
|
async backfillIndices(tx) {
|
|
@@ -10295,12 +10523,12 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10295
10523
|
if (metadata.lastId === 0) {
|
|
10296
10524
|
return 0;
|
|
10297
10525
|
}
|
|
10298
|
-
const
|
|
10299
|
-
const
|
|
10300
|
-
for (const
|
|
10301
|
-
const tree = this.trees.get(
|
|
10302
|
-
if (tree &&
|
|
10303
|
-
|
|
10526
|
+
const indexTxMap = {};
|
|
10527
|
+
const indexEntryMap = /* @__PURE__ */ new Map();
|
|
10528
|
+
for (const indexName of backfillTargets) {
|
|
10529
|
+
const tree = this.trees.get(indexName);
|
|
10530
|
+
if (tree && indexName !== "_id") {
|
|
10531
|
+
indexTxMap[indexName] = await tree.createTransaction();
|
|
10304
10532
|
}
|
|
10305
10533
|
}
|
|
10306
10534
|
let backfilledCount = 0;
|
|
@@ -10315,35 +10543,44 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10315
10543
|
const doc = await this.getDocument(k, tx2);
|
|
10316
10544
|
if (!doc) continue;
|
|
10317
10545
|
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
|
-
|
|
10546
|
+
for (const indexName of backfillTargets) {
|
|
10547
|
+
if (!(indexName in indexTxMap)) continue;
|
|
10548
|
+
const config = this.registeredIndices.get(indexName);
|
|
10549
|
+
if (!config) continue;
|
|
10550
|
+
const btx = indexTxMap[indexName];
|
|
10551
|
+
if (config.type === "fts") {
|
|
10552
|
+
const primaryField = this.getPrimaryField(config);
|
|
10553
|
+
const v = flatDoc[primaryField];
|
|
10554
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
10555
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10556
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
10557
|
+
const batchInsertData = [];
|
|
10558
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
10559
|
+
const token = tokens[i];
|
|
10560
|
+
const keyToInsert = this.getTokenKey(k, token);
|
|
10561
|
+
const entry = { k, v: token };
|
|
10562
|
+
batchInsertData.push([keyToInsert, entry]);
|
|
10563
|
+
if (!indexEntryMap.has(btx)) {
|
|
10564
|
+
indexEntryMap.set(btx, []);
|
|
10565
|
+
}
|
|
10566
|
+
indexEntryMap.get(btx).push({ k: keyToInsert, v: entry });
|
|
10567
|
+
}
|
|
10568
|
+
await btx.batchInsert(batchInsertData);
|
|
10569
|
+
} else {
|
|
10570
|
+
const indexVal = this.getIndexValue(config, flatDoc);
|
|
10571
|
+
if (indexVal === void 0) continue;
|
|
10572
|
+
const entry = { k, v: indexVal };
|
|
10573
|
+
const batchInsertData = [[k, entry]];
|
|
10574
|
+
if (!indexEntryMap.has(btx)) {
|
|
10575
|
+
indexEntryMap.set(btx, []);
|
|
10339
10576
|
}
|
|
10340
|
-
|
|
10577
|
+
indexEntryMap.get(btx).push(entry);
|
|
10578
|
+
await btx.batchInsert(batchInsertData);
|
|
10341
10579
|
}
|
|
10342
|
-
await btx.batchInsert(batchInsertData);
|
|
10343
10580
|
}
|
|
10344
10581
|
backfilledCount++;
|
|
10345
10582
|
}
|
|
10346
|
-
const btxs = Object.values(
|
|
10583
|
+
const btxs = Object.values(indexTxMap);
|
|
10347
10584
|
const success = [];
|
|
10348
10585
|
try {
|
|
10349
10586
|
for (const btx of btxs) {
|
|
@@ -10355,7 +10592,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10355
10592
|
await btx.rollback();
|
|
10356
10593
|
}
|
|
10357
10594
|
for (const btx of success) {
|
|
10358
|
-
const entries =
|
|
10595
|
+
const entries = indexEntryMap.get(btx);
|
|
10359
10596
|
if (!entries) continue;
|
|
10360
10597
|
for (const entry of entries) {
|
|
10361
10598
|
await btx.delete(entry.k, entry);
|
|
@@ -10374,6 +10611,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10374
10611
|
createdAt: Date.now(),
|
|
10375
10612
|
updatedAt: Date.now(),
|
|
10376
10613
|
lastId: 0,
|
|
10614
|
+
schemeVersion: 0,
|
|
10377
10615
|
indices
|
|
10378
10616
|
};
|
|
10379
10617
|
}
|
|
@@ -10383,7 +10621,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10383
10621
|
throw new Error("Document metadata already exists");
|
|
10384
10622
|
}
|
|
10385
10623
|
const metaObj = this.createDocumentInnerMetadata({
|
|
10386
|
-
_id: [-1,
|
|
10624
|
+
_id: [-1, { type: "btree", fields: ["_id"] }]
|
|
10387
10625
|
});
|
|
10388
10626
|
await this.insertAsOverflow(JSON.stringify(metaObj), false, tx);
|
|
10389
10627
|
}
|
|
@@ -10408,18 +10646,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10408
10646
|
}
|
|
10409
10647
|
/**
|
|
10410
10648
|
* returns flattened document
|
|
10411
|
-
* @param document
|
|
10412
|
-
* @returns
|
|
10649
|
+
* @param document
|
|
10650
|
+
* @returns
|
|
10413
10651
|
*/
|
|
10414
10652
|
flattenDocument(document) {
|
|
10415
10653
|
return this.flatten(document, "", {});
|
|
10416
10654
|
}
|
|
10417
10655
|
async getDocumentMetadata(tx) {
|
|
10418
10656
|
const metadata = await this.getMetadata(tx);
|
|
10657
|
+
const innerMetadata = await this.getDocumentInnerMetadata(tx);
|
|
10658
|
+
const indices = [];
|
|
10659
|
+
for (const name of this.registeredIndices.keys()) {
|
|
10660
|
+
if (name !== "_id") {
|
|
10661
|
+
indices.push(name);
|
|
10662
|
+
}
|
|
10663
|
+
}
|
|
10419
10664
|
return {
|
|
10420
10665
|
pageSize: metadata.pageSize,
|
|
10421
10666
|
pageCount: metadata.pageCount,
|
|
10422
|
-
rowCount: metadata.rowCount
|
|
10667
|
+
rowCount: metadata.rowCount,
|
|
10668
|
+
indices,
|
|
10669
|
+
schemeVersion: innerMetadata.schemeVersion ?? 0
|
|
10423
10670
|
};
|
|
10424
10671
|
}
|
|
10425
10672
|
async getDocumentInnerMetadata(tx) {
|
|
@@ -10432,6 +10679,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10432
10679
|
async updateDocumentInnerMetadata(metadata, tx) {
|
|
10433
10680
|
await this.update(1, JSON.stringify(metadata), tx);
|
|
10434
10681
|
}
|
|
10682
|
+
/**
|
|
10683
|
+
* Run a migration if the current schemeVersion is lower than the target version.
|
|
10684
|
+
* After the callback completes, schemeVersion is updated to the target version.
|
|
10685
|
+
* @param version The target scheme version
|
|
10686
|
+
* @param callback The migration callback
|
|
10687
|
+
* @param tx Optional transaction
|
|
10688
|
+
*/
|
|
10689
|
+
async migration(version, callback, tx) {
|
|
10690
|
+
await this.runWithDefault(async (tx2) => {
|
|
10691
|
+
const innerMetadata = await this.getDocumentInnerMetadata(tx2);
|
|
10692
|
+
const currentVersion = innerMetadata.schemeVersion ?? 0;
|
|
10693
|
+
if (currentVersion < version) {
|
|
10694
|
+
await callback(tx2);
|
|
10695
|
+
innerMetadata.schemeVersion = version;
|
|
10696
|
+
innerMetadata.updatedAt = Date.now();
|
|
10697
|
+
await this.updateDocumentInnerMetadata(innerMetadata, tx2);
|
|
10698
|
+
}
|
|
10699
|
+
}, tx);
|
|
10700
|
+
}
|
|
10435
10701
|
/**
|
|
10436
10702
|
* Transforms a query object into a verbose query object
|
|
10437
10703
|
* @param query The query object to transform
|
|
@@ -10470,33 +10736,74 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10470
10736
|
return result;
|
|
10471
10737
|
}
|
|
10472
10738
|
/**
|
|
10473
|
-
*
|
|
10474
|
-
*
|
|
10739
|
+
* Choose the best index (driver) for the given query.
|
|
10740
|
+
* Scores each index based on field coverage and condition type.
|
|
10741
|
+
*
|
|
10742
|
+
* @param query The verbose query conditions
|
|
10475
10743
|
* @param orderByField Optional field name for orderBy optimization
|
|
10476
10744
|
* @returns Driver and other candidates for query execution
|
|
10477
10745
|
*/
|
|
10478
10746
|
async getSelectivityCandidate(query, orderByField) {
|
|
10747
|
+
const queryFields = new Set(Object.keys(query));
|
|
10479
10748
|
const candidates = [];
|
|
10480
|
-
const
|
|
10481
|
-
|
|
10482
|
-
const tree = this.trees.get(field);
|
|
10749
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10750
|
+
const tree = this.trees.get(indexName);
|
|
10483
10751
|
if (!tree) continue;
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10752
|
+
if (config.type === "btree") {
|
|
10753
|
+
const primaryField = config.fields[0];
|
|
10754
|
+
if (!queryFields.has(primaryField)) continue;
|
|
10755
|
+
const condition = query[primaryField];
|
|
10756
|
+
const treeTx = await tree.createTransaction();
|
|
10757
|
+
let score = 0;
|
|
10758
|
+
const coveredFields = config.fields.filter((f) => queryFields.has(f));
|
|
10759
|
+
score += coveredFields.length;
|
|
10760
|
+
if (condition) {
|
|
10761
|
+
if (typeof condition !== "object" || condition === null) {
|
|
10762
|
+
score += 100;
|
|
10763
|
+
} else if ("primaryEqual" in condition || "equal" in condition) {
|
|
10764
|
+
score += 100;
|
|
10765
|
+
} 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) {
|
|
10766
|
+
score += 50;
|
|
10767
|
+
} else if ("primaryOr" in condition || "or" in condition) {
|
|
10768
|
+
score += 20;
|
|
10769
|
+
} else if ("like" in condition) {
|
|
10770
|
+
score += 15;
|
|
10771
|
+
} else {
|
|
10772
|
+
score += 10;
|
|
10773
|
+
}
|
|
10774
|
+
}
|
|
10775
|
+
if (orderByField && primaryField === orderByField) {
|
|
10776
|
+
score += 200;
|
|
10777
|
+
}
|
|
10778
|
+
const compositeVerifyFields = coveredFields.filter((f) => f !== primaryField);
|
|
10779
|
+
candidates.push({
|
|
10780
|
+
tree: treeTx,
|
|
10781
|
+
condition,
|
|
10782
|
+
field: primaryField,
|
|
10783
|
+
indexName,
|
|
10784
|
+
isFtsMatch: false,
|
|
10785
|
+
score,
|
|
10786
|
+
compositeVerifyFields
|
|
10787
|
+
});
|
|
10788
|
+
} else if (config.type === "fts") {
|
|
10789
|
+
const field = config.fields;
|
|
10790
|
+
if (!queryFields.has(field)) continue;
|
|
10791
|
+
const condition = query[field];
|
|
10792
|
+
if (!condition || typeof condition !== "object" || !("match" in condition)) continue;
|
|
10793
|
+
const treeTx = await tree.createTransaction();
|
|
10794
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10795
|
+
const matchTokens = ftsConfig ? tokenize(condition.match, ftsConfig) : [];
|
|
10796
|
+
candidates.push({
|
|
10797
|
+
tree: treeTx,
|
|
10798
|
+
condition,
|
|
10799
|
+
field,
|
|
10800
|
+
indexName,
|
|
10801
|
+
isFtsMatch: true,
|
|
10802
|
+
matchTokens,
|
|
10803
|
+
score: 90,
|
|
10804
|
+
compositeVerifyFields: []
|
|
10805
|
+
});
|
|
10806
|
+
}
|
|
10500
10807
|
}
|
|
10501
10808
|
const rollback = () => {
|
|
10502
10809
|
for (const { tree } of candidates) {
|
|
@@ -10507,41 +10814,19 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10507
10814
|
rollback();
|
|
10508
10815
|
return null;
|
|
10509
10816
|
}
|
|
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
|
-
};
|
|
10817
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
10818
|
+
const driver = candidates[0];
|
|
10819
|
+
const others = candidates.slice(1);
|
|
10820
|
+
const compositeVerifyConditions = [];
|
|
10821
|
+
for (const field of driver.compositeVerifyFields) {
|
|
10822
|
+
if (query[field]) {
|
|
10823
|
+
compositeVerifyConditions.push({ field, condition: query[field] });
|
|
10535
10824
|
}
|
|
10536
10825
|
}
|
|
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
10826
|
return {
|
|
10543
|
-
driver
|
|
10544
|
-
others
|
|
10827
|
+
driver,
|
|
10828
|
+
others,
|
|
10829
|
+
compositeVerifyConditions,
|
|
10545
10830
|
rollback
|
|
10546
10831
|
};
|
|
10547
10832
|
}
|
|
@@ -10634,7 +10919,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10634
10919
|
orderBy
|
|
10635
10920
|
);
|
|
10636
10921
|
if (!selectivity) return null;
|
|
10637
|
-
const { driver, others, rollback } = selectivity;
|
|
10922
|
+
const { driver, others, compositeVerifyConditions, rollback } = selectivity;
|
|
10638
10923
|
const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
|
|
10639
10924
|
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10640
10925
|
let keys;
|
|
@@ -10651,6 +10936,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10651
10936
|
return {
|
|
10652
10937
|
keys: new Float64Array(Array.from(keys)),
|
|
10653
10938
|
others,
|
|
10939
|
+
compositeVerifyConditions,
|
|
10940
|
+
isDriverOrderByField: useIndexOrder,
|
|
10654
10941
|
rollback
|
|
10655
10942
|
};
|
|
10656
10943
|
}
|
|
@@ -10677,25 +10964,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10677
10964
|
async insertSingleDocument(document, tx) {
|
|
10678
10965
|
return this.writeLock(() => this.runWithDefault(async (tx2) => {
|
|
10679
10966
|
const { pk: dpk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10680
|
-
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10681
10967
|
const flattenDocument = this.flattenDocument(dataplyDocument);
|
|
10682
|
-
for (const
|
|
10683
|
-
const tree = this.trees.get(
|
|
10968
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10969
|
+
const tree = this.trees.get(indexName);
|
|
10684
10970
|
if (!tree) continue;
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
tokens = tokenize(v,
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
if (error) {
|
|
10697
|
-
throw error;
|
|
10971
|
+
if (config.type === "fts") {
|
|
10972
|
+
const primaryField = this.getPrimaryField(config);
|
|
10973
|
+
const v = flattenDocument[primaryField];
|
|
10974
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
10975
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
10976
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
10977
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
10978
|
+
const token = tokens[i];
|
|
10979
|
+
const keyToInsert = this.getTokenKey(dpk, token);
|
|
10980
|
+
const [error] = await catchPromise(tree.insert(keyToInsert, { k: dpk, v: token }));
|
|
10981
|
+
if (error) throw error;
|
|
10698
10982
|
}
|
|
10983
|
+
} else {
|
|
10984
|
+
const indexVal = this.getIndexValue(config, flattenDocument);
|
|
10985
|
+
if (indexVal === void 0) continue;
|
|
10986
|
+
const [error] = await catchPromise(tree.insert(dpk, { k: dpk, v: indexVal }));
|
|
10987
|
+
if (error) throw error;
|
|
10699
10988
|
}
|
|
10700
10989
|
}
|
|
10701
10990
|
return dataplyDocument._id;
|
|
@@ -10731,23 +11020,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10731
11020
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
10732
11021
|
flattenedData[i].pk = pks[i];
|
|
10733
11022
|
}
|
|
10734
|
-
for (const [
|
|
11023
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
11024
|
+
const tree = this.trees.get(indexName);
|
|
11025
|
+
if (!tree) continue;
|
|
10735
11026
|
const treeTx = await tree.createTransaction();
|
|
10736
|
-
const indexConfig = metadata.indices[field]?.[1];
|
|
10737
11027
|
const batchInsertData = [];
|
|
10738
|
-
|
|
10739
|
-
const
|
|
10740
|
-
const
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
tokens = tokenize(v,
|
|
11028
|
+
if (config.type === "fts") {
|
|
11029
|
+
const primaryField = this.getPrimaryField(config);
|
|
11030
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11031
|
+
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
11032
|
+
const item = flattenedData[i];
|
|
11033
|
+
const v = item.data[primaryField];
|
|
11034
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
11035
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
11036
|
+
for (let j = 0, tLen = tokens.length; j < tLen; j++) {
|
|
11037
|
+
const token = tokens[j];
|
|
11038
|
+
batchInsertData.push([this.getTokenKey(item.pk, token), { k: item.pk, v: token }]);
|
|
11039
|
+
}
|
|
10746
11040
|
}
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
const
|
|
10750
|
-
|
|
11041
|
+
} else {
|
|
11042
|
+
for (let i = 0, len = flattenedData.length; i < len; i++) {
|
|
11043
|
+
const item = flattenedData[i];
|
|
11044
|
+
const indexVal = this.getIndexValue(config, item.data);
|
|
11045
|
+
if (indexVal === void 0) continue;
|
|
11046
|
+
batchInsertData.push([item.pk, { k: item.pk, v: indexVal }]);
|
|
10751
11047
|
}
|
|
10752
11048
|
}
|
|
10753
11049
|
const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
|
|
@@ -10773,8 +11069,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10773
11069
|
const pks = await this.getKeys(query);
|
|
10774
11070
|
let updatedCount = 0;
|
|
10775
11071
|
const treeTxs = /* @__PURE__ */ new Map();
|
|
10776
|
-
for (const [
|
|
10777
|
-
treeTxs.set(
|
|
11072
|
+
for (const [indexName, tree] of this.trees) {
|
|
11073
|
+
treeTxs.set(indexName, await tree.createTransaction());
|
|
10778
11074
|
}
|
|
10779
11075
|
treeTxs.delete("_id");
|
|
10780
11076
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
@@ -10784,42 +11080,45 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10784
11080
|
const updatedDoc = computeUpdatedDoc(doc);
|
|
10785
11081
|
const oldFlatDoc = this.flattenDocument(doc);
|
|
10786
11082
|
const newFlatDoc = this.flattenDocument(updatedDoc);
|
|
10787
|
-
const
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
if (
|
|
10797
|
-
oldTokens = tokenize(oldV,
|
|
11083
|
+
for (const [indexName, treeTx] of treeTxs) {
|
|
11084
|
+
const config = this.registeredIndices.get(indexName);
|
|
11085
|
+
if (!config) continue;
|
|
11086
|
+
if (config.type === "fts") {
|
|
11087
|
+
const primaryField = this.getPrimaryField(config);
|
|
11088
|
+
const oldV = oldFlatDoc[primaryField];
|
|
11089
|
+
const newV = newFlatDoc[primaryField];
|
|
11090
|
+
if (oldV === newV) continue;
|
|
11091
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11092
|
+
if (typeof oldV === "string") {
|
|
11093
|
+
const oldTokens = ftsConfig ? tokenize(oldV, ftsConfig) : [oldV];
|
|
11094
|
+
for (let j = 0, jLen = oldTokens.length; j < jLen; j++) {
|
|
11095
|
+
await treeTx.delete(this.getTokenKey(pk, oldTokens[j]), { k: pk, v: oldTokens[j] });
|
|
11096
|
+
}
|
|
10798
11097
|
}
|
|
10799
|
-
|
|
10800
|
-
const
|
|
10801
|
-
const
|
|
10802
|
-
|
|
11098
|
+
if (typeof newV === "string") {
|
|
11099
|
+
const newTokens = ftsConfig ? tokenize(newV, ftsConfig) : [newV];
|
|
11100
|
+
const batchInsertData = [];
|
|
11101
|
+
for (let j = 0, jLen = newTokens.length; j < jLen; j++) {
|
|
11102
|
+
batchInsertData.push([this.getTokenKey(pk, newTokens[j]), { k: pk, v: newTokens[j] }]);
|
|
11103
|
+
}
|
|
11104
|
+
await treeTx.batchInsert(batchInsertData);
|
|
10803
11105
|
}
|
|
10804
|
-
}
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
if (
|
|
10808
|
-
|
|
11106
|
+
} else {
|
|
11107
|
+
const oldIndexVal = this.getIndexValue(config, oldFlatDoc);
|
|
11108
|
+
const newIndexVal = this.getIndexValue(config, newFlatDoc);
|
|
11109
|
+
if (JSON.stringify(oldIndexVal) === JSON.stringify(newIndexVal)) continue;
|
|
11110
|
+
if (oldIndexVal !== void 0) {
|
|
11111
|
+
await treeTx.delete(pk, { k: pk, v: oldIndexVal });
|
|
10809
11112
|
}
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
const newToken = newTokens[j];
|
|
10813
|
-
const keyToInsert = isFts ? this.getTokenKey(pk, newToken) : pk;
|
|
10814
|
-
batchInsertData.push([keyToInsert, { k: pk, v: newToken }]);
|
|
11113
|
+
if (newIndexVal !== void 0) {
|
|
11114
|
+
await treeTx.batchInsert([[pk, { k: pk, v: newIndexVal }]]);
|
|
10815
11115
|
}
|
|
10816
|
-
await treeTx.batchInsert(batchInsertData);
|
|
10817
11116
|
}
|
|
10818
11117
|
}
|
|
10819
11118
|
await this.update(pk, JSON.stringify(updatedDoc), tx);
|
|
10820
11119
|
updatedCount++;
|
|
10821
11120
|
}
|
|
10822
|
-
for (const [
|
|
11121
|
+
for (const [indexName, treeTx] of treeTxs) {
|
|
10823
11122
|
const result = await treeTx.commit();
|
|
10824
11123
|
if (!result.success) {
|
|
10825
11124
|
for (const rollbackTx of treeTxs.values()) {
|
|
@@ -10832,7 +11131,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10832
11131
|
}
|
|
10833
11132
|
/**
|
|
10834
11133
|
* Fully update documents from the database that match the query
|
|
10835
|
-
* @param query The query to use
|
|
11134
|
+
* @param query The query to use
|
|
10836
11135
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
10837
11136
|
* @param tx The transaction to use
|
|
10838
11137
|
* @returns The number of updated documents
|
|
@@ -10847,7 +11146,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10847
11146
|
}
|
|
10848
11147
|
/**
|
|
10849
11148
|
* Partially update documents from the database that match the query
|
|
10850
|
-
* @param query The query to use
|
|
11149
|
+
* @param query The query to use
|
|
10851
11150
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
10852
11151
|
* @param tx The transaction to use
|
|
10853
11152
|
* @returns The number of updated documents
|
|
@@ -10864,7 +11163,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10864
11163
|
}
|
|
10865
11164
|
/**
|
|
10866
11165
|
* Delete documents from the database that match the query
|
|
10867
|
-
* @param query The query to use
|
|
11166
|
+
* @param query The query to use
|
|
10868
11167
|
* @param tx The transaction to use
|
|
10869
11168
|
* @returns The number of deleted documents
|
|
10870
11169
|
*/
|
|
@@ -10877,20 +11176,22 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10877
11176
|
const doc = await this.getDocument(pk, tx2);
|
|
10878
11177
|
if (!doc) continue;
|
|
10879
11178
|
const flatDoc = this.flattenDocument(doc);
|
|
10880
|
-
const
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
if (
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
tokens = tokenize(v,
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
11179
|
+
for (const [indexName, tree] of this.trees) {
|
|
11180
|
+
const config = this.registeredIndices.get(indexName);
|
|
11181
|
+
if (!config) continue;
|
|
11182
|
+
if (config.type === "fts") {
|
|
11183
|
+
const primaryField = this.getPrimaryField(config);
|
|
11184
|
+
const v = flatDoc[primaryField];
|
|
11185
|
+
if (v === void 0 || typeof v !== "string") continue;
|
|
11186
|
+
const ftsConfig = this.getFtsConfig(config);
|
|
11187
|
+
const tokens = ftsConfig ? tokenize(v, ftsConfig) : [v];
|
|
11188
|
+
for (let j = 0, jLen = tokens.length; j < jLen; j++) {
|
|
11189
|
+
await tree.delete(this.getTokenKey(pk, tokens[j]), { k: pk, v: tokens[j] });
|
|
11190
|
+
}
|
|
11191
|
+
} else {
|
|
11192
|
+
const indexVal = this.getIndexValue(config, flatDoc);
|
|
11193
|
+
if (indexVal === void 0) continue;
|
|
11194
|
+
await tree.delete(pk, { k: pk, v: indexVal });
|
|
10894
11195
|
}
|
|
10895
11196
|
}
|
|
10896
11197
|
await super.delete(pk, true, tx2);
|
|
@@ -10901,7 +11202,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10901
11202
|
}
|
|
10902
11203
|
/**
|
|
10903
11204
|
* Count documents from the database that match the query
|
|
10904
|
-
* @param query The query to use
|
|
11205
|
+
* @param query The query to use
|
|
10905
11206
|
* @param tx The transaction to use
|
|
10906
11207
|
* @returns The number of documents that match the query
|
|
10907
11208
|
*/
|
|
@@ -10927,6 +11228,51 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10927
11228
|
}
|
|
10928
11229
|
return true;
|
|
10929
11230
|
}
|
|
11231
|
+
/**
|
|
11232
|
+
* 복합 인덱스의 non-primary 필드에 대해 문서가 유효한지 검증합니다.
|
|
11233
|
+
*/
|
|
11234
|
+
verifyCompositeConditions(doc, conditions) {
|
|
11235
|
+
if (conditions.length === 0) return true;
|
|
11236
|
+
const flatDoc = this.flattenDocument(doc);
|
|
11237
|
+
for (let i = 0, len = conditions.length; i < len; i++) {
|
|
11238
|
+
const { field, condition } = conditions[i];
|
|
11239
|
+
const docValue = flatDoc[field];
|
|
11240
|
+
if (docValue === void 0) return false;
|
|
11241
|
+
const treeValue = { k: doc._id, v: docValue };
|
|
11242
|
+
if (!this.verifyValue(docValue, condition)) return false;
|
|
11243
|
+
}
|
|
11244
|
+
return true;
|
|
11245
|
+
}
|
|
11246
|
+
/**
|
|
11247
|
+
* 단일 값에 대해 verbose 조건을 검증합니다.
|
|
11248
|
+
*/
|
|
11249
|
+
verifyValue(value, condition) {
|
|
11250
|
+
if (typeof condition !== "object" || condition === null) {
|
|
11251
|
+
return value === condition;
|
|
11252
|
+
}
|
|
11253
|
+
if ("primaryEqual" in condition) {
|
|
11254
|
+
return value === condition.primaryEqual?.v;
|
|
11255
|
+
}
|
|
11256
|
+
if ("primaryNotEqual" in condition) {
|
|
11257
|
+
return value !== condition.primaryNotEqual?.v;
|
|
11258
|
+
}
|
|
11259
|
+
if ("primaryLt" in condition) {
|
|
11260
|
+
return value !== null && condition.primaryLt?.v !== void 0 && value < condition.primaryLt.v;
|
|
11261
|
+
}
|
|
11262
|
+
if ("primaryLte" in condition) {
|
|
11263
|
+
return value !== null && condition.primaryLte?.v !== void 0 && value <= condition.primaryLte.v;
|
|
11264
|
+
}
|
|
11265
|
+
if ("primaryGt" in condition) {
|
|
11266
|
+
return value !== null && condition.primaryGt?.v !== void 0 && value > condition.primaryGt.v;
|
|
11267
|
+
}
|
|
11268
|
+
if ("primaryGte" in condition) {
|
|
11269
|
+
return value !== null && condition.primaryGte?.v !== void 0 && value >= condition.primaryGte.v;
|
|
11270
|
+
}
|
|
11271
|
+
if ("primaryOr" in condition && Array.isArray(condition.primaryOr)) {
|
|
11272
|
+
return condition.primaryOr.some((c) => value === c?.v);
|
|
11273
|
+
}
|
|
11274
|
+
return true;
|
|
11275
|
+
}
|
|
10930
11276
|
/**
|
|
10931
11277
|
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
10932
11278
|
*/
|
|
@@ -10939,10 +11285,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10939
11285
|
}
|
|
10940
11286
|
/**
|
|
10941
11287
|
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
10942
|
-
* FTS
|
|
10943
|
-
* 교집합 대신 스트리밍 중 검증하여 첫 결과 반환 시간을 단축합니다.
|
|
11288
|
+
* FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
|
|
10944
11289
|
*/
|
|
10945
|
-
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, others, tx) {
|
|
11290
|
+
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
|
|
10946
11291
|
const verifyOthers = others.filter((o) => !o.isFtsMatch);
|
|
10947
11292
|
let i = startIdx;
|
|
10948
11293
|
const totalKeys = keys.length;
|
|
@@ -10968,6 +11313,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10968
11313
|
const doc = JSON.parse(s);
|
|
10969
11314
|
chunkTotalSize += s.length * 2;
|
|
10970
11315
|
if (ftsConditions.length > 0 && !this.verifyFts(doc, ftsConditions)) continue;
|
|
11316
|
+
if (compositeVerifyConditions.length > 0 && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
|
|
10971
11317
|
if (verifyOthers.length > 0) {
|
|
10972
11318
|
const flatDoc = this.flattenDocument(doc);
|
|
10973
11319
|
let passed = true;
|
|
@@ -10993,7 +11339,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10993
11339
|
}
|
|
10994
11340
|
/**
|
|
10995
11341
|
* Select documents from the database
|
|
10996
|
-
* @param query The query to use
|
|
11342
|
+
* @param query The query to use
|
|
10997
11343
|
* @param options The options to use
|
|
10998
11344
|
* @param tx The transaction to use
|
|
10999
11345
|
* @returns The documents that match the query
|
|
@@ -11017,32 +11363,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11017
11363
|
} = options;
|
|
11018
11364
|
const self = this;
|
|
11019
11365
|
const stream = this.streamWithDefault(async function* (tx2) {
|
|
11020
|
-
const metadata = await self.getDocumentInnerMetadata(tx2);
|
|
11021
11366
|
const ftsConditions = [];
|
|
11022
11367
|
for (const field in query) {
|
|
11023
11368
|
const q = query[field];
|
|
11024
11369
|
if (q && typeof q === "object" && "match" in q && typeof q.match === "string") {
|
|
11025
|
-
const
|
|
11026
|
-
|
|
11027
|
-
|
|
11370
|
+
const indexNames = self.fieldToIndices.get(field) || [];
|
|
11371
|
+
for (const indexName of indexNames) {
|
|
11372
|
+
const config = self.registeredIndices.get(indexName);
|
|
11373
|
+
if (config && config.type === "fts") {
|
|
11374
|
+
const ftsConfig = self.getFtsConfig(config);
|
|
11375
|
+
if (ftsConfig) {
|
|
11376
|
+
ftsConditions.push({ field, matchTokens: tokenize(q.match, ftsConfig) });
|
|
11377
|
+
}
|
|
11378
|
+
break;
|
|
11379
|
+
}
|
|
11028
11380
|
}
|
|
11029
11381
|
}
|
|
11030
11382
|
}
|
|
11031
11383
|
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
|
|
11032
11384
|
if (!driverResult) return;
|
|
11033
|
-
const { keys, others, rollback } = driverResult;
|
|
11385
|
+
const { keys, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
|
|
11034
11386
|
if (keys.length === 0) {
|
|
11035
11387
|
rollback();
|
|
11036
11388
|
return;
|
|
11037
11389
|
}
|
|
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
11390
|
try {
|
|
11047
11391
|
if (!isDriverOrderByField && orderByField) {
|
|
11048
11392
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
@@ -11061,6 +11405,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11061
11405
|
0,
|
|
11062
11406
|
self.options.pageSize,
|
|
11063
11407
|
ftsConditions,
|
|
11408
|
+
compositeVerifyConditions,
|
|
11064
11409
|
others,
|
|
11065
11410
|
tx2
|
|
11066
11411
|
)) {
|
|
@@ -11098,6 +11443,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11098
11443
|
offset,
|
|
11099
11444
|
self.options.pageSize,
|
|
11100
11445
|
ftsConditions,
|
|
11446
|
+
compositeVerifyConditions,
|
|
11101
11447
|
others,
|
|
11102
11448
|
tx2
|
|
11103
11449
|
)) {
|
|
@@ -11131,8 +11477,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11131
11477
|
static Define() {
|
|
11132
11478
|
return {
|
|
11133
11479
|
/**
|
|
11134
|
-
* Sets the options for the database, such as
|
|
11135
|
-
* @template IC The configuration of indices.
|
|
11480
|
+
* Sets the options for the database, such as WAL settings.
|
|
11136
11481
|
* @param options The database initialization options.
|
|
11137
11482
|
*/
|
|
11138
11483
|
Options: (options) => _DocumentDataply.Options(options)
|
|
@@ -11160,6 +11505,30 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11160
11505
|
constructor(file, options) {
|
|
11161
11506
|
this.api = new DocumentDataplyAPI(file, options ?? {});
|
|
11162
11507
|
}
|
|
11508
|
+
/**
|
|
11509
|
+
* Create a named index on the database.
|
|
11510
|
+
* Can be called before or after init().
|
|
11511
|
+
* If called after init(), the index is immediately created and backfilled.
|
|
11512
|
+
* @param name The name of the index
|
|
11513
|
+
* @param option The index configuration (btree or fts)
|
|
11514
|
+
* @param tx Optional transaction
|
|
11515
|
+
* @returns Promise<this> for chaining
|
|
11516
|
+
*/
|
|
11517
|
+
async createIndex(name, option, tx) {
|
|
11518
|
+
await this.api.registerIndex(name, option, tx);
|
|
11519
|
+
return this;
|
|
11520
|
+
}
|
|
11521
|
+
/**
|
|
11522
|
+
* Drop (remove) a named index from the database.
|
|
11523
|
+
* The '_id' index cannot be dropped.
|
|
11524
|
+
* @param name The name of the index to drop
|
|
11525
|
+
* @param tx Optional transaction
|
|
11526
|
+
* @returns Promise<this> for chaining
|
|
11527
|
+
*/
|
|
11528
|
+
async dropIndex(name, tx) {
|
|
11529
|
+
await this.api.dropIndex(name, tx);
|
|
11530
|
+
return this;
|
|
11531
|
+
}
|
|
11163
11532
|
/**
|
|
11164
11533
|
* Initialize the document database
|
|
11165
11534
|
*/
|
|
@@ -11167,6 +11536,17 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11167
11536
|
await this.api.init();
|
|
11168
11537
|
await this.api.backfillIndices();
|
|
11169
11538
|
}
|
|
11539
|
+
/**
|
|
11540
|
+
* Run a migration if the current schemeVersion is lower than the target version.
|
|
11541
|
+
* The callback is only executed when the database's schemeVersion is below the given version.
|
|
11542
|
+
* After the callback completes, schemeVersion is updated to the target version.
|
|
11543
|
+
* @param version The target scheme version
|
|
11544
|
+
* @param callback The migration callback receiving a transaction
|
|
11545
|
+
* @param tx Optional transaction
|
|
11546
|
+
*/
|
|
11547
|
+
async migration(version, callback, tx) {
|
|
11548
|
+
await this.api.migration(version, callback, tx);
|
|
11549
|
+
}
|
|
11170
11550
|
/**
|
|
11171
11551
|
* Get the metadata of the document database
|
|
11172
11552
|
*/
|
|
@@ -11199,7 +11579,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11199
11579
|
}
|
|
11200
11580
|
/**
|
|
11201
11581
|
* Fully update documents from the database that match the query
|
|
11202
|
-
* @param query The query to use
|
|
11582
|
+
* @param query The query to use
|
|
11203
11583
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
11204
11584
|
* @param tx The transaction to use
|
|
11205
11585
|
* @returns The number of updated documents
|
|
@@ -11209,7 +11589,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11209
11589
|
}
|
|
11210
11590
|
/**
|
|
11211
11591
|
* Partially update documents from the database that match the query
|
|
11212
|
-
* @param query The query to use
|
|
11592
|
+
* @param query The query to use
|
|
11213
11593
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
11214
11594
|
* @param tx The transaction to use
|
|
11215
11595
|
* @returns The number of updated documents
|
|
@@ -11219,7 +11599,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11219
11599
|
}
|
|
11220
11600
|
/**
|
|
11221
11601
|
* Delete documents from the database that match the query
|
|
11222
|
-
* @param query The query to use
|
|
11602
|
+
* @param query The query to use
|
|
11223
11603
|
* @param tx The transaction to use
|
|
11224
11604
|
* @returns The number of deleted documents
|
|
11225
11605
|
*/
|
|
@@ -11228,7 +11608,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11228
11608
|
}
|
|
11229
11609
|
/**
|
|
11230
11610
|
* Count documents from the database that match the query
|
|
11231
|
-
* @param query The query to use
|
|
11611
|
+
* @param query The query to use
|
|
11232
11612
|
* @param tx The transaction to use
|
|
11233
11613
|
* @returns The number of documents that match the query
|
|
11234
11614
|
*/
|
|
@@ -11237,7 +11617,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11237
11617
|
}
|
|
11238
11618
|
/**
|
|
11239
11619
|
* Select documents from the database
|
|
11240
|
-
* @param query The query to use
|
|
11620
|
+
* @param query The query to use
|
|
11241
11621
|
* @param options The options to use
|
|
11242
11622
|
* @param tx The transaction to use
|
|
11243
11623
|
* @returns The documents that match the query
|