@objectql/driver-mongo 3.0.1 → 4.0.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/src/index.ts CHANGED
@@ -1,7 +1,70 @@
1
+ import { Data, System } from '@objectstack/spec';
2
+ type QueryAST = Data.QueryAST;
3
+ type FilterNode = Data.FilterNode;
4
+ type SortNode = Data.SortNode;
5
+ type DriverInterface = System.DriverInterface;
6
+ /**
7
+ * ObjectQL
8
+ * Copyright (c) 2026-present ObjectStack Inc.
9
+ *
10
+ * This source code is licensed under the MIT license found in the
11
+ * LICENSE file in the root directory of this source tree.
12
+ */
13
+
1
14
  import { Driver } from '@objectql/types';
2
15
  import { MongoClient, Db, Filter, ObjectId, FindOptions } from 'mongodb';
3
16
 
17
+ /**
18
+ * Command interface for executeCommand method
19
+ */
20
+ export interface Command {
21
+ type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
22
+ object: string;
23
+ data?: any;
24
+ id?: string | number;
25
+ ids?: Array<string | number>;
26
+ records?: any[];
27
+ updates?: Array<{id: string | number, data: any}>;
28
+ options?: any;
29
+ }
30
+
31
+ /**
32
+ * Command result interface
33
+ */
34
+ export interface CommandResult {
35
+ success: boolean;
36
+ data?: any;
37
+ affected: number;
38
+ error?: string;
39
+ }
40
+
41
+ /**
42
+ * MongoDB Driver for ObjectQL
43
+ *
44
+ * Implements both the legacy Driver interface from @objectql/types and
45
+ * the standard DriverInterface from @objectstack/spec for compatibility
46
+ * with the new kernel-based plugin system.
47
+ *
48
+ * The driver internally converts QueryAST format to MongoDB query format.
49
+ */
4
50
  export class MongoDriver implements Driver {
51
+ // Driver metadata (ObjectStack-compatible)
52
+ public readonly name = 'MongoDriver';
53
+ public readonly version = '3.0.1';
54
+ public readonly supports = {
55
+ transactions: true,
56
+ joins: false,
57
+ fullTextSearch: true,
58
+ jsonFields: true,
59
+ arrayFields: true,
60
+ queryFilters: true,
61
+ queryAggregations: true,
62
+ querySorting: true,
63
+ queryPagination: true,
64
+ queryWindowFunctions: false,
65
+ querySubqueries: false
66
+ };
67
+
5
68
  private client: MongoClient;
6
69
  private db?: Db;
7
70
  private config: any;
@@ -10,14 +73,39 @@ export class MongoDriver implements Driver {
10
73
  constructor(config: { url: string, dbName?: string }) {
11
74
  this.config = config;
12
75
  this.client = new MongoClient(config.url);
13
- this.connected = this.connect();
76
+ this.connected = this.internalConnect();
14
77
  }
15
78
 
16
- private async connect() {
79
+ /**
80
+ * Internal connect method used in constructor
81
+ */
82
+ private async internalConnect() {
17
83
  await this.client.connect();
18
84
  this.db = this.client.db(this.config.dbName);
19
85
  }
20
86
 
87
+ /**
88
+ * Connect to the database (for DriverInterface compatibility)
89
+ * This method ensures the connection is established.
90
+ */
91
+ async connect(): Promise<void> {
92
+ await this.connected;
93
+ }
94
+
95
+ /**
96
+ * Check database connection health
97
+ */
98
+ async checkHealth(): Promise<boolean> {
99
+ try {
100
+ await this.connected;
101
+ if (!this.db) return false;
102
+ await this.db.admin().ping();
103
+ return true;
104
+ } catch (error) {
105
+ return false;
106
+ }
107
+ }
108
+
21
109
  private async getCollection(objectName: string) {
22
110
  await this.connected;
23
111
  if (!this.db) throw new Error("Database not initialized");
@@ -162,29 +250,79 @@ export class MongoDriver implements Driver {
162
250
  return mongoCondition;
163
251
  }
164
252
 
253
+ /**
254
+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
255
+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
256
+ *
257
+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
258
+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
259
+ * QueryAST uses 'aggregations', while legacy uses 'aggregate'.
260
+ */
261
+ private normalizeQuery(query: any): any {
262
+ if (!query) return {};
263
+
264
+ const normalized: any = { ...query };
265
+
266
+ // Normalize limit/top
267
+ if (normalized.top !== undefined && normalized.limit === undefined) {
268
+ normalized.limit = normalized.top;
269
+ }
270
+
271
+ // Normalize aggregations/aggregate
272
+ if (normalized.aggregations !== undefined && normalized.aggregate === undefined) {
273
+ // Convert QueryAST aggregations format to legacy aggregate format
274
+ normalized.aggregate = normalized.aggregations.map((agg: any) => ({
275
+ func: agg.function || agg.func,
276
+ field: agg.field,
277
+ alias: agg.alias
278
+ }));
279
+ }
280
+
281
+ // Normalize sort format
282
+ if (normalized.sort && Array.isArray(normalized.sort)) {
283
+ // Check if it's already in the array format [field, order]
284
+ const firstSort = normalized.sort[0];
285
+ if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
286
+ // Convert from QueryAST format {field, order} to internal format [field, order]
287
+ normalized.sort = normalized.sort.map((item: any) => [
288
+ item.field,
289
+ item.order || item.direction || item.dir || 'asc'
290
+ ]);
291
+ }
292
+ }
293
+
294
+ return normalized;
295
+ }
296
+
165
297
  async find(objectName: string, query: any, options?: any): Promise<any[]> {
298
+ const normalizedQuery = this.normalizeQuery(query);
166
299
  const collection = await this.getCollection(objectName);
167
- const filter = this.mapFilters(query.filters);
300
+ const filter = this.mapFilters(normalizedQuery.filters);
168
301
 
169
302
  const findOptions: FindOptions = {};
170
- if (query.skip) findOptions.skip = query.skip;
171
- if (query.limit) findOptions.limit = query.limit;
172
- if (query.sort) {
303
+ if (normalizedQuery.skip) findOptions.skip = normalizedQuery.skip;
304
+ if (normalizedQuery.limit) findOptions.limit = normalizedQuery.limit;
305
+ if (normalizedQuery.sort) {
173
306
  // map [['field', 'desc']] to { field: -1 }
174
307
  findOptions.sort = {};
175
- for (const [field, order] of query.sort) {
308
+ for (const [field, order] of normalizedQuery.sort) {
176
309
  // Map both 'id' and '_id' to '_id' for backward compatibility
177
310
  const dbField = (field === 'id' || field === '_id') ? '_id' : field;
178
311
  (findOptions.sort as any)[dbField] = order === 'desc' ? -1 : 1;
179
312
  }
180
313
  }
181
- if (query.fields && query.fields.length > 0) {
314
+ if (normalizedQuery.fields && normalizedQuery.fields.length > 0) {
182
315
  findOptions.projection = {};
183
- for (const field of query.fields) {
316
+ for (const field of normalizedQuery.fields) {
184
317
  // Map both 'id' and '_id' to '_id' for backward compatibility
185
318
  const dbField = (field === 'id' || field === '_id') ? '_id' : field;
186
319
  (findOptions.projection as any)[dbField] = 1;
187
320
  }
321
+ // Explicitly exclude _id if 'id' is not in the requested fields
322
+ const hasIdField = normalizedQuery.fields.some((f: string) => f === 'id' || f === '_id');
323
+ if (!hasIdField) {
324
+ (findOptions.projection as any)._id = 0;
325
+ }
188
326
  }
189
327
 
190
328
  const results = await collection.find(filter, findOptions).toArray();
@@ -245,7 +383,10 @@ export class MongoDriver implements Driver {
245
383
 
246
384
  async count(objectName: string, filters: any, options?: any): Promise<number> {
247
385
  const collection = await this.getCollection(objectName);
248
- const filter = this.mapFilters(filters);
386
+ // Normalize to support both filter arrays and full query objects
387
+ const normalizedQuery = this.normalizeQuery(filters);
388
+ const actualFilters = normalizedQuery.filters || filters;
389
+ const filter = this.mapFilters(actualFilters);
249
390
  return await collection.countDocuments(filter);
250
391
  }
251
392
 
@@ -299,5 +440,205 @@ export class MongoDriver implements Driver {
299
440
  await this.client.close();
300
441
  }
301
442
  }
443
+
444
+ /**
445
+ * Execute a query using QueryAST (DriverInterface v4.0 method)
446
+ *
447
+ * This is the new standard method for query execution using the
448
+ * ObjectStack QueryAST format.
449
+ *
450
+ * @param ast - The QueryAST representing the query
451
+ * @param options - Optional execution options
452
+ * @returns Query results with value and count
453
+ */
454
+ async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
455
+ const objectName = ast.object || '';
456
+
457
+ // Convert QueryAST to legacy query format
458
+ const legacyQuery: any = {
459
+ fields: ast.fields,
460
+ filters: this.convertFilterNodeToLegacy(ast.filters),
461
+ sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
462
+ limit: ast.top,
463
+ skip: ast.skip,
464
+ };
465
+
466
+ // Use existing find method
467
+ const results = await this.find(objectName, legacyQuery, options);
468
+
469
+ return {
470
+ value: results,
471
+ count: results.length
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Execute a command (DriverInterface v4.0 method)
477
+ *
478
+ * This method handles all mutation operations (create, update, delete)
479
+ * using a unified command interface.
480
+ *
481
+ * @param command - The command to execute
482
+ * @param options - Optional execution options
483
+ * @returns Command execution result
484
+ */
485
+ async executeCommand(command: Command, options?: any): Promise<CommandResult> {
486
+ try {
487
+ const cmdOptions = { ...options, ...command.options };
488
+
489
+ switch (command.type) {
490
+ case 'create':
491
+ if (!command.data) {
492
+ throw new Error('Create command requires data');
493
+ }
494
+ const created = await this.create(command.object, command.data, cmdOptions);
495
+ return {
496
+ success: true,
497
+ data: created,
498
+ affected: 1
499
+ };
500
+
501
+ case 'update':
502
+ if (!command.id || !command.data) {
503
+ throw new Error('Update command requires id and data');
504
+ }
505
+ const updated = await this.update(command.object, command.id, command.data, cmdOptions);
506
+ return {
507
+ success: true,
508
+ data: updated,
509
+ affected: updated ? 1 : 0
510
+ };
511
+
512
+ case 'delete':
513
+ if (!command.id) {
514
+ throw new Error('Delete command requires id');
515
+ }
516
+ const deleteCount = await this.delete(command.object, command.id, cmdOptions);
517
+ return {
518
+ success: true,
519
+ affected: deleteCount
520
+ };
521
+
522
+ case 'bulkCreate':
523
+ if (!command.records || !Array.isArray(command.records)) {
524
+ throw new Error('BulkCreate command requires records array');
525
+ }
526
+ const bulkCreated = await this.createMany(command.object, command.records, cmdOptions);
527
+ return {
528
+ success: true,
529
+ data: bulkCreated,
530
+ affected: command.records.length
531
+ };
532
+
533
+ case 'bulkUpdate':
534
+ if (!command.updates || !Array.isArray(command.updates)) {
535
+ throw new Error('BulkUpdate command requires updates array');
536
+ }
537
+ let updateCount = 0;
538
+ const updateResults = [];
539
+ for (const update of command.updates) {
540
+ const result = await this.update(command.object, update.id, update.data, cmdOptions);
541
+ updateResults.push(result);
542
+ if (result) updateCount++;
543
+ }
544
+ return {
545
+ success: true,
546
+ data: updateResults,
547
+ affected: updateCount
548
+ };
549
+
550
+ case 'bulkDelete':
551
+ if (!command.ids || !Array.isArray(command.ids)) {
552
+ throw new Error('BulkDelete command requires ids array');
553
+ }
554
+ let deleted = 0;
555
+ for (const id of command.ids) {
556
+ const result = await this.delete(command.object, id, cmdOptions);
557
+ deleted += result;
558
+ }
559
+ return {
560
+ success: true,
561
+ affected: deleted
562
+ };
563
+
564
+ default:
565
+ const validTypes = ['create', 'update', 'delete', 'bulkCreate', 'bulkUpdate', 'bulkDelete'];
566
+ throw new Error(`Unknown command type: ${(command as any).type}. Valid types are: ${validTypes.join(', ')}`);
567
+ }
568
+ } catch (error: any) {
569
+ return {
570
+ success: false,
571
+ error: error.message || 'Command execution failed',
572
+ affected: 0
573
+ };
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Convert FilterNode (QueryAST format) to legacy filter array format
579
+ * This allows reuse of existing filter logic while supporting new QueryAST
580
+ *
581
+ * @private
582
+ */
583
+ private convertFilterNodeToLegacy(node?: FilterNode): any {
584
+ if (!node) return undefined;
585
+
586
+ switch (node.type) {
587
+ case 'comparison':
588
+ // Convert comparison node to [field, operator, value] format
589
+ const operator = node.operator || '=';
590
+ return [[node.field, operator, node.value]];
591
+
592
+ case 'and':
593
+ case 'or':
594
+ // Convert AND/OR node to array with separator
595
+ if (!node.children || node.children.length === 0) return undefined;
596
+ const results: any[] = [];
597
+ const separator = node.type; // 'and' or 'or'
598
+
599
+ for (const child of node.children) {
600
+ const converted = this.convertFilterNodeToLegacy(child);
601
+ if (converted) {
602
+ if (results.length > 0) {
603
+ results.push(separator);
604
+ }
605
+ results.push(...(Array.isArray(converted) ? converted : [converted]));
606
+ }
607
+ }
608
+ return results.length > 0 ? results : undefined;
609
+
610
+ case 'not':
611
+ // NOT is not directly supported in the legacy filter format
612
+ // MongoDB supports $not, but legacy array format doesn't have a NOT operator
613
+ // Use native MongoDB queries with $not instead:
614
+ // Example: { field: { $not: { $eq: value } } }
615
+ throw new Error(
616
+ 'NOT filters are not supported in legacy filter format. ' +
617
+ 'Use native MongoDB queries with $not operator instead. ' +
618
+ 'Example: { field: { $not: { $eq: value } } }'
619
+ );
620
+
621
+ default:
622
+ return undefined;
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Execute command (alternative signature for compatibility)
628
+ *
629
+ * @param command - Command string or object
630
+ * @param parameters - Command parameters
631
+ * @param options - Execution options
632
+ */
633
+ async execute(command: any, parameters?: any[], options?: any): Promise<any> {
634
+ // MongoDB driver doesn't support raw command execution in the traditional SQL sense
635
+ // Use executeCommand() instead for mutations (create/update/delete)
636
+ // Example: await driver.executeCommand({ type: 'create', object: 'users', data: {...} })
637
+ throw new Error(
638
+ 'MongoDB driver does not support raw command execution. ' +
639
+ 'Use executeCommand() for mutations or aggregate() for complex queries. ' +
640
+ 'Example: driver.executeCommand({ type: "create", object: "users", data: {...} })'
641
+ );
642
+ }
302
643
  }
303
644
 
@@ -1,3 +1,11 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
1
9
  import { MongoDriver } from '../src';
2
10
  import { MongoClient } from 'mongodb';
3
11
 
@@ -9,6 +17,10 @@ const mockCollection = {
9
17
  toArray: jest.fn().mockResolvedValue([]),
10
18
  findOne: jest.fn().mockResolvedValue(null),
11
19
  insertOne: jest.fn().mockResolvedValue({ insertedId: '123' }),
20
+ insertMany: jest.fn().mockResolvedValue({
21
+ insertedIds: { 0: 'id1', 1: 'id2' },
22
+ insertedCount: 2
23
+ }),
12
24
  updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
13
25
  deleteOne: jest.fn().mockResolvedValue({ deletedCount: 1 }),
14
26
  countDocuments: jest.fn().mockResolvedValue(10)
@@ -26,7 +38,10 @@ const mockClient = {
26
38
  jest.mock('mongodb', () => {
27
39
  return {
28
40
  MongoClient: jest.fn().mockImplementation(() => mockClient),
29
- ObjectId: jest.fn(id => id)
41
+ ObjectId: jest.fn().mockImplementation((id?: string) => ({
42
+ toHexString: () => id || 'generated-object-id',
43
+ toString: () => id || 'generated-object-id'
44
+ }))
30
45
  };
31
46
  });
32
47
 
@@ -320,4 +335,246 @@ describe('MongoDriver', () => {
320
335
  );
321
336
  });
322
337
 
338
+ describe('DriverInterface v4.0 methods', () => {
339
+ describe('executeQuery', () => {
340
+ it('should execute a simple QueryAST query', async () => {
341
+ const ast = {
342
+ object: 'users',
343
+ fields: ['name', 'email'],
344
+ filters: {
345
+ type: 'comparison' as const,
346
+ field: 'status',
347
+ operator: '=',
348
+ value: 'active'
349
+ },
350
+ top: 10,
351
+ skip: 0
352
+ };
353
+
354
+ mockCollection.toArray.mockResolvedValue([
355
+ { name: 'User 1', email: 'user1@example.com' },
356
+ { name: 'User 2', email: 'user2@example.com' }
357
+ ]);
358
+
359
+ const result = await driver.executeQuery(ast);
360
+
361
+ expect(result.value).toHaveLength(2);
362
+ expect(result.count).toBe(2);
363
+ expect(mockCollection.find).toHaveBeenCalled();
364
+ });
365
+
366
+ it('should handle complex QueryAST with AND filters', async () => {
367
+ const ast = {
368
+ object: 'users',
369
+ filters: {
370
+ type: 'and' as const,
371
+ children: [
372
+ {
373
+ type: 'comparison' as const,
374
+ field: 'status',
375
+ operator: '=',
376
+ value: 'active'
377
+ },
378
+ {
379
+ type: 'comparison' as const,
380
+ field: 'age',
381
+ operator: '>',
382
+ value: 18
383
+ }
384
+ ]
385
+ }
386
+ };
387
+
388
+ mockCollection.toArray.mockResolvedValue([]);
389
+
390
+ const result = await driver.executeQuery(ast);
391
+
392
+ expect(result.value).toEqual([]);
393
+ expect(mockCollection.find).toHaveBeenCalled();
394
+ });
395
+
396
+ it('should handle QueryAST with sort', async () => {
397
+ const ast = {
398
+ object: 'users',
399
+ sort: [
400
+ { field: 'name', order: 'asc' as const }
401
+ ]
402
+ };
403
+
404
+ mockCollection.toArray.mockResolvedValue([]);
405
+
406
+ await driver.executeQuery(ast);
407
+
408
+ expect(mockCollection.find).toHaveBeenCalledWith(
409
+ {},
410
+ expect.objectContaining({
411
+ sort: { name: 1 }
412
+ })
413
+ );
414
+ });
415
+ });
416
+
417
+ describe('executeCommand', () => {
418
+ it('should execute create command', async () => {
419
+ const command = {
420
+ type: 'create' as const,
421
+ object: 'users',
422
+ data: { name: 'New User', email: 'new@example.com' }
423
+ };
424
+
425
+ mockCollection.insertOne.mockResolvedValue({
426
+ insertedId: 'new123',
427
+ acknowledged: true
428
+ } as any);
429
+
430
+ const result = await driver.executeCommand(command);
431
+
432
+ expect(result.success).toBe(true);
433
+ expect(result.affected).toBe(1);
434
+ expect(result.data).toBeDefined();
435
+ expect(mockCollection.insertOne).toHaveBeenCalled();
436
+ });
437
+
438
+ it('should execute update command', async () => {
439
+ const command = {
440
+ type: 'update' as const,
441
+ object: 'users',
442
+ id: '123',
443
+ data: { name: 'Updated User' }
444
+ };
445
+
446
+ mockCollection.updateOne.mockResolvedValue({
447
+ modifiedCount: 1,
448
+ acknowledged: true
449
+ } as any);
450
+ mockCollection.findOne.mockResolvedValue({
451
+ _id: '123',
452
+ name: 'Updated User'
453
+ });
454
+
455
+ const result = await driver.executeCommand(command);
456
+
457
+ expect(result.success).toBe(true);
458
+ expect(result.affected).toBe(1);
459
+ expect(mockCollection.updateOne).toHaveBeenCalled();
460
+ });
461
+
462
+ it('should execute delete command', async () => {
463
+ const command = {
464
+ type: 'delete' as const,
465
+ object: 'users',
466
+ id: '123'
467
+ };
468
+
469
+ mockCollection.deleteOne.mockResolvedValue({
470
+ deletedCount: 1,
471
+ acknowledged: true
472
+ } as any);
473
+
474
+ const result = await driver.executeCommand(command);
475
+
476
+ expect(result.success).toBe(true);
477
+ expect(result.affected).toBe(1);
478
+ expect(mockCollection.deleteOne).toHaveBeenCalled();
479
+ });
480
+
481
+ it('should execute bulkCreate command', async () => {
482
+ const command = {
483
+ type: 'bulkCreate' as const,
484
+ object: 'users',
485
+ records: [
486
+ { name: 'User 1', email: 'user1@example.com' },
487
+ { name: 'User 2', email: 'user2@example.com' }
488
+ ]
489
+ };
490
+
491
+ mockCollection.insertOne.mockResolvedValue({
492
+ insertedId: 'id1',
493
+ acknowledged: true
494
+ } as any);
495
+
496
+ const result = await driver.executeCommand(command);
497
+
498
+ expect(result.success).toBe(true);
499
+ expect(result.affected).toBe(2);
500
+ expect(result.data).toHaveLength(2);
501
+ });
502
+
503
+ it('should execute bulkUpdate command', async () => {
504
+ const command = {
505
+ type: 'bulkUpdate' as const,
506
+ object: 'users',
507
+ updates: [
508
+ { id: '1', data: { name: 'Updated 1' } },
509
+ { id: '2', data: { name: 'Updated 2' } }
510
+ ]
511
+ };
512
+
513
+ mockCollection.updateOne.mockResolvedValue({
514
+ modifiedCount: 1,
515
+ acknowledged: true
516
+ } as any);
517
+ mockCollection.findOne.mockResolvedValue({ _id: '1', name: 'Updated 1' });
518
+
519
+ const result = await driver.executeCommand(command);
520
+
521
+ expect(result.success).toBe(true);
522
+ expect(result.affected).toBe(2);
523
+ });
524
+
525
+ it('should execute bulkDelete command', async () => {
526
+ const command = {
527
+ type: 'bulkDelete' as const,
528
+ object: 'users',
529
+ ids: ['1', '2', '3']
530
+ };
531
+
532
+ mockCollection.deleteOne.mockResolvedValue({
533
+ deletedCount: 1,
534
+ acknowledged: true
535
+ } as any);
536
+
537
+ const result = await driver.executeCommand(command);
538
+
539
+ expect(result.success).toBe(true);
540
+ expect(result.affected).toBe(3);
541
+ });
542
+
543
+ it('should handle command errors gracefully', async () => {
544
+ const command = {
545
+ type: 'create' as const,
546
+ object: 'users',
547
+ data: undefined // Invalid data
548
+ };
549
+
550
+ const result = await driver.executeCommand(command);
551
+
552
+ expect(result.success).toBe(false);
553
+ expect(result.error).toBeDefined();
554
+ expect(result.affected).toBe(0);
555
+ });
556
+
557
+ it('should reject unknown command types', async () => {
558
+ const command = {
559
+ type: 'invalidCommand' as any,
560
+ object: 'users'
561
+ };
562
+
563
+ const result = await driver.executeCommand(command);
564
+
565
+ expect(result.success).toBe(false);
566
+ expect(result.error).toContain('Unknown command type');
567
+ expect(result.error).toContain('Valid types are');
568
+ });
569
+ });
570
+
571
+ describe('execute', () => {
572
+ it('should throw error as MongoDB does not support raw command execution', async () => {
573
+ await expect(driver.execute('SELECT * FROM users')).rejects.toThrow(
574
+ 'MongoDB driver does not support raw command execution'
575
+ );
576
+ });
577
+ });
578
+ });
579
+
323
580
  });