@@ -37,6 +37,205 @@ public static TNode SimplifyAndConvert<TNode>(TNode node)
3737 }
3838 #endregion
3939
40+ public override AstNode VisitFieldOperationFilter ( AstFieldOperationFilter node )
41+ {
42+ node = ( AstFieldOperationFilter ) base . VisitFieldOperationFilter ( node ) ;
43+
44+ if ( node . Field . Path != "@<elem>" )
45+ {
46+ // { field : { $eq : value } } => { field : value } where value is not a regex
47+ if ( IsFieldEqValue ( node , out var value ) )
48+ {
49+ var impliedOperation = new AstImpliedOperationFilterOperation ( value ) ;
50+ return new AstFieldOperationFilter ( node . Field , impliedOperation ) ;
51+ }
52+
53+ // { field : { $regex : "pattern", $options : "options" } } => { field : /pattern/options }
54+ if ( IsFieldRegex ( node , out var regex ) )
55+ {
56+ var impliedOperation = new AstImpliedOperationFilterOperation ( regex ) ;
57+ return new AstFieldOperationFilter ( node . Field , impliedOperation ) ;
58+ }
59+
60+ // { field : { $not : { $regex : "pattern", $options : "options" } } } => { field : { $not : /pattern/options } }
61+ if ( IsFieldNotRegex ( node , out regex ) )
62+ {
63+ var notImpliedOperation = new AstNotFilterOperation ( new AstImpliedOperationFilterOperation ( regex ) ) ;
64+ return new AstFieldOperationFilter ( node . Field , notImpliedOperation ) ;
65+ }
66+
67+ // { field : { $elemMatch : { $eq : value } } } => { field : value } where value is not regex
68+ if ( IsFieldElemMatchEqValue ( node , out value ) )
69+ {
70+ var impliedOperation = new AstImpliedOperationFilterOperation ( value ) ;
71+ return new AstFieldOperationFilter ( node . Field , impliedOperation ) ;
72+ }
73+
74+ // { field : { $elemMatch : { $regex : "pattern", $options : "options" } } } => { field : /pattern/options }
75+ if ( IsFieldElemMatchRegex ( node , out regex ) )
76+ {
77+ var impliedOperation = new AstImpliedOperationFilterOperation ( regex ) ;
78+ return new AstFieldOperationFilter ( node . Field , impliedOperation ) ;
79+ }
80+
81+ // { field : { $elemMatch : { $not : { $regex : "pattern", $options : "options" } } } } => { field : { $elemMatch : { $not : /pattern/options } } }
82+ if ( IsFieldElemMatchNotRegex ( node , out var elemField , out regex ) )
83+ {
84+ var notRegexOperation = new AstNotFilterOperation ( new AstImpliedOperationFilterOperation ( regex ) ) ;
85+ var elemFilter = new AstFieldOperationFilter ( elemField , notRegexOperation ) ;
86+ var elemMatchOperation = new AstElemMatchFilterOperation ( elemFilter ) ;
87+ return new AstFieldOperationFilter ( node . Field , elemMatchOperation ) ;
88+ }
89+
90+ // { field : { $not : { $elemMatch : { $eq : value } } } } => { field : { $ne : value } } where value is not regex
91+ if ( IsFieldNotElemMatchEqValue ( node , out value ) )
92+ {
93+ var impliedOperation = new AstComparisonFilterOperation ( AstComparisonFilterOperator . Ne , value ) ;
94+ return new AstFieldOperationFilter ( node . Field , impliedOperation ) ;
95+ }
96+
97+ // { field : { $not : { $elemMatch : { $regex : "pattern", $options : "options" } } } } => { field : { $not : /pattern/options } }
98+ if ( IsFieldNotElemMatchRegex ( node , out regex ) )
99+ {
100+ var notImpliedOperation = new AstNotFilterOperation ( new AstImpliedOperationFilterOperation ( regex ) ) ;
101+ return new AstFieldOperationFilter ( node . Field , notImpliedOperation ) ;
102+ }
103+ }
104+
105+ return node ;
106+
107+ static bool IsFieldEqValue ( AstFieldOperationFilter node , out BsonValue value )
108+ {
109+ // { field : { $eq : value } } where value is not a a regex
110+ if ( node . Operation is AstComparisonFilterOperation comparisonOperation &&
111+ comparisonOperation . Operator == AstComparisonFilterOperator . Eq &&
112+ comparisonOperation . Value . BsonType != BsonType . RegularExpression )
113+ {
114+ value = comparisonOperation . Value ;
115+ return true ;
116+ }
117+
118+ value = null ;
119+ return false ;
120+ }
121+
122+ static bool IsFieldRegex ( AstFieldOperationFilter node , out BsonRegularExpression regex )
123+ {
124+ // { field : { $regex : "pattern", $options : "options" } }
125+ if ( node . Operation is AstRegexFilterOperation regexOperation )
126+ {
127+ regex = new BsonRegularExpression ( regexOperation . Pattern , regexOperation . Options ) ;
128+ return true ;
129+ }
130+
131+ regex = null ;
132+ return false ;
133+ }
134+
135+ static bool IsFieldNotRegex ( AstFieldOperationFilter node , out BsonRegularExpression regex )
136+ {
137+ // { field : { $not : { $regex : "pattern", $options : "options" } } }
138+ if ( node . Operation is AstNotFilterOperation notOperation &&
139+ notOperation . Operation is AstRegexFilterOperation regexOperation )
140+ {
141+ regex = new BsonRegularExpression ( regexOperation . Pattern , regexOperation . Options ) ;
142+ return true ;
143+ }
144+
145+ regex = null ;
146+ return false ;
147+ }
148+
149+ static bool IsFieldElemMatchEqValue ( AstFieldOperationFilter node , out BsonValue value )
150+ {
151+ // { field : { $elemMatch : { $eq : value } } } where value is not regex
152+ if ( node . Operation is AstElemMatchFilterOperation elemMatchOperation &&
153+ elemMatchOperation . Filter is AstFieldOperationFilter elemFilter &&
154+ elemFilter . Field . Path == "@<elem>" &&
155+ elemFilter . Operation is AstComparisonFilterOperation comparisonOperation &&
156+ comparisonOperation . Operator == AstComparisonFilterOperator . Eq &&
157+ comparisonOperation . Value . BsonType != BsonType . RegularExpression )
158+ {
159+ value = comparisonOperation . Value ;
160+ return true ;
161+ }
162+
163+ value = null ;
164+ return false ;
165+ }
166+
167+ static bool IsFieldElemMatchRegex ( AstFieldOperationFilter node , out BsonRegularExpression regex )
168+ {
169+ // { field : { $elemMatch : { $regex : "pattern", $options : "options" } } }
170+ if ( node . Operation is AstElemMatchFilterOperation elemMatchOperation &&
171+ elemMatchOperation . Filter is AstFieldOperationFilter elemFilter &&
172+ elemFilter . Field . Path == "@<elem>" &&
173+ elemFilter . Operation is AstRegexFilterOperation regexOperation )
174+ {
175+ regex = new BsonRegularExpression ( regexOperation . Pattern , regexOperation . Options ) ;
176+ return true ;
177+ }
178+
179+ regex = null ;
180+ return false ;
181+ }
182+
183+ static bool IsFieldElemMatchNotRegex ( AstFieldOperationFilter node , out AstFilterField elemField , out BsonRegularExpression regex )
184+ {
185+ // { field : { $elemMatch : { $not : { $regex : "pattern", $options : "options" } } } }
186+ if ( node . Operation is AstElemMatchFilterOperation elemMatch &&
187+ elemMatch . Filter is AstFieldOperationFilter elemFilter &&
188+ elemFilter . Field . Path == "@<elem>" &&
189+ elemFilter . Operation is AstNotFilterOperation notOperation &&
190+ notOperation . Operation is AstRegexFilterOperation regexOperation )
191+ {
192+ elemField = elemFilter . Field ;
193+ regex = new BsonRegularExpression ( regexOperation . Pattern , regexOperation . Options ) ;
194+ return true ;
195+ }
196+
197+ elemField = null ;
198+ regex = null ;
199+ return false ;
200+ }
201+
202+ static bool IsFieldNotElemMatchEqValue ( AstFieldOperationFilter node , out BsonValue value )
203+ {
204+ // { field : { $not : { $elemMatch : { $eq : value } } } } where value is not regex
205+ if ( node . Operation is AstNotFilterOperation notFilterOperation &&
206+ notFilterOperation . Operation is AstElemMatchFilterOperation elemMatchOperation &&
207+ elemMatchOperation . Filter is AstFieldOperationFilter elemFilter &&
208+ elemFilter . Field . Path == "@<elem>" &&
209+ elemFilter . Operation is AstComparisonFilterOperation comparisonOperation &&
210+ comparisonOperation . Operator == AstComparisonFilterOperator . Eq &&
211+ comparisonOperation . Value . BsonType != BsonType . RegularExpression )
212+ {
213+ value = comparisonOperation . Value ;
214+ return true ;
215+ }
216+
217+ value = null ;
218+ return false ;
219+ }
220+
221+ static bool IsFieldNotElemMatchRegex ( AstFieldOperationFilter node , out BsonRegularExpression regex )
222+ {
223+ // { field : { $not : { $elemMatch : { $regex : "pattern", $options : "options" } } } }
224+ if ( node . Operation is AstNotFilterOperation notFilterOperation &&
225+ notFilterOperation . Operation is AstElemMatchFilterOperation elemMatchOperation &&
226+ elemMatchOperation . Filter is AstFieldOperationFilter elemFilter &&
227+ elemFilter . Field . Path == "@<elem>" &&
228+ elemFilter . Operation is AstRegexFilterOperation regexOperation )
229+ {
230+ regex = new BsonRegularExpression ( regexOperation . Pattern , regexOperation . Options ) ;
231+ return true ;
232+ }
233+
234+ regex = null ;
235+ return false ;
236+ }
237+ }
238+
40239 public override AstNode VisitGetFieldExpression ( AstGetFieldExpression node )
41240 {
42241 if ( TrySimplifyAsFieldPath ( node , out var simplified ) )
0 commit comments