Skip to content

Commit 2d30930

Browse files
Copilothotlong
andcommitted
Complete driver-mongo DriverInterface implementation (v4.0.0)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 05fb089 commit 2d30930

2 files changed

Lines changed: 230 additions & 3 deletions

File tree

packages/drivers/mongo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@objectql/driver-mongo",
3-
"version": "3.0.1",
3+
"version": "4.0.0",
44
"description": "MongoDB driver for ObjectQL - Native aggregation pipeline translation for high-performance NoSQL operations",
55
"keywords": [
66
"objectql",

packages/drivers/mongo/src/index.ts

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,33 @@
77
*/
88

99
import { Driver } from '@objectql/types';
10+
import { DriverInterface, QueryAST, FilterNode, SortNode } from '@objectstack/spec';
1011
import { MongoClient, Db, Filter, ObjectId, FindOptions } from 'mongodb';
1112

13+
/**
14+
* Command interface for executeCommand method
15+
*/
16+
export interface Command {
17+
type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
18+
object: string;
19+
data?: any;
20+
id?: string | number;
21+
ids?: Array<string | number>;
22+
records?: any[];
23+
updates?: Array<{id: string | number, data: any}>;
24+
options?: any;
25+
}
26+
27+
/**
28+
* Command result interface
29+
*/
30+
export interface CommandResult {
31+
success: boolean;
32+
data?: any;
33+
affected: number;
34+
error?: string;
35+
}
36+
1237
/**
1338
* MongoDB Driver for ObjectQL
1439
*
@@ -18,10 +43,10 @@ import { MongoClient, Db, Filter, ObjectId, FindOptions } from 'mongodb';
1843
*
1944
* The driver internally converts QueryAST format to MongoDB query format.
2045
*/
21-
export class MongoDriver implements Driver {
46+
export class MongoDriver implements Driver, DriverInterface {
2247
// Driver metadata (ObjectStack-compatible)
2348
public readonly name = 'MongoDriver';
24-
public readonly version = '3.0.1';
49+
public readonly version = '4.0.0';
2550
public readonly supports = {
2651
transactions: true,
2752
joins: false,
@@ -405,5 +430,207 @@ export class MongoDriver implements Driver {
405430
await this.client.close();
406431
}
407432
}
433+
434+
/**
435+
* Execute a query using QueryAST (DriverInterface v4.0 method)
436+
*
437+
* This is the new standard method for query execution using the
438+
* ObjectStack QueryAST format.
439+
*
440+
* @param ast - The QueryAST representing the query
441+
* @param options - Optional execution options
442+
* @returns Query results with value and count
443+
*/
444+
async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
445+
const objectName = ast.object || '';
446+
447+
// Convert QueryAST to legacy query format
448+
const legacyQuery: any = {
449+
fields: ast.fields,
450+
filters: this.convertFilterNodeToLegacy(ast.filters),
451+
sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
452+
limit: ast.top,
453+
skip: ast.skip,
454+
};
455+
456+
// Use existing find method
457+
const results = await this.find(objectName, legacyQuery, options);
458+
459+
return {
460+
value: results,
461+
count: results.length
462+
};
463+
}
464+
465+
/**
466+
* Execute a command (DriverInterface v4.0 method)
467+
*
468+
* This method handles all mutation operations (create, update, delete)
469+
* using a unified command interface.
470+
*
471+
* @param command - The command to execute
472+
* @param options - Optional execution options
473+
* @returns Command execution result
474+
*/
475+
async executeCommand(command: Command, options?: any): Promise<CommandResult> {
476+
try {
477+
const cmdOptions = { ...options, ...command.options };
478+
479+
switch (command.type) {
480+
case 'create':
481+
if (!command.data) {
482+
throw new Error('Create command requires data');
483+
}
484+
const created = await this.create(command.object, command.data, cmdOptions);
485+
return {
486+
success: true,
487+
data: created,
488+
affected: 1
489+
};
490+
491+
case 'update':
492+
if (!command.id || !command.data) {
493+
throw new Error('Update command requires id and data');
494+
}
495+
const updated = await this.update(command.object, command.id, command.data, cmdOptions);
496+
return {
497+
success: true,
498+
data: updated,
499+
affected: updated ? 1 : 0
500+
};
501+
502+
case 'delete':
503+
if (!command.id) {
504+
throw new Error('Delete command requires id');
505+
}
506+
const deleteCount = await this.delete(command.object, command.id, cmdOptions);
507+
return {
508+
success: true,
509+
affected: deleteCount
510+
};
511+
512+
case 'bulkCreate':
513+
if (!command.records || !Array.isArray(command.records)) {
514+
throw new Error('BulkCreate command requires records array');
515+
}
516+
const bulkCreated = await this.createMany(command.object, command.records, cmdOptions);
517+
return {
518+
success: true,
519+
data: bulkCreated,
520+
affected: command.records.length
521+
};
522+
523+
case 'bulkUpdate':
524+
if (!command.updates || !Array.isArray(command.updates)) {
525+
throw new Error('BulkUpdate command requires updates array');
526+
}
527+
let updateCount = 0;
528+
const updateResults = [];
529+
for (const update of command.updates) {
530+
const result = await this.update(command.object, update.id, update.data, cmdOptions);
531+
updateResults.push(result);
532+
if (result) updateCount++;
533+
}
534+
return {
535+
success: true,
536+
data: updateResults,
537+
affected: updateCount
538+
};
539+
540+
case 'bulkDelete':
541+
if (!command.ids || !Array.isArray(command.ids)) {
542+
throw new Error('BulkDelete command requires ids array');
543+
}
544+
let deleted = 0;
545+
for (const id of command.ids) {
546+
const result = await this.delete(command.object, id, cmdOptions);
547+
deleted += result;
548+
}
549+
return {
550+
success: true,
551+
affected: deleted
552+
};
553+
554+
default:
555+
throw new Error(`Unknown command type: ${(command as any).type}`);
556+
}
557+
} catch (error: any) {
558+
return {
559+
success: false,
560+
error: error.message || 'Command execution failed',
561+
affected: 0
562+
};
563+
}
564+
}
565+
566+
/**
567+
* Convert FilterNode (QueryAST format) to legacy filter array format
568+
* This allows reuse of existing filter logic while supporting new QueryAST
569+
*
570+
* @private
571+
*/
572+
private convertFilterNodeToLegacy(node?: FilterNode): any {
573+
if (!node) return undefined;
574+
575+
switch (node.type) {
576+
case 'comparison':
577+
// Convert comparison node to [field, operator, value] format
578+
const operator = node.operator || '=';
579+
return [[node.field, operator, node.value]];
580+
581+
case 'and':
582+
// Convert AND node to array with 'and' separator
583+
if (!node.children || node.children.length === 0) return undefined;
584+
const andResults: any[] = [];
585+
for (const child of node.children) {
586+
const converted = this.convertFilterNodeToLegacy(child);
587+
if (converted) {
588+
if (andResults.length > 0) {
589+
andResults.push('and');
590+
}
591+
andResults.push(...(Array.isArray(converted) ? converted : [converted]));
592+
}
593+
}
594+
return andResults.length > 0 ? andResults : undefined;
595+
596+
case 'or':
597+
// Convert OR node to array with 'or' separator
598+
if (!node.children || node.children.length === 0) return undefined;
599+
const orResults: any[] = [];
600+
for (const child of node.children) {
601+
const converted = this.convertFilterNodeToLegacy(child);
602+
if (converted) {
603+
if (orResults.length > 0) {
604+
orResults.push('or');
605+
}
606+
orResults.push(...(Array.isArray(converted) ? converted : [converted]));
607+
}
608+
}
609+
return orResults.length > 0 ? orResults : undefined;
610+
611+
case 'not':
612+
// NOT is complex - we'll just process the first child for now
613+
if (node.children && node.children.length > 0) {
614+
return this.convertFilterNodeToLegacy(node.children[0]);
615+
}
616+
return undefined;
617+
618+
default:
619+
return undefined;
620+
}
621+
}
622+
623+
/**
624+
* Execute command (alternative signature for compatibility)
625+
*
626+
* @param command - Command string or object
627+
* @param parameters - Command parameters
628+
* @param options - Execution options
629+
*/
630+
async execute(command: any, parameters?: any[], options?: any): Promise<any> {
631+
// MongoDB driver doesn't support raw command execution in the traditional SQL sense
632+
// This method is here for DriverInterface compatibility
633+
throw new Error('MongoDB driver does not support raw command execution. Use executeCommand() instead.');
634+
}
408635
}
409636

0 commit comments

Comments
 (0)