@@ -64,65 +64,102 @@ export class MongoDriver implements Driver {
6464 private mapFilters ( filters : any ) : Filter < any > {
6565 if ( ! filters || filters . length === 0 ) return { } ;
6666
67- // Simple case: Array of arrays [['a','=','1'], ['b','=','2']] (Implicit AND)
68- // or Mixed: [['a','=','1'], 'and', ['b','=','2']]
69-
67+ const result = this . buildFilterConditions ( filters ) ;
68+ return result ;
69+ }
70+
71+ /**
72+ * Build MongoDB filter conditions from ObjectQL filter array.
73+ * Supports nested filter groups and logical operators (AND/OR).
74+ */
75+ private buildFilterConditions ( filters : any [ ] ) : Filter < any > {
7076 const conditions : any [ ] = [ ] ;
71- let currentLogic = '$and' ; // Default implicit logic
77+ let nextJoin = '$and' ; // Default logic operator for next condition
7278
7379 for ( const item of filters ) {
80+ if ( typeof item === 'string' ) {
81+ // Update the logic operator for the next condition
82+ if ( item . toLowerCase ( ) === 'or' ) {
83+ nextJoin = '$or' ;
84+ } else if ( item . toLowerCase ( ) === 'and' ) {
85+ nextJoin = '$and' ;
86+ }
87+ continue ;
88+ }
89+
7490 if ( Array . isArray ( item ) ) {
75- const [ field , op , value ] = item ;
76- let mongoCondition : any = { } ;
77-
78- // Map both 'id' and '_id' to '_id' for MongoDB compatibility
79- // This ensures backward compatibility for queries using '_id'
80- const dbField = ( field === 'id' || field === '_id' ) ? '_id' : field ;
91+ // Heuristic to detect if it is a criterion [field, op, value] or a nested group
92+ const [ fieldRaw , op , value ] = item ;
93+ const isCriterion = typeof fieldRaw === 'string' && typeof op === 'string' ;
94+
95+ let condition : any ;
8196
82- if ( dbField === '_id' ) {
83- mongoCondition [ dbField ] = this . normalizeId ( value ) ;
97+ if ( isCriterion ) {
98+ // This is a single criterion [field, op, value]
99+ condition = this . buildSingleCondition ( fieldRaw , op , value ) ;
84100 } else {
85- switch ( op ) {
86- case '=' : mongoCondition [ dbField ] = { $eq : value } ; break ;
87- case '!=' : mongoCondition [ dbField ] = { $ne : value } ; break ;
88- case '>' : mongoCondition [ dbField ] = { $gt : value } ; break ;
89- case '>=' : mongoCondition [ dbField ] = { $gte : value } ; break ;
90- case '<' : mongoCondition [ dbField ] = { $lt : value } ; break ;
91- case '<=' : mongoCondition [ dbField ] = { $lte : value } ; break ;
92- case 'in' : mongoCondition [ dbField ] = { $in : value } ; break ;
93- case 'nin' : mongoCondition [ dbField ] = { $nin : value } ; break ;
94- case 'contains' :
95- // Basic regex escape should be added for safety
96- mongoCondition [ dbField ] = { $regex : value , $options : 'i' } ;
97- break ;
98- default : mongoCondition [ dbField ] = { $eq : value } ;
99- }
101+ // This is a nested group - recursively process it
102+ condition = this . buildFilterConditions ( item ) ;
100103 }
101- conditions . push ( mongoCondition ) ;
102-
103- } else if ( typeof item === 'string' ) {
104- if ( item . toLowerCase ( ) === 'or' ) {
105- // If we encounter an OR, we might need to restructure.
106- // For simplicity in this v1, let's assume simple ANDs usually,
107- // OR handling requires more complex tree parsing if mixed.
108- // But if it's strictly [A, 'or', B], we can do strict mapping.
109- // This is a simplified parser:
110- currentLogic = '$or' ;
111- } else if ( item . toLowerCase ( ) === 'and' ) {
112- currentLogic = '$and' ;
104+
105+ // Apply the join logic
106+ if ( conditions . length > 0 && nextJoin === '$or' ) {
107+ // Collect all OR conditions together
108+ const lastItem = conditions [ conditions . length - 1 ] ;
109+ if ( lastItem && lastItem . $or ) {
110+ // Extend existing $or array
111+ lastItem . $or . push ( condition ) ;
112+ } else {
113+ // Create new $or with previous and current condition
114+ const previous = conditions . pop ( ) ;
115+ conditions . push ( { $or : [ previous , condition ] } ) ;
116+ }
117+ } else {
118+ // Default AND - just add to conditions array
119+ conditions . push ( condition ) ;
113120 }
121+
122+ // Reset to default AND logic after processing each item
123+ nextJoin = '$and' ;
114124 }
115125 }
116126
117127 if ( conditions . length === 0 ) return { } ;
118128 if ( conditions . length === 1 ) return conditions [ 0 ] ;
119129
120- // If 'or' was detected, wrap all in $or (very naive implementation)
121- if ( currentLogic === '$or' ) {
122- return { $or : conditions } ;
130+ // Multiple conditions - wrap in AND
131+ return { $and : conditions } ;
132+ }
133+
134+ /**
135+ * Build a single MongoDB condition from field, operator, and value.
136+ */
137+ private buildSingleCondition ( fieldRaw : string , op : string , value : any ) : any {
138+ const mongoCondition : any = { } ;
139+
140+ // Map both 'id' and '_id' to '_id' for MongoDB compatibility
141+ const dbField = ( fieldRaw === 'id' || fieldRaw === '_id' ) ? '_id' : fieldRaw ;
142+
143+ if ( dbField === '_id' ) {
144+ mongoCondition [ dbField ] = this . normalizeId ( value ) ;
145+ } else {
146+ switch ( op ) {
147+ case '=' : mongoCondition [ dbField ] = { $eq : value } ; break ;
148+ case '!=' : mongoCondition [ dbField ] = { $ne : value } ; break ;
149+ case '>' : mongoCondition [ dbField ] = { $gt : value } ; break ;
150+ case '>=' : mongoCondition [ dbField ] = { $gte : value } ; break ;
151+ case '<' : mongoCondition [ dbField ] = { $lt : value } ; break ;
152+ case '<=' : mongoCondition [ dbField ] = { $lte : value } ; break ;
153+ case 'in' : mongoCondition [ dbField ] = { $in : value } ; break ;
154+ case 'nin' : mongoCondition [ dbField ] = { $nin : value } ; break ;
155+ case 'contains' :
156+ mongoCondition [ dbField ] = { $regex : value , $options : 'i' } ;
157+ break ;
158+ default : mongoCondition [ dbField ] = { $eq : value } ;
159+ }
123160 }
124161
125- return { $and : conditions } ;
162+ return mongoCondition ;
126163 }
127164
128165 async find ( objectName : string , query : any , options ?: any ) : Promise < any [ ] > {
0 commit comments