document-dataply 0.0.8 → 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 +627 -232
- 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 +2 -2
- package/readme.md +69 -20
package/dist/cjs/index.js
CHANGED
|
@@ -6206,6 +6206,22 @@ var require_cjs = __commonJS({
|
|
|
6206
6206
|
}
|
|
6207
6207
|
return (crc ^ -1) >>> 0;
|
|
6208
6208
|
}
|
|
6209
|
+
function getMinMaxValue(array) {
|
|
6210
|
+
let i = 0;
|
|
6211
|
+
let min = Infinity;
|
|
6212
|
+
let max = -Infinity;
|
|
6213
|
+
let len = array.length;
|
|
6214
|
+
while (i < len) {
|
|
6215
|
+
if (array[i] < min) {
|
|
6216
|
+
min = array[i];
|
|
6217
|
+
}
|
|
6218
|
+
if (array[i] > max) {
|
|
6219
|
+
max = array[i];
|
|
6220
|
+
}
|
|
6221
|
+
i++;
|
|
6222
|
+
}
|
|
6223
|
+
return [min, max];
|
|
6224
|
+
}
|
|
6209
6225
|
var Row = class _Row {
|
|
6210
6226
|
static CONSTANT = {
|
|
6211
6227
|
FLAG_DELETED: 0,
|
|
@@ -9120,8 +9136,7 @@ var require_cjs = __commonJS({
|
|
|
9120
9136
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
9121
9137
|
pkIndexMap.set(pks[i], i);
|
|
9122
9138
|
}
|
|
9123
|
-
const minPk =
|
|
9124
|
-
const maxPk = Math.max(...pks);
|
|
9139
|
+
const [minPk, maxPk] = getMinMaxValue(pks);
|
|
9125
9140
|
const pkRidPairs = new Array(pks.length).fill(null);
|
|
9126
9141
|
const btx = await this.getBPTreeTransaction(tx);
|
|
9127
9142
|
const stream = btx.whereStream({ gte: minPk, lte: maxPk });
|
|
@@ -10028,22 +10043,46 @@ var DocumentSerializeStrategyAsync = class extends import_dataply.SerializeStrat
|
|
|
10028
10043
|
|
|
10029
10044
|
// src/core/bptree/documentComparator.ts
|
|
10030
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
|
+
}
|
|
10031
10074
|
var DocumentValueComparator = class extends import_dataply2.ValueComparator {
|
|
10032
10075
|
primaryAsc(a, b) {
|
|
10033
|
-
|
|
10034
|
-
return +a.v - +b.v;
|
|
10035
|
-
}
|
|
10036
|
-
return a.v.localeCompare(b.v);
|
|
10076
|
+
return compareValue(a.v, b.v);
|
|
10037
10077
|
}
|
|
10038
10078
|
asc(a, b) {
|
|
10039
|
-
|
|
10040
|
-
const diff2 = +a.v - +b.v;
|
|
10041
|
-
return diff2 === 0 ? a.k - b.k : diff2;
|
|
10042
|
-
}
|
|
10043
|
-
const diff = a.v.localeCompare(b.v);
|
|
10079
|
+
const diff = compareValue(a.v, b.v);
|
|
10044
10080
|
return diff === 0 ? a.k - b.k : diff;
|
|
10045
10081
|
}
|
|
10046
10082
|
match(value) {
|
|
10083
|
+
if (Array.isArray(value.v)) {
|
|
10084
|
+
return value.v[0] + "";
|
|
10085
|
+
}
|
|
10047
10086
|
return value.v + "";
|
|
10048
10087
|
}
|
|
10049
10088
|
};
|
|
@@ -10156,7 +10195,23 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10156
10195
|
comparator = new DocumentValueComparator();
|
|
10157
10196
|
pendingBackfillFields = [];
|
|
10158
10197
|
lock;
|
|
10198
|
+
_initialized = false;
|
|
10159
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();
|
|
10160
10215
|
operatorConverters = {
|
|
10161
10216
|
equal: "primaryEqual",
|
|
10162
10217
|
notEqual: "primaryNotEqual",
|
|
@@ -10172,11 +10227,6 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10172
10227
|
this.trees = /* @__PURE__ */ new Map();
|
|
10173
10228
|
this.lock = new import_dataply3.Ryoiki();
|
|
10174
10229
|
this.indexedFields = /* @__PURE__ */ new Set(["_id"]);
|
|
10175
|
-
if (options?.indices) {
|
|
10176
|
-
for (const field of Object.keys(options.indices)) {
|
|
10177
|
-
this.indexedFields.add(field);
|
|
10178
|
-
}
|
|
10179
|
-
}
|
|
10180
10230
|
this.hook.onceAfter("init", async (tx, isNewlyCreated) => {
|
|
10181
10231
|
if (isNewlyCreated) {
|
|
10182
10232
|
await this.initializeDocumentFile(tx);
|
|
@@ -10185,31 +10235,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10185
10235
|
throw new Error("Document metadata verification failed");
|
|
10186
10236
|
}
|
|
10187
10237
|
const metadata = await this.getDocumentInnerMetadata(tx);
|
|
10188
|
-
const
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
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
|
+
}
|
|
10193
10244
|
const backfillTargets = [];
|
|
10194
10245
|
let isMetadataChanged = false;
|
|
10195
|
-
for (const
|
|
10196
|
-
const
|
|
10197
|
-
const existingIndex = metadata.indices[field];
|
|
10246
|
+
for (const [indexName, config] of targetIndices) {
|
|
10247
|
+
const existingIndex = metadata.indices[indexName];
|
|
10198
10248
|
if (!existingIndex) {
|
|
10199
|
-
metadata.indices[
|
|
10249
|
+
metadata.indices[indexName] = [-1, config];
|
|
10200
10250
|
isMetadataChanged = true;
|
|
10201
|
-
if (
|
|
10202
|
-
backfillTargets.push(
|
|
10251
|
+
if (!isNewlyCreated) {
|
|
10252
|
+
backfillTargets.push(indexName);
|
|
10203
10253
|
}
|
|
10204
10254
|
} else {
|
|
10205
|
-
const [_pk,
|
|
10206
|
-
if (
|
|
10207
|
-
metadata.indices[
|
|
10208
|
-
isMetadataChanged = true;
|
|
10209
|
-
backfillTargets.push(field);
|
|
10210
|
-
} else if (!isBackfillEnabled && isMetaBackfillEnabled) {
|
|
10211
|
-
metadata.indices[field][1] = false;
|
|
10255
|
+
const [_pk, existingConfig] = existingIndex;
|
|
10256
|
+
if (JSON.stringify(existingConfig) !== JSON.stringify(config)) {
|
|
10257
|
+
metadata.indices[indexName] = [_pk, config];
|
|
10212
10258
|
isMetadataChanged = true;
|
|
10259
|
+
if (!isNewlyCreated) {
|
|
10260
|
+
backfillTargets.push(indexName);
|
|
10261
|
+
}
|
|
10213
10262
|
}
|
|
10214
10263
|
}
|
|
10215
10264
|
}
|
|
@@ -10217,25 +10266,220 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10217
10266
|
await this.updateDocumentInnerMetadata(metadata, tx);
|
|
10218
10267
|
}
|
|
10219
10268
|
this.indices = metadata.indices;
|
|
10220
|
-
|
|
10221
|
-
|
|
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]) {
|
|
10222
10284
|
const tree = new import_dataply3.BPTreeAsync(
|
|
10223
10285
|
new DocumentSerializeStrategyAsync(
|
|
10224
10286
|
this.rowTableEngine.order,
|
|
10225
10287
|
this,
|
|
10226
10288
|
this.txContext,
|
|
10227
|
-
|
|
10289
|
+
indexName
|
|
10228
10290
|
),
|
|
10229
10291
|
this.comparator
|
|
10230
10292
|
);
|
|
10231
10293
|
await tree.init();
|
|
10232
|
-
this.trees.set(
|
|
10294
|
+
this.trees.set(indexName, tree);
|
|
10233
10295
|
}
|
|
10234
10296
|
}
|
|
10235
10297
|
this.pendingBackfillFields = backfillTargets;
|
|
10298
|
+
this._initialized = true;
|
|
10236
10299
|
return tx;
|
|
10237
10300
|
});
|
|
10238
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
|
+
}
|
|
10239
10483
|
async getDocument(pk, tx) {
|
|
10240
10484
|
return this.runWithDefault(async (tx2) => {
|
|
10241
10485
|
const row = await this.select(pk, false, tx2);
|
|
@@ -10264,10 +10508,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10264
10508
|
});
|
|
10265
10509
|
}
|
|
10266
10510
|
/**
|
|
10267
|
-
* Backfill indices for
|
|
10268
|
-
* This method should be called after `init()
|
|
10269
|
-
*
|
|
10270
|
-
*
|
|
10511
|
+
* Backfill indices for newly created indices after data was inserted.
|
|
10512
|
+
* This method should be called after `init()`.
|
|
10513
|
+
*
|
|
10271
10514
|
* @returns Number of documents that were backfilled
|
|
10272
10515
|
*/
|
|
10273
10516
|
async backfillIndices(tx) {
|
|
@@ -10280,12 +10523,12 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10280
10523
|
if (metadata.lastId === 0) {
|
|
10281
10524
|
return 0;
|
|
10282
10525
|
}
|
|
10283
|
-
const
|
|
10284
|
-
const
|
|
10285
|
-
for (const
|
|
10286
|
-
const tree = this.trees.get(
|
|
10287
|
-
if (tree &&
|
|
10288
|
-
|
|
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();
|
|
10289
10532
|
}
|
|
10290
10533
|
}
|
|
10291
10534
|
let backfilledCount = 0;
|
|
@@ -10300,35 +10543,44 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10300
10543
|
const doc = await this.getDocument(k, tx2);
|
|
10301
10544
|
if (!doc) continue;
|
|
10302
10545
|
const flatDoc = this.flattenDocument(doc);
|
|
10303
|
-
for (const
|
|
10304
|
-
if (!(
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
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 });
|
|
10324
10567
|
}
|
|
10325
|
-
|
|
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, []);
|
|
10576
|
+
}
|
|
10577
|
+
indexEntryMap.get(btx).push(entry);
|
|
10578
|
+
await btx.batchInsert(batchInsertData);
|
|
10326
10579
|
}
|
|
10327
|
-
await btx.batchInsert(batchInsertData);
|
|
10328
10580
|
}
|
|
10329
10581
|
backfilledCount++;
|
|
10330
10582
|
}
|
|
10331
|
-
const btxs = Object.values(
|
|
10583
|
+
const btxs = Object.values(indexTxMap);
|
|
10332
10584
|
const success = [];
|
|
10333
10585
|
try {
|
|
10334
10586
|
for (const btx of btxs) {
|
|
@@ -10340,7 +10592,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10340
10592
|
await btx.rollback();
|
|
10341
10593
|
}
|
|
10342
10594
|
for (const btx of success) {
|
|
10343
|
-
const entries =
|
|
10595
|
+
const entries = indexEntryMap.get(btx);
|
|
10344
10596
|
if (!entries) continue;
|
|
10345
10597
|
for (const entry of entries) {
|
|
10346
10598
|
await btx.delete(entry.k, entry);
|
|
@@ -10359,6 +10611,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10359
10611
|
createdAt: Date.now(),
|
|
10360
10612
|
updatedAt: Date.now(),
|
|
10361
10613
|
lastId: 0,
|
|
10614
|
+
schemeVersion: 0,
|
|
10362
10615
|
indices
|
|
10363
10616
|
};
|
|
10364
10617
|
}
|
|
@@ -10368,7 +10621,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10368
10621
|
throw new Error("Document metadata already exists");
|
|
10369
10622
|
}
|
|
10370
10623
|
const metaObj = this.createDocumentInnerMetadata({
|
|
10371
|
-
_id: [-1,
|
|
10624
|
+
_id: [-1, { type: "btree", fields: ["_id"] }]
|
|
10372
10625
|
});
|
|
10373
10626
|
await this.insertAsOverflow(JSON.stringify(metaObj), false, tx);
|
|
10374
10627
|
}
|
|
@@ -10393,18 +10646,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10393
10646
|
}
|
|
10394
10647
|
/**
|
|
10395
10648
|
* returns flattened document
|
|
10396
|
-
* @param document
|
|
10397
|
-
* @returns
|
|
10649
|
+
* @param document
|
|
10650
|
+
* @returns
|
|
10398
10651
|
*/
|
|
10399
10652
|
flattenDocument(document) {
|
|
10400
10653
|
return this.flatten(document, "", {});
|
|
10401
10654
|
}
|
|
10402
10655
|
async getDocumentMetadata(tx) {
|
|
10403
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
|
+
}
|
|
10404
10664
|
return {
|
|
10405
10665
|
pageSize: metadata.pageSize,
|
|
10406
10666
|
pageCount: metadata.pageCount,
|
|
10407
|
-
rowCount: metadata.rowCount
|
|
10667
|
+
rowCount: metadata.rowCount,
|
|
10668
|
+
indices,
|
|
10669
|
+
schemeVersion: innerMetadata.schemeVersion ?? 0
|
|
10408
10670
|
};
|
|
10409
10671
|
}
|
|
10410
10672
|
async getDocumentInnerMetadata(tx) {
|
|
@@ -10417,6 +10679,25 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10417
10679
|
async updateDocumentInnerMetadata(metadata, tx) {
|
|
10418
10680
|
await this.update(1, JSON.stringify(metadata), tx);
|
|
10419
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
|
+
}
|
|
10420
10701
|
/**
|
|
10421
10702
|
* Transforms a query object into a verbose query object
|
|
10422
10703
|
* @param query The query object to transform
|
|
@@ -10455,33 +10736,74 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10455
10736
|
return result;
|
|
10456
10737
|
}
|
|
10457
10738
|
/**
|
|
10458
|
-
*
|
|
10459
|
-
*
|
|
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
|
|
10460
10743
|
* @param orderByField Optional field name for orderBy optimization
|
|
10461
10744
|
* @returns Driver and other candidates for query execution
|
|
10462
10745
|
*/
|
|
10463
10746
|
async getSelectivityCandidate(query, orderByField) {
|
|
10747
|
+
const queryFields = new Set(Object.keys(query));
|
|
10464
10748
|
const candidates = [];
|
|
10465
|
-
const
|
|
10466
|
-
|
|
10467
|
-
const tree = this.trees.get(field);
|
|
10749
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10750
|
+
const tree = this.trees.get(indexName);
|
|
10468
10751
|
if (!tree) continue;
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
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
|
+
}
|
|
10485
10807
|
}
|
|
10486
10808
|
const rollback = () => {
|
|
10487
10809
|
for (const { tree } of candidates) {
|
|
@@ -10492,41 +10814,19 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10492
10814
|
rollback();
|
|
10493
10815
|
return null;
|
|
10494
10816
|
}
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
};
|
|
10503
|
-
}
|
|
10504
|
-
}
|
|
10505
|
-
const ftsCandidate = candidates.find(
|
|
10506
|
-
(c) => c.isFtsMatch && c.matchTokens && c.matchTokens.length > 0
|
|
10507
|
-
);
|
|
10508
|
-
if (ftsCandidate) {
|
|
10509
|
-
const hasHigherPriority = candidates.some((c) => {
|
|
10510
|
-
if (c === ftsCandidate) return false;
|
|
10511
|
-
const cond = c.condition;
|
|
10512
|
-
return "equal" in cond || "primaryEqual" in cond;
|
|
10513
|
-
});
|
|
10514
|
-
if (!hasHigherPriority) {
|
|
10515
|
-
return {
|
|
10516
|
-
driver: ftsCandidate,
|
|
10517
|
-
others: candidates.filter((c) => c !== ftsCandidate),
|
|
10518
|
-
rollback
|
|
10519
|
-
};
|
|
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] });
|
|
10520
10824
|
}
|
|
10521
10825
|
}
|
|
10522
|
-
let res = import_dataply3.BPTreeAsync.ChooseDriver(candidates);
|
|
10523
|
-
if (!res && candidates.length > 0) {
|
|
10524
|
-
res = candidates[0];
|
|
10525
|
-
}
|
|
10526
|
-
if (!res) return null;
|
|
10527
10826
|
return {
|
|
10528
|
-
driver
|
|
10529
|
-
others
|
|
10827
|
+
driver,
|
|
10828
|
+
others,
|
|
10829
|
+
compositeVerifyConditions,
|
|
10530
10830
|
rollback
|
|
10531
10831
|
};
|
|
10532
10832
|
}
|
|
@@ -10619,7 +10919,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10619
10919
|
orderBy
|
|
10620
10920
|
);
|
|
10621
10921
|
if (!selectivity) return null;
|
|
10622
|
-
const { driver, others, rollback } = selectivity;
|
|
10922
|
+
const { driver, others, compositeVerifyConditions, rollback } = selectivity;
|
|
10623
10923
|
const useIndexOrder = orderBy === void 0 || driver.field === orderBy;
|
|
10624
10924
|
const currentOrder = useIndexOrder ? sortOrder : void 0;
|
|
10625
10925
|
let keys;
|
|
@@ -10636,6 +10936,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10636
10936
|
return {
|
|
10637
10937
|
keys: new Float64Array(Array.from(keys)),
|
|
10638
10938
|
others,
|
|
10939
|
+
compositeVerifyConditions,
|
|
10940
|
+
isDriverOrderByField: useIndexOrder,
|
|
10639
10941
|
rollback
|
|
10640
10942
|
};
|
|
10641
10943
|
}
|
|
@@ -10662,25 +10964,27 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10662
10964
|
async insertSingleDocument(document, tx) {
|
|
10663
10965
|
return this.writeLock(() => this.runWithDefault(async (tx2) => {
|
|
10664
10966
|
const { pk: dpk, document: dataplyDocument } = await this.insertDocumentInternal(document, tx2);
|
|
10665
|
-
const metadata = await this.getDocumentInnerMetadata(tx2);
|
|
10666
10967
|
const flattenDocument = this.flattenDocument(dataplyDocument);
|
|
10667
|
-
for (const
|
|
10668
|
-
const tree = this.trees.get(
|
|
10968
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
10969
|
+
const tree = this.trees.get(indexName);
|
|
10669
10970
|
if (!tree) continue;
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
tokens = tokenize(v,
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
if (error) {
|
|
10682
|
-
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;
|
|
10683
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;
|
|
10684
10988
|
}
|
|
10685
10989
|
}
|
|
10686
10990
|
return dataplyDocument._id;
|
|
@@ -10716,23 +11020,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10716
11020
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
10717
11021
|
flattenedData[i].pk = pks[i];
|
|
10718
11022
|
}
|
|
10719
|
-
for (const [
|
|
11023
|
+
for (const [indexName, config] of this.registeredIndices) {
|
|
11024
|
+
const tree = this.trees.get(indexName);
|
|
11025
|
+
if (!tree) continue;
|
|
10720
11026
|
const treeTx = await tree.createTransaction();
|
|
10721
|
-
const indexConfig = metadata.indices[field]?.[1];
|
|
10722
11027
|
const batchInsertData = [];
|
|
10723
|
-
|
|
10724
|
-
const
|
|
10725
|
-
const
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
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
|
+
}
|
|
10731
11040
|
}
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
const
|
|
10735
|
-
|
|
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 }]);
|
|
10736
11047
|
}
|
|
10737
11048
|
}
|
|
10738
11049
|
const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
|
|
@@ -10758,8 +11069,8 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10758
11069
|
const pks = await this.getKeys(query);
|
|
10759
11070
|
let updatedCount = 0;
|
|
10760
11071
|
const treeTxs = /* @__PURE__ */ new Map();
|
|
10761
|
-
for (const [
|
|
10762
|
-
treeTxs.set(
|
|
11072
|
+
for (const [indexName, tree] of this.trees) {
|
|
11073
|
+
treeTxs.set(indexName, await tree.createTransaction());
|
|
10763
11074
|
}
|
|
10764
11075
|
treeTxs.delete("_id");
|
|
10765
11076
|
for (let i = 0, len = pks.length; i < len; i++) {
|
|
@@ -10769,42 +11080,45 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10769
11080
|
const updatedDoc = computeUpdatedDoc(doc);
|
|
10770
11081
|
const oldFlatDoc = this.flattenDocument(doc);
|
|
10771
11082
|
const newFlatDoc = this.flattenDocument(updatedDoc);
|
|
10772
|
-
const
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
if (
|
|
10782
|
-
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
|
+
}
|
|
10783
11097
|
}
|
|
10784
|
-
|
|
10785
|
-
const
|
|
10786
|
-
const
|
|
10787
|
-
|
|
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);
|
|
10788
11105
|
}
|
|
10789
|
-
}
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
if (
|
|
10793
|
-
|
|
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 });
|
|
10794
11112
|
}
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
const newToken = newTokens[j];
|
|
10798
|
-
const keyToInsert = isFts ? this.getTokenKey(pk, newToken) : pk;
|
|
10799
|
-
batchInsertData.push([keyToInsert, { k: pk, v: newToken }]);
|
|
11113
|
+
if (newIndexVal !== void 0) {
|
|
11114
|
+
await treeTx.batchInsert([[pk, { k: pk, v: newIndexVal }]]);
|
|
10800
11115
|
}
|
|
10801
|
-
await treeTx.batchInsert(batchInsertData);
|
|
10802
11116
|
}
|
|
10803
11117
|
}
|
|
10804
11118
|
await this.update(pk, JSON.stringify(updatedDoc), tx);
|
|
10805
11119
|
updatedCount++;
|
|
10806
11120
|
}
|
|
10807
|
-
for (const [
|
|
11121
|
+
for (const [indexName, treeTx] of treeTxs) {
|
|
10808
11122
|
const result = await treeTx.commit();
|
|
10809
11123
|
if (!result.success) {
|
|
10810
11124
|
for (const rollbackTx of treeTxs.values()) {
|
|
@@ -10817,7 +11131,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10817
11131
|
}
|
|
10818
11132
|
/**
|
|
10819
11133
|
* Fully update documents from the database that match the query
|
|
10820
|
-
* @param query The query to use
|
|
11134
|
+
* @param query The query to use
|
|
10821
11135
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
10822
11136
|
* @param tx The transaction to use
|
|
10823
11137
|
* @returns The number of updated documents
|
|
@@ -10832,7 +11146,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10832
11146
|
}
|
|
10833
11147
|
/**
|
|
10834
11148
|
* Partially update documents from the database that match the query
|
|
10835
|
-
* @param query The query to use
|
|
11149
|
+
* @param query The query to use
|
|
10836
11150
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
10837
11151
|
* @param tx The transaction to use
|
|
10838
11152
|
* @returns The number of updated documents
|
|
@@ -10849,7 +11163,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10849
11163
|
}
|
|
10850
11164
|
/**
|
|
10851
11165
|
* Delete documents from the database that match the query
|
|
10852
|
-
* @param query The query to use
|
|
11166
|
+
* @param query The query to use
|
|
10853
11167
|
* @param tx The transaction to use
|
|
10854
11168
|
* @returns The number of deleted documents
|
|
10855
11169
|
*/
|
|
@@ -10862,20 +11176,22 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10862
11176
|
const doc = await this.getDocument(pk, tx2);
|
|
10863
11177
|
if (!doc) continue;
|
|
10864
11178
|
const flatDoc = this.flattenDocument(doc);
|
|
10865
|
-
const
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
if (
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
tokens = tokenize(v,
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
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 });
|
|
10879
11195
|
}
|
|
10880
11196
|
}
|
|
10881
11197
|
await super.delete(pk, true, tx2);
|
|
@@ -10886,7 +11202,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10886
11202
|
}
|
|
10887
11203
|
/**
|
|
10888
11204
|
* Count documents from the database that match the query
|
|
10889
|
-
* @param query The query to use
|
|
11205
|
+
* @param query The query to use
|
|
10890
11206
|
* @param tx The transaction to use
|
|
10891
11207
|
* @returns The number of documents that match the query
|
|
10892
11208
|
*/
|
|
@@ -10912,6 +11228,51 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10912
11228
|
}
|
|
10913
11229
|
return true;
|
|
10914
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
|
+
}
|
|
10915
11276
|
/**
|
|
10916
11277
|
* 메모리 기반으로 청크 크기를 동적 조절합니다.
|
|
10917
11278
|
*/
|
|
@@ -10924,10 +11285,9 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10924
11285
|
}
|
|
10925
11286
|
/**
|
|
10926
11287
|
* Prefetch 방식으로 키 배열을 청크 단위로 조회하여 문서를 순회합니다.
|
|
10927
|
-
* FTS
|
|
10928
|
-
* 교집합 대신 스트리밍 중 검증하여 첫 결과 반환 시간을 단축합니다.
|
|
11288
|
+
* FTS 검증, 복합 인덱스 검증, others 후보에 대한 tree.verify() 검증을 통과한 문서만 yield 합니다.
|
|
10929
11289
|
*/
|
|
10930
|
-
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, others, tx) {
|
|
11290
|
+
async *processChunkedKeysWithVerify(keys, startIdx, initialChunkSize, ftsConditions, compositeVerifyConditions, others, tx) {
|
|
10931
11291
|
const verifyOthers = others.filter((o) => !o.isFtsMatch);
|
|
10932
11292
|
let i = startIdx;
|
|
10933
11293
|
const totalKeys = keys.length;
|
|
@@ -10953,6 +11313,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10953
11313
|
const doc = JSON.parse(s);
|
|
10954
11314
|
chunkTotalSize += s.length * 2;
|
|
10955
11315
|
if (ftsConditions.length > 0 && !this.verifyFts(doc, ftsConditions)) continue;
|
|
11316
|
+
if (compositeVerifyConditions.length > 0 && this.verifyCompositeConditions(doc, compositeVerifyConditions) === false) continue;
|
|
10956
11317
|
if (verifyOthers.length > 0) {
|
|
10957
11318
|
const flatDoc = this.flattenDocument(doc);
|
|
10958
11319
|
let passed = true;
|
|
@@ -10978,7 +11339,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
10978
11339
|
}
|
|
10979
11340
|
/**
|
|
10980
11341
|
* Select documents from the database
|
|
10981
|
-
* @param query The query to use
|
|
11342
|
+
* @param query The query to use
|
|
10982
11343
|
* @param options The options to use
|
|
10983
11344
|
* @param tx The transaction to use
|
|
10984
11345
|
* @returns The documents that match the query
|
|
@@ -11002,32 +11363,30 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11002
11363
|
} = options;
|
|
11003
11364
|
const self = this;
|
|
11004
11365
|
const stream = this.streamWithDefault(async function* (tx2) {
|
|
11005
|
-
const metadata = await self.getDocumentInnerMetadata(tx2);
|
|
11006
11366
|
const ftsConditions = [];
|
|
11007
11367
|
for (const field in query) {
|
|
11008
11368
|
const q = query[field];
|
|
11009
11369
|
if (q && typeof q === "object" && "match" in q && typeof q.match === "string") {
|
|
11010
|
-
const
|
|
11011
|
-
|
|
11012
|
-
|
|
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
|
+
}
|
|
11013
11380
|
}
|
|
11014
11381
|
}
|
|
11015
11382
|
}
|
|
11016
11383
|
const driverResult = await self.getDriverKeys(query, orderByField, sortOrder);
|
|
11017
11384
|
if (!driverResult) return;
|
|
11018
|
-
const { keys, others, rollback } = driverResult;
|
|
11385
|
+
const { keys, others, compositeVerifyConditions, isDriverOrderByField, rollback } = driverResult;
|
|
11019
11386
|
if (keys.length === 0) {
|
|
11020
11387
|
rollback();
|
|
11021
11388
|
return;
|
|
11022
11389
|
}
|
|
11023
|
-
const isQueryEmpty = Object.keys(query).length === 0;
|
|
11024
|
-
const normalizedQuery = isQueryEmpty ? { _id: { gte: 0 } } : query;
|
|
11025
|
-
const selectivity = await self.getSelectivityCandidate(
|
|
11026
|
-
self.verboseQuery(normalizedQuery),
|
|
11027
|
-
orderByField
|
|
11028
|
-
);
|
|
11029
|
-
const isDriverOrderByField = orderByField === void 0 || selectivity && selectivity.driver.field === orderByField;
|
|
11030
|
-
if (selectivity) selectivity.rollback();
|
|
11031
11390
|
try {
|
|
11032
11391
|
if (!isDriverOrderByField && orderByField) {
|
|
11033
11392
|
const topK = limit === Infinity ? Infinity : offset + limit;
|
|
@@ -11046,6 +11405,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11046
11405
|
0,
|
|
11047
11406
|
self.options.pageSize,
|
|
11048
11407
|
ftsConditions,
|
|
11408
|
+
compositeVerifyConditions,
|
|
11049
11409
|
others,
|
|
11050
11410
|
tx2
|
|
11051
11411
|
)) {
|
|
@@ -11083,6 +11443,7 @@ var DocumentDataplyAPI = class extends import_dataply3.DataplyAPI {
|
|
|
11083
11443
|
offset,
|
|
11084
11444
|
self.options.pageSize,
|
|
11085
11445
|
ftsConditions,
|
|
11446
|
+
compositeVerifyConditions,
|
|
11086
11447
|
others,
|
|
11087
11448
|
tx2
|
|
11088
11449
|
)) {
|
|
@@ -11116,8 +11477,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11116
11477
|
static Define() {
|
|
11117
11478
|
return {
|
|
11118
11479
|
/**
|
|
11119
|
-
* Sets the options for the database, such as
|
|
11120
|
-
* @template IC The configuration of indices.
|
|
11480
|
+
* Sets the options for the database, such as WAL settings.
|
|
11121
11481
|
* @param options The database initialization options.
|
|
11122
11482
|
*/
|
|
11123
11483
|
Options: (options) => _DocumentDataply.Options(options)
|
|
@@ -11145,6 +11505,30 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11145
11505
|
constructor(file, options) {
|
|
11146
11506
|
this.api = new DocumentDataplyAPI(file, options ?? {});
|
|
11147
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
|
+
}
|
|
11148
11532
|
/**
|
|
11149
11533
|
* Initialize the document database
|
|
11150
11534
|
*/
|
|
@@ -11152,6 +11536,17 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11152
11536
|
await this.api.init();
|
|
11153
11537
|
await this.api.backfillIndices();
|
|
11154
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
|
+
}
|
|
11155
11550
|
/**
|
|
11156
11551
|
* Get the metadata of the document database
|
|
11157
11552
|
*/
|
|
@@ -11184,7 +11579,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11184
11579
|
}
|
|
11185
11580
|
/**
|
|
11186
11581
|
* Fully update documents from the database that match the query
|
|
11187
|
-
* @param query The query to use
|
|
11582
|
+
* @param query The query to use
|
|
11188
11583
|
* @param newRecord Complete document to replace with, or function that receives current document and returns new document
|
|
11189
11584
|
* @param tx The transaction to use
|
|
11190
11585
|
* @returns The number of updated documents
|
|
@@ -11194,7 +11589,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11194
11589
|
}
|
|
11195
11590
|
/**
|
|
11196
11591
|
* Partially update documents from the database that match the query
|
|
11197
|
-
* @param query The query to use
|
|
11592
|
+
* @param query The query to use
|
|
11198
11593
|
* @param newRecord Partial document to merge, or function that receives current document and returns partial update
|
|
11199
11594
|
* @param tx The transaction to use
|
|
11200
11595
|
* @returns The number of updated documents
|
|
@@ -11204,7 +11599,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11204
11599
|
}
|
|
11205
11600
|
/**
|
|
11206
11601
|
* Delete documents from the database that match the query
|
|
11207
|
-
* @param query The query to use
|
|
11602
|
+
* @param query The query to use
|
|
11208
11603
|
* @param tx The transaction to use
|
|
11209
11604
|
* @returns The number of deleted documents
|
|
11210
11605
|
*/
|
|
@@ -11213,7 +11608,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11213
11608
|
}
|
|
11214
11609
|
/**
|
|
11215
11610
|
* Count documents from the database that match the query
|
|
11216
|
-
* @param query The query to use
|
|
11611
|
+
* @param query The query to use
|
|
11217
11612
|
* @param tx The transaction to use
|
|
11218
11613
|
* @returns The number of documents that match the query
|
|
11219
11614
|
*/
|
|
@@ -11222,7 +11617,7 @@ var DocumentDataply = class _DocumentDataply {
|
|
|
11222
11617
|
}
|
|
11223
11618
|
/**
|
|
11224
11619
|
* Select documents from the database
|
|
11225
|
-
* @param query The query to use
|
|
11620
|
+
* @param query The query to use
|
|
11226
11621
|
* @param options The options to use
|
|
11227
11622
|
* @param tx The transaction to use
|
|
11228
11623
|
* @returns The documents that match the query
|