DSInternals/Src/Microsoft.Database.Isam/TableDefinition.cs

416 lines
16 KiB
C#

// ---------------------------------------------------------------------------
// <copyright file="TableDefinition.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------
// <summary>
// </summary>
// ---------------------------------------------------------------------
namespace Microsoft.Database.Isam
{
using Microsoft.Isam.Esent.Interop;
using System.Collections.Generic;
/// <summary>
/// A Table Definition contains the schema for a single table. It can be
/// used to explore the schema for an existing table, modify the schema
/// for an existing table, and to create the definition for a new table.
/// </summary>
public class TableDefinition
{
/// <summary>
/// The database
/// </summary>
private IsamDatabase database = null;
/// <summary>
/// The name
/// </summary>
private string name = null;
/// <summary>
/// The table type
/// </summary>
private TableType tableType = TableType.None;
/// <summary>
/// The column collection
/// </summary>
private ColumnCollection columnCollection = null;
/// <summary>
/// The index collection
/// </summary>
private IndexCollection indexCollection = null;
/// <summary>
/// Initializes a new instance of the <see cref="TableDefinition"/> class.
/// For use when defining a new table.
/// </summary>
/// <param name="name">the name of the table to be defined</param>
public TableDefinition(string name)
{
this.name = name;
this.tableType = TableType.Base;
this.columnCollection = new ColumnCollection();
this.indexCollection = new IndexCollection();
}
/// <summary>
/// Initializes a new instance of the <see cref="TableDefinition" /> class.
/// For use when defining a new table.
/// </summary>
/// <param name="name">the name of the table to be defined</param>
/// <param name="tableType">Type of the table.</param>
public TableDefinition(string name, TableType tableType)
{
this.name = name;
this.tableType = tableType;
this.columnCollection = new ColumnCollection();
this.indexCollection = new IndexCollection();
}
/// <summary>
/// Initializes a new instance of the <see cref="TableDefinition"/> class.
/// </summary>
internal TableDefinition()
{
}
/// <summary>
/// Gets the name of the table.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name
{
get
{
return this.name;
}
}
/// <summary>
/// Gets the type of the table.
/// </summary>
public TableType Type
{
get
{
return this.tableType;
}
}
/// <summary>
/// Gets a collection containing the table's columns.
/// </summary>
public ColumnCollection Columns
{
get
{
return this.columnCollection;
}
}
/// <summary>
/// Gets a collection containing the tables indices.
/// </summary>
public IndexCollection Indices
{
get
{
return this.indexCollection;
}
}
/// <summary>
/// Gets a collection containing the tables indices.
/// </summary>
public IEnumerable<IndexInfo> Indices2
{
//HACK: We added support to retrieve the low-level IndexInfo instead of high-level IndexCollection.
/* There is a bug in Isam IndexCollection enumerator, which causes it to loop indefinitely
* through the first few indices under some very rare circumstances. */
get
{
// TODO: Possibly add a lock for thread safety.
return Api.GetTableIndexes(this.IsamSession.Sesid, this.Database.Dbid, this.Name);
}
}
/// <summary>
/// Gets the session.
/// </summary>
/// <value>
/// The session.
/// </value>
internal IsamSession IsamSession
{
get
{
return this.database.IsamSession;
}
}
/// <summary>
/// Gets the database.
/// </summary>
/// <value>
/// The database.
/// </value>
internal IsamDatabase Database
{
get
{
return this.database;
}
}
/// <summary>
/// Creates a single column with the specified definition in the table
/// underlying this table definition
/// </summary>
/// <param name="columnDefinition">The column definition.</param>
/// <returns>The <see cref="Columnid"/> object corresponding to the
/// newly-added column.</returns>
/// <remarks>
/// It is currently not possible to add an AutoIncrement column to a
/// table that is being used by a Cursor. All such Cursors must be
/// disposed before the column can be successfully added.
/// </remarks>
public Columnid AddColumn(ColumnDefinition columnDefinition)
{
lock (this.database.IsamSession)
{
using (IsamTransaction trx = new IsamTransaction(this.database.IsamSession))
{
OpenTableGrbit grbit = OpenTableGrbit.None;
// if we are trying to add an auto-inc column then we must
// be able to open the table for exclusive access. if we can't
// then we will not be able to add the column
if ((columnDefinition.Flags & ColumnFlags.AutoIncrement) != 0)
{
grbit = grbit | OpenTableGrbit.DenyRead;
}
// open the table with the appropriate access
JET_TABLEID tableid;
Api.JetOpenTable(this.database.IsamSession.Sesid, this.database.Dbid, this.name, null, 0, grbit, out tableid);
// add the new column to the table
JET_COLUMNDEF columndef = new JET_COLUMNDEF();
columndef.coltyp = DatabaseCommon.ColtypFromColumnDefinition(columnDefinition);
columndef.cp = JET_CP.Unicode;
columndef.cbMax = columnDefinition.MaxLength;
columndef.grbit = Converter.ColumndefGrbitFromColumnFlags(columnDefinition.Flags);
byte[] defaultValueBytes = Converter.BytesFromObject(
columndef.coltyp,
false /* ASCII */,
columnDefinition.DefaultValue);
int defaultValueBytesLength = (defaultValueBytes == null) ? 0 : defaultValueBytes.Length;
JET_COLUMNID jetColumnid;
Api.JetAddColumn(
this.database.IsamSession.Sesid,
tableid,
columnDefinition.Name,
columndef,
defaultValueBytes,
defaultValueBytesLength,
out jetColumnid);
// commit our change
Api.JetCloseTable(this.database.IsamSession.Sesid, tableid);
trx.Commit();
DatabaseCommon.SchemaUpdateID++;
// return the columnid for the new column
return new Columnid(
columnDefinition.Name,
jetColumnid,
columndef.coltyp,
columndef.cp == JET_CP.ASCII);
}
}
}
/// <summary>
/// Deletes a single column from the table underlying this table
/// definition
/// </summary>
/// <param name="columnName">Name of the column.</param>
/// <remarks>
/// It is not possible to delete a column that is currently referenced
/// by an index over this table.
/// </remarks>
public void DropColumn(string columnName)
{
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);
// delete the column from the table
Api.JetDeleteColumn(this.database.IsamSession.Sesid, tableid, columnName);
// commit our change
Api.JetCloseTable(this.database.IsamSession.Sesid, tableid);
trx.Commit();
DatabaseCommon.SchemaUpdateID++;
}
}
}
/// <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>
/// Deletes a single index from the table underlying this table
/// </summary>
/// <param name="name">The name.</param>
/// <remarks>
/// It is currently not possible to delete an index that is being used
/// by a Cursor as its CurrentIndex. All such Cursors must either be
/// disposed or set to a different index before the index can be
/// successfully deleted.
/// </remarks>
public void DropIndex(string name)
{
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);
// delete the index from the table
Api.JetDeleteIndex(this.database.IsamSession.Sesid, tableid, name);
// commit our change
Api.JetCloseTable(this.database.IsamSession.Sesid, tableid);
trx.Commit();
DatabaseCommon.SchemaUpdateID++;
}
}
}
/// <summary>
/// Loads the specified table specified in the <see cref="JET_OBJECTLIST"/> object.
/// </summary>
/// <param name="database">The database.</param>
/// <param name="objectList">The object list.</param>
/// <returns>A <see cref="TableDefinition"/> corresponding to the table specified in <paramref name="objectList"/>.</returns>
internal static TableDefinition Load(IsamDatabase database, JET_OBJECTLIST objectList)
{
lock (database.IsamSession)
{
// save the database
TableDefinition tableDefinition = new TableDefinition();
tableDefinition.database = database;
// load info for the table
tableDefinition.name = Api.RetrieveColumnAsString(
database.IsamSession.Sesid,
objectList.tableid,
objectList.columnidobjectname);
// create our column and index collections
tableDefinition.columnCollection = new ColumnCollection(database, tableDefinition.name);
tableDefinition.indexCollection = new IndexCollection(database, tableDefinition.name);
return tableDefinition;
}
}
/// <summary>
/// Loads the specified table specified in the <see cref="JET_OBJECTINFO"/> object.
/// </summary>
/// <param name="database">The database.</param>
/// <param name="tableName">Name of the table.</param>
/// <param name="objectInfo">The object information.</param>
/// <returns>A <see cref="TableDefinition"/> corresponding to the table specified in <paramref name="objectInfo"/>.</returns>
internal static TableDefinition Load(IsamDatabase database, string tableName, JET_OBJECTINFO objectInfo)
{
lock (database.IsamSession)
{
// save the database
TableDefinition tableDefinition = new TableDefinition();
tableDefinition.database = database;
// load info for the table
tableDefinition.name = tableName;
// create our column and index collections
tableDefinition.columnCollection = new ColumnCollection(database, tableDefinition.name);
tableDefinition.indexCollection = new IndexCollection(database, tableDefinition.name);
return tableDefinition;
}
}
}
}