diff options
Diffstat (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs')
-rw-r--r-- | src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs new file mode 100644 index 00000000..4b3145fd --- /dev/null +++ b/src/dtf/WixToolset.Dtf.WindowsInstaller.Linq/QRecord.cs | |||
@@ -0,0 +1,501 @@ | |||
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.Text; | ||
8 | using System.Globalization; | ||
9 | using System.Collections.Generic; | ||
10 | using System.Diagnostics.CodeAnalysis; | ||
11 | |||
12 | /// <summary> | ||
13 | /// Generic record entity for queryable databases, | ||
14 | /// and base for strongly-typed entity subclasses. | ||
15 | /// </summary> | ||
16 | /// <remarks> | ||
17 | /// Several predefined specialized subclasses are provided for common | ||
18 | /// standard tables. Subclasses for additional standard tables | ||
19 | /// or custom tables are not necessary, but they are easy to create | ||
20 | /// and make the coding experience much nicer. | ||
21 | /// <para>When creating subclasses, the following attributes may be | ||
22 | /// useful: <see cref="DatabaseTableAttribute"/>, | ||
23 | /// <see cref="DatabaseColumnAttribute"/></para> | ||
24 | /// </remarks> | ||
25 | public class QRecord | ||
26 | { | ||
27 | /// <summary> | ||
28 | /// Do not call. Use QTable.NewRecord() instead. | ||
29 | /// </summary> | ||
30 | /// <remarks> | ||
31 | /// Subclasses must also provide a public parameterless constructor. | ||
32 | /// <para>QRecord constructors are only public due to implementation | ||
33 | /// reasons (to satisfy the new() constraint on the QTable generic | ||
34 | /// class). They are not intended to be called by user code other than | ||
35 | /// a subclass constructor. If the constructor is invoked directly, | ||
36 | /// the record instance will not be properly initialized (associated | ||
37 | /// with a database table) and calls to methods on the instance | ||
38 | /// will throw a NullReferenceException.</para> | ||
39 | /// </remarks> | ||
40 | /// <seealso cref="QTable<TRecord>.NewRecord()"/> | ||
41 | public QRecord() | ||
42 | { | ||
43 | } | ||
44 | |||
45 | internal QDatabase Database { get; set; } | ||
46 | |||
47 | internal TableInfo TableInfo { get; set; } | ||
48 | |||
49 | internal IList<string> Values { get; set; } | ||
50 | |||
51 | internal bool Exists { get; set; } | ||
52 | |||
53 | /// <summary> | ||
54 | /// Gets the number of fields in the record. | ||
55 | /// </summary> | ||
56 | public int FieldCount | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | return this.Values.Count; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Gets or sets a record field. | ||
66 | /// </summary> | ||
67 | /// <param name="field">column name of the field</param> | ||
68 | /// <remarks> | ||
69 | /// Setting a field value will automatically update the database. | ||
70 | /// </remarks> | ||
71 | public string this[string field] | ||
72 | { | ||
73 | get | ||
74 | { | ||
75 | if (field == null) | ||
76 | { | ||
77 | throw new ArgumentNullException("field"); | ||
78 | } | ||
79 | |||
80 | int index = this.TableInfo.Columns.IndexOf(field); | ||
81 | if (index < 0) | ||
82 | { | ||
83 | throw new ArgumentOutOfRangeException("field"); | ||
84 | } | ||
85 | |||
86 | return this[index]; | ||
87 | } | ||
88 | |||
89 | set | ||
90 | { | ||
91 | if (field == null) | ||
92 | { | ||
93 | throw new ArgumentNullException("field"); | ||
94 | } | ||
95 | |||
96 | this.Update(new string[] { field }, new string[] { value }); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | /// <summary> | ||
101 | /// Gets or sets a record field. | ||
102 | /// </summary> | ||
103 | /// <param name="index">zero-based column index of the field</param> | ||
104 | /// <remarks> | ||
105 | /// Setting a field value will automatically update the database. | ||
106 | /// </remarks> | ||
107 | public string this[int index] | ||
108 | { | ||
109 | get | ||
110 | { | ||
111 | if (index < 0 || index >= this.FieldCount) | ||
112 | { | ||
113 | throw new ArgumentOutOfRangeException("index"); | ||
114 | } | ||
115 | |||
116 | return this.Values[index]; | ||
117 | } | ||
118 | |||
119 | set | ||
120 | { | ||
121 | if (index < 0 || index >= this.FieldCount) | ||
122 | { | ||
123 | throw new ArgumentOutOfRangeException("index"); | ||
124 | } | ||
125 | |||
126 | this.Update(new int[] { index }, new string[] { value }); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// Used by subclasses to get a field as an integer. | ||
132 | /// </summary> | ||
133 | /// <param name="index">zero-based column index of the field</param> | ||
134 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")] | ||
135 | protected int I(int index) | ||
136 | { | ||
137 | string value = this[index]; | ||
138 | return value.Length > 0 ? | ||
139 | Int32.Parse(value, CultureInfo.InvariantCulture) : 0; | ||
140 | } | ||
141 | |||
142 | /// <summary> | ||
143 | /// Used by subclasses to get a field as a nullable integer. | ||
144 | /// </summary> | ||
145 | /// <param name="index">zero-based column index of the field</param> | ||
146 | protected int? NI(int index) | ||
147 | { | ||
148 | string value = this[index]; | ||
149 | return value.Length > 0 ? | ||
150 | new int?(Int32.Parse(value, CultureInfo.InvariantCulture)) : null; | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Dumps all record fields to a string. | ||
155 | /// </summary> | ||
156 | public override string ToString() | ||
157 | { | ||
158 | StringBuilder buf = new StringBuilder(this.GetType().Name); | ||
159 | buf.Append(" {"); | ||
160 | for (int i = 0; i < this.FieldCount; i++) | ||
161 | { | ||
162 | buf.AppendFormat("{0} {1} = {2}", | ||
163 | (i > 0 ? "," : String.Empty), | ||
164 | this.TableInfo.Columns[i].Name, | ||
165 | this[i]); | ||
166 | } | ||
167 | buf.Append(" }"); | ||
168 | return buf.ToString(); | ||
169 | } | ||
170 | |||
171 | /// <summary> | ||
172 | /// Update multiple fields in the record (and the database). | ||
173 | /// </summary> | ||
174 | /// <param name="fields">column names of fields to update</param> | ||
175 | /// <param name="values">new values for each field being updated</param> | ||
176 | public void Update(IList<string> fields, IList<string> values) | ||
177 | { | ||
178 | if (fields == null) | ||
179 | { | ||
180 | throw new ArgumentNullException("fields"); | ||
181 | } | ||
182 | |||
183 | if (values == null) | ||
184 | { | ||
185 | throw new ArgumentNullException("values"); | ||
186 | } | ||
187 | |||
188 | if (fields.Count == 0 || values.Count == 0 || | ||
189 | fields.Count > this.FieldCount || | ||
190 | values.Count != fields.Count) | ||
191 | { | ||
192 | throw new ArgumentOutOfRangeException("fields"); | ||
193 | } | ||
194 | |||
195 | int[] indexes = new int[fields.Count]; | ||
196 | for (int i = 0; i < indexes.Length; i++) | ||
197 | { | ||
198 | if (fields[i] == null) | ||
199 | { | ||
200 | throw new ArgumentNullException("fields[" + i + "]"); | ||
201 | } | ||
202 | |||
203 | indexes[i] = this.TableInfo.Columns.IndexOf(fields[i]); | ||
204 | |||
205 | if (indexes[i] < 0) | ||
206 | { | ||
207 | throw new ArgumentOutOfRangeException("fields[" + i + "]"); | ||
208 | } | ||
209 | } | ||
210 | |||
211 | this.Update(indexes, values); | ||
212 | } | ||
213 | |||
214 | /// <summary> | ||
215 | /// Update multiple fields in the record (and the database). | ||
216 | /// </summary> | ||
217 | /// <param name="indexes">column indexes of fields to update</param> | ||
218 | /// <param name="values">new values for each field being updated</param> | ||
219 | /// <remarks> | ||
220 | /// The record (primary keys) must already exist in the table. | ||
221 | /// <para>Updating primary key fields is not yet implemented; use Delete() | ||
222 | /// and Insert() instead.</para> | ||
223 | /// </remarks> | ||
224 | public void Update(IList<int> indexes, IList<string> values) | ||
225 | { | ||
226 | if (indexes == null) | ||
227 | { | ||
228 | throw new ArgumentNullException("indexes"); | ||
229 | } | ||
230 | |||
231 | if (values == null) | ||
232 | { | ||
233 | throw new ArgumentNullException("values"); | ||
234 | } | ||
235 | |||
236 | if (indexes.Count == 0 || values.Count == 0 || | ||
237 | indexes.Count > this.FieldCount || | ||
238 | values.Count != indexes.Count) | ||
239 | { | ||
240 | throw new ArgumentOutOfRangeException("indexes"); | ||
241 | } | ||
242 | |||
243 | bool primaryKeyChanged = false; | ||
244 | for (int i = 0; i < indexes.Count; i++) | ||
245 | { | ||
246 | int index = indexes[i]; | ||
247 | if (index < 0 || index >= this.FieldCount) | ||
248 | { | ||
249 | throw new ArgumentOutOfRangeException("index[" + i + "]"); | ||
250 | } | ||
251 | |||
252 | ColumnInfo col = this.TableInfo.Columns[index]; | ||
253 | if (this.TableInfo.PrimaryKeys.Contains(col.Name)) | ||
254 | { | ||
255 | if (values[i] == null) | ||
256 | { | ||
257 | throw new ArgumentNullException("values[" + i + "]"); | ||
258 | } | ||
259 | |||
260 | primaryKeyChanged = true; | ||
261 | } | ||
262 | else if (values[i] == null) | ||
263 | { | ||
264 | if (col.IsRequired) | ||
265 | { | ||
266 | throw new ArgumentNullException("values[" + i + "]"); | ||
267 | } | ||
268 | } | ||
269 | |||
270 | this.Values[index] = values[i]; | ||
271 | } | ||
272 | |||
273 | if (this.Exists) | ||
274 | { | ||
275 | if (!primaryKeyChanged) | ||
276 | { | ||
277 | int updateRecSize = indexes.Count + this.TableInfo.PrimaryKeys.Count; | ||
278 | using (Record updateRec = this.Database.CreateRecord(updateRecSize)) | ||
279 | { | ||
280 | StringBuilder s = new StringBuilder("UPDATE `"); | ||
281 | s.Append(this.TableInfo.Name); | ||
282 | s.Append("` SET"); | ||
283 | |||
284 | for (int i = 0; i < indexes.Count; i++) | ||
285 | { | ||
286 | ColumnInfo col = this.TableInfo.Columns[indexes[i]]; | ||
287 | if (col.Type == typeof(Stream)) | ||
288 | { | ||
289 | throw new NotSupportedException( | ||
290 | "Cannot update stream columns via QRecord."); | ||
291 | } | ||
292 | |||
293 | int index = indexes[i]; | ||
294 | s.AppendFormat("{0} `{1}` = ?", | ||
295 | (i > 0 ? "," : String.Empty), | ||
296 | col.Name); | ||
297 | |||
298 | if (values[i] != null) | ||
299 | { | ||
300 | updateRec[i + 1] = values[i]; | ||
301 | } | ||
302 | } | ||
303 | |||
304 | for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++) | ||
305 | { | ||
306 | string key = this.TableInfo.PrimaryKeys[i]; | ||
307 | s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key); | ||
308 | int index = this.TableInfo.Columns.IndexOf(key); | ||
309 | updateRec[indexes.Count + i + 1] = this.Values[index]; | ||
310 | |||
311 | } | ||
312 | |||
313 | string updateSql = s.ToString(); | ||
314 | TextWriter log = this.Database.Log; | ||
315 | if (log != null) | ||
316 | { | ||
317 | log.WriteLine(); | ||
318 | log.WriteLine(updateSql); | ||
319 | for (int field = 1; field <= updateRecSize; field++) | ||
320 | { | ||
321 | log.WriteLine(" ? = " + updateRec.GetString(field)); | ||
322 | } | ||
323 | } | ||
324 | |||
325 | this.Database.Execute(updateSql, updateRec); | ||
326 | } | ||
327 | } | ||
328 | else | ||
329 | { | ||
330 | throw new NotImplementedException( | ||
331 | "Update() cannot handle changed primary keys yet."); | ||
332 | // TODO: | ||
333 | // query using old values | ||
334 | // update values | ||
335 | // View.Replace | ||
336 | } | ||
337 | } | ||
338 | } | ||
339 | |||
340 | /// <summary> | ||
341 | /// Inserts the record in the database. | ||
342 | /// </summary> | ||
343 | /// <remarks> | ||
344 | /// The record (primary keys) may not already exist in the table. | ||
345 | /// <para>Use <see cref="QTable<TRecord>.NewRecord()"/> to get a new | ||
346 | /// record. Prmary keys and all required fields | ||
347 | /// must be filled in before insertion.</para> | ||
348 | /// </remarks> | ||
349 | public void Insert() | ||
350 | { | ||
351 | this.Insert(false); | ||
352 | } | ||
353 | |||
354 | /// <summary> | ||
355 | /// Inserts the record into the table. | ||
356 | /// </summary> | ||
357 | /// <param name="temporary">true if the record is temporarily | ||
358 | /// inserted, to be visible only as long as the database is open</param> | ||
359 | /// <remarks> | ||
360 | /// The record (primary keys) may not already exist in the table. | ||
361 | /// <para>Use <see cref="QTable<TRecord>.NewRecord()"/> to get a new | ||
362 | /// record. Prmary keys and all required fields | ||
363 | /// must be filled in before insertion.</para> | ||
364 | /// </remarks> | ||
365 | public void Insert(bool temporary) | ||
366 | { | ||
367 | using (Record updateRec = this.Database.CreateRecord(this.FieldCount)) | ||
368 | { | ||
369 | string insertSql = this.TableInfo.SqlInsertString; | ||
370 | if (temporary) | ||
371 | { | ||
372 | insertSql += " TEMPORARY"; | ||
373 | } | ||
374 | |||
375 | TextWriter log = this.Database.Log; | ||
376 | if (log != null) | ||
377 | { | ||
378 | log.WriteLine(); | ||
379 | log.WriteLine(insertSql); | ||
380 | } | ||
381 | |||
382 | for (int index = 0; index < this.FieldCount; index++) | ||
383 | { | ||
384 | ColumnInfo col = this.TableInfo.Columns[index]; | ||
385 | if (col.Type == typeof(Stream)) | ||
386 | { | ||
387 | throw new NotSupportedException( | ||
388 | "Cannot insert stream columns via QRecord."); | ||
389 | } | ||
390 | |||
391 | if (this.Values[index] != null) | ||
392 | { | ||
393 | updateRec[index + 1] = this.Values[index]; | ||
394 | } | ||
395 | |||
396 | if (log != null) | ||
397 | { | ||
398 | log.WriteLine(" ? = " + this.Values[index]); | ||
399 | } | ||
400 | } | ||
401 | |||
402 | this.Database.Execute(insertSql, updateRec); | ||
403 | this.Exists = true; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | /// <summary> | ||
408 | /// Deletes the record from the table if it exists. | ||
409 | /// </summary> | ||
410 | public void Delete() | ||
411 | { | ||
412 | using (Record keyRec = this.Database.CreateRecord(this.TableInfo.PrimaryKeys.Count)) | ||
413 | { | ||
414 | StringBuilder s = new StringBuilder("DELETE FROM `"); | ||
415 | s.Append(this.TableInfo.Name); | ||
416 | s.Append("`"); | ||
417 | for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++) | ||
418 | { | ||
419 | string key = this.TableInfo.PrimaryKeys[i]; | ||
420 | s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key); | ||
421 | int index = this.TableInfo.Columns.IndexOf(key); | ||
422 | keyRec[i + 1] = this.Values[index]; | ||
423 | } | ||
424 | |||
425 | string deleteSql = s.ToString(); | ||
426 | |||
427 | TextWriter log = this.Database.Log; | ||
428 | if (log != null) | ||
429 | { | ||
430 | log.WriteLine(); | ||
431 | log.WriteLine(deleteSql); | ||
432 | |||
433 | for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++) | ||
434 | { | ||
435 | log.WriteLine(" ? = " + keyRec.GetString(i + 1)); | ||
436 | } | ||
437 | } | ||
438 | |||
439 | this.Database.Execute(deleteSql, keyRec); | ||
440 | this.Exists = false; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | /// <summary> | ||
445 | /// Not yet implemented. | ||
446 | /// </summary> | ||
447 | public void Refresh() | ||
448 | { | ||
449 | throw new NotImplementedException(); | ||
450 | } | ||
451 | |||
452 | /// <summary> | ||
453 | /// Not yet implemented. | ||
454 | /// </summary> | ||
455 | public void Assign() | ||
456 | { | ||
457 | throw new NotImplementedException(); | ||
458 | } | ||
459 | |||
460 | /// <summary> | ||
461 | /// Not yet implemented. | ||
462 | /// </summary> | ||
463 | public bool Merge() | ||
464 | { | ||
465 | throw new NotImplementedException(); | ||
466 | } | ||
467 | |||
468 | /// <summary> | ||
469 | /// Not yet implemented. | ||
470 | /// </summary> | ||
471 | public ICollection<ValidationErrorInfo> Validate() | ||
472 | { | ||
473 | throw new NotImplementedException(); | ||
474 | } | ||
475 | |||
476 | /// <summary> | ||
477 | /// Not yet implemented. | ||
478 | /// </summary> | ||
479 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] | ||
480 | public ICollection<ValidationErrorInfo> ValidateNew() | ||
481 | { | ||
482 | throw new NotImplementedException(); | ||
483 | } | ||
484 | |||
485 | /// <summary> | ||
486 | /// Not yet implemented. | ||
487 | /// </summary> | ||
488 | public ICollection<ValidationErrorInfo> ValidateFields() | ||
489 | { | ||
490 | throw new NotImplementedException(); | ||
491 | } | ||
492 | |||
493 | /// <summary> | ||
494 | /// Not yet implemented. | ||
495 | /// </summary> | ||
496 | public ICollection<ValidationErrorInfo> ValidateDelete() | ||
497 | { | ||
498 | throw new NotImplementedException(); | ||
499 | } | ||
500 | } | ||
501 | } | ||