@push.rocks/smartmongo 3.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/tsmdb/engine/IndexEngine.d.ts +23 -3
- package/dist_ts/tsmdb/engine/IndexEngine.js +357 -55
- package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
- package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
- package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
- package/dist_ts/tsmdb/index.d.ts +7 -0
- package/dist_ts/tsmdb/index.js +6 -1
- package/dist_ts/tsmdb/server/CommandRouter.d.ts +36 -0
- package/dist_ts/tsmdb/server/CommandRouter.js +91 -1
- package/dist_ts/tsmdb/server/TsmdbServer.js +3 -1
- package/dist_ts/tsmdb/server/handlers/AdminHandler.js +106 -6
- package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +15 -3
- package/dist_ts/tsmdb/server/handlers/FindHandler.js +44 -14
- package/dist_ts/tsmdb/server/handlers/InsertHandler.js +4 -1
- package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +31 -5
- package/dist_ts/tsmdb/storage/FileStorageAdapter.d.ts +25 -1
- package/dist_ts/tsmdb/storage/FileStorageAdapter.js +75 -6
- package/dist_ts/tsmdb/storage/IStorageAdapter.d.ts +5 -0
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.d.ts +1 -0
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +12 -1
- package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
- package/dist_ts/tsmdb/storage/WAL.js +286 -0
- package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
- package/dist_ts/tsmdb/utils/checksum.js +77 -0
- package/dist_ts/tsmdb/utils/index.d.ts +1 -0
- package/dist_ts/tsmdb/utils/index.js +2 -0
- package/package.json +2 -2
- package/readme.md +140 -17
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/tsmdb/engine/IndexEngine.ts +375 -56
- package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
- package/ts/tsmdb/engine/SessionEngine.ts +292 -0
- package/ts/tsmdb/index.ts +9 -0
- package/ts/tsmdb/server/CommandRouter.ts +109 -0
- package/ts/tsmdb/server/TsmdbServer.ts +3 -0
- package/ts/tsmdb/server/handlers/AdminHandler.ts +110 -5
- package/ts/tsmdb/server/handlers/DeleteHandler.ts +17 -2
- package/ts/tsmdb/server/handlers/FindHandler.ts +42 -13
- package/ts/tsmdb/server/handlers/InsertHandler.ts +6 -0
- package/ts/tsmdb/server/handlers/UpdateHandler.ts +33 -4
- package/ts/tsmdb/storage/FileStorageAdapter.ts +88 -5
- package/ts/tsmdb/storage/IStorageAdapter.ts +6 -0
- package/ts/tsmdb/storage/MemoryStorageAdapter.ts +12 -0
- package/ts/tsmdb/storage/WAL.ts +375 -0
- package/ts/tsmdb/utils/checksum.ts +88 -0
- package/ts/tsmdb/utils/index.ts +1 -0
|
@@ -2,6 +2,9 @@ import * as plugins from '../tsmdb.plugins.js';
|
|
|
2
2
|
import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
|
|
3
3
|
import type { IParsedCommand } from './WireProtocol.js';
|
|
4
4
|
import type { TsmdbServer } from './TsmdbServer.js';
|
|
5
|
+
import { IndexEngine } from '../engine/IndexEngine.js';
|
|
6
|
+
import { TransactionEngine } from '../engine/TransactionEngine.js';
|
|
7
|
+
import { SessionEngine } from '../engine/SessionEngine.js';
|
|
5
8
|
|
|
6
9
|
// Import handlers
|
|
7
10
|
import { HelloHandler } from './handlers/HelloHandler.js';
|
|
@@ -22,6 +25,16 @@ export interface IHandlerContext {
|
|
|
22
25
|
database: string;
|
|
23
26
|
command: plugins.bson.Document;
|
|
24
27
|
documentSequences?: Map<string, plugins.bson.Document[]>;
|
|
28
|
+
/** Get or create an IndexEngine for a collection */
|
|
29
|
+
getIndexEngine: (collName: string) => IndexEngine;
|
|
30
|
+
/** Transaction engine instance */
|
|
31
|
+
transactionEngine: TransactionEngine;
|
|
32
|
+
/** Current transaction ID (if in a transaction) */
|
|
33
|
+
txnId?: string;
|
|
34
|
+
/** Session ID (from lsid) */
|
|
35
|
+
sessionId?: string;
|
|
36
|
+
/** Session engine instance */
|
|
37
|
+
sessionEngine: SessionEngine;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
/**
|
|
@@ -43,12 +56,54 @@ export class CommandRouter {
|
|
|
43
56
|
private cursors: Map<bigint, ICursorState> = new Map();
|
|
44
57
|
private cursorIdCounter: bigint = BigInt(1);
|
|
45
58
|
|
|
59
|
+
// Index engine cache: db.collection -> IndexEngine
|
|
60
|
+
private indexEngines: Map<string, IndexEngine> = new Map();
|
|
61
|
+
|
|
62
|
+
// Transaction engine (shared across all handlers)
|
|
63
|
+
private transactionEngine: TransactionEngine;
|
|
64
|
+
|
|
65
|
+
// Session engine (shared across all handlers)
|
|
66
|
+
private sessionEngine: SessionEngine;
|
|
67
|
+
|
|
46
68
|
constructor(storage: IStorageAdapter, server: TsmdbServer) {
|
|
47
69
|
this.storage = storage;
|
|
48
70
|
this.server = server;
|
|
71
|
+
this.transactionEngine = new TransactionEngine(storage);
|
|
72
|
+
this.sessionEngine = new SessionEngine();
|
|
73
|
+
// Link session engine to transaction engine for auto-abort on session expiry
|
|
74
|
+
this.sessionEngine.setTransactionEngine(this.transactionEngine);
|
|
49
75
|
this.registerHandlers();
|
|
50
76
|
}
|
|
51
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Get or create an IndexEngine for a database.collection
|
|
80
|
+
*/
|
|
81
|
+
getIndexEngine(dbName: string, collName: string): IndexEngine {
|
|
82
|
+
const key = `${dbName}.${collName}`;
|
|
83
|
+
let engine = this.indexEngines.get(key);
|
|
84
|
+
if (!engine) {
|
|
85
|
+
engine = new IndexEngine(dbName, collName, this.storage);
|
|
86
|
+
this.indexEngines.set(key, engine);
|
|
87
|
+
}
|
|
88
|
+
return engine;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear index engine cache for a collection (used when collection is dropped)
|
|
93
|
+
*/
|
|
94
|
+
clearIndexEngineCache(dbName: string, collName?: string): void {
|
|
95
|
+
if (collName) {
|
|
96
|
+
this.indexEngines.delete(`${dbName}.${collName}`);
|
|
97
|
+
} else {
|
|
98
|
+
// Clear all engines for the database
|
|
99
|
+
for (const key of this.indexEngines.keys()) {
|
|
100
|
+
if (key.startsWith(`${dbName}.`)) {
|
|
101
|
+
this.indexEngines.delete(key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
52
107
|
/**
|
|
53
108
|
* Register all command handlers
|
|
54
109
|
*/
|
|
@@ -120,6 +175,29 @@ export class CommandRouter {
|
|
|
120
175
|
async route(parsedCommand: IParsedCommand): Promise<plugins.bson.Document> {
|
|
121
176
|
const { commandName, command, database, documentSequences } = parsedCommand;
|
|
122
177
|
|
|
178
|
+
// Extract session ID from lsid using SessionEngine helper
|
|
179
|
+
let sessionId = SessionEngine.extractSessionId(command.lsid);
|
|
180
|
+
let txnId: string | undefined;
|
|
181
|
+
|
|
182
|
+
// If we have a session ID, register/touch the session
|
|
183
|
+
if (sessionId) {
|
|
184
|
+
this.sessionEngine.getOrCreateSession(sessionId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if this starts a new transaction
|
|
188
|
+
if (command.startTransaction && sessionId) {
|
|
189
|
+
txnId = this.transactionEngine.startTransaction(sessionId);
|
|
190
|
+
this.sessionEngine.startTransaction(sessionId, txnId, command.txnNumber);
|
|
191
|
+
} else if (sessionId && this.sessionEngine.isInTransaction(sessionId)) {
|
|
192
|
+
// Continue existing transaction
|
|
193
|
+
txnId = this.sessionEngine.getTransactionId(sessionId);
|
|
194
|
+
// Verify transaction is still active
|
|
195
|
+
if (txnId && !this.transactionEngine.isActive(txnId)) {
|
|
196
|
+
this.sessionEngine.endTransaction(sessionId);
|
|
197
|
+
txnId = undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
123
201
|
// Create handler context
|
|
124
202
|
const context: IHandlerContext = {
|
|
125
203
|
storage: this.storage,
|
|
@@ -127,6 +205,11 @@ export class CommandRouter {
|
|
|
127
205
|
database,
|
|
128
206
|
command,
|
|
129
207
|
documentSequences,
|
|
208
|
+
getIndexEngine: (collName: string) => this.getIndexEngine(database, collName),
|
|
209
|
+
transactionEngine: this.transactionEngine,
|
|
210
|
+
sessionEngine: this.sessionEngine,
|
|
211
|
+
txnId,
|
|
212
|
+
sessionId,
|
|
130
213
|
};
|
|
131
214
|
|
|
132
215
|
// Find handler
|
|
@@ -164,6 +247,32 @@ export class CommandRouter {
|
|
|
164
247
|
};
|
|
165
248
|
}
|
|
166
249
|
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Close the command router and cleanup resources
|
|
253
|
+
*/
|
|
254
|
+
close(): void {
|
|
255
|
+
// Close session engine (stops cleanup interval, clears sessions)
|
|
256
|
+
this.sessionEngine.close();
|
|
257
|
+
// Clear cursors
|
|
258
|
+
this.cursors.clear();
|
|
259
|
+
// Clear index engine cache
|
|
260
|
+
this.indexEngines.clear();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get session engine (for administrative purposes)
|
|
265
|
+
*/
|
|
266
|
+
getSessionEngine(): SessionEngine {
|
|
267
|
+
return this.sessionEngine;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get transaction engine (for administrative purposes)
|
|
272
|
+
*/
|
|
273
|
+
getTransactionEngine(): TransactionEngine {
|
|
274
|
+
return this.transactionEngine;
|
|
275
|
+
}
|
|
167
276
|
}
|
|
168
277
|
|
|
169
278
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../tsmdb.plugins.js';
|
|
2
2
|
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
import { SessionEngine } from '../../engine/SessionEngine.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* AdminHandler - Handles administrative commands
|
|
@@ -237,10 +238,12 @@ export class AdminHandler implements ICommandHandler {
|
|
|
237
238
|
* Handle serverStatus command
|
|
238
239
|
*/
|
|
239
240
|
private async handleServerStatus(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
240
|
-
const { server } = context;
|
|
241
|
+
const { server, sessionEngine } = context;
|
|
241
242
|
|
|
242
243
|
const uptime = server.getUptime();
|
|
243
244
|
const connections = server.getConnectionCount();
|
|
245
|
+
const sessions = sessionEngine.listSessions();
|
|
246
|
+
const sessionsWithTxn = sessionEngine.getSessionsWithTransactions();
|
|
244
247
|
|
|
245
248
|
return {
|
|
246
249
|
ok: 1,
|
|
@@ -263,6 +266,26 @@ export class AdminHandler implements ICommandHandler {
|
|
|
263
266
|
totalCreated: connections,
|
|
264
267
|
active: connections,
|
|
265
268
|
},
|
|
269
|
+
logicalSessionRecordCache: {
|
|
270
|
+
activeSessionsCount: sessions.length,
|
|
271
|
+
sessionsCollectionJobCount: 0,
|
|
272
|
+
lastSessionsCollectionJobDurationMillis: 0,
|
|
273
|
+
lastSessionsCollectionJobTimestamp: new Date(),
|
|
274
|
+
transactionReaperJobCount: 0,
|
|
275
|
+
lastTransactionReaperJobDurationMillis: 0,
|
|
276
|
+
lastTransactionReaperJobTimestamp: new Date(),
|
|
277
|
+
},
|
|
278
|
+
transactions: {
|
|
279
|
+
retriedCommandsCount: 0,
|
|
280
|
+
retriedStatementsCount: 0,
|
|
281
|
+
transactionsCollectionWriteCount: 0,
|
|
282
|
+
currentActive: sessionsWithTxn.length,
|
|
283
|
+
currentInactive: 0,
|
|
284
|
+
currentOpen: sessionsWithTxn.length,
|
|
285
|
+
totalStarted: sessionsWithTxn.length,
|
|
286
|
+
totalCommitted: 0,
|
|
287
|
+
totalAborted: 0,
|
|
288
|
+
},
|
|
266
289
|
network: {
|
|
267
290
|
bytesIn: 0,
|
|
268
291
|
bytesOut: 0,
|
|
@@ -409,6 +432,17 @@ export class AdminHandler implements ICommandHandler {
|
|
|
409
432
|
* Handle endSessions command
|
|
410
433
|
*/
|
|
411
434
|
private async handleEndSessions(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
435
|
+
const { command, sessionEngine } = context;
|
|
436
|
+
|
|
437
|
+
// End each session in the array
|
|
438
|
+
const sessions = command.endSessions || [];
|
|
439
|
+
for (const sessionSpec of sessions) {
|
|
440
|
+
const sessionId = SessionEngine.extractSessionId(sessionSpec);
|
|
441
|
+
if (sessionId) {
|
|
442
|
+
await sessionEngine.endSession(sessionId);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
412
446
|
return { ok: 1 };
|
|
413
447
|
}
|
|
414
448
|
|
|
@@ -416,16 +450,87 @@ export class AdminHandler implements ICommandHandler {
|
|
|
416
450
|
* Handle abortTransaction command
|
|
417
451
|
*/
|
|
418
452
|
private async handleAbortTransaction(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
419
|
-
|
|
420
|
-
|
|
453
|
+
const { transactionEngine, sessionEngine, txnId, sessionId } = context;
|
|
454
|
+
|
|
455
|
+
if (!txnId) {
|
|
456
|
+
return {
|
|
457
|
+
ok: 0,
|
|
458
|
+
errmsg: 'No transaction started',
|
|
459
|
+
code: 251,
|
|
460
|
+
codeName: 'NoSuchTransaction',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
await transactionEngine.abortTransaction(txnId);
|
|
466
|
+
transactionEngine.endTransaction(txnId);
|
|
467
|
+
// Update session state
|
|
468
|
+
if (sessionId) {
|
|
469
|
+
sessionEngine.endTransaction(sessionId);
|
|
470
|
+
}
|
|
471
|
+
return { ok: 1 };
|
|
472
|
+
} catch (error: any) {
|
|
473
|
+
return {
|
|
474
|
+
ok: 0,
|
|
475
|
+
errmsg: error.message || 'Abort transaction failed',
|
|
476
|
+
code: error.code || 1,
|
|
477
|
+
codeName: error.codeName || 'UnknownError',
|
|
478
|
+
};
|
|
479
|
+
}
|
|
421
480
|
}
|
|
422
481
|
|
|
423
482
|
/**
|
|
424
483
|
* Handle commitTransaction command
|
|
425
484
|
*/
|
|
426
485
|
private async handleCommitTransaction(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
427
|
-
|
|
428
|
-
|
|
486
|
+
const { transactionEngine, sessionEngine, txnId, sessionId } = context;
|
|
487
|
+
|
|
488
|
+
if (!txnId) {
|
|
489
|
+
return {
|
|
490
|
+
ok: 0,
|
|
491
|
+
errmsg: 'No transaction started',
|
|
492
|
+
code: 251,
|
|
493
|
+
codeName: 'NoSuchTransaction',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
await transactionEngine.commitTransaction(txnId);
|
|
499
|
+
transactionEngine.endTransaction(txnId);
|
|
500
|
+
// Update session state
|
|
501
|
+
if (sessionId) {
|
|
502
|
+
sessionEngine.endTransaction(sessionId);
|
|
503
|
+
}
|
|
504
|
+
return { ok: 1 };
|
|
505
|
+
} catch (error: any) {
|
|
506
|
+
// If commit fails, transaction should be aborted
|
|
507
|
+
try {
|
|
508
|
+
await transactionEngine.abortTransaction(txnId);
|
|
509
|
+
transactionEngine.endTransaction(txnId);
|
|
510
|
+
if (sessionId) {
|
|
511
|
+
sessionEngine.endTransaction(sessionId);
|
|
512
|
+
}
|
|
513
|
+
} catch {
|
|
514
|
+
// Ignore abort errors
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (error.code === 112) {
|
|
518
|
+
// Write conflict
|
|
519
|
+
return {
|
|
520
|
+
ok: 0,
|
|
521
|
+
errmsg: error.message || 'Write conflict during commit',
|
|
522
|
+
code: 112,
|
|
523
|
+
codeName: 'WriteConflict',
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
ok: 0,
|
|
529
|
+
errmsg: error.message || 'Commit transaction failed',
|
|
530
|
+
code: error.code || 1,
|
|
531
|
+
codeName: error.codeName || 'UnknownError',
|
|
532
|
+
};
|
|
533
|
+
}
|
|
429
534
|
}
|
|
430
535
|
|
|
431
536
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../tsmdb.plugins.js';
|
|
2
2
|
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
import type { IStoredDocument } from '../../types/interfaces.js';
|
|
3
4
|
import { QueryEngine } from '../../engine/QueryEngine.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -47,6 +48,8 @@ export class DeleteHandler implements ICommandHandler {
|
|
|
47
48
|
return { ok: 1, n: 0 };
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
const indexEngine = context.getIndexEngine(collection);
|
|
52
|
+
|
|
50
53
|
for (let i = 0; i < deletes.length; i++) {
|
|
51
54
|
const deleteSpec = deletes[i];
|
|
52
55
|
const filter = deleteSpec.q || deleteSpec.filter || {};
|
|
@@ -56,8 +59,15 @@ export class DeleteHandler implements ICommandHandler {
|
|
|
56
59
|
const deleteAll = limit === 0;
|
|
57
60
|
|
|
58
61
|
try {
|
|
59
|
-
//
|
|
60
|
-
const
|
|
62
|
+
// Try to use index-accelerated query
|
|
63
|
+
const candidateIds = await indexEngine.findCandidateIds(filter);
|
|
64
|
+
|
|
65
|
+
let documents: IStoredDocument[];
|
|
66
|
+
if (candidateIds !== null) {
|
|
67
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
68
|
+
} else {
|
|
69
|
+
documents = await storage.findAll(database, collection);
|
|
70
|
+
}
|
|
61
71
|
|
|
62
72
|
// Apply filter
|
|
63
73
|
const matchingDocs = QueryEngine.filter(documents, filter);
|
|
@@ -69,6 +79,11 @@ export class DeleteHandler implements ICommandHandler {
|
|
|
69
79
|
// Determine which documents to delete
|
|
70
80
|
const docsToDelete = deleteAll ? matchingDocs : matchingDocs.slice(0, 1);
|
|
71
81
|
|
|
82
|
+
// Update indexes for deleted documents
|
|
83
|
+
for (const doc of docsToDelete) {
|
|
84
|
+
await indexEngine.onDelete(doc as any);
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// Delete the documents
|
|
73
88
|
const idsToDelete = docsToDelete.map(doc => doc._id);
|
|
74
89
|
const deleted = await storage.deleteByIds(database, collection, idsToDelete);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../tsmdb.plugins.js';
|
|
2
2
|
import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js';
|
|
3
|
+
import type { IStoredDocument } from '../../types/interfaces.js';
|
|
3
4
|
import { QueryEngine } from '../../engine/QueryEngine.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -45,7 +46,7 @@ export class FindHandler implements ICommandHandler {
|
|
|
45
46
|
* Handle find command
|
|
46
47
|
*/
|
|
47
48
|
private async handleFind(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
48
|
-
const { storage, database, command } = context;
|
|
49
|
+
const { storage, database, command, getIndexEngine } = context;
|
|
49
50
|
|
|
50
51
|
const collection = command.find;
|
|
51
52
|
const filter = command.filter || {};
|
|
@@ -70,11 +71,22 @@ export class FindHandler implements ICommandHandler {
|
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
//
|
|
74
|
-
|
|
74
|
+
// Try to use index-accelerated query
|
|
75
|
+
const indexEngine = getIndexEngine(collection);
|
|
76
|
+
const candidateIds = await indexEngine.findCandidateIds(filter);
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
let documents: IStoredDocument[];
|
|
79
|
+
if (candidateIds !== null) {
|
|
80
|
+
// Index hit - fetch only candidate documents
|
|
81
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
82
|
+
// Still apply filter for any conditions the index couldn't fully satisfy
|
|
83
|
+
documents = QueryEngine.filter(documents, filter);
|
|
84
|
+
} else {
|
|
85
|
+
// No suitable index - full collection scan
|
|
86
|
+
documents = await storage.findAll(database, collection);
|
|
87
|
+
// Apply filter
|
|
88
|
+
documents = QueryEngine.filter(documents, filter);
|
|
89
|
+
}
|
|
78
90
|
|
|
79
91
|
// Apply sort
|
|
80
92
|
if (sort) {
|
|
@@ -233,7 +245,7 @@ export class FindHandler implements ICommandHandler {
|
|
|
233
245
|
* Handle count command
|
|
234
246
|
*/
|
|
235
247
|
private async handleCount(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
236
|
-
const { storage, database, command } = context;
|
|
248
|
+
const { storage, database, command, getIndexEngine } = context;
|
|
237
249
|
|
|
238
250
|
const collection = command.count;
|
|
239
251
|
const query = command.query || {};
|
|
@@ -246,11 +258,20 @@ export class FindHandler implements ICommandHandler {
|
|
|
246
258
|
return { ok: 1, n: 0 };
|
|
247
259
|
}
|
|
248
260
|
|
|
249
|
-
//
|
|
250
|
-
|
|
261
|
+
// Try to use index-accelerated query
|
|
262
|
+
const indexEngine = getIndexEngine(collection);
|
|
263
|
+
const candidateIds = await indexEngine.findCandidateIds(query);
|
|
251
264
|
|
|
252
|
-
|
|
253
|
-
|
|
265
|
+
let documents: IStoredDocument[];
|
|
266
|
+
if (candidateIds !== null) {
|
|
267
|
+
// Index hit - fetch only candidate documents
|
|
268
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
269
|
+
documents = QueryEngine.filter(documents, query);
|
|
270
|
+
} else {
|
|
271
|
+
// No suitable index - full collection scan
|
|
272
|
+
documents = await storage.findAll(database, collection);
|
|
273
|
+
documents = QueryEngine.filter(documents, query);
|
|
274
|
+
}
|
|
254
275
|
|
|
255
276
|
// Apply skip
|
|
256
277
|
if (skip > 0) {
|
|
@@ -269,7 +290,7 @@ export class FindHandler implements ICommandHandler {
|
|
|
269
290
|
* Handle distinct command
|
|
270
291
|
*/
|
|
271
292
|
private async handleDistinct(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
272
|
-
const { storage, database, command } = context;
|
|
293
|
+
const { storage, database, command, getIndexEngine } = context;
|
|
273
294
|
|
|
274
295
|
const collection = command.distinct;
|
|
275
296
|
const key = command.key;
|
|
@@ -290,8 +311,16 @@ export class FindHandler implements ICommandHandler {
|
|
|
290
311
|
return { ok: 1, values: [] };
|
|
291
312
|
}
|
|
292
313
|
|
|
293
|
-
//
|
|
294
|
-
const
|
|
314
|
+
// Try to use index-accelerated query
|
|
315
|
+
const indexEngine = getIndexEngine(collection);
|
|
316
|
+
const candidateIds = await indexEngine.findCandidateIds(query);
|
|
317
|
+
|
|
318
|
+
let documents: IStoredDocument[];
|
|
319
|
+
if (candidateIds !== null) {
|
|
320
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
321
|
+
} else {
|
|
322
|
+
documents = await storage.findAll(database, collection);
|
|
323
|
+
}
|
|
295
324
|
|
|
296
325
|
// Get distinct values
|
|
297
326
|
const values = QueryEngine.distinct(documents, key, query);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../tsmdb.plugins.js';
|
|
2
2
|
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
import type { IStoredDocument } from '../../types/interfaces.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* InsertHandler - Handles insert commands
|
|
@@ -42,6 +43,8 @@ export class InsertHandler implements ICommandHandler {
|
|
|
42
43
|
// Ensure collection exists
|
|
43
44
|
await storage.createCollection(database, collection);
|
|
44
45
|
|
|
46
|
+
const indexEngine = context.getIndexEngine(collection);
|
|
47
|
+
|
|
45
48
|
// Insert documents
|
|
46
49
|
for (let i = 0; i < documents.length; i++) {
|
|
47
50
|
const doc = documents[i];
|
|
@@ -52,6 +55,9 @@ export class InsertHandler implements ICommandHandler {
|
|
|
52
55
|
doc._id = new plugins.bson.ObjectId();
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
// Check index constraints before insert (doc now has _id)
|
|
59
|
+
await indexEngine.onInsert(doc as IStoredDocument);
|
|
60
|
+
|
|
55
61
|
await storage.insertOne(database, collection, doc);
|
|
56
62
|
insertedCount++;
|
|
57
63
|
} catch (error: any) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../tsmdb.plugins.js';
|
|
2
2
|
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
import type { IStoredDocument } from '../../types/interfaces.js';
|
|
3
4
|
import { QueryEngine } from '../../engine/QueryEngine.js';
|
|
4
5
|
import { UpdateEngine } from '../../engine/UpdateEngine.js';
|
|
5
6
|
|
|
@@ -69,6 +70,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
69
70
|
// Ensure collection exists
|
|
70
71
|
await storage.createCollection(database, collection);
|
|
71
72
|
|
|
73
|
+
const indexEngine = context.getIndexEngine(collection);
|
|
74
|
+
|
|
72
75
|
for (let i = 0; i < updates.length; i++) {
|
|
73
76
|
const updateSpec = updates[i];
|
|
74
77
|
const filter = updateSpec.q || updateSpec.filter || {};
|
|
@@ -78,8 +81,15 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
78
81
|
const arrayFilters = updateSpec.arrayFilters;
|
|
79
82
|
|
|
80
83
|
try {
|
|
81
|
-
//
|
|
82
|
-
|
|
84
|
+
// Try to use index-accelerated query
|
|
85
|
+
const candidateIds = await indexEngine.findCandidateIds(filter);
|
|
86
|
+
|
|
87
|
+
let documents: IStoredDocument[];
|
|
88
|
+
if (candidateIds !== null) {
|
|
89
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
90
|
+
} else {
|
|
91
|
+
documents = await storage.findAll(database, collection);
|
|
92
|
+
}
|
|
83
93
|
|
|
84
94
|
// Apply filter
|
|
85
95
|
let matchingDocs = QueryEngine.filter(documents, filter);
|
|
@@ -99,6 +109,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
99
109
|
Object.assign(updatedDoc, update.$setOnInsert);
|
|
100
110
|
}
|
|
101
111
|
|
|
112
|
+
// Update index for the new document
|
|
113
|
+
await indexEngine.onInsert(updatedDoc);
|
|
102
114
|
await storage.insertOne(database, collection, updatedDoc);
|
|
103
115
|
totalUpserted++;
|
|
104
116
|
upserted.push({ index: i, _id: updatedDoc._id });
|
|
@@ -113,6 +125,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
113
125
|
// Check if document actually changed
|
|
114
126
|
const changed = JSON.stringify(doc) !== JSON.stringify(updatedDoc);
|
|
115
127
|
if (changed) {
|
|
128
|
+
// Update index
|
|
129
|
+
await indexEngine.onUpdate(doc as any, updatedDoc);
|
|
116
130
|
await storage.updateById(database, collection, doc._id, updatedDoc);
|
|
117
131
|
totalModified++;
|
|
118
132
|
}
|
|
@@ -186,8 +200,17 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
186
200
|
// Ensure collection exists
|
|
187
201
|
await storage.createCollection(database, collection);
|
|
188
202
|
|
|
189
|
-
//
|
|
190
|
-
|
|
203
|
+
// Try to use index-accelerated query
|
|
204
|
+
const indexEngine = context.getIndexEngine(collection);
|
|
205
|
+
const candidateIds = await indexEngine.findCandidateIds(query);
|
|
206
|
+
|
|
207
|
+
let documents: IStoredDocument[];
|
|
208
|
+
if (candidateIds !== null) {
|
|
209
|
+
documents = await storage.findByIds(database, collection, candidateIds);
|
|
210
|
+
} else {
|
|
211
|
+
documents = await storage.findAll(database, collection);
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
let matchingDocs = QueryEngine.filter(documents, query);
|
|
192
215
|
|
|
193
216
|
// Apply sort if specified
|
|
@@ -203,6 +226,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
203
226
|
return { ok: 1, value: null };
|
|
204
227
|
}
|
|
205
228
|
|
|
229
|
+
// Update index for delete
|
|
230
|
+
await indexEngine.onDelete(doc as any);
|
|
206
231
|
await storage.deleteById(database, collection, doc._id);
|
|
207
232
|
|
|
208
233
|
let result = doc;
|
|
@@ -231,6 +256,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
231
256
|
// Update existing
|
|
232
257
|
originalDoc = { ...doc };
|
|
233
258
|
resultDoc = UpdateEngine.applyUpdate(doc, update, arrayFilters);
|
|
259
|
+
// Update index
|
|
260
|
+
await indexEngine.onUpdate(doc as any, resultDoc as any);
|
|
234
261
|
await storage.updateById(database, collection, doc._id, resultDoc as any);
|
|
235
262
|
} else {
|
|
236
263
|
// Upsert
|
|
@@ -243,6 +270,8 @@ export class UpdateHandler implements ICommandHandler {
|
|
|
243
270
|
Object.assign(resultDoc, update.$setOnInsert);
|
|
244
271
|
}
|
|
245
272
|
|
|
273
|
+
// Update index for insert
|
|
274
|
+
await indexEngine.onInsert(resultDoc as any);
|
|
246
275
|
await storage.insertOne(database, collection, resultDoc);
|
|
247
276
|
}
|
|
248
277
|
|