Datei: NDODLL/Linq/ExpressionTreeTransformer.cs

Last Commit (f96ae8c)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Collections.Generic;
25 using System.Collections;
26 using System.Text;
27 using System.Linq.Expressions;
28 using System.Linq;
29
30 namespace NDO.Linq
31 {
32 ····/// <summary>
33 ····/// This class transforms Linq queries to NDOql
34 ····/// </summary>
35 ····public class ExpressionTreeTransformer
36 ····{
37 ········class OperatorEntry
38 ········{
39 ············public OperatorEntry( ExpressionType exprType, string replacement, int prec )
40 ············{
41 ················this.ExpressionType = exprType;
42 ················this.Precedence = prec;
43 ················this.Replacement = replacement;
44 ············}
45 ············public ExpressionType ExpressionType;
46 ············public string Replacement;
47 ············public int Precedence;
48
49 ············public static bool IsComparison( ExpressionType expType )
50 ············{
51 ················return expType == ExpressionType.GreaterThanOrEqual || expType == ExpressionType.GreaterThan || expType == ExpressionType.LessThan || expType == ExpressionType.LessThanOrEqual || expType == ExpressionType.Equal || expType == ExpressionType.NotEqual;
52 ············}
53 ········}
54
55 ········static OperatorEntry[] operators =
56 ········{
57 ············new OperatorEntry(ExpressionType.Modulo, "%", 0),
58 ············new OperatorEntry(ExpressionType.Multiply, "*", 0),
59 ············new OperatorEntry(ExpressionType.Divide, "/", 0),
60 ············new OperatorEntry(ExpressionType.Add, "+", 1),
61 ············new OperatorEntry(ExpressionType.Subtract, "-", 1),
62 ············new OperatorEntry(ExpressionType.GreaterThanOrEqual, ">=", 2),
63 ············new OperatorEntry(ExpressionType.GreaterThan, ">", 2),
64 ············new OperatorEntry(ExpressionType.LessThanOrEqual, "<=", 2),
65 ············new OperatorEntry(ExpressionType.LessThan, "<", 2),
66 ············new OperatorEntry(ExpressionType.NotEqual, "<>", 2),
67 ············new OperatorEntry(ExpressionType.Equal, "=", 3),
68 ············new OperatorEntry(ExpressionType.And, "AND", 4),
69 ············new OperatorEntry(ExpressionType.AndAlso, "AND", 4),
70 ············new OperatorEntry(ExpressionType.Or, "OR", 5),
71 ············new OperatorEntry(ExpressionType.OrElse, "OR", 5),
72 ········};
73
74 ········LambdaExpression baseExpression;
75 ········Expression baseLeftSide;
76 ········StringBuilder sb;
77 ········List<object> parameters;
78 ········string baseParameterName;
79 ········int baseParameterLength;
80 ········Stack<Expression> expressionStack;
81
82 ········/// <summary>
83 ········/// Returns the generated parameters
84 ········/// </summary>
85 ········public IEnumerable<object> Parameters
86 ········{
87 ············get { return parameters; }
88 ········}
89
90 ········/// <summary>
91 ········/// Constructs an ExpressionTreeTransformer objct
92 ········/// </summary>
93 ········/// <param name="ex"></param>
94 ········public ExpressionTreeTransformer( LambdaExpression ex )
95 ········{
96 ············this.baseExpression = ex;
97 ········}
98
99 ········/// <summary>
100 ········/// Transforms the lambda expression passed to the constructor into an NDOql string.
101 ········/// </summary>
102 ········/// <returns></returns>
103 ········public string Transform()
104 ········{
105 ············baseParameterName = baseExpression.Parameters[0].Name;
106 ············baseLeftSide = baseExpression.Parameters[0];
107 ············baseParameterLength = baseParameterName.Length + 1;
108 ············this.expressionStack = new Stack<Expression>();
109 ············sb = new StringBuilder();
110 ············parameters = new List<object>();
111 ············Transform( baseExpression.Body );
112 ············return sb.ToString();
113 ········}
114
115 ········bool IsNull( Expression ex )
116 ········{
117 ············if (ex is ConstantExpression ce)
118 ············{
119 ················if (ce.Value == null)
120 ····················return true;
121 ············}
122
123 ············if (ex.NodeType == ExpressionType.MemberAccess)
124 ············{
125 ················var me = ex as MemberExpression;
126 ················if (me != null)
127 ················{
128 ····················if (me.Type == typeof( DateTime ))
129 ····················{
130 ························try
131 ························{
132 ····························var d = Expression.Lambda( ex ).Compile();
133 ····························var dt = (DateTime)d.DynamicInvoke();
134 ····························if (dt == DateTime.MinValue)
135 ································return true;
136 ························}
137 ························catch { }
138 ····················}
139 ····················else if (me.Type == typeof( Guid ))
140 ····················{
141 ························try
142 ························{
143 ····························var d = Expression.Lambda( ex ).Compile();
144 ····························var g = (Guid)d.DynamicInvoke();
145 ····························if (g == Guid.Empty)
146 ································return true;
147 ························}
148 ························catch { }
149 ····················}
150 ················}
151 ············}
152
153 ············return false;
154 ········}
155
156 ········void TransformBinaryOperator( ExpressionType exprType, ref Expression right )
157 ········{
158 ············sb.Append( ' ' );
159 ············if (IsNull( right ))
160 ············{
161 ················if (exprType == ExpressionType.NotEqual)
162 ················{
163 ····················sb.Append( "IS NOT" );
164 ····················right = Expression.Constant(null);
165 ················}
166 ················else if (exprType == ExpressionType.Equal)
167 ················{
168 ····················sb.Append( "IS" );
169 ····················right = Expression.Constant( null );
170 ················}
171 ················else
172 ················{
173 ····················OperatorEntry oe = FindOperator( exprType );
174 ····················sb.Append( oe.Replacement );
175 ················}
176 ············}
177 ············else
178 ············{
179 ················OperatorEntry oe = FindOperator( exprType );
180 ················sb.Append( oe.Replacement );
181 ············}
182 ············sb.Append( ' ' );
183 ········}
184
185 ········OperatorEntry FindOperator( ExpressionType exprType )
186 ········{
187 ············foreach (OperatorEntry oe in operators)
188 ················if (oe.ExpressionType == exprType)
189 ····················return oe;
190 ············throw new Exception( "ExpressionTreeTransformer: Unsupported operator: " + exprType );
191 ········}
192
193 ········int GetPrecedence( ExpressionType exprType )
194 ········{
195 ············OperatorEntry oe = FindOperator(exprType);
196 ············return oe.Precedence;
197 ········}
198
199 ········void AddParameter( object value )
200 ········{
201 ············var ix = parameters.FindIndex(p => p.Equals(value));
202 ············if (ix > -1)
203 ············{
204 ················sb.Append( '{' );
205 ················sb.Append( ix.ToString() );
206 ················sb.Append( '}' );
207 ············}
208 ············else
209 ············{
210 ················sb.Append( '{' );
211 ················sb.Append( parameters.Count.ToString() );
212 ················sb.Append( '}' );
213 ················parameters.Add( value );
214 ············}
215 ········}
216
217 ········BinaryExpression FlipExpression( BinaryExpression binex )
218 ········{
219 ············if (binex.NodeType == ExpressionType.GreaterThan)
220 ················return BinaryExpression.LessThan( binex.Right, binex.Left );
221 ············if (binex.NodeType == ExpressionType.GreaterThanOrEqual)
222 ················return BinaryExpression.LessThanOrEqual( binex.Right, binex.Left );
223 ············if (binex.NodeType == ExpressionType.LessThan)
224 ················return BinaryExpression.GreaterThan( binex.Right, binex.Left );
225 ············if (binex.NodeType == ExpressionType.LessThanOrEqual)
226 ················return BinaryExpression.GreaterThanOrEqual( binex.Right, binex.Left );
227 ············return Expression.MakeBinary( binex.NodeType, binex.Right, binex.Left );
228 ········}
229
230 ········void FlipArguments( List<Expression> arguments )
231 ········{
232 ············var temp = arguments[0];
233 ············arguments[0] = arguments[1];
234 ············arguments[1] = temp;
235 ········}
236
237 ········bool IsReversedExpression( List<Expression> arguments )
238 ········{
239 ············return arguments.Count == 2 && arguments[0] is ConstantExpression && !( arguments[1] is ConstantExpression );
240 ········}
241
242 ········void TransformEquality(Expression ex1, Expression ex2)
243 ········{
244 ············Transform( ex1 );
245 ············TransformBinaryOperator( ExpressionType.Equal, ref ex2 );
246 ············Transform( ex2 );
247 ········}
248
249 ········bool IsPropertyOfBaseExpression( Expression ex )
250 ········{
251 ············if (ex is MemberExpression pe)
252 ············{
253 ················return pe.Expression == baseLeftSide;
254 ············}
255
256 ············return false;
257 ········}
258
259 ········void Transform( Expression ex )
260 ········{
261 ············expressionStack.Push( ex );··// This helps determining parent expressions
262 ············try
263 ············{
264 ················if (ex == this.baseLeftSide)
265 ····················return;
266 ··················//-------
267
268 ················string exStr = ex.ToString();
269 ················if (exStr == "null")
270 ················{
271 ····················sb.Append( "NULL" );
272 ····················return;
273 ··················//-------
274 ················}
275
276 ················if (ex.NodeType == ExpressionType.MemberAccess )
277 ················{
278 ····················// We try to compile the expression.
279 ····················// If it can be compiled, we have a value,
280 ····················// which should be added as a parameter.
281 ····················// We are safe to examine MemberAccess nodes only,
282 ····················// because local variables will be wrapped in
283 ····················// display classes and accessed as members of
284 ····················// these classes. All other cases are anyway
285 ····················// members of arbitrary types (like String.Empty).
286 ····················try
287 ····················{
288 ························var d = Expression.Lambda( ex ).Compile();
289 ························AddParameter( d.DynamicInvoke() );
290 ························return;
291 ······················//-------
292 ····················}
293 ····················catch
294 ····················{··// Can't be compiled, so pass on.
295 ····················}
296 ················}
297
298 ················BinaryExpression binex = ex as BinaryExpression;
299 ················if (binex != null)
300 ················{
301 ····················Expression left = binex.Left;
302 ····················Expression right = binex.Right;
303 ····················if (left.NodeType == ExpressionType.Convert)
304 ························left = ( (UnaryExpression) left ).Operand;
305 ····················if (right.NodeType == ExpressionType.Convert)
306 ························right = ( (UnaryExpression) right ).Operand;
307
308 ····················if (!IsPropertyOfBaseExpression( binex.Left ) && IsPropertyOfBaseExpression( binex.Right ))
309 ····················{
310 ························Transform( FlipExpression( binex ) );
311 ························return;
312 ························//-------
313 ····················}
314
315 ····················int ownPrecedence = GetPrecedence(binex.NodeType);
316 ····················int childPrecedence = 0;
317 ····················BinaryExpression childBinex = binex.Left as BinaryExpression;
318 ····················bool leftbracket = false;
319 ····················if (childBinex != null)
320 ····················{
321 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
322 ························leftbracket = childPrecedence > ownPrecedence;
323 ····················}
324 ····················childBinex = binex.Right as BinaryExpression;
325 ····················bool rightbracket = false;
326 ····················if (childBinex != null)
327 ····················{
328 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
329 ························rightbracket = childPrecedence > ownPrecedence;
330 ····················}
331 ····················if (leftbracket)
332 ························sb.Append( '(' );
333 ····················Transform( left );
334 ····················if (leftbracket)
335 ························sb.Append( ')' );
336 ····················TransformBinaryOperator( ex.NodeType, ref right );
337 ····················if (rightbracket)
338 ························sb.Append( '(' );
339 ····················Transform( right );
340 ····················if (rightbracket)
341 ························sb.Append( ')' );
342 ····················return;
343 ····················//-------
344 ················}
345
346 ················MethodCallExpression mcex = ex as MethodCallExpression;
347 ················if (mcex != null)
348 ················{
349 ····················var arguments = new List<Expression>(mcex.Arguments);
350 ····················string mname = mcex.Method.Name;
351 ····················if (mname == "op_Equality")
352 ····················{
353 ························if (IsReversedExpression( arguments ))
354 ························{
355 ····························FlipArguments( arguments );
356 ························}
357 ························TransformEquality( arguments[0], arguments[1] );
358 ····················}
359 ····················else if (mname == "Equals")
360 ····················{
361 ························TransformEquality( mcex.Object, arguments[0] );
362 ····················}
363 ····················else if (mname == "Like")
364 ····················{
365 ························if (IsReversedExpression( arguments ))
366 ························{
367 ····························FlipArguments( arguments );
368 ························}
369 ························Transform( arguments[0] );
370 ························sb.Append( " LIKE " );
371 ························Transform( arguments[1] );
372 ····················}
373 ····················else if (mname == "GreaterEqual")
374 ····················{
375 ························string op = " >= ";
376 ························if (IsReversedExpression( arguments ))
377 ························{
378 ····························FlipArguments( arguments );
379 ····························op = " <= ";
380 ························}
381 ························Transform( arguments[0] );
382 ························sb.Append( op );
383 ························Transform( arguments[1] );
384 ····················}
385 ····················else if (mname == "LowerEqual")
386 ····················{
387 ························string op = " <= ";
388 ························if (IsReversedExpression( arguments ))
389 ························{
390 ····························FlipArguments( arguments );
391 ····························op = " >= ";
392 ························}
393 ························Transform( arguments[0] );
394 ························sb.Append( op );
395 ························Transform( arguments[1] );
396 ····················}
397 ····················else if (mname == "GreaterThan")
398 ····················{
399 ························string op = " > ";
400 ························if (IsReversedExpression( arguments ))
401 ························{
402 ····························FlipArguments( arguments );
403 ····························op = " < ";
404 ························}
405 ························Transform( arguments[0] );
406 ························sb.Append( op );
407 ························Transform( arguments[1] );
408 ····················}
409 ····················else if (mname == "LowerThan")
410 ····················{
411 ························string op = " < ";
412 ························if (IsReversedExpression( arguments ))
413 ························{
414 ····························FlipArguments( arguments );
415 ····························op = " > ";
416 ························}
417 ························Transform( arguments[0] );
418 ························sb.Append( op );
419 ························Transform( arguments[1] );
420 ····················}
421 ····················else if (mname == "Between")
422 ····················{
423 ························Transform( mcex.Arguments[0] );
424 ························sb.Append( " BETWEEN " );
425 ························Transform( mcex.Arguments[1] );
426 ························sb.Append( " AND " );
427 ························Transform( mcex.Arguments[2] );
428 ····················}
429 ····················else if (mname == "get_Item")
430 ····················{
431 ························string argStr = mcex.Arguments[0].ToString();
432 ························if (argStr == "Any.Index")
433 ························{
434 ····························Transform( mcex.Object );
435 ····························return;
436 ····························//-------
437 ························}
438 ························Transform( mcex.Object );
439 ························if (mcex.Object.Type.FullName == "NDO.ObjectId")
440 ························{
441 ····························TransformOidIndex( mcex.Arguments[0] );
442 ····························return;
443 ····························//-------
444 ························}
445 ························sb.Append( '(' );
446 ························Transform( mcex.Arguments[0] );
447 ························sb.Append( ')' );
448 ····················}
449 ····················else if (mname == "ElementAt")
450 ····················{
451 ························string argStr = mcex.Arguments[1].ToString();
452 ························if (argStr == "Any.Index" || "Index.Any" == argStr)
453 ························{
454 ····························Transform( mcex.Arguments[0] );
455 ····························return;
456 ····························//-------
457 ························}
458 ························Transform( mcex.Arguments[0] );
459 ························sb.Append( '(' );
460 ························Transform( mcex.Arguments[1] );
461 ························sb.Append( ')' );
462 ····················}
463 ····················else if (mname == "Any")
464 ····················{
465 ························Transform( mcex.Arguments[0] );
466 ························var exprx = mcex.Arguments[1];
467 ························if (sb[sb.Length - 1] != '.')
468 ····························sb.Append( '.' );
469 ························Transform( ( (LambdaExpression) mcex.Arguments[1] ).Body );
470 ····················}
471 ····················else if (mname == "In")
472 ····················{
473 ························var arg = mcex.Arguments[1];
474 ························IEnumerable list = (IEnumerable)(Expression.Lambda(arg).Compile().DynamicInvoke());
475 ························Transform( mcex.Arguments[0] );
476 ························sb.Append( " IN (" );
477 ························var en = list.GetEnumerator();
478 ························en.MoveNext();
479 ························bool doQuote = en.Current is string || en.Current is Guid || en.Current is DateTime;
480 foreach ( object obj in list)
 
 
 
481 ························{
 
 
482 ····························if (doQuote)
483 ································sb.Append( '\'' );
484 ····························sb.Append( obj );
485 ····························if (doQuote)
486 ································sb.Append( '\'' );
487 ····························sb.Append( ',' );
488 ························}
489 ························sb.Length -= 1;
490 ························sb.Append( ')' );
491 ····················}
492 ····················else if (mname == "Oid")
493 ····················{
494 ························var sbLength = sb.Length;
495 ························Transform( mcex.Arguments[0] );
496 ························if (sb.Length > sbLength)
497 ····························sb.Append( ".oid" );
498 ························else
499 ····························sb.Append( "oid" );
500 ························if (mcex.Arguments.Count > 1)
501 ························{
502 ····························TransformOidIndex( mcex.Arguments[1] );
503 ························}
504 ····················}
505 ····················else··// Assume, we have a server function here
506 ····················{
507 ························var method = mcex.Method;
508 ························var attrs = method.GetCustomAttributes( typeof( ServerFunctionAttribute ), true );
509 ························if (attrs.Length > 0)
510 ····························mname = ((ServerFunctionAttribute) attrs[0]).Name;
511 ························sb.Append( mname );
512 ························sb.Append( '(' );
513 ························var end = arguments.Count - 1;
514 ························var i = 0;
515 ························foreach (var arg in arguments)
516 ························{
517 ····························Transform( arg );
518 ····························if (i < end)
519 ································sb.Append( ", " );
520 ····························i++;
521 ························}
522 ························sb.Append( ')' );
523 ····················}
524 ····················return;
525 ····················//-------
526 ················}
527
528 ················if (ex.NodeType == ExpressionType.MemberAccess)
529 ················{
530 ····················MemberExpression memberex = (MemberExpression) ex;
531 ····················if (memberex.Member.Name == "NDOObjectId")
532 ····················{
533 ························if (memberex.Expression.ToString() != this.baseParameterName)
534 ························{
535 ····························Transform( memberex.Expression );
536 ····························if (sb[sb.Length - 1] != '.')
537 ································sb.Append( '.' );
538 ························}
539 ························sb.Append( "oid" );
540 ························return;
541 ····················}
542 ····················if (ex.Type == typeof( bool ))
543 ····················{
544 ························var top = expressionStack.Pop();
545 ························bool transformIt = expressionStack.Count == 0;··// The boolean expression is the top
546 ························if (expressionStack.Count > 0)
547 ························{
548 ····························var parent = expressionStack.Peek();
549 ····························if (parent.NodeType != ExpressionType.Equal && parent.NodeType != ExpressionType.NotEqual)
550 ····························{
551 ································// A Boolean Expression, which is not part of an Equals or NotEquals expression,
552 ································// must be unary. Since Sql Server doesn't support unary boolean expressions, we add an Equals Expression which compares with 1.
553 ································// Fortunately this works with other databases, too.
554 ································transformIt = true;
555 ····························}
556 ························}
557 ························if (transformIt)
558 ························{
559 ····························sb.Append( memberex.Member.Name );
560 ····························sb.Append( " = 1" );
561 ························}
562 ························expressionStack.Push( top );
563 ························if (transformIt)
564 ····························return;
565 ························//-------
566 ····················}
567 ····················if (memberex.Expression != baseLeftSide)
568 ····················{
569 ························// This happens while navigating through relations.
570 ························Transform( memberex.Expression );
571 ························if (sb[sb.Length - 1] != '.')
572 ····························sb.Append( '.' );
573 ····················}
574 ····················sb.Append( memberex.Member.Name );
575 ····················return;
576 ····················//-------
577 ················}
578
579 ················else if (ex.NodeType == ExpressionType.Constant)
580 ················{
581 ····················ConstantExpression constEx = (ConstantExpression) ex;
582 ····················AddParameter( constEx.Value );
583 ····················return;
584 ····················//-------
585 ················}
586
587 ················else if (ex.NodeType == ExpressionType.Convert)
588 ················{
589 ····················Transform( ( (UnaryExpression) ex ).Operand );
590 ····················return;
591 ····················//-------
592 ················}
593
594 ················else if (ex.NodeType == ExpressionType.Not)
595 ················{
596 ····················sb.Append( " NOT " );
597 ····················Expression inner = ((UnaryExpression)ex).Operand;
598 ····················var binary = (inner is BinaryExpression);
599 ····················if (binary)
600 ························sb.Append( '(' );
601 ····················Transform( inner );
602 ····················if (binary)
603 ························sb.Append( ')' );
604 ····················return;
605 ····················//-------
606 ················}
607 ············}
608 ············finally
609 ············{················
610 ················expressionStack.Pop();
611 ············}
612 ········}
613
614 ········private void TransformEquality( List<Expression> arguments )
615 ········{
616 ············Transform( arguments[0] );
617 ············sb.Append( " = " );
618 ············Transform( arguments[1] );
619 ········}
620
621 ········private void TransformOidIndex( Expression ex )
622 ········{
623 ············var ce = ex as ConstantExpression;
624 ············if (ce == null)
625 ················throw new Exception( "Oid index expression must be a ConstantExpression" );
626 ············if (!( ce.Value is System.Int32 ))
627 ················throw new Exception( "Oid index expression must be an Int32" );
628
629 ············if ((int) ce.Value != 0)
630 ············{
631 ················sb.Append( '(' );
632 ················sb.Append( ce.Value );
633 ················sb.Append( ')' );
634 ············}
635 ········}
636 ····}
637 }
New Commit (fe801ed)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Collections.Generic;
25 using System.Collections;
26 using System.Text;
27 using System.Linq.Expressions;
28 using System.Linq;
29
30 namespace NDO.Linq
31 {
32 ····/// <summary>
33 ····/// This class transforms Linq queries to NDOql
34 ····/// </summary>
35 ····public class ExpressionTreeTransformer
36 ····{
37 ········class OperatorEntry
38 ········{
39 ············public OperatorEntry( ExpressionType exprType, string replacement, int prec )
40 ············{
41 ················this.ExpressionType = exprType;
42 ················this.Precedence = prec;
43 ················this.Replacement = replacement;
44 ············}
45 ············public ExpressionType ExpressionType;
46 ············public string Replacement;
47 ············public int Precedence;
48
49 ············public static bool IsComparison( ExpressionType expType )
50 ············{
51 ················return expType == ExpressionType.GreaterThanOrEqual || expType == ExpressionType.GreaterThan || expType == ExpressionType.LessThan || expType == ExpressionType.LessThanOrEqual || expType == ExpressionType.Equal || expType == ExpressionType.NotEqual;
52 ············}
53 ········}
54
55 ········static OperatorEntry[] operators =
56 ········{
57 ············new OperatorEntry(ExpressionType.Modulo, "%", 0),
58 ············new OperatorEntry(ExpressionType.Multiply, "*", 0),
59 ············new OperatorEntry(ExpressionType.Divide, "/", 0),
60 ············new OperatorEntry(ExpressionType.Add, "+", 1),
61 ············new OperatorEntry(ExpressionType.Subtract, "-", 1),
62 ············new OperatorEntry(ExpressionType.GreaterThanOrEqual, ">=", 2),
63 ············new OperatorEntry(ExpressionType.GreaterThan, ">", 2),
64 ············new OperatorEntry(ExpressionType.LessThanOrEqual, "<=", 2),
65 ············new OperatorEntry(ExpressionType.LessThan, "<", 2),
66 ············new OperatorEntry(ExpressionType.NotEqual, "<>", 2),
67 ············new OperatorEntry(ExpressionType.Equal, "=", 3),
68 ············new OperatorEntry(ExpressionType.And, "AND", 4),
69 ············new OperatorEntry(ExpressionType.AndAlso, "AND", 4),
70 ············new OperatorEntry(ExpressionType.Or, "OR", 5),
71 ············new OperatorEntry(ExpressionType.OrElse, "OR", 5),
72 ········};
73
74 ········LambdaExpression baseExpression;
75 ········Expression baseLeftSide;
76 ········StringBuilder sb;
77 ········List<object> parameters;
78 ········string baseParameterName;
79 ········int baseParameterLength;
80 ········Stack<Expression> expressionStack;
81
82 ········/// <summary>
83 ········/// Returns the generated parameters
84 ········/// </summary>
85 ········public IEnumerable<object> Parameters
86 ········{
87 ············get { return parameters; }
88 ········}
89
90 ········/// <summary>
91 ········/// Constructs an ExpressionTreeTransformer objct
92 ········/// </summary>
93 ········/// <param name="ex"></param>
94 ········public ExpressionTreeTransformer( LambdaExpression ex )
95 ········{
96 ············this.baseExpression = ex;
97 ········}
98
99 ········/// <summary>
100 ········/// Transforms the lambda expression passed to the constructor into an NDOql string.
101 ········/// </summary>
102 ········/// <returns></returns>
103 ········public string Transform()
104 ········{
105 ············baseParameterName = baseExpression.Parameters[0].Name;
106 ············baseLeftSide = baseExpression.Parameters[0];
107 ············baseParameterLength = baseParameterName.Length + 1;
108 ············this.expressionStack = new Stack<Expression>();
109 ············sb = new StringBuilder();
110 ············parameters = new List<object>();
111 ············Transform( baseExpression.Body );
112 ············return sb.ToString();
113 ········}
114
115 ········bool IsNull( Expression ex )
116 ········{
117 ············if (ex is ConstantExpression ce)
118 ············{
119 ················if (ce.Value == null)
120 ····················return true;
121 ············}
122
123 ············if (ex.NodeType == ExpressionType.MemberAccess)
124 ············{
125 ················var me = ex as MemberExpression;
126 ················if (me != null)
127 ················{
128 ····················if (me.Type == typeof( DateTime ))
129 ····················{
130 ························try
131 ························{
132 ····························var d = Expression.Lambda( ex ).Compile();
133 ····························var dt = (DateTime)d.DynamicInvoke();
134 ····························if (dt == DateTime.MinValue)
135 ································return true;
136 ························}
137 ························catch { }
138 ····················}
139 ····················else if (me.Type == typeof( Guid ))
140 ····················{
141 ························try
142 ························{
143 ····························var d = Expression.Lambda( ex ).Compile();
144 ····························var g = (Guid)d.DynamicInvoke();
145 ····························if (g == Guid.Empty)
146 ································return true;
147 ························}
148 ························catch { }
149 ····················}
150 ················}
151 ············}
152
153 ············return false;
154 ········}
155
156 ········void TransformBinaryOperator( ExpressionType exprType, ref Expression right )
157 ········{
158 ············sb.Append( ' ' );
159 ············if (IsNull( right ))
160 ············{
161 ················if (exprType == ExpressionType.NotEqual)
162 ················{
163 ····················sb.Append( "IS NOT" );
164 ····················right = Expression.Constant(null);
165 ················}
166 ················else if (exprType == ExpressionType.Equal)
167 ················{
168 ····················sb.Append( "IS" );
169 ····················right = Expression.Constant( null );
170 ················}
171 ················else
172 ················{
173 ····················OperatorEntry oe = FindOperator( exprType );
174 ····················sb.Append( oe.Replacement );
175 ················}
176 ············}
177 ············else
178 ············{
179 ················OperatorEntry oe = FindOperator( exprType );
180 ················sb.Append( oe.Replacement );
181 ············}
182 ············sb.Append( ' ' );
183 ········}
184
185 ········OperatorEntry FindOperator( ExpressionType exprType )
186 ········{
187 ············foreach (OperatorEntry oe in operators)
188 ················if (oe.ExpressionType == exprType)
189 ····················return oe;
190 ············throw new Exception( "ExpressionTreeTransformer: Unsupported operator: " + exprType );
191 ········}
192
193 ········int GetPrecedence( ExpressionType exprType )
194 ········{
195 ············OperatorEntry oe = FindOperator(exprType);
196 ············return oe.Precedence;
197 ········}
198
199 ········void AddParameter( object value )
200 ········{
201 ············var ix = parameters.FindIndex(p => p.Equals(value));
202 ············if (ix > -1)
203 ············{
204 ················sb.Append( '{' );
205 ················sb.Append( ix.ToString() );
206 ················sb.Append( '}' );
207 ············}
208 ············else
209 ············{
210 ················sb.Append( '{' );
211 ················sb.Append( parameters.Count.ToString() );
212 ················sb.Append( '}' );
213 ················parameters.Add( value );
214 ············}
215 ········}
216
217 ········BinaryExpression FlipExpression( BinaryExpression binex )
218 ········{
219 ············if (binex.NodeType == ExpressionType.GreaterThan)
220 ················return BinaryExpression.LessThan( binex.Right, binex.Left );
221 ············if (binex.NodeType == ExpressionType.GreaterThanOrEqual)
222 ················return BinaryExpression.LessThanOrEqual( binex.Right, binex.Left );
223 ············if (binex.NodeType == ExpressionType.LessThan)
224 ················return BinaryExpression.GreaterThan( binex.Right, binex.Left );
225 ············if (binex.NodeType == ExpressionType.LessThanOrEqual)
226 ················return BinaryExpression.GreaterThanOrEqual( binex.Right, binex.Left );
227 ············return Expression.MakeBinary( binex.NodeType, binex.Right, binex.Left );
228 ········}
229
230 ········void FlipArguments( List<Expression> arguments )
231 ········{
232 ············var temp = arguments[0];
233 ············arguments[0] = arguments[1];
234 ············arguments[1] = temp;
235 ········}
236
237 ········bool IsReversedExpression( List<Expression> arguments )
238 ········{
239 ············return arguments.Count == 2 && arguments[0] is ConstantExpression && !( arguments[1] is ConstantExpression );
240 ········}
241
242 ········void TransformEquality(Expression ex1, Expression ex2)
243 ········{
244 ············Transform( ex1 );
245 ············TransformBinaryOperator( ExpressionType.Equal, ref ex2 );
246 ············Transform( ex2 );
247 ········}
248
249 ········bool IsPropertyOfBaseExpression( Expression ex )
250 ········{
251 ············if (ex is MemberExpression pe)
252 ············{
253 ················return pe.Expression == baseLeftSide;
254 ············}
255
256 ············return false;
257 ········}
258
259 ········void Transform( Expression ex )
260 ········{
261 ············expressionStack.Push( ex );··// This helps determining parent expressions
262 ············try
263 ············{
264 ················if (ex == this.baseLeftSide)
265 ····················return;
266 ··················//-------
267
268 ················string exStr = ex.ToString();
269 ················if (exStr == "null")
270 ················{
271 ····················sb.Append( "NULL" );
272 ····················return;
273 ··················//-------
274 ················}
275
276 ················if (ex.NodeType == ExpressionType.MemberAccess )
277 ················{
278 ····················// We try to compile the expression.
279 ····················// If it can be compiled, we have a value,
280 ····················// which should be added as a parameter.
281 ····················// We are safe to examine MemberAccess nodes only,
282 ····················// because local variables will be wrapped in
283 ····················// display classes and accessed as members of
284 ····················// these classes. All other cases are anyway
285 ····················// members of arbitrary types (like String.Empty).
286 ····················try
287 ····················{
288 ························var d = Expression.Lambda( ex ).Compile();
289 ························AddParameter( d.DynamicInvoke() );
290 ························return;
291 ······················//-------
292 ····················}
293 ····················catch
294 ····················{··// Can't be compiled, so pass on.
295 ····················}
296 ················}
297
298 ················BinaryExpression binex = ex as BinaryExpression;
299 ················if (binex != null)
300 ················{
301 ····················Expression left = binex.Left;
302 ····················Expression right = binex.Right;
303 ····················if (left.NodeType == ExpressionType.Convert)
304 ························left = ( (UnaryExpression) left ).Operand;
305 ····················if (right.NodeType == ExpressionType.Convert)
306 ························right = ( (UnaryExpression) right ).Operand;
307
308 ····················if (!IsPropertyOfBaseExpression( binex.Left ) && IsPropertyOfBaseExpression( binex.Right ))
309 ····················{
310 ························Transform( FlipExpression( binex ) );
311 ························return;
312 ························//-------
313 ····················}
314
315 ····················int ownPrecedence = GetPrecedence(binex.NodeType);
316 ····················int childPrecedence = 0;
317 ····················BinaryExpression childBinex = binex.Left as BinaryExpression;
318 ····················bool leftbracket = false;
319 ····················if (childBinex != null)
320 ····················{
321 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
322 ························leftbracket = childPrecedence > ownPrecedence;
323 ····················}
324 ····················childBinex = binex.Right as BinaryExpression;
325 ····················bool rightbracket = false;
326 ····················if (childBinex != null)
327 ····················{
328 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
329 ························rightbracket = childPrecedence > ownPrecedence;
330 ····················}
331 ····················if (leftbracket)
332 ························sb.Append( '(' );
333 ····················Transform( left );
334 ····················if (leftbracket)
335 ························sb.Append( ')' );
336 ····················TransformBinaryOperator( ex.NodeType, ref right );
337 ····················if (rightbracket)
338 ························sb.Append( '(' );
339 ····················Transform( right );
340 ····················if (rightbracket)
341 ························sb.Append( ')' );
342 ····················return;
343 ····················//-------
344 ················}
345
346 ················MethodCallExpression mcex = ex as MethodCallExpression;
347 ················if (mcex != null)
348 ················{
349 ····················var arguments = new List<Expression>(mcex.Arguments);
350 ····················string mname = mcex.Method.Name;
351 ····················if (mname == "op_Equality")
352 ····················{
353 ························if (IsReversedExpression( arguments ))
354 ························{
355 ····························FlipArguments( arguments );
356 ························}
357 ························TransformEquality( arguments[0], arguments[1] );
358 ····················}
359 ····················else if (mname == "Equals")
360 ····················{
361 ························TransformEquality( mcex.Object, arguments[0] );
362 ····················}
363 ····················else if (mname == "Like")
364 ····················{
365 ························if (IsReversedExpression( arguments ))
366 ························{
367 ····························FlipArguments( arguments );
368 ························}
369 ························Transform( arguments[0] );
370 ························sb.Append( " LIKE " );
371 ························Transform( arguments[1] );
372 ····················}
373 ····················else if (mname == "GreaterEqual")
374 ····················{
375 ························string op = " >= ";
376 ························if (IsReversedExpression( arguments ))
377 ························{
378 ····························FlipArguments( arguments );
379 ····························op = " <= ";
380 ························}
381 ························Transform( arguments[0] );
382 ························sb.Append( op );
383 ························Transform( arguments[1] );
384 ····················}
385 ····················else if (mname == "LowerEqual")
386 ····················{
387 ························string op = " <= ";
388 ························if (IsReversedExpression( arguments ))
389 ························{
390 ····························FlipArguments( arguments );
391 ····························op = " >= ";
392 ························}
393 ························Transform( arguments[0] );
394 ························sb.Append( op );
395 ························Transform( arguments[1] );
396 ····················}
397 ····················else if (mname == "GreaterThan")
398 ····················{
399 ························string op = " > ";
400 ························if (IsReversedExpression( arguments ))
401 ························{
402 ····························FlipArguments( arguments );
403 ····························op = " < ";
404 ························}
405 ························Transform( arguments[0] );
406 ························sb.Append( op );
407 ························Transform( arguments[1] );
408 ····················}
409 ····················else if (mname == "LowerThan")
410 ····················{
411 ························string op = " < ";
412 ························if (IsReversedExpression( arguments ))
413 ························{
414 ····························FlipArguments( arguments );
415 ····························op = " > ";
416 ························}
417 ························Transform( arguments[0] );
418 ························sb.Append( op );
419 ························Transform( arguments[1] );
420 ····················}
421 ····················else if (mname == "Between")
422 ····················{
423 ························Transform( mcex.Arguments[0] );
424 ························sb.Append( " BETWEEN " );
425 ························Transform( mcex.Arguments[1] );
426 ························sb.Append( " AND " );
427 ························Transform( mcex.Arguments[2] );
428 ····················}
429 ····················else if (mname == "get_Item")
430 ····················{
431 ························string argStr = mcex.Arguments[0].ToString();
432 ························if (argStr == "Any.Index")
433 ························{
434 ····························Transform( mcex.Object );
435 ····························return;
436 ····························//-------
437 ························}
438 ························Transform( mcex.Object );
439 ························if (mcex.Object.Type.FullName == "NDO.ObjectId")
440 ························{
441 ····························TransformOidIndex( mcex.Arguments[0] );
442 ····························return;
443 ····························//-------
444 ························}
445 ························sb.Append( '(' );
446 ························Transform( mcex.Arguments[0] );
447 ························sb.Append( ')' );
448 ····················}
449 ····················else if (mname == "ElementAt")
450 ····················{
451 ························string argStr = mcex.Arguments[1].ToString();
452 ························if (argStr == "Any.Index" || "Index.Any" == argStr)
453 ························{
454 ····························Transform( mcex.Arguments[0] );
455 ····························return;
456 ····························//-------
457 ························}
458 ························Transform( mcex.Arguments[0] );
459 ························sb.Append( '(' );
460 ························Transform( mcex.Arguments[1] );
461 ························sb.Append( ')' );
462 ····················}
463 ····················else if (mname == "Any")
464 ····················{
465 ························Transform( mcex.Arguments[0] );
466 ························var exprx = mcex.Arguments[1];
467 ························if (sb[sb.Length - 1] != '.')
468 ····························sb.Append( '.' );
469 ························Transform( ( (LambdaExpression) mcex.Arguments[1] ).Body );
470 ····················}
471 ····················else if (mname == "In")
472 ····················{
473 ························var arg = mcex.Arguments[1];
474 ························IEnumerable list = (IEnumerable)(Expression.Lambda(arg).Compile().DynamicInvoke());
475 ························Transform( mcex.Arguments[0] );
476 ························sb.Append( " IN (" );
477 ························var en = list.GetEnumerator();
478 ························en.MoveNext();
479 ························bool doQuote = en.Current is string || en.Current is Guid || en.Current is DateTime;
480 foreach ( object item in list)
481 ························{
482 ····························var obj = item;
483 ····························if (obj is string s)
484 ····························{
485 ································obj = s.Replace( "'", "''" );
486 ····························}
487 ····························if (doQuote)
488 ································sb.Append( '\'' );
489 ····························sb.Append( obj );
490 ····························if (doQuote)
491 ································sb.Append( '\'' );
492 ····························sb.Append( ',' );
493 ························}
494 ························sb.Length -= 1;
495 ························sb.Append( ')' );
496 ····················}
497 ····················else if (mname == "Oid")
498 ····················{
499 ························var sbLength = sb.Length;
500 ························Transform( mcex.Arguments[0] );
501 ························if (sb.Length > sbLength)
502 ····························sb.Append( ".oid" );
503 ························else
504 ····························sb.Append( "oid" );
505 ························if (mcex.Arguments.Count > 1)
506 ························{
507 ····························TransformOidIndex( mcex.Arguments[1] );
508 ························}
509 ····················}
510 ····················else··// Assume, we have a server function here
511 ····················{
512 ························var method = mcex.Method;
513 ························var attrs = method.GetCustomAttributes( typeof( ServerFunctionAttribute ), true );
514 ························if (attrs.Length > 0)
515 ····························mname = ((ServerFunctionAttribute) attrs[0]).Name;
516 ························sb.Append( mname );
517 ························sb.Append( '(' );
518 ························var end = arguments.Count - 1;
519 ························var i = 0;
520 ························foreach (var arg in arguments)
521 ························{
522 ····························Transform( arg );
523 ····························if (i < end)
524 ································sb.Append( ", " );
525 ····························i++;
526 ························}
527 ························sb.Append( ')' );
528 ····················}
529 ····················return;
530 ····················//-------
531 ················}
532
533 ················if (ex.NodeType == ExpressionType.MemberAccess)
534 ················{
535 ····················MemberExpression memberex = (MemberExpression) ex;
536 ····················if (memberex.Member.Name == "NDOObjectId")
537 ····················{
538 ························if (memberex.Expression.ToString() != this.baseParameterName)
539 ························{
540 ····························Transform( memberex.Expression );
541 ····························if (sb[sb.Length - 1] != '.')
542 ································sb.Append( '.' );
543 ························}
544 ························sb.Append( "oid" );
545 ························return;
546 ····················}
547 ····················if (ex.Type == typeof( bool ))
548 ····················{
549 ························var top = expressionStack.Pop();
550 ························bool transformIt = expressionStack.Count == 0;··// The boolean expression is the top
551 ························if (expressionStack.Count > 0)
552 ························{
553 ····························var parent = expressionStack.Peek();
554 ····························if (parent.NodeType != ExpressionType.Equal && parent.NodeType != ExpressionType.NotEqual)
555 ····························{
556 ································// A Boolean Expression, which is not part of an Equals or NotEquals expression,
557 ································// must be unary. Since Sql Server doesn't support unary boolean expressions, we add an Equals Expression which compares with 1.
558 ································// Fortunately this works with other databases, too.
559 ································transformIt = true;
560 ····························}
561 ························}
562 ························if (transformIt)
563 ························{
564 ····························sb.Append( memberex.Member.Name );
565 ····························sb.Append( " = 1" );
566 ························}
567 ························expressionStack.Push( top );
568 ························if (transformIt)
569 ····························return;
570 ························//-------
571 ····················}
572 ····················if (memberex.Expression != baseLeftSide)
573 ····················{
574 ························// This happens while navigating through relations.
575 ························Transform( memberex.Expression );
576 ························if (sb[sb.Length - 1] != '.')
577 ····························sb.Append( '.' );
578 ····················}
579 ····················sb.Append( memberex.Member.Name );
580 ····················return;
581 ····················//-------
582 ················}
583
584 ················else if (ex.NodeType == ExpressionType.Constant)
585 ················{
586 ····················ConstantExpression constEx = (ConstantExpression) ex;
587 ····················AddParameter( constEx.Value );
588 ····················return;
589 ····················//-------
590 ················}
591
592 ················else if (ex.NodeType == ExpressionType.Convert)
593 ················{
594 ····················Transform( ( (UnaryExpression) ex ).Operand );
595 ····················return;
596 ····················//-------
597 ················}
598
599 ················else if (ex.NodeType == ExpressionType.Not)
600 ················{
601 ····················sb.Append( " NOT " );
602 ····················Expression inner = ((UnaryExpression)ex).Operand;
603 ····················var binary = (inner is BinaryExpression);
604 ····················if (binary)
605 ························sb.Append( '(' );
606 ····················Transform( inner );
607 ····················if (binary)
608 ························sb.Append( ')' );
609 ····················return;
610 ····················//-------
611 ················}
612 ············}
613 ············finally
614 ············{················
615 ················expressionStack.Pop();
616 ············}
617 ········}
618
619 ········private void TransformEquality( List<Expression> arguments )
620 ········{
621 ············Transform( arguments[0] );
622 ············sb.Append( " = " );
623 ············Transform( arguments[1] );
624 ········}
625
626 ········private void TransformOidIndex( Expression ex )
627 ········{
628 ············var ce = ex as ConstantExpression;
629 ············if (ce == null)
630 ················throw new Exception( "Oid index expression must be a ConstantExpression" );
631 ············if (!( ce.Value is System.Int32 ))
632 ················throw new Exception( "Oid index expression must be an Int32" );
633
634 ············if ((int) ce.Value != 0)
635 ············{
636 ················sb.Append( '(' );
637 ················sb.Append( ce.Value );
638 ················sb.Append( ')' );
639 ············}
640 ········}
641 ····}
642 }