aboutsummaryrefslogtreecommitdiff
path: root/src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs')
-rw-r--r--src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs b/src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs
new file mode 100644
index 00000000..a7602d05
--- /dev/null
+++ b/src/api/wix/WixToolset.Data/WindowsInstaller/TableDefinition.cs
@@ -0,0 +1,267 @@
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.Data.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Xml;
9
10 /// <summary>
11 /// Definition of a table in a database.
12 /// </summary>
13 public sealed class TableDefinition : IComparable<TableDefinition>
14 {
15 /// <summary>
16 /// Tracks the maximum number of columns supported in a real table.
17 /// This is a Windows Installer limitation.
18 /// </summary>
19 public const int MaxColumnsInRealTable = 32;
20
21 /// <summary>
22 /// Creates a table definition.
23 /// </summary>
24 /// <param name="name">Name of table to create.</param>
25 /// <param name="symbolDefinition">Optional symbol definition for this table.</param>
26 /// <param name="columns">Column definitions for the table.</param>
27 /// <param name="unreal">Flag if table is unreal.</param>
28 /// <param name="symbolIdIsPrimaryKey">Whether the primary key is the id of the symbol definition associated with this table.</param>
29 /// <param name="strongRowType">The specialized type for the rows.</param>
30 public TableDefinition(string name, IntermediateSymbolDefinition symbolDefinition, IEnumerable<ColumnDefinition> columns, bool unreal = false, bool symbolIdIsPrimaryKey = false, Type strongRowType = null)
31 {
32 this.Name = name;
33 this.SymbolDefinition = symbolDefinition;
34 this.SymbolIdIsPrimaryKey = symbolIdIsPrimaryKey;
35 this.Unreal = unreal;
36 this.Columns = columns?.ToArray();
37 this.StrongRowType = strongRowType ?? typeof(Row);
38
39 if (this.Columns == null || this.Columns.Length == 0)
40 {
41 throw new ArgumentOutOfRangeException(nameof(columns));
42 }
43#if DEBUG
44 if (this.StrongRowType != typeof(Row) && !this.StrongRowType.IsSubclassOf(typeof(Row))) { throw new ArgumentException(nameof(strongRowType)); }
45#endif
46 }
47
48 /// <summary>
49 /// Gets the name of the table.
50 /// </summary>
51 /// <value>Name of the table.</value>
52 public string Name { get; }
53
54 /// <summary>
55 /// Gets the symbol definition associated with this table.
56 /// </summary>
57 /// <value>The symbol definition.</value>
58 public IntermediateSymbolDefinition SymbolDefinition { get; }
59
60 /// <summary>
61 /// Gets if the table is unreal.
62 /// </summary>
63 /// <value>Flag if table is unreal.</value>
64 public bool Unreal { get; }
65
66 /// <summary>
67 /// Gets the collection of column definitions for this table.
68 /// </summary>
69 /// <value>Collection of column definitions for this table.</value>
70 public ColumnDefinition[] Columns { get; }
71
72 /// <summary>
73 /// Gets if the primary key is the id of the symbol definition associated with this table.
74 /// </summary>
75 /// <value>Flag if table is unreal.</value>
76 public bool SymbolIdIsPrimaryKey { get; }
77
78 private Type StrongRowType { get; }
79
80 /// <summary>
81 /// Gets the column definition in the table by index.
82 /// </summary>
83 /// <param name="columnIndex">Index of column to locate.</param>
84 /// <value>Column definition in the table by index.</value>
85 public ColumnDefinition this[int columnIndex] => this.Columns[columnIndex];
86
87 /// <summary>
88 /// In general this method shouldn't be used - create rows from a Table instead.
89 /// Creates a new row object of the type specified in this definition.
90 /// </summary>
91 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
92 /// <returns>Created row.</returns>
93 public Row CreateRow(SourceLineNumber sourceLineNumbers)
94 {
95 var result = (Row)Activator.CreateInstance(this.StrongRowType, sourceLineNumbers, this);
96 return result;
97 }
98
99 /// <summary>
100 /// Creates a new row object of the type specified in this definition for the given table.
101 /// External callers should create the row from the table.
102 /// </summary>
103 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
104 /// <param name="table">The owning table for this row.</param>
105 /// <returns>Created row.</returns>
106 internal Row CreateRow(SourceLineNumber sourceLineNumbers, Table table)
107 {
108 var result = (Row)Activator.CreateInstance(this.StrongRowType, sourceLineNumbers, table);
109 return result;
110 }
111
112 /// <summary>
113 /// Compares this table definition to another table definition.
114 /// </summary>
115 /// <remarks>
116 /// Only Windows Installer traits are compared, allowing for updates to WiX-specific table definitions.
117 /// </remarks>
118 /// <param name="updated">The updated <see cref="TableDefinition"/> to compare with this target definition.</param>
119 /// <returns>0 if the tables' core properties are the same; otherwise, non-0.</returns>
120 public int CompareTo(TableDefinition updated)
121 {
122 // by definition, this object is greater than null
123 if (null == updated)
124 {
125 return 1;
126 }
127
128 // compare the table names
129 var ret = String.Compare(this.Name, updated.Name, StringComparison.Ordinal);
130
131 // compare the column count
132 if (0 == ret)
133 {
134 // transforms can only add columns
135 ret = Math.Min(0, updated.Columns.Length - this.Columns.Length);
136
137 // compare name, type, and length of each column
138 for (var i = 0; 0 == ret && this.Columns.Length > i; i++)
139 {
140 var thisColumnDef = this.Columns[i];
141 var updatedColumnDef = updated.Columns[i];
142
143 // Igmore unreal columns when comparing table definitions.
144 if (thisColumnDef.Unreal || updatedColumnDef.Unreal)
145 {
146 continue;
147 }
148
149 ret = thisColumnDef.CompareTo(updatedColumnDef);
150 }
151 }
152
153 return ret;
154 }
155
156 /// <summary>
157 /// Parses table definition from xml reader.
158 /// </summary>
159 /// <param name="reader">Reader to get data from.</param>
160 /// <param name="tableDefinitions">Table definitions to use for strongly-typed rows.</param>
161 /// <returns>The TableDefintion represented by the Xml.</returns>
162 internal static TableDefinition Read(XmlReader reader, TableDefinitionCollection tableDefinitions)
163 {
164 var empty = reader.IsEmptyElement;
165 string name = null;
166 IntermediateSymbolDefinition symbolDefinition = null;
167 var unreal = false;
168 var symbolIdIsPrimaryKey = false;
169 Type strongRowType = null;
170
171 while (reader.MoveToNextAttribute())
172 {
173 switch (reader.LocalName)
174 {
175 case "name":
176 name = reader.Value;
177 break;
178 case "unreal":
179 unreal = reader.Value.Equals("yes");
180 break;
181 }
182 }
183
184 if (null == name)
185 {
186 throw new XmlException();
187 }
188
189 if (tableDefinitions.TryGet(name, out var tableDefinition))
190 {
191 symbolDefinition = tableDefinition.SymbolDefinition;
192 symbolIdIsPrimaryKey = tableDefinition.SymbolIdIsPrimaryKey;
193 strongRowType = tableDefinition.StrongRowType;
194 }
195
196 var columns = new List<ColumnDefinition>();
197 var hasPrimaryKeyColumn = false;
198
199 // parse the child elements
200 if (!empty)
201 {
202 var done = false;
203
204 while (!done && reader.Read())
205 {
206 switch (reader.NodeType)
207 {
208 case XmlNodeType.Element:
209 switch (reader.LocalName)
210 {
211 case "columnDefinition":
212 var columnDefinition = ColumnDefinition.Read(reader);
213 columns.Add(columnDefinition);
214
215 if (columnDefinition.PrimaryKey)
216 {
217 hasPrimaryKeyColumn = true;
218 }
219 break;
220 default:
221 throw new XmlException();
222 }
223 break;
224 case XmlNodeType.EndElement:
225 done = true;
226 break;
227 }
228 }
229
230 if (!unreal && !hasPrimaryKeyColumn)
231 {
232 throw new WixException(ErrorMessages.RealTableMissingPrimaryKeyColumn(SourceLineNumber.CreateFromUri(reader.BaseURI), name));
233 }
234
235 if (!done)
236 {
237 throw new XmlException();
238 }
239 }
240
241 return new TableDefinition(name, symbolDefinition, columns.ToArray(), unreal, symbolIdIsPrimaryKey, strongRowType);
242 }
243
244 /// <summary>
245 /// Persists an output in an XML format.
246 /// </summary>
247 /// <param name="writer">XmlWriter where the Output should persist itself as XML.</param>
248 internal void Write(XmlWriter writer)
249 {
250 writer.WriteStartElement("tableDefinition", TableDefinitionCollection.XmlNamespaceUri);
251
252 writer.WriteAttributeString("name", this.Name);
253
254 if (this.Unreal)
255 {
256 writer.WriteAttributeString("unreal", "yes");
257 }
258
259 foreach (var columnDefinition in this.Columns)
260 {
261 columnDefinition.Write(writer);
262 }
263
264 writer.WriteEndElement();
265 }
266 }
267}