diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs | 992 |
1 files changed, 992 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs new file mode 100644 index 00000000..ea58757c --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs | |||
@@ -0,0 +1,992 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.WindowsInstaller.Linq | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections; | ||
8 | using System.Collections.Generic; | ||
9 | using System.Linq; | ||
10 | using System.Linq.Expressions; | ||
11 | using System.Text; | ||
12 | using System.Reflection; | ||
13 | using System.Globalization; | ||
14 | using System.Diagnostics.CodeAnalysis; | ||
15 | |||
16 | /// <summary> | ||
17 | /// Implements the LINQ to MSI query functionality. | ||
18 | /// </summary> | ||
19 | /// <typeparam name="T">the result type of the current query -- | ||
20 | /// either some kind of QRecord, or some projection of record data</typeparam> | ||
21 | internal sealed class Query<T> : IOrderedQueryable<T>, IQueryProvider | ||
22 | { | ||
23 | private QDatabase db; | ||
24 | private Expression queryableExpression; | ||
25 | private List<TableInfo> tables; | ||
26 | private List<Type> recordTypes; | ||
27 | private List<string> selectors; | ||
28 | private string where; | ||
29 | private List<object> whereParameters; | ||
30 | private List<TableColumn> orderbyColumns; | ||
31 | private List<TableColumn> selectColumns; | ||
32 | private List<TableColumn> joinColumns; | ||
33 | private List<Delegate> projectionDelegates; | ||
34 | |||
35 | internal Query(QDatabase db, Expression expression) | ||
36 | { | ||
37 | if (expression == null) | ||
38 | { | ||
39 | throw new ArgumentNullException("expression"); | ||
40 | } | ||
41 | |||
42 | this.db = db; | ||
43 | this.queryableExpression = expression; | ||
44 | this.tables = new List<TableInfo>(); | ||
45 | this.recordTypes = new List<Type>(); | ||
46 | this.selectors = new List<string>(); | ||
47 | this.whereParameters = new List<object>(); | ||
48 | this.orderbyColumns = new List<TableColumn>(); | ||
49 | this.selectColumns = new List<TableColumn>(); | ||
50 | this.joinColumns = new List<TableColumn>(); | ||
51 | this.projectionDelegates = new List<Delegate>(); | ||
52 | } | ||
53 | |||
54 | public IEnumerator<T> GetEnumerator() | ||
55 | { | ||
56 | if (this.selectColumns.Count == 0) | ||
57 | { | ||
58 | AddAllColumns(this.tables[0], this.selectColumns); | ||
59 | } | ||
60 | |||
61 | string query = this.CompileQuery(); | ||
62 | return this.InvokeQuery(query); | ||
63 | } | ||
64 | |||
65 | private string CompileQuery() | ||
66 | { | ||
67 | bool explicitTables = this.tables.Count > 1; | ||
68 | |||
69 | StringBuilder queryBuilder = new StringBuilder("SELECT"); | ||
70 | |||
71 | for (int i = 0; i < this.selectColumns.Count; i++) | ||
72 | { | ||
73 | queryBuilder.AppendFormat( | ||
74 | CultureInfo.InvariantCulture, | ||
75 | (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"), | ||
76 | (i > 0 ? "," : String.Empty), | ||
77 | this.selectColumns[i].Table.Name, | ||
78 | this.selectColumns[i].Column.Name); | ||
79 | } | ||
80 | |||
81 | for (int i = 0; i < this.tables.Count; i++) | ||
82 | { | ||
83 | queryBuilder.AppendFormat( | ||
84 | CultureInfo.InvariantCulture, | ||
85 | "{0} `{1}`", | ||
86 | (i == 0 ? " FROM" : ","), | ||
87 | this.tables[i].Name); | ||
88 | } | ||
89 | |||
90 | bool startedWhere = false; | ||
91 | for (int i = 0; i < this.joinColumns.Count - 1; i += 2) | ||
92 | { | ||
93 | queryBuilder.AppendFormat( | ||
94 | CultureInfo.InvariantCulture, | ||
95 | "{0} `{1}`.`{2}` = `{3}`.`{4}` ", | ||
96 | (i == 0 ? " WHERE" : "AND"), | ||
97 | this.joinColumns[i].Table, | ||
98 | this.joinColumns[i].Column, | ||
99 | this.joinColumns[i + 1].Table, | ||
100 | this.joinColumns[i + 1].Column); | ||
101 | startedWhere = true; | ||
102 | } | ||
103 | |||
104 | if (this.where != null) | ||
105 | { | ||
106 | queryBuilder.Append(startedWhere ? "AND " : " WHERE"); | ||
107 | queryBuilder.Append(this.where); | ||
108 | } | ||
109 | |||
110 | for (int i = 0; i < this.orderbyColumns.Count; i++) | ||
111 | { | ||
112 | VerifyOrderByColumn(this.orderbyColumns[i]); | ||
113 | |||
114 | queryBuilder.AppendFormat( | ||
115 | CultureInfo.InvariantCulture, | ||
116 | (explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"), | ||
117 | (i == 0 ? " ORDER BY" : ","), | ||
118 | this.orderbyColumns[i].Table.Name, | ||
119 | this.orderbyColumns[i].Column.Name); | ||
120 | } | ||
121 | |||
122 | return queryBuilder.ToString(); | ||
123 | } | ||
124 | |||
125 | private static void VerifyOrderByColumn(TableColumn tableColumn) | ||
126 | { | ||
127 | if (tableColumn.Column.Type != typeof(int) && | ||
128 | tableColumn.Column.Type != typeof(short)) | ||
129 | { | ||
130 | throw new NotSupportedException( | ||
131 | "Cannot orderby column: " + tableColumn.Column.Name + | ||
132 | "; orderby is only supported on integer fields"); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | private IEnumerator<T> InvokeQuery(string query) | ||
137 | { | ||
138 | TextWriter log = this.db.Log; | ||
139 | if (log != null) | ||
140 | { | ||
141 | log.WriteLine(); | ||
142 | log.WriteLine(query); | ||
143 | } | ||
144 | |||
145 | using (View queryView = this.db.OpenView(query)) | ||
146 | { | ||
147 | if (this.whereParameters != null && this.whereParameters.Count > 0) | ||
148 | { | ||
149 | using (Record paramsRec = this.db.CreateRecord(this.whereParameters.Count)) | ||
150 | { | ||
151 | for (int i = 0; i < this.whereParameters.Count; i++) | ||
152 | { | ||
153 | paramsRec[i + 1] = this.whereParameters[i]; | ||
154 | |||
155 | if (log != null) | ||
156 | { | ||
157 | log.WriteLine(" ? = " + this.whereParameters[i]); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | queryView.Execute(paramsRec); | ||
162 | } | ||
163 | } | ||
164 | else | ||
165 | { | ||
166 | queryView.Execute(); | ||
167 | } | ||
168 | |||
169 | foreach (Record resultRec in queryView) using (resultRec) | ||
170 | { | ||
171 | yield return this.GetResult(resultRec); | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | |||
176 | private T GetResult(Record resultRec) | ||
177 | { | ||
178 | object[] results = new object[this.tables.Count]; | ||
179 | |||
180 | for (int i = 0; i < this.tables.Count; i++) | ||
181 | { | ||
182 | string[] values = new string[this.tables[i].Columns.Count]; | ||
183 | for (int j = 0; j < this.selectColumns.Count; j++) | ||
184 | { | ||
185 | TableColumn col = this.selectColumns[j]; | ||
186 | if (col.Table.Name == this.tables[i].Name) | ||
187 | { | ||
188 | int index = this.tables[i].Columns.IndexOf( | ||
189 | col.Column.Name); | ||
190 | if (index >= 0) | ||
191 | { | ||
192 | if (col.Column.Type == typeof(Stream)) | ||
193 | { | ||
194 | values[index] = "[Binary Data]"; | ||
195 | } | ||
196 | else | ||
197 | { | ||
198 | values[index] = resultRec.GetString(j + 1); | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
204 | QRecord result = (QRecord) this.recordTypes[i] | ||
205 | .GetConstructor(Type.EmptyTypes).Invoke(null); | ||
206 | result.Database = this.db; | ||
207 | result.TableInfo = this.tables[i]; | ||
208 | result.Values = values; | ||
209 | result.Exists = true; | ||
210 | results[i] = result; | ||
211 | } | ||
212 | |||
213 | if (this.projectionDelegates.Count > 0) | ||
214 | { | ||
215 | object resultsProjection = results[0]; | ||
216 | for (int i = 1; i <= results.Length; i++) | ||
217 | { | ||
218 | if (i < results.Length) | ||
219 | { | ||
220 | resultsProjection = this.projectionDelegates[i - 1] | ||
221 | .DynamicInvoke(new object[] { resultsProjection, results[i] }); | ||
222 | } | ||
223 | else | ||
224 | { | ||
225 | resultsProjection = this.projectionDelegates[i - 1] | ||
226 | .DynamicInvoke(resultsProjection); | ||
227 | } | ||
228 | } | ||
229 | |||
230 | return (T) resultsProjection; | ||
231 | } | ||
232 | else | ||
233 | { | ||
234 | return (T) (object) results[0]; | ||
235 | } | ||
236 | } | ||
237 | |||
238 | IEnumerator IEnumerable.GetEnumerator() | ||
239 | { | ||
240 | return ((IEnumerable<T>) this).GetEnumerator(); | ||
241 | } | ||
242 | |||
243 | public IQueryable<TElement> CreateQuery<TElement>(Expression expression) | ||
244 | { | ||
245 | if (expression == null) | ||
246 | { | ||
247 | throw new ArgumentNullException("expression"); | ||
248 | } | ||
249 | |||
250 | Query<TElement> q = new Query<TElement>(this.db, expression); | ||
251 | q.tables.AddRange(this.tables); | ||
252 | q.recordTypes.AddRange(this.recordTypes); | ||
253 | q.selectors.AddRange(this.selectors); | ||
254 | q.where = this.where; | ||
255 | q.whereParameters.AddRange(this.whereParameters); | ||
256 | q.orderbyColumns.AddRange(this.orderbyColumns); | ||
257 | q.selectColumns.AddRange(this.selectColumns); | ||
258 | q.joinColumns.AddRange(this.joinColumns); | ||
259 | q.projectionDelegates.AddRange(this.projectionDelegates); | ||
260 | |||
261 | MethodCallExpression methodCallExpression = (MethodCallExpression) expression; | ||
262 | string methodName = methodCallExpression.Method.Name; | ||
263 | if (methodName == "Select") | ||
264 | { | ||
265 | LambdaExpression argumentExpression = (LambdaExpression) | ||
266 | ((UnaryExpression) methodCallExpression.Arguments[1]).Operand; | ||
267 | q.BuildProjection(null, argumentExpression); | ||
268 | } | ||
269 | else if (methodName == "Where") | ||
270 | { | ||
271 | LambdaExpression argumentExpression = (LambdaExpression) | ||
272 | ((UnaryExpression) methodCallExpression.Arguments[1]).Operand; | ||
273 | q.BuildQuery(null, argumentExpression); | ||
274 | } | ||
275 | else if (methodName == "ThenBy") | ||
276 | { | ||
277 | LambdaExpression argumentExpression = (LambdaExpression) | ||
278 | ((UnaryExpression) methodCallExpression.Arguments[1]).Operand; | ||
279 | q.BuildSequence(null, argumentExpression); | ||
280 | } | ||
281 | else if (methodName == "Join") | ||
282 | { | ||
283 | ConstantExpression constantExpression = (ConstantExpression) | ||
284 | methodCallExpression.Arguments[1]; | ||
285 | IQueryable inner = (IQueryable) constantExpression.Value; | ||
286 | q.PerformJoin( | ||
287 | null, | ||
288 | null, | ||
289 | inner, | ||
290 | GetJoinLambda(methodCallExpression.Arguments[2]), | ||
291 | GetJoinLambda(methodCallExpression.Arguments[3]), | ||
292 | GetJoinLambda(methodCallExpression.Arguments[4])); | ||
293 | } | ||
294 | else | ||
295 | { | ||
296 | throw new NotSupportedException( | ||
297 | "Query operation not supported: " + methodName); | ||
298 | } | ||
299 | |||
300 | return q; | ||
301 | } | ||
302 | |||
303 | public IQueryable CreateQuery(Expression expression) | ||
304 | { | ||
305 | return this.CreateQuery<T>(expression); | ||
306 | } | ||
307 | |||
308 | private static LambdaExpression GetJoinLambda(Expression expresion) | ||
309 | { | ||
310 | UnaryExpression unaryExpression = (UnaryExpression) expresion; | ||
311 | return (LambdaExpression) unaryExpression.Operand; | ||
312 | } | ||
313 | |||
314 | public TResult Execute<TResult>(Expression expression) | ||
315 | { | ||
316 | throw new NotSupportedException( | ||
317 | "Direct method calls not supported -- use AsEnumerable() instead."); | ||
318 | } | ||
319 | |||
320 | object IQueryProvider.Execute(Expression expression) | ||
321 | { | ||
322 | throw new NotSupportedException( | ||
323 | "Direct method calls not supported -- use AsEnumerable() instead."); | ||
324 | } | ||
325 | |||
326 | public IQueryProvider Provider | ||
327 | { | ||
328 | get | ||
329 | { | ||
330 | return this; | ||
331 | } | ||
332 | } | ||
333 | |||
334 | public Type ElementType | ||
335 | { | ||
336 | get | ||
337 | { | ||
338 | return typeof(T); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | public Expression Expression | ||
343 | { | ||
344 | get | ||
345 | { | ||
346 | return this.queryableExpression; | ||
347 | } | ||
348 | } | ||
349 | |||
350 | internal void BuildQuery(TableInfo tableInfo, LambdaExpression expression) | ||
351 | { | ||
352 | if (tableInfo != null) | ||
353 | { | ||
354 | this.tables.Add(tableInfo); | ||
355 | this.recordTypes.Add(typeof(T)); | ||
356 | this.selectors.Add(expression.Parameters[0].Name); | ||
357 | } | ||
358 | |||
359 | StringBuilder queryBuilder = new StringBuilder(); | ||
360 | |||
361 | this.ParseQuery(expression.Body, queryBuilder); | ||
362 | |||
363 | this.where = queryBuilder.ToString(); | ||
364 | } | ||
365 | |||
366 | internal void BuildNullQuery(TableInfo tableInfo, Type recordType, LambdaExpression expression) | ||
367 | { | ||
368 | this.tables.Add(tableInfo); | ||
369 | this.recordTypes.Add(recordType); | ||
370 | this.selectors.Add(expression.Parameters[0].Name); | ||
371 | } | ||
372 | |||
373 | private void ParseQuery(Expression expression, StringBuilder queryBuilder) | ||
374 | { | ||
375 | queryBuilder.Append("("); | ||
376 | |||
377 | BinaryExpression binaryExpression; | ||
378 | UnaryExpression unaryExpression; | ||
379 | MethodCallExpression methodCallExpression; | ||
380 | |||
381 | if ((binaryExpression = expression as BinaryExpression) != null) | ||
382 | { | ||
383 | switch (binaryExpression.NodeType) | ||
384 | { | ||
385 | case ExpressionType.AndAlso: | ||
386 | this.ParseQuery(binaryExpression.Left, queryBuilder); | ||
387 | queryBuilder.Append(" AND "); | ||
388 | this.ParseQuery(binaryExpression.Right, queryBuilder); | ||
389 | break; | ||
390 | |||
391 | case ExpressionType.OrElse: | ||
392 | this.ParseQuery(binaryExpression.Left, queryBuilder); | ||
393 | queryBuilder.Append(" OR "); | ||
394 | this.ParseQuery(binaryExpression.Right, queryBuilder); | ||
395 | break; | ||
396 | |||
397 | case ExpressionType.Equal: | ||
398 | case ExpressionType.NotEqual: | ||
399 | case ExpressionType.GreaterThan: | ||
400 | case ExpressionType.LessThan: | ||
401 | case ExpressionType.GreaterThanOrEqual: | ||
402 | case ExpressionType.LessThanOrEqual: | ||
403 | this.ParseQueryCondition(binaryExpression, queryBuilder); | ||
404 | break; | ||
405 | |||
406 | default: | ||
407 | throw new NotSupportedException( | ||
408 | "Expression type not supported: " + binaryExpression.NodeType ); | ||
409 | } | ||
410 | } | ||
411 | else if ((unaryExpression = expression as UnaryExpression) != null) | ||
412 | { | ||
413 | throw new NotSupportedException( | ||
414 | "Expression type not supported: " + unaryExpression.NodeType); | ||
415 | } | ||
416 | else if ((methodCallExpression = expression as MethodCallExpression) != null) | ||
417 | { | ||
418 | throw new NotSupportedException( | ||
419 | "Method call not supported: " + methodCallExpression.Method.Name + "()"); | ||
420 | } | ||
421 | else | ||
422 | { | ||
423 | throw new NotSupportedException( | ||
424 | "Query filter expression not supported: " + expression); | ||
425 | } | ||
426 | |||
427 | queryBuilder.Append(")"); | ||
428 | } | ||
429 | |||
430 | private static ExpressionType OppositeExpression(ExpressionType e) | ||
431 | { | ||
432 | switch (e) | ||
433 | { | ||
434 | case ExpressionType.LessThan: | ||
435 | return ExpressionType.GreaterThan; | ||
436 | case ExpressionType.LessThanOrEqual: | ||
437 | return ExpressionType.GreaterThanOrEqual; | ||
438 | case ExpressionType.GreaterThan: | ||
439 | return ExpressionType.LessThan; | ||
440 | case ExpressionType.GreaterThanOrEqual: | ||
441 | return ExpressionType.LessThanOrEqual; | ||
442 | default: | ||
443 | return e; | ||
444 | } | ||
445 | } | ||
446 | |||
447 | private static bool IsIntegerType(Type t) | ||
448 | { | ||
449 | return | ||
450 | t == typeof(sbyte) || | ||
451 | t == typeof(byte) || | ||
452 | t == typeof(short) || | ||
453 | t == typeof(ushort) || | ||
454 | t == typeof(int) || | ||
455 | t == typeof(uint) || | ||
456 | t == typeof(long) || | ||
457 | t == typeof(ulong); | ||
458 | } | ||
459 | |||
460 | private void ParseQueryCondition( | ||
461 | BinaryExpression binaryExpression, StringBuilder queryBuilder) | ||
462 | { | ||
463 | bool swap; | ||
464 | string column = this.GetConditionColumn(binaryExpression, out swap); | ||
465 | queryBuilder.Append(column); | ||
466 | |||
467 | ExpressionType expressionType = binaryExpression.NodeType; | ||
468 | if (swap) | ||
469 | { | ||
470 | expressionType = OppositeExpression(expressionType); | ||
471 | } | ||
472 | |||
473 | LambdaExpression valueExpression = Expression.Lambda( | ||
474 | swap ? binaryExpression.Left : binaryExpression.Right); | ||
475 | object value = valueExpression.Compile().DynamicInvoke(); | ||
476 | |||
477 | bool valueIsInt = false; | ||
478 | if (value != null) | ||
479 | { | ||
480 | if (IsIntegerType(value.GetType())) | ||
481 | { | ||
482 | valueIsInt = true; | ||
483 | } | ||
484 | else | ||
485 | { | ||
486 | value = value.ToString(); | ||
487 | } | ||
488 | } | ||
489 | |||
490 | switch (expressionType) | ||
491 | { | ||
492 | case ExpressionType.Equal: | ||
493 | if (value == null) | ||
494 | { | ||
495 | queryBuilder.Append(" IS NULL"); | ||
496 | } | ||
497 | else if (valueIsInt) | ||
498 | { | ||
499 | queryBuilder.Append(" = "); | ||
500 | queryBuilder.Append(value); | ||
501 | } | ||
502 | else | ||
503 | { | ||
504 | queryBuilder.Append(" = ?"); | ||
505 | this.whereParameters.Add(value); | ||
506 | } | ||
507 | return; | ||
508 | |||
509 | case ExpressionType.NotEqual: | ||
510 | if (value == null) | ||
511 | { | ||
512 | queryBuilder.Append(" IS NOT NULL"); | ||
513 | } | ||
514 | else if (valueIsInt) | ||
515 | { | ||
516 | queryBuilder.Append(" <> "); | ||
517 | queryBuilder.Append(value); | ||
518 | } | ||
519 | else | ||
520 | { | ||
521 | queryBuilder.Append(" <> ?"); | ||
522 | this.whereParameters.Add(value); | ||
523 | } | ||
524 | return; | ||
525 | } | ||
526 | |||
527 | if (value == null) | ||
528 | { | ||
529 | throw new InvalidOperationException( | ||
530 | "A null value was used in a greater-than/less-than operation."); | ||
531 | } | ||
532 | |||
533 | if (!valueIsInt) | ||
534 | { | ||
535 | throw new NotSupportedException( | ||
536 | "Greater-than/less-than operators not supported on strings."); | ||
537 | } | ||
538 | |||
539 | switch (expressionType) | ||
540 | { | ||
541 | case ExpressionType.LessThan: | ||
542 | queryBuilder.Append(" < "); | ||
543 | break; | ||
544 | |||
545 | case ExpressionType.LessThanOrEqual: | ||
546 | queryBuilder.Append(" <= "); | ||
547 | break; | ||
548 | |||
549 | case ExpressionType.GreaterThan: | ||
550 | queryBuilder.Append(" > "); | ||
551 | break; | ||
552 | |||
553 | case ExpressionType.GreaterThanOrEqual: | ||
554 | queryBuilder.Append(" >= "); | ||
555 | break; | ||
556 | |||
557 | default: | ||
558 | throw new NotSupportedException( | ||
559 | "Unsupported query expression type: " + expressionType); | ||
560 | } | ||
561 | |||
562 | queryBuilder.Append(value); | ||
563 | } | ||
564 | |||
565 | private string GetConditionColumn( | ||
566 | BinaryExpression binaryExpression, out bool swap) | ||
567 | { | ||
568 | MemberExpression memberExpression; | ||
569 | MethodCallExpression methodCallExpression; | ||
570 | |||
571 | if (((memberExpression = binaryExpression.Left as MemberExpression) != null) || | ||
572 | ((binaryExpression.Left.NodeType == ExpressionType.Convert || | ||
573 | binaryExpression.Left.NodeType == ExpressionType.ConvertChecked) && | ||
574 | (memberExpression = ((UnaryExpression) binaryExpression.Left).Operand | ||
575 | as MemberExpression) != null)) | ||
576 | { | ||
577 | string column = this.GetConditionColumn(memberExpression); | ||
578 | if (column != null) | ||
579 | { | ||
580 | swap = false; | ||
581 | return column; | ||
582 | } | ||
583 | } | ||
584 | else if (((memberExpression = binaryExpression.Right as MemberExpression) != null) || | ||
585 | ((binaryExpression.Right.NodeType == ExpressionType.Convert || | ||
586 | binaryExpression.Right.NodeType == ExpressionType.ConvertChecked) && | ||
587 | (memberExpression = ((UnaryExpression) binaryExpression.Right).Operand | ||
588 | as MemberExpression) != null)) | ||
589 | { | ||
590 | string column = this.GetConditionColumn(memberExpression); | ||
591 | if (column != null) | ||
592 | { | ||
593 | swap = true; | ||
594 | return column; | ||
595 | } | ||
596 | } | ||
597 | else if ((methodCallExpression = binaryExpression.Left as MethodCallExpression) != null) | ||
598 | { | ||
599 | string column = this.GetConditionColumn(methodCallExpression); | ||
600 | if (column != null) | ||
601 | { | ||
602 | swap = false; | ||
603 | return column; | ||
604 | } | ||
605 | } | ||
606 | else if ((methodCallExpression = binaryExpression.Right as MethodCallExpression) != null) | ||
607 | { | ||
608 | string column = this.GetConditionColumn(methodCallExpression); | ||
609 | if (column != null) | ||
610 | { | ||
611 | swap = true; | ||
612 | return column; | ||
613 | } | ||
614 | } | ||
615 | |||
616 | throw new NotSupportedException( | ||
617 | "Unsupported binary expression: " + binaryExpression); | ||
618 | } | ||
619 | |||
620 | private string GetConditionColumn(MemberExpression memberExpression) | ||
621 | { | ||
622 | string columnName = GetColumnName(memberExpression.Member); | ||
623 | string selectorName = GetConditionSelectorName(memberExpression.Expression); | ||
624 | string tableName = this.GetConditionTable(selectorName, columnName); | ||
625 | return this.FormatColumn(tableName, columnName); | ||
626 | } | ||
627 | |||
628 | private string GetConditionColumn(MethodCallExpression methodCallExpression) | ||
629 | { | ||
630 | LambdaExpression argumentExpression = | ||
631 | Expression.Lambda(methodCallExpression.Arguments[0]); | ||
632 | string columnName = (string) argumentExpression.Compile().DynamicInvoke(); | ||
633 | string selectorName = GetConditionSelectorName(methodCallExpression.Object); | ||
634 | string tableName = this.GetConditionTable(selectorName, columnName); | ||
635 | return this.FormatColumn(tableName, columnName); | ||
636 | } | ||
637 | |||
638 | private static string GetConditionSelectorName(Expression expression) | ||
639 | { | ||
640 | ParameterExpression parameterExpression; | ||
641 | MemberExpression memberExpression; | ||
642 | if ((parameterExpression = expression as ParameterExpression) != null) | ||
643 | { | ||
644 | return parameterExpression.Name; | ||
645 | } | ||
646 | else if ((memberExpression = expression as MemberExpression) != null) | ||
647 | { | ||
648 | return memberExpression.Member.Name; | ||
649 | } | ||
650 | else | ||
651 | { | ||
652 | throw new NotSupportedException( | ||
653 | "Unsupported conditional selector expression: " + expression); | ||
654 | } | ||
655 | } | ||
656 | |||
657 | private string GetConditionTable(string selectorName, string columnName) | ||
658 | { | ||
659 | string tableName = null; | ||
660 | |||
661 | for (int i = 0; i < this.tables.Count; i++) | ||
662 | { | ||
663 | if (this.selectors[i] == selectorName) | ||
664 | { | ||
665 | tableName = this.tables[i].Name; | ||
666 | break; | ||
667 | } | ||
668 | } | ||
669 | |||
670 | if (tableName == null) | ||
671 | { | ||
672 | throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, | ||
673 | "Conditional expression contains column {0}.{1} " + | ||
674 | "from a table that is not in the query.", | ||
675 | selectorName, | ||
676 | columnName)); | ||
677 | } | ||
678 | |||
679 | return tableName; | ||
680 | } | ||
681 | |||
682 | private string FormatColumn(string tableName, string columnName) | ||
683 | { | ||
684 | if (tableName != null && this.tables.Count > 1) | ||
685 | { | ||
686 | return String.Format(CultureInfo.InvariantCulture, "`{0}`.`{1}`", tableName, columnName); | ||
687 | } | ||
688 | else | ||
689 | { | ||
690 | return String.Format(CultureInfo.InvariantCulture, "`{0}`", columnName); | ||
691 | } | ||
692 | } | ||
693 | |||
694 | private static string GetColumnName(MemberInfo memberInfo) | ||
695 | { | ||
696 | foreach (var attr in memberInfo.GetCustomAttributes( | ||
697 | typeof(DatabaseColumnAttribute), false)) | ||
698 | { | ||
699 | return ((DatabaseColumnAttribute) attr).Column; | ||
700 | } | ||
701 | |||
702 | return memberInfo.Name; | ||
703 | } | ||
704 | |||
705 | internal void BuildProjection(TableInfo tableInfo, LambdaExpression expression) | ||
706 | { | ||
707 | if (tableInfo != null) | ||
708 | { | ||
709 | this.tables.Add(tableInfo); | ||
710 | this.recordTypes.Add(typeof(T)); | ||
711 | this.selectors.Add(expression.Parameters[0].Name); | ||
712 | } | ||
713 | |||
714 | this.FindColumns(expression, this.selectColumns); | ||
715 | this.projectionDelegates.Add(expression.Compile()); | ||
716 | } | ||
717 | |||
718 | internal void BuildSequence(TableInfo tableInfo, LambdaExpression expression) | ||
719 | { | ||
720 | if (tableInfo != null) | ||
721 | { | ||
722 | this.tables.Add(tableInfo); | ||
723 | this.recordTypes.Add(typeof(T)); | ||
724 | this.selectors.Add(expression.Parameters[0].Name); | ||
725 | } | ||
726 | |||
727 | this.FindColumns(expression.Body, this.orderbyColumns); | ||
728 | } | ||
729 | |||
730 | private static void AddAllColumns(TableInfo tableInfo, IList<TableColumn> columnList) | ||
731 | { | ||
732 | foreach (ColumnInfo column in tableInfo.Columns) | ||
733 | { | ||
734 | columnList.Add(new TableColumn(tableInfo, column)); | ||
735 | } | ||
736 | } | ||
737 | |||
738 | [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] | ||
739 | private void FindColumns(Expression expression, IList<TableColumn> columnList) | ||
740 | { | ||
741 | if (expression is ParameterExpression) | ||
742 | { | ||
743 | ParameterExpression e = expression as ParameterExpression; | ||
744 | string selector = e.Name; | ||
745 | for (int i = 0; i < this.tables.Count; i++) | ||
746 | { | ||
747 | if (this.selectors[i] == selector) | ||
748 | { | ||
749 | AddAllColumns(this.tables[i], columnList); | ||
750 | break; | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | else if (expression.NodeType == ExpressionType.MemberAccess) | ||
755 | { | ||
756 | this.FindColumns(expression as MemberExpression, columnList); | ||
757 | } | ||
758 | else if (expression is MethodCallExpression) | ||
759 | { | ||
760 | this.FindColumns(expression as MethodCallExpression, columnList); | ||
761 | } | ||
762 | else if (expression is BinaryExpression) | ||
763 | { | ||
764 | BinaryExpression e = expression as BinaryExpression; | ||
765 | this.FindColumns(e.Left, columnList); | ||
766 | this.FindColumns(e.Right, columnList); | ||
767 | } | ||
768 | else if (expression is UnaryExpression) | ||
769 | { | ||
770 | UnaryExpression e = expression as UnaryExpression; | ||
771 | this.FindColumns(e.Operand, columnList); | ||
772 | } | ||
773 | else if (expression is ConditionalExpression) | ||
774 | { | ||
775 | ConditionalExpression e = expression as ConditionalExpression; | ||
776 | this.FindColumns(e.Test, columnList); | ||
777 | this.FindColumns(e.IfTrue, columnList); | ||
778 | this.FindColumns(e.IfFalse, columnList); | ||
779 | } | ||
780 | else if (expression is InvocationExpression) | ||
781 | { | ||
782 | InvocationExpression e = expression as InvocationExpression; | ||
783 | this.FindColumns(e.Expression, columnList); | ||
784 | this.FindColumns(e.Arguments, columnList); | ||
785 | } | ||
786 | else if (expression is LambdaExpression) | ||
787 | { | ||
788 | LambdaExpression e = expression as LambdaExpression; | ||
789 | this.FindColumns(e.Body, columnList); | ||
790 | } | ||
791 | else if (expression is ListInitExpression) | ||
792 | { | ||
793 | ListInitExpression e = expression as ListInitExpression; | ||
794 | this.FindColumns(e.NewExpression, columnList); | ||
795 | foreach (ElementInit ei in e.Initializers) | ||
796 | { | ||
797 | this.FindColumns(ei.Arguments, columnList); | ||
798 | } | ||
799 | } | ||
800 | else if (expression is MemberInitExpression) | ||
801 | { | ||
802 | MemberInitExpression e = expression as MemberInitExpression; | ||
803 | this.FindColumns(e.NewExpression, columnList); | ||
804 | foreach (MemberAssignment b in e.Bindings) | ||
805 | { | ||
806 | this.FindColumns(b.Expression, columnList); | ||
807 | } | ||
808 | } | ||
809 | else if (expression is NewExpression) | ||
810 | { | ||
811 | NewExpression e = expression as NewExpression; | ||
812 | this.FindColumns(e.Arguments, columnList); | ||
813 | } | ||
814 | else if (expression is NewArrayExpression) | ||
815 | { | ||
816 | NewArrayExpression e = expression as NewArrayExpression; | ||
817 | this.FindColumns(e.Expressions, columnList); | ||
818 | } | ||
819 | else if (expression is TypeBinaryExpression) | ||
820 | { | ||
821 | TypeBinaryExpression e = expression as TypeBinaryExpression; | ||
822 | this.FindColumns(e.Expression, columnList); | ||
823 | } | ||
824 | } | ||
825 | |||
826 | private void FindColumns(IEnumerable<Expression> expressions, IList<TableColumn> columnList) | ||
827 | { | ||
828 | foreach (Expression expression in expressions) | ||
829 | { | ||
830 | this.FindColumns(expression, columnList); | ||
831 | } | ||
832 | } | ||
833 | |||
834 | private void FindColumns(MemberExpression memberExpression, IList<TableColumn> columnList) | ||
835 | { | ||
836 | string selector = null; | ||
837 | MemberExpression objectMemberExpression; | ||
838 | ParameterExpression objectParameterExpression; | ||
839 | if ((objectParameterExpression = memberExpression.Expression as | ||
840 | ParameterExpression) != null) | ||
841 | { | ||
842 | selector = objectParameterExpression.Name; | ||
843 | } | ||
844 | else if ((objectMemberExpression = memberExpression.Expression as | ||
845 | MemberExpression) != null) | ||
846 | { | ||
847 | selector = objectMemberExpression.Member.Name; | ||
848 | } | ||
849 | |||
850 | if (selector != null) | ||
851 | { | ||
852 | for (int i = 0; i < this.tables.Count; i++) | ||
853 | { | ||
854 | if (this.selectors[i] == selector) | ||
855 | { | ||
856 | string columnName = GetColumnName(memberExpression.Member); | ||
857 | ColumnInfo column = this.tables[i].Columns[columnName]; | ||
858 | columnList.Add(new TableColumn(this.tables[i], column)); | ||
859 | break; | ||
860 | } | ||
861 | } | ||
862 | } | ||
863 | |||
864 | selector = memberExpression.Member.Name; | ||
865 | for (int i = 0; i < this.tables.Count; i++) | ||
866 | { | ||
867 | if (this.selectors[i] == selector) | ||
868 | { | ||
869 | AddAllColumns(this.tables[i], columnList); | ||
870 | break; | ||
871 | } | ||
872 | } | ||
873 | } | ||
874 | |||
875 | private void FindColumns(MethodCallExpression methodCallExpression, IList<TableColumn> columnList) | ||
876 | { | ||
877 | if (methodCallExpression.Method.Name == "get_Item" && | ||
878 | methodCallExpression.Arguments.Count == 1 && | ||
879 | methodCallExpression.Arguments[0].Type == typeof(string)) | ||
880 | { | ||
881 | string selector = null; | ||
882 | MemberExpression objectMemberExpression; | ||
883 | ParameterExpression objectParameterExpression; | ||
884 | if ((objectParameterExpression = methodCallExpression.Object as ParameterExpression) != null) | ||
885 | { | ||
886 | selector = objectParameterExpression.Name; | ||
887 | } | ||
888 | else if ((objectMemberExpression = methodCallExpression.Object as MemberExpression) != null) | ||
889 | { | ||
890 | selector = objectMemberExpression.Member.Name; | ||
891 | } | ||
892 | |||
893 | if (selector != null) | ||
894 | { | ||
895 | for (int i = 0; i < this.tables.Count; i++) | ||
896 | { | ||
897 | if (this.selectors[i] == selector) | ||
898 | { | ||
899 | LambdaExpression argumentExpression = | ||
900 | Expression.Lambda(methodCallExpression.Arguments[0]); | ||
901 | string columnName = (string) | ||
902 | argumentExpression.Compile().DynamicInvoke(); | ||
903 | ColumnInfo column = this.tables[i].Columns[columnName]; | ||
904 | columnList.Add(new TableColumn(this.tables[i], column)); | ||
905 | break; | ||
906 | } | ||
907 | } | ||
908 | } | ||
909 | } | ||
910 | |||
911 | if (methodCallExpression.Object != null && methodCallExpression.Object.NodeType != ExpressionType.Parameter) | ||
912 | { | ||
913 | this.FindColumns(methodCallExpression.Object, columnList); | ||
914 | } | ||
915 | } | ||
916 | |||
917 | internal void PerformJoin( | ||
918 | TableInfo tableInfo, | ||
919 | Type recordType, | ||
920 | IQueryable joinTable, | ||
921 | LambdaExpression outerKeySelector, | ||
922 | LambdaExpression innerKeySelector, | ||
923 | LambdaExpression resultSelector) | ||
924 | { | ||
925 | if (joinTable == null) | ||
926 | { | ||
927 | throw new ArgumentNullException("joinTable"); | ||
928 | } | ||
929 | |||
930 | if (tableInfo != null) | ||
931 | { | ||
932 | this.tables.Add(tableInfo); | ||
933 | this.recordTypes.Add(recordType); | ||
934 | this.selectors.Add(outerKeySelector.Parameters[0].Name); | ||
935 | } | ||
936 | |||
937 | PropertyInfo tableInfoProp = joinTable.GetType().GetProperty("TableInfo"); | ||
938 | if (tableInfoProp == null) | ||
939 | { | ||
940 | throw new NotSupportedException( | ||
941 | "Cannot join with object: " + joinTable.GetType().Name + | ||
942 | "; join is only supported on another QTable."); | ||
943 | } | ||
944 | |||
945 | TableInfo joinTableInfo = (TableInfo) tableInfoProp.GetValue(joinTable, null); | ||
946 | if (joinTableInfo == null) | ||
947 | { | ||
948 | throw new InvalidOperationException("Missing join table info."); | ||
949 | } | ||
950 | |||
951 | this.tables.Add(joinTableInfo); | ||
952 | this.recordTypes.Add(joinTable.ElementType); | ||
953 | this.selectors.Add(innerKeySelector.Parameters[0].Name); | ||
954 | this.projectionDelegates.Add(resultSelector.Compile()); | ||
955 | |||
956 | int joinColumnCount = this.joinColumns.Count; | ||
957 | this.FindColumns(outerKeySelector.Body, this.joinColumns); | ||
958 | if (this.joinColumns.Count > joinColumnCount + 1) | ||
959 | { | ||
960 | throw new NotSupportedException("Join operations involving " + | ||
961 | "multiple columns are not supported."); | ||
962 | } | ||
963 | else if (this.joinColumns.Count != joinColumnCount + 1) | ||
964 | { | ||
965 | throw new InvalidOperationException("Bad outer key selector for join."); | ||
966 | } | ||
967 | |||
968 | this.FindColumns(innerKeySelector.Body, this.joinColumns); | ||
969 | if (this.joinColumns.Count > joinColumnCount + 2) | ||
970 | { | ||
971 | throw new NotSupportedException("Join operations involving " + | ||
972 | "multiple columns not are supported."); | ||
973 | } | ||
974 | if (this.joinColumns.Count != joinColumnCount + 2) | ||
975 | { | ||
976 | throw new InvalidOperationException("Bad inner key selector for join."); | ||
977 | } | ||
978 | } | ||
979 | } | ||
980 | |||
981 | internal class TableColumn | ||
982 | { | ||
983 | public TableColumn(TableInfo table, ColumnInfo column) | ||
984 | { | ||
985 | this.Table = table; | ||
986 | this.Column = column; | ||
987 | } | ||
988 | |||
989 | public TableInfo Table { get; set; } | ||
990 | public ColumnInfo Column { get; set; } | ||
991 | } | ||
992 | } | ||