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