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