// --------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // --------------------------------------------------------------------------- // --------------------------------------------------------------------- // // // --------------------------------------------------------------------- namespace Microsoft.Database.Isam { using System; using System.Globalization; using System.Text; using Microsoft.Isam.Esent.Interop; using Microsoft.Isam.Esent.Interop.Vista; /// /// An Index Definition contains the schema for a single index. It can be /// used to explore the schema for an existing index and to create the /// definition for a new index. /// public class IndexDefinition { /// /// The name /// private string name; /// /// The flags /// private IndexFlags flags = IndexFlags.None; /// /// The density /// private int density = 100; /// /// The culture information /// private CultureInfo cultureInfo = new CultureInfo("en-us"); /// /// The compare options /// private CompareOptions compareOptions = CompareOptions.None; /// /// The maximum key length /// private int maxKeyLength = 256; // force new index semantics in ESENT (don't truncate source data to max key length prior to normalization) /// /// The key column collection /// private KeyColumnCollection keyColumnCollection; /// /// The conditional column collection /// private ConditionalColumnCollection conditionalColumnCollection; /// /// The read only /// private bool readOnly = false; /// /// Initializes a new instance of the class. /// For use when defining a new index. /// /// /// The name of the index to be defined. /// public IndexDefinition(string name) { this.name = name; this.keyColumnCollection = new KeyColumnCollection(); this.conditionalColumnCollection = new ConditionalColumnCollection(); } /// /// Initializes a new instance of the class. /// internal IndexDefinition() { } /// /// Gets the name of the index. /// public string Name { get { return this.name; } } /// /// Gets or sets the index's flags. /// public IndexFlags Flags { get { return this.flags; } set { this.CheckReadOnly(); this.flags = value; } } /// /// Gets or sets the ideal density of the index in percent. /// /// /// The ideal density of an index is used to setup a newly created /// index into a layout on disk that is appropriate for the typical /// workload on that index. For example, an index that will always be /// appended should have a density of 100% and an index that will /// experience random insertion should have a lower density to reflect /// the average density on which that index would converge at run time. /// /// The density is primarily tweaked for performance reasons. /// /// public int Density { get { return this.density; } set { this.CheckReadOnly(); this.density = value; } } /// /// Gets or sets the locale of the index. /// /// /// A locale can be specified for any index but will only affect /// indexes with key columns containing string data. /// public CultureInfo CultureInfo { get { return this.cultureInfo; } set { this.CheckReadOnly(); this.cultureInfo = value; } } /// /// Gets or sets the locale sensitive collation options for the index. /// /// /// These options can be specified for any index but will only affect /// indexes with key columns containing string data. /// public CompareOptions CompareOptions { get { return this.compareOptions; } set { this.CheckReadOnly(); this.compareOptions = value; } } /// /// Gets or sets the maximum length of a normalized key that will be stored in the /// index in bytes. /// /// /// In many ways, this is one of the most critical parameters of an /// index. Key truncation can cause different records to end up with /// the same key which makes them indistinguishable to the index. This /// can cause records to be ordered randomly with respect to each other /// according to the index's sort order. This can also cause otherwise /// unique index entries to be considered duplicates which can cause /// some updates to fail. /// /// The current limits for the ISAM are as follows: databases with 2KB /// pages can have keys up to 255B in length; databases with 4KB pages /// can have keys up to 1000B in length; and databases with 8KB pages /// can have keys up to 2000B in length. Note that these lengths are /// for normalized keys not raw key data. Normalized keys are always /// slightly longer than the raw data from which they are derived. /// /// /// NOTE: a max key length of 255 is "special" in that it uses legacy /// semantics for building keys. Those semantics include the truncation /// of the source data for normalization at 256 bytes. This mode should /// only be used if you specifically want this behavior for backwards /// compatibility because it causes errors during the generation of /// index keys under certain conditions. /// /// public int MaxKeyLength { get { return this.maxKeyLength; } set { this.CheckReadOnly(); this.maxKeyLength = value; } } /// /// Gets a collection of the key columns in this index. /// public KeyColumnCollection KeyColumns { get { return this.keyColumnCollection; } } /// /// Gets a collection of conditional columns in this index. /// public ConditionalColumnCollection ConditionalColumns { get { return this.conditionalColumnCollection; } } /// /// Gets a value indicating whether this index definition cannot be changed /// /// /// true if [is read only]; otherwise, false. /// public bool IsReadOnly { get { return this.readOnly; } } /// /// Sets a value indicating whether [read only]. /// /// /// true if [read only]; otherwise, false. /// internal bool ReadOnly { set { this.readOnly = value; } } /// /// Creates an object from the specified . /// /// The database. /// Name of the table. /// The index list. /// An object represting the specified index. internal static IndexDefinition Load(IsamDatabase database, string tableName, JET_INDEXLIST indexList) { lock (database.IsamSession) { JET_SESID sesid = database.IsamSession.Sesid; using (IsamTransaction trx = new IsamTransaction(database.IsamSession)) { // load info for the index IndexDefinition indexDefinition = new IndexDefinition(); indexDefinition.name = Api.RetrieveColumnAsString( sesid, indexList.tableid, indexList.columnidindexname); CreateIndexGrbit grbitIndex = (CreateIndexGrbit)Api.RetrieveColumnAsUInt32(sesid, indexList.tableid, indexList.columnidgrbitIndex); indexDefinition.flags = IndexFlagsFromGrbits(grbitIndex); Api.JetGetIndexInfo( sesid, database.Dbid, tableName, indexDefinition.name, out indexDefinition.density, JET_IdxInfo.SpaceAlloc); int lcid; Api.JetGetIndexInfo( database.IsamSession.Sesid, database.Dbid, tableName, indexDefinition.name, out lcid, JET_IdxInfo.LCID); indexDefinition.cultureInfo = new CultureInfo(lcid); indexDefinition.compareOptions = Conversions.CompareOptionsFromLCMapFlags( Api.RetrieveColumnAsUInt32( database.IsamSession.Sesid, indexList.tableid, indexList.columnidLCMapFlags).GetValueOrDefault()); // CONSIDER: move this workaround into Isam.Interop try { ushort maxKeyLength; Api.JetGetIndexInfo( database.IsamSession.Sesid, database.Dbid, tableName, indexDefinition.name, out maxKeyLength, JET_IdxInfo.KeyMost); indexDefinition.maxKeyLength = maxKeyLength; } catch (EsentInvalidParameterException) { indexDefinition.maxKeyLength = 255; } catch (EsentColumnNotFoundException) { indexDefinition.maxKeyLength = 255; } // load info for each key column in the index int currentColumn = 0; int totalNumberColumns = Api.RetrieveColumnAsInt32(sesid, indexList.tableid, indexList.columnidcColumn).GetValueOrDefault(); indexDefinition.keyColumnCollection = new KeyColumnCollection(); do { // load info for this key column IndexKeyGrbit grbitColumn = (IndexKeyGrbit)Api.RetrieveColumnAsUInt32(sesid, indexList.tableid, indexList.columnidgrbitColumn); bool isAscending = (grbitColumn & IndexKeyGrbit.Descending) == 0; string columnName = Api.RetrieveColumnAsString( sesid, indexList.tableid, indexList.columnidcolumnname); JET_COLUMNBASE columnbase; Api.JetGetColumnInfo(sesid, database.Dbid, tableName, columnName, out columnbase); indexDefinition.keyColumnCollection.Add(new KeyColumn(new Columnid(columnbase), isAscending)); // move onto the next key column definition, unless it is // the last key column if (currentColumn != totalNumberColumns - 1) { Api.TryMoveNext(sesid, indexList.tableid); } } while (++currentColumn < totalNumberColumns); indexDefinition.keyColumnCollection.ReadOnly = true; // Vista: There is currently no efficient means to retrieve the // conditional columns for an index from JET. so, we are // going to reach into the catalog and fetch them directly. // // FUTURE: Windows 7 introduced Windows7IdxInfo.CreateIndex and Windows7IdxInfo.CreateIndex2 (and // Win8 has Windows8IdxInfo.CreateIndex3). Consider retrieving the conditional columns with that // API and converting the results. But that does not solve the problem for Vista. indexDefinition.conditionalColumnCollection = new ConditionalColumnCollection(); JET_TABLEID tableidCatalog; Api.JetOpenTable( database.IsamSession.Sesid, database.Dbid, "MSysObjects", null, 0, OpenTableGrbit.ReadOnly, out tableidCatalog); Api.JetSetCurrentIndex(sesid, tableidCatalog, "RootObjects"); Api.MakeKey(sesid, tableidCatalog, true, MakeKeyGrbit.NewKey); Api.MakeKey(sesid, tableidCatalog, tableName, Encoding.ASCII, MakeKeyGrbit.None); Api.JetSeek(sesid, tableidCatalog, SeekGrbit.SeekEQ); JET_COLUMNID columnidTemp = Api.GetTableColumnid(sesid, tableidCatalog, "ObjidTable"); int objidTable = Api.RetrieveColumnAsInt32(sesid, tableidCatalog, columnidTemp).GetValueOrDefault(); Api.JetSetCurrentIndex(sesid, tableidCatalog, "Name"); Api.MakeKey(sesid, tableidCatalog, objidTable, MakeKeyGrbit.NewKey); Api.MakeKey(sesid, tableidCatalog, (short)3, MakeKeyGrbit.None); Api.MakeKey(sesid, tableidCatalog, indexDefinition.name, Encoding.ASCII, MakeKeyGrbit.None); Api.JetSeek(sesid, tableidCatalog, SeekGrbit.SeekEQ); columnidTemp = Api.GetTableColumnid(sesid, tableidCatalog, "Flags"); int indexFlagsBytes = Api.RetrieveColumnAsInt32(sesid, tableidCatalog, columnidTemp).GetValueOrDefault(); columnidTemp = Api.GetTableColumnid(sesid, tableidCatalog, "ConditionalColumns"); byte[] conditionalColumnsBytes = Api.RetrieveColumn(sesid, tableidCatalog, columnidTemp); for (int ib = 0; conditionalColumnsBytes != null && ib < conditionalColumnsBytes.Length; ib += 4) { uint colid; bool mustBeNull; JET_COLUMNBASE columnBase; // fIDXExtendedColumns if ((indexFlagsBytes & 0xffff0000) == 0x00010000) { // fIDXSEGTemplateColumn if ((conditionalColumnsBytes[ib + 0] & 0x80) != 0) { // fCOLUMNIDTemplate colid = 0x80000000 | (uint)(conditionalColumnsBytes[ib + 3] << 8) | (uint)conditionalColumnsBytes[ib + 2]; } else { colid = (uint)(conditionalColumnsBytes[ib + 3] << 8) | (uint)conditionalColumnsBytes[ib + 2]; } // fIDXSEGMustBeNull if ((conditionalColumnsBytes[ib + 0] & 0x20) != 0) { mustBeNull = true; } else { mustBeNull = false; } } else { // do not load conditional columns from an unknown format continue; } JET_COLUMNID castedColid = JET_COLUMNID.CreateColumnidFromNativeValue(unchecked((int)colid)); VistaApi.JetGetColumnInfo( database.IsamSession.Sesid, database.Dbid, tableName, castedColid, out columnBase); indexDefinition.conditionalColumnCollection.Add(new ConditionalColumn(new Columnid(columnBase), mustBeNull)); } indexDefinition.conditionalColumnCollection.ReadOnly = true; indexDefinition.ReadOnly = true; return indexDefinition; } } } /// /// Converts the enumeration to . /// /// Index of the grbit. /// The equivalent to . private static IndexFlags IndexFlagsFromGrbits(CreateIndexGrbit grbitIndex) { IndexFlags flags = IndexFlags.None; if ((grbitIndex & CreateIndexGrbit.IndexUnique) != 0) { flags = flags | IndexFlags.Unique; } if ((grbitIndex & CreateIndexGrbit.IndexPrimary) != 0) { flags = flags | IndexFlags.Primary; } if ((grbitIndex & CreateIndexGrbit.IndexDisallowNull) != 0) { flags = flags | IndexFlags.DisallowNull; } if ((grbitIndex & CreateIndexGrbit.IndexIgnoreNull) != 0) { flags = flags | IndexFlags.IgnoreNull; } if ((grbitIndex & CreateIndexGrbit.IndexIgnoreAnyNull) != 0) { flags = flags | IndexFlags.IgnoreAnyNull; } if ((grbitIndex & CreateIndexGrbit.IndexSortNullsHigh) != 0) { flags = flags | IndexFlags.SortNullsHigh; } if ((grbitIndex & VistaGrbits.IndexDisallowTruncation) != 0) { flags = flags | IndexFlags.DisallowTruncation; } else { flags = flags | IndexFlags.AllowTruncation; } return flags; } /// /// Checks the read only. /// /// this index definition cannot be changed private void CheckReadOnly() { if (this.readOnly) { throw new NotSupportedException("this index definition cannot be changed"); } } } }