// --------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // --------------------------------------------------------------------------- // --------------------------------------------------------------------- // // // --------------------------------------------------------------------- namespace Microsoft.Database.Isam { using System; using System.Globalization; using Microsoft.Isam.Esent.Interop; using Microsoft.Isam.Esent.Interop.Server2003; using Microsoft.Isam.Esent.Interop.Vista; using Microsoft.Isam.Esent.Interop.Windows7; /// /// A Database is a file used by the ISAM to store data. It is organized /// into tables which are in turn comprised of columns and indices and /// contain data in the form of records. The database's schema can be /// enumerated and manipulated by this object. Also, the database's /// tables can be opened for access by this object. /// /// A is used to access the temporary database. There /// is one temporary database per instance and its location is configured /// by Instance.IsamSystemParameters.TempPath. The temporary database is used /// to store temporary tables. /// /// public class TemporaryDatabase : DatabaseCommon, IDisposable { /// /// The table collection /// private TableCollection tableCollection = null; /// /// The temporary table handle collection /// private TempTableHandleCollection tempTableHandleCollection = null; /// /// The cleanup /// private bool cleanup = false; /// /// The disposed /// private bool disposed = false; /// /// Initializes a new instance of the class. /// /// The session. internal TemporaryDatabase(IsamSession isamSession) : base(isamSession) { lock (isamSession) { this.cleanup = true; this.tableCollection = new TableCollection(); this.tempTableHandleCollection = new TempTableHandleCollection(false); } } /// /// Finalizes an instance of the TemporaryDatabase class /// ~TemporaryDatabase() { this.Dispose(false); } /// /// Gets a collection of tables in the database. /// /// a collection of tables in the database public override TableCollection Tables { get { this.CheckDisposed(); return this.tableCollection; } } /// /// Gets or sets a value indicating whether [disposed]. /// /// /// true if [disposed]; otherwise, false. /// internal override bool Disposed { get { return this.disposed || this.IsamSession.Disposed; } set { this.disposed = value; } } /// /// Gets the temporary table handles. /// /// /// The temporary table handles. /// private TempTableHandleCollection TempTableHandles { get { this.CheckDisposed(); return this.tempTableHandleCollection; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public new void Dispose() { lock (this) { this.Dispose(true); } GC.SuppressFinalize(this); } /// /// Creates a single table with the specified definition in the database /// /// The table definition. /// /// Thrown when the table definition overlaps with an already existing table. /// /// A MaxKeyLength > 255 is not supported for indices over a temporary table on this version of the database engine.;tableDefinition public override void CreateTable(TableDefinition tableDefinition) { lock (this.IsamSession) { this.CheckDisposed(); // validate the table definition for creating a TT this.ValidateTableDefinition(tableDefinition); // convert the given table definition into an JET_OPENTEMPORARYTABLE // struct that we will use to create the TT JET_OPENTEMPORARYTABLE openTemporaryTable = this.MakeOpenTemporaryTable(tableDefinition); // check if the TT already exists if (this.Exists(tableDefinition.Name)) { throw new EsentTableDuplicateException(); } // do not allow the TT to be created if the session is in a // transaction. we disallow this to sidestep the problem where // JET will automatically close (and destroy) the TT if the // current level of the transaction is aborted if (this.IsamSession.TransactionLevel > 0) { // NOTE: i'm thinking that this requirement is pretty lame, // especially since it only hits us on an abort. I am going // to allow this for now and see what happens // throw new ArgumentException( "We do not currently allow you to create temp tables while inside of a transaction." ); } // create the TT JET_TABLEID tableid = new JET_TABLEID(); if (DatabaseCommon.CheckEngineVersion( this.IsamSession, DatabaseCommon.ESENTVersion(6, 0, 6000, 0), DatabaseCommon.ESEVersion(8, 0, 685, 0))) { VistaApi.JetOpenTemporaryTable(this.IsamSession.Sesid, openTemporaryTable); tableid = openTemporaryTable.tableid; } else { if (openTemporaryTable.cbKeyMost > 255) { throw new ArgumentException("A MaxKeyLength > 255 is not supported for indices over a temporary table on this version of the database engine.", "tableDefinition"); } Api.JetOpenTempTable2( this.IsamSession.Sesid, openTemporaryTable.prgcolumndef, openTemporaryTable.prgcolumndef.Length, openTemporaryTable.pidxunicode.lcid, openTemporaryTable.grbit, out tableid, openTemporaryTable.prgcolumnid); } // re-create the TT's schema to reflect the created TT TableDefinition tableDefinitionToCache = MakeTableDefinitionToCache(tableDefinition, openTemporaryTable); // cache the TT and its handle TempTableHandle tempTableHandle = new TempTableHandle( tableDefinitionToCache.Name, this.IsamSession.Sesid, tableid, tableDefinitionToCache.Type == TableType.Sort || tableDefinitionToCache.Type == TableType.PreSortTemporary); this.Tables.Add(tableDefinitionToCache); this.TempTableHandles.Add(tempTableHandle); this.IsamSession.IsamInstance.TempTableHandles.Add(tempTableHandle); } } /// /// Deletes a single table in the database /// /// Name of the table. /// /// Thrown when the specified table can't be found. /// /// /// Thrown when the specified table still has cursors open. /// /// /// It is currently not possible to delete a table that is being used /// by a Cursor. All such Cursors must be disposed before the /// table can be successfully deleted. /// public override void DropTable(string tableName) { lock (this.IsamSession) { this.CheckDisposed(); if (!this.Exists(tableName)) { throw new EsentObjectNotFoundException(); } TempTableHandle tempTableHandle = this.TempTableHandles[tableName]; if (tempTableHandle.CursorCount > 0) { throw new EsentTableInUseException(); } this.Tables.Remove(tableName); Api.JetCloseTable(this.IsamSession.Sesid, tempTableHandle.Handle); this.TempTableHandles.Remove(tempTableHandle.Name); this.IsamSession.IsamInstance.TempTableHandles.Remove(tempTableHandle.Guid.ToString()); } } /// /// Determines if a given table exists in the database /// /// Name of the table. /// /// true if the table was found, false otherwise /// public override bool Exists(string tableName) { this.CheckDisposed(); return this.Tables.Contains(tableName); } /// /// Opens a cursor over the specified table. /// /// the name of the table to be opened /// a cursor over the specified table in this database public override Cursor OpenCursor(string tableName) { lock (this.IsamSession) { this.CheckDisposed(); if (!this.Exists(tableName)) { throw new EsentObjectNotFoundException(); } TableDefinition tableDefinition = this.Tables[tableName]; TempTableHandle tempTableHandle = this.TempTableHandles[tableName]; JET_TABLEID tableid; try { // if this is a Sort then we must always fail to dup the // cursor // // NOTE: this is a hack to work around problems in ESE/ESENT // that we cannot fix because we must work downlevel if (tableDefinition.Type == TableType.Sort) { throw new EsentIllegalOperationException(); } Api.JetDupCursor(this.IsamSession.Sesid, tempTableHandle.Handle, out tableid, DupCursorGrbit.None); tempTableHandle.InInsertMode = false; } catch (EsentIllegalOperationException) { if (tempTableHandle.CursorCount > 0) { throw new InvalidOperationException("It is not possible to have multiple open cursors on a temporary table that is currently in insert mode."); } else { tableid = tempTableHandle.Handle; } } Cursor newCursor = new Cursor(this.IsamSession, this, tableName, tableid, tempTableHandle.InInsertMode); tempTableHandle.CursorCount++; return newCursor; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// void IDisposable.Dispose() { this.Dispose(); } /// /// Releases the temporary table. /// /// Name of the table. /// if set to true [in insert mode]. internal void ReleaseTempTable(string tableName, bool inInsertMode) { lock (this.IsamSession) { TempTableHandle tempTableHandle = this.TempTableHandles[tableName]; tempTableHandle.InInsertMode = inInsertMode; tempTableHandle.CursorCount--; } } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { lock (this.IsamSession) { if (!this.Disposed) { if (this.cleanup) { foreach (TempTableHandle tempTableHandle in this.TempTableHandles) { Api.JetCloseTable(this.IsamSession.Sesid, tempTableHandle.Handle); this.IsamSession.IsamInstance.TempTableHandles.Remove(tempTableHandle.Guid.ToString()); } base.Dispose(disposing); this.cleanup = false; } this.Disposed = true; } } } /// /// Creates a object from a /// object, suitable for caching. /// /// The table definition. /// The open temporary table. /// A object suitable for caching. private static TableDefinition MakeTableDefinitionToCache( TableDefinition tableDefinition, JET_OPENTEMPORARYTABLE openTemporaryTable) { // set the new table properties TableDefinition tableDefinitionToCache = new TableDefinition(tableDefinition.Name, tableDefinition.Type); // add the columns complete with the columnids generated when the // TT was created // // NOTE: this processing loop has to mirror the loop used to generate // the columndefs in MakeOpenTemporaryTable int currentColumndef = 0; foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { ColumnDefinition columnDefinition = tableDefinition.Columns[keyColumn.Name]; Columnid columnid = new Columnid( columnDefinition.Name, openTemporaryTable.prgcolumnid[currentColumndef], DatabaseCommon.ColtypFromColumnDefinition(columnDefinition), columnDefinition.IsAscii); ColumnDefinition columnDefinitionToCache = new ColumnDefinition(columnid); columnDefinitionToCache.Flags = columnDefinition.Flags; columnDefinitionToCache.MaxLength = columnDefinition.MaxLength; columnDefinitionToCache.ReadOnly = true; tableDefinitionToCache.Columns.Add(columnDefinitionToCache); currentColumndef++; } } // next collect the rest of the columns and put them after the key // columns, skipping over the columns we already added foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { bool alreadyAdded = false; foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { if (keyColumn.Name.ToLower(CultureInfo.InvariantCulture) == columnDefinition.Name.ToLower(CultureInfo.InvariantCulture)) { alreadyAdded = true; } } } if (!alreadyAdded) { Columnid columnid = new Columnid( columnDefinition.Name, openTemporaryTable.prgcolumnid[currentColumndef], DatabaseCommon.ColtypFromColumnDefinition(columnDefinition), columnDefinition.IsAscii); ColumnDefinition columnDefinitionToCache = new ColumnDefinition(columnid); columnDefinitionToCache.Flags = columnDefinition.Flags; columnDefinitionToCache.MaxLength = columnDefinition.MaxLength; columnDefinitionToCache.ReadOnly = true; tableDefinitionToCache.Columns.Add(columnDefinitionToCache); currentColumndef++; } } tableDefinitionToCache.Columns.ReadOnly = true; // add the indices foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { IndexDefinition indexDefinitionToCache = new IndexDefinition(indexDefinition.Name); indexDefinitionToCache.Flags = indexDefinition.Flags; indexDefinitionToCache.Density = 100; indexDefinitionToCache.CultureInfo = indexDefinition.CultureInfo; indexDefinitionToCache.CompareOptions = indexDefinition.CompareOptions; indexDefinitionToCache.MaxKeyLength = indexDefinition.MaxKeyLength; foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { Columnid columnid = tableDefinitionToCache.Columns[keyColumn.Name].Columnid; KeyColumn keyColumnToCache = new KeyColumn(columnid, keyColumn.IsAscending); indexDefinitionToCache.KeyColumns.Add(keyColumnToCache); } indexDefinitionToCache.KeyColumns.ReadOnly = true; indexDefinitionToCache.ReadOnly = true; tableDefinitionToCache.Indices.Add(indexDefinitionToCache); } tableDefinitionToCache.Indices.ReadOnly = true; // return the table definition return tableDefinitionToCache; } /// /// Makes the object to later open it. /// /// The table definition. /// The newly created object. private JET_OPENTEMPORARYTABLE MakeOpenTemporaryTable(TableDefinition tableDefinition) { JET_OPENTEMPORARYTABLE openTemporaryTable = new JET_OPENTEMPORARYTABLE(); // allocate room for our columns int currentColumndef = 0; openTemporaryTable.ccolumn = tableDefinition.Columns.Count; openTemporaryTable.prgcolumndef = new JET_COLUMNDEF[openTemporaryTable.ccolumn]; openTemporaryTable.prgcolumnid = new JET_COLUMNID[openTemporaryTable.ccolumn]; for (int coldef = 0; coldef < openTemporaryTable.ccolumn; ++coldef) { openTemporaryTable.prgcolumndef[coldef] = new JET_COLUMNDEF(); } // first, collect all the key columns in order and put them as the // first columndefs. we have to do this to guarantee that the TT // is sorted properly foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { ColumnDefinition columnDefinition = tableDefinition.Columns[keyColumn.Name]; openTemporaryTable.prgcolumndef[currentColumndef].coltyp = DatabaseCommon.ColtypFromColumnDefinition(columnDefinition); openTemporaryTable.prgcolumndef[currentColumndef].cp = JET_CP.Unicode; openTemporaryTable.prgcolumndef[currentColumndef].cbMax = columnDefinition.MaxLength; openTemporaryTable.prgcolumndef[currentColumndef].grbit = (ColumndefGrbit)columnDefinition.Flags | ColumndefGrbit.TTKey | (keyColumn.IsAscending ? ColumndefGrbit.None : ColumndefGrbit.TTDescending); currentColumndef++; } } // next collect the rest of the columns and put them after the key // columns, skipping over the columns we already added foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { bool alreadyAdded = false; foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { if (keyColumn.Name.ToLower(CultureInfo.InvariantCulture) == columnDefinition.Name.ToLower(CultureInfo.InvariantCulture)) { alreadyAdded = true; } } } if (!alreadyAdded) { openTemporaryTable.prgcolumndef[currentColumndef].coltyp = DatabaseCommon.ColtypFromColumnDefinition(columnDefinition); openTemporaryTable.prgcolumndef[currentColumndef].cp = JET_CP.Unicode; openTemporaryTable.prgcolumndef[currentColumndef].cbMax = columnDefinition.MaxLength; openTemporaryTable.prgcolumndef[currentColumndef].grbit = Converter.ColumndefGrbitFromColumnFlags(columnDefinition.Flags); currentColumndef++; } } // set the index flags foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { openTemporaryTable.pidxunicode = new JET_UNICODEINDEX(); openTemporaryTable.pidxunicode.lcid = indexDefinition.CultureInfo.LCID; UnicodeIndexFlags unicodeIndexFlags = Converter.UnicodeFlagsFromCompareOptions(indexDefinition.CompareOptions); openTemporaryTable.pidxunicode.dwMapFlags = Converter.MapFlagsFromUnicodeIndexFlags(unicodeIndexFlags); } // infer the TT mode of operation and set its grbits accordingly bool haveColumnWithLongValue = false; foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { JET_coltyp coltyp = DatabaseCommon.ColtypFromColumnDefinition(columnDefinition); if (coltyp == JET_coltyp.LongText || coltyp == JET_coltyp.LongBinary) { haveColumnWithLongValue = true; } } bool haveIndexWithSortNullsHigh = false; foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { if ((indexDefinition.Flags & IndexFlags.SortNullsHigh) != 0) { haveIndexWithSortNullsHigh = true; } } if (tableDefinition.Type == TableType.Sort) { foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { if ((indexDefinition.Flags & (IndexFlags.Unique | IndexFlags.Primary)) == 0) { // External Sort without duplicate removal openTemporaryTable.grbit = Server2003Grbits.ForwardOnly | (haveColumnWithLongValue ? Windows7Grbits.IntrinsicLVsOnly : TempTableGrbit.None) | (haveIndexWithSortNullsHigh ? TempTableGrbit.SortNullsHigh : TempTableGrbit.None); } else { // External Sort TT with deferred duplicate removal openTemporaryTable.grbit = TempTableGrbit.Unique | (haveColumnWithLongValue ? Windows7Grbits.IntrinsicLVsOnly : TempTableGrbit.None) | (haveIndexWithSortNullsHigh ? TempTableGrbit.SortNullsHigh : TempTableGrbit.None); } } } else if (tableDefinition.Type == TableType.PreSortTemporary) { // Pre-sorted B+ Tree TT with deferred duplicate removal openTemporaryTable.grbit = TempTableGrbit.Indexed | TempTableGrbit.Unique | TempTableGrbit.Updatable | TempTableGrbit.Scrollable | (haveColumnWithLongValue ? Windows7Grbits.IntrinsicLVsOnly : TempTableGrbit.None) | (haveIndexWithSortNullsHigh ? TempTableGrbit.SortNullsHigh : TempTableGrbit.None); } else if (tableDefinition.Type == TableType.Temporary) { if (tableDefinition.Indices.Count != 0) { // B+ Tree TT with immediate duplicate removal openTemporaryTable.grbit = TempTableGrbit.Indexed | TempTableGrbit.Unique | TempTableGrbit.Updatable | TempTableGrbit.Scrollable | TempTableGrbit.ForceMaterialization | (haveIndexWithSortNullsHigh ? TempTableGrbit.SortNullsHigh : TempTableGrbit.None); } else { // B+ Tree TT with a sequential index openTemporaryTable.grbit = TempTableGrbit.Updatable | TempTableGrbit.Scrollable; } } // set the key construction parameters for the TT foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { openTemporaryTable.cbKeyMost = indexDefinition.MaxKeyLength; openTemporaryTable.cbVarSegMac = 0; } // return the constructed JET_OPENTEMPORARYTABLE (whew!) return openTemporaryTable; } /// /// Checks whether this object is disposed. /// /// If the object has already been disposed. private void CheckDisposed() { lock (this.IsamSession) { if (this.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } } } /// /// Validates the table definition. /// /// The table definition. /// /// Illegal name for a temporary table.;tableDefinition /// or /// Illegal TableType for a temporary table.;tableDefinition /// or /// Temporary tables must have at least one column.;tableDefinition /// or /// Illegal name for a column in a temporary table.;tableDefinition /// or /// Illegal ColumnFlags for a column in a temporary table.;tableDefinition /// or /// Default values are not supported for temporary table columns.;tableDefinition /// or /// Temporary tables of type TableType.Sort and TableType.PreSortTemporary must have an index defined.;tableDefinition /// or /// Temporary tables may only have a single index defined.;tableDefinition /// or /// Illegal name for an index in a temporary table.;tableDefinition /// or /// Illegal IndexFlags for an index in a temporary table.;tableDefinition /// or /// Illegal or unsupported MaxKeyLength for an index in a temporary table.;tableDefinition /// or /// No KeyColumns for an index in a temporary table.;tableDefinition /// or /// Too many KeyColumns for an index in a temporary table.;tableDefinition /// or /// A KeyColumn for an index in the temporary table refers to a column that doesn't exist.;tableDefinition /// or /// Conditional columns are not supported for temporary table indices.;tableDefinition /// or /// Temporary tables of type TableType.PreSortTemporary and TableType.Temporary must have a primary index defined.;tableDefinition /// private void ValidateTableDefinition(TableDefinition tableDefinition) { // validate the table's properties DatabaseCommon.CheckName( tableDefinition.Name, new ArgumentException("Illegal name for a temporary table.", "tableDefinition")); if (tableDefinition.Name == null) { throw new ArgumentException("Illegal name for a temporary table.", "tableDefinition"); } if ( !(tableDefinition.Type == TableType.Sort || tableDefinition.Type == TableType.PreSortTemporary || tableDefinition.Type == TableType.Temporary)) { throw new ArgumentException("Illegal TableType for a temporary table.", "tableDefinition"); } // validate all columns if (tableDefinition.Columns.Count == 0) { throw new ArgumentException("Temporary tables must have at least one column.", "tableDefinition"); } foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { DatabaseCommon.CheckName( columnDefinition.Name, new ArgumentException("Illegal name for a column in a temporary table.", "tableDefinition")); if (columnDefinition.Name == null) { throw new ArgumentException("Illegal name for a column in a temporary table.", "tableDefinition"); } if (tableDefinition.Type == TableType.Sort || tableDefinition.Type == TableType.PreSortTemporary) { JET_coltyp coltyp = DatabaseCommon.ColtypFromColumnDefinition(columnDefinition); if (coltyp == JET_coltyp.LongText || coltyp == JET_coltyp.LongBinary) { // timestamp when ESE/ESENT supports JET_bitTTIntrinsicLVsOnly DatabaseCommon.CheckEngineVersion( this.IsamSession, DatabaseCommon.ESENTVersion(6, 1, 6492, 0), DatabaseCommon.ESEVersion(14, 0, 46, 0), new ArgumentException( "LongText and LongBinary columns are not supported for columns in a temporary table of type TableType.Sort or TableType.PreSortTemporary on this version of the database engine.", "tableDefinition")); } } if (0 != (columnDefinition.Flags & ~(ColumnFlags.Fixed | ColumnFlags.Variable | ColumnFlags.Sparse | ColumnFlags.NonNull | ColumnFlags.MultiValued))) { throw new ArgumentException("Illegal ColumnFlags for a column in a temporary table.", "tableDefinition"); } if (columnDefinition.DefaultValue != null) { throw new ArgumentException("Default values are not supported for temporary table columns.", "tableDefinition"); } } // validate all indices if (tableDefinition.Indices.Count == 0 && (tableDefinition.Type == TableType.Sort || tableDefinition.Type == TableType.PreSortTemporary)) { throw new ArgumentException("Temporary tables of type TableType.Sort and TableType.PreSortTemporary must have an index defined.", "tableDefinition"); } if (tableDefinition.Indices.Count > 1) { throw new ArgumentException("Temporary tables may only have a single index defined.", "tableDefinition"); } foreach (IndexDefinition indexDefinition in tableDefinition.Indices) { DatabaseCommon.CheckName( indexDefinition.Name, new ArgumentException("Illegal name for an index in a temporary table.", "tableDefinition")); if (indexDefinition.Name == null) { throw new ArgumentException("Illegal name for an index in a temporary table.", "tableDefinition"); } if (0 != (indexDefinition.Flags & ~(IndexFlags.Unique | IndexFlags.Primary | IndexFlags.AllowNull | IndexFlags.SortNullsLow | IndexFlags.SortNullsHigh | IndexFlags.AllowTruncation))) { throw new ArgumentException("Illegal IndexFlags for an index in a temporary table.", "tableDefinition"); } // Require AllowTruncation. if (0 == (indexDefinition.Flags & IndexFlags.AllowTruncation)) { throw new ArgumentException("Illegal IndexFlags for an index in a temporary table.", "tableDefinition"); } // 255 in XP. long keyMost = this.IsamSession.IsamInstance.IsamSystemParameters.KeyMost; if (indexDefinition.MaxKeyLength < 255 || indexDefinition.MaxKeyLength > keyMost) { throw new ArgumentException("Illegal or unsupported MaxKeyLength for an index in a temporary table.", "tableDefinition"); } // 12 in XP. 16 in Vista. int keyColumnMost = 16; if (indexDefinition.KeyColumns.Count == 0) { throw new ArgumentException("No KeyColumns for an index in a temporary table.", "tableDefinition"); } if (indexDefinition.KeyColumns.Count > keyColumnMost) { throw new ArgumentException("Too many KeyColumns for an index in a temporary table.", "tableDefinition"); } foreach (KeyColumn keyColumn in indexDefinition.KeyColumns) { if (!tableDefinition.Columns.Contains(keyColumn.Name)) { throw new ArgumentException("A KeyColumn for an index in the temporary table refers to a column that doesn't exist.", "tableDefinition"); } } if (indexDefinition.ConditionalColumns.Count != 0) { throw new ArgumentException("Conditional columns are not supported for temporary table indices.", "tableDefinition"); } if ((indexDefinition.Flags & IndexFlags.Primary) == 0 && (tableDefinition.Type == TableType.PreSortTemporary || tableDefinition.Type == TableType.Temporary)) { throw new ArgumentException("Temporary tables of type TableType.PreSortTemporary and TableType.Temporary must have a primary index defined.", "tableDefinition"); } } } } }