/// <summary>
        /// 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.
        /// </summary>
        /// <param name="sesid">The session to use.</param>
        /// <param name="tableid">The cursor to retrieve the bookmark from.</param>
        /// <returns>The bookmark of the record.</returns>
        public static byte[] GetBookmark(JET_SESID sesid, JET_TABLEID tableid)
        {
            byte[] buffer = Caches.BookmarkCache.Allocate();
            int    bookmarkSize;

            Api.JetGetBookmark(sesid, tableid, buffer, buffer.Length, out bookmarkSize);
            byte[] bookmark = MemoryCache.Duplicate(buffer, bookmarkSize);
            Caches.BookmarkCache.Free(ref buffer);
            return(bookmark);
        }
        /// <summary>
        /// Retrieves the key for the index entry at the current position of a cursor.
        /// </summary>
        /// <param name="sesid">The session to use.</param>
        /// <param name="tableid">The cursor to retrieve the key from.</param>
        /// <param name="grbit">Retrieve key options.</param>
        /// <returns>The retrieved key.</returns>
        public static byte[] RetrieveKey(JET_SESID sesid, JET_TABLEID tableid, RetrieveKeyGrbit grbit)
        {
            byte[] buffer = Caches.BookmarkCache.Allocate();
            int    keySize;

            Api.JetRetrieveKey(sesid, tableid, buffer, buffer.Length, out keySize, grbit);
            byte[] key = MemoryCache.Duplicate(buffer, keySize);
            Caches.BookmarkCache.Free(ref buffer);
            return(key);
        }
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="sesid">The session to use.</param>
        /// <param name="tableid">The cursor to retrieve the column from.</param>
        /// <param name="columnid">The columnid to retrieve.</param>
        /// <param name="grbit">Retrieve column options.</param>
        /// <param name="retinfo">
        /// 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).
        /// </param>
        /// <returns>The data retrieved from the column. Null if the column is null.</returns>
        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 = Caches.ColumnCache.Allocate();
            byte[]  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.Success != 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);
                }
            }

            Caches.ColumnCache.Free(ref cache);
            return(data);
        }