//----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. // //----------------------------------------------------------------------- namespace Microsoft.Isam.Esent.Interop { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; #if MANAGEDESENT_SUPPORTS_SERIALIZATION using System.Runtime.Serialization.Formatters.Binary; #endif using System.Text; /// /// Helper methods for the ESENT API. These aren't interop versions /// of the API, but encapsulate very common uses of the functions. /// public static partial class Api { /// /// Encoding to use to decode ASCII text. We use this because /// UTF8.GetString is faster than ASCII.GetString. /// private static readonly Encoding AsciiDecoder = new UTF8Encoding(false, true); /// /// Retrieves the bookmark for the record that is associated with the index entry /// at the current position of a cursor. This bookmark can then be used to /// reposition that cursor back to the same record using JetGotoBookmark. /// /// The session to use. /// The cursor to retrieve the bookmark from. /// The bookmark of the record. public static byte[] GetBookmark(JET_SESID sesid, JET_TABLEID tableid) { byte[] buffer = null; byte[] bookmark; try { buffer = Caches.BookmarkCache.Allocate(); int bookmarkSize; Api.JetGetBookmark(sesid, tableid, buffer, buffer.Length, out bookmarkSize); bookmark = MemoryCache.Duplicate(buffer, bookmarkSize); } finally { if (buffer != null) { Caches.BookmarkCache.Free(ref buffer); } } return bookmark; } /// /// Retrieves the bookmark for the record that is associated with the index entry /// at the current position of a cursor. This bookmark can then be used to /// reposition that cursor back to the same record using JetGotoBookmark. /// /// The session to use. /// The cursor to retrieve the bookmark from. /// Returns the primary bookmark. /// The secondary bookmark of the record. public static byte[] GetSecondaryBookmark( JET_SESID sesid, JET_TABLEID tableid, out byte[] primaryBookmark) { byte[] bufferPrimary = null; byte[] bufferSecondary = null; byte[] secondaryBookmark; primaryBookmark = null; try { bufferPrimary = Caches.BookmarkCache.Allocate(); bufferSecondary = Caches.SecondaryBookmarkCache.Allocate(); int bookmarkSizePrimary; int bookmarkSizeSecondary; Api.JetGetSecondaryIndexBookmark( sesid, tableid, bufferSecondary, bufferSecondary.Length, out bookmarkSizeSecondary, bufferPrimary, bufferPrimary.Length, out bookmarkSizePrimary, GetSecondaryIndexBookmarkGrbit.None); primaryBookmark = MemoryCache.Duplicate(bufferPrimary, bookmarkSizePrimary); secondaryBookmark = MemoryCache.Duplicate(bufferSecondary, bookmarkSizeSecondary); } finally { if (bufferPrimary != null) { Caches.BookmarkCache.Free(ref bufferPrimary); } if (bufferSecondary != null) { Caches.BookmarkCache.Free(ref bufferSecondary); } } return secondaryBookmark; } /// /// Retrieves the key for the index entry at the current position of a cursor. /// /// The session to use. /// The cursor to retrieve the key from. /// Retrieve key options. /// The retrieved key. public static byte[] RetrieveKey(JET_SESID sesid, JET_TABLEID tableid, RetrieveKeyGrbit grbit) { byte[] buffer = null; byte[] key; try { buffer = Caches.BookmarkCache.Allocate(); int keySize; Api.JetRetrieveKey(sesid, tableid, buffer, buffer.Length, out keySize, grbit); key = MemoryCache.Duplicate(buffer, keySize); } finally { if (buffer != null) { Caches.BookmarkCache.Free(ref buffer); } } return key; } /// /// Retrieves the size of a single column value from the current record. /// The record is that record associated with the index entry at the /// current position of the cursor. Alternatively, this function can /// retrieve a column from a record being created in the cursor copy /// buffer. This function can also retrieve column data from an index /// entry that references the current record. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The size of the column. 0 if the column is null. public static int? RetrieveColumnSize(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnSize(sesid, tableid, columnid, 1, RetrieveColumnGrbit.None); } /// /// Retrieves the size of a single column value from the current record. /// The record is that record associated with the index entry at the /// current position of the cursor. Alternatively, this function can /// retrieve a column from a record being created in the cursor copy /// buffer. This function can also retrieve column data from an index /// entry that references the current record. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// /// The sequence number of value in a multi-valued column. /// The array of values is one-based. The first value is /// sequence 1, not 0. If the record column has only one value then /// 1 should be passed as the itagSequence. /// /// Retrieve column options. /// The size of the column. 0 if the column is null. public static int? RetrieveColumnSize( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, int itagSequence, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } var retinfo = new JET_RETINFO { itagSequence = itagSequence }; int dataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, null, 0, out dataSize, grbit, retinfo); if (JET_wrn.ColumnNull == wrn) { return null; } return dataSize; } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// Alternatively, this function can retrieve a column from a record being created /// in the cursor copy buffer. This function can also retrieve column data from an /// index entry that references the current record. In addition to retrieving the /// actual column value, JetRetrieveColumn can also be used to retrieve the size /// of a column, before retrieving the column data itself so that application /// buffers can be sized appropriately. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieve column options. /// /// If pretinfo is give as NULL then the function behaves as though an itagSequence /// of 1 and an ibLongValue of 0 (zero) were given. This causes column retrieval to /// retrieve the first value of a multi-valued column, and to retrieve long data at /// offset 0 (zero). /// /// The data retrieved from the column. Null if the column is null. public static byte[] RetrieveColumn( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit, JET_RETINFO retinfo) { // We expect most column values retrieved this way to be small (retrieving a 1GB LV as one // chunk is a bit extreme!). Allocate a cached buffer and use that, allocating a larger one // if needed. byte[] cache = null; byte[] data; try { cache = Caches.ColumnCache.Allocate(); data = cache; int dataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, data, data.Length, out dataSize, grbit, retinfo); if (JET_wrn.ColumnNull == wrn) { // null column data = null; } else if (JET_wrn.Success == wrn) { data = MemoryCache.Duplicate(data, dataSize); } else { // there is more data to retrieve Debug.Assert(JET_wrn.BufferTruncated == wrn, "Unexpected warning: " + wrn.ToString()); data = new byte[dataSize]; wrn = JetRetrieveColumn( sesid, tableid, columnid, data, data.Length, out dataSize, grbit, retinfo); if (JET_wrn.BufferTruncated == wrn) { string error = string.Format( CultureInfo.CurrentCulture, "Column size changed from {0} to {1}. The record was probably updated by another thread.", data.Length, dataSize); Trace.TraceError(error); throw new InvalidOperationException(error); } } } finally { if (cache != null) { Caches.ColumnCache.Free(ref cache); } } return data; } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column. Null if the column is null. public static byte[] RetrieveColumn(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumn(sesid, tableid, columnid, RetrieveColumnGrbit.None, null); } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// The Unicode encoding is used. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a string. Null if the column is null. public static string RetrieveColumnAsString(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsString(sesid, tableid, columnid, Encoding.Unicode, RetrieveColumnGrbit.None); } /// /// Retrieves a string column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The string encoding to use when converting data. /// The data retrieved from the column as a string. Null if the column is null. public static string RetrieveColumnAsString( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, Encoding encoding) { return RetrieveColumnAsString(sesid, tableid, columnid, encoding, RetrieveColumnGrbit.None); } /// /// Retrieves a string column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The string encoding to use when converting data. /// Retrieval options. /// The data retrieved from the column as a string. Null if the column is null. public static string RetrieveColumnAsString( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, Encoding encoding, RetrieveColumnGrbit grbit) { // This is an optimization for retrieving Unicode strings if (Encoding.Unicode == encoding) { return RetrieveUnicodeString(sesid, tableid, columnid, grbit); } // Retrieving a string happens in two stages: first the data is retrieved into a data // buffer and then the buffer is converted to a string. The buffer isn't needed for // very long so we try to use a cached buffer. byte[] cachedBuffer = null; string s; try { cachedBuffer = Caches.ColumnCache.Allocate(); byte[] data = cachedBuffer; int dataSize; JET_wrn wrn = JetRetrieveColumn(sesid, tableid, columnid, data, data.Length, out dataSize, grbit, null); if (JET_wrn.ColumnNull == wrn) { return null; } if (JET_wrn.BufferTruncated == wrn) { Debug.Assert(dataSize > data.Length, "Expected column to be bigger than buffer"); data = new byte[dataSize]; wrn = JetRetrieveColumn(sesid, tableid, columnid, data, data.Length, out dataSize, grbit, null); if (JET_wrn.BufferTruncated == wrn) { string error = string.Format( CultureInfo.CurrentCulture, "Column size changed from {0} to {1}. The record was probably updated by another thread.", data.Length, dataSize); Trace.TraceError(error); throw new InvalidOperationException(error); } } #if MANAGEDESENT_SUPPORTS_ANSI // If we are about to decode ASCII data then use the UTF8 decoder instead. This // is done because the UTF8 decoder is faster and will produce the same results // on ASCII data. Different results will be produced on invalid data, but that // behaviour can be considered undefined. Encoding decoder = (encoding is ASCIIEncoding) ? AsciiDecoder : encoding; #else Encoding decoder = encoding; #endif s = decoder.GetString(data, 0, dataSize); } finally { if (cachedBuffer != null) { // Now we have extracted the string from the buffer we can free (cache) the buffer. Caches.ColumnCache.Free(ref cachedBuffer); } } return s; } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a short. Null if the column is null. public static short? RetrieveColumnAsInt16(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsInt16(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves an int16 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a short. Null if the column is null. public static short? RetrieveColumnAsInt16( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(short); short data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as an int. Null if the column is null. public static int? RetrieveColumnAsInt32(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsInt32(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves an int32 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as an int. Null if the column is null. public static int? RetrieveColumnAsInt32( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(int); int data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a long. Null if the column is null. public static long? RetrieveColumnAsInt64(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsInt64(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a single column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a long. Null if the column is null. public static long? RetrieveColumnAsInt64( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(long); long data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a float column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a float. Null if the column is null. public static float? RetrieveColumnAsFloat(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsFloat(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a float column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a float. Null if the column is null. public static float? RetrieveColumnAsFloat( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(float); float data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a double column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a double. Null if the column is null. public static double? RetrieveColumnAsDouble(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsDouble(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a double column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a double. Null if the column is null. public static double? RetrieveColumnAsDouble( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(double); double data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a boolean column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a boolean. Null if the column is null. public static bool? RetrieveColumnAsBoolean(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsBoolean(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a boolean column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a boolean. Null if the column is null. public static bool? RetrieveColumnAsBoolean( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { byte? b = RetrieveColumnAsByte(sesid, tableid, columnid, grbit); if (b.HasValue) { return 0 != b.Value; } return new bool?(); } /// /// Retrieves a byte column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a byte. Null if the column is null. public static byte? RetrieveColumnAsByte(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsByte(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a byte column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a byte. Null if the column is null. public static byte? RetrieveColumnAsByte( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(byte); byte data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a guid column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a guid. Null if the column is null. public static Guid? RetrieveColumnAsGuid(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsGuid(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a guid column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a guid. Null if the column is null. public static Guid? RetrieveColumnAsGuid( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = 16; Guid data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a datetime column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as a datetime. Null if the column is null. public static DateTime? RetrieveColumnAsDateTime(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsDateTime(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a datetime column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as a datetime. Null if the column is null. public static DateTime? RetrieveColumnAsDateTime( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // Internally DateTime is stored in OLE Automation format double? oadate = RetrieveColumnAsDouble(sesid, tableid, columnid, grbit); if (oadate.HasValue) { return Conversions.ConvertDoubleToDateTime(oadate.Value); } return new DateTime?(); } /// /// Retrieves a uint16 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as an UInt16. Null if the column is null. [CLSCompliant(false)] public static ushort? RetrieveColumnAsUInt16(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsUInt16(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a uint16 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as an UInt16. Null if the column is null. [CLSCompliant(false)] public static ushort? RetrieveColumnAsUInt16( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(ushort); ushort data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a uint32 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as an UInt32. Null if the column is null. [CLSCompliant(false)] public static uint? RetrieveColumnAsUInt32(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsUInt32(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a uint32 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as an UInt32. Null if the column is null. [CLSCompliant(false)] public static uint? RetrieveColumnAsUInt32( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(uint); uint data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } /// /// Retrieves a uint64 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// The data retrieved from the column as an UInt64. Null if the column is null. [CLSCompliant(false)] public static ulong? RetrieveColumnAsUInt64(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return RetrieveColumnAsUInt64(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Retrieves a uint64 column value from the current record. The record is that /// record associated with the index entry at the current position of the cursor. /// /// The session to use. /// The cursor to retrieve the column from. /// The columnid to retrieve. /// Retrieval options. /// The data retrieved from the column as an UInt64. Null if the column is null. [CLSCompliant(false)] public static ulong? RetrieveColumnAsUInt64( JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } unsafe { const int DataSize = sizeof(ulong); ulong data; var pointer = new IntPtr(&data); int actualDataSize; JET_wrn wrn = JetRetrieveColumn( sesid, tableid, columnid, pointer, DataSize, out actualDataSize, grbit, null); return CreateReturnValue(data, DataSize, wrn, actualDataSize); } } #if MANAGEDESENT_SUPPORTS_SERIALIZATION /// /// Deserialize an object from a column. /// /// The session to use. /// The table to read from. /// The column to read from. /// The deserialized object. Null if the column was null. public static object DeserializeObjectFromColumn(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid) { return DeserializeObjectFromColumn(sesid, tableid, columnid, RetrieveColumnGrbit.None); } /// /// Deserialize an object from a column. /// /// The session to use. /// The table to read from. /// The column to read from. /// The retrieval options to use. /// The deserialized object. Null if the column was null. [SuppressMessage("Exchange.Security", "EX0043:DoNotUseBinarySoapFormatter", Justification = "Suppress warning in current code base.The usage has already been verified.")] public static object DeserializeObjectFromColumn(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { // We cannot support this request when there is no way to indicate that a column reference is returned. if ((grbit & (RetrieveColumnGrbit)0x00020000) != 0) // UnpublishedGrbits.RetrieveAsRefIfNotInRecord { throw new EsentInvalidGrbitException(); } int actualSize; if (JET_wrn.ColumnNull == Api.JetRetrieveColumn(sesid, tableid, columnid, null, 0, out actualSize, grbit, null)) { return null; } using (var stream = new ColumnStream(sesid, tableid, columnid)) { var deseriaizer = new BinaryFormatter(); return deseriaizer.Deserialize(stream); } } #endif /// /// Retrieves columns into ColumnValue objects. /// /// The session to use. /// The cursor retrieve the data from. The cursor should be positioned on a record. /// The values to retrieve. public static void RetrieveColumns(JET_SESID sesid, JET_TABLEID tableid, params ColumnValue[] values) { if (null == values) { throw new ArgumentNullException("values"); } if (0 == values.Length) { throw new ArgumentOutOfRangeException("values", values.Length, "must have at least one value"); } ColumnValue.RetrieveColumns(sesid, tableid, values); } /// /// Efficiently retrieves a set of columns and their values from the /// current record of a cursor or the copy buffer of that cursor. /// /// The session to use. /// The cursor to retrieve data from. /// Enumerate options. /// The discovered columns and their values. public static IEnumerable EnumerateColumns( JET_SESID sesid, JET_TABLEID tableid, EnumerateColumnsGrbit grbit) { IEnumerable enumeratedColumns; Api.JetEnumerateColumns(sesid, tableid, grbit, out enumeratedColumns); return enumeratedColumns; } /// /// Create the nullable return value. /// /// The (struct) type to return. /// The data retrieved from the column. /// The size of the data. /// The warning code from esent. /// The actual size of the data retireved fomr esent. /// A nullable struct of type T. private static T? CreateReturnValue(T data, int dataSize, JET_wrn wrn, int actualDataSize) where T : struct { if (JET_wrn.ColumnNull == wrn) { return new T?(); } CheckDataSize(dataSize, actualDataSize); return data; } /// /// Make sure the retrieved data size is at least as long as the expected size. /// An exception is thrown if the data isn't long enough. /// /// The expected data size. /// The size of the retrieved data. private static void CheckDataSize(int expectedDataSize, int actualDataSize) { if (actualDataSize < expectedDataSize) { throw new EsentInvalidColumnException(); } } /// /// Recursively pin the retrieve buffers in the JET_RETRIEVECOLUMN /// structures and then retrieve the columns. This is done to avoid /// creating GCHandles, which are expensive. This function pins /// the current retrievecolumn structure (indicated by i) and then /// recursively calls itself until all structures are pinned. This /// is done because it isn't possible to create an arbitrary number /// of pinned variables in a method. /// /// /// The session to use. /// /// /// The table to retrieve from. /// /// /// The nativeretrievecolumns structure. /// /// The managed retrieve columns structure. /// /// The number of columns. /// The column currently being processed. /// An error code from JetRetrieveColumns. private static unsafe int PinColumnsAndRetrieve( JET_SESID sesid, JET_TABLEID tableid, NATIVE_RETRIEVECOLUMN* nativeretrievecolumns, IList retrievecolumns, int numColumns, int i) { // If consecutive JET_RETRIEVECOLUMN structures are using the same buffer then only pin it once. fixed (byte* pinnedBuffer = retrievecolumns[i].pvData) { do { retrievecolumns[i].CheckDataSize(); retrievecolumns[i].GetNativeRetrievecolumn(ref nativeretrievecolumns[i]); nativeretrievecolumns[i].pvData = new IntPtr(pinnedBuffer + retrievecolumns[i].ibData); i++; } while (i < numColumns && retrievecolumns[i].pvData == retrievecolumns[i - 1].pvData); return i == numColumns ? Impl.JetRetrieveColumns(sesid, tableid, nativeretrievecolumns, numColumns) : PinColumnsAndRetrieve(sesid, tableid, nativeretrievecolumns, retrievecolumns, numColumns, i); } } /// /// Retrieve a Unicode (UTF16) string. This is optimized to take advantage of the fact /// that no conversion is needed. /// /// The session to use. /// The table to retrieve from. /// The column to retrieve. /// Retrieve options. /// The string retrieved from the column. private static unsafe string RetrieveUnicodeString(JET_SESID sesid, JET_TABLEID tableid, JET_COLUMNID columnid, RetrieveColumnGrbit grbit) { Debug.Assert((grbit & (RetrieveColumnGrbit)0x00020000) == 0, "UnpublishedGrbits.RetrieveAsRefIfNotInRecord is not supported."); // 512 Unicode characters (1kb on stack) const int BufferSize = 512; char* buffer = stackalloc char[BufferSize]; int actualDataSize; JET_wrn wrn = JetRetrieveColumn(sesid, tableid, columnid, new IntPtr(buffer), BufferSize * sizeof(char), out actualDataSize, grbit, null); if (JET_wrn.ColumnNull == wrn) { return null; } if (JET_wrn.Success == wrn) { ////return StringCache.GetString(buffer, 0, actualDataSize); return new string(buffer, 0, actualDataSize / sizeof(char)); } Debug.Assert(JET_wrn.BufferTruncated == wrn, "Unexpected warning code"); // Create a fake string of the appropriate size and then fill it in. var s = new string('\0', actualDataSize / sizeof(char)); fixed (char* p = s) { int newDataSize; wrn = JetRetrieveColumn(sesid, tableid, columnid, new IntPtr(p), actualDataSize, out newDataSize, grbit, null); if (JET_wrn.BufferTruncated == wrn) { string error = string.Format( CultureInfo.CurrentCulture, "Column size changed from {0} to {1}. The record was probably updated by another thread.", actualDataSize, newDataSize); Trace.TraceError(error); throw new InvalidOperationException(error); } } return s; } } }