2008-11-25 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / Sugar / Implementation / ExpressionDispatcher.Analyzer.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System;\r
28 using System.Collections.Generic;\r
29 using System.Linq;\r
30 using System.Linq.Expressions;\r
31 using System.Reflection;\r
32 \r
33 #if MONO_STRICT\r
34 using System.Data.Linq.Implementation;\r
35 using System.Data.Linq.Sugar;\r
36 using System.Data.Linq.Sugar.ExpressionMutator;\r
37 using System.Data.Linq.Sugar.Expressions;\r
38 using System.Data.Linq.Sugar.Implementation;\r
39 #else\r
40 using DbLinq.Data.Linq.Implementation;\r
41 using DbLinq.Data.Linq.Sugar;\r
42 using DbLinq.Data.Linq.Sugar.ExpressionMutator;\r
43 using DbLinq.Data.Linq.Sugar.Expressions;\r
44 using DbLinq.Data.Linq.Sugar.Implementation;\r
45 #endif\r
46 \r
47 using DbLinq.Factory;\r
48 using DbLinq.Util;\r
49 \r
50 #if MONO_STRICT\r
51 namespace System.Data.Linq.Sugar.Implementation\r
52 #else\r
53 namespace DbLinq.Data.Linq.Sugar.Implementation\r
54 #endif\r
55 {\r
56     partial class ExpressionDispatcher\r
57     {\r
58         /// <summary>\r
59         /// Entry point for Analyzis\r
60         /// </summary>\r
61         /// <param name="expression"></param>\r
62         /// <param name="parameter"></param>\r
63         /// <param name="builderContext"></param>\r
64         /// <returns></returns>\r
65         public virtual Expression Analyze(Expression expression, Expression parameter, BuilderContext builderContext)\r
66         {\r
67             return Analyze(expression, new[] { parameter }, builderContext);\r
68         }\r
69 \r
70         protected virtual Expression Analyze(Expression expression, BuilderContext builderContext)\r
71         {\r
72             return Analyze(expression, new Expression[0], builderContext);\r
73         }\r
74 \r
75         protected virtual Expression Analyze(Expression expression, IList<Expression> parameters, BuilderContext builderContext)\r
76         {\r
77             switch (expression.NodeType)\r
78             {\r
79                 case ExpressionType.Call:\r
80                     return AnalyzeCall((MethodCallExpression)expression, parameters, builderContext);\r
81                 case ExpressionType.Lambda:\r
82                     return AnalyzeLambda(expression, parameters, builderContext);\r
83                 case ExpressionType.Parameter:\r
84                     return AnalyzeParameter(expression, builderContext);\r
85                 case ExpressionType.Quote:\r
86                     return AnalyzeQuote(expression, parameters, builderContext);\r
87                 case ExpressionType.MemberAccess:\r
88                     return AnalyzeMember(expression, builderContext);\r
89                 #region case ExpressionType.<Common operators>:\r
90                 case ExpressionType.Add:\r
91                 case ExpressionType.AddChecked:\r
92                 case ExpressionType.Divide:\r
93                 case ExpressionType.Modulo:\r
94                 case ExpressionType.Multiply:\r
95                 case ExpressionType.MultiplyChecked:\r
96                 case ExpressionType.Power:\r
97                 case ExpressionType.Subtract:\r
98                 case ExpressionType.SubtractChecked:\r
99                 case ExpressionType.And:\r
100                 case ExpressionType.Or:\r
101                 case ExpressionType.ExclusiveOr:\r
102                 case ExpressionType.LeftShift:\r
103                 case ExpressionType.RightShift:\r
104                 case ExpressionType.AndAlso:\r
105                 case ExpressionType.OrElse:\r
106                 case ExpressionType.Equal:\r
107                 case ExpressionType.NotEqual:\r
108                 case ExpressionType.GreaterThanOrEqual:\r
109                 case ExpressionType.GreaterThan:\r
110                 case ExpressionType.LessThan:\r
111                 case ExpressionType.LessThanOrEqual:\r
112                 case ExpressionType.Coalesce:\r
113                 //case ExpressionType.ArrayIndex\r
114                 //case ExpressionType.ArrayLength\r
115                 case ExpressionType.Convert:\r
116                 case ExpressionType.ConvertChecked:\r
117                 case ExpressionType.Negate:\r
118                 case ExpressionType.NegateChecked:\r
119                 case ExpressionType.Not:\r
120                 //case ExpressionType.TypeAs\r
121                 case ExpressionType.UnaryPlus:\r
122                 case ExpressionType.MemberInit:\r
123                 #endregion\r
124                     return AnalyzeOperator(expression, builderContext);\r
125                 case ExpressionType.New:\r
126                     return AnalyzeNewOperator(expression, builderContext);\r
127                 case ExpressionType.Constant:\r
128                     return AnalyzeConstant(expression, builderContext);\r
129                 case ExpressionType.Invoke:\r
130                     return AnalyzeInvoke(expression, parameters, builderContext);\r
131             }\r
132             return expression;\r
133         }\r
134 \r
135         /// <summary>\r
136         /// Analyzes method call, uses specified parameters\r
137         /// </summary>\r
138         /// <param name="expression"></param>\r
139         /// <param name="parameters"></param>\r
140         /// <param name="builderContext"></param>\r
141         /// <returns></returns>\r
142         protected virtual Expression AnalyzeCall(MethodCallExpression expression, IList<Expression> parameters, BuilderContext builderContext)\r
143         {\r
144             var operands = expression.GetOperands().ToList();\r
145             var operarandsToSkip = expression.Method.IsStatic ? 1 : 0;\r
146             var originalParameters = operands.Skip(parameters.Count + operarandsToSkip);\r
147             var newParameters = parameters.Union(originalParameters);\r
148 \r
149             return AnalyzeCall(expression.Method, newParameters.ToList(), builderContext);\r
150         }\r
151 \r
152         enum DateTimePart\r
153         {\r
154             Yeah,\r
155             Month,\r
156             Day,\r
157             Hour,\r
158             Minute,\r
159             Second,\r
160             Millisecond,\r
161             Microsecond,\r
162             Nanosecond\r
163         }\r
164 \r
165         /// <summary>\r
166         /// Analyzes method call\r
167         /// </summary>\r
168         /// <param name="method"></param>\r
169         /// <param name="parameters"></param>\r
170         /// <param name="builderContext"></param>\r
171         /// <returns></returns>\r
172         protected virtual Expression AnalyzeCall(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)\r
173         {\r
174             // all methods to handle are listed here:\r
175             // ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.core/html/2a54ce9d-76f2-81e2-95bb-59740c85386b.htm\r
176             string methodName = method.Name;\r
177             if (method.DeclaringType == typeof(System.Data.Linq.SqlClient.SqlMethods))\r
178             {\r
179                 switch (methodName)\r
180                 {\r
181                     case "Like":\r
182                         if (parameters.Count == 3)\r
183                             throw new NotImplementedException();\r
184                         return AnalyzeLike(parameters[0], null, parameters[1], null, builderContext);\r
185                     case "DateDiffDay":\r
186                         return AnalyzeSqlDateDiff(DateTimePart.Day, parameters, builderContext);\r
187                     case "DateDiffHour":\r
188                         return AnalyzeSqlDateDiff(DateTimePart.Hour, parameters, builderContext);\r
189                     case "DateDiffMicrosecond":\r
190                         return AnalyzeSqlDateDiff(DateTimePart.Microsecond, parameters, builderContext);\r
191                     case "DateDiffMillisecond":\r
192                         return AnalyzeSqlDateDiff(DateTimePart.Millisecond, parameters, builderContext);\r
193                     case "DateDiffMinute":\r
194                         return AnalyzeSqlDateDiff(DateTimePart.Minute, parameters, builderContext);\r
195                     case "DateDiffMonth":\r
196                         return AnalyzeSqlDateDiff(DateTimePart.Month, parameters, builderContext);\r
197                     case "DateDiffNanosecond":\r
198                         return AnalyzeSqlDateDiff(DateTimePart.Nanosecond, parameters, builderContext);\r
199                     case "DateDiffSecond":\r
200                         return AnalyzeSqlDateDiff(DateTimePart.Second, parameters, builderContext);\r
201                     case "DateDiffYeah":\r
202                         return AnalyzeSqlDateDiff(DateTimePart.Yeah, parameters, builderContext);\r
203                     default:\r
204                         throw Error.BadArgument("S0134: Implement QueryMethod '{0}'", methodName);\r
205                 }\r
206             }\r
207             switch (methodName)\r
208             {\r
209                 case "Select":\r
210                     return AnalyzeSelect(parameters, builderContext);\r
211                 case "Where":\r
212                     return AnalyzeWhere(parameters, builderContext);\r
213                 case "SelectMany":\r
214                     return AnalyzeSelectMany(parameters, builderContext);\r
215                 case "Join":\r
216                     return AnalyzeJoin(parameters, builderContext);\r
217                 case "GroupJoin":\r
218                     return AnalyzeGroupJoin(parameters, builderContext);\r
219                 case "DefaultIfEmpty":\r
220                     return AnalyzeOuterJoin(parameters, builderContext);\r
221                 case "Distinct":\r
222                     return AnalyzeDistinct(parameters, builderContext);\r
223                 case "GroupBy":\r
224                     return AnalyzeGroupBy(parameters, builderContext);\r
225                 case "All":\r
226                     return AnalyzeAll(parameters, builderContext);\r
227                 case "Any":\r
228                     return AnalyzeAny(parameters, builderContext);\r
229                 case "Average":\r
230                     return AnalyzeProjectionQuery(SpecialExpressionType.Average, parameters, builderContext);\r
231                 case "Count":\r
232                     return AnalyzeProjectionQuery(SpecialExpressionType.Count, parameters, builderContext);\r
233                 case "Max":\r
234                     return AnalyzeProjectionQuery(SpecialExpressionType.Max, parameters, builderContext);\r
235                 case "Min":\r
236                     return AnalyzeProjectionQuery(SpecialExpressionType.Min, parameters, builderContext);\r
237                 case "Sum":\r
238                     return AnalyzeProjectionQuery(SpecialExpressionType.Sum, parameters, builderContext);\r
239                 case "StartsWith":\r
240                     return AnalyzeLikeStart(parameters, builderContext);\r
241                 case "EndsWith":\r
242                     return AnalyzeLikeEnd(parameters, builderContext);\r
243                 case "Contains":\r
244                     if (typeof(string).IsAssignableFrom(parameters[0].Type))\r
245                         return AnalyzeLike(parameters, builderContext);\r
246                     return AnalyzeContains(parameters, builderContext);\r
247                 case "Substring":\r
248                     return AnalyzeSubString(parameters, builderContext);\r
249                 case "First":\r
250                 case "FirstOrDefault":\r
251                     return AnalyzeScalar(methodName, 1, parameters, builderContext);\r
252                 case "Single":\r
253                 case "SingleOrDefault":\r
254                     return AnalyzeScalar(methodName, 2, parameters, builderContext);\r
255                 case "Last":\r
256                     return AnalyzeScalar(methodName, null, parameters, builderContext);\r
257                 case "Take":\r
258                     return AnalyzeTake(parameters, builderContext);\r
259                 case "Skip":\r
260                     return AnalyzeSkip(parameters, builderContext);\r
261                 case "ToUpper":\r
262                     return AnalyzeToUpper(parameters, builderContext);\r
263                 case "ToLower":\r
264                     return AnalyzeToLower(parameters, builderContext);\r
265                 case "OrderBy":\r
266                 case "ThenBy":\r
267                     return AnalyzeOrderBy(parameters, false, builderContext);\r
268                 case "OrderByDescending":\r
269                 case "ThenByDescending":\r
270                     return AnalyzeOrderBy(parameters, true, builderContext);\r
271                 case "Union":\r
272                     return AnalyzeSelectOperation(SelectOperatorType.Union, parameters, builderContext);\r
273                 case "Concat":\r
274                     return AnalyzeSelectOperation(SelectOperatorType.UnionAll, parameters, builderContext);\r
275                 case "Intersect":\r
276                     return AnalyzeSelectOperation(SelectOperatorType.Intersection, parameters, builderContext);\r
277                 case "Except":\r
278                     return AnalyzeSelectOperation(SelectOperatorType.Exception, parameters, builderContext);\r
279                 case "Trim":\r
280                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Trim, parameters, builderContext);\r
281                 case "TrimStart":\r
282                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.LTrim, parameters, builderContext);\r
283                 case "TrimEnd":\r
284                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.RTrim, parameters, builderContext);\r
285                 case "Insert":\r
286                     return AnalyzeStringInsert(parameters, builderContext);\r
287                 case "Replace":\r
288                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Replace, parameters, builderContext);\r
289                 case "Remove":\r
290                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Remove, parameters, builderContext);\r
291                 case "IndexOf":\r
292                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.IndexOf, parameters, builderContext);\r
293                 case "ToString":\r
294                     return AnalyzeToString(method, parameters, builderContext);\r
295                 case "Parse":\r
296                     return AnalyzeParse(method, parameters, builderContext);\r
297                 case "Abs":\r
298                 case "Exp":\r
299                 case "Floor":\r
300                 case "Pow":\r
301                 case "Round":\r
302                 case "Sign":\r
303                 case "Sqrt":\r
304                     return AnalyzeGenericSpecialExpressionType((SpecialExpressionType)Enum.Parse(typeof(SpecialExpressionType), methodName), parameters, builderContext);\r
305                 case "Log10":\r
306                     return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Log, parameters, builderContext);\r
307                 case "Log":\r
308                     return AnalyzeLog(parameters, builderContext);\r
309                 default:\r
310                     throw Error.BadArgument("S0133: Implement QueryMethod '{0}'", methodName);\r
311             }\r
312         }\r
313 \r
314         static readonly MethodInfo dateTimeHasValue = typeof(DateTime?).GetProperty("HasValue").GetGetMethod();\r
315         static readonly MethodInfo dateTimeOffsetHasValue = typeof(DateTimeOffset?).GetProperty("HasValue").GetGetMethod();\r
316         static readonly MethodInfo dateTimeValue = typeof(DateTime?).GetProperty("Value").GetGetMethod();\r
317         static readonly MethodInfo dateTimeOffsetValue = typeof(DateTimeOffset?).GetProperty("Value").GetGetMethod();\r
318         static readonly ConstructorInfo nintctr = typeof(int?).GetConstructors()[0];\r
319 \r
320         static MethodInfo GetPropertyGetterForDatePart(Type t, DateTimePart p)\r
321         {\r
322             switch (p)\r
323             {\r
324                 case DateTimePart.Day: return t.GetProperty("Day").GetGetMethod();\r
325                 case DateTimePart.Hour: return t.GetProperty("Hour").GetGetMethod();\r
326                 case DateTimePart.Microsecond: return t.GetProperty("Microsecond").GetGetMethod();\r
327                 case DateTimePart.Millisecond: return t.GetProperty("Millisecond").GetGetMethod();\r
328                 case DateTimePart.Minute: return t.GetProperty("Minute").GetGetMethod();\r
329                 case DateTimePart.Month: return t.GetProperty("Month").GetGetMethod();\r
330                 case DateTimePart.Nanosecond: return t.GetProperty("Nanosecond").GetGetMethod();\r
331                 case DateTimePart.Second: return t.GetProperty("Second").GetGetMethod();\r
332                 case DateTimePart.Yeah: return t.GetProperty("Yeah").GetGetMethod();\r
333             }\r
334             throw new NotImplementedException();\r
335         }\r
336 \r
337         private Expression AnalyzeSqlDateDiff(DateTimePart part, IList<Expression> parameters, BuilderContext builderContext)\r
338         {\r
339             Expression l = parameters[0], r = parameters [1];\r
340             Type valueType = l.Type.IsNullable() ? (l.Type == typeof(DateTime?) ? typeof(DateTime) : typeof(DateTimeOffset)) : l.Type;\r
341             MethodInfo p = GetPropertyGetterForDatePart(valueType, part);\r
342             if (l.Type.IsNullable())\r
343             {\r
344                 MethodInfo hasValue = l.Type == typeof(DateTime?) ? dateTimeHasValue : dateTimeOffsetHasValue;\r
345                 MethodInfo value = l.Type == typeof(DateTime?) ? dateTimeValue : dateTimeOffsetValue;\r
346 //                Console.WriteLine("!!!! {0}, {1}", Expression.Subtract(Expression.Property(Expression.Property(l, value), p), Expression.Property(Expression.Property(r, value), p)).Type, Expression.New(int?).Type);\r
347                 return Expression.Condition(\r
348                     Expression.AndAlso(Expression.Property(l, hasValue), Expression.Property(r, hasValue)),\r
349                     Expression.New (nintctr,\r
350                         Expression.Subtract(\r
351                             Expression.Property(Expression.Property (l, value), p),\r
352                             Expression.Property(Expression.Property (r, value), p))),\r
353                     Expression.Convert (Expression.Constant (null), typeof (int?)));\r
354             }\r
355             else\r
356                 return Expression.Subtract(Expression.Property(l, p), Expression.Property(r, p));\r
357         }\r
358 \r
359         private Expression AnalyzeStringInsert(IList<Expression> parameters, BuilderContext builderContext)\r
360         {\r
361             var startIndexExpression = new StartIndexOffsetExpression(builderContext.QueryContext.DataContext.Vendor.SqlProvider.StringIndexStartsAtOne, parameters.ElementAt(1));\r
362             var stringToInsertExpression = parameters.ElementAt(2);\r
363             return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.StringInsert, new Expression[] { parameters.First(), startIndexExpression, stringToInsertExpression }, builderContext);\r
364         }\r
365 \r
366         protected virtual Expression AnalyzeLog(IList<Expression> parameters, BuilderContext builderContext)\r
367         {\r
368             if (parameters.Count == 1)\r
369                 return new SpecialExpression(SpecialExpressionType.Ln, parameters.Select(p => Analyze(p, builderContext)).ToList());\r
370             else if (parameters.Count == 2)\r
371                 return new SpecialExpression(SpecialExpressionType.Log, parameters.Select(p => Analyze(p, builderContext)).ToList());\r
372             else\r
373                 throw new NotSupportedException();\r
374         }\r
375 \r
376         protected virtual Expression AnalyzeGenericSpecialExpressionType(SpecialExpressionType specialType, IList<Expression> parameters, BuilderContext builderContext)\r
377         {\r
378             return new SpecialExpression(specialType, parameters.Select(p => Analyze(p, builderContext)).ToList());\r
379         }\r
380 \r
381         protected virtual Expression AnalyzeParse(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)\r
382         {\r
383             if (method.IsStatic && parameters.Count == 1)\r
384             {\r
385                 var expression = Expression.Convert(Analyze(parameters.First(), builderContext), method.ReturnType, method);\r
386                 ExpressionTier tier = ExpressionQualifier.GetTier(expression);\r
387 \r
388 \r
389                 //pibgeus: I would like to call to the expression optimizer since the exception must be thrown if the expression cannot be executed\r
390                 //in Clr tier, if it can be executed in Clr tier it should continue\r
391                 // ie: from e in db.Employees where DateTime.Parse("1/1/1999").Year==1999 select e  <--- this should work\r
392                 // ie: from e in db.Employees where DateTime.Parse(e.BirthDate).Year==1999 select e  <--- a NotSupportedException must be throwed (this is the behaviour of linq2sql)\r
393 \r
394                 //if (method.ReturnType == typeof(DateTime))\r
395                 //{\r
396                 //        expression = ExpressionOptimizer.Analyze(expression);\r
397                 //        //same behaviour that Linq2Sql\r
398                 //        throw new NotSupportedException("Method 'System.DateTime Parse(System.String)' has no supported translation to SQL");\r
399                 //}\r
400 \r
401                 return expression;\r
402             }\r
403             else\r
404                 throw new ArgumentException();\r
405 \r
406         }\r
407 \r
408         protected virtual Expression AnalyzeToString(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)\r
409         {\r
410             if (parameters.Count != 1)\r
411                 throw new ArgumentException();\r
412 \r
413             Expression parameter;\r
414             if (parameters.First().Type.IsNullable())\r
415                 parameter = Expression.Convert(parameters.First(), parameters.First().Type.GetNullableType());\r
416             else\r
417                 parameter = parameters.First();\r
418 \r
419             if (!parameter.Type.IsPrimitive && parameter.Type != typeof(string))\r
420             {\r
421                 //TODO: ExpressionDispacher.Analyze.AnalyzeToString is not complete\r
422                 //This is the standar behaviour in linq2sql, nonetheless the behaviour isn't complete since when the expression\r
423                 //can be executed in the clr, ie: (where new StrangeObject().ToString()) should work. The problem is that\r
424                 //we don't have a reference to the optimizer here.\r
425                 //Working samples in: /Tests/Test_Nunit/ReadTests_Conversions.cs\r
426                 throw new NotSupportedException("Method ToString can only be translated to SQL for primitive types.");\r
427             }\r
428 \r
429             return Expression.Convert(Analyze(parameter, builderContext), typeof(string), typeof(Convert).GetMethod("ToString", new[] { parameter.Type }));\r
430         }\r
431 \r
432         /// <summary>\r
433         /// Limits selection count\r
434         /// </summary>\r
435         /// <param name="parameters"></param>\r
436         /// <param name="builderContext"></param>\r
437         /// <returns></returns>\r
438         protected virtual Expression AnalyzeTake(IList<Expression> parameters, BuilderContext builderContext)\r
439         {\r
440             AddLimit(Analyze(parameters[1], builderContext), builderContext);\r
441             return Analyze(parameters[0], builderContext);\r
442         }\r
443 \r
444         protected virtual void AddLimit(Expression limit, BuilderContext builderContext)\r
445         {\r
446             var previousLimit = builderContext.CurrentSelect.Limit;\r
447             if (previousLimit != null)\r
448                 builderContext.CurrentSelect.Limit = Expression.Condition(Expression.LessThan(previousLimit, limit),\r
449                                                                           previousLimit, limit);\r
450             else\r
451                 builderContext.CurrentSelect.Limit = limit;\r
452         }\r
453 \r
454         /// <summary>\r
455         /// Skip selection items\r
456         /// </summary>\r
457         /// <param name="parameters"></param>\r
458         /// <param name="builderContext"></param>\r
459         /// <returns></returns>\r
460         protected virtual Expression AnalyzeSkip(IList<Expression> parameters, BuilderContext builderContext)\r
461         {\r
462             AddOffset(Analyze(parameters[1], builderContext), builderContext);\r
463             return Analyze(parameters[0], builderContext);\r
464         }\r
465 \r
466         protected virtual void AddOffset(Expression offset, BuilderContext builderContext)\r
467         {\r
468             var previousOffset = builderContext.CurrentSelect.Offset;\r
469             if (previousOffset != null)\r
470                 builderContext.CurrentSelect.Offset = Expression.Add(offset, previousOffset);\r
471             else\r
472                 builderContext.CurrentSelect.Offset = offset;\r
473         }\r
474 \r
475         /// <summary>\r
476         /// Registers a scalar method call for result\r
477         /// </summary>\r
478         /// <param name="methodName"></param>\r
479         /// <param name="limit"></param>\r
480         /// <param name="parameters"></param>\r
481         /// <param name="builderContext"></param>\r
482         /// <returns></returns>\r
483         protected virtual Expression AnalyzeScalar(string methodName, int? limit, IList<Expression> parameters, BuilderContext builderContext)\r
484         {\r
485             builderContext.CurrentSelect.ExecuteMethodName = methodName;\r
486             if (limit.HasValue)\r
487                 AddLimit(Expression.Constant(limit.Value), builderContext);\r
488             var table = Analyze(parameters[0], builderContext);\r
489             CheckWhere(table, parameters, 1, builderContext);\r
490             return table;\r
491         }\r
492 \r
493         /// <summary>\r
494         /// Some methods, like Single(), Count(), etc. can get an extra parameter, specifying a restriction.\r
495         /// This method checks if the parameter is specified, and adds it to the WHERE clauses\r
496         /// </summary>\r
497         /// <param name="table"></param>\r
498         /// <param name="parameters"></param>\r
499         /// <param name="extraParameterIndex"></param>\r
500         /// <param name="builderContext"></param>\r
501         private void CheckWhere(Expression table, IList<Expression> parameters, int extraParameterIndex, BuilderContext builderContext)\r
502         {\r
503             if (parameters.Count > extraParameterIndex) // a lambda can be specified here, this is a restriction\r
504                 RegisterWhere(Analyze(parameters[extraParameterIndex], table, builderContext), builderContext);\r
505         }\r
506 \r
507         /// <summary>\r
508         /// Returns a projection method call\r
509         /// </summary>\r
510         /// <param name="specialExpressionType"></param>\r
511         /// <param name="parameters"></param>\r
512         /// <param name="builderContext"></param>\r
513         /// <returns></returns>\r
514         protected virtual Expression AnalyzeProjectionQuery(SpecialExpressionType specialExpressionType, IList<Expression> parameters,\r
515                                                             BuilderContext builderContext)\r
516         {\r
517 \r
518             if (builderContext.IsExternalInExpressionChain)\r
519             {\r
520                 var operand0 = Analyze(parameters[0], builderContext);\r
521                 Expression projectionOperand;\r
522 \r
523                 // basically, we have three options for projection methods:\r
524                 // - projection on grouped table (1 operand, a GroupExpression)\r
525                 // - projection on grouped column (2 operands, GroupExpression and ColumnExpression)\r
526                 // - projection on table/column, with optional restriction\r
527                 var groupOperand0 = operand0 as GroupExpression;\r
528                 if (groupOperand0 != null)\r
529                 {\r
530                     if (parameters.Count > 1)\r
531                     {\r
532                         projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression,\r
533                                                     builderContext);\r
534                     }\r
535                     else\r
536                         projectionOperand = Analyze(groupOperand0.GroupedExpression, builderContext);\r
537                 }\r
538                 else\r
539                 {\r
540                     projectionOperand = operand0;\r
541                     CheckWhere(projectionOperand, parameters, 1, builderContext);\r
542                 }\r
543 \r
544                 if (projectionOperand is TableExpression)\r
545                     projectionOperand = RegisterTable((TableExpression)projectionOperand, builderContext);\r
546 \r
547                 if (groupOperand0 != null)\r
548                     projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression);\r
549 \r
550                 return new SpecialExpression(specialExpressionType, projectionOperand);\r
551             }\r
552             else\r
553             {\r
554                 var projectionQueryBuilderContext = builderContext.NewSelect();\r
555                 var tableExpression = Analyze(parameters[0], projectionQueryBuilderContext);\r
556 \r
557                 if (!(tableExpression is TableExpression))\r
558                     tableExpression = Analyze(tableExpression, projectionQueryBuilderContext);\r
559 \r
560                 // from here we build a custom clause:\r
561                 // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0"\r
562                 // TODO (later...): see if some vendors support native Any operator and avoid this substitution\r
563                 if (parameters.Count > 1)\r
564                 {\r
565                     var anyClause = Analyze(parameters[1], tableExpression, projectionQueryBuilderContext);\r
566                     RegisterWhere(anyClause, projectionQueryBuilderContext);\r
567                 }\r
568 \r
569                 projectionQueryBuilderContext.CurrentSelect = projectionQueryBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(specialExpressionType, tableExpression));\r
570 \r
571                 // we now switch back to current context, and compare the result with 0\r
572                 return projectionQueryBuilderContext.CurrentSelect;\r
573 \r
574             }\r
575         }\r
576 \r
577         /// <summary>\r
578         /// Entry point for a Select()\r
579         /// static Select(this Expression table, λ(table))\r
580         /// </summary>\r
581         /// <param name="parameters"></param>\r
582         /// <param name="builderContext"></param>\r
583         /// <returns></returns>\r
584         protected virtual Expression AnalyzeSelect(IList<Expression> parameters, BuilderContext builderContext)\r
585         {\r
586             // just call back the underlying lambda (or quote, whatever)\r
587             return Analyze(parameters[1], parameters[0], builderContext);\r
588         }\r
589 \r
590         /// <summary>\r
591         /// Entry point for a Where()\r
592         /// static Where(this Expression table, λ(table))\r
593         /// </summary>\r
594         /// <param name="parameters"></param>\r
595         /// <param name="builderContext"></param>\r
596         /// <returns></returns>\r
597         protected virtual Expression AnalyzeWhere(IList<Expression> parameters, BuilderContext builderContext)\r
598         {\r
599             var tablePiece = parameters[0];\r
600             RegisterWhere(Analyze(parameters[1], tablePiece, builderContext), builderContext);\r
601             return tablePiece;\r
602         }\r
603 \r
604         /// <summary>\r
605         /// Handling a lambda consists in:\r
606         /// - filling its input parameters with what's on the stack\r
607         /// - using the body (parameters are registered in the context)\r
608         /// </summary>\r
609         /// <param name="expression"></param>\r
610         /// <param name="parameters"></param>\r
611         /// <param name="builderContext"></param>\r
612         /// <returns></returns>\r
613         protected virtual Expression AnalyzeLambda(Expression expression, IList<Expression> parameters, BuilderContext builderContext)\r
614         {\r
615             var lambdaExpression = expression as LambdaExpression;\r
616             if (lambdaExpression == null)\r
617                 throw Error.BadArgument("S0227: Unknown type for AnalyzeLambda() ({0})", expression.GetType());\r
618             // for a lambda, first parameter is body, others are input parameters\r
619             // so we create a parameters stack\r
620             for (int parameterIndex = 0; parameterIndex < lambdaExpression.Parameters.Count; parameterIndex++)\r
621             {\r
622                 var parameterExpression = lambdaExpression.Parameters[parameterIndex];\r
623                 builderContext.Parameters[parameterExpression.Name] = Analyze(parameters[parameterIndex], builderContext);\r
624             }\r
625             // we keep only the body, the header is now useless\r
626             // and once the parameters have been substituted, we don't pass one anymore\r
627             return Analyze(lambdaExpression.Body, builderContext);\r
628         }\r
629 \r
630         /// <summary>\r
631         /// When a parameter is used, we replace it with its original value\r
632         /// </summary>\r
633         /// <param name="expression"></param>\r
634         /// <param name="builderContext"></param>\r
635         /// <returns></returns>\r
636         protected virtual Expression AnalyzeParameter(Expression expression, BuilderContext builderContext)\r
637         {\r
638             Expression unaliasedExpression;\r
639             var parameterName = GetParameterName(expression);\r
640             builderContext.Parameters.TryGetValue(parameterName, out unaliasedExpression);\r
641             if (unaliasedExpression == null)\r
642                 throw Error.BadArgument("S0257: can not find parameter '{0}'", parameterName);\r
643 \r
644             #region set alias helper\r
645 \r
646             // for table...\r
647             var unaliasedTableExpression = unaliasedExpression as TableExpression;\r
648             if (unaliasedTableExpression != null && unaliasedTableExpression.Alias == null)\r
649                 unaliasedTableExpression.Alias = parameterName;\r
650             // .. or column\r
651             var unaliasedColumnExpression = unaliasedExpression as ColumnExpression;\r
652             if (unaliasedColumnExpression != null && unaliasedColumnExpression.Alias == null)\r
653                 unaliasedColumnExpression.Alias = parameterName;\r
654 \r
655             #endregion\r
656 \r
657             //var groupByExpression = unaliasedExpression as GroupByExpression;\r
658             //if (groupByExpression != null)\r
659             //    unaliasedExpression = groupByExpression.ColumnExpression.Table;\r
660 \r
661             return unaliasedExpression;\r
662         }\r
663 \r
664         /// <summary>\r
665         /// Returns if the given member can be considered as an EntitySet<>\r
666         /// </summary>\r
667         /// <param name="memberType"></param>\r
668         /// <param name="entityType"></param>\r
669         /// <returns></returns>\r
670         protected virtual bool IsEntitySet(Type memberType, out Type entityType)\r
671         {\r
672             entityType = memberType;\r
673             // one check, a generic EntityRef<> or inherited\r
674             if (memberType.IsGenericType && typeof(EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))\r
675             {\r
676                 entityType = memberType.GetGenericArguments()[0];\r
677                 return true;\r
678             }\r
679 #if !MONO_STRICT\r
680             // this is for compatibility with previously generated .cs files\r
681             // TODO: remove in 2009\r
682             if (memberType.IsGenericType && typeof(System.Data.Linq.EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))\r
683             {\r
684                 entityType = memberType.GetGenericArguments()[0];\r
685                 return true;\r
686             }\r
687 #endif\r
688             return false;\r
689         }\r
690 \r
691         /// <summary>\r
692         /// Analyzes a member access.\r
693         /// This analyzis is down to top: the highest identifier is at bottom\r
694         /// </summary>\r
695         /// <param name="expression"></param>\r
696         /// <param name="builderContext"></param>\r
697         /// <returns></returns>\r
698         protected virtual Expression AnalyzeMember(Expression expression, BuilderContext builderContext)\r
699         {\r
700             var memberExpression = (MemberExpression)expression;\r
701 \r
702             Expression objectExpression = null;\r
703             //maybe is a static member access like DateTime.Now\r
704             bool isStaticMemberAccess = memberExpression.Member.GetIsStaticMember();\r
705 \r
706             if (!isStaticMemberAccess)\r
707                 // first parameter is object, second is member\r
708                 objectExpression = Analyze(memberExpression.Expression, builderContext);\r
709 \r
710             var memberInfo = memberExpression.Member;\r
711             // then see what we can do, depending on object type\r
712             // - MetaTable --> then the result is a table\r
713             // - Table --> the result may be a column or a join\r
714             // - Object --> external parameter or table (can this happen here? probably not... to be checked)\r
715 \r
716             if (objectExpression is MetaTableExpression)\r
717             {\r
718                 var metaTableExpression = (MetaTableExpression)objectExpression;\r
719                 var tableExpression = metaTableExpression.GetTableExpression(memberInfo);\r
720                 if (tableExpression == null)\r
721                     throw Error.BadArgument("S0270: MemberInfo '{0}' not found in MetaTable", memberInfo.Name);\r
722                 return tableExpression;\r
723             }\r
724 \r
725             if (objectExpression is GroupExpression)\r
726             {\r
727                 if (memberInfo.Name == "Key")\r
728                     return ((GroupExpression)objectExpression).KeyExpression;\r
729             }\r
730 \r
731             // if object is a table, then we need a column, or an association\r
732             if (objectExpression is TableExpression)\r
733             {\r
734                 var tableExpression = (TableExpression)objectExpression;\r
735 \r
736                 // before finding an association, we check for an EntitySet<>\r
737                 // this will be used in RegisterAssociation\r
738                 Type entityType;\r
739                 bool isEntitySet = IsEntitySet(memberInfo.GetMemberType(), out entityType);\r
740                 // first of all, then, try to find the association\r
741                 var queryAssociationExpression = RegisterAssociation(tableExpression, memberInfo, entityType,\r
742                                                                      builderContext);\r
743                 if (queryAssociationExpression != null)\r
744                 {\r
745                     // no entitySet? we have right association\r
746                     //if (!isEntitySet)\r
747                     return queryAssociationExpression;\r
748 \r
749                     // from here, we may require to cast the table to an entitySet\r
750                     return new EntitySetExpression(queryAssociationExpression, memberInfo.GetMemberType());\r
751                 }\r
752                 // then, try the column\r
753                 var queryColumnExpression = RegisterColumn(tableExpression, memberInfo, builderContext);\r
754                 if (queryColumnExpression != null)\r
755                     return queryColumnExpression;\r
756 \r
757                 if (memberInfo.Name == "Count")\r
758                     return AnalyzeProjectionQuery(SpecialExpressionType.Count, new[] { memberExpression.Expression }, builderContext);\r
759                 // then, cry\r
760                 throw Error.BadArgument("S0293: Column must be mapped. Non-mapped columns are not handled by now.");\r
761             }\r
762 \r
763             // if object is still an object (== a constant), then we have an external parameter\r
764             if (objectExpression is ConstantExpression)\r
765             {\r
766                 // the memberInfo.Name is provided here only to ease the SQL reading\r
767                 var parameterExpression = RegisterParameter(expression, memberInfo.Name, builderContext);\r
768                 if (parameterExpression != null)\r
769                     return parameterExpression;\r
770                 throw Error.BadArgument("S0302: Can not created parameter from expression '{0}'", expression);\r
771             }\r
772 \r
773             // we have here a special cases for nullables\r
774             if (!isStaticMemberAccess && objectExpression.Type != null && objectExpression.Type.IsNullable())\r
775             {\r
776                 // Value means we convert the nullable to a value --> use Convert instead (works both on CLR and SQL, too)\r
777                 if (memberInfo.Name == "Value")\r
778                     return Expression.Convert(objectExpression, memberInfo.GetMemberType());\r
779                 // HasValue means not null (works both on CLR and SQL, too)\r
780                 if (memberInfo.Name == "HasValue")\r
781                     return new SpecialExpression(SpecialExpressionType.IsNotNull, objectExpression);\r
782             }\r
783 \r
784 \r
785             if (memberInfo.DeclaringType == typeof(DateTime))\r
786                 return AnalyzeDateTimeMemberAccess(objectExpression, memberInfo, isStaticMemberAccess);\r
787 \r
788             // TODO: make this expresion safe (objectExpression can be null here)\r
789             if (objectExpression.Type == typeof(TimeSpan))\r
790                 return AnalyzeTimeSpanMemberAccess(objectExpression, memberInfo);\r
791 \r
792 \r
793             if (objectExpression is InputParameterExpression)\r
794             {\r
795                 return AnalyzeExternalParameterMember((InputParameterExpression)objectExpression, memberInfo, builderContext);\r
796             }\r
797 \r
798             if (objectExpression is MemberInitExpression)\r
799             {\r
800                 var foundExpression = AnalyzeMemberInit((MemberInitExpression)objectExpression, memberInfo, builderContext);\r
801                 if (foundExpression != null)\r
802                     return foundExpression;\r
803             }\r
804 \r
805             return AnalyzeCommonMember(objectExpression, memberInfo, builderContext);\r
806         }\r
807 \r
808         private Expression AnalyzeTimeSpanMemberAccess(Expression objectExpression, MemberInfo memberInfo)\r
809         {\r
810             throw new NotImplementedException();\r
811         }\r
812 \r
813         protected Expression AnalyzeTimeSpamMemberAccess(Expression objectExpression, MemberInfo memberInfo)\r
814         {\r
815             //A timespan expression can be only generated in a c# query as a DateTime difference, as a function call return or as a paramter\r
816             //this case is for the DateTime difference operation\r
817 \r
818             if (!(objectExpression is BinaryExpression))\r
819                 throw new NotSupportedException();\r
820 \r
821             var operands = objectExpression.GetOperands();\r
822 \r
823             bool absoluteSpam = memberInfo.Name.StartsWith("Total");\r
824             string operationKey = absoluteSpam ? memberInfo.Name.Substring(5) : memberInfo.Name;\r
825 \r
826             Expression currentExpression;\r
827             switch (operationKey)\r
828             {\r
829                 case "Milliseconds":\r
830                     currentExpression = Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double));\r
831                     break;\r
832                 case "Seconds":\r
833                     currentExpression = Expression.Divide(\r
834                         Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),\r
835                         Expression.Constant(1000.0));\r
836                     break;\r
837                 case "Minutes":\r
838                     currentExpression = Expression.Divide(\r
839                             Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),\r
840                             Expression.Constant(60000.0));\r
841                     break;\r
842                 case "Hours":\r
843                     currentExpression = Expression.Divide(\r
844                             Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),\r
845                             Expression.Constant(3600000.0));\r
846                     break;\r
847                 case "Days":\r
848                     currentExpression = Expression.Divide(\r
849                             Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),\r
850                             Expression.Constant(86400000.0));\r
851                     break;\r
852                 default:\r
853                     throw new NotSupportedException(string.Format("The operation {0} over the TimeSpan isn't currently supported", memberInfo.Name));\r
854             }\r
855 \r
856             if (!absoluteSpam)\r
857             {\r
858                 switch (memberInfo.Name)\r
859                 {\r
860                     case "Milliseconds":\r
861                         currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(1000L)), typeof(int));\r
862                         break;\r
863                     case "Seconds":\r
864                         currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),\r
865                                                               Expression.Constant(60L)), typeof(int));\r
866                         break;\r
867                     case "Minutes":\r
868                         currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),\r
869                                                                 Expression.Constant(60L)), typeof(int));\r
870                         break;\r
871                     case "Hours":\r
872                         currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(\r
873                                                                                         currentExpression, typeof(long)),\r
874                                                                 Expression.Constant(24L)), typeof(int));\r
875                         break;\r
876                     case "Days":\r
877                         currentExpression = Expression.Convert(currentExpression, typeof(int));\r
878                         break;\r
879                 }\r
880 \r
881             }\r
882             return currentExpression;\r
883         }\r
884 \r
885         protected Expression AnalyzeDateTimeMemberAccess(Expression objectExpression, MemberInfo memberInfo, bool isStaticMemberAccess)\r
886         {\r
887             if (isStaticMemberAccess)\r
888             {\r
889                 if (memberInfo.Name == "Now")\r
890                     return new SpecialExpression(SpecialExpressionType.Now);\r
891                 else\r
892                     throw new NotSupportedException(string.Format("DateTime Member access {0} not supported", memberInfo.Name));\r
893             }\r
894             else\r
895             {\r
896                 switch (memberInfo.Name)\r
897                 {\r
898                     case "Year":\r
899                         return new SpecialExpression(SpecialExpressionType.Year, objectExpression);\r
900                     case "Month":\r
901                         return new SpecialExpression(SpecialExpressionType.Month, objectExpression);\r
902                     case "Day":\r
903                         return new SpecialExpression(SpecialExpressionType.Day, objectExpression);\r
904                     case "Hour":\r
905                         return new SpecialExpression(SpecialExpressionType.Hour, objectExpression);\r
906                     case "Minute":\r
907                         return new SpecialExpression(SpecialExpressionType.Minute, objectExpression);\r
908                     case "Second":\r
909                         return new SpecialExpression(SpecialExpressionType.Second, objectExpression);\r
910                     case "Millisecond":\r
911                         return new SpecialExpression(SpecialExpressionType.Millisecond, objectExpression);\r
912                     default:\r
913                         throw new NotSupportedException(string.Format("DateTime Member access {0} not supported", memberInfo.Name));\r
914                 }\r
915             }\r
916         }\r
917 \r
918         /// <summary>\r
919         /// This method analyzes the case of a new followed by a member access\r
920         /// for example new "A(M = value).M", where the Expression can be reduced to "value"\r
921         /// Caution: it may return null if no result is found\r
922         /// </summary>\r
923         /// <param name="expression"></param>\r
924         /// <param name="memberInfo"></param>\r
925         /// <param name="builderContext"></param>\r
926         /// <returns>A member initializer or null</returns>\r
927         protected virtual Expression AnalyzeMemberInit(MemberInitExpression expression, MemberInfo memberInfo,\r
928                                                        BuilderContext builderContext)\r
929         {\r
930             // TODO: a method for NewExpression that we will use directly from AnalyzeMember and indirectly from here\r
931             foreach (var binding in expression.Bindings)\r
932             {\r
933                 var memberAssignment = binding as MemberAssignment;\r
934                 if (memberAssignment != null)\r
935                 {\r
936                     if (memberAssignment.Member == memberInfo)\r
937                         return memberAssignment.Expression;\r
938                 }\r
939             }\r
940             return null;\r
941         }\r
942 \r
943         protected virtual Expression AnalyzeExternalParameterMember(InputParameterExpression expression, MemberInfo memberInfo, BuilderContext builderContext)\r
944         {\r
945             UnregisterParameter(expression, builderContext);\r
946             return RegisterParameter(Expression.MakeMemberAccess(expression.Expression, memberInfo), memberInfo.Name, builderContext);\r
947         }\r
948 \r
949         protected virtual Expression AnalyzeCommonMember(Expression objectExpression, MemberInfo memberInfo, BuilderContext builderContext)\r
950         {\r
951             if (typeof(string).IsAssignableFrom(objectExpression.Type))\r
952             {\r
953                 switch (memberInfo.Name)\r
954                 {\r
955                     case "Length":\r
956                         return new SpecialExpression(SpecialExpressionType.StringLength, objectExpression);\r
957                 }\r
958             }\r
959             //throw Error.BadArgument("S0324: Don't know how to handle Piece");\r
960             return Expression.MakeMemberAccess(objectExpression, memberInfo);\r
961         }\r
962 \r
963         /// <summary>\r
964         /// A Quote creates a new local context, outside which created parameters disappear\r
965         /// This is why we clone the BuilderContext\r
966         /// </summary>\r
967         /// <param name="piece"></param>\r
968         /// <param name="parameters"></param>\r
969         /// <param name="builderContext"></param>\r
970         /// <returns></returns>\r
971         protected virtual Expression AnalyzeQuote(Expression piece, IList<Expression> parameters, BuilderContext builderContext)\r
972         {\r
973             var builderContextClone = builderContext.NewQuote();\r
974             var firstExpression = piece.GetOperands().First();\r
975             return Analyze(firstExpression, parameters, builderContextClone);\r
976         }\r
977 \r
978         /// <summary>\r
979         /// </summary>\r
980         /// <param name="expression"></param>\r
981         /// <param name="builderContext"></param>\r
982         /// <returns></returns>\r
983         protected virtual Expression AnalyzeOperatorSubstract(Expression expression, BuilderContext builderContext)\r
984         {\r
985             return AnalyzeOperator(expression, builderContext);\r
986         }\r
987 \r
988         /// <summary>\r
989         /// Operator analysis consists in anlyzing all operands\r
990         /// </summary>\r
991         /// <param name="expression"></param>\r
992         /// <param name="builderContext"></param>\r
993         /// <returns></returns>\r
994         protected virtual Expression AnalyzeOperator(Expression expression, BuilderContext builderContext)\r
995         {\r
996             var operands = expression.GetOperands().ToList();\r
997             for (int operandIndex = 0; operandIndex < operands.Count; operandIndex++)\r
998             {\r
999                 var operand = operands[operandIndex];\r
1000                 operands[operandIndex] = Analyze(operand, builderContext);\r
1001             }\r
1002             return expression.ChangeOperands(operands);\r
1003         }\r
1004 \r
1005         protected virtual Expression AnalyzeNewOperator(Expression expression, BuilderContext builderContext)\r
1006         {\r
1007             if (builderContext.ExpectMetaTableDefinition)\r
1008             {\r
1009                 // first, check if we have a MetaTable definition\r
1010                 Type metaType;\r
1011                 var typeInitializers = GetTypeInitializers<Expression>((NewExpression)expression, true, out metaType);\r
1012                 var aliases = new Dictionary<MemberInfo, TableExpression>();\r
1013                 foreach (var memberInfo in typeInitializers.Keys)\r
1014                 {\r
1015                     var tableExpression = Analyze(typeInitializers[memberInfo], builderContext) as TableExpression;\r
1016                     aliases[memberInfo] = tableExpression;\r
1017                 }\r
1018                 if (IsMetaTableDefinition(aliases))\r
1019                     return RegisterMetaTable(metaType, aliases, builderContext);\r
1020             }\r
1021             return AnalyzeOperator(expression, builderContext);\r
1022         }\r
1023 \r
1024         protected virtual bool IsMetaTableDefinition(IDictionary<MemberInfo, TableExpression> aliases)\r
1025         {\r
1026             if (aliases.Count != 2)\r
1027                 return false;\r
1028             foreach (var tableExpression in aliases.Values)\r
1029             {\r
1030                 if (tableExpression == null)\r
1031                     return false;\r
1032             }\r
1033             return true;\r
1034         }\r
1035 \r
1036         /// <summary>\r
1037         /// SelectMany() joins tables\r
1038         /// </summary>\r
1039         /// <param name="parameters"></param>\r
1040         /// <param name="builderContext"></param>\r
1041         /// <returns></returns>\r
1042         protected virtual Expression AnalyzeSelectMany(IList<Expression> parameters, BuilderContext builderContext)\r
1043         {\r
1044             if (parameters.Count == 3)\r
1045             {\r
1046                 // ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.core/html/3371348f-7811-b0bc-8c0a-2a595e08e086.htm\r
1047                 var tableExpression = parameters[0];\r
1048                 var projectionExpression = Analyze(parameters[1], new[] { tableExpression }, builderContext);\r
1049                 //var manyPiece = Analyze(parameters[2], new[] { tableExpression, projectionExpression }, builderContext);\r
1050                 // from here, our manyPiece is a MetaTable definition\r
1051                 //var newExpression = manyPiece as NewExpression;\r
1052                 //if (newExpression == null)\r
1053                 //    throw Error.BadArgument("S0377: Expected a NewExpression as SelectMany() return value");\r
1054                 //Type metaTableType;\r
1055                 //var associations = GetTypeInitializers<TableExpression>(newExpression, true, out metaTableType);\r
1056                 //return RegisterMetaTable(metaTableType, associations, builderContext);\r
1057                 var metaTableDefinitionBuilderContext = builderContext.Clone();\r
1058                 metaTableDefinitionBuilderContext.ExpectMetaTableDefinition = true;\r
1059                 var expression = Analyze(parameters[2], new[] { tableExpression, projectionExpression },\r
1060                                          metaTableDefinitionBuilderContext);\r
1061                 return expression;\r
1062             }\r
1063             throw Error.BadArgument("S0358: Don't know how to handle this SelectMany() overload ({0} parameters)", parameters.Count);\r
1064         }\r
1065 \r
1066         protected virtual IDictionary<MemberInfo, E> GetTypeInitializers<E>(NewExpression newExpression,\r
1067                                                                             bool checkCast, out Type metaType)\r
1068             where E : Expression\r
1069         {\r
1070             var associations = new Dictionary<MemberInfo, E>();\r
1071             metaType = null;\r
1072             for (int ctorParameterIndex = 0; ctorParameterIndex < newExpression.Arguments.Count; ctorParameterIndex++)\r
1073             {\r
1074                 var aliasedExpression = newExpression.Arguments[ctorParameterIndex] as E;\r
1075                 if (aliasedExpression == null && checkCast)\r
1076                     throw Error.BadArgument("S0541: Expected an specific Expression type for GetTypeInitializers()");\r
1077                 var memberInfo = newExpression.Members[ctorParameterIndex];\r
1078                 metaType = memberInfo.ReflectedType;\r
1079                 // the property info is the reflecting property for the memberInfo, if memberInfo is a get_*\r
1080                 // otherwise we keep the memberInfo as is, since it is a field\r
1081                 var propertyInfo = memberInfo.GetExposingProperty() ?? memberInfo;\r
1082                 associations[propertyInfo] = aliasedExpression;\r
1083             }\r
1084             if (metaType == null && checkCast)\r
1085                 throw Error.BadArgument("S0550: Empty NewExpression found"); // this should never happen, otherwise we may simply ignore it or take the type from elsewhere\r
1086             return associations;\r
1087         }\r
1088 \r
1089         //protected virtual IDictionary<MemberInfo, E> GetTypeInitializers<E>(NewExpression newExpression)\r
1090         //    where E : Expression\r
1091         //{\r
1092         //    Type metaType;\r
1093         //    return GetTypeInitializers<E>(newExpression, out metaType);\r
1094         //}\r
1095 \r
1096         /// <summary>\r
1097         /// Analyzes a Join statement (explicit join)\r
1098         /// </summary>\r
1099         /// <param name="parameters"></param>\r
1100         /// <param name="builderContext"></param>\r
1101         /// <returns></returns>\r
1102         protected virtual Expression AnalyzeJoin(IList<Expression> parameters, BuilderContext builderContext)\r
1103         {\r
1104             return AnalyzeJoin(parameters, TableJoinType.Inner, builderContext);\r
1105         }\r
1106 \r
1107         /// <summary>\r
1108         /// Analyzes a Join statement (explicit join)\r
1109         /// </summary>\r
1110         /// <param name="parameters"></param>\r
1111         /// <param name="builderContext"></param>\r
1112         /// <returns></returns>\r
1113         protected virtual Expression AnalyzeGroupJoin(IList<Expression> parameters, BuilderContext builderContext)\r
1114         {\r
1115             return AnalyzeJoin(parameters, TableJoinType.Inner, builderContext);\r
1116         }\r
1117 \r
1118         protected virtual Expression AnalyzeOuterJoin(IList<Expression> parameters, BuilderContext builderContext)\r
1119         {\r
1120             var expression = Analyze(parameters[0], builderContext);\r
1121             var tableExpression = expression as TableExpression;\r
1122             if (tableExpression != null)\r
1123             {\r
1124                 tableExpression.SetOuterJoin();\r
1125             }\r
1126             return expression;\r
1127         }\r
1128 \r
1129         private Expression AnalyzeJoin(IList<Expression> parameters, TableJoinType joinType, BuilderContext builderContext)\r
1130         {\r
1131             if (parameters.Count == 5)\r
1132             {\r
1133                 var leftExpression = Analyze(parameters[0], builderContext);\r
1134                 var rightTable = Analyze(parameters[1], builderContext) as TableExpression;\r
1135                 if (rightTable == null)\r
1136                     throw Error.BadArgument("S0536: Expected a TableExpression for Join");\r
1137                 var leftJoin = Analyze(parameters[2], leftExpression, builderContext);\r
1138                 var rightJoin = Analyze(parameters[3], rightTable, builderContext);\r
1139                 // from here, we have two options to join:\r
1140                 // 1. left and right are tables, we can use generic expressions (most common)\r
1141                 // 2. left is something else (a meta table)\r
1142                 var leftTable = leftExpression as TableExpression;\r
1143                 if (leftTable == null)\r
1144                 {\r
1145                     var leftColumn = leftJoin as ColumnExpression;\r
1146                     if (leftColumn == null)\r
1147                         throw Error.BadArgument("S0701: No way to find left table for Join");\r
1148                     leftTable = leftColumn.Table;\r
1149                 }\r
1150                 rightTable.Join(joinType, leftTable, Expression.Equal(leftJoin, rightJoin),\r
1151                                 string.Format("join{0}", builderContext.EnumerateAllTables().Count()));\r
1152                 // last part is lambda, with two tables as parameters\r
1153                 var metaTableDefinitionBuilderContext = builderContext.Clone();\r
1154                 metaTableDefinitionBuilderContext.ExpectMetaTableDefinition = true;\r
1155                 var expression = Analyze(parameters[4], new[] { leftExpression, rightTable }, metaTableDefinitionBuilderContext);\r
1156                 return expression;\r
1157             }\r
1158             throw Error.BadArgument("S0530: Don't know how to handle GroupJoin() with {0} parameters", parameters.Count);\r
1159         }\r
1160 \r
1161         /// <summary>\r
1162         /// "Distinct" means select X group by X\r
1163         /// </summary>\r
1164         /// <param name="parameters"></param>\r
1165         /// <param name="builderContext"></param>\r
1166         /// <returns></returns>\r
1167         protected virtual Expression AnalyzeDistinct(IList<Expression> parameters, BuilderContext builderContext)\r
1168         {\r
1169             var expression = Analyze(parameters[0], builderContext);\r
1170             // we select and group by the same criterion\r
1171             var group = new GroupExpression(expression, expression);\r
1172             builderContext.CurrentSelect.Group.Add(group);\r
1173             // "Distinct" method is equivalent to a GroupBy\r
1174             // but for some obscure reasons, Linq expects a IQueryable instead of an IGrouping\r
1175             // so we return the column, not the group\r
1176             return expression;\r
1177         }\r
1178 \r
1179         /// <summary>\r
1180         /// Creates a group by clause\r
1181         /// </summary>\r
1182         /// <param name="parameters"></param>\r
1183         /// <param name="builderContext"></param>\r
1184         /// <returns></returns>\r
1185         protected virtual Expression AnalyzeGroupBy(IList<Expression> parameters, BuilderContext builderContext)\r
1186         {\r
1187             var table = Analyze(parameters[0], builderContext);\r
1188             var keyExpression = Analyze(parameters[1], table, builderContext);\r
1189 \r
1190             Expression result;\r
1191             if (parameters.Count == 2)\r
1192                 result = table; // we return the whole table\r
1193             else if (parameters.Count == 3)\r
1194                 result = Analyze(parameters[2], table, builderContext); // 3 parameters for a projection expression\r
1195             else\r
1196                 throw Error.BadArgument("S0629: Don't know how to handle Expression to group by with {0} parameters", parameters.Count);\r
1197 \r
1198             var group = new GroupExpression(result, keyExpression);\r
1199             builderContext.CurrentSelect.Group.Add(group);\r
1200             return group;\r
1201         }\r
1202 \r
1203         /// <summary>\r
1204         /// All() returns true if the given condition satisfies all provided elements\r
1205         /// </summary>\r
1206         /// <param name="parameters"></param>\r
1207         /// <param name="builderContext"></param>\r
1208         /// <returns></returns>\r
1209         protected virtual Expression AnalyzeAll(IList<Expression> parameters, BuilderContext builderContext)\r
1210         {\r
1211             var allBuilderContext = builderContext.NewSelect();\r
1212             var tableExpression = Analyze(parameters[0], allBuilderContext);\r
1213             var allClause = Analyze(parameters[1], tableExpression, allBuilderContext);\r
1214             // from here we build a custom clause:\r
1215             // <allClause> ==> "(select count(*) from <table> where not <allClause>)==0"\r
1216             // TODO (later...): see if some vendors support native All operator and avoid this substitution\r
1217             var whereExpression = Expression.Not(allClause);\r
1218             RegisterWhere(whereExpression, allBuilderContext);\r
1219             allBuilderContext.CurrentSelect = allBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(SpecialExpressionType.Count, tableExpression));\r
1220             // TODO: see if we need to register the tablePiece here (we probably don't)\r
1221 \r
1222             // we now switch back to current context, and compare the result with 0\r
1223             var allExpression = Expression.Equal(allBuilderContext.CurrentSelect, Expression.Constant(0));\r
1224             return allExpression;\r
1225         }\r
1226 \r
1227         /// <summary>\r
1228         /// Any() returns true if the given condition satisfies at least one of provided elements\r
1229         /// </summary>\r
1230         /// <param name="parameters"></param>\r
1231         /// <param name="builderContext"></param>\r
1232         /// <returns></returns>\r
1233         protected virtual Expression AnalyzeAny(IList<Expression> parameters, BuilderContext builderContext)\r
1234         {\r
1235             if (builderContext.IsExternalInExpressionChain)\r
1236             {\r
1237                 var tableExpression = Analyze(parameters[0], builderContext);\r
1238                 Expression projectionOperand;\r
1239 \r
1240                 // basically, we have three options for projection methods:\r
1241                 // - projection on grouped table (1 operand, a GroupExpression)\r
1242                 // - projection on grouped column (2 operands, GroupExpression and ColumnExpression)\r
1243                 // - projection on table/column, with optional restriction\r
1244                 var groupOperand0 = tableExpression as GroupExpression;\r
1245                 if (groupOperand0 != null)\r
1246                 {\r
1247                     if (parameters.Count > 1)\r
1248                     {\r
1249                         projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression,\r
1250                                                     builderContext);\r
1251                     }\r
1252                     else\r
1253                         projectionOperand = Analyze(groupOperand0.GroupedExpression, builderContext);\r
1254                 }\r
1255                 else\r
1256                 {\r
1257                     projectionOperand = tableExpression;\r
1258                     CheckWhere(projectionOperand, parameters, 1, builderContext);\r
1259                 }\r
1260 \r
1261                 if (projectionOperand is TableExpression)\r
1262                     projectionOperand = RegisterTable((TableExpression)projectionOperand, builderContext);\r
1263 \r
1264                 if (groupOperand0 != null)\r
1265                     projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression);\r
1266 \r
1267                 return Expression.GreaterThan(new SpecialExpression(SpecialExpressionType.Count, projectionOperand), Expression.Constant(0));\r
1268             }\r
1269             else\r
1270             {\r
1271                 var anyBuilderContext = builderContext.NewSelect();\r
1272                 var tableExpression = Analyze(parameters[0], anyBuilderContext);\r
1273 \r
1274                 if (!(tableExpression is TableExpression))\r
1275                     tableExpression = Analyze(tableExpression, anyBuilderContext);\r
1276 \r
1277                 // from here we build a custom clause:\r
1278                 // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0"\r
1279                 // TODO (later...): see if some vendors support native Any operator and avoid this substitution\r
1280                 if (parameters.Count > 1)\r
1281                 {\r
1282                     var anyClause = Analyze(parameters[1], tableExpression, anyBuilderContext);\r
1283                     RegisterWhere(anyClause, anyBuilderContext);\r
1284                 }\r
1285                 anyBuilderContext.CurrentSelect = anyBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(SpecialExpressionType.Count, tableExpression));\r
1286                 // TODO: see if we need to register the tablePiece here (we probably don't)\r
1287 \r
1288                 // we now switch back to current context, and compare the result with 0\r
1289                 var anyExpression = Expression.GreaterThan(anyBuilderContext.CurrentSelect, Expression.Constant(0));\r
1290                 return anyExpression;\r
1291             }\r
1292         }\r
1293 \r
1294         protected virtual Expression AnalyzeLikeStart(IList<Expression> parameters, BuilderContext builderContext)\r
1295         {\r
1296             return AnalyzeLike(parameters[0], null, parameters[1], "%", builderContext);\r
1297         }\r
1298 \r
1299         protected virtual Expression AnalyzeLikeEnd(IList<Expression> parameters, BuilderContext builderContext)\r
1300         {\r
1301             return AnalyzeLike(parameters[0], "%", parameters[1], null, builderContext);\r
1302         }\r
1303 \r
1304         protected virtual Expression AnalyzeLike(IList<Expression> parameters, BuilderContext builderContext)\r
1305         {\r
1306             return AnalyzeLike(parameters[0], "%", parameters[1], "%", builderContext);\r
1307         }\r
1308 \r
1309         protected virtual Expression AnalyzeLike(Expression value, string before, Expression operand, string after, BuilderContext builderContext)\r
1310         {\r
1311             operand = Analyze(operand, builderContext);\r
1312             if (before != null)\r
1313                 operand = new SpecialExpression(SpecialExpressionType.Concat, Expression.Constant(before), operand);\r
1314             if (after != null)\r
1315                 operand = new SpecialExpression(SpecialExpressionType.Concat, operand, Expression.Constant(after));\r
1316             return new SpecialExpression(SpecialExpressionType.Like, Analyze(value, builderContext), operand);\r
1317         }\r
1318 \r
1319         protected virtual Expression AnalyzeSubString(IList<Expression> parameters, BuilderContext builderContext)\r
1320         {\r
1321             var stringExpression = Analyze(parameters[0], builderContext);\r
1322             var startExpression = new StartIndexOffsetExpression(builderContext.QueryContext.DataContext.Vendor.SqlProvider.StringIndexStartsAtOne,\r
1323                                                         Analyze(parameters[1], builderContext));\r
1324             if (parameters.Count > 2)\r
1325             {\r
1326                 var lengthExpression = parameters[2];\r
1327                 return new SpecialExpression(SpecialExpressionType.Substring, stringExpression, startExpression, lengthExpression);\r
1328             }\r
1329             return new SpecialExpression(SpecialExpressionType.Substring, stringExpression, startExpression);\r
1330         }\r
1331 \r
1332         protected virtual Expression AnalyzeContains(IList<Expression> parameters, BuilderContext builderContext)\r
1333         {\r
1334             if (parameters[0].Type.IsArray)\r
1335             {\r
1336                 var array = Analyze(parameters[0], builderContext);\r
1337                 var expression = Analyze(parameters[1], builderContext);\r
1338                 return new SpecialExpression(SpecialExpressionType.In, expression, array);\r
1339             }\r
1340             throw Error.BadArgument("S0548: Can't analyze Contains() method");\r
1341         }\r
1342 \r
1343         protected virtual Expression AnalyzeToUpper(IList<Expression> parameters, BuilderContext builderContext)\r
1344         {\r
1345             return new SpecialExpression(SpecialExpressionType.ToUpper, Analyze(parameters[0], builderContext));\r
1346         }\r
1347 \r
1348         protected virtual Expression AnalyzeToLower(IList<Expression> parameters, BuilderContext builderContext)\r
1349         {\r
1350             return new SpecialExpression(SpecialExpressionType.ToLower, Analyze(parameters[0], builderContext));\r
1351         }\r
1352 \r
1353         /// <summary>\r
1354         /// Registers ordering request\r
1355         /// </summary>\r
1356         /// <param name="parameters"></param>\r
1357         /// <param name="descending"></param>\r
1358         /// <param name="builderContext"></param>\r
1359         /// <returns></returns>\r
1360         protected virtual Expression AnalyzeOrderBy(IList<Expression> parameters, bool descending, BuilderContext builderContext)\r
1361         {\r
1362             var table = Analyze(parameters[0], builderContext);\r
1363             // the column is related to table\r
1364             var column = Analyze(parameters[1], table, builderContext);\r
1365             builderContext.CurrentSelect.OrderBy.Add(new OrderByExpression(descending, column));\r
1366             return table;\r
1367         }\r
1368 \r
1369         /// <summary>\r
1370         /// Analyzes constant expression value, and eventually extracts a table\r
1371         /// </summary>\r
1372         /// <param name="expression"></param>\r
1373         /// <param name="builderContext"></param>\r
1374         /// <returns></returns>\r
1375         protected virtual Expression AnalyzeConstant(Expression expression, BuilderContext builderContext)\r
1376         {\r
1377             var constantExpression = expression as ConstantExpression;\r
1378             if (constantExpression != null)\r
1379             {\r
1380                 var queriedType = GetQueriedType(expression);\r
1381                 if (queriedType != null)\r
1382                 {\r
1383 \r
1384                 }\r
1385                 if (constantExpression.Value is ITable)\r
1386                 {\r
1387                     var tableType = constantExpression.Type.GetGenericArguments()[0];\r
1388                     return new TableExpression(tableType, DataMapper.GetTableName(tableType, builderContext.QueryContext.DataContext));\r
1389                 }\r
1390             }\r
1391             return expression;\r
1392         }\r
1393 \r
1394         protected virtual Expression AnalyzeSelectOperation(SelectOperatorType operatorType, IList<Expression> parameters, BuilderContext builderContext)\r
1395         {\r
1396             // a special case: if we have several SELECT expressions linked together,\r
1397             // we maximize the load to the database, since the result must use the same parameters\r
1398             // types and count.\r
1399             builderContext.QueryContext.MaximumDatabaseLoad = true; // all select expression goes to SQL tier\r
1400 \r
1401             var currentSelect = builderContext.CurrentSelect;\r
1402             var nextSelect = new SelectExpression(currentSelect.Parent);\r
1403             builderContext.CurrentSelect = nextSelect;\r
1404 \r
1405             // TODO: this is very dirty code, unreliable, unstable, and unlovable\r
1406             var constantExpression = parameters[1] as ConstantExpression;\r
1407             var queryProvider = (QueryProvider)constantExpression.Value;\r
1408             var expressionChain = queryProvider.ExpressionChain;\r
1409             var table = queryProvider.TableType;\r
1410             var queryBuilder = ObjectFactory.Get<QueryBuilder>();\r
1411             var tableExpression = new TableExpression(table,\r
1412                                                       DataMapper.GetTableName(table,\r
1413                                                                               builderContext.QueryContext.DataContext));\r
1414             // /TODO\r
1415             var selectOperandExpression = queryBuilder.BuildSelectExpression(expressionChain, tableExpression, builderContext);\r
1416             builderContext.SelectExpressions.Add(selectOperandExpression);\r
1417 \r
1418             nextSelect = builderContext.CurrentSelect;\r
1419             builderContext.CurrentSelect = currentSelect;\r
1420             currentSelect.NextSelectExpression = nextSelect;\r
1421             currentSelect.NextSelectExpressionOperator = operatorType;\r
1422 \r
1423             return Analyze(parameters[0], builderContext);\r
1424         }\r
1425 \r
1426         /// <summary>\r
1427         /// Analyses InvokeExpression\r
1428         /// </summary>\r
1429         /// <param name="expression"></param>\r
1430         /// <param name="parameters"></param>\r
1431         /// <param name="builderContext"></param>\r
1432         /// <returns></returns>\r
1433         protected virtual Expression AnalyzeInvoke(Expression expression, IList<Expression> parameters,\r
1434                                                    BuilderContext builderContext)\r
1435         {\r
1436             var invocationExpression = (InvocationExpression)expression;\r
1437             var lambda = invocationExpression.Expression as LambdaExpression;\r
1438             if (lambda != null)\r
1439             {\r
1440                 var localBuilderContext = builderContext.NewQuote();\r
1441                 //for (int parameterIndex = 0; parameterIndex < lambda.Parameters.Count; parameterIndex++)\r
1442                 //{\r
1443                 //    var parameter = lambda.Parameters[parameterIndex];\r
1444                 //    localBuilderContext.Parameters[parameter.Name] = Analyze(invocationExpression.Arguments[parameterIndex], builderContext);\r
1445                 //}\r
1446                 //return Analyze(lambda, localBuilderContext);\r
1447                 return Analyze(lambda, invocationExpression.Arguments, localBuilderContext);\r
1448             }\r
1449             // TODO: see what we must do here\r
1450             return expression;\r
1451         }\r
1452     }\r
1453 }\r