aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/Query.cs992
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
3namespace 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}