@push.rocks/smartmongo 2.0.14 → 2.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/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/congodb/congodb.plugins.d.ts +10 -0
- package/dist_ts/congodb/congodb.plugins.js +14 -0
- package/dist_ts/congodb/engine/AggregationEngine.d.ts +66 -0
- package/dist_ts/congodb/engine/AggregationEngine.js +189 -0
- package/dist_ts/congodb/engine/IndexEngine.d.ts +77 -0
- package/dist_ts/congodb/engine/IndexEngine.js +376 -0
- package/dist_ts/congodb/engine/QueryEngine.d.ts +54 -0
- package/dist_ts/congodb/engine/QueryEngine.js +271 -0
- package/dist_ts/congodb/engine/TransactionEngine.d.ts +85 -0
- package/dist_ts/congodb/engine/TransactionEngine.js +287 -0
- package/dist_ts/congodb/engine/UpdateEngine.d.ts +47 -0
- package/dist_ts/congodb/engine/UpdateEngine.js +461 -0
- package/dist_ts/congodb/errors/CongoErrors.d.ts +100 -0
- package/dist_ts/congodb/errors/CongoErrors.js +155 -0
- package/dist_ts/congodb/index.d.ts +19 -0
- package/dist_ts/congodb/index.js +26 -0
- package/dist_ts/congodb/server/CommandRouter.d.ts +51 -0
- package/dist_ts/congodb/server/CommandRouter.js +132 -0
- package/dist_ts/congodb/server/CongoServer.d.ts +95 -0
- package/dist_ts/congodb/server/CongoServer.js +227 -0
- package/dist_ts/congodb/server/WireProtocol.d.ts +117 -0
- package/dist_ts/congodb/server/WireProtocol.js +298 -0
- package/dist_ts/congodb/server/handlers/AdminHandler.d.ts +100 -0
- package/dist_ts/congodb/server/handlers/AdminHandler.js +568 -0
- package/dist_ts/congodb/server/handlers/AggregateHandler.d.ts +31 -0
- package/dist_ts/congodb/server/handlers/AggregateHandler.js +277 -0
- package/dist_ts/congodb/server/handlers/DeleteHandler.d.ts +8 -0
- package/dist_ts/congodb/server/handlers/DeleteHandler.js +83 -0
- package/dist_ts/congodb/server/handlers/FindHandler.d.ts +31 -0
- package/dist_ts/congodb/server/handlers/FindHandler.js +261 -0
- package/dist_ts/congodb/server/handlers/HelloHandler.d.ts +11 -0
- package/dist_ts/congodb/server/handlers/HelloHandler.js +62 -0
- package/dist_ts/congodb/server/handlers/IndexHandler.d.ts +20 -0
- package/dist_ts/congodb/server/handlers/IndexHandler.js +183 -0
- package/dist_ts/congodb/server/handlers/InsertHandler.d.ts +8 -0
- package/dist_ts/congodb/server/handlers/InsertHandler.js +76 -0
- package/dist_ts/congodb/server/handlers/UpdateHandler.d.ts +24 -0
- package/dist_ts/congodb/server/handlers/UpdateHandler.js +270 -0
- package/dist_ts/congodb/server/handlers/index.d.ts +8 -0
- package/dist_ts/congodb/server/handlers/index.js +10 -0
- package/dist_ts/congodb/server/index.d.ts +6 -0
- package/dist_ts/congodb/server/index.js +7 -0
- package/dist_ts/congodb/storage/FileStorageAdapter.d.ts +61 -0
- package/dist_ts/congodb/storage/FileStorageAdapter.js +396 -0
- package/dist_ts/congodb/storage/IStorageAdapter.d.ts +140 -0
- package/dist_ts/congodb/storage/IStorageAdapter.js +2 -0
- package/dist_ts/congodb/storage/MemoryStorageAdapter.d.ts +66 -0
- package/dist_ts/congodb/storage/MemoryStorageAdapter.js +367 -0
- package/dist_ts/congodb/storage/OpLog.d.ts +93 -0
- package/dist_ts/congodb/storage/OpLog.js +221 -0
- package/dist_ts/congodb/types/interfaces.d.ts +363 -0
- package/dist_ts/congodb/types/interfaces.js +2 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +8 -6
- package/npmextra.json +17 -7
- package/package.json +20 -12
- package/readme.hints.md +79 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/congodb/congodb.plugins.ts +17 -0
- package/ts/congodb/engine/AggregationEngine.ts +283 -0
- package/ts/congodb/engine/IndexEngine.ts +479 -0
- package/ts/congodb/engine/QueryEngine.ts +301 -0
- package/ts/congodb/engine/TransactionEngine.ts +351 -0
- package/ts/congodb/engine/UpdateEngine.ts +506 -0
- package/ts/congodb/errors/CongoErrors.ts +181 -0
- package/ts/congodb/index.ts +37 -0
- package/ts/congodb/server/CommandRouter.ts +180 -0
- package/ts/congodb/server/CongoServer.ts +298 -0
- package/ts/congodb/server/WireProtocol.ts +416 -0
- package/ts/congodb/server/handlers/AdminHandler.ts +614 -0
- package/ts/congodb/server/handlers/AggregateHandler.ts +342 -0
- package/ts/congodb/server/handlers/DeleteHandler.ts +100 -0
- package/ts/congodb/server/handlers/FindHandler.ts +301 -0
- package/ts/congodb/server/handlers/HelloHandler.ts +78 -0
- package/ts/congodb/server/handlers/IndexHandler.ts +207 -0
- package/ts/congodb/server/handlers/InsertHandler.ts +91 -0
- package/ts/congodb/server/handlers/UpdateHandler.ts +315 -0
- package/ts/congodb/server/handlers/index.ts +10 -0
- package/ts/congodb/server/index.ts +10 -0
- package/ts/congodb/storage/FileStorageAdapter.ts +479 -0
- package/ts/congodb/storage/IStorageAdapter.ts +202 -0
- package/ts/congodb/storage/MemoryStorageAdapter.ts +443 -0
- package/ts/congodb/storage/OpLog.ts +282 -0
- package/ts/congodb/types/interfaces.ts +433 -0
- package/ts/index.ts +3 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import * as plugins from '../congodb.plugins.js';
|
|
2
|
+
import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
|
|
3
|
+
import type {
|
|
4
|
+
Document,
|
|
5
|
+
IStoredDocument,
|
|
6
|
+
IIndexSpecification,
|
|
7
|
+
IIndexInfo,
|
|
8
|
+
ICreateIndexOptions,
|
|
9
|
+
} from '../types/interfaces.js';
|
|
10
|
+
import { CongoDuplicateKeyError, CongoIndexError } from '../errors/CongoErrors.js';
|
|
11
|
+
import { QueryEngine } from './QueryEngine.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Index data structure for fast lookups
|
|
15
|
+
*/
|
|
16
|
+
interface IIndexData {
|
|
17
|
+
name: string;
|
|
18
|
+
key: Record<string, 1 | -1 | string>;
|
|
19
|
+
unique: boolean;
|
|
20
|
+
sparse: boolean;
|
|
21
|
+
expireAfterSeconds?: number;
|
|
22
|
+
// Map from index key value to document _id(s)
|
|
23
|
+
entries: Map<string, Set<string>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Index engine for managing indexes and query optimization
|
|
28
|
+
*/
|
|
29
|
+
export class IndexEngine {
|
|
30
|
+
private dbName: string;
|
|
31
|
+
private collName: string;
|
|
32
|
+
private storage: IStorageAdapter;
|
|
33
|
+
private indexes: Map<string, IIndexData> = new Map();
|
|
34
|
+
private initialized = false;
|
|
35
|
+
|
|
36
|
+
constructor(dbName: string, collName: string, storage: IStorageAdapter) {
|
|
37
|
+
this.dbName = dbName;
|
|
38
|
+
this.collName = collName;
|
|
39
|
+
this.storage = storage;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialize indexes from storage
|
|
44
|
+
*/
|
|
45
|
+
async initialize(): Promise<void> {
|
|
46
|
+
if (this.initialized) return;
|
|
47
|
+
|
|
48
|
+
const storedIndexes = await this.storage.getIndexes(this.dbName, this.collName);
|
|
49
|
+
const documents = await this.storage.findAll(this.dbName, this.collName);
|
|
50
|
+
|
|
51
|
+
for (const indexSpec of storedIndexes) {
|
|
52
|
+
const indexData: IIndexData = {
|
|
53
|
+
name: indexSpec.name,
|
|
54
|
+
key: indexSpec.key,
|
|
55
|
+
unique: indexSpec.unique || false,
|
|
56
|
+
sparse: indexSpec.sparse || false,
|
|
57
|
+
expireAfterSeconds: indexSpec.expireAfterSeconds,
|
|
58
|
+
entries: new Map(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Build index entries
|
|
62
|
+
for (const doc of documents) {
|
|
63
|
+
const keyValue = this.extractKeyValue(doc, indexSpec.key);
|
|
64
|
+
if (keyValue !== null || !indexData.sparse) {
|
|
65
|
+
const keyStr = JSON.stringify(keyValue);
|
|
66
|
+
if (!indexData.entries.has(keyStr)) {
|
|
67
|
+
indexData.entries.set(keyStr, new Set());
|
|
68
|
+
}
|
|
69
|
+
indexData.entries.get(keyStr)!.add(doc._id.toHexString());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.indexes.set(indexSpec.name, indexData);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a new index
|
|
81
|
+
*/
|
|
82
|
+
async createIndex(
|
|
83
|
+
key: Record<string, 1 | -1 | 'text' | '2dsphere'>,
|
|
84
|
+
options?: ICreateIndexOptions
|
|
85
|
+
): Promise<string> {
|
|
86
|
+
await this.initialize();
|
|
87
|
+
|
|
88
|
+
// Generate index name if not provided
|
|
89
|
+
const name = options?.name || this.generateIndexName(key);
|
|
90
|
+
|
|
91
|
+
// Check if index already exists
|
|
92
|
+
if (this.indexes.has(name)) {
|
|
93
|
+
return name;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create index data structure
|
|
97
|
+
const indexData: IIndexData = {
|
|
98
|
+
name,
|
|
99
|
+
key: key as Record<string, 1 | -1 | string>,
|
|
100
|
+
unique: options?.unique || false,
|
|
101
|
+
sparse: options?.sparse || false,
|
|
102
|
+
expireAfterSeconds: options?.expireAfterSeconds,
|
|
103
|
+
entries: new Map(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Build index from existing documents
|
|
107
|
+
const documents = await this.storage.findAll(this.dbName, this.collName);
|
|
108
|
+
|
|
109
|
+
for (const doc of documents) {
|
|
110
|
+
const keyValue = this.extractKeyValue(doc, key);
|
|
111
|
+
|
|
112
|
+
if (keyValue === null && indexData.sparse) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const keyStr = JSON.stringify(keyValue);
|
|
117
|
+
|
|
118
|
+
if (indexData.unique && indexData.entries.has(keyStr)) {
|
|
119
|
+
throw new CongoDuplicateKeyError(
|
|
120
|
+
`E11000 duplicate key error index: ${this.dbName}.${this.collName}.$${name}`,
|
|
121
|
+
key as Record<string, 1>,
|
|
122
|
+
keyValue
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!indexData.entries.has(keyStr)) {
|
|
127
|
+
indexData.entries.set(keyStr, new Set());
|
|
128
|
+
}
|
|
129
|
+
indexData.entries.get(keyStr)!.add(doc._id.toHexString());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Store index
|
|
133
|
+
this.indexes.set(name, indexData);
|
|
134
|
+
await this.storage.saveIndex(this.dbName, this.collName, name, {
|
|
135
|
+
key,
|
|
136
|
+
unique: options?.unique,
|
|
137
|
+
sparse: options?.sparse,
|
|
138
|
+
expireAfterSeconds: options?.expireAfterSeconds,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return name;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Drop an index
|
|
146
|
+
*/
|
|
147
|
+
async dropIndex(name: string): Promise<void> {
|
|
148
|
+
await this.initialize();
|
|
149
|
+
|
|
150
|
+
if (name === '_id_') {
|
|
151
|
+
throw new CongoIndexError('cannot drop _id index');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!this.indexes.has(name)) {
|
|
155
|
+
throw new CongoIndexError(`index not found: ${name}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.indexes.delete(name);
|
|
159
|
+
await this.storage.dropIndex(this.dbName, this.collName, name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Drop all indexes except _id
|
|
164
|
+
*/
|
|
165
|
+
async dropAllIndexes(): Promise<void> {
|
|
166
|
+
await this.initialize();
|
|
167
|
+
|
|
168
|
+
const names = Array.from(this.indexes.keys()).filter(n => n !== '_id_');
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
this.indexes.delete(name);
|
|
171
|
+
await this.storage.dropIndex(this.dbName, this.collName, name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* List all indexes
|
|
177
|
+
*/
|
|
178
|
+
async listIndexes(): Promise<IIndexInfo[]> {
|
|
179
|
+
await this.initialize();
|
|
180
|
+
|
|
181
|
+
return Array.from(this.indexes.values()).map(idx => ({
|
|
182
|
+
v: 2,
|
|
183
|
+
key: idx.key,
|
|
184
|
+
name: idx.name,
|
|
185
|
+
unique: idx.unique || undefined,
|
|
186
|
+
sparse: idx.sparse || undefined,
|
|
187
|
+
expireAfterSeconds: idx.expireAfterSeconds,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if an index exists
|
|
193
|
+
*/
|
|
194
|
+
async indexExists(name: string): Promise<boolean> {
|
|
195
|
+
await this.initialize();
|
|
196
|
+
return this.indexes.has(name);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Update index entries after document insert
|
|
201
|
+
*/
|
|
202
|
+
async onInsert(doc: IStoredDocument): Promise<void> {
|
|
203
|
+
await this.initialize();
|
|
204
|
+
|
|
205
|
+
for (const [name, indexData] of this.indexes) {
|
|
206
|
+
const keyValue = this.extractKeyValue(doc, indexData.key);
|
|
207
|
+
|
|
208
|
+
if (keyValue === null && indexData.sparse) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const keyStr = JSON.stringify(keyValue);
|
|
213
|
+
|
|
214
|
+
// Check unique constraint
|
|
215
|
+
if (indexData.unique) {
|
|
216
|
+
const existing = indexData.entries.get(keyStr);
|
|
217
|
+
if (existing && existing.size > 0) {
|
|
218
|
+
throw new CongoDuplicateKeyError(
|
|
219
|
+
`E11000 duplicate key error collection: ${this.dbName}.${this.collName} index: ${name}`,
|
|
220
|
+
indexData.key as Record<string, 1>,
|
|
221
|
+
keyValue
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!indexData.entries.has(keyStr)) {
|
|
227
|
+
indexData.entries.set(keyStr, new Set());
|
|
228
|
+
}
|
|
229
|
+
indexData.entries.get(keyStr)!.add(doc._id.toHexString());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Update index entries after document update
|
|
235
|
+
*/
|
|
236
|
+
async onUpdate(oldDoc: IStoredDocument, newDoc: IStoredDocument): Promise<void> {
|
|
237
|
+
await this.initialize();
|
|
238
|
+
|
|
239
|
+
for (const [name, indexData] of this.indexes) {
|
|
240
|
+
const oldKeyValue = this.extractKeyValue(oldDoc, indexData.key);
|
|
241
|
+
const newKeyValue = this.extractKeyValue(newDoc, indexData.key);
|
|
242
|
+
const oldKeyStr = JSON.stringify(oldKeyValue);
|
|
243
|
+
const newKeyStr = JSON.stringify(newKeyValue);
|
|
244
|
+
|
|
245
|
+
// Remove old entry if key changed
|
|
246
|
+
if (oldKeyStr !== newKeyStr) {
|
|
247
|
+
if (oldKeyValue !== null || !indexData.sparse) {
|
|
248
|
+
const oldSet = indexData.entries.get(oldKeyStr);
|
|
249
|
+
if (oldSet) {
|
|
250
|
+
oldSet.delete(oldDoc._id.toHexString());
|
|
251
|
+
if (oldSet.size === 0) {
|
|
252
|
+
indexData.entries.delete(oldKeyStr);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Add new entry
|
|
258
|
+
if (newKeyValue !== null || !indexData.sparse) {
|
|
259
|
+
// Check unique constraint
|
|
260
|
+
if (indexData.unique) {
|
|
261
|
+
const existing = indexData.entries.get(newKeyStr);
|
|
262
|
+
if (existing && existing.size > 0) {
|
|
263
|
+
throw new CongoDuplicateKeyError(
|
|
264
|
+
`E11000 duplicate key error collection: ${this.dbName}.${this.collName} index: ${name}`,
|
|
265
|
+
indexData.key as Record<string, 1>,
|
|
266
|
+
newKeyValue
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!indexData.entries.has(newKeyStr)) {
|
|
272
|
+
indexData.entries.set(newKeyStr, new Set());
|
|
273
|
+
}
|
|
274
|
+
indexData.entries.get(newKeyStr)!.add(newDoc._id.toHexString());
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Update index entries after document delete
|
|
282
|
+
*/
|
|
283
|
+
async onDelete(doc: IStoredDocument): Promise<void> {
|
|
284
|
+
await this.initialize();
|
|
285
|
+
|
|
286
|
+
for (const indexData of this.indexes.values()) {
|
|
287
|
+
const keyValue = this.extractKeyValue(doc, indexData.key);
|
|
288
|
+
|
|
289
|
+
if (keyValue === null && indexData.sparse) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const keyStr = JSON.stringify(keyValue);
|
|
294
|
+
const set = indexData.entries.get(keyStr);
|
|
295
|
+
if (set) {
|
|
296
|
+
set.delete(doc._id.toHexString());
|
|
297
|
+
if (set.size === 0) {
|
|
298
|
+
indexData.entries.delete(keyStr);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Find the best index for a query
|
|
306
|
+
*/
|
|
307
|
+
selectIndex(filter: Document): { name: string; data: IIndexData } | null {
|
|
308
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Get filter fields
|
|
313
|
+
const filterFields = new Set(this.getFilterFields(filter));
|
|
314
|
+
|
|
315
|
+
// Score each index
|
|
316
|
+
let bestIndex: { name: string; data: IIndexData } | null = null;
|
|
317
|
+
let bestScore = 0;
|
|
318
|
+
|
|
319
|
+
for (const [name, indexData] of this.indexes) {
|
|
320
|
+
const indexFields = Object.keys(indexData.key);
|
|
321
|
+
let score = 0;
|
|
322
|
+
|
|
323
|
+
// Count how many index fields are in the filter
|
|
324
|
+
for (const field of indexFields) {
|
|
325
|
+
if (filterFields.has(field)) {
|
|
326
|
+
score++;
|
|
327
|
+
} else {
|
|
328
|
+
break; // Index fields must be contiguous
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Prefer unique indexes
|
|
333
|
+
if (indexData.unique && score > 0) {
|
|
334
|
+
score += 0.5;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (score > bestScore) {
|
|
338
|
+
bestScore = score;
|
|
339
|
+
bestIndex = { name, data: indexData };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return bestIndex;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Use index to find candidate document IDs
|
|
348
|
+
*/
|
|
349
|
+
async findCandidateIds(filter: Document): Promise<Set<string> | null> {
|
|
350
|
+
await this.initialize();
|
|
351
|
+
|
|
352
|
+
const index = this.selectIndex(filter);
|
|
353
|
+
if (!index) return null;
|
|
354
|
+
|
|
355
|
+
// Try to use the index for equality matches
|
|
356
|
+
const indexFields = Object.keys(index.data.key);
|
|
357
|
+
const equalityValues: Record<string, any> = {};
|
|
358
|
+
|
|
359
|
+
for (const field of indexFields) {
|
|
360
|
+
const filterValue = this.getFilterValue(filter, field);
|
|
361
|
+
if (filterValue === undefined) break;
|
|
362
|
+
|
|
363
|
+
// Only use equality matches for index lookup
|
|
364
|
+
if (typeof filterValue === 'object' && filterValue !== null) {
|
|
365
|
+
if (filterValue.$eq !== undefined) {
|
|
366
|
+
equalityValues[field] = filterValue.$eq;
|
|
367
|
+
} else if (filterValue.$in !== undefined) {
|
|
368
|
+
// Handle $in with multiple lookups
|
|
369
|
+
const results = new Set<string>();
|
|
370
|
+
for (const val of filterValue.$in) {
|
|
371
|
+
equalityValues[field] = val;
|
|
372
|
+
const keyStr = JSON.stringify(this.buildKeyValue(equalityValues, index.data.key));
|
|
373
|
+
const ids = index.data.entries.get(keyStr);
|
|
374
|
+
if (ids) {
|
|
375
|
+
for (const id of ids) {
|
|
376
|
+
results.add(id);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return results;
|
|
381
|
+
} else {
|
|
382
|
+
break; // Non-equality operator, stop here
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
equalityValues[field] = filterValue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (Object.keys(equalityValues).length === 0) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const keyStr = JSON.stringify(this.buildKeyValue(equalityValues, index.data.key));
|
|
394
|
+
return index.data.entries.get(keyStr) || new Set();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============================================================================
|
|
398
|
+
// Helper Methods
|
|
399
|
+
// ============================================================================
|
|
400
|
+
|
|
401
|
+
private generateIndexName(key: Record<string, any>): string {
|
|
402
|
+
return Object.entries(key)
|
|
403
|
+
.map(([field, dir]) => `${field}_${dir}`)
|
|
404
|
+
.join('_');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private extractKeyValue(doc: Document, key: Record<string, any>): any {
|
|
408
|
+
const values: any[] = [];
|
|
409
|
+
|
|
410
|
+
for (const field of Object.keys(key)) {
|
|
411
|
+
const value = QueryEngine.getNestedValue(doc, field);
|
|
412
|
+
values.push(value === undefined ? null : value);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// For single-field index, return the value directly
|
|
416
|
+
if (values.length === 1) {
|
|
417
|
+
return values[0];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return values;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private buildKeyValue(values: Record<string, any>, key: Record<string, any>): any {
|
|
424
|
+
const result: any[] = [];
|
|
425
|
+
|
|
426
|
+
for (const field of Object.keys(key)) {
|
|
427
|
+
result.push(values[field] !== undefined ? values[field] : null);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (result.length === 1) {
|
|
431
|
+
return result[0];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private getFilterFields(filter: Document, prefix = ''): string[] {
|
|
438
|
+
const fields: string[] = [];
|
|
439
|
+
|
|
440
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
441
|
+
if (key.startsWith('$')) {
|
|
442
|
+
// Logical operator
|
|
443
|
+
if (key === '$and' || key === '$or' || key === '$nor') {
|
|
444
|
+
for (const subFilter of value as Document[]) {
|
|
445
|
+
fields.push(...this.getFilterFields(subFilter, prefix));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
450
|
+
fields.push(fullKey);
|
|
451
|
+
|
|
452
|
+
// Check for nested filters
|
|
453
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
454
|
+
const subKeys = Object.keys(value);
|
|
455
|
+
if (subKeys.length > 0 && !subKeys[0].startsWith('$')) {
|
|
456
|
+
fields.push(...this.getFilterFields(value, fullKey));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return fields;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private getFilterValue(filter: Document, field: string): any {
|
|
466
|
+
// Handle dot notation
|
|
467
|
+
const parts = field.split('.');
|
|
468
|
+
let current: any = filter;
|
|
469
|
+
|
|
470
|
+
for (const part of parts) {
|
|
471
|
+
if (current === null || current === undefined) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
current = current[part];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return current;
|
|
478
|
+
}
|
|
479
|
+
}
|