aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs
new file mode 100644
index 00000000..e0e1c154
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QTable.cs
@@ -0,0 +1,296 @@
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.Reflection;
10 using System.Linq;
11 using System.Linq.Expressions;
12 using System.Diagnostics.CodeAnalysis;
13
14 /// <summary>
15 /// Represents one table in a LINQ-queryable Database.
16 /// </summary>
17 /// <typeparam name="TRecord">type that represents one record in the table</typeparam>
18 /// <remarks>
19 /// This class is the primary gateway to all LINQ to MSI query functionality.
20 /// <para>The TRecord generic parameter may be the general <see cref="QRecord" />
21 /// class, or a specialized subclass of QRecord.</para>
22 /// </remarks>
23 [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
24 public sealed class QTable<TRecord> : IOrderedQueryable<TRecord>, IQueryProvider
25 where TRecord : QRecord, new()
26 {
27 private QDatabase db;
28 private TableInfo tableInfo;
29
30 /// <summary>
31 /// Infers the name of the table this instance will be
32 /// associated with.
33 /// </summary>
34 /// <returns>table name</returns>
35 /// <remarks>
36 /// The table name is retrieved from a DatabaseTableAttribute
37 /// on the record type if it exists; otherwise the name is
38 /// derived from the name of the record type itself.
39 /// (An optional underscore suffix on the record type name is dropped.)
40 /// </remarks>
41 private static string InferTableName()
42 {
43 foreach (DatabaseTableAttribute attr in typeof(TRecord).GetCustomAttributes(
44 typeof(DatabaseTableAttribute), false))
45 {
46 string tableName = attr.Table;
47 if (!String.IsNullOrEmpty(tableName))
48 {
49 return tableName;
50 }
51 }
52
53 string recordTypeName = typeof(TRecord).Name;
54 if (recordTypeName[recordTypeName.Length - 1] == '_')
55 {
56 return recordTypeName.Substring(0, recordTypeName.Length - 1);
57 }
58 else
59 {
60 return recordTypeName;
61 }
62 }
63
64 /// <summary>
65 /// Creates a new QTable, inferring the table name
66 /// from the name of the record type parameter.
67 /// </summary>
68 /// <param name="db">database that contains the table</param>
69 public QTable(QDatabase db)
70 : this(db, InferTableName())
71 {
72 }
73
74 /// <summary>
75 /// Creates a new QTable with an explicit table name.
76 /// </summary>
77 /// <param name="db">database that contains the table</param>
78 /// <param name="table">name of the table</param>
79 public QTable(QDatabase db, string table)
80 {
81 if (db == null)
82 {
83 throw new ArgumentNullException("db");
84 }
85
86 if (String.IsNullOrEmpty(table))
87 {
88 throw new ArgumentNullException("table");
89 }
90
91 this.db = db;
92 this.tableInfo = db.Tables[table];
93 if (this.tableInfo == null)
94 {
95 throw new ArgumentException(
96 "Table does not exist in database: " + table);
97 }
98 }
99
100 /// <summary>
101 /// Gets schema information about the table.
102 /// </summary>
103 public TableInfo TableInfo
104 {
105 get
106 {
107 return this.tableInfo;
108 }
109 }
110
111 /// <summary>
112 /// Gets the database this table is associated with.
113 /// </summary>
114 public QDatabase Database
115 {
116 get
117 {
118 return this.db;
119 }
120 }
121
122 /// <summary>
123 /// Enumerates over all records in the table.
124 /// </summary>
125 /// <returns></returns>
126 public IEnumerator<TRecord> GetEnumerator()
127 {
128 string query = this.tableInfo.SqlSelectString;
129
130 TextWriter log = this.db.Log;
131 if (log != null)
132 {
133 log.WriteLine();
134 log.WriteLine(query);
135 }
136
137 using (View view = db.OpenView(query))
138 {
139 view.Execute();
140
141 ColumnCollection columns = this.tableInfo.Columns;
142 int columnCount = columns.Count;
143 bool[] isBinary = new bool[columnCount];
144
145 for (int i = 0; i < isBinary.Length; i++)
146 {
147 isBinary[i] = columns[i].Type == typeof(System.IO.Stream);
148 }
149
150 foreach (Record rec in view) using (rec)
151 {
152 string[] values = new string[columnCount];
153 for (int i = 0; i < values.Length; i++)
154 {
155 values[i] = isBinary[i] ? "[Binary Data]" : rec.GetString(i + 1);
156 }
157
158 TRecord trec = new TRecord();
159 trec.Database = this.Database;
160 trec.TableInfo = this.TableInfo;
161 trec.Values = values;
162 trec.Exists = true;
163 yield return trec;
164 }
165 }
166 }
167
168 IEnumerator IEnumerable.GetEnumerator()
169 {
170 return ((IEnumerable<TRecord>) this).GetEnumerator();
171 }
172
173 IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
174 {
175 if (expression == null)
176 {
177 throw new ArgumentNullException("expression");
178 }
179
180 Query<TElement> q = new Query<TElement>(this.Database, expression);
181
182 MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
183 string methodName = methodCallExpression.Method.Name;
184 if (methodName == "Where")
185 {
186 LambdaExpression argumentExpression = (LambdaExpression)
187 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
188 q.BuildQuery(this.TableInfo, argumentExpression);
189 }
190 else if (methodName == "OrderBy")
191 {
192 LambdaExpression argumentExpression = (LambdaExpression)
193 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
194 q.BuildSequence(this.TableInfo, argumentExpression);
195 }
196 else if (methodName == "Select")
197 {
198 LambdaExpression argumentExpression = (LambdaExpression)
199 ((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
200 q.BuildNullQuery(this.TableInfo, typeof(TRecord), argumentExpression);
201 q.BuildProjection(null, argumentExpression);
202 }
203 else if (methodName == "Join")
204 {
205 ConstantExpression constantExpression = (ConstantExpression)
206 methodCallExpression.Arguments[1];
207 IQueryable inner = (IQueryable) constantExpression.Value;
208 q.PerformJoin(
209 this.TableInfo,
210 typeof(TRecord),
211 inner,
212 GetJoinLambda(methodCallExpression.Arguments[2]),
213 GetJoinLambda(methodCallExpression.Arguments[3]),
214 GetJoinLambda(methodCallExpression.Arguments[4]));
215 }
216 else
217 {
218 throw new NotSupportedException(
219 "Query operation not supported: " + methodName);
220 }
221
222 return q;
223 }
224
225 private static LambdaExpression GetJoinLambda(Expression expresion)
226 {
227 UnaryExpression unaryExpression = (UnaryExpression) expresion;
228 return (LambdaExpression) unaryExpression.Operand;
229 }
230
231 IQueryable IQueryProvider.CreateQuery(Expression expression)
232 {
233 return ((IQueryProvider) this).CreateQuery<TRecord>(expression);
234 }
235
236 TResult IQueryProvider.Execute<TResult>(Expression expression)
237 {
238 throw new NotSupportedException(
239 "Direct method calls not supported -- use AsEnumerable() instead.");
240 }
241
242 object IQueryProvider.Execute(Expression expression)
243 {
244 throw new NotSupportedException(
245 "Direct method calls not supported -- use AsEnumerable() instead.");
246 }
247
248 IQueryProvider IQueryable.Provider
249 {
250 get
251 {
252 return this;
253 }
254 }
255
256 Type IQueryable.ElementType
257 {
258 get
259 {
260 return typeof(TRecord);
261 }
262 }
263
264 Expression IQueryable.Expression
265 {
266 get
267 {
268 return Expression.Constant(this);
269 }
270 }
271
272 /// <summary>
273 /// Creates a new record that can be inserted into this table.
274 /// </summary>
275 /// <returns>a record with all fields initialized to null</returns>
276 /// <remarks>
277 /// Primary keys and required fields must be filled in with
278 /// non-null values before the record can be inserted.
279 /// <para>The record is tied to this table in this database;
280 /// it cannot be inserted into another table or database.</para>
281 /// </remarks>
282 public TRecord NewRecord()
283 {
284 TRecord rec = new TRecord();
285 rec.Database = this.Database;
286 rec.TableInfo = this.TableInfo;
287 IList<string> values = new List<string>(this.TableInfo.Columns.Count);
288 for (int i = 0; i < this.TableInfo.Columns.Count; i++)
289 {
290 values.Add(null);
291 }
292 rec.Values = values;
293 return rec;
294 }
295 }
296}