/// <summary> /// Creates a single index with the specified definition in the table /// underlying this table definition /// </summary> /// <param name="indexDefinition">The index definition.</param> public void CreateIndex(IndexDefinition indexDefinition) { lock (this.database.IsamSession) { using (IsamTransaction trx = new IsamTransaction(this.database.IsamSession)) { // open the table JET_TABLEID tableid; Api.JetOpenTable( this.database.IsamSession.Sesid, this.database.Dbid, this.name, null, 0, OpenTableGrbit.None, out tableid); // add the new index to the table JET_INDEXCREATE[] indexcreates = new JET_INDEXCREATE[1] { new JET_INDEXCREATE() }; indexcreates[0].szIndexName = indexDefinition.Name; indexcreates[0].szKey = DatabaseCommon.IndexKeyFromIndexDefinition(indexDefinition); indexcreates[0].cbKey = indexcreates[0].szKey.Length; indexcreates[0].grbit = DatabaseCommon.GrbitFromIndexDefinition(indexDefinition); indexcreates[0].ulDensity = indexDefinition.Density; indexcreates[0].pidxUnicode = new JET_UNICODEINDEX(); indexcreates[0].pidxUnicode.lcid = indexDefinition.CultureInfo.LCID; indexcreates[0].pidxUnicode.dwMapFlags = Converter.MapFlagsFromUnicodeIndexFlags(Converter.UnicodeFlagsFromCompareOptions(indexDefinition.CompareOptions)); indexcreates[0].rgconditionalcolumn = DatabaseCommon.ConditionalColumnsFromIndexDefinition(indexDefinition); indexcreates[0].cConditionalColumn = indexcreates[0].rgconditionalcolumn.Length; indexcreates[0].cbKeyMost = indexDefinition.MaxKeyLength; Api.JetCreateIndex2(this.database.IsamSession.Sesid, tableid, indexcreates, indexcreates.Length); // commit our change Api.JetCloseTable(this.database.IsamSession.Sesid, tableid); trx.Commit(); DatabaseCommon.SchemaUpdateID++; } } }
/// <summary> /// Validates the table definition. /// </summary> /// <param name="tableDefinition">The table definition.</param> /// <exception cref="System.ArgumentException"> /// 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 /// </exception> 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"); } } }
/// <summary> /// Makes the <see cref="JET_OPENTEMPORARYTABLE"/> object to later open it. /// </summary> /// <param name="tableDefinition">The table definition.</param> /// <returns>The newly created <see cref="JET_OPENTEMPORARYTABLE"/> object.</returns> 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); }
/// <summary> /// Creates a <see cref="TableDefinition"/> object from a <see cref="JET_OPENTEMPORARYTABLE"/> /// object, suitable for caching. /// </summary> /// <param name="tableDefinition">The table definition.</param> /// <param name="openTemporaryTable">The open temporary table.</param> /// <returns>A <see cref="TableDefinition"/> object suitable for caching.</returns> 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); }
/// <summary> /// Creates a single table with the specified definition in the database /// </summary> /// <param name="tableDefinition">The table definition.</param> /// <exception cref="EsentTableDuplicateException"> /// Thrown when the table definition overlaps with an already existing table. /// </exception> /// <exception cref="System.ArgumentException">A MaxKeyLength > 255 is not supported for indices over a temporary table on this version of the database engine.;tableDefinition</exception> 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); } }