@push.rocks/smartmongo 2.0.12 → 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/dist_ts/smartmongo.plugins.d.ts +1 -1
- package/dist_ts/smartmongo.plugins.js +2 -2
- package/npmextra.json +17 -7
- package/package.json +20 -12
- package/readme.hints.md +89 -1
- 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
- package/ts/smartmongo.plugins.ts +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import * as plugins from '../../congodb.plugins.js';
|
|
2
|
+
import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js';
|
|
3
|
+
import { QueryEngine } from '../../engine/QueryEngine.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FindHandler - Handles find, getMore, killCursors, count, distinct commands
|
|
7
|
+
*/
|
|
8
|
+
export class FindHandler implements ICommandHandler {
|
|
9
|
+
private cursors: Map<bigint, ICursorState>;
|
|
10
|
+
private nextCursorId: () => bigint;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
cursors: Map<bigint, ICursorState>,
|
|
14
|
+
nextCursorId: () => bigint
|
|
15
|
+
) {
|
|
16
|
+
this.cursors = cursors;
|
|
17
|
+
this.nextCursorId = nextCursorId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
21
|
+
const { command } = context;
|
|
22
|
+
|
|
23
|
+
// Determine which operation to perform
|
|
24
|
+
if (command.find) {
|
|
25
|
+
return this.handleFind(context);
|
|
26
|
+
} else if (command.getMore !== undefined) {
|
|
27
|
+
return this.handleGetMore(context);
|
|
28
|
+
} else if (command.killCursors) {
|
|
29
|
+
return this.handleKillCursors(context);
|
|
30
|
+
} else if (command.count) {
|
|
31
|
+
return this.handleCount(context);
|
|
32
|
+
} else if (command.distinct) {
|
|
33
|
+
return this.handleDistinct(context);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
ok: 0,
|
|
38
|
+
errmsg: 'Unknown find-related command',
|
|
39
|
+
code: 59,
|
|
40
|
+
codeName: 'CommandNotFound',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handle find command
|
|
46
|
+
*/
|
|
47
|
+
private async handleFind(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
48
|
+
const { storage, database, command } = context;
|
|
49
|
+
|
|
50
|
+
const collection = command.find;
|
|
51
|
+
const filter = command.filter || {};
|
|
52
|
+
const projection = command.projection;
|
|
53
|
+
const sort = command.sort;
|
|
54
|
+
const skip = command.skip || 0;
|
|
55
|
+
const limit = command.limit || 0;
|
|
56
|
+
const batchSize = command.batchSize || 101;
|
|
57
|
+
const singleBatch = command.singleBatch || false;
|
|
58
|
+
|
|
59
|
+
// Ensure collection exists
|
|
60
|
+
const exists = await storage.collectionExists(database, collection);
|
|
61
|
+
if (!exists) {
|
|
62
|
+
// Return empty cursor for non-existent collection
|
|
63
|
+
return {
|
|
64
|
+
ok: 1,
|
|
65
|
+
cursor: {
|
|
66
|
+
id: plugins.bson.Long.fromNumber(0),
|
|
67
|
+
ns: `${database}.${collection}`,
|
|
68
|
+
firstBatch: [],
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get all documents
|
|
74
|
+
let documents = await storage.findAll(database, collection);
|
|
75
|
+
|
|
76
|
+
// Apply filter
|
|
77
|
+
documents = QueryEngine.filter(documents, filter);
|
|
78
|
+
|
|
79
|
+
// Apply sort
|
|
80
|
+
if (sort) {
|
|
81
|
+
documents = QueryEngine.sort(documents, sort);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Apply skip
|
|
85
|
+
if (skip > 0) {
|
|
86
|
+
documents = documents.slice(skip);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Apply limit
|
|
90
|
+
if (limit > 0) {
|
|
91
|
+
documents = documents.slice(0, limit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Apply projection
|
|
95
|
+
if (projection) {
|
|
96
|
+
documents = QueryEngine.project(documents, projection) as any[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine how many documents to return in first batch
|
|
100
|
+
const effectiveBatchSize = Math.min(batchSize, documents.length);
|
|
101
|
+
const firstBatch = documents.slice(0, effectiveBatchSize);
|
|
102
|
+
const remaining = documents.slice(effectiveBatchSize);
|
|
103
|
+
|
|
104
|
+
// Create cursor if there are more documents
|
|
105
|
+
let cursorId = BigInt(0);
|
|
106
|
+
if (remaining.length > 0 && !singleBatch) {
|
|
107
|
+
cursorId = this.nextCursorId();
|
|
108
|
+
this.cursors.set(cursorId, {
|
|
109
|
+
id: cursorId,
|
|
110
|
+
database,
|
|
111
|
+
collection,
|
|
112
|
+
documents: remaining,
|
|
113
|
+
position: 0,
|
|
114
|
+
batchSize,
|
|
115
|
+
createdAt: new Date(),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ok: 1,
|
|
121
|
+
cursor: {
|
|
122
|
+
id: plugins.bson.Long.fromBigInt(cursorId),
|
|
123
|
+
ns: `${database}.${collection}`,
|
|
124
|
+
firstBatch,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handle getMore command
|
|
131
|
+
*/
|
|
132
|
+
private async handleGetMore(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
133
|
+
const { database, command } = context;
|
|
134
|
+
|
|
135
|
+
const cursorIdInput = command.getMore;
|
|
136
|
+
const collection = command.collection;
|
|
137
|
+
const batchSize = command.batchSize || 101;
|
|
138
|
+
|
|
139
|
+
// Convert cursorId to bigint
|
|
140
|
+
let cursorId: bigint;
|
|
141
|
+
if (typeof cursorIdInput === 'bigint') {
|
|
142
|
+
cursorId = cursorIdInput;
|
|
143
|
+
} else if (cursorIdInput instanceof plugins.bson.Long) {
|
|
144
|
+
cursorId = cursorIdInput.toBigInt();
|
|
145
|
+
} else {
|
|
146
|
+
cursorId = BigInt(cursorIdInput);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const cursor = this.cursors.get(cursorId);
|
|
150
|
+
if (!cursor) {
|
|
151
|
+
return {
|
|
152
|
+
ok: 0,
|
|
153
|
+
errmsg: `cursor id ${cursorId} not found`,
|
|
154
|
+
code: 43,
|
|
155
|
+
codeName: 'CursorNotFound',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Verify namespace
|
|
160
|
+
if (cursor.database !== database || cursor.collection !== collection) {
|
|
161
|
+
return {
|
|
162
|
+
ok: 0,
|
|
163
|
+
errmsg: 'cursor namespace mismatch',
|
|
164
|
+
code: 43,
|
|
165
|
+
codeName: 'CursorNotFound',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get next batch
|
|
170
|
+
const start = cursor.position;
|
|
171
|
+
const end = Math.min(start + batchSize, cursor.documents.length);
|
|
172
|
+
const nextBatch = cursor.documents.slice(start, end);
|
|
173
|
+
cursor.position = end;
|
|
174
|
+
|
|
175
|
+
// Check if cursor is exhausted
|
|
176
|
+
let returnCursorId = cursorId;
|
|
177
|
+
if (cursor.position >= cursor.documents.length) {
|
|
178
|
+
this.cursors.delete(cursorId);
|
|
179
|
+
returnCursorId = BigInt(0);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
ok: 1,
|
|
184
|
+
cursor: {
|
|
185
|
+
id: plugins.bson.Long.fromBigInt(returnCursorId),
|
|
186
|
+
ns: `${database}.${collection}`,
|
|
187
|
+
nextBatch,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handle killCursors command
|
|
194
|
+
*/
|
|
195
|
+
private async handleKillCursors(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
196
|
+
const { command } = context;
|
|
197
|
+
|
|
198
|
+
const collection = command.killCursors;
|
|
199
|
+
const cursorIds = command.cursors || [];
|
|
200
|
+
|
|
201
|
+
const cursorsKilled: plugins.bson.Long[] = [];
|
|
202
|
+
const cursorsNotFound: plugins.bson.Long[] = [];
|
|
203
|
+
const cursorsUnknown: plugins.bson.Long[] = [];
|
|
204
|
+
|
|
205
|
+
for (const idInput of cursorIds) {
|
|
206
|
+
let cursorId: bigint;
|
|
207
|
+
if (typeof idInput === 'bigint') {
|
|
208
|
+
cursorId = idInput;
|
|
209
|
+
} else if (idInput instanceof plugins.bson.Long) {
|
|
210
|
+
cursorId = idInput.toBigInt();
|
|
211
|
+
} else {
|
|
212
|
+
cursorId = BigInt(idInput);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (this.cursors.has(cursorId)) {
|
|
216
|
+
this.cursors.delete(cursorId);
|
|
217
|
+
cursorsKilled.push(plugins.bson.Long.fromBigInt(cursorId));
|
|
218
|
+
} else {
|
|
219
|
+
cursorsNotFound.push(plugins.bson.Long.fromBigInt(cursorId));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
ok: 1,
|
|
225
|
+
cursorsKilled,
|
|
226
|
+
cursorsNotFound,
|
|
227
|
+
cursorsUnknown,
|
|
228
|
+
cursorsAlive: [],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Handle count command
|
|
234
|
+
*/
|
|
235
|
+
private async handleCount(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
236
|
+
const { storage, database, command } = context;
|
|
237
|
+
|
|
238
|
+
const collection = command.count;
|
|
239
|
+
const query = command.query || {};
|
|
240
|
+
const skip = command.skip || 0;
|
|
241
|
+
const limit = command.limit || 0;
|
|
242
|
+
|
|
243
|
+
// Check if collection exists
|
|
244
|
+
const exists = await storage.collectionExists(database, collection);
|
|
245
|
+
if (!exists) {
|
|
246
|
+
return { ok: 1, n: 0 };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Get all documents
|
|
250
|
+
let documents = await storage.findAll(database, collection);
|
|
251
|
+
|
|
252
|
+
// Apply filter
|
|
253
|
+
documents = QueryEngine.filter(documents, query);
|
|
254
|
+
|
|
255
|
+
// Apply skip
|
|
256
|
+
if (skip > 0) {
|
|
257
|
+
documents = documents.slice(skip);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Apply limit
|
|
261
|
+
if (limit > 0) {
|
|
262
|
+
documents = documents.slice(0, limit);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { ok: 1, n: documents.length };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Handle distinct command
|
|
270
|
+
*/
|
|
271
|
+
private async handleDistinct(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
272
|
+
const { storage, database, command } = context;
|
|
273
|
+
|
|
274
|
+
const collection = command.distinct;
|
|
275
|
+
const key = command.key;
|
|
276
|
+
const query = command.query || {};
|
|
277
|
+
|
|
278
|
+
if (!key) {
|
|
279
|
+
return {
|
|
280
|
+
ok: 0,
|
|
281
|
+
errmsg: 'distinct requires a key',
|
|
282
|
+
code: 2,
|
|
283
|
+
codeName: 'BadValue',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check if collection exists
|
|
288
|
+
const exists = await storage.collectionExists(database, collection);
|
|
289
|
+
if (!exists) {
|
|
290
|
+
return { ok: 1, values: [] };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Get all documents
|
|
294
|
+
const documents = await storage.findAll(database, collection);
|
|
295
|
+
|
|
296
|
+
// Get distinct values
|
|
297
|
+
const values = QueryEngine.distinct(documents, key, query);
|
|
298
|
+
|
|
299
|
+
return { ok: 1, values };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as plugins from '../../congodb.plugins.js';
|
|
2
|
+
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HelloHandler - Handles hello/isMaster handshake commands
|
|
6
|
+
*
|
|
7
|
+
* This is the first command sent by MongoDB drivers to establish a connection.
|
|
8
|
+
* It returns server capabilities and configuration.
|
|
9
|
+
*/
|
|
10
|
+
export class HelloHandler implements ICommandHandler {
|
|
11
|
+
async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
12
|
+
const { command, server } = context;
|
|
13
|
+
|
|
14
|
+
// Build response with server capabilities
|
|
15
|
+
const response: plugins.bson.Document = {
|
|
16
|
+
ismaster: true,
|
|
17
|
+
ok: 1,
|
|
18
|
+
|
|
19
|
+
// Maximum sizes
|
|
20
|
+
maxBsonObjectSize: 16777216, // 16 MB
|
|
21
|
+
maxMessageSizeBytes: 48000000, // 48 MB
|
|
22
|
+
maxWriteBatchSize: 100000, // 100k documents per batch
|
|
23
|
+
|
|
24
|
+
// Timestamps
|
|
25
|
+
localTime: new Date(),
|
|
26
|
+
|
|
27
|
+
// Session support
|
|
28
|
+
logicalSessionTimeoutMinutes: 30,
|
|
29
|
+
|
|
30
|
+
// Connection info
|
|
31
|
+
connectionId: 1,
|
|
32
|
+
|
|
33
|
+
// Wire protocol versions (support MongoDB 3.6 through 7.0)
|
|
34
|
+
minWireVersion: 0,
|
|
35
|
+
maxWireVersion: 21,
|
|
36
|
+
|
|
37
|
+
// Server mode
|
|
38
|
+
readOnly: false,
|
|
39
|
+
|
|
40
|
+
// Topology info (standalone mode)
|
|
41
|
+
isWritablePrimary: true,
|
|
42
|
+
|
|
43
|
+
// Additional info
|
|
44
|
+
topologyVersion: {
|
|
45
|
+
processId: new plugins.bson.ObjectId(),
|
|
46
|
+
counter: plugins.bson.Long.fromNumber(0),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Handle hello-specific fields
|
|
51
|
+
if (command.hello || command.hello === 1) {
|
|
52
|
+
response.helloOk = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle client metadata
|
|
56
|
+
if (command.client) {
|
|
57
|
+
// Client is providing metadata about itself
|
|
58
|
+
// We just acknowledge it - no need to do anything special
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle SASL mechanisms query
|
|
62
|
+
if (command.saslSupportedMechs) {
|
|
63
|
+
response.saslSupportedMechs = [
|
|
64
|
+
// We don't actually support auth, but the driver needs to see this
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Compression support (none for now)
|
|
69
|
+
if (command.compression) {
|
|
70
|
+
response.compression = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Server version info
|
|
74
|
+
response.version = '7.0.0';
|
|
75
|
+
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import * as plugins from '../../congodb.plugins.js';
|
|
2
|
+
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
import { IndexEngine } from '../../engine/IndexEngine.js';
|
|
4
|
+
|
|
5
|
+
// Cache of index engines per collection
|
|
6
|
+
const indexEngines: Map<string, IndexEngine> = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get or create an IndexEngine for a collection
|
|
10
|
+
*/
|
|
11
|
+
function getIndexEngine(storage: any, database: string, collection: string): IndexEngine {
|
|
12
|
+
const key = `${database}.${collection}`;
|
|
13
|
+
let engine = indexEngines.get(key);
|
|
14
|
+
|
|
15
|
+
if (!engine) {
|
|
16
|
+
engine = new IndexEngine(database, collection, storage);
|
|
17
|
+
indexEngines.set(key, engine);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return engine;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* IndexHandler - Handles createIndexes, dropIndexes, listIndexes commands
|
|
25
|
+
*/
|
|
26
|
+
export class IndexHandler implements ICommandHandler {
|
|
27
|
+
async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
28
|
+
const { command } = context;
|
|
29
|
+
|
|
30
|
+
if (command.createIndexes) {
|
|
31
|
+
return this.handleCreateIndexes(context);
|
|
32
|
+
} else if (command.dropIndexes) {
|
|
33
|
+
return this.handleDropIndexes(context);
|
|
34
|
+
} else if (command.listIndexes) {
|
|
35
|
+
return this.handleListIndexes(context);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
ok: 0,
|
|
40
|
+
errmsg: 'Unknown index command',
|
|
41
|
+
code: 59,
|
|
42
|
+
codeName: 'CommandNotFound',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handle createIndexes command
|
|
48
|
+
*/
|
|
49
|
+
private async handleCreateIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
50
|
+
const { storage, database, command } = context;
|
|
51
|
+
|
|
52
|
+
const collection = command.createIndexes;
|
|
53
|
+
const indexes = command.indexes || [];
|
|
54
|
+
|
|
55
|
+
if (!Array.isArray(indexes)) {
|
|
56
|
+
return {
|
|
57
|
+
ok: 0,
|
|
58
|
+
errmsg: 'indexes must be an array',
|
|
59
|
+
code: 2,
|
|
60
|
+
codeName: 'BadValue',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Ensure collection exists
|
|
65
|
+
await storage.createCollection(database, collection);
|
|
66
|
+
|
|
67
|
+
const indexEngine = getIndexEngine(storage, database, collection);
|
|
68
|
+
const createdNames: string[] = [];
|
|
69
|
+
let numIndexesBefore = 0;
|
|
70
|
+
let numIndexesAfter = 0;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const existingIndexes = await indexEngine.listIndexes();
|
|
74
|
+
numIndexesBefore = existingIndexes.length;
|
|
75
|
+
|
|
76
|
+
for (const indexSpec of indexes) {
|
|
77
|
+
const key = indexSpec.key;
|
|
78
|
+
const options = {
|
|
79
|
+
name: indexSpec.name,
|
|
80
|
+
unique: indexSpec.unique,
|
|
81
|
+
sparse: indexSpec.sparse,
|
|
82
|
+
expireAfterSeconds: indexSpec.expireAfterSeconds,
|
|
83
|
+
background: indexSpec.background,
|
|
84
|
+
partialFilterExpression: indexSpec.partialFilterExpression,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const name = await indexEngine.createIndex(key, options);
|
|
88
|
+
createdNames.push(name);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const finalIndexes = await indexEngine.listIndexes();
|
|
92
|
+
numIndexesAfter = finalIndexes.length;
|
|
93
|
+
} catch (error: any) {
|
|
94
|
+
return {
|
|
95
|
+
ok: 0,
|
|
96
|
+
errmsg: error.message || 'Failed to create index',
|
|
97
|
+
code: error.code || 1,
|
|
98
|
+
codeName: error.codeName || 'InternalError',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
ok: 1,
|
|
104
|
+
numIndexesBefore,
|
|
105
|
+
numIndexesAfter,
|
|
106
|
+
createdCollectionAutomatically: false,
|
|
107
|
+
commitQuorum: 'votingMembers',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handle dropIndexes command
|
|
113
|
+
*/
|
|
114
|
+
private async handleDropIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
115
|
+
const { storage, database, command } = context;
|
|
116
|
+
|
|
117
|
+
const collection = command.dropIndexes;
|
|
118
|
+
const indexName = command.index;
|
|
119
|
+
|
|
120
|
+
// Check if collection exists
|
|
121
|
+
const exists = await storage.collectionExists(database, collection);
|
|
122
|
+
if (!exists) {
|
|
123
|
+
return {
|
|
124
|
+
ok: 0,
|
|
125
|
+
errmsg: `ns not found ${database}.${collection}`,
|
|
126
|
+
code: 26,
|
|
127
|
+
codeName: 'NamespaceNotFound',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const indexEngine = getIndexEngine(storage, database, collection);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
if (indexName === '*') {
|
|
135
|
+
// Drop all indexes except _id
|
|
136
|
+
await indexEngine.dropAllIndexes();
|
|
137
|
+
} else if (typeof indexName === 'string') {
|
|
138
|
+
// Drop specific index by name
|
|
139
|
+
await indexEngine.dropIndex(indexName);
|
|
140
|
+
} else if (typeof indexName === 'object') {
|
|
141
|
+
// Drop index by key specification
|
|
142
|
+
const indexes = await indexEngine.listIndexes();
|
|
143
|
+
const keyStr = JSON.stringify(indexName);
|
|
144
|
+
|
|
145
|
+
for (const idx of indexes) {
|
|
146
|
+
if (JSON.stringify(idx.key) === keyStr) {
|
|
147
|
+
await indexEngine.dropIndex(idx.name);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { ok: 1, nIndexesWas: 1 };
|
|
154
|
+
} catch (error: any) {
|
|
155
|
+
return {
|
|
156
|
+
ok: 0,
|
|
157
|
+
errmsg: error.message || 'Failed to drop index',
|
|
158
|
+
code: error.code || 27,
|
|
159
|
+
codeName: error.codeName || 'IndexNotFound',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handle listIndexes command
|
|
166
|
+
*/
|
|
167
|
+
private async handleListIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
168
|
+
const { storage, database, command } = context;
|
|
169
|
+
|
|
170
|
+
const collection = command.listIndexes;
|
|
171
|
+
const cursor = command.cursor || {};
|
|
172
|
+
const batchSize = cursor.batchSize || 101;
|
|
173
|
+
|
|
174
|
+
// Check if collection exists
|
|
175
|
+
const exists = await storage.collectionExists(database, collection);
|
|
176
|
+
if (!exists) {
|
|
177
|
+
return {
|
|
178
|
+
ok: 0,
|
|
179
|
+
errmsg: `ns not found ${database}.${collection}`,
|
|
180
|
+
code: 26,
|
|
181
|
+
codeName: 'NamespaceNotFound',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const indexEngine = getIndexEngine(storage, database, collection);
|
|
186
|
+
const indexes = await indexEngine.listIndexes();
|
|
187
|
+
|
|
188
|
+
// Format indexes for response
|
|
189
|
+
const indexDocs = indexes.map(idx => ({
|
|
190
|
+
v: idx.v || 2,
|
|
191
|
+
key: idx.key,
|
|
192
|
+
name: idx.name,
|
|
193
|
+
...(idx.unique ? { unique: idx.unique } : {}),
|
|
194
|
+
...(idx.sparse ? { sparse: idx.sparse } : {}),
|
|
195
|
+
...(idx.expireAfterSeconds !== undefined ? { expireAfterSeconds: idx.expireAfterSeconds } : {}),
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
ok: 1,
|
|
200
|
+
cursor: {
|
|
201
|
+
id: plugins.bson.Long.fromNumber(0),
|
|
202
|
+
ns: `${database}.${collection}`,
|
|
203
|
+
firstBatch: indexDocs,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as plugins from '../../congodb.plugins.js';
|
|
2
|
+
import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* InsertHandler - Handles insert commands
|
|
6
|
+
*/
|
|
7
|
+
export class InsertHandler implements ICommandHandler {
|
|
8
|
+
async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
|
|
9
|
+
const { storage, database, command, documentSequences } = context;
|
|
10
|
+
|
|
11
|
+
const collection = command.insert;
|
|
12
|
+
if (typeof collection !== 'string') {
|
|
13
|
+
return {
|
|
14
|
+
ok: 0,
|
|
15
|
+
errmsg: 'insert command requires a collection name',
|
|
16
|
+
code: 2,
|
|
17
|
+
codeName: 'BadValue',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get documents from command or document sequences
|
|
22
|
+
let documents: plugins.bson.Document[] = command.documents || [];
|
|
23
|
+
|
|
24
|
+
// Check for OP_MSG document sequences (for bulk inserts)
|
|
25
|
+
if (documentSequences && documentSequences.has('documents')) {
|
|
26
|
+
documents = documentSequences.get('documents')!;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!Array.isArray(documents) || documents.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
ok: 0,
|
|
32
|
+
errmsg: 'insert command requires documents array',
|
|
33
|
+
code: 2,
|
|
34
|
+
codeName: 'BadValue',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ordered = command.ordered !== false;
|
|
39
|
+
const writeErrors: plugins.bson.Document[] = [];
|
|
40
|
+
let insertedCount = 0;
|
|
41
|
+
|
|
42
|
+
// Ensure collection exists
|
|
43
|
+
await storage.createCollection(database, collection);
|
|
44
|
+
|
|
45
|
+
// Insert documents
|
|
46
|
+
for (let i = 0; i < documents.length; i++) {
|
|
47
|
+
const doc = documents[i];
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Ensure _id exists
|
|
51
|
+
if (!doc._id) {
|
|
52
|
+
doc._id = new plugins.bson.ObjectId();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await storage.insertOne(database, collection, doc);
|
|
56
|
+
insertedCount++;
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
const writeError: plugins.bson.Document = {
|
|
59
|
+
index: i,
|
|
60
|
+
code: error.code || 11000,
|
|
61
|
+
errmsg: error.message || 'Insert failed',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Check for duplicate key error
|
|
65
|
+
if (error.message?.includes('Duplicate key')) {
|
|
66
|
+
writeError.code = 11000;
|
|
67
|
+
writeError.keyPattern = { _id: 1 };
|
|
68
|
+
writeError.keyValue = { _id: doc._id };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
writeErrors.push(writeError);
|
|
72
|
+
|
|
73
|
+
if (ordered) {
|
|
74
|
+
// Stop on first error for ordered inserts
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const response: plugins.bson.Document = {
|
|
81
|
+
ok: 1,
|
|
82
|
+
n: insertedCount,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (writeErrors.length > 0) {
|
|
86
|
+
response.writeErrors = writeErrors;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
}
|