77 */
88
99import { Driver } from '@objectql/types' ;
10+ import { DriverInterface , QueryAST , FilterNode , SortNode } from '@objectstack/spec' ;
1011import { 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