@objy/server 0.0.0-stage → 0.1.0
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/API.md +1469 -0
- package/LICENSE +21 -0
- package/NOTICE.md +9 -0
- package/README.md +21 -2
- package/objydb.config.json +48 -0
- package/package.json +42 -4
- package/public/index.html +4134 -0
- package/public/objy-db-logo.png +0 -0
- package/src/aggregate.js +438 -0
- package/src/api.js +15 -0
- package/src/auth.js +472 -0
- package/src/clock.js +18 -0
- package/src/cluster-secret.js +25 -0
- package/src/cluster-status.js +562 -0
- package/src/compression-stats.js +32 -0
- package/src/config.js +360 -0
- package/src/crdt.js +1076 -0
- package/src/derived-state-scheduler.js +128 -0
- package/src/files.js +324 -0
- package/src/hnsw.js +269 -0
- package/src/http-cors.js +31 -0
- package/src/http-router.js +162 -0
- package/src/index.js +137 -0
- package/src/interfaces/auth.js +123 -0
- package/src/interfaces/cluster.js +296 -0
- package/src/interfaces/public-object.js +2452 -0
- package/src/interfaces/ui.js +38 -0
- package/src/lib/crypto.cjs +54 -0
- package/src/lib/jwt.cjs +231 -0
- package/src/local-wal.js +122 -0
- package/src/memory-guard.js +152 -0
- package/src/ndjson.js +117 -0
- package/src/node-runtime.js +29 -0
- package/src/object-hooks.js +282 -0
- package/src/object-id.js +7 -0
- package/src/object-storage.js +263 -0
- package/src/object-store.js +288 -0
- package/src/patch.js +87 -0
- package/src/permissions.js +178 -0
- package/src/persistence.js +1918 -0
- package/src/plugin-loader.js +21 -0
- package/src/query-planner.js +266 -0
- package/src/query.js +416 -0
- package/src/references.js +349 -0
- package/src/replication-scheduler.js +303 -0
- package/src/restore-status.js +77 -0
- package/src/runtime.js +47 -0
- package/src/server.js +242 -0
- package/src/shape.js +286 -0
- package/src/shard-map-store.js +85 -0
- package/src/shard-map.js +333 -0
- package/src/shard-router.js +737 -0
- package/src/store.js +3410 -0
- package/src/system-secrets.js +107 -0
- package/src/vector.js +83 -0
package/src/store.js
ADDED
|
@@ -0,0 +1,3410 @@
|
|
|
1
|
+
import { ObjectRowStore } from "./object-store.js";
|
|
2
|
+
import { ShapeStore } from "./shape.js";
|
|
3
|
+
import { gt } from "./clock.js";
|
|
4
|
+
import { percentOf, summarizeCompression } from "./compression-stats.js";
|
|
5
|
+
import { DerivedStateScheduler } from "./derived-state-scheduler.js";
|
|
6
|
+
import {
|
|
7
|
+
aggregateLegacyObjects,
|
|
8
|
+
aggregateObjects,
|
|
9
|
+
aggregatePartialObjects,
|
|
10
|
+
isObjectDbAggregateSpec,
|
|
11
|
+
mergeAggregatePartials,
|
|
12
|
+
} from "./aggregate.js";
|
|
13
|
+
import { HNSWIndex } from "./hnsw.js";
|
|
14
|
+
import { normalizeObjectHooksMeta } from "./object-hooks.js";
|
|
15
|
+
import { compareSortValues, getPathValue, matchesQuery, stableKey, stripQueryControls } from "./query.js";
|
|
16
|
+
import { AGGREGATE_PATH, planAggregate, planCount, planRead, READ_PATH } from "./query-planner.js";
|
|
17
|
+
import { cloneVector, isValidVector, normalizeVectorConfig, rankBySimilarity, VectorIndexWarmingError } from "./vector.js";
|
|
18
|
+
|
|
19
|
+
const COLUMN_BUILD_BATCH_SIZE = 4096;
|
|
20
|
+
const FIELD_BUILD_BATCH_SIZE = 4096;
|
|
21
|
+
const INDEX_DEFER_MIN_ROWS = 5000;
|
|
22
|
+
const INDEX_DIRTY_BUILD_BATCH_SIZE = 4096;
|
|
23
|
+
const INDEX_WARMUP_MAX_HEAP_RATIO = 0.9;
|
|
24
|
+
const INDEX_WARMUP_RETRY_MS = 30_000;
|
|
25
|
+
const VECTOR_BUILD_BATCH_SIZE = 4096;
|
|
26
|
+
const DEFAULT_TIERING_MIN_BYTES = 4096;
|
|
27
|
+
|
|
28
|
+
class Collection {
|
|
29
|
+
constructor(name, options = {}) {
|
|
30
|
+
this.name = name;
|
|
31
|
+
this.disposed = false;
|
|
32
|
+
this.compression = options.compression || null;
|
|
33
|
+
this.vectorConfig = normalizeVectorConfig(options.vector);
|
|
34
|
+
this.vectorField = this.vectorConfig.enabled ? this.vectorConfig.field : null;
|
|
35
|
+
this.store = createPhysicalStore(options);
|
|
36
|
+
this.vectors = new Map();
|
|
37
|
+
this.vectorIndexes = new Map();
|
|
38
|
+
this.vectorBuilds = new Set();
|
|
39
|
+
this.meta = new Map();
|
|
40
|
+
this.idByRid = [];
|
|
41
|
+
this.indexes = new Map();
|
|
42
|
+
this.fieldBuilds = new Map();
|
|
43
|
+
this.columns = new Map();
|
|
44
|
+
this.columnBuilds = new Set();
|
|
45
|
+
this.indexDirtyRids = new RidBitmapQueue();
|
|
46
|
+
this.indexDirtyBuild = null;
|
|
47
|
+
this.indexBuildPending = false;
|
|
48
|
+
this.derivedStateScheduler = options.derivedStateScheduler || new DerivedStateScheduler(options.derivedState);
|
|
49
|
+
this.derivedStateOwner = {};
|
|
50
|
+
this.shardIds = new Map();
|
|
51
|
+
this.unshardedLiveCount = 0;
|
|
52
|
+
this.compressedCold = [];
|
|
53
|
+
this.compressionTotals = emptyCompressionTotals();
|
|
54
|
+
this.collectionMeta = normalizeCollectionMeta();
|
|
55
|
+
this.liveCount = 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_ensureMeta(id) {
|
|
59
|
+
const key = String(id);
|
|
60
|
+
let m = this.meta.get(key);
|
|
61
|
+
if (!m) {
|
|
62
|
+
m = { ts: null, deleted: null, rid: this._allocateRid(key) };
|
|
63
|
+
this.meta.set(key, m);
|
|
64
|
+
} else if (!Number.isInteger(m.rid)) {
|
|
65
|
+
m.rid = this._allocateRid(key);
|
|
66
|
+
}
|
|
67
|
+
return m;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_allocateRid(id) {
|
|
71
|
+
const rid = this.idByRid.length;
|
|
72
|
+
this.idByRid.push(String(id));
|
|
73
|
+
return rid;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_ridForId(id) {
|
|
77
|
+
return this.meta.get(String(id))?.rid;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_idForRid(rid) {
|
|
81
|
+
return this.idByRid[rid];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ensure(id, ts) {
|
|
85
|
+
return this.putObjects([{ id, value: {}, ts }]).length === 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
putObject(id, value, ts, shard = null, options = {}) {
|
|
89
|
+
return this.putObjects([{ id, value, ts, shard }], options).length === 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
putObjects(rows, options = {}) {
|
|
93
|
+
const applied = [];
|
|
94
|
+
const indexDeltas = [];
|
|
95
|
+
const inputRows = rows || [];
|
|
96
|
+
const writeOptions = this._shouldDeferIndexDeltasForCount(inputRows.length)
|
|
97
|
+
? { ...options, keepColumnFields: true }
|
|
98
|
+
: options;
|
|
99
|
+
for (const row of inputRows) {
|
|
100
|
+
if (this._putObject(row.id, row.value, row.ts, row.shard, writeOptions, indexDeltas)) applied.push(row);
|
|
101
|
+
}
|
|
102
|
+
this._applyIndexDeltas(indexDeltas);
|
|
103
|
+
return applied;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
applySnapshotObjects(rows, options = {}) {
|
|
107
|
+
let applied = 0;
|
|
108
|
+
for (const row of rows || []) {
|
|
109
|
+
if (this._applySnapshotObject(row, options)) applied++;
|
|
110
|
+
}
|
|
111
|
+
if (applied && !options.deferDerivedIndexes) this.rebuildDerivedState();
|
|
112
|
+
return applied;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_applySnapshotObject(row = {}, options = {}) {
|
|
116
|
+
if (row.id === undefined || row.id === null) return false;
|
|
117
|
+
const id = String(row.id);
|
|
118
|
+
const m = this._ensureMeta(id);
|
|
119
|
+
|
|
120
|
+
if (row.deleted) {
|
|
121
|
+
const last = m.deleted || m.ts;
|
|
122
|
+
if (last && !gt(row.deleted, last)) return false;
|
|
123
|
+
if (row.ts) m.ts = row.ts;
|
|
124
|
+
m.deleted = row.deleted;
|
|
125
|
+
m.shard = normalizeShard(row.shard) || m.shard || null;
|
|
126
|
+
this.store.removeObject(id);
|
|
127
|
+
this._clearCompressedCold(m.rid);
|
|
128
|
+
this._removeVector(id);
|
|
129
|
+
this._refreshColumns(m.rid, undefined);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!row.ts) return false;
|
|
134
|
+
if (m.deleted && !gt(row.ts, m.deleted)) return false;
|
|
135
|
+
if (m.ts && !gt(row.ts, m.ts)) return false;
|
|
136
|
+
const prepared = this._prepareStoredValue(row.value || {});
|
|
137
|
+
const baseValue = options.deferDerivedIndexes ? prepared.value : this._baseValueForStorage(prepared.value);
|
|
138
|
+
m.ts = row.ts;
|
|
139
|
+
m.deleted = null;
|
|
140
|
+
m.shard = normalizeShard(row.shard) || m.shard || null;
|
|
141
|
+
this._writeBaseValue(id, m.rid, baseValue, { trusted: options.trustedValues === true });
|
|
142
|
+
this._applyVectorUpdate(id, prepared.vector);
|
|
143
|
+
this._refreshColumns(m.rid, prepared.value);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_putObject(id, value, ts, shard = null, options = {}, indexDeltas = null) {
|
|
148
|
+
const key = String(id);
|
|
149
|
+
const m = this._ensureMeta(key);
|
|
150
|
+
const rid = m.rid;
|
|
151
|
+
if (m.deleted && !gt(ts, m.deleted)) return false;
|
|
152
|
+
const wasLive = Boolean(m.ts && !m.deleted);
|
|
153
|
+
const previousShard = wasLive ? m.shard : null;
|
|
154
|
+
const before = wasLive ? this.readForQuery(key) : undefined;
|
|
155
|
+
if (m.ts && !gt(ts, m.ts)) return false;
|
|
156
|
+
const nextShard = normalizeShard(shard) || m.shard || null;
|
|
157
|
+
const prepared = this._prepareStoredValue(value);
|
|
158
|
+
const baseValue = options.keepColumnFields || this._hasDirtyIndexes()
|
|
159
|
+
? prepared.value
|
|
160
|
+
: this._baseValueForStorage(prepared.value);
|
|
161
|
+
m.ts = ts;
|
|
162
|
+
m.deleted = null;
|
|
163
|
+
m.shard = nextShard;
|
|
164
|
+
this._writeBaseValue(key, rid, baseValue, { trusted: options.trustedValues === true });
|
|
165
|
+
this._applyVectorUpdate(key, prepared.vector);
|
|
166
|
+
const after = queryObjectFromStoredValue(key, prepared.value);
|
|
167
|
+
this._recordIndexDelta({ rid, before, after }, indexDeltas);
|
|
168
|
+
if (!wasLive) {
|
|
169
|
+
this.liveCount++;
|
|
170
|
+
this._addShardRid(rid, nextShard);
|
|
171
|
+
} else if (previousShard !== nextShard) {
|
|
172
|
+
this._removeShardRid(rid, previousShard);
|
|
173
|
+
this._addShardRid(rid, nextShard);
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_baseValueForStorage(value = {}, paths = null) {
|
|
179
|
+
let base = value || {};
|
|
180
|
+
if (paths) {
|
|
181
|
+
for (const path of paths) base = unsetPath(base, path);
|
|
182
|
+
return base;
|
|
183
|
+
}
|
|
184
|
+
for (const [path, column] of this.columns) {
|
|
185
|
+
if (column.ready) base = unsetPath(base, path);
|
|
186
|
+
}
|
|
187
|
+
return base;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_writeBaseValue(id, rid, value = {}, options = {}) {
|
|
191
|
+
const baseValue = this._tierBaseValue(rid, value);
|
|
192
|
+
this.store.rebuild(id, Object.entries(baseValue).filter(([field]) => field !== "_id"), {
|
|
193
|
+
trusted: options.trusted === true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_tierBaseValue(rid, value = {}) {
|
|
198
|
+
const tiering = this.collectionMeta.tiering;
|
|
199
|
+
if (!tiering || tiering.mode !== "compressedMemory") {
|
|
200
|
+
this._clearCompressedCold(rid);
|
|
201
|
+
return value || {};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const split = splitTieredValue(value || {}, tiering, this._forcedHotTieringPaths());
|
|
205
|
+
if (!split.cold) {
|
|
206
|
+
this._clearCompressedCold(rid);
|
|
207
|
+
return value || {};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const encoded = this.compression.encodeUtf8(JSON.stringify(split.cold));
|
|
211
|
+
const rawBytes = encoded.byteLength;
|
|
212
|
+
if (rawBytes < tiering.minBytes) {
|
|
213
|
+
this._clearCompressedCold(rid);
|
|
214
|
+
return value || {};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const compact = this.compression.compress(encoded);
|
|
218
|
+
this._setCompressedCold(rid, {
|
|
219
|
+
bytes: rawBytes,
|
|
220
|
+
compressed: compact,
|
|
221
|
+
});
|
|
222
|
+
return split.hot;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_forcedHotTieringPaths() {
|
|
226
|
+
return unique([
|
|
227
|
+
...this.collectionMeta.indexes,
|
|
228
|
+
...this.collectionMeta.columns,
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_clearCompressedCold(rid) {
|
|
233
|
+
this._setCompressedCold(rid, undefined);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_setCompressedCold(rid, entry) {
|
|
237
|
+
if (!Number.isInteger(rid)) return;
|
|
238
|
+
const previous = this.compressedCold[rid];
|
|
239
|
+
if (!previous && !entry) return;
|
|
240
|
+
if (previous) {
|
|
241
|
+
this.compressionTotals.objects--;
|
|
242
|
+
this.compressionTotals.uncompressedBytes -= previous.bytes;
|
|
243
|
+
this.compressionTotals.compressedBytes -= previous.compressed.byteLength;
|
|
244
|
+
}
|
|
245
|
+
this.compressedCold[rid] = entry;
|
|
246
|
+
if (entry) {
|
|
247
|
+
this.compressionTotals.objects++;
|
|
248
|
+
this.compressionTotals.uncompressedBytes += entry.bytes;
|
|
249
|
+
this.compressionTotals.compressedBytes += entry.compressed.byteLength;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_hydrateCompressedCold(value = {}, rid) {
|
|
254
|
+
const entry = this.compressedCold[rid];
|
|
255
|
+
if (!entry) return value || {};
|
|
256
|
+
const cold = JSON.parse(this.compression.decodeUtf8(this.compression.decompress(entry.compressed)));
|
|
257
|
+
return mergePlainObjects(cold, value || {});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_hydrateStoredValue(value = {}, rid, id) {
|
|
261
|
+
return this._hydrateColumns(this._hydrateCompressedCold(value, rid), rid, id);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_hydrateColumns(value = {}, rid, id) {
|
|
265
|
+
let out = { ...(value || {}), _id: id };
|
|
266
|
+
if (this.indexDirtyRids.has(rid)) return out;
|
|
267
|
+
for (const [path, column] of this.columns) {
|
|
268
|
+
if (!column.hasValue(rid)) continue;
|
|
269
|
+
out = setPath(out, path, column.valueFor(rid));
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_rewriteBaseValueForColumns(rid, object = {}, paths = null) {
|
|
275
|
+
const id = this._idForRid(rid);
|
|
276
|
+
if (id === undefined) return;
|
|
277
|
+
const { _id, ...value } = object || {};
|
|
278
|
+
const baseValue = this._baseValueForStorage(value, paths);
|
|
279
|
+
this._writeBaseValue(id, rid, baseValue, { trusted: true });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
deleteObject(id, ts, shard = null) {
|
|
283
|
+
return this.deleteObjects([{ id, ts, shard }]).length === 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
deleteObjects(rows) {
|
|
287
|
+
const applied = [];
|
|
288
|
+
const indexDeltas = [];
|
|
289
|
+
const inputRows = rows || [];
|
|
290
|
+
for (const row of inputRows) {
|
|
291
|
+
if (this._deleteObject(row.id, row.ts, row.shard, indexDeltas)) applied.push(row);
|
|
292
|
+
}
|
|
293
|
+
this._applyIndexDeltas(indexDeltas);
|
|
294
|
+
return applied;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_deleteObject(id, ts, shard = null, indexDeltas = null) {
|
|
298
|
+
const key = String(id);
|
|
299
|
+
const m = this._ensureMeta(key);
|
|
300
|
+
const rid = m.rid;
|
|
301
|
+
const last = m.deleted || m.ts;
|
|
302
|
+
if (last && !gt(ts, last)) return false;
|
|
303
|
+
const wasLive = Boolean(m.ts && !m.deleted);
|
|
304
|
+
const previousShard = wasLive ? m.shard : null;
|
|
305
|
+
const before = wasLive ? this.readForQuery(key) : undefined;
|
|
306
|
+
m.deleted = ts;
|
|
307
|
+
if (shard) m.shard = normalizeShard(shard);
|
|
308
|
+
this.store.removeObject(key);
|
|
309
|
+
this._clearCompressedCold(rid);
|
|
310
|
+
this._removeVector(key);
|
|
311
|
+
this._recordIndexDelta({ rid, before, after: undefined }, indexDeltas);
|
|
312
|
+
if (wasLive) {
|
|
313
|
+
this.liveCount--;
|
|
314
|
+
this._removeShardRid(rid, previousShard);
|
|
315
|
+
}
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
read(id) {
|
|
320
|
+
const key = String(id);
|
|
321
|
+
const m = this.meta.get(key);
|
|
322
|
+
if (!m || m.deleted) return undefined;
|
|
323
|
+
const vals = this.store.read(key) || {};
|
|
324
|
+
return this._hydrateStoredValue(vals, m.rid, key);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
readForQuery(id) {
|
|
328
|
+
const key = String(id);
|
|
329
|
+
const m = this.meta.get(key);
|
|
330
|
+
if (!m || m.deleted) return undefined;
|
|
331
|
+
const vals = typeof this.store.readForQuery === "function"
|
|
332
|
+
? this.store.readForQuery(key) || {}
|
|
333
|
+
: this.store.read(key) || {};
|
|
334
|
+
return this._hydrateStoredValue(vals, m.rid, key);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
readRid(rid) {
|
|
338
|
+
const id = this._idForRid(rid);
|
|
339
|
+
return id === undefined ? undefined : this.read(id);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
readRidForQuery(rid) {
|
|
343
|
+
const id = this._idForRid(rid);
|
|
344
|
+
return id === undefined ? undefined : this.readForQuery(id);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
dispose() {
|
|
348
|
+
this.disposed = true;
|
|
349
|
+
this.derivedStateScheduler.cancel(this.derivedStateOwner);
|
|
350
|
+
this.indexes.clear();
|
|
351
|
+
this.fieldBuilds.clear();
|
|
352
|
+
this.columns.clear();
|
|
353
|
+
this.columnBuilds.clear();
|
|
354
|
+
this.indexDirtyRids.clear();
|
|
355
|
+
this.indexDirtyBuild = null;
|
|
356
|
+
this.indexBuildPending = false;
|
|
357
|
+
this.clearVectorIndexes();
|
|
358
|
+
this.vectors.clear();
|
|
359
|
+
this.meta.clear();
|
|
360
|
+
this.idByRid = [];
|
|
361
|
+
this.compressedCold = [];
|
|
362
|
+
this.compressionTotals = emptyCompressionTotals();
|
|
363
|
+
this.shardIds.clear();
|
|
364
|
+
this.unshardedLiveCount = 0;
|
|
365
|
+
this.liveCount = 0;
|
|
366
|
+
this.collectionMeta = normalizeCollectionMeta();
|
|
367
|
+
this.store.clear?.();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
readObjects(ids) {
|
|
371
|
+
return (ids || []).map((id) => this.read(id)).filter(Boolean);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
has(id) {
|
|
375
|
+
const m = this.meta.get(id);
|
|
376
|
+
return !!m && !m.deleted;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
list(options = {}) {
|
|
380
|
+
return this.find({}, options);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
find(query = {}, options = {}) {
|
|
384
|
+
const criteria = stripQueryControls(query);
|
|
385
|
+
const offset = options.offset || 0;
|
|
386
|
+
const limit = options.limit ?? Infinity;
|
|
387
|
+
const includeId = typeof options.includeId === "function" ? options.includeId : null;
|
|
388
|
+
const plan = this._planRead(criteria, options);
|
|
389
|
+
|
|
390
|
+
if (!options.sortBy) {
|
|
391
|
+
if (limit <= 0) return [];
|
|
392
|
+
const results = [];
|
|
393
|
+
let skipped = 0;
|
|
394
|
+
const verifyScan = plan.path === READ_PATH.SCAN && !isEmptyCriteria(criteria);
|
|
395
|
+
for (const rid of this._ridsForPlan(plan.idPlan, criteria)) {
|
|
396
|
+
const id = includeId ? this._idForRid(rid) : null;
|
|
397
|
+
if (includeId && !includeId(id)) continue;
|
|
398
|
+
if (verifyScan) {
|
|
399
|
+
const obj = this.readRidForQuery(rid);
|
|
400
|
+
if (!obj || !matchesQuery(obj, criteria)) continue;
|
|
401
|
+
}
|
|
402
|
+
if (skipped < offset) {
|
|
403
|
+
skipped++;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const safe = this.readRid(rid);
|
|
407
|
+
if (safe) results.push(safe);
|
|
408
|
+
if (results.length >= limit) break;
|
|
409
|
+
}
|
|
410
|
+
return results;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const indexed = plan.path === READ_PATH.ORDERED_INDEX ? this._findWithOrderedIndex(criteria, options, plan) : null;
|
|
414
|
+
if (indexed) return indexed;
|
|
415
|
+
|
|
416
|
+
const matched = [];
|
|
417
|
+
for (const rid of this._ridsForPlan(plan.idPlan, criteria)) {
|
|
418
|
+
const id = includeId ? this._idForRid(rid) : null;
|
|
419
|
+
if (includeId && !includeId(id)) continue;
|
|
420
|
+
const obj = this.readRidForQuery(rid);
|
|
421
|
+
if (obj && matchesQuery(obj, criteria)) matched.push(obj);
|
|
422
|
+
}
|
|
423
|
+
return sortObjects(matched, options)
|
|
424
|
+
.slice(offset, offset + limit)
|
|
425
|
+
.map((obj) => this.read(obj._id))
|
|
426
|
+
.filter(Boolean);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
all(options = {}) {
|
|
430
|
+
return this.find({}, options);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
findForQuery(query = {}, options = {}) {
|
|
434
|
+
const criteria = stripQueryControls(query);
|
|
435
|
+
const offset = options.offset || 0;
|
|
436
|
+
const limit = options.limit ?? Infinity;
|
|
437
|
+
const includeId = typeof options.includeId === "function" ? options.includeId : null;
|
|
438
|
+
const includeIds = options.includeIds instanceof Set ? options.includeIds : null;
|
|
439
|
+
const plan = this._planRead(criteria, options);
|
|
440
|
+
|
|
441
|
+
if (!options.sortBy) {
|
|
442
|
+
if (limit <= 0) return [];
|
|
443
|
+
const results = [];
|
|
444
|
+
let skipped = 0;
|
|
445
|
+
const verifyScan = plan.path === READ_PATH.SCAN && !isEmptyCriteria(criteria);
|
|
446
|
+
for (const rid of this._ridsForPlan(plan.idPlan, criteria)) {
|
|
447
|
+
if (includeIds && !includeIds.has(rid)) continue;
|
|
448
|
+
const id = includeId ? this._idForRid(rid) : null;
|
|
449
|
+
if (includeId && !includeId(id)) continue;
|
|
450
|
+
const object = this.readRidForQuery(rid);
|
|
451
|
+
if (!object) continue;
|
|
452
|
+
if (verifyScan && !matchesQuery(object, criteria)) continue;
|
|
453
|
+
if (skipped < offset) {
|
|
454
|
+
skipped++;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
results.push(object);
|
|
458
|
+
if (results.length >= limit) break;
|
|
459
|
+
}
|
|
460
|
+
return results;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const matched = [];
|
|
464
|
+
for (const rid of this._ridsForPlan(plan.idPlan, criteria)) {
|
|
465
|
+
if (includeIds && !includeIds.has(rid)) continue;
|
|
466
|
+
const id = includeId ? this._idForRid(rid) : null;
|
|
467
|
+
if (includeId && !includeId(id)) continue;
|
|
468
|
+
const object = this.readRidForQuery(rid);
|
|
469
|
+
if (object && matchesQuery(object, criteria)) matched.push(object);
|
|
470
|
+
}
|
|
471
|
+
return sortObjects(matched, options).slice(offset, offset + limit);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
count(query = {}, options = {}) {
|
|
475
|
+
const criteria = stripQueryControls(query);
|
|
476
|
+
const includeId = typeof options.includeId === "function" ? options.includeId : null;
|
|
477
|
+
const includeIds = options.includeIds instanceof Set ? options.includeIds : null;
|
|
478
|
+
if (!criteria || Object.keys(criteria).length === 0) {
|
|
479
|
+
if (includeIds && includeId) {
|
|
480
|
+
let n = 0;
|
|
481
|
+
for (const rid of includeIds) if (includeId(this._idForRid(rid))) n++;
|
|
482
|
+
return n;
|
|
483
|
+
}
|
|
484
|
+
if (includeIds) return includeIds.size;
|
|
485
|
+
if (includeId) {
|
|
486
|
+
let n = 0;
|
|
487
|
+
for (const rid of this._liveRidIterator()) if (includeId(this._idForRid(rid))) n++;
|
|
488
|
+
return n;
|
|
489
|
+
}
|
|
490
|
+
return this.liveCount;
|
|
491
|
+
}
|
|
492
|
+
const plan = this._planCount(criteria, options);
|
|
493
|
+
if (plan.path === READ_PATH.INDEX_EXACT && !plan.idPlan?.verify) {
|
|
494
|
+
if (includeIds && includeId) {
|
|
495
|
+
let n = 0;
|
|
496
|
+
for (const rid of smallerSet(plan.idPlan.ids, includeIds)) {
|
|
497
|
+
if (plan.idPlan.ids.has(rid) && includeIds.has(rid) && includeId(this._idForRid(rid))) n++;
|
|
498
|
+
}
|
|
499
|
+
return n;
|
|
500
|
+
}
|
|
501
|
+
if (includeIds) return countIntersection(plan.idPlan.ids, includeIds);
|
|
502
|
+
if (!includeId) return plan.idPlan.ids.size;
|
|
503
|
+
let n = 0;
|
|
504
|
+
for (const rid of plan.idPlan.ids) if (includeId(this._idForRid(rid))) n++;
|
|
505
|
+
return n;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let n = 0;
|
|
509
|
+
const verifyScan = plan.path === READ_PATH.SCAN && !isEmptyCriteria(criteria);
|
|
510
|
+
for (const rid of this._ridsForPlan(plan.idPlan, criteria)) {
|
|
511
|
+
if (includeIds && !includeIds.has(rid)) continue;
|
|
512
|
+
const id = includeId ? this._idForRid(rid) : null;
|
|
513
|
+
if (includeId && !includeId(id)) continue;
|
|
514
|
+
if (verifyScan) {
|
|
515
|
+
const obj = this.readRidForQuery(rid);
|
|
516
|
+
if (!obj || !matchesQuery(obj, criteria)) continue;
|
|
517
|
+
}
|
|
518
|
+
n++;
|
|
519
|
+
}
|
|
520
|
+
return n;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
aggregate(query = {}, spec = {}, options = {}) {
|
|
524
|
+
if (isObjectDbAggregateSpec(spec)) {
|
|
525
|
+
const criteria = stripQueryControls(query);
|
|
526
|
+
const indexed = this._executeAggregatePlan(this._planAggregate(criteria, spec, options), criteria, spec, options);
|
|
527
|
+
if (indexed) return indexed;
|
|
528
|
+
|
|
529
|
+
const rows = this.findForQuery(criteria, options);
|
|
530
|
+
return spec?.$partial === true ? aggregatePartialObjects(rows, spec) : aggregateObjects(rows, spec);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return aggregateLegacyObjects(this.findForQuery(query, options), spec);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
createIndex(path) {
|
|
537
|
+
const key = String(path);
|
|
538
|
+
this._createIndexFromMeta(key);
|
|
539
|
+
this.collectionMeta = normalizeCollectionMeta({
|
|
540
|
+
...this.collectionMeta,
|
|
541
|
+
indexes: unique([...this.collectionMeta.indexes, key]),
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
dropIndex(path) {
|
|
546
|
+
const key = String(path);
|
|
547
|
+
const removed = this.indexes.delete(key);
|
|
548
|
+
const building = this.fieldBuilds.delete(key);
|
|
549
|
+
this.collectionMeta = normalizeCollectionMeta({
|
|
550
|
+
...this.collectionMeta,
|
|
551
|
+
indexes: this.collectionMeta.indexes.filter((index) => index !== key),
|
|
552
|
+
});
|
|
553
|
+
return removed || building;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
getMeta() {
|
|
557
|
+
return clone(this.collectionMeta);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
setMeta(meta = {}, options = {}) {
|
|
561
|
+
const previousTiering = stableKey(this.collectionMeta.tiering || null);
|
|
562
|
+
const next = normalizeCollectionMeta(meta);
|
|
563
|
+
if (next.tiering) assertCompressionAdapter(this.compression);
|
|
564
|
+
this.collectionMeta = next;
|
|
565
|
+
if (options.deferIndexes) this._deferMetaIndexes();
|
|
566
|
+
else {
|
|
567
|
+
this._applyMetaIndexes();
|
|
568
|
+
this._applyMetaColumns();
|
|
569
|
+
}
|
|
570
|
+
if (previousTiering !== stableKey(this.collectionMeta.tiering || null)) this._retierBaseValues();
|
|
571
|
+
return this.getMeta();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
indexStats() {
|
|
575
|
+
return [
|
|
576
|
+
...[...this.indexes.entries()].map(([path, index]) => ({
|
|
577
|
+
type: "field",
|
|
578
|
+
path,
|
|
579
|
+
status: this.indexDirtyRids.size ? "building" : "ready",
|
|
580
|
+
keys: index.lookupKeys,
|
|
581
|
+
entries: index.lookupEntries,
|
|
582
|
+
orderedKeys: index.orderedKeys,
|
|
583
|
+
orderedEntries: index.orderedEntries,
|
|
584
|
+
dirtyEntries: this.indexDirtyRids.size,
|
|
585
|
+
})),
|
|
586
|
+
...[...this.fieldBuilds.values()].map((build) => ({
|
|
587
|
+
type: "field",
|
|
588
|
+
path: build.path,
|
|
589
|
+
status: build.status,
|
|
590
|
+
pendingEntries: build.pending?.size || 0,
|
|
591
|
+
...(build.error ? { error: build.error } : {}),
|
|
592
|
+
})),
|
|
593
|
+
...[...this.columns.entries()].map(([path, column]) => ({
|
|
594
|
+
type: "column",
|
|
595
|
+
path,
|
|
596
|
+
status: column.ready && this.indexDirtyRids.size ? "building" : column.status,
|
|
597
|
+
entries: column.entries,
|
|
598
|
+
scalarEntries: column.scalarEntries,
|
|
599
|
+
nonScalarEntries: column.nonScalarEntries,
|
|
600
|
+
pendingEntries: column.pendingEntries + this.indexDirtyRids.size,
|
|
601
|
+
dirtyEntries: this.indexDirtyRids.size,
|
|
602
|
+
numberSum: column.numberSum,
|
|
603
|
+
numberCount: column.numberCount,
|
|
604
|
+
...(column.error ? { error: column.error } : {}),
|
|
605
|
+
})),
|
|
606
|
+
];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
rebuildDerivedState() {
|
|
610
|
+
const fieldPaths = [...this.collectionMeta.indexes];
|
|
611
|
+
const columnPaths = [...this.collectionMeta.columns];
|
|
612
|
+
this.liveCount = 0;
|
|
613
|
+
this.shardIds.clear();
|
|
614
|
+
this.unshardedLiveCount = 0;
|
|
615
|
+
const fieldIndexes = new Map(fieldPaths.map((path) => [path, new FieldIndex(path)]));
|
|
616
|
+
const columnData = new Map(columnPaths.map((path) => [path, new TypedRidColumn(path)]));
|
|
617
|
+
|
|
618
|
+
for (const [id, m] of this.meta) {
|
|
619
|
+
if (m.deleted) continue;
|
|
620
|
+
this.liveCount++;
|
|
621
|
+
this._addShardRid(m.rid, m.shard || null);
|
|
622
|
+
const object = this.readForQuery(id);
|
|
623
|
+
if (!object) continue;
|
|
624
|
+
for (const [path, index] of fieldIndexes) index.addValue(m.rid, getPathValue(object, path));
|
|
625
|
+
for (const [path, column] of columnData) column.updateScalarState(m.rid, columnScalarState(getPathValue(object, path)));
|
|
626
|
+
this._rewriteBaseValueForColumns(m.rid, object, columnPaths);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
this.indexes = fieldIndexes;
|
|
630
|
+
this.fieldBuilds.clear();
|
|
631
|
+
this.columns = columnData;
|
|
632
|
+
this.columnBuilds.clear();
|
|
633
|
+
this.indexDirtyRids.clear();
|
|
634
|
+
this.indexDirtyBuild = null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
rebuildCoreState() {
|
|
638
|
+
this.liveCount = 0;
|
|
639
|
+
this.shardIds.clear();
|
|
640
|
+
this.unshardedLiveCount = 0;
|
|
641
|
+
|
|
642
|
+
for (const m of this.meta.values()) {
|
|
643
|
+
if (m.deleted) continue;
|
|
644
|
+
this.liveCount++;
|
|
645
|
+
this._addShardRid(m.rid, m.shard || null);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
scheduleIndexBuildsFromMeta() {
|
|
650
|
+
this._applyMetaIndexes({ background: true, rebuild: true });
|
|
651
|
+
this._applyMetaColumns({ rebuild: true });
|
|
652
|
+
this.indexBuildPending = false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async awaitFieldIndexes() {
|
|
656
|
+
for (;;) {
|
|
657
|
+
const builds = [...this.fieldBuilds.values()]
|
|
658
|
+
.filter((build) => build.status === "building" && build.promise)
|
|
659
|
+
.map((build) => build.promise);
|
|
660
|
+
if (!builds.length) return;
|
|
661
|
+
await Promise.allSettled(builds);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async awaitColumns() {
|
|
666
|
+
for (;;) {
|
|
667
|
+
const builds = [...this.columnBuilds];
|
|
668
|
+
if (!builds.length) return;
|
|
669
|
+
await Promise.allSettled(builds);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async awaitDirtyIndexes() {
|
|
674
|
+
for (;;) {
|
|
675
|
+
const build = this.indexDirtyBuild;
|
|
676
|
+
if (!build) {
|
|
677
|
+
if (this.indexDirtyRids.size) {
|
|
678
|
+
this._scheduleDirtyIndexCatchup();
|
|
679
|
+
if (this.indexDirtyBuild) continue;
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
await Promise.allSettled([build]);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
shapeStats() {
|
|
688
|
+
return this.store.shapeStats().map((entry) => ({
|
|
689
|
+
...entry,
|
|
690
|
+
ridTable: {
|
|
691
|
+
live: this.liveCount,
|
|
692
|
+
allocated: this.idByRid.length,
|
|
693
|
+
},
|
|
694
|
+
tiering: this.compressionStats(),
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
compressionStats() {
|
|
699
|
+
const tiering = this.collectionMeta.tiering;
|
|
700
|
+
const { objects, uncompressedBytes, compressedBytes } = this.compressionTotals;
|
|
701
|
+
const savedBytes = uncompressedBytes - compressedBytes;
|
|
702
|
+
return {
|
|
703
|
+
mode: tiering?.mode || "off",
|
|
704
|
+
objects,
|
|
705
|
+
liveObjects: this.liveCount,
|
|
706
|
+
compressedObjects: objects,
|
|
707
|
+
coveragePercent: percentOf(objects, this.liveCount),
|
|
708
|
+
rawBytes: uncompressedBytes,
|
|
709
|
+
uncompressedBytes,
|
|
710
|
+
compressedBytes,
|
|
711
|
+
savedBytes,
|
|
712
|
+
savedPercent: percentOf(savedBytes, uncompressedBytes),
|
|
713
|
+
...(tiering ? {
|
|
714
|
+
policy: {
|
|
715
|
+
...(tiering.keep?.length ? { keep: [...tiering.keep] } : {}),
|
|
716
|
+
...(tiering.offload?.length ? { offload: [...tiering.offload] } : {}),
|
|
717
|
+
minBytes: tiering.minBytes,
|
|
718
|
+
},
|
|
719
|
+
} : {}),
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
_retierBaseValues() {
|
|
724
|
+
for (const rid of this._liveRidIterator()) {
|
|
725
|
+
const object = this.readRidForQuery(rid);
|
|
726
|
+
if (object) this._rewriteBaseValueForColumns(rid, object);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
*snapshotObjects({ includeId } = {}) {
|
|
731
|
+
const allow = typeof includeId === "function" ? includeId : () => true;
|
|
732
|
+
for (const [id, m] of this.meta) {
|
|
733
|
+
if (!allow(id)) continue;
|
|
734
|
+
if (m.deleted) {
|
|
735
|
+
yield { id, ts: m.ts, deleted: m.deleted, ...(m.shard ? { shard: m.shard } : {}) };
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const { _id, ...value } = this.readForQuery(id) || {};
|
|
739
|
+
const vector = this.getVector(id);
|
|
740
|
+
if (vector && this.vectorField) value[this.vectorField] = vector;
|
|
741
|
+
yield { id, ts: m.ts, deleted: null, value, ...(m.shard ? { shard: m.shard } : {}) };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
getVector(id) {
|
|
746
|
+
const vector = this.vectors.get(String(id));
|
|
747
|
+
return vector ? [...vector] : null;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
preserveVector(id, value = {}) {
|
|
751
|
+
if (!this.vectorField || Object.prototype.hasOwnProperty.call(value || {}, this.vectorField)) return value;
|
|
752
|
+
const vector = this.getVector(id);
|
|
753
|
+
return vector ? { ...value, [this.vectorField]: vector } : value;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
validateWriteValue(value = {}) {
|
|
757
|
+
this._extractVector(value);
|
|
758
|
+
return value;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
vectorSearch(queryVec, limit = 20, options = {}) {
|
|
762
|
+
if (!this.vectorField || !isValidVector(queryVec)) return [];
|
|
763
|
+
if (this.vectors.size === 0) return [];
|
|
764
|
+
const index = this._readyVectorIndex(options.indexKey || "all", options.includeId);
|
|
765
|
+
const k = Math.max(0, Number.isFinite(limit) ? Math.floor(limit) : 20);
|
|
766
|
+
if (k <= 0) return [];
|
|
767
|
+
return index.search(queryVec, k, options.ef).map(({ id, dist }) => ({
|
|
768
|
+
_id: id,
|
|
769
|
+
dist,
|
|
770
|
+
_score: 1 - dist,
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
rankVectorRows(rows, queryVec, limit = 20) {
|
|
775
|
+
return rankBySimilarity(rows, queryVec, {
|
|
776
|
+
limit,
|
|
777
|
+
getVec: (id) => this.getVector(id),
|
|
778
|
+
idOf: (row) => row?._id,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
vectorStats() {
|
|
783
|
+
let nodes = 0;
|
|
784
|
+
const entries = [];
|
|
785
|
+
for (const [key, entry] of this.vectorIndexes) {
|
|
786
|
+
nodes += entry.index.size;
|
|
787
|
+
entries.push({
|
|
788
|
+
key,
|
|
789
|
+
status: entry.status,
|
|
790
|
+
nodes: entry.index.size,
|
|
791
|
+
scanned: entry.scanned,
|
|
792
|
+
inserted: entry.inserted,
|
|
793
|
+
...(entry.startedAt ? { startedAt: entry.startedAt } : {}),
|
|
794
|
+
...(entry.readyAt ? { readyAt: entry.readyAt } : {}),
|
|
795
|
+
...(entry.error ? { error: entry.error } : {}),
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
field: this.vectorField,
|
|
800
|
+
stored: this.vectors.size,
|
|
801
|
+
indexes: this.vectorIndexes.size,
|
|
802
|
+
nodes,
|
|
803
|
+
status: this._vectorIndexStatus(),
|
|
804
|
+
indexStates: entries,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
indexSummary() {
|
|
809
|
+
const hasVector = this.vectors.size > 0 || this.vectorIndexes.size > 0 || this.vectorBuilds.size > 0;
|
|
810
|
+
let activeFieldBuilds = this.indexDirtyBuild ? 1 : 0;
|
|
811
|
+
for (const build of this.fieldBuilds.values()) {
|
|
812
|
+
if (build.status === "building") activeFieldBuilds++;
|
|
813
|
+
}
|
|
814
|
+
const statuses = [
|
|
815
|
+
...this.collectionMeta.indexes.map((path) => this._fieldIndexStatus(path)),
|
|
816
|
+
...this.collectionMeta.columns.map((path) => {
|
|
817
|
+
const column = this.columns.get(path);
|
|
818
|
+
return column?.ready && this.indexDirtyRids.size
|
|
819
|
+
? "building"
|
|
820
|
+
: column?.status || (this.indexBuildPending ? "pending" : "missing");
|
|
821
|
+
}),
|
|
822
|
+
...(hasVector ? [this._vectorIndexStatus()] : []),
|
|
823
|
+
];
|
|
824
|
+
return {
|
|
825
|
+
configured: statuses.length > 0,
|
|
826
|
+
status: overallIndexStatus(statuses),
|
|
827
|
+
activeTasks: activeFieldBuilds + this.columnBuilds.size + this.vectorBuilds.size,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
indexReadiness() {
|
|
832
|
+
const fieldIndexes = this.collectionMeta.indexes.map((path) => ({
|
|
833
|
+
path,
|
|
834
|
+
status: this._fieldIndexStatus(path),
|
|
835
|
+
pendingEntries: (this.fieldBuilds.get(path)?.pending?.size || 0) + this.indexDirtyRids.size,
|
|
836
|
+
dirtyEntries: this.indexDirtyRids.size,
|
|
837
|
+
...(this.fieldBuilds.get(path)?.error ? { error: this.fieldBuilds.get(path).error } : {}),
|
|
838
|
+
}));
|
|
839
|
+
const columns = this.collectionMeta.columns.map((path) => {
|
|
840
|
+
const column = this.columns.get(path);
|
|
841
|
+
return {
|
|
842
|
+
path,
|
|
843
|
+
status: column?.ready && this.indexDirtyRids.size ? "building" : column?.status || (this.indexBuildPending ? "pending" : "missing"),
|
|
844
|
+
pendingEntries: (column?.pendingEntries || 0) + this.indexDirtyRids.size,
|
|
845
|
+
dirtyEntries: this.indexDirtyRids.size,
|
|
846
|
+
...(column?.error ? { error: column.error } : {}),
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
const vector = this.vectorStats();
|
|
850
|
+
const workers = {
|
|
851
|
+
fieldIndex: {
|
|
852
|
+
type: "same_thread_task",
|
|
853
|
+
active: [...this.fieldBuilds.values()].filter((build) => build.status === "building").length + (this.indexDirtyBuild ? 1 : 0),
|
|
854
|
+
background: true,
|
|
855
|
+
},
|
|
856
|
+
columns: {
|
|
857
|
+
type: "same_thread_task",
|
|
858
|
+
active: this.columnBuilds.size,
|
|
859
|
+
background: true,
|
|
860
|
+
},
|
|
861
|
+
vectorIndex: {
|
|
862
|
+
type: "same_thread_task",
|
|
863
|
+
active: this.vectorBuilds.size,
|
|
864
|
+
background: true,
|
|
865
|
+
},
|
|
866
|
+
};
|
|
867
|
+
const hasVector = hasVectorIndexValue(vector);
|
|
868
|
+
const statuses = [
|
|
869
|
+
...fieldIndexes.map((index) => index.status),
|
|
870
|
+
...columns.map((column) => column.status),
|
|
871
|
+
...(hasVector ? [vector.status] : []),
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
status: overallIndexStatus(statuses),
|
|
876
|
+
fieldIndexes,
|
|
877
|
+
columns,
|
|
878
|
+
...(hasVector ? { vector } : {}),
|
|
879
|
+
workers,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
clearVectorIndexes() {
|
|
884
|
+
this.vectorIndexes.clear();
|
|
885
|
+
this.vectorBuilds.clear();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
assertVectorIndexReady(key = "all", includeId = null) {
|
|
889
|
+
if (!this.vectorField || this.vectors.size === 0) return true;
|
|
890
|
+
this._readyVectorIndex(key, includeId);
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
warmVectorIndex(key = "all", includeId = null) {
|
|
895
|
+
if (!this.vectorField || this.vectors.size === 0) return Promise.resolve(null);
|
|
896
|
+
const indexKey = String(key || "all");
|
|
897
|
+
const current = this.vectorIndexes.get(indexKey);
|
|
898
|
+
if (current?.status === "ready") return Promise.resolve(current);
|
|
899
|
+
if (current?.status === "building" && current.promise) return current.promise;
|
|
900
|
+
return this._startVectorIndexWarmup(indexKey, includeId);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async awaitVectorIndexes() {
|
|
904
|
+
for (;;) {
|
|
905
|
+
const builds = [...this.vectorBuilds];
|
|
906
|
+
if (!builds.length) return;
|
|
907
|
+
await Promise.allSettled(builds);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
snapshot(options = {}) {
|
|
912
|
+
const out = [];
|
|
913
|
+
for (const object of this.snapshotObjects(options)) out.push(object);
|
|
914
|
+
return out;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
*_liveRidIterator() {
|
|
918
|
+
for (const m of this.meta.values()) {
|
|
919
|
+
if (!m.deleted) yield m.rid;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
_planRead(criteria, options = {}) {
|
|
924
|
+
return planRead(criteria, options, this._plannerAdapter());
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
_planCount(criteria, options = {}) {
|
|
928
|
+
return planCount(criteria, options, this._plannerAdapter());
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
_planAggregate(criteria, spec, options = {}) {
|
|
932
|
+
return planAggregate(criteria, spec, options, this._plannerAdapter());
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
readPath(criteria = {}, options = {}) {
|
|
936
|
+
return this._planRead(stripQueryControls(criteria), options).path;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
aggregatePath(criteria = {}, spec = {}, options = {}) {
|
|
940
|
+
if (!isObjectDbAggregateSpec(spec)) return AGGREGATE_PATH.SCAN;
|
|
941
|
+
return this._planAggregate(stripQueryControls(criteria), spec, options).path;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
_plannerAdapter() {
|
|
945
|
+
return {
|
|
946
|
+
idsForCriteria: (criteria) => this._ridsForCriteria(criteria),
|
|
947
|
+
partialIdsForCriteria: (criteria) => this._partialRidsForCriteria(criteria),
|
|
948
|
+
hasOrderedIndex: (path) => !this._hasDirtyIndexes() && this.indexes.has(String(path)),
|
|
949
|
+
hasGroupIndex: (path) => !this._hasDirtyIndexes() && this.indexes.has(String(path)),
|
|
950
|
+
hasGroupForwardIndex: (path) => !this._hasDirtyIndexes() && this.indexes.get(String(path))?.canReadValuesById === true,
|
|
951
|
+
fieldIndex: (path) => this.indexes.get(String(path)) || null,
|
|
952
|
+
column: (path) => this._hasDirtyIndexes() ? null : this.columns.get(String(path)) || null,
|
|
953
|
+
idsForAll: (includeId) => this._ridSetForAll(includeId),
|
|
954
|
+
filterIds: (ids, includeId) => this._filterRidSet(ids, includeId),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
_hasDirtyIndexes() {
|
|
959
|
+
return this.indexDirtyRids.size > 0;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
_ridSetForAll(includeId = null) {
|
|
963
|
+
const rids = new Set();
|
|
964
|
+
for (const [id, m] of this.meta) {
|
|
965
|
+
if (!m.deleted && (!includeId || includeId(id))) rids.add(m.rid);
|
|
966
|
+
}
|
|
967
|
+
return rids;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
_filterRidSet(rids, includeId = null) {
|
|
971
|
+
if (!includeId) return rids;
|
|
972
|
+
const out = new Set();
|
|
973
|
+
for (const rid of rids || []) if (includeId(this._idForRid(rid))) out.add(rid);
|
|
974
|
+
return out;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
idsForShards(shards = []) {
|
|
978
|
+
if (this.unshardedLiveCount > 0) return null;
|
|
979
|
+
const names = [...new Set((shards || []).map(normalizeShard).filter(Boolean))];
|
|
980
|
+
if (!names.length) return null;
|
|
981
|
+
if (names.length === 1) return this.shardIds.get(names[0]) || new Set();
|
|
982
|
+
let rids = new Set();
|
|
983
|
+
for (const shard of names) rids = setUnion(rids, this.shardIds.get(shard) || new Set());
|
|
984
|
+
return rids;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
*_ridsForPlan(idPlan, criteria = {}) {
|
|
988
|
+
const rids = idPlan?.ids === null || idPlan?.ids === undefined
|
|
989
|
+
? this._liveRidIterator()
|
|
990
|
+
: idPlan.ids;
|
|
991
|
+
for (const rid of rids) {
|
|
992
|
+
if (idPlan?.scopeIds && !idPlan.scopeIds.has(rid)) continue;
|
|
993
|
+
if (!idPlan?.verify) {
|
|
994
|
+
yield rid;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const object = this.readRidForQuery(rid);
|
|
998
|
+
if (object && matchesQuery(object, criteria)) yield rid;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
_ridSetForPlan(idPlan, criteria = {}) {
|
|
1003
|
+
if (!idPlan || idPlan.ids === null) return null;
|
|
1004
|
+
if (!idPlan.verify) return idPlan.scopeIds ? setIntersection(idPlan.ids, idPlan.scopeIds) : idPlan.ids;
|
|
1005
|
+
const rids = new Set();
|
|
1006
|
+
for (const rid of this._ridsForPlan(idPlan, criteria)) rids.add(rid);
|
|
1007
|
+
return rids;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
_planIndexedIds(query) {
|
|
1011
|
+
if (!query || Object.keys(query).length === 0) return null;
|
|
1012
|
+
const exact = this._ridsForCriteria(query);
|
|
1013
|
+
if (exact) return { ids: exact, exact: true };
|
|
1014
|
+
const partial = this._partialRidsForCriteria(query);
|
|
1015
|
+
return partial ? { ids: partial, exact: false } : null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
_ridsForCriteria(query) {
|
|
1019
|
+
const entries = Object.entries(query || {});
|
|
1020
|
+
if (!entries.length) return null;
|
|
1021
|
+
|
|
1022
|
+
let current = null;
|
|
1023
|
+
for (const [path, expected] of entries) {
|
|
1024
|
+
let rids = null;
|
|
1025
|
+
|
|
1026
|
+
if (path === "$and" && Array.isArray(expected)) {
|
|
1027
|
+
const childSets = expected
|
|
1028
|
+
.filter((child) => !isEmptyCriteria(child))
|
|
1029
|
+
.map((child) => this._ridsForCriteria(child));
|
|
1030
|
+
if (childSets.some((set) => set === null)) return null;
|
|
1031
|
+
rids = childSets.length ? childSets.reduce((left, right) => setIntersection(left, right)) : null;
|
|
1032
|
+
} else if (path === "$or" && Array.isArray(expected)) {
|
|
1033
|
+
const childSets = expected.map((child) => this._ridsForCriteria(child));
|
|
1034
|
+
if (childSets.some((set) => set === null)) return null;
|
|
1035
|
+
rids = childSets.length ? childSets.reduce((left, right) => setUnion(left, right)) : new Set();
|
|
1036
|
+
} else if (path.startsWith("$")) {
|
|
1037
|
+
return null;
|
|
1038
|
+
} else {
|
|
1039
|
+
rids = this._ridsForFieldConstraint(path, expected);
|
|
1040
|
+
if (rids === null) return null;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (rids === null) continue;
|
|
1044
|
+
current = current === null ? rids : setIntersection(current, rids);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return current;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
_partialRidsForCriteria(query) {
|
|
1051
|
+
const entries = Object.entries(query || {});
|
|
1052
|
+
if (!entries.length) return null;
|
|
1053
|
+
|
|
1054
|
+
let current = null;
|
|
1055
|
+
for (const [path, expected] of entries) {
|
|
1056
|
+
if (path === "$and" && Array.isArray(expected)) {
|
|
1057
|
+
for (const child of expected) {
|
|
1058
|
+
if (isEmptyCriteria(child)) continue;
|
|
1059
|
+
const rids = this._ridsForCriteria(child) || this._partialRidsForCriteria(child);
|
|
1060
|
+
if (rids) current = current === null ? rids : setIntersection(current, rids);
|
|
1061
|
+
}
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (path === "$or" && Array.isArray(expected)) {
|
|
1066
|
+
const rids = this._ridsForCriteria({ $or: expected });
|
|
1067
|
+
if (rids) current = current === null ? rids : setIntersection(current, rids);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (path.startsWith("$")) continue;
|
|
1072
|
+
|
|
1073
|
+
const rids = this._ridsForFieldConstraint(path, expected);
|
|
1074
|
+
if (rids !== null) current = current === null ? rids : setIntersection(current, rids);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return current;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
_ridsForFieldConstraint(path, expected) {
|
|
1081
|
+
if (path === "_id") return this._ridsForObjectIdConstraint(expected);
|
|
1082
|
+
|
|
1083
|
+
const index = this.indexes.get(path);
|
|
1084
|
+
if (!index) return null;
|
|
1085
|
+
|
|
1086
|
+
const containsValue = indexableContainsValue(expected);
|
|
1087
|
+
if (containsValue !== null) return this._dirtyAwareIndexedRids(path, expected, index.idsForContains(containsValue));
|
|
1088
|
+
|
|
1089
|
+
const values = indexableConstraintValues(expected);
|
|
1090
|
+
if (values === null) return null;
|
|
1091
|
+
return this._dirtyAwareIndexedRids(path, expected, index.idsForValues(values));
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
_dirtyAwareIndexedRids(path, expected, indexed) {
|
|
1095
|
+
if (indexed === null) return null;
|
|
1096
|
+
if (!this.indexDirtyRids.size) return indexed;
|
|
1097
|
+
|
|
1098
|
+
const out = new Set();
|
|
1099
|
+
for (const rid of indexed) {
|
|
1100
|
+
if (!this.indexDirtyRids.has(rid)) out.add(rid);
|
|
1101
|
+
}
|
|
1102
|
+
for (const rid of this.indexDirtyRids) {
|
|
1103
|
+
const object = this.readRidForQuery(rid);
|
|
1104
|
+
if (object && matchesQuery(object, { [path]: expected })) out.add(rid);
|
|
1105
|
+
}
|
|
1106
|
+
return out;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
_ridsForObjectIdConstraint(expected) {
|
|
1110
|
+
const values = indexableConstraintValues(expected);
|
|
1111
|
+
if (values === null) return null;
|
|
1112
|
+
|
|
1113
|
+
const rids = new Set();
|
|
1114
|
+
for (const value of values) {
|
|
1115
|
+
const id = String(value);
|
|
1116
|
+
const rid = this._ridForId(id);
|
|
1117
|
+
if (rid !== undefined && this.has(id)) rids.add(rid);
|
|
1118
|
+
}
|
|
1119
|
+
return rids;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
*_candidateIds(query) {
|
|
1123
|
+
if (!query || Object.keys(query).length === 0) {
|
|
1124
|
+
for (const [id, m] of this.meta) if (!m.deleted) yield id;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const plan = this._planIndexedIds(query);
|
|
1128
|
+
for (const rid of plan?.ids || this._liveRidIterator()) {
|
|
1129
|
+
const id = this._idForRid(rid);
|
|
1130
|
+
if (id !== undefined) yield id;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
_recordIndexDelta(delta, indexDeltas = null) {
|
|
1135
|
+
if (indexDeltas) {
|
|
1136
|
+
indexDeltas.push(delta);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
this._applyIndexDeltas([delta]);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
_refreshIndexes(rid, before, after) {
|
|
1143
|
+
this._applyIndexDeltas([{ rid, before, after }]);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
_refreshColumns(rid, after) {
|
|
1147
|
+
this._applyIndexDeltas([{ rid, before: undefined, after }]);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
_addToIndexes(rid, object) {
|
|
1151
|
+
this._applyIndexDeltas([{ rid, before: undefined, after: object }]);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
_removeFromIndexes(rid, object) {
|
|
1155
|
+
this._applyIndexDeltas([{ rid, before: object, after: undefined }]);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
_applyIndexDeltas(deltas = []) {
|
|
1159
|
+
if (!deltas?.length) return;
|
|
1160
|
+
if (this._shouldDeferIndexDeltas(deltas)) {
|
|
1161
|
+
for (const delta of deltas) this._markIndexDirty(delta);
|
|
1162
|
+
this._scheduleDirtyIndexCatchup();
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
for (const { rid, before, after } of deltas) {
|
|
1167
|
+
if (!Number.isInteger(rid)) continue;
|
|
1168
|
+
this._recordFieldBuildDelta(rid, before, after);
|
|
1169
|
+
this._recordColumnBuildDelta(rid, after);
|
|
1170
|
+
if (this.indexDirtyRids.has(rid)) continue;
|
|
1171
|
+
|
|
1172
|
+
for (const index of this.indexes.values()) {
|
|
1173
|
+
if (before) index.removeValue(rid, getPathValue(before, index.path));
|
|
1174
|
+
if (after) index.addValue(rid, getPathValue(after, index.path));
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
for (const column of this.columns.values()) {
|
|
1178
|
+
if (column.status === "building") continue;
|
|
1179
|
+
if (after) column.updateScalarState(rid, columnScalarState(getPathValue(after, column.path)));
|
|
1180
|
+
else column.remove(rid);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
_shouldDeferIndexDeltas(deltas = []) {
|
|
1186
|
+
return this._shouldDeferIndexDeltasForCount(deltas.length);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
_shouldDeferIndexDeltasForCount(count = 0) {
|
|
1190
|
+
return count >= INDEX_DEFER_MIN_ROWS && this._hasIndexWork();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
_hasIndexWork() {
|
|
1194
|
+
return Boolean(
|
|
1195
|
+
this.indexes.size ||
|
|
1196
|
+
this.columns.size ||
|
|
1197
|
+
this.fieldBuilds.size ||
|
|
1198
|
+
this.columnBuilds.size,
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
_markIndexDirty({ rid, before, after } = {}) {
|
|
1203
|
+
if (!Number.isInteger(rid)) return;
|
|
1204
|
+
this._recordFieldBuildDelta(rid, before, after);
|
|
1205
|
+
this._recordColumnBuildDelta(rid, after);
|
|
1206
|
+
this.indexDirtyRids.add(rid);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
_recordColumnBuildDelta(rid, after) {
|
|
1210
|
+
for (const column of this.columns.values()) {
|
|
1211
|
+
if (column.status !== "building") continue;
|
|
1212
|
+
if (after) column.updateScalarState(rid, columnScalarState(getPathValue(after, column.path)));
|
|
1213
|
+
else column.remove(rid);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
_scheduleDirtyIndexCatchup() {
|
|
1218
|
+
if (this.indexDirtyBuild || !this.indexDirtyRids.size) return;
|
|
1219
|
+
const build = this.derivedStateScheduler.schedule(
|
|
1220
|
+
`dirty-index:${this.name}`,
|
|
1221
|
+
(job) => this._runDirtyIndexCatchup(job),
|
|
1222
|
+
{ owner: this.derivedStateOwner },
|
|
1223
|
+
);
|
|
1224
|
+
this.indexDirtyBuild = build;
|
|
1225
|
+
build.then(
|
|
1226
|
+
() => {
|
|
1227
|
+
if (this.indexDirtyBuild === build) this.indexDirtyBuild = null;
|
|
1228
|
+
if (this.indexDirtyRids.size) this._scheduleDirtyIndexCatchup();
|
|
1229
|
+
},
|
|
1230
|
+
() => {
|
|
1231
|
+
if (this.indexDirtyBuild === build) this.indexDirtyBuild = null;
|
|
1232
|
+
},
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async _runDirtyIndexCatchup(job) {
|
|
1237
|
+
if (this.disposed) return;
|
|
1238
|
+
while (this.indexDirtyRids.size) {
|
|
1239
|
+
if (this.disposed) return;
|
|
1240
|
+
const batch = this.indexDirtyRids.take(INDEX_DIRTY_BUILD_BATCH_SIZE);
|
|
1241
|
+
if (!batch.length) {
|
|
1242
|
+
this.indexDirtyRids.clear();
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
for (const rid of batch) {
|
|
1247
|
+
this._refreshReadyIndexesForRid(rid);
|
|
1248
|
+
this.indexDirtyRids.delete(rid);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (this.indexDirtyRids.size) await job.checkpoint(INDEX_DIRTY_BUILD_BATCH_SIZE);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
_refreshReadyIndexesForRid(rid) {
|
|
1256
|
+
const object = this.readRidForQuery(rid);
|
|
1257
|
+
for (const index of this.indexes.values()) {
|
|
1258
|
+
index.removeRid(rid);
|
|
1259
|
+
if (object) index.addValue(rid, getPathValue(object, index.path));
|
|
1260
|
+
}
|
|
1261
|
+
for (const column of this.columns.values()) {
|
|
1262
|
+
if (object) column.updateScalarState(rid, columnScalarState(getPathValue(object, column.path)));
|
|
1263
|
+
else column.remove(rid);
|
|
1264
|
+
}
|
|
1265
|
+
if (object) this._rewriteBaseValueForColumns(rid, object);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
_addShardRid(rid, shard) {
|
|
1269
|
+
if (!shard) {
|
|
1270
|
+
this.unshardedLiveCount++;
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const key = String(shard);
|
|
1274
|
+
const rids = this.shardIds.get(key) || new Set();
|
|
1275
|
+
rids.add(rid);
|
|
1276
|
+
this.shardIds.set(key, rids);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
_removeShardRid(rid, shard) {
|
|
1280
|
+
if (!shard) {
|
|
1281
|
+
if (this.unshardedLiveCount > 0) this.unshardedLiveCount--;
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const rids = this.shardIds.get(String(shard));
|
|
1285
|
+
rids?.delete(rid);
|
|
1286
|
+
if (rids?.size === 0) this.shardIds.delete(String(shard));
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
_applyMetaIndexes(options = {}) {
|
|
1290
|
+
const background = options.background === true;
|
|
1291
|
+
const rebuild = options.rebuild === true;
|
|
1292
|
+
const wanted = new Set(this.collectionMeta.indexes);
|
|
1293
|
+
for (const path of [...this.indexes.keys()]) {
|
|
1294
|
+
if (!wanted.has(path)) this.indexes.delete(path);
|
|
1295
|
+
}
|
|
1296
|
+
for (const path of [...this.fieldBuilds.keys()]) {
|
|
1297
|
+
if (!wanted.has(path)) this.fieldBuilds.delete(path);
|
|
1298
|
+
}
|
|
1299
|
+
const toBuild = [];
|
|
1300
|
+
for (const path of wanted) {
|
|
1301
|
+
if (this.indexes.has(path) && !rebuild) continue;
|
|
1302
|
+
if (this.fieldBuilds.has(path) && !rebuild) continue;
|
|
1303
|
+
if (this.liveCount === 0) {
|
|
1304
|
+
this.fieldBuilds.delete(path);
|
|
1305
|
+
this.indexes.set(path, new FieldIndex(path));
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (background) toBuild.push(path);
|
|
1309
|
+
else this._createIndexFromMeta(path);
|
|
1310
|
+
}
|
|
1311
|
+
if (toBuild.length) this._startFieldIndexBuild(toBuild);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
_createIndexFromMeta(path) {
|
|
1315
|
+
this.fieldBuilds.delete(path);
|
|
1316
|
+
const index = new FieldIndex(path);
|
|
1317
|
+
for (const rid of this._liveRidIterator()) {
|
|
1318
|
+
const object = this.readRidForQuery(rid);
|
|
1319
|
+
if (object) index.addValue(rid, getPathValue(object, path));
|
|
1320
|
+
}
|
|
1321
|
+
this.indexes.set(path, index);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
_deferMetaIndexes() {
|
|
1325
|
+
const wantedColumns = new Set(this.collectionMeta.columns);
|
|
1326
|
+
this.indexes.clear();
|
|
1327
|
+
this.fieldBuilds.clear();
|
|
1328
|
+
this.indexBuildPending = true;
|
|
1329
|
+
for (const path of [...this.columns.keys()]) {
|
|
1330
|
+
if (!wantedColumns.has(path)) this.columns.delete(path);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
_fieldIndexStatus(path) {
|
|
1335
|
+
if (this.indexes.has(path)) return this.indexDirtyRids.size ? "building" : "ready";
|
|
1336
|
+
const build = this.fieldBuilds.get(path);
|
|
1337
|
+
if (!build) return this.indexBuildPending ? "pending" : "missing";
|
|
1338
|
+
return build.status || "building";
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
_startFieldIndexBuild(paths) {
|
|
1342
|
+
const targets = [];
|
|
1343
|
+
for (const path of paths) {
|
|
1344
|
+
this.indexes.delete(path);
|
|
1345
|
+
const build = {
|
|
1346
|
+
path,
|
|
1347
|
+
status: "building",
|
|
1348
|
+
pending: new Map(),
|
|
1349
|
+
error: null,
|
|
1350
|
+
startedAt: new Date().toISOString(),
|
|
1351
|
+
promise: null,
|
|
1352
|
+
};
|
|
1353
|
+
this.fieldBuilds.set(path, build);
|
|
1354
|
+
targets.push(build);
|
|
1355
|
+
}
|
|
1356
|
+
if (!targets.length) return null;
|
|
1357
|
+
|
|
1358
|
+
const promise = this.derivedStateScheduler.schedule(
|
|
1359
|
+
`field-index:${this.name}:${targets.map((target) => target.path).join(",")}`,
|
|
1360
|
+
(job) => this._runFieldIndexBuild(targets, job),
|
|
1361
|
+
{ owner: this.derivedStateOwner },
|
|
1362
|
+
);
|
|
1363
|
+
for (const target of targets) target.promise = promise;
|
|
1364
|
+
promise.catch(() => {});
|
|
1365
|
+
return promise;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
async _runFieldIndexBuild(targets, job) {
|
|
1369
|
+
if (this.disposed) return;
|
|
1370
|
+
const built = new Map(targets.map((target) => [target.path, new FieldIndex(target.path)]));
|
|
1371
|
+
try {
|
|
1372
|
+
let scanned = 0;
|
|
1373
|
+
for (const rid of this._liveRidIterator()) {
|
|
1374
|
+
if (this.disposed) return;
|
|
1375
|
+
const object = this.readRidForQuery(rid);
|
|
1376
|
+
if (object) {
|
|
1377
|
+
for (const target of targets) {
|
|
1378
|
+
if (this.fieldBuilds.get(target.path) === target) {
|
|
1379
|
+
built.get(target.path)?.addValue(rid, getPathValue(object, target.path));
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
scanned++;
|
|
1384
|
+
if (scanned % FIELD_BUILD_BATCH_SIZE === 0) await job.checkpoint(scanned);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
for (const target of targets) {
|
|
1388
|
+
if (this.fieldBuilds.get(target.path) !== target) continue;
|
|
1389
|
+
const index = built.get(target.path);
|
|
1390
|
+
if (!index) continue;
|
|
1391
|
+
for (const [rid, change] of target.pending) {
|
|
1392
|
+
if (change.before !== NO_VALUE) index.removeValue(rid, change.before);
|
|
1393
|
+
if (change.after !== NO_VALUE) index.addValue(rid, change.after);
|
|
1394
|
+
}
|
|
1395
|
+
this.indexes.set(target.path, index);
|
|
1396
|
+
this.fieldBuilds.delete(target.path);
|
|
1397
|
+
}
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
for (const target of targets) {
|
|
1400
|
+
if (this.fieldBuilds.get(target.path) !== target) continue;
|
|
1401
|
+
target.status = "error";
|
|
1402
|
+
target.error = error?.message || String(error);
|
|
1403
|
+
target.pending = new Map();
|
|
1404
|
+
}
|
|
1405
|
+
throw error;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
_recordFieldBuildDelta(rid, before, after) {
|
|
1410
|
+
if (!this.fieldBuilds.size) return;
|
|
1411
|
+
for (const build of this.fieldBuilds.values()) {
|
|
1412
|
+
if (build.status !== "building") continue;
|
|
1413
|
+
const current = build.pending.get(rid);
|
|
1414
|
+
build.pending.set(rid, {
|
|
1415
|
+
before: current ? current.before : (before ? getPathValue(before, build.path) : NO_VALUE),
|
|
1416
|
+
after: after ? getPathValue(after, build.path) : NO_VALUE,
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
_applyMetaColumns(options = {}) {
|
|
1422
|
+
const rebuild = options.rebuild === true;
|
|
1423
|
+
const wanted = new Set(this.collectionMeta.columns);
|
|
1424
|
+
for (const path of [...this.columns.keys()]) {
|
|
1425
|
+
if (!wanted.has(path)) this.columns.delete(path);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const toBuild = [];
|
|
1429
|
+
for (const path of wanted) {
|
|
1430
|
+
let column = this.columns.get(path);
|
|
1431
|
+
const created = !column;
|
|
1432
|
+
if (!column) {
|
|
1433
|
+
column = new TypedRidColumn(path);
|
|
1434
|
+
this.columns.set(path, column);
|
|
1435
|
+
}
|
|
1436
|
+
if (this.liveCount === 0) {
|
|
1437
|
+
const buildId = column.startBuild();
|
|
1438
|
+
column.install(new TypedRidColumn(path), buildId);
|
|
1439
|
+
}
|
|
1440
|
+
else if (created || rebuild || column.status === "error") toBuild.push(path);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (toBuild.length) this._startColumnBuild(toBuild);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
_rebuildColumns() {
|
|
1447
|
+
const paths = [...this.columns.keys()];
|
|
1448
|
+
if (!paths.length) return;
|
|
1449
|
+
if (this.liveCount === 0) {
|
|
1450
|
+
for (const column of this.columns.values()) {
|
|
1451
|
+
const buildId = column.startBuild();
|
|
1452
|
+
column.install(new TypedRidColumn(column.path), buildId);
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
this._startColumnBuild(paths);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
_startColumnBuild(paths) {
|
|
1460
|
+
const targets = [];
|
|
1461
|
+
for (const path of paths) {
|
|
1462
|
+
const column = this.columns.get(path);
|
|
1463
|
+
if (!column) continue;
|
|
1464
|
+
const buildId = column.startBuild();
|
|
1465
|
+
targets.push({ path, buildId });
|
|
1466
|
+
}
|
|
1467
|
+
if (!targets.length) return null;
|
|
1468
|
+
|
|
1469
|
+
const build = this.derivedStateScheduler.schedule(
|
|
1470
|
+
`column:${this.name}:${targets.map((target) => target.path).join(",")}`,
|
|
1471
|
+
(job) => this._runColumnBuild(targets, job),
|
|
1472
|
+
{ owner: this.derivedStateOwner },
|
|
1473
|
+
);
|
|
1474
|
+
this.columnBuilds.add(build);
|
|
1475
|
+
build.then(
|
|
1476
|
+
() => this.columnBuilds.delete(build),
|
|
1477
|
+
() => this.columnBuilds.delete(build),
|
|
1478
|
+
);
|
|
1479
|
+
return build;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
async _runColumnBuild(targets, job) {
|
|
1483
|
+
if (this.disposed) return;
|
|
1484
|
+
const paths = targets.map((target) => target.path);
|
|
1485
|
+
const built = new Map(paths.map((path) => [path, new TypedRidColumn(path)]));
|
|
1486
|
+
try {
|
|
1487
|
+
let scanned = 0;
|
|
1488
|
+
for (const rid of this._liveRidIterator()) {
|
|
1489
|
+
if (this.disposed) return;
|
|
1490
|
+
const object = this.readRidForQuery(rid);
|
|
1491
|
+
if (object) {
|
|
1492
|
+
for (const path of paths) {
|
|
1493
|
+
const column = built.get(path);
|
|
1494
|
+
if (!column) continue;
|
|
1495
|
+
column.updateScalarState(rid, columnScalarState(getPathValue(object, path)));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
scanned++;
|
|
1499
|
+
if (scanned % COLUMN_BUILD_BATCH_SIZE === 0) await job.checkpoint(scanned);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const installedPaths = [];
|
|
1503
|
+
for (const { path, buildId } of targets) {
|
|
1504
|
+
const target = this.columns.get(path);
|
|
1505
|
+
const column = built.get(path);
|
|
1506
|
+
if (target && column && target.install(column, buildId)) installedPaths.push(path);
|
|
1507
|
+
}
|
|
1508
|
+
await this._stripBaseValuesForColumns(installedPaths, job);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
for (const { path, buildId } of targets) {
|
|
1511
|
+
const column = this.columns.get(path);
|
|
1512
|
+
if (column) column.fail(error, buildId);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
async _stripBaseValuesForColumns(paths = [], job = null) {
|
|
1518
|
+
if (!paths.length) return;
|
|
1519
|
+
let scanned = 0;
|
|
1520
|
+
for (const rid of this._liveRidIterator()) {
|
|
1521
|
+
if (this.disposed) return;
|
|
1522
|
+
const object = this.readRidForQuery(rid);
|
|
1523
|
+
if (object) this._rewriteBaseValueForColumns(rid, object, paths);
|
|
1524
|
+
scanned++;
|
|
1525
|
+
if (scanned % COLUMN_BUILD_BATCH_SIZE === 0 && job) await job.checkpoint(scanned);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
_executeAggregatePlan(plan, criteria, spec) {
|
|
1530
|
+
switch (plan.path) {
|
|
1531
|
+
case AGGREGATE_PATH.COLUMN_SUMMARY:
|
|
1532
|
+
return this._aggregateFlatFromColumnSummary(spec, plan.normalized, plan.columns);
|
|
1533
|
+
case AGGREGATE_PATH.COLUMN_SCAN:
|
|
1534
|
+
return finishFlatColumnAggregate(
|
|
1535
|
+
scanFlatColumnAggregate(this._ridsForPlan(plan.idPlan, criteria), plan.normalized, plan.columns),
|
|
1536
|
+
spec,
|
|
1537
|
+
plan.normalized,
|
|
1538
|
+
);
|
|
1539
|
+
case AGGREGATE_PATH.GROUP_FORWARD_INDEX:
|
|
1540
|
+
return this._aggregateGroupForwardIndex(plan, criteria, spec);
|
|
1541
|
+
case AGGREGATE_PATH.GROUP_INDEX:
|
|
1542
|
+
return this._aggregateGroupIndex(plan, criteria, spec);
|
|
1543
|
+
case AGGREGATE_PATH.GROUP_COLUMN_SCAN: {
|
|
1544
|
+
const grouped = scanGroupedColumnAggregate(this._ridsForPlan(plan.idPlan, criteria), plan.normalized, plan.columns);
|
|
1545
|
+
return grouped ? finishGroupedColumnAggregate(grouped, spec, plan.normalized) : null;
|
|
1546
|
+
}
|
|
1547
|
+
default:
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
_aggregateFlatFromColumnSummary(spec, normalized, columns) {
|
|
1553
|
+
if (this.liveCount === 0) return [];
|
|
1554
|
+
|
|
1555
|
+
const state = {};
|
|
1556
|
+
for (const field of normalized.fields) {
|
|
1557
|
+
switch (field.operator) {
|
|
1558
|
+
case "$sum": {
|
|
1559
|
+
state[field.resultKey] = { sum: columns.get(field.operand).numberSum };
|
|
1560
|
+
break;
|
|
1561
|
+
}
|
|
1562
|
+
case "$avg": {
|
|
1563
|
+
const column = columns.get(field.operand);
|
|
1564
|
+
state[field.resultKey] = { sum: column.numberSum, count: column.numberCount };
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
case "$count": {
|
|
1568
|
+
state[field.resultKey] = { count: field.operand === true ? this.liveCount : columns.get(field.operand).entries };
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
return finishFlatColumnAggregate({ rowsSeen: this.liveCount, state }, spec, normalized);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
_aggregateGroupIndex(plan, criteria, spec) {
|
|
1578
|
+
const groups = [];
|
|
1579
|
+
let rowsSeen = 0;
|
|
1580
|
+
|
|
1581
|
+
if (isRowCountOnlyAggregate(plan.normalized)) {
|
|
1582
|
+
const filterIds = plan.idPlan?.verify ? this._ridSetForPlan(plan.idPlan, criteria) : plan.idPlan?.ids || null;
|
|
1583
|
+
const scopeIds = plan.idPlan?.verify ? null : plan.idPlan?.scopeIds || null;
|
|
1584
|
+
for (const [groupValue, count] of plan.groupIndex.iterateGroupCounts(filterIds, scopeIds)) {
|
|
1585
|
+
if (count <= 0) continue;
|
|
1586
|
+
rowsSeen += count;
|
|
1587
|
+
groups.push({
|
|
1588
|
+
groupValues: { [plan.groupBy]: groupValue === undefined ? null : groupValue },
|
|
1589
|
+
state: rowCountOnlyState(plan.normalized, count),
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return finishGroupedColumnAggregate({ rowsSeen, groups }, spec, plan.normalized);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const filterIds = this._ridSetForPlan(plan.idPlan, criteria);
|
|
1596
|
+
for (const [groupValue, ids] of plan.groupIndex.iterateGroups(filterIds)) {
|
|
1597
|
+
const scanned = scanFlatColumnAggregate(ids, plan.normalized, plan.columns);
|
|
1598
|
+
if (!scanned.rowsSeen) continue;
|
|
1599
|
+
rowsSeen += scanned.rowsSeen;
|
|
1600
|
+
groups.push({
|
|
1601
|
+
groupValues: { [plan.groupBy]: groupValue === undefined ? null : groupValue },
|
|
1602
|
+
state: scanned.state,
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
return finishGroupedColumnAggregate({ rowsSeen, groups }, spec, plan.normalized);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
_aggregateGroupForwardIndex(plan, criteria, spec) {
|
|
1610
|
+
const grouped = scanForwardGroupedColumnAggregate(
|
|
1611
|
+
this._ridsForPlan(plan.idPlan, criteria),
|
|
1612
|
+
plan.normalized,
|
|
1613
|
+
plan.groupBy,
|
|
1614
|
+
plan.groupIndex,
|
|
1615
|
+
plan.columns,
|
|
1616
|
+
);
|
|
1617
|
+
return finishGroupedColumnAggregate(grouped, spec, plan.normalized);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
_findWithOrderedIndex(criteria, options, plan = this._planRead(criteria, options)) {
|
|
1621
|
+
const index = this.indexes.get(String(options.sortBy));
|
|
1622
|
+
if (!index) return null;
|
|
1623
|
+
const filterProven = Boolean(plan.idPlan && !plan.idPlan.verify && plan.path !== READ_PATH.SCAN);
|
|
1624
|
+
const includeId = typeof options.includeId === "function" ? options.includeId : null;
|
|
1625
|
+
return index.find({
|
|
1626
|
+
candidateIds: plan.idPlan?.ids || null,
|
|
1627
|
+
includeRid: includeId ? (rid) => includeId(this._idForRid(rid)) : null,
|
|
1628
|
+
desc: options.sort === "desc",
|
|
1629
|
+
offset: options.offset || 0,
|
|
1630
|
+
limit: options.limit ?? Infinity,
|
|
1631
|
+
read: (rid) => this.readRidForQuery(rid),
|
|
1632
|
+
matches: (object) => filterProven ? true : matchesQuery(object, criteria),
|
|
1633
|
+
}).map((object) => this.read(object._id)).filter(Boolean);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
_prepareStoredValue(value = {}) {
|
|
1637
|
+
const extracted = this._extractVector(value);
|
|
1638
|
+
return {
|
|
1639
|
+
value: extracted.value,
|
|
1640
|
+
vector: extracted.vector,
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
_extractVector(value = {}) {
|
|
1645
|
+
const source = value || {};
|
|
1646
|
+
if (!this.vectorField || !Object.prototype.hasOwnProperty.call(source, this.vectorField)) {
|
|
1647
|
+
return { value: source, vector: { present: false } };
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const { [this.vectorField]: rawVector, ...rest } = source;
|
|
1651
|
+
if (rawVector === null) return { value: rest, vector: { present: true, remove: true } };
|
|
1652
|
+
const vector = cloneVector(rawVector);
|
|
1653
|
+
if (!vector) throw new TypeError(`${this.vectorField} must be an array of finite numbers`);
|
|
1654
|
+
return { value: rest, vector: { present: true, value: vector } };
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
_applyVectorUpdate(id, vector) {
|
|
1658
|
+
if (!this.vectorField) return;
|
|
1659
|
+
if (vector.present && !vector.remove) {
|
|
1660
|
+
this._setVector(id, vector.value);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
this._removeVector(id);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
_setVector(id, vector) {
|
|
1667
|
+
const key = String(id);
|
|
1668
|
+
this.vectors.set(key, [...vector]);
|
|
1669
|
+
for (const entry of this.vectorIndexes.values()) {
|
|
1670
|
+
if (entry.status === "failed") continue;
|
|
1671
|
+
if (!entry.includeId || entry.includeId(key)) entry.index.insert(key, vector);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
_removeVector(id) {
|
|
1676
|
+
const key = String(id);
|
|
1677
|
+
this.vectors.delete(key);
|
|
1678
|
+
for (const entry of this.vectorIndexes.values()) {
|
|
1679
|
+
if (entry.status !== "failed") entry.index.remove(key);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
_readyVectorIndex(key = "all", includeId = null) {
|
|
1684
|
+
const indexKey = String(key || "all");
|
|
1685
|
+
const entry = this.vectorIndexes.get(indexKey);
|
|
1686
|
+
if (entry?.status === "ready") return entry.index;
|
|
1687
|
+
if (!entry || entry.status === "failed") this._startVectorIndexWarmup(indexKey, includeId);
|
|
1688
|
+
throw new VectorIndexWarmingError({
|
|
1689
|
+
collection: this.name,
|
|
1690
|
+
indexKey,
|
|
1691
|
+
indexStatus: this.vectorIndexes.get(indexKey)?.status || "pending",
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
_startVectorIndexWarmup(key = "all", includeId = null) {
|
|
1696
|
+
const indexKey = String(key || "all");
|
|
1697
|
+
const filter = typeof includeId === "function" ? includeId : null;
|
|
1698
|
+
const index = new HNSWIndex({
|
|
1699
|
+
...this.vectorConfig.hnsw,
|
|
1700
|
+
getVec: (id) => this.vectors.get(String(id)) || null,
|
|
1701
|
+
});
|
|
1702
|
+
const entry = {
|
|
1703
|
+
index,
|
|
1704
|
+
includeId: filter,
|
|
1705
|
+
status: "building",
|
|
1706
|
+
startedAt: new Date().toISOString(),
|
|
1707
|
+
readyAt: null,
|
|
1708
|
+
error: null,
|
|
1709
|
+
scanned: 0,
|
|
1710
|
+
inserted: 0,
|
|
1711
|
+
promise: null,
|
|
1712
|
+
};
|
|
1713
|
+
this.vectorIndexes.set(indexKey, entry);
|
|
1714
|
+
const build = this.derivedStateScheduler.schedule(
|
|
1715
|
+
`vector-index:${this.name}:${indexKey}`,
|
|
1716
|
+
(job) => this._runVectorIndexBuild(indexKey, entry, job),
|
|
1717
|
+
{ owner: this.derivedStateOwner },
|
|
1718
|
+
);
|
|
1719
|
+
entry.promise = build;
|
|
1720
|
+
this.vectorBuilds.add(build);
|
|
1721
|
+
build.then(
|
|
1722
|
+
() => this.vectorBuilds.delete(build),
|
|
1723
|
+
() => this.vectorBuilds.delete(build),
|
|
1724
|
+
);
|
|
1725
|
+
return build;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
async _runVectorIndexBuild(indexKey, entry, job) {
|
|
1729
|
+
if (this.disposed) return;
|
|
1730
|
+
try {
|
|
1731
|
+
for (const [id, vector] of this.vectors) {
|
|
1732
|
+
if (this.disposed) return;
|
|
1733
|
+
entry.scanned++;
|
|
1734
|
+
if (!entry.includeId || entry.includeId(id)) {
|
|
1735
|
+
entry.index.insert(id, vector);
|
|
1736
|
+
entry.inserted++;
|
|
1737
|
+
}
|
|
1738
|
+
if (entry.scanned % VECTOR_BUILD_BATCH_SIZE === 0) await job.checkpoint(entry.scanned);
|
|
1739
|
+
}
|
|
1740
|
+
if (this.vectorIndexes.get(indexKey) === entry) {
|
|
1741
|
+
entry.status = "ready";
|
|
1742
|
+
entry.readyAt = new Date().toISOString();
|
|
1743
|
+
}
|
|
1744
|
+
return entry;
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
if (this.vectorIndexes.get(indexKey) === entry) {
|
|
1747
|
+
entry.status = "failed";
|
|
1748
|
+
entry.error = error.message;
|
|
1749
|
+
}
|
|
1750
|
+
throw error;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
_vectorIndexStatus() {
|
|
1755
|
+
if (!this.vectorField) return "disabled";
|
|
1756
|
+
if (this.vectors.size === 0) return "ready";
|
|
1757
|
+
if (!this.vectorIndexes.size) return "pending";
|
|
1758
|
+
let building = false;
|
|
1759
|
+
for (const entry of this.vectorIndexes.values()) {
|
|
1760
|
+
if (entry.status === "failed") return "failed";
|
|
1761
|
+
if (entry.status === "building") building = true;
|
|
1762
|
+
}
|
|
1763
|
+
return building ? "building" : "ready";
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function createPhysicalStore(options = {}) {
|
|
1768
|
+
const engine = options.engine || "object";
|
|
1769
|
+
if (engine === "shape") return new ShapeStore();
|
|
1770
|
+
if (engine === "object") return new ObjectRowStore(options.object);
|
|
1771
|
+
throw new TypeError(`unsupported storage engine: ${engine}`);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
class RidBitmapQueue {
|
|
1775
|
+
constructor() {
|
|
1776
|
+
this.bits = new Uint8Array(0);
|
|
1777
|
+
this.queue = [];
|
|
1778
|
+
this.head = 0;
|
|
1779
|
+
this.size = 0;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
add(rid) {
|
|
1783
|
+
if (!Number.isInteger(rid) || rid < 0) return false;
|
|
1784
|
+
this._ensure(rid);
|
|
1785
|
+
if (this.bits[rid]) return false;
|
|
1786
|
+
this.bits[rid] = 1;
|
|
1787
|
+
this.size++;
|
|
1788
|
+
this.queue.push(rid);
|
|
1789
|
+
return true;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
delete(rid) {
|
|
1793
|
+
if (!Number.isInteger(rid) || rid < 0 || rid >= this.bits.length || !this.bits[rid]) return false;
|
|
1794
|
+
this.bits[rid] = 0;
|
|
1795
|
+
this.size--;
|
|
1796
|
+
if (this.size === 0) this._compactQueue();
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
has(rid) {
|
|
1801
|
+
return Number.isInteger(rid) && rid >= 0 && rid < this.bits.length && this.bits[rid] === 1;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
clear() {
|
|
1805
|
+
this.bits.fill(0);
|
|
1806
|
+
this.queue = [];
|
|
1807
|
+
this.head = 0;
|
|
1808
|
+
this.size = 0;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
take(limit = Infinity) {
|
|
1812
|
+
const max = Math.max(0, Math.floor(Number(limit) || 0));
|
|
1813
|
+
if (max <= 0 || this.size <= 0) return [];
|
|
1814
|
+
const out = [];
|
|
1815
|
+
while (out.length < max && this.head < this.queue.length) {
|
|
1816
|
+
const rid = this.queue[this.head++];
|
|
1817
|
+
if (this.has(rid)) out.push(rid);
|
|
1818
|
+
}
|
|
1819
|
+
this._compactQueue();
|
|
1820
|
+
return out;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
*[Symbol.iterator]() {
|
|
1824
|
+
for (let i = this.head; i < this.queue.length; i++) {
|
|
1825
|
+
const rid = this.queue[i];
|
|
1826
|
+
if (this.has(rid)) yield rid;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
_ensure(rid) {
|
|
1831
|
+
if (rid < this.bits.length) return;
|
|
1832
|
+
let next = this.bits.length || 1024;
|
|
1833
|
+
while (next <= rid) next *= 2;
|
|
1834
|
+
const bits = new Uint8Array(next);
|
|
1835
|
+
bits.set(this.bits);
|
|
1836
|
+
this.bits = bits;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
_compactQueue() {
|
|
1840
|
+
if (this.size === 0) {
|
|
1841
|
+
this.queue = [];
|
|
1842
|
+
this.head = 0;
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
if (this.head < 4096 || this.head * 2 < this.queue.length) return;
|
|
1846
|
+
this.queue = this.queue.slice(this.head);
|
|
1847
|
+
this.head = 0;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
class FieldIndex {
|
|
1852
|
+
constructor(path) {
|
|
1853
|
+
this.path = String(path);
|
|
1854
|
+
this.lookup = new Map();
|
|
1855
|
+
this.ordered = new Map();
|
|
1856
|
+
this.forward = new Map();
|
|
1857
|
+
this.containsStringRids = new Set();
|
|
1858
|
+
this.containsScalarRids = new Set();
|
|
1859
|
+
this.containsNestedArrayRids = new Set();
|
|
1860
|
+
this.sortedOrderedKeys = null;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
get canReadValuesById() {
|
|
1864
|
+
return true;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
get lookupKeys() {
|
|
1868
|
+
return this.lookup.size;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
get lookupEntries() {
|
|
1872
|
+
return [...this.lookup.values()].reduce((sum, ids) => sum + ids.size, 0);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
get orderedKeys() {
|
|
1876
|
+
return this.ordered.size;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
get orderedEntries() {
|
|
1880
|
+
return [...this.ordered.values()].reduce((sum, bucket) => sum + bucket.ids.size, 0);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
addValue(rid, value) {
|
|
1884
|
+
if (!Number.isInteger(rid)) return;
|
|
1885
|
+
this.forward.set(rid, value);
|
|
1886
|
+
for (const key of indexedStoredValues(value).map(indexValueKey)) {
|
|
1887
|
+
const rids = this.lookup.get(key) || new Set();
|
|
1888
|
+
rids.add(rid);
|
|
1889
|
+
this.lookup.set(key, rids);
|
|
1890
|
+
}
|
|
1891
|
+
this._addOrdered(rid, value);
|
|
1892
|
+
this._addContainsState(rid, value);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
removeValue(rid, value) {
|
|
1896
|
+
this.forward.delete(rid);
|
|
1897
|
+
for (const key of indexedStoredValues(value).map(indexValueKey)) {
|
|
1898
|
+
const rids = this.lookup.get(key);
|
|
1899
|
+
rids?.delete(rid);
|
|
1900
|
+
if (rids?.size === 0) this.lookup.delete(key);
|
|
1901
|
+
}
|
|
1902
|
+
this._removeOrdered(rid, value);
|
|
1903
|
+
this._removeContainsState(rid, value);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
removeRid(rid) {
|
|
1907
|
+
if (!this.forward.has(rid)) return;
|
|
1908
|
+
this.removeValue(rid, this.forward.get(rid));
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
idsForValues(values) {
|
|
1912
|
+
const unique = uniqueIndexValues(values);
|
|
1913
|
+
if (unique.length === 1) return this.lookup.get(indexValueKey(unique[0])) || new Set();
|
|
1914
|
+
let ids = new Set();
|
|
1915
|
+
for (const value of unique) ids = setUnion(ids, this.lookup.get(indexValueKey(value)) || new Set());
|
|
1916
|
+
return ids;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
idsForContains(value) {
|
|
1920
|
+
if (!isScalarIndexValue(value)) return null;
|
|
1921
|
+
if (this.containsStringRids.size || this.containsScalarRids.size || this.containsNestedArrayRids.size) return null;
|
|
1922
|
+
return this.lookup.get(indexValueKey(value)) || new Set();
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
valueForRid(rid) {
|
|
1926
|
+
return this.forward.get(rid);
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
*iterateGroups(filterIds = null) {
|
|
1930
|
+
for (const bucket of this.ordered.values()) {
|
|
1931
|
+
const ids = filterIds ? setIntersection(filterIds, bucket.ids) : bucket.ids;
|
|
1932
|
+
if (ids.size > 0) yield [bucket.value, ids];
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
*iterateGroupCounts(filterIds = null, scopeIds = null) {
|
|
1937
|
+
for (const bucket of this.ordered.values()) {
|
|
1938
|
+
const count = countIntersectingSets(bucket.ids, filterIds, scopeIds);
|
|
1939
|
+
if (count > 0) yield [bucket.value, count];
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
find({ candidateIds, includeRid, desc = false, offset = 0, limit = Infinity, read, matches }) {
|
|
1944
|
+
if (limit <= 0) return [];
|
|
1945
|
+
const results = [];
|
|
1946
|
+
let skipped = 0;
|
|
1947
|
+
for (const rid of this.orderedIds(desc)) {
|
|
1948
|
+
if (candidateIds && !candidateIds.has(rid)) continue;
|
|
1949
|
+
if (includeRid && !includeRid(rid)) continue;
|
|
1950
|
+
const object = read(rid);
|
|
1951
|
+
if (!object || !matches(object)) continue;
|
|
1952
|
+
if (skipped < offset) {
|
|
1953
|
+
skipped++;
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
results.push(object);
|
|
1957
|
+
if (results.length >= limit) break;
|
|
1958
|
+
}
|
|
1959
|
+
return results;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
*orderedIds(desc = false) {
|
|
1963
|
+
const keys = this._sortedOrderedKeys();
|
|
1964
|
+
const start = desc ? keys.length - 1 : 0;
|
|
1965
|
+
const end = desc ? -1 : keys.length;
|
|
1966
|
+
const step = desc ? -1 : 1;
|
|
1967
|
+
for (let i = start; i !== end; i += step) {
|
|
1968
|
+
const bucket = this.ordered.get(keys[i]);
|
|
1969
|
+
if (!bucket) continue;
|
|
1970
|
+
yield* bucket.ids;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
_addOrdered(rid, value) {
|
|
1975
|
+
const key = orderedValueKey(value);
|
|
1976
|
+
let bucket = this.ordered.get(key);
|
|
1977
|
+
if (!bucket) {
|
|
1978
|
+
bucket = { value, ids: new Set() };
|
|
1979
|
+
this.ordered.set(key, bucket);
|
|
1980
|
+
this.sortedOrderedKeys = null;
|
|
1981
|
+
}
|
|
1982
|
+
bucket.ids.add(rid);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
_removeOrdered(rid, value) {
|
|
1986
|
+
const key = orderedValueKey(value);
|
|
1987
|
+
const bucket = this.ordered.get(key);
|
|
1988
|
+
bucket?.ids.delete(rid);
|
|
1989
|
+
if (bucket?.ids.size === 0) {
|
|
1990
|
+
this.ordered.delete(key);
|
|
1991
|
+
this.sortedOrderedKeys = null;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
_addContainsState(rid, value) {
|
|
1996
|
+
if (typeof value === "string") this.containsStringRids.add(rid);
|
|
1997
|
+
else if (typeof value === "number" || typeof value === "boolean") this.containsScalarRids.add(rid);
|
|
1998
|
+
else if (Array.isArray(value) && value.some(Array.isArray)) this.containsNestedArrayRids.add(rid);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
_removeContainsState(rid, value) {
|
|
2002
|
+
if (typeof value === "string") this.containsStringRids.delete(rid);
|
|
2003
|
+
else if (typeof value === "number" || typeof value === "boolean") this.containsScalarRids.delete(rid);
|
|
2004
|
+
else if (Array.isArray(value) && value.some(Array.isArray)) this.containsNestedArrayRids.delete(rid);
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
_sortedOrderedKeys() {
|
|
2008
|
+
if (!this.sortedOrderedKeys) {
|
|
2009
|
+
this.sortedOrderedKeys = [...this.ordered.entries()]
|
|
2010
|
+
.sort((left, right) => {
|
|
2011
|
+
const compared = compareSortValues(left[1].value, right[1].value);
|
|
2012
|
+
return compared || String(left[0]).localeCompare(String(right[0]));
|
|
2013
|
+
})
|
|
2014
|
+
.map(([key]) => key);
|
|
2015
|
+
}
|
|
2016
|
+
return this.sortedOrderedKeys;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
class TypedRidColumn {
|
|
2021
|
+
constructor(path) {
|
|
2022
|
+
this.path = String(path);
|
|
2023
|
+
this.numberValues = new Float64Array(0);
|
|
2024
|
+
this.numberPresent = new Uint8Array(0);
|
|
2025
|
+
this.booleanValues = new Uint8Array(0);
|
|
2026
|
+
this.booleanPresent = new Uint8Array(0);
|
|
2027
|
+
this.stringCodes = new Uint32Array(0);
|
|
2028
|
+
this.nulls = new Uint8Array(0);
|
|
2029
|
+
this.strings = [];
|
|
2030
|
+
this.stringIds = new Map();
|
|
2031
|
+
this.stringRefs = [];
|
|
2032
|
+
this.freeStringCodes = [];
|
|
2033
|
+
this.overflow = new Map();
|
|
2034
|
+
this.pending = null;
|
|
2035
|
+
this.status = "ready";
|
|
2036
|
+
this.error = null;
|
|
2037
|
+
this.buildId = 0;
|
|
2038
|
+
this.numberSum = 0;
|
|
2039
|
+
this.numberCount = 0;
|
|
2040
|
+
this.scalarCount = 0;
|
|
2041
|
+
this.nullCount = 0;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
get ready() {
|
|
2045
|
+
return this.status === "ready";
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
get entries() {
|
|
2049
|
+
return this.scalarCount + this.nullCount + this.overflow.size;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
get scalarEntries() {
|
|
2053
|
+
return this.scalarCount;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
get nonScalarEntries() {
|
|
2057
|
+
return this.overflow.size;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
get pendingEntries() {
|
|
2061
|
+
return this.pending?.size || 0;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
startBuild() {
|
|
2065
|
+
this.status = "building";
|
|
2066
|
+
this.error = null;
|
|
2067
|
+
this.pending = new Map();
|
|
2068
|
+
return ++this.buildId;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
install(source, buildId) {
|
|
2072
|
+
if (buildId !== this.buildId) return false;
|
|
2073
|
+
this.numberValues = source.numberValues;
|
|
2074
|
+
this.numberPresent = source.numberPresent;
|
|
2075
|
+
this.booleanValues = source.booleanValues;
|
|
2076
|
+
this.booleanPresent = source.booleanPresent;
|
|
2077
|
+
this.stringCodes = source.stringCodes;
|
|
2078
|
+
this.nulls = source.nulls;
|
|
2079
|
+
this.strings = source.strings;
|
|
2080
|
+
this.stringIds = source.stringIds;
|
|
2081
|
+
this.stringRefs = source.stringRefs;
|
|
2082
|
+
this.freeStringCodes = source.freeStringCodes;
|
|
2083
|
+
this.overflow = source.overflow;
|
|
2084
|
+
this.numberSum = source.numberSum;
|
|
2085
|
+
this.numberCount = source.numberCount;
|
|
2086
|
+
this.scalarCount = source.scalarCount;
|
|
2087
|
+
this.nullCount = source.nullCount;
|
|
2088
|
+
const pending = this.pending || new Map();
|
|
2089
|
+
for (const [rid, state] of pending) this._applyState(rid, state);
|
|
2090
|
+
this.pending = null;
|
|
2091
|
+
this.status = "ready";
|
|
2092
|
+
this.error = null;
|
|
2093
|
+
return true;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
fail(error, buildId) {
|
|
2097
|
+
if (buildId !== this.buildId) return;
|
|
2098
|
+
this.status = "error";
|
|
2099
|
+
this.error = error?.message || String(error);
|
|
2100
|
+
this.pending = null;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
updateScalarState(rid, state) {
|
|
2104
|
+
if (this.status === "building") {
|
|
2105
|
+
this.pending.set(rid, state);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
this._applyState(rid, state);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
remove(rid) {
|
|
2112
|
+
this.updateScalarState(rid, { kind: "absent" });
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
hasScalar(rid) {
|
|
2116
|
+
return Boolean(
|
|
2117
|
+
this.numberPresent[rid] ||
|
|
2118
|
+
this.booleanPresent[rid] ||
|
|
2119
|
+
this.stringCodes[rid],
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
hasNonScalar(rid) {
|
|
2124
|
+
return this.overflow.has(rid);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
hasValue(rid) {
|
|
2128
|
+
return this.hasScalar(rid) || this.hasNonScalar(rid) || this.nulls[rid] === 1;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
valueFor(rid) {
|
|
2132
|
+
if (this.numberPresent[rid]) return this.numberValues[rid];
|
|
2133
|
+
if (this.booleanPresent[rid]) return this.booleanValues[rid] === 1;
|
|
2134
|
+
const code = this.stringCodes[rid] || 0;
|
|
2135
|
+
if (code) return this.strings[code - 1];
|
|
2136
|
+
if (this.nulls[rid]) return null;
|
|
2137
|
+
if (this.overflow.has(rid)) return clone(this.overflow.get(rid));
|
|
2138
|
+
return undefined;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
_applyState(rid, state) {
|
|
2142
|
+
if (!Number.isInteger(rid) || rid < 0) return;
|
|
2143
|
+
this._clearRid(rid);
|
|
2144
|
+
if (state.kind === "absent") {
|
|
2145
|
+
this._resetIfEmpty();
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
this._ensure(rid);
|
|
2149
|
+
if (state.kind === "null") {
|
|
2150
|
+
this.nulls[rid] = 1;
|
|
2151
|
+
this.nullCount++;
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if (state.kind === "scalar") {
|
|
2155
|
+
this._setScalar(rid, state.value);
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (state.kind === "nonScalar") this.overflow.set(rid, clone(state.value));
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
_setScalar(rid, value) {
|
|
2162
|
+
if (typeof value === "number") {
|
|
2163
|
+
this.numberValues[rid] = value;
|
|
2164
|
+
this.numberPresent[rid] = 1;
|
|
2165
|
+
this.numberSum += value;
|
|
2166
|
+
this.numberCount++;
|
|
2167
|
+
this.scalarCount++;
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
if (typeof value === "boolean") {
|
|
2171
|
+
this.booleanValues[rid] = value ? 1 : 0;
|
|
2172
|
+
this.booleanPresent[rid] = 1;
|
|
2173
|
+
this.scalarCount++;
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (typeof value === "string") {
|
|
2177
|
+
this.stringCodes[rid] = this._stringCode(value);
|
|
2178
|
+
this.scalarCount++;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
_clearRid(rid) {
|
|
2183
|
+
if (rid < this.numberPresent.length && this.numberPresent[rid]) {
|
|
2184
|
+
this.numberPresent[rid] = 0;
|
|
2185
|
+
this.numberSum -= this.numberValues[rid];
|
|
2186
|
+
this.numberValues[rid] = 0;
|
|
2187
|
+
this.numberCount--;
|
|
2188
|
+
this.scalarCount--;
|
|
2189
|
+
}
|
|
2190
|
+
if (rid < this.booleanPresent.length && this.booleanPresent[rid]) {
|
|
2191
|
+
this.booleanPresent[rid] = 0;
|
|
2192
|
+
this.booleanValues[rid] = 0;
|
|
2193
|
+
this.scalarCount--;
|
|
2194
|
+
}
|
|
2195
|
+
if (rid < this.stringCodes.length && this.stringCodes[rid]) {
|
|
2196
|
+
const code = this.stringCodes[rid];
|
|
2197
|
+
const index = code - 1;
|
|
2198
|
+
const refs = (this.stringRefs[index] || 1) - 1;
|
|
2199
|
+
this.stringRefs[index] = refs;
|
|
2200
|
+
if (refs === 0) {
|
|
2201
|
+
const value = this.strings[index];
|
|
2202
|
+
if (this.stringIds.get(value) === code) this.stringIds.delete(value);
|
|
2203
|
+
this.strings[index] = undefined;
|
|
2204
|
+
this.freeStringCodes.push(code);
|
|
2205
|
+
}
|
|
2206
|
+
this.stringCodes[rid] = 0;
|
|
2207
|
+
this.scalarCount--;
|
|
2208
|
+
}
|
|
2209
|
+
if (rid < this.nulls.length && this.nulls[rid]) {
|
|
2210
|
+
this.nulls[rid] = 0;
|
|
2211
|
+
this.nullCount--;
|
|
2212
|
+
}
|
|
2213
|
+
this.overflow.delete(rid);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
_stringCode(value) {
|
|
2217
|
+
let code = this.stringIds.get(value);
|
|
2218
|
+
if (code) {
|
|
2219
|
+
this.stringRefs[code - 1]++;
|
|
2220
|
+
return code;
|
|
2221
|
+
}
|
|
2222
|
+
code = this.freeStringCodes.pop() || this.strings.length + 1;
|
|
2223
|
+
this.strings[code - 1] = value;
|
|
2224
|
+
this.stringRefs[code - 1] = 1;
|
|
2225
|
+
this.stringIds.set(value, code);
|
|
2226
|
+
return code;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
_resetIfEmpty() {
|
|
2230
|
+
if (this.scalarCount || this.nullCount || this.overflow.size) return;
|
|
2231
|
+
this.numberValues = new Float64Array(0);
|
|
2232
|
+
this.numberPresent = new Uint8Array(0);
|
|
2233
|
+
this.booleanValues = new Uint8Array(0);
|
|
2234
|
+
this.booleanPresent = new Uint8Array(0);
|
|
2235
|
+
this.stringCodes = new Uint32Array(0);
|
|
2236
|
+
this.nulls = new Uint8Array(0);
|
|
2237
|
+
this.strings = [];
|
|
2238
|
+
this.stringIds.clear();
|
|
2239
|
+
this.stringRefs = [];
|
|
2240
|
+
this.freeStringCodes = [];
|
|
2241
|
+
this.numberSum = 0;
|
|
2242
|
+
this.numberCount = 0;
|
|
2243
|
+
this.scalarCount = 0;
|
|
2244
|
+
this.nullCount = 0;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
_ensure(rid) {
|
|
2248
|
+
if (rid < this.numberValues.length) return;
|
|
2249
|
+
let next = this.numberValues.length || 1024;
|
|
2250
|
+
while (next <= rid) next *= 2;
|
|
2251
|
+
this.numberValues = growTypedArray(this.numberValues, Float64Array, next);
|
|
2252
|
+
this.numberPresent = growTypedArray(this.numberPresent, Uint8Array, next);
|
|
2253
|
+
this.booleanValues = growTypedArray(this.booleanValues, Uint8Array, next);
|
|
2254
|
+
this.booleanPresent = growTypedArray(this.booleanPresent, Uint8Array, next);
|
|
2255
|
+
this.stringCodes = growTypedArray(this.stringCodes, Uint32Array, next);
|
|
2256
|
+
this.nulls = growTypedArray(this.nulls, Uint8Array, next);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function scanFlatColumnAggregate(ids, normalized, columns) {
|
|
2261
|
+
const state = Object.fromEntries(normalized.fields.map((field) => [field.resultKey, flatFieldState(field)]));
|
|
2262
|
+
const rowCount = idSetSize(ids);
|
|
2263
|
+
if (rowCount !== null) {
|
|
2264
|
+
for (const field of normalized.fields) {
|
|
2265
|
+
if (isRowCountField(field)) state[field.resultKey].count += rowCount;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
const fieldsNeedingIds = normalized.fields.filter((field) => !isRowCountField(field));
|
|
2269
|
+
if (fieldsNeedingIds.length === 0) return { rowsSeen: rowCount, state };
|
|
2270
|
+
|
|
2271
|
+
for (const id of ids) {
|
|
2272
|
+
for (const field of fieldsNeedingIds) stepFlatField(state[field.resultKey], field, id, columns);
|
|
2273
|
+
}
|
|
2274
|
+
return { rowsSeen: rowCount, state };
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
let rowsSeen = 0;
|
|
2278
|
+
|
|
2279
|
+
for (const id of ids) {
|
|
2280
|
+
rowsSeen++;
|
|
2281
|
+
for (const field of normalized.fields) {
|
|
2282
|
+
stepFlatField(state[field.resultKey], field, id, columns);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
return { rowsSeen, state };
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
function isRowCountOnlyAggregate(normalized) {
|
|
2290
|
+
return normalized.fields.length > 0 && normalized.fields.every(isRowCountField);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function isRowCountField(field) {
|
|
2294
|
+
return field.operator === "$count" && field.operand === true;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
function rowCountOnlyState(normalized, count) {
|
|
2298
|
+
const state = Object.fromEntries(normalized.fields.map((field) => [field.resultKey, flatFieldState(field)]));
|
|
2299
|
+
for (const field of normalized.fields) state[field.resultKey].count = count;
|
|
2300
|
+
return state;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
function idSetSize(ids) {
|
|
2304
|
+
return ids instanceof Set ? ids.size : null;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
function flatFieldState(field) {
|
|
2308
|
+
switch (field.operator) {
|
|
2309
|
+
case "$sum":
|
|
2310
|
+
return { sum: 0 };
|
|
2311
|
+
case "$avg":
|
|
2312
|
+
return { sum: 0, count: 0 };
|
|
2313
|
+
case "$min":
|
|
2314
|
+
case "$max":
|
|
2315
|
+
return { value: undefined };
|
|
2316
|
+
case "$count":
|
|
2317
|
+
return { count: 0 };
|
|
2318
|
+
default:
|
|
2319
|
+
return {};
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
function stepFlatField(state, field, id, columns) {
|
|
2324
|
+
switch (field.operator) {
|
|
2325
|
+
case "$sum": {
|
|
2326
|
+
const value = columns.get(field.operand).valueFor(id);
|
|
2327
|
+
if (typeof value === "number" && Number.isFinite(value)) state.sum += value;
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
case "$avg": {
|
|
2331
|
+
const value = columns.get(field.operand).valueFor(id);
|
|
2332
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2333
|
+
state.sum += value;
|
|
2334
|
+
state.count++;
|
|
2335
|
+
}
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
case "$count": {
|
|
2339
|
+
if (field.operand === true) {
|
|
2340
|
+
state.count++;
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
const column = columns.get(field.operand);
|
|
2344
|
+
if (column.hasScalar(id) || column.hasNonScalar(id)) state.count++;
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
case "$min": {
|
|
2348
|
+
const value = columns.get(field.operand).valueFor(id);
|
|
2349
|
+
if (!isComparableAggregateValue(value)) return;
|
|
2350
|
+
if (state.value === undefined || sameAggregateTypeLessThan(value, state.value)) state.value = value;
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
case "$max": {
|
|
2354
|
+
const value = columns.get(field.operand).valueFor(id);
|
|
2355
|
+
if (!isComparableAggregateValue(value)) return;
|
|
2356
|
+
if (state.value === undefined || sameAggregateTypeLessThan(state.value, value)) state.value = value;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function finishFlatColumnAggregate(aggregate, spec, normalized) {
|
|
2362
|
+
if (!aggregate.rowsSeen) return [];
|
|
2363
|
+
const row = {};
|
|
2364
|
+
for (const field of normalized.fields) writeFlatField(row, aggregate.state[field.resultKey], field, spec?.$partial === true);
|
|
2365
|
+
return [row];
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function writeFlatField(row, state, field, partial) {
|
|
2369
|
+
switch (field.operator) {
|
|
2370
|
+
case "$sum":
|
|
2371
|
+
row[field.resultKey] = state.sum;
|
|
2372
|
+
return;
|
|
2373
|
+
case "$avg":
|
|
2374
|
+
if (partial) {
|
|
2375
|
+
row[partialAvgSumKey(field.resultKey)] = state.sum;
|
|
2376
|
+
row[partialAvgCountKey(field.resultKey)] = state.count;
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
row[field.resultKey] = state.count ? state.sum / state.count : null;
|
|
2380
|
+
return;
|
|
2381
|
+
case "$count":
|
|
2382
|
+
row[field.resultKey] = state.count;
|
|
2383
|
+
return;
|
|
2384
|
+
case "$min":
|
|
2385
|
+
case "$max":
|
|
2386
|
+
row[field.resultKey] = state.value === undefined ? null : state.value;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
function scanGroupedColumnAggregate(ids, normalized, columns) {
|
|
2391
|
+
const groups = new Map();
|
|
2392
|
+
let rowsSeen = 0;
|
|
2393
|
+
|
|
2394
|
+
for (const id of ids) {
|
|
2395
|
+
const groupValues = {};
|
|
2396
|
+
const keyValues = [];
|
|
2397
|
+
for (const path of normalized.groupBy) {
|
|
2398
|
+
const column = columns.get(path);
|
|
2399
|
+
if (column.hasNonScalar(id)) return null;
|
|
2400
|
+
const value = column.hasScalar(id) ? column.valueFor(id) : null;
|
|
2401
|
+
groupValues[path] = value;
|
|
2402
|
+
keyValues.push(value);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
const key = stableKey(keyValues);
|
|
2406
|
+
let group = groups.get(key);
|
|
2407
|
+
if (!group) {
|
|
2408
|
+
group = {
|
|
2409
|
+
groupValues,
|
|
2410
|
+
state: Object.fromEntries(normalized.fields.map((field) => [field.resultKey, flatFieldState(field)])),
|
|
2411
|
+
};
|
|
2412
|
+
groups.set(key, group);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
rowsSeen++;
|
|
2416
|
+
for (const field of normalized.fields) {
|
|
2417
|
+
stepFlatField(group.state[field.resultKey], field, id, columns);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
return { rowsSeen, groups: [...groups.values()] };
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function scanForwardGroupedColumnAggregate(ids, normalized, groupBy, groupIndex, columns) {
|
|
2425
|
+
const groups = new Map();
|
|
2426
|
+
let rowsSeen = 0;
|
|
2427
|
+
|
|
2428
|
+
for (const id of ids) {
|
|
2429
|
+
const rawGroupValue = groupIndex.valueForRid(id);
|
|
2430
|
+
const groupValue = rawGroupValue === undefined ? null : rawGroupValue;
|
|
2431
|
+
const key = stableKey([groupValue]);
|
|
2432
|
+
let group = groups.get(key);
|
|
2433
|
+
if (!group) {
|
|
2434
|
+
group = {
|
|
2435
|
+
groupValues: { [groupBy]: groupValue },
|
|
2436
|
+
state: Object.fromEntries(normalized.fields.map((field) => [field.resultKey, flatFieldState(field)])),
|
|
2437
|
+
};
|
|
2438
|
+
groups.set(key, group);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
rowsSeen++;
|
|
2442
|
+
for (const field of normalized.fields) {
|
|
2443
|
+
stepFlatField(group.state[field.resultKey], field, id, columns);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
return { rowsSeen, groups: [...groups.values()] };
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function finishGroupedColumnAggregate(aggregate, spec, normalized) {
|
|
2451
|
+
if (!aggregate || !aggregate.rowsSeen) return [];
|
|
2452
|
+
const partialRows = aggregate.groups.map((group) => {
|
|
2453
|
+
const row = { ...group.groupValues };
|
|
2454
|
+
for (const field of normalized.fields) writeFlatField(row, group.state[field.resultKey], field, true);
|
|
2455
|
+
return row;
|
|
2456
|
+
});
|
|
2457
|
+
return spec?.$partial === true ? partialRows : mergeAggregatePartials(partialRows, spec);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
function partialAvgSumKey(resultKey) {
|
|
2461
|
+
return `$partial.${resultKey}.sum`;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function partialAvgCountKey(resultKey) {
|
|
2465
|
+
return `$partial.${resultKey}.count`;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
function isComparableAggregateValue(value) {
|
|
2469
|
+
if (value === null || value === undefined) return false;
|
|
2470
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
2471
|
+
return typeof value === "string" || typeof value === "boolean";
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
function sameAggregateTypeLessThan(left, right) {
|
|
2475
|
+
if (typeof left !== typeof right) return false;
|
|
2476
|
+
return compareSortValues(left, right) < 0;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
function sortObjects(objects, options = {}) {
|
|
2480
|
+
const direction = options.sort === "desc" ? -1 : 1;
|
|
2481
|
+
return [...objects].sort((left, right) => {
|
|
2482
|
+
return compareSortValues(getPathValue(left, options.sortBy), getPathValue(right, options.sortBy)) * direction;
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
function indexableConstraintValues(constraint) {
|
|
2487
|
+
if (constraint && typeof constraint === "object" && !Array.isArray(constraint)) {
|
|
2488
|
+
if (Object.prototype.hasOwnProperty.call(constraint, "$eq")) {
|
|
2489
|
+
return indexableEqualityValue(constraint.$eq);
|
|
2490
|
+
}
|
|
2491
|
+
if (Object.prototype.hasOwnProperty.call(constraint, "$equal")) {
|
|
2492
|
+
return indexableEqualityValue(constraint.$equal);
|
|
2493
|
+
}
|
|
2494
|
+
if (Array.isArray(constraint.$in)) {
|
|
2495
|
+
return indexableValueList(constraint.$in);
|
|
2496
|
+
}
|
|
2497
|
+
if (Array.isArray(constraint.$or)) {
|
|
2498
|
+
return indexableDisjunctionValues(constraint.$or);
|
|
2499
|
+
}
|
|
2500
|
+
if (Array.isArray(constraint.$any)) {
|
|
2501
|
+
return indexableDisjunctionValues(constraint.$any);
|
|
2502
|
+
}
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
if (Array.isArray(constraint)) return indexableValueList(constraint);
|
|
2507
|
+
return indexableEqualityValue(constraint);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
function indexableContainsValue(constraint) {
|
|
2511
|
+
if (!constraint || typeof constraint !== "object" || Array.isArray(constraint)) return null;
|
|
2512
|
+
const keys = Object.keys(constraint);
|
|
2513
|
+
if (keys.length !== 1 || keys[0] !== "$contains") return null;
|
|
2514
|
+
return isScalarIndexValue(constraint.$contains) ? constraint.$contains : null;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function indexableDisjunctionValues(constraints) {
|
|
2518
|
+
const values = [];
|
|
2519
|
+
for (const constraint of constraints) {
|
|
2520
|
+
const indexed = indexableConstraintValues(constraint);
|
|
2521
|
+
if (indexed === null) return null;
|
|
2522
|
+
values.push(...indexed);
|
|
2523
|
+
}
|
|
2524
|
+
return values;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
function indexableValueList(values) {
|
|
2528
|
+
const indexed = [];
|
|
2529
|
+
for (const value of values) {
|
|
2530
|
+
const single = indexableEqualityValue(value);
|
|
2531
|
+
if (single === null) return null;
|
|
2532
|
+
indexed.push(...single);
|
|
2533
|
+
}
|
|
2534
|
+
return indexed;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function indexableEqualityValue(value) {
|
|
2538
|
+
return isScalarIndexValue(value) ? [value] : null;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
const NO_VALUE = Symbol("no-value");
|
|
2542
|
+
|
|
2543
|
+
function columnScalarState(value) {
|
|
2544
|
+
if (isScalarColumnValue(value)) return { kind: "scalar", value };
|
|
2545
|
+
if (value === null) return { kind: "null" };
|
|
2546
|
+
if (value !== undefined) return { kind: "nonScalar", value };
|
|
2547
|
+
return { kind: "absent" };
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
function isScalarColumnValue(value) {
|
|
2551
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
2552
|
+
return typeof value === "string" || typeof value === "boolean";
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
function indexedStoredValues(value) {
|
|
2556
|
+
if (value === undefined || value === null) return [];
|
|
2557
|
+
if (Array.isArray(value)) return value.flatMap(indexedStoredValues);
|
|
2558
|
+
return isScalarIndexValue(value) ? [value] : [];
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function isScalarIndexValue(value) {
|
|
2562
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
function indexValueKey(value) {
|
|
2566
|
+
return `${typeof value}:${JSON.stringify(value)}`;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
function orderedValueKey(value) {
|
|
2570
|
+
return `${typeof value}:${stableKey(value)}`;
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
function uniqueIndexValues(values = []) {
|
|
2574
|
+
const unique = new Map();
|
|
2575
|
+
for (const value of values) unique.set(indexValueKey(value), value);
|
|
2576
|
+
return [...unique.values()];
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function setIntersection(left, right) {
|
|
2580
|
+
if (left.size > right.size) return setIntersection(right, left);
|
|
2581
|
+
const out = new Set();
|
|
2582
|
+
for (const id of left) if (right.has(id)) out.add(id);
|
|
2583
|
+
return out;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
function countIntersection(left, right) {
|
|
2587
|
+
if (left.size > right.size) return countIntersection(right, left);
|
|
2588
|
+
let count = 0;
|
|
2589
|
+
for (const id of left) if (right.has(id)) count++;
|
|
2590
|
+
return count;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
function smallerSet(left, right) {
|
|
2594
|
+
return left.size <= right.size ? left : right;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function countIntersectingSets(...sets) {
|
|
2598
|
+
const present = sets.filter((set) => set instanceof Set);
|
|
2599
|
+
if (!present.length) return 0;
|
|
2600
|
+
if (present.length === 1) return present[0].size;
|
|
2601
|
+
if (present.length === 2) return countIntersection(present[0], present[1]);
|
|
2602
|
+
const smallest = present.reduce((best, set) => (set.size < best.size ? set : best));
|
|
2603
|
+
let count = 0;
|
|
2604
|
+
for (const id of smallest) {
|
|
2605
|
+
let found = true;
|
|
2606
|
+
for (const set of present) {
|
|
2607
|
+
if (set === smallest || set.has(id)) continue;
|
|
2608
|
+
found = false;
|
|
2609
|
+
break;
|
|
2610
|
+
}
|
|
2611
|
+
if (found) count++;
|
|
2612
|
+
}
|
|
2613
|
+
return count;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function setUnion(left, right) {
|
|
2617
|
+
const out = new Set(left);
|
|
2618
|
+
for (const id of right) out.add(id);
|
|
2619
|
+
return out;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
function normalizeShard(shard) {
|
|
2623
|
+
return typeof shard === "string" && shard.length > 0 ? shard : null;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function isEmptyCriteria(criteria) {
|
|
2627
|
+
return Boolean(
|
|
2628
|
+
criteria &&
|
|
2629
|
+
typeof criteria === "object" &&
|
|
2630
|
+
!Array.isArray(criteria) &&
|
|
2631
|
+
Object.keys(criteria).length === 0,
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
function normalizeCollectionMeta(meta = {}) {
|
|
2636
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
2637
|
+
throw new TypeError("collection meta must be a plain object");
|
|
2638
|
+
}
|
|
2639
|
+
const next = clone(meta);
|
|
2640
|
+
delete next.columnIndexes;
|
|
2641
|
+
delete next.tiering;
|
|
2642
|
+
delete next.hooks;
|
|
2643
|
+
const indexes = normalizeIndexList(meta.indexes, "indexes");
|
|
2644
|
+
const columns = normalizeIndexList(meta.columns, "columns");
|
|
2645
|
+
const tiering = normalizeTieringMeta(meta.tiering);
|
|
2646
|
+
const hooks = normalizeObjectHooksMeta(meta.hooks);
|
|
2647
|
+
const out = {
|
|
2648
|
+
...next,
|
|
2649
|
+
indexes,
|
|
2650
|
+
columns,
|
|
2651
|
+
};
|
|
2652
|
+
if (tiering) out.tiering = tiering;
|
|
2653
|
+
if (hooks) out.hooks = hooks;
|
|
2654
|
+
return out;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function normalizeTieringMeta(tiering) {
|
|
2658
|
+
if (tiering === undefined || tiering === null || tiering === false) return null;
|
|
2659
|
+
if (!tiering || typeof tiering !== "object" || Array.isArray(tiering)) {
|
|
2660
|
+
throw new TypeError("collection meta tiering must be a plain object");
|
|
2661
|
+
}
|
|
2662
|
+
const mode = tiering.mode === undefined ? "compressedMemory" : String(tiering.mode);
|
|
2663
|
+
if (mode === "off") return null;
|
|
2664
|
+
if (mode !== "compressedMemory") throw new TypeError("collection meta tiering.mode must be compressedMemory or off");
|
|
2665
|
+
const keep = normalizeIndexList(tiering.keep, "tiering.keep");
|
|
2666
|
+
const offload = normalizeIndexList(tiering.offload, "tiering.offload");
|
|
2667
|
+
if (keep.length && offload.length) throw new TypeError("collection meta tiering must use keep or offload, not both");
|
|
2668
|
+
const minBytes = Math.max(0, Math.floor(Number(tiering.minBytes ?? DEFAULT_TIERING_MIN_BYTES) || 0));
|
|
2669
|
+
return {
|
|
2670
|
+
mode: "compressedMemory",
|
|
2671
|
+
...(keep.length ? { keep } : {}),
|
|
2672
|
+
...(offload.length ? { offload } : {}),
|
|
2673
|
+
minBytes,
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function assertCompressionAdapter(compression) {
|
|
2678
|
+
const methods = ["encodeUtf8", "decodeUtf8", "compress", "decompress"];
|
|
2679
|
+
if (compression && methods.every((method) => typeof compression[method] === "function")) return;
|
|
2680
|
+
throw new Error("compressed memory tiering requires a compression adapter");
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function normalizeIndexList(indexes = [], label = "indexes") {
|
|
2684
|
+
if (indexes === undefined || indexes === null) return [];
|
|
2685
|
+
if (!Array.isArray(indexes)) throw new TypeError(`collection meta ${label} must be an array`);
|
|
2686
|
+
return unique(indexes.map((entry) => {
|
|
2687
|
+
if (typeof entry === "string") return entry;
|
|
2688
|
+
if (entry && typeof entry === "object" && typeof entry.path === "string") return entry.path;
|
|
2689
|
+
throw new TypeError(`collection meta ${label} must contain strings or { path } objects`);
|
|
2690
|
+
}).map((path) => path.trim()).filter(Boolean));
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
function unique(values) {
|
|
2694
|
+
return [...new Set(values)];
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
function splitTieredValue(value = {}, tiering = {}, forcedHotPaths = []) {
|
|
2698
|
+
if (!isPlainObject(value)) return { hot: value || {}, cold: null };
|
|
2699
|
+
const forced = unique(forcedHotPaths);
|
|
2700
|
+
if (Array.isArray(tiering.keep) && tiering.keep.length) {
|
|
2701
|
+
const hotPaths = unique([...tiering.keep, ...forced]);
|
|
2702
|
+
const hot = pickPaths(value, hotPaths);
|
|
2703
|
+
const cold = unsetPaths(value, hotPaths);
|
|
2704
|
+
return isRemovableEmptyObject(cold) ? { hot: value, cold: null } : { hot, cold };
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
if (!Array.isArray(tiering.offload) || !tiering.offload.length) return { hot: value, cold: null };
|
|
2708
|
+
let hot = unsetPaths(value, tiering.offload);
|
|
2709
|
+
let cold = pickPaths(value, tiering.offload);
|
|
2710
|
+
for (const path of forced) {
|
|
2711
|
+
const current = getPathValue(value, path);
|
|
2712
|
+
if (current === undefined) continue;
|
|
2713
|
+
hot = setPath(hot, path, clone(current));
|
|
2714
|
+
cold = unsetPath(cold, path);
|
|
2715
|
+
}
|
|
2716
|
+
return isRemovableEmptyObject(cold) ? { hot: value, cold: null } : { hot, cold };
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
function pickPaths(value = {}, paths = []) {
|
|
2720
|
+
let out = {};
|
|
2721
|
+
for (const path of paths) {
|
|
2722
|
+
const current = getPathValue(value, path);
|
|
2723
|
+
if (current !== undefined) out = setPath(out, path, clone(current));
|
|
2724
|
+
}
|
|
2725
|
+
return out;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
function unsetPaths(value = {}, paths = []) {
|
|
2729
|
+
let out = clone(value || {});
|
|
2730
|
+
for (const path of paths) out = unsetPath(out, path);
|
|
2731
|
+
return out;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function mergePlainObjects(base = {}, overlay = {}) {
|
|
2735
|
+
const out = isPlainObject(base) ? { ...base } : {};
|
|
2736
|
+
for (const [key, value] of Object.entries(overlay || {})) {
|
|
2737
|
+
out[key] = isPlainObject(value) && isPlainObject(out[key])
|
|
2738
|
+
? mergePlainObjects(out[key], value)
|
|
2739
|
+
: value;
|
|
2740
|
+
}
|
|
2741
|
+
return out;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
function clone(value) {
|
|
2745
|
+
return JSON.parse(JSON.stringify(value));
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function queryObjectFromStoredValue(id, value = {}) {
|
|
2749
|
+
const { _id, ...rest } = value || {};
|
|
2750
|
+
return { ...rest, _id: id };
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function setPath(object = {}, path, value) {
|
|
2754
|
+
const parts = pathParts(path);
|
|
2755
|
+
if (!parts.length) return object;
|
|
2756
|
+
const root = isPlainObject(object) ? { ...object } : {};
|
|
2757
|
+
let target = root;
|
|
2758
|
+
let source = isPlainObject(object) ? object : {};
|
|
2759
|
+
for (const part of parts.slice(0, -1)) {
|
|
2760
|
+
const current = isPlainObject(source?.[part]) ? source[part] : {};
|
|
2761
|
+
const next = { ...current };
|
|
2762
|
+
target[part] = next;
|
|
2763
|
+
target = next;
|
|
2764
|
+
source = current;
|
|
2765
|
+
}
|
|
2766
|
+
target[parts[parts.length - 1]] = value;
|
|
2767
|
+
return root;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function unsetPath(object = {}, path) {
|
|
2771
|
+
const parts = pathParts(path);
|
|
2772
|
+
if (!parts.length || !isPlainObject(object)) return object;
|
|
2773
|
+
const [next] = unsetPathParts(object, parts, 0);
|
|
2774
|
+
return next || {};
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
function unsetPathParts(value, parts, index) {
|
|
2778
|
+
if (!isPlainObject(value)) return [value, false];
|
|
2779
|
+
const key = parts[index];
|
|
2780
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) return [value, false];
|
|
2781
|
+
const next = { ...value };
|
|
2782
|
+
if (index === parts.length - 1) {
|
|
2783
|
+
delete next[key];
|
|
2784
|
+
} else {
|
|
2785
|
+
const [child, changed] = unsetPathParts(value[key], parts, index + 1);
|
|
2786
|
+
if (!changed) return [value, false];
|
|
2787
|
+
if (isRemovableEmptyObject(child)) delete next[key];
|
|
2788
|
+
else next[key] = child;
|
|
2789
|
+
}
|
|
2790
|
+
return [next, true];
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
function isRemovableEmptyObject(value) {
|
|
2794
|
+
return isPlainObject(value) && Object.keys(value).length === 0;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function pathParts(path) {
|
|
2798
|
+
return String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function isPlainObject(value) {
|
|
2802
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
function growTypedArray(current, Type, length) {
|
|
2806
|
+
const next = new Type(length);
|
|
2807
|
+
next.set(current);
|
|
2808
|
+
return next;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function annotateVectorIndexError(err, database, collection) {
|
|
2812
|
+
if (err?.code !== "VECTOR_INDEX_WARMING") return err;
|
|
2813
|
+
err.body = {
|
|
2814
|
+
...(err.body || { error: err.message }),
|
|
2815
|
+
database,
|
|
2816
|
+
collection,
|
|
2817
|
+
};
|
|
2818
|
+
return err;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
function overallIndexStatus(statuses = []) {
|
|
2822
|
+
if (statuses.some((status) => status === "error" || status === "failed" || status === "missing")) return "error";
|
|
2823
|
+
if (statuses.some((status) => status === "building" || status === "pending")) return "building";
|
|
2824
|
+
return "ready";
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
function hasCollectionIndexReadiness(readiness = {}) {
|
|
2828
|
+
return Boolean(
|
|
2829
|
+
readiness.fieldIndexes?.length ||
|
|
2830
|
+
readiness.columns?.length ||
|
|
2831
|
+
hasVectorIndexValue(readiness.vector),
|
|
2832
|
+
);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
function hasVectorIndexValue(vector = null) {
|
|
2836
|
+
return Boolean(
|
|
2837
|
+
Number(vector?.stored) > 0 ||
|
|
2838
|
+
Number(vector?.indexes) > 0 ||
|
|
2839
|
+
Number(vector?.nodes) > 0 ||
|
|
2840
|
+
(Array.isArray(vector?.indexStates) && vector.indexStates.length > 0) ||
|
|
2841
|
+
(vector?.status && vector.status !== "ready"),
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
function heapUsageRatio(readRatio) {
|
|
2846
|
+
if (typeof readRatio !== "function") return 0;
|
|
2847
|
+
const ratio = Number(readRatio());
|
|
2848
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : 0;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
function normalizeVectorWarmupPlans(plans = []) {
|
|
2852
|
+
const source = Array.isArray(plans) && plans.length ? plans : [{ key: "all" }];
|
|
2853
|
+
const out = [];
|
|
2854
|
+
const seen = new Set();
|
|
2855
|
+
for (const plan of source) {
|
|
2856
|
+
const key = String(plan?.key || plan?.indexKey || "all");
|
|
2857
|
+
if (seen.has(key)) continue;
|
|
2858
|
+
seen.add(key);
|
|
2859
|
+
out.push({
|
|
2860
|
+
key,
|
|
2861
|
+
includeId: typeof plan?.includeId === "function" ? plan.includeId : null,
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
return out;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
class Database {
|
|
2868
|
+
constructor(name, options = {}) {
|
|
2869
|
+
this.name = name;
|
|
2870
|
+
this.options = options;
|
|
2871
|
+
this.collections = new Map();
|
|
2872
|
+
this.meta = {};
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
collection(name) {
|
|
2876
|
+
let c = this.collections.get(name);
|
|
2877
|
+
if (!c) {
|
|
2878
|
+
c = new Collection(name, this.options);
|
|
2879
|
+
this.collections.set(name, c);
|
|
2880
|
+
}
|
|
2881
|
+
return c;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
collectionNames() {
|
|
2885
|
+
return [...this.collections.keys()];
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
getMeta() {
|
|
2889
|
+
return clone(this.meta);
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
setMeta(meta = {}) {
|
|
2893
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
2894
|
+
throw new TypeError("database meta must be a plain object");
|
|
2895
|
+
}
|
|
2896
|
+
this.meta = clone(meta);
|
|
2897
|
+
return this.getMeta();
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
deleteCollection(name) {
|
|
2901
|
+
return this.collections.delete(name);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
export class Store {
|
|
2906
|
+
constructor(options = {}) {
|
|
2907
|
+
this.options = options;
|
|
2908
|
+
this.derivedStateScheduler = options.derivedStateScheduler || new DerivedStateScheduler(options.derivedState);
|
|
2909
|
+
this.collectionOptions = { ...options, derivedStateScheduler: this.derivedStateScheduler };
|
|
2910
|
+
this.dbs = new Map();
|
|
2911
|
+
this.databaseTombstones = new Set();
|
|
2912
|
+
this.collectionTombstones = new Set();
|
|
2913
|
+
this.bulkLoadDepth = 0;
|
|
2914
|
+
this.bulkLoadDirty = new Set();
|
|
2915
|
+
this.indexWarmupPending = new Set();
|
|
2916
|
+
this.indexWarmupRetryTimer = null;
|
|
2917
|
+
this.readHeapUsageRatio = options.readHeapUsageRatio;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
dispose() {
|
|
2921
|
+
if (this.indexWarmupRetryTimer) clearTimeout(this.indexWarmupRetryTimer);
|
|
2922
|
+
this.indexWarmupRetryTimer = null;
|
|
2923
|
+
for (const db of this.dbs.values()) {
|
|
2924
|
+
for (const collection of db.collections.values()) this._disposeCollection(collection);
|
|
2925
|
+
}
|
|
2926
|
+
this.derivedStateScheduler.close();
|
|
2927
|
+
this.dbs.clear();
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
db(name) {
|
|
2931
|
+
let d = this.dbs.get(name);
|
|
2932
|
+
if (!d) {
|
|
2933
|
+
d = new Database(name, this.collectionOptions);
|
|
2934
|
+
this.dbs.set(name, d);
|
|
2935
|
+
}
|
|
2936
|
+
return d;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
collection(dbName, collName) {
|
|
2940
|
+
this.assertCollectionWritable(dbName, collName);
|
|
2941
|
+
return this.db(dbName).collection(collName);
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
getCollection(dbName, collName) {
|
|
2945
|
+
return this.dbs.get(dbName)?.collections.get(collName) || null;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
isCollectionDeleted(dbName, collName) {
|
|
2949
|
+
return this.collectionTombstones.has(storeCollectionKey(dbName, collName));
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
isDatabaseDeleted(dbName) {
|
|
2953
|
+
return this.databaseTombstones.has(String(dbName));
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
assertCollectionWritable(dbName, collName) {
|
|
2957
|
+
if (this.isDatabaseDeleted(dbName)) {
|
|
2958
|
+
const error = new Error("database was deleted; create it explicitly before writing");
|
|
2959
|
+
error.status = 409;
|
|
2960
|
+
throw error;
|
|
2961
|
+
}
|
|
2962
|
+
if (!this.isCollectionDeleted(dbName, collName)) return;
|
|
2963
|
+
const error = new Error("collection was deleted; create it explicitly before writing");
|
|
2964
|
+
error.status = 409;
|
|
2965
|
+
throw error;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
createDatabase(dbName, meta = {}) {
|
|
2969
|
+
this.databaseTombstones.delete(String(dbName));
|
|
2970
|
+
return this.db(dbName).setMeta(meta);
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
getDatabaseMeta(dbName) {
|
|
2974
|
+
const db = this.dbs.get(dbName);
|
|
2975
|
+
return db ? db.getMeta() : null;
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
setDatabaseMeta(dbName, meta = {}) {
|
|
2979
|
+
this.databaseTombstones.delete(String(dbName));
|
|
2980
|
+
return this.db(dbName).setMeta(meta);
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
hasDatabase(dbName) {
|
|
2984
|
+
return this.dbs.has(dbName);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
deleteDatabase(dbName) {
|
|
2988
|
+
this.databaseTombstones.add(String(dbName));
|
|
2989
|
+
const db = this.dbs.get(dbName);
|
|
2990
|
+
if (!db) return false;
|
|
2991
|
+
for (const collection of db.collections.values()) this._disposeCollection(collection);
|
|
2992
|
+
return this.dbs.delete(dbName);
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
createCollection(dbName, collName, meta = {}) {
|
|
2996
|
+
this.collectionTombstones.delete(storeCollectionKey(dbName, collName));
|
|
2997
|
+
const collection = this.collection(dbName, collName);
|
|
2998
|
+
const next = collection.setMeta(meta, { deferIndexes: this.bulkLoadDepth > 0 });
|
|
2999
|
+
if (this.bulkLoadDepth > 0) this.bulkLoadDirty.add(collection);
|
|
3000
|
+
return next;
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
deleteCollection(dbName, collName) {
|
|
3004
|
+
this.collectionTombstones.add(storeCollectionKey(dbName, collName));
|
|
3005
|
+
const db = this.dbs.get(dbName);
|
|
3006
|
+
if (!db) return false;
|
|
3007
|
+
const collection = db.collections.get(collName);
|
|
3008
|
+
const removed = db.deleteCollection(collName);
|
|
3009
|
+
if (removed) this._disposeCollection(collection);
|
|
3010
|
+
return removed;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
_disposeCollection(collection) {
|
|
3014
|
+
if (!collection) return;
|
|
3015
|
+
this.bulkLoadDirty.delete(collection);
|
|
3016
|
+
this.indexWarmupPending.delete(collection);
|
|
3017
|
+
collection.dispose?.();
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
collectionNames(dbName) {
|
|
3021
|
+
const db = this.dbs.get(dbName);
|
|
3022
|
+
return db ? db.collectionNames() : [];
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
getCollectionMeta(dbName, collName) {
|
|
3026
|
+
const db = this.dbs.get(dbName);
|
|
3027
|
+
const collection = db?.collections.get(collName);
|
|
3028
|
+
return collection ? collection.getMeta() : null;
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
setCollectionMeta(dbName, collName, meta) {
|
|
3032
|
+
this.collectionTombstones.delete(storeCollectionKey(dbName, collName));
|
|
3033
|
+
const collection = this.collection(dbName, collName);
|
|
3034
|
+
const next = collection.setMeta(meta, { deferIndexes: this.bulkLoadDepth > 0 });
|
|
3035
|
+
if (this.bulkLoadDepth > 0) this.bulkLoadDirty.add(collection);
|
|
3036
|
+
return next;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
awaitFieldIndexes(dbName, collName) {
|
|
3040
|
+
return this.collection(dbName, collName).awaitFieldIndexes();
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
awaitColumns(dbName, collName) {
|
|
3044
|
+
return this.collection(dbName, collName).awaitColumns();
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
async awaitIndexes(dbName, collName) {
|
|
3048
|
+
const collection = this.collection(dbName, collName);
|
|
3049
|
+
if (collection.indexBuildPending) collection.scheduleIndexBuildsFromMeta();
|
|
3050
|
+
await Promise.all([
|
|
3051
|
+
collection.awaitFieldIndexes(),
|
|
3052
|
+
collection.awaitColumns(),
|
|
3053
|
+
collection.awaitDirtyIndexes(),
|
|
3054
|
+
]);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
idsForShards(dbName, collName, shards = []) {
|
|
3058
|
+
const db = this.dbs.get(dbName);
|
|
3059
|
+
const collection = db?.collections.get(collName);
|
|
3060
|
+
return collection ? collection.idsForShards(shards) : null;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
collectionMetadata() {
|
|
3064
|
+
const out = [];
|
|
3065
|
+
for (const [dbName, db] of this.dbs) {
|
|
3066
|
+
for (const [collName, collection] of db.collections) {
|
|
3067
|
+
out.push({ database: dbName, collection: collName, meta: collection.getMeta() });
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return out;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
applyOps(ops) {
|
|
3074
|
+
const applied = [];
|
|
3075
|
+
let runKey = null;
|
|
3076
|
+
let run = [];
|
|
3077
|
+
|
|
3078
|
+
const flush = () => {
|
|
3079
|
+
if (!run.length) return;
|
|
3080
|
+
const [dbName, collName, kind] = runKey.split("\0");
|
|
3081
|
+
const collection = this.collection(dbName, collName);
|
|
3082
|
+
const appliedRows = kind === "delObj"
|
|
3083
|
+
? collection.deleteObjects(run)
|
|
3084
|
+
: collection.putObjects(run);
|
|
3085
|
+
for (const item of appliedRows) applied.push(item.op);
|
|
3086
|
+
run = [];
|
|
3087
|
+
runKey = null;
|
|
3088
|
+
};
|
|
3089
|
+
|
|
3090
|
+
for (const op of ops || []) {
|
|
3091
|
+
if (this.isDatabaseDeleted(op.db) || this.isCollectionDeleted(op.db, op.coll)) continue;
|
|
3092
|
+
const kind = op.kind === "delObj" ? "delObj" : "putObj";
|
|
3093
|
+
const key = `${op.db}\0${op.coll}\0${kind}`;
|
|
3094
|
+
if (runKey && runKey !== key) flush();
|
|
3095
|
+
runKey = key;
|
|
3096
|
+
run.push({
|
|
3097
|
+
id: op.id,
|
|
3098
|
+
value: op.kind === "touch" ? {} : op.value || {},
|
|
3099
|
+
ts: op.ts,
|
|
3100
|
+
shard: op.shard,
|
|
3101
|
+
op,
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
flush();
|
|
3105
|
+
return applied;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
beginBulkLoad() {
|
|
3109
|
+
this.bulkLoadDepth++;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
endBulkLoad() {
|
|
3113
|
+
if (this.bulkLoadDepth <= 0) return;
|
|
3114
|
+
this.bulkLoadDepth--;
|
|
3115
|
+
if (this.bulkLoadDepth > 0) return;
|
|
3116
|
+
const dirty = [...this.bulkLoadDirty];
|
|
3117
|
+
this.bulkLoadDirty.clear();
|
|
3118
|
+
for (const collection of dirty) {
|
|
3119
|
+
collection.rebuildCoreState();
|
|
3120
|
+
collection.indexBuildPending = true;
|
|
3121
|
+
this.indexWarmupPending.add(collection);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
startIndexWarmup() {
|
|
3126
|
+
if (!this.indexWarmupPending.size) return 0;
|
|
3127
|
+
if (heapUsageRatio(this.readHeapUsageRatio) >= INDEX_WARMUP_MAX_HEAP_RATIO) {
|
|
3128
|
+
this._scheduleIndexWarmupRetry();
|
|
3129
|
+
return 0;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
const collection = this.indexWarmupPending.values().next().value;
|
|
3133
|
+
this.indexWarmupPending.delete(collection);
|
|
3134
|
+
collection.scheduleIndexBuildsFromMeta();
|
|
3135
|
+
if (this.indexWarmupPending.size) this._scheduleIndexWarmupRetry();
|
|
3136
|
+
return 1;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
_scheduleIndexWarmupRetry() {
|
|
3140
|
+
if (this.indexWarmupRetryTimer || !this.indexWarmupPending.size) return;
|
|
3141
|
+
this.indexWarmupRetryTimer = setTimeout(() => {
|
|
3142
|
+
this.indexWarmupRetryTimer = null;
|
|
3143
|
+
this.startIndexWarmup();
|
|
3144
|
+
}, INDEX_WARMUP_RETRY_MS);
|
|
3145
|
+
this.indexWarmupRetryTimer.unref?.();
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
applySnapshotObjects(dbName, collName, rows, options = {}) {
|
|
3149
|
+
if (this.isCollectionDeleted(dbName, collName)) return 0;
|
|
3150
|
+
const collection = this.collection(dbName, collName);
|
|
3151
|
+
const applied = collection.applySnapshotObjects(rows, {
|
|
3152
|
+
...options,
|
|
3153
|
+
deferDerivedIndexes: this.bulkLoadDepth > 0,
|
|
3154
|
+
});
|
|
3155
|
+
if (applied && this.bulkLoadDepth > 0) this.bulkLoadDirty.add(collection);
|
|
3156
|
+
return applied;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
readObjects(dbName, collName, ids) {
|
|
3160
|
+
return this.getCollection(dbName, collName)?.readObjects(ids) || [];
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
queryPath(dbName, collName, criteria = {}, options = {}) {
|
|
3164
|
+
return this.getCollection(dbName, collName)?.readPath(criteria, options) || READ_PATH.SCAN;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
aggregatePath(dbName, collName, criteria = {}, spec = {}, options = {}) {
|
|
3168
|
+
return this.getCollection(dbName, collName)?.aggregatePath(criteria, spec, options) || AGGREGATE_PATH.SCAN;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
validateWriteValue(dbName, collName, value = {}) {
|
|
3172
|
+
return this.collection(dbName, collName).validateWriteValue(value);
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
preserveVector(dbName, collName, id, value = {}) {
|
|
3176
|
+
return this.collection(dbName, collName).preserveVector(id, value);
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
getVector(dbName, collName, id) {
|
|
3180
|
+
return this.getCollection(dbName, collName)?.getVector(id) || null;
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
vectorSearch(dbName, collName, queryVec, limit = 20, options = {}) {
|
|
3184
|
+
try {
|
|
3185
|
+
return this.getCollection(dbName, collName)?.vectorSearch(queryVec, limit, options) || [];
|
|
3186
|
+
} catch (err) {
|
|
3187
|
+
throw annotateVectorIndexError(err, dbName, collName);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
assertVectorIndexReady(dbName, collName, options = {}) {
|
|
3192
|
+
try {
|
|
3193
|
+
return this.collection(dbName, collName).assertVectorIndexReady(options.indexKey || "all", options.includeId);
|
|
3194
|
+
} catch (err) {
|
|
3195
|
+
throw annotateVectorIndexError(err, dbName, collName);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
rankVectorRows(dbName, collName, rows, queryVec, limit = 20) {
|
|
3200
|
+
return this.collection(dbName, collName).rankVectorRows(rows, queryVec, limit);
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
vectorStats() {
|
|
3204
|
+
const out = [];
|
|
3205
|
+
for (const [dbName, db] of this.dbs) {
|
|
3206
|
+
for (const [collName, collection] of db.collections) {
|
|
3207
|
+
const stats = collection.vectorStats();
|
|
3208
|
+
if (!hasVectorIndexValue(stats)) continue;
|
|
3209
|
+
out.push({ database: dbName, collection: collName, ...stats });
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
return out;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
vectorSummary() {
|
|
3216
|
+
const summary = {
|
|
3217
|
+
storedVectors: 0,
|
|
3218
|
+
activeIndexes: 0,
|
|
3219
|
+
warmingIndexes: 0,
|
|
3220
|
+
errors: 0,
|
|
3221
|
+
};
|
|
3222
|
+
for (const db of this.dbs.values()) {
|
|
3223
|
+
for (const collection of db.collections.values()) {
|
|
3224
|
+
summary.storedVectors += collection.vectors.size;
|
|
3225
|
+
summary.activeIndexes += collection.vectorIndexes.size;
|
|
3226
|
+
for (const index of collection.vectorIndexes.values()) {
|
|
3227
|
+
if (index.status === "error" || index.status === "failed") summary.errors++;
|
|
3228
|
+
else if (index.status !== "ready") summary.warmingIndexes++;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return summary;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
compressionStats() {
|
|
3236
|
+
const collections = [];
|
|
3237
|
+
for (const [dbName, db] of this.dbs) {
|
|
3238
|
+
for (const [collName, collection] of db.collections) {
|
|
3239
|
+
const stats = collection.compressionStats();
|
|
3240
|
+
if (stats.mode === "off" && stats.compressedObjects === 0) continue;
|
|
3241
|
+
collections.push({ database: dbName, collection: collName, ...stats });
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
return { ...summarizeCompression(collections), collections };
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
compressionSummary() {
|
|
3248
|
+
const summary = {
|
|
3249
|
+
collectionCount: 0,
|
|
3250
|
+
liveObjects: 0,
|
|
3251
|
+
compressedObjects: 0,
|
|
3252
|
+
uncompressedBytes: 0,
|
|
3253
|
+
compressedBytes: 0,
|
|
3254
|
+
};
|
|
3255
|
+
for (const db of this.dbs.values()) {
|
|
3256
|
+
for (const collection of db.collections.values()) {
|
|
3257
|
+
const { objects, uncompressedBytes, compressedBytes } = collection.compressionTotals;
|
|
3258
|
+
if (!collection.collectionMeta.tiering && objects === 0) continue;
|
|
3259
|
+
summary.collectionCount++;
|
|
3260
|
+
summary.liveObjects += collection.liveCount;
|
|
3261
|
+
summary.compressedObjects += objects;
|
|
3262
|
+
summary.uncompressedBytes += uncompressedBytes;
|
|
3263
|
+
summary.compressedBytes += compressedBytes;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
const savedBytes = summary.uncompressedBytes - summary.compressedBytes;
|
|
3267
|
+
return {
|
|
3268
|
+
...summary,
|
|
3269
|
+
coveragePercent: percentOf(summary.compressedObjects, summary.liveObjects),
|
|
3270
|
+
savedBytes,
|
|
3271
|
+
savedPercent: percentOf(savedBytes, summary.uncompressedBytes),
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
indexSummary() {
|
|
3276
|
+
const summary = { ready: 0, building: 0, error: 0, activeTasks: 0 };
|
|
3277
|
+
for (const db of this.dbs.values()) {
|
|
3278
|
+
for (const collection of db.collections.values()) {
|
|
3279
|
+
const readiness = collection.indexSummary();
|
|
3280
|
+
summary.activeTasks += readiness.activeTasks;
|
|
3281
|
+
if (!readiness.configured) continue;
|
|
3282
|
+
summary[readiness.status === "ready" ? "ready" : readiness.status === "building" ? "building" : "error"]++;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
return summary;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
indexReadiness() {
|
|
3289
|
+
const collections = [];
|
|
3290
|
+
const workers = {
|
|
3291
|
+
fieldIndex: {
|
|
3292
|
+
type: "same_thread_task",
|
|
3293
|
+
active: 0,
|
|
3294
|
+
background: true,
|
|
3295
|
+
},
|
|
3296
|
+
columns: {
|
|
3297
|
+
type: "same_thread_task",
|
|
3298
|
+
active: 0,
|
|
3299
|
+
background: true,
|
|
3300
|
+
},
|
|
3301
|
+
vectorIndex: {
|
|
3302
|
+
type: "same_thread_task",
|
|
3303
|
+
active: 0,
|
|
3304
|
+
background: true,
|
|
3305
|
+
},
|
|
3306
|
+
};
|
|
3307
|
+
|
|
3308
|
+
for (const [dbName, db] of this.dbs) {
|
|
3309
|
+
for (const [collName, collection] of db.collections) {
|
|
3310
|
+
const readiness = collection.indexReadiness();
|
|
3311
|
+
workers.fieldIndex.active += readiness.workers.fieldIndex.active;
|
|
3312
|
+
workers.columns.active += readiness.workers.columns.active;
|
|
3313
|
+
workers.vectorIndex.active += readiness.workers.vectorIndex.active;
|
|
3314
|
+
if (hasCollectionIndexReadiness(readiness)) {
|
|
3315
|
+
collections.push({
|
|
3316
|
+
database: dbName,
|
|
3317
|
+
collection: collName,
|
|
3318
|
+
...readiness,
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
return {
|
|
3325
|
+
status: overallIndexStatus(collections.map((collection) => collection.status)),
|
|
3326
|
+
workers,
|
|
3327
|
+
collections,
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
clearVectorIndexes() {
|
|
3332
|
+
for (const db of this.dbs.values()) {
|
|
3333
|
+
for (const collection of db.collections.values()) collection.clearVectorIndexes();
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
warmVectorIndexes(plansForCollection = null) {
|
|
3338
|
+
const builds = [];
|
|
3339
|
+
for (const [dbName, db] of this.dbs) {
|
|
3340
|
+
for (const [collName, collection] of db.collections) {
|
|
3341
|
+
const plans = normalizeVectorWarmupPlans(
|
|
3342
|
+
typeof plansForCollection === "function"
|
|
3343
|
+
? plansForCollection(dbName, collName)
|
|
3344
|
+
: [{ key: "all" }],
|
|
3345
|
+
);
|
|
3346
|
+
for (const plan of plans) builds.push(collection.warmVectorIndex(plan.key, plan.includeId));
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
return Promise.all(builds);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
async awaitVectorIndexes() {
|
|
3353
|
+
for (;;) {
|
|
3354
|
+
const waits = [];
|
|
3355
|
+
for (const db of this.dbs.values()) {
|
|
3356
|
+
for (const collection of db.collections.values()) waits.push(collection.awaitVectorIndexes());
|
|
3357
|
+
}
|
|
3358
|
+
await Promise.allSettled(waits);
|
|
3359
|
+
const stillBuilding = [];
|
|
3360
|
+
for (const db of this.dbs.values()) {
|
|
3361
|
+
for (const collection of db.collections.values()) {
|
|
3362
|
+
if (collection.vectorBuilds.size) stillBuilding.push(collection);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (!stillBuilding.length) return;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
dbNames() {
|
|
3370
|
+
return [...this.dbs.keys()];
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
*collectionSnapshotObjects(dbName, collName, options = {}) {
|
|
3374
|
+
const collection = this.dbs.get(dbName)?.collections.get(collName);
|
|
3375
|
+
if (!collection) return;
|
|
3376
|
+
yield* collection.snapshotObjects(options);
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
snapshot({ database, collection, includeObject } = {}) {
|
|
3380
|
+
const collections = [];
|
|
3381
|
+
for (const [dbName, d] of this.dbs) {
|
|
3382
|
+
if (!matchesSnapshotName(dbName, database)) continue;
|
|
3383
|
+
for (const [collName, c] of d.collections) {
|
|
3384
|
+
if (!matchesSnapshotName(collName, collection)) continue;
|
|
3385
|
+
collections.push({
|
|
3386
|
+
db: dbName,
|
|
3387
|
+
coll: collName,
|
|
3388
|
+
objects: c.snapshot({
|
|
3389
|
+
includeId: typeof includeObject === "function"
|
|
3390
|
+
? (id) => includeObject(dbName, collName, id)
|
|
3391
|
+
: undefined,
|
|
3392
|
+
}),
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
return collections;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
function emptyCompressionTotals() {
|
|
3401
|
+
return { objects: 0, uncompressedBytes: 0, compressedBytes: 0 };
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function storeCollectionKey(database, collection) {
|
|
3405
|
+
return `${String(database)}\0${String(collection)}`;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
function matchesSnapshotName(name, expected) {
|
|
3409
|
+
return !expected || expected === "*" || expected === name;
|
|
3410
|
+
}
|