// 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();
}
}
}