1
1
using CouchDB . Driver . DTOs ;
2
- using CouchDB . Driver . ExpressionVisitors ;
2
+ using CouchDB . Driver . CompositeExpressionsEvaluator ;
3
3
using CouchDB . Driver . Helpers ;
4
4
using CouchDB . Driver . Settings ;
5
5
using CouchDB . Driver . Types ;
@@ -29,49 +29,51 @@ public CouchQueryProvider(IFlurlClient flurlClient, CouchSettings settings, stri
29
29
30
30
public override string GetQueryText ( Expression expression )
31
31
{
32
- return Translate ( expression ) ;
32
+ return Translate ( ref expression ) ;
33
33
}
34
34
35
- public override object Execute ( Expression e , bool completeResponse )
35
+ public override object Execute ( Expression expression , bool completeResponse )
36
36
{
37
- MethodInfo _filterMethodInfo = null ;
38
- Expression [ ] _filteringExpressions = Array . Empty < Expression > ( ) ;
39
- if ( e is MethodCallExpression m )
37
+ // Remove from the expressions tree all IQueryable methods not supported by CouchDB and put them into the list
38
+ var unsupportedMethodCallExpressions = new List < MethodCallExpression > ( ) ;
39
+ expression = RemoveUnsupportedMethodExpressions ( expression , out var hasUnsupportedMethods , unsupportedMethodCallExpressions ) ;
40
+
41
+ var body = Translate ( ref expression ) ;
42
+ Type elementType = TypeSystem . GetElementType ( expression . Type ) ;
43
+
44
+ // Create generic GetCouchList method and invoke it, sending the request to CouchDB
45
+ MethodInfo method = typeof ( CouchQueryProvider ) . GetMethod ( nameof ( CouchQueryProvider . GetCouchList ) ) ;
46
+ MethodInfo generic = method . MakeGenericMethod ( elementType ) ;
47
+ var result = generic . Invoke ( this , new [ ] { body } ) ;
48
+
49
+ // If no unsupported methods, return the result
50
+ if ( ! hasUnsupportedMethods )
40
51
{
41
- if (
42
- m . Method . Name == "First" ||
43
- m . Method . Name == "FirstOrDefault" ||
44
- m . Method . Name == "Last" ||
45
- m . Method . Name == "LastOrDefault" ||
46
- m . Method . Name == "Single" ||
47
- m . Method . Name == "SingleOrDefault" )
48
- {
49
- _filterMethodInfo = m . Method ;
50
- _filteringExpressions = m . Arguments . Skip ( 1 ) . ToArray ( ) ;
51
- e = m . Arguments [ 0 ] ;
52
- }
52
+ return result ;
53
53
}
54
54
55
- var body = Translate ( e ) ;
56
- Type elementType = TypeSystem . GetElementType ( e . Type ) ;
57
-
58
- MethodInfo method = typeof ( CouchQueryProvider ) . GetMethod ( nameof ( CouchQueryProvider . GetCouchListOrFiltered ) ) ;
59
- MethodInfo generic = method . MakeGenericMethod ( elementType ) ;
60
- var result = generic . Invoke ( this , new [ ] { body , ( object ) _filterMethodInfo , _filteringExpressions } ) ;
55
+ // For every unsupported method expression, execute it on the result
56
+ foreach ( MethodCallExpression inMemoryCall in unsupportedMethodCallExpressions )
57
+ {
58
+ result = InvokeUnsupportedMethodCallExpression ( result , inMemoryCall ) ;
59
+ }
61
60
return result ;
62
61
}
63
62
64
- private string Translate ( Expression e )
63
+ private string Translate ( ref Expression e )
65
64
{
66
- e = Evaluator . PartialEval ( e ) ;
67
- var whereVisitor = new WhereExpressionVisitor ( ) ;
65
+ e = Local . PartialEval ( e ) ;
66
+ var whereVisitor = new BoolMemberToConstantEvaluator ( ) ;
68
67
e = whereVisitor . Visit ( e ) ;
69
68
69
+ var pretranslator = new QueryPretranslator ( ) ;
70
+ e = pretranslator . Visit ( e ) ;
71
+
70
72
return new QueryTranslator ( _settings ) . Translate ( e ) ;
71
73
}
72
-
73
- public object GetCouchListOrFiltered < T > ( string body , MethodInfo filteringMethodInfo , Expression [ ] filteringExpressions )
74
- {
74
+
75
+ public object GetCouchList < T > ( string body )
76
+ {
75
77
FindResult < T > result = _flurlClient
76
78
. Request ( _connectionString )
77
79
. AppendPathSegments ( _db , "_find" )
@@ -80,49 +82,116 @@ public object GetCouchListOrFiltered<T>(string body, MethodInfo filteringMethodI
80
82
. SendRequest ( ) ;
81
83
82
84
var couchList = new CouchList < T > ( result . Docs . ToList ( ) , result . Bookmark , result . ExecutionStats ) ;
85
+ return couchList ;
86
+ }
83
87
84
- if ( filteringMethodInfo == null )
88
+ private Expression RemoveUnsupportedMethodExpressions ( Expression expression , out bool hasUnsupportedMethods , IList < MethodCallExpression > unsupportedMethodCallExpressions )
89
+ {
90
+ if ( unsupportedMethodCallExpressions == null )
85
91
{
86
- return couchList ;
92
+ throw new ArgumentNullException ( nameof ( unsupportedMethodCallExpressions ) ) ;
87
93
}
88
94
89
- var filteringMethods = typeof ( Enumerable ) . GetMethods ( )
90
- . Where ( m =>
91
- m . Name == filteringMethodInfo . Name &&
92
- m . GetParameters ( ) . Length - 1 == filteringExpressions . Length )
93
- . OrderBy ( m => m . GetParameters ( ) . Length ) . ToList ( ) ;
94
-
95
-
96
- var invokeParameter = new object [ filteringExpressions . Length + 1 ] ;
97
- invokeParameter [ 0 ] = couchList ;
98
-
99
- bool IsRightOverload ( MethodInfo m )
95
+ // Search for method calls to run in-memory,
96
+ // Once one is found all method calls after that must run in-memory.
97
+ // The expression to translate in JSON ends with the last not in-memory call.
98
+ bool IsUnsupportedMethodCallExpression ( Expression ex )
100
99
{
101
- ParameterInfo [ ] parameters = m . GetParameters ( ) ;
102
- for ( var i = 0 ; i < filteringExpressions . Length ; i ++ )
100
+ if ( ex is MethodCallExpression m )
103
101
{
104
- var lamdaExpression = filteringExpressions [ i ] as UnaryExpression ;
105
- if ( lamdaExpression == null )
102
+ Expression nextCall = m . Arguments [ 0 ] ;
103
+ // Check if the next expression is unsupported
104
+ var isUnsupported = IsUnsupportedMethodCallExpression ( nextCall ) ;
105
+ if ( isUnsupported )
106
106
{
107
- return false ;
107
+ unsupportedMethodCallExpressions . Add ( m ) ;
108
+ return isUnsupported ;
108
109
}
109
-
110
- if ( lamdaExpression . Operand . Type != parameters [ i + 1 ] . ParameterType )
110
+ // If the next call is supported and the current is in the composite list
111
+ if ( QueryTranslator . CompositeQueryableMethods . Contains ( m . Method . Name ) )
111
112
{
112
- return false ;
113
+ unsupportedMethodCallExpressions . Add ( m ) ;
114
+ return true ;
115
+ }
116
+ // If the next call is supported and the current is not in the supported list
117
+ if ( ! QueryTranslator . NativeQueryableMethods . Contains ( m . Method . Name ) )
118
+ {
119
+ unsupportedMethodCallExpressions . Add ( m ) ;
120
+ expression = nextCall ;
121
+ return true ;
113
122
}
114
- invokeParameter [ i + 1 ] = lamdaExpression . Operand ;
115
123
}
116
- return true ;
124
+ return false ;
117
125
}
118
126
119
- MethodInfo rightOverload = filteringMethods . Single ( IsRightOverload ) ;
127
+ hasUnsupportedMethods = IsUnsupportedMethodCallExpression ( expression ) ;
128
+ return expression ;
129
+ }
120
130
121
- MethodInfo enumerableGenericFilteringMethod = rightOverload . MakeGenericMethod ( typeof ( T ) ) ;
131
+ private object InvokeUnsupportedMethodCallExpression ( object result , MethodCallExpression methodCallExpression )
132
+ {
133
+ MethodInfo queryableMethodInfo = methodCallExpression . Method ;
134
+ Expression [ ] queryableMethodArguments = methodCallExpression . Arguments . ToArray ( ) ;
122
135
136
+ // Since Max and Min are not map 1 to 1 from Queryable to Enumerable
137
+ // they need to be handled differently
138
+ MethodInfo FindEnumerableMethod ( )
139
+ {
140
+ if ( queryableMethodInfo . Name == nameof ( Queryable . Max ) || queryableMethodInfo . Name == nameof ( Queryable . Min ) )
141
+ {
142
+ return FindEnumerableMinMax ( queryableMethodInfo ) ;
143
+ }
144
+ return typeof ( Enumerable ) . GetMethods ( ) . Single ( enumerableMethodInfo =>
145
+ {
146
+ return
147
+ queryableMethodInfo . Name == enumerableMethodInfo . Name &&
148
+ ReflectionComparator . IsCompatible ( queryableMethodInfo , enumerableMethodInfo ) ;
149
+ } ) ;
150
+ }
123
151
124
- var filtered = enumerableGenericFilteringMethod . Invoke ( null , invokeParameter ) ;
152
+ // Find the equivalent method in Enumerable
153
+ MethodInfo enumarableMethodInfo = FindEnumerableMethod ( ) ;
154
+
155
+ // Add the list as first parameter of the call
156
+ var invokeParameter = new List < object > { result } ;
157
+ // Convert everty other parameter expression to real values
158
+ IEnumerable < object > enumerableParameters = queryableMethodArguments . Skip ( 1 ) . Select ( GetArgumentValueFromExpression ) ;
159
+ // Add the other parameter to the complete list
160
+ invokeParameter . AddRange ( enumerableParameters ) ;
161
+
162
+ Type [ ] requestedGenericParameters = enumarableMethodInfo . GetGenericMethodDefinition ( ) . GetGenericArguments ( ) ;
163
+ Type [ ] genericParameters = queryableMethodInfo . GetGenericArguments ( ) ;
164
+ Type [ ] usableParameters = genericParameters . Take ( requestedGenericParameters . Length ) . ToArray ( ) ;
165
+ MethodInfo enumarableGenericMethod = enumarableMethodInfo . MakeGenericMethod ( usableParameters ) ;
166
+ var filtered = enumarableGenericMethod . Invoke ( null , invokeParameter . ToArray ( ) ) ;
125
167
return filtered ;
126
168
}
169
+
170
+ private object GetArgumentValueFromExpression ( Expression e )
171
+ {
172
+ if ( e is ConstantExpression c )
173
+ {
174
+ return c . Value ;
175
+ }
176
+ if ( e is UnaryExpression u && u . Operand is LambdaExpression l )
177
+ {
178
+ return l . Compile ( ) ;
179
+ }
180
+ throw new NotImplementedException ( $ "Expression of type { e . NodeType } not supported.") ;
181
+ }
182
+
183
+ private static MethodInfo FindEnumerableMinMax ( MethodInfo queryableMethodInfo )
184
+ {
185
+ Type [ ] genericParams = queryableMethodInfo . GetGenericArguments ( ) ;
186
+ MethodInfo finalMethodInfo = typeof ( Enumerable ) . GetMethods ( ) . Single ( enumerableMethodInfo =>
187
+ {
188
+ Type [ ] enumerableArguments = enumerableMethodInfo . GetGenericArguments ( ) ;
189
+ return
190
+ enumerableMethodInfo . Name == queryableMethodInfo . Name &&
191
+ enumerableArguments . Length == genericParams . Length - 1 &&
192
+ enumerableMethodInfo . ReturnType == genericParams [ 1 ] ;
193
+ } ) ;
194
+ return finalMethodInfo ;
195
+ }
127
196
}
128
197
}
0 commit comments