// 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. namespace WixToolset.Data.WindowsInstaller { using System; using System.Collections.Generic; using System.Linq; using System.Xml; /// /// Definition of a table in a database. /// public sealed class TableDefinition : IComparable { /// /// Tracks the maximum number of columns supported in a real table. /// This is a Windows Installer limitation. /// public const int MaxColumnsInRealTable = 32; /// /// Creates a table definition. /// /// Name of table to create. /// Optional symbol definition for this table. /// Column definitions for the table. /// Flag if table is unreal. /// Whether the primary key is the id of the symbol definition associated with this table. public TableDefinition(string name, IntermediateSymbolDefinition symbolDefinition, IEnumerable columns, bool unreal = false, bool symbolIdIsPrimaryKey = false, Type strongRowType = null) { this.Name = name; this.SymbolDefinition = symbolDefinition; this.SymbolIdIsPrimaryKey = symbolIdIsPrimaryKey; this.Unreal = unreal; this.Columns = columns?.ToArray(); this.StrongRowType = strongRowType ?? typeof(Row); if (this.Columns == null || this.Columns.Length == 0) { throw new ArgumentOutOfRangeException(nameof(columns)); } #if DEBUG if (this.StrongRowType != typeof(Row) && !this.StrongRowType.IsSubclassOf(typeof(Row))) { throw new ArgumentException(nameof(strongRowType)); } #endif } /// /// Gets the name of the table. /// /// Name of the table. public string Name { get; } /// /// Gets the symbol definition associated with this table. /// /// The symbol definition. public IntermediateSymbolDefinition SymbolDefinition { get; } /// /// Gets if the table is unreal. /// /// Flag if table is unreal. public bool Unreal { get; } /// /// Gets the collection of column definitions for this table. /// /// Collection of column definitions for this table. public ColumnDefinition[] Columns { get; } /// /// Gets if the primary key is the id of the symbol definition associated with this table. /// /// Flag if table is unreal. public bool SymbolIdIsPrimaryKey { get; } private Type StrongRowType { get; } /// /// Gets the column definition in the table by index. /// /// Index of column to locate. /// Column definition in the table by index. public ColumnDefinition this[int columnIndex] => this.Columns[columnIndex]; /// /// In general this method shouldn't be used - create rows from a Table instead. /// Creates a new row object of the type specified in this definition. /// /// Original source lines for this row. /// Created row. public Row CreateRow(SourceLineNumber sourceLineNumbers) { var result = (Row)Activator.CreateInstance(this.StrongRowType, sourceLineNumbers, this); return result; } /// /// Creates a new row object of the type specified in this definition for the given table. /// External callers should create the row from the table. /// /// Original source lines for this row. /// The owning table for this row. /// Created row. internal Row CreateRow(SourceLineNumber sourceLineNumbers, Table table) { var result = (Row)Activator.CreateInstance(this.StrongRowType, sourceLineNumbers, table); return result; } /// /// Compares this table definition to another table definition. /// /// /// Only Windows Installer traits are compared, allowing for updates to WiX-specific table definitions. /// /// The updated to compare with this target definition. /// 0 if the tables' core properties are the same; otherwise, non-0. public int CompareTo(TableDefinition updated) { // by definition, this object is greater than null if (null == updated) { return 1; } // compare the table names var ret = String.Compare(this.Name, updated.Name, StringComparison.Ordinal); // compare the column count if (0 == ret) { // transforms can only add columns ret = Math.Min(0, updated.Columns.Length - this.Columns.Length); // compare name, type, and length of each column for (var i = 0; 0 == ret && this.Columns.Length > i; i++) { var thisColumnDef = this.Columns[i]; var updatedColumnDef = updated.Columns[i]; // Igmore unreal columns when comparing table definitions. if (thisColumnDef.Unreal || updatedColumnDef.Unreal) { continue; } ret = thisColumnDef.CompareTo(updatedColumnDef); } } return ret; } /// /// Parses table definition from xml reader. /// /// Reader to get data from. /// Table definitions to use for strongly-typed rows. /// The TableDefintion represented by the Xml. internal static TableDefinition Read(XmlReader reader, TableDefinitionCollection tableDefinitions) { var empty = reader.IsEmptyElement; string name = null; IntermediateSymbolDefinition symbolDefinition = null; var unreal = false; var symbolIdIsPrimaryKey = false; Type strongRowType = null; while (reader.MoveToNextAttribute()) { switch (reader.LocalName) { case "name": name = reader.Value; break; case "unreal": unreal = reader.Value.Equals("yes"); break; } } if (null == name) { throw new XmlException(); } if (tableDefinitions.TryGet(name, out var tableDefinition)) { symbolDefinition = tableDefinition.SymbolDefinition; symbolIdIsPrimaryKey = tableDefinition.SymbolIdIsPrimaryKey; strongRowType = tableDefinition.StrongRowType; } var columns = new List(); var hasPrimaryKeyColumn = false; // parse the child elements if (!empty) { var done = false; while (!done && reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: switch (reader.LocalName) { case "columnDefinition": var columnDefinition = ColumnDefinition.Read(reader); columns.Add(columnDefinition); if (columnDefinition.PrimaryKey) { hasPrimaryKeyColumn = true; } break; default: throw new XmlException(); } break; case XmlNodeType.EndElement: done = true; break; } } if (!unreal && !hasPrimaryKeyColumn) { throw new WixException(ErrorMessages.RealTableMissingPrimaryKeyColumn(SourceLineNumber.CreateFromUri(reader.BaseURI), name)); } if (!done) { throw new XmlException(); } } return new TableDefinition(name, symbolDefinition, columns.ToArray(), unreal, symbolIdIsPrimaryKey, strongRowType); } /// /// Persists an output in an XML format. /// /// XmlWriter where the Output should persist itself as XML. internal void Write(XmlWriter writer) { writer.WriteStartElement("tableDefinition", TableDefinitionCollection.XmlNamespaceUri); writer.WriteAttributeString("name", this.Name); if (this.Unreal) { writer.WriteAttributeString("unreal", "yes"); } foreach (var columnDefinition in this.Columns) { columnDefinition.Write(writer); } writer.WriteEndElement(); } } }