context: /*-
   * See the file LICENSE for redistribution information.
   *
   * Copyright (c) 2009, 2015 Oracle and/or its affiliates.  All rights reserved.
   *
   */
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Text;
  using BerkeleyDB.Internal;
  
  namespace BerkeleyDB {
      /// <summary>
      /// A class representing database cursors, which allows for traversal of 
      /// database records.
      /// </summary>
      public class Cursor
          : BaseCursor,
          IDisposable, IEnumerable<KeyValuePair<DatabaseEntry, DatabaseEntry>> {
          private KeyValuePair<DatabaseEntry, DatabaseEntry> cur;
          private KeyValuePair<DatabaseEntry, MultipleDatabaseEntry> curMult;
          private MultipleKeyDatabaseEntry curMultKey;
          private DatabaseType dbtype;
  
          /// <summary>
          /// Protected member, storing the pagesize of the underlying database.
          /// Used during bulk get (i.e. Move*Multiple).
          /// </summary>
          protected uint pgsz;
  
          /// <summary>
          /// Specifies where to place duplicate data elements of the key to which
          /// the cursor refers.
          /// </summary>
          public enum InsertLocation {
              /// <summary>
              /// The new element appears immediately after the current cursor
              /// position.
              /// </summary>
              AFTER,
              /// <summary>
              /// The new element appears immediately before the current cursor
              /// position.
              /// </summary>
              BEFORE,
              /// <summary>
              /// The new element appears as the first of the data items for the 
              /// given key
              /// </summary>
              FIRST,
              /// <summary>
              /// The new element appears as the last of the data items for the 
              /// given key
              /// </summary>
              LAST
          };
  
          /// <summary>
          /// The key/data pair at which the cursor currently points.
          /// </summary>
          /// <remarks>
          /// Only one of <see cref="Current"/>, <see cref="CurrentMultiple"/> and
          /// <see cref="CurrentMultipleKey"/> is ever non-empty.
          /// </remarks>
          public KeyValuePair<DatabaseEntry, DatabaseEntry> Current {
              private set {
                  cur = value;
                  curMult =
                      new KeyValuePair<DatabaseEntry, MultipleDatabaseEntry>();
                  curMultKey = null;
              }
              get { return cur; }
          }
          /// <summary>
          /// The key and multiple data items at which the cursor currently
          /// points.
          /// </summary>
          /// <remarks>
          /// Only one of <see cref="Current"/>, <see cref="CurrentMultiple"/> and
          /// <see cref="CurrentMultipleKey"/> is ever non-empty.
          /// </remarks>
          public
              KeyValuePair<DatabaseEntry, MultipleDatabaseEntry> CurrentMultiple {
              private set {
                  cur = new KeyValuePair<DatabaseEntry, DatabaseEntry>();
                  curMult = value;
                  curMultKey = null;
              }
              get { return curMult; }
          }
          /// <summary>
          /// The multiple key and data items at which the cursor currently
          /// points.
          /// </summary>
          /// <remarks>
          /// Only one of <see cref="Current"/>, <see cref="CurrentMultiple"/> and
          /// <see cref="CurrentMultipleKey"/> is ever non-empty.
          /// </remarks>
          public MultipleKeyDatabaseEntry CurrentMultipleKey {
              private set {
                  cur = new KeyValuePair<DatabaseEntry, DatabaseEntry>();
                  curMult =
                      new KeyValuePair<DatabaseEntry, MultipleDatabaseEntry>();
                  curMultKey = value;
              }
              get { return curMultKey; }
          }
          /// <summary>
          /// The cache priority for pages referenced by the cursor.
          /// </summary>
          /// <remarks>
          /// The priority of a page biases the replacement algorithm to be more
          /// or less likely to discard a page when space is needed in the buffer
          /// pool. The bias is temporary, and pages are eventually discarded
          /// if they are not referenced again. The setting is only advisory, and
          /// does not guarantee pages are treated in a specific way.
          /// </remarks>
          public CachePriority Priority {
              get { 
          uint pri = 0;
          dbc.get_priority(ref pri);
          return CachePriority.fromUInt(pri);
          }
              set { dbc.set_priority(value.priority); }
          }
  
          internal Cursor(DBC dbc, DatabaseType DbType, uint pagesize)
              : base(dbc) {
          Priority = CachePriority.DEFAULT;
              pgsz = pagesize;
              dbtype = DbType;
          }
          internal Cursor(
              DBC dbc, DatabaseType DbType, uint pagesize, CachePriority pri)
              : base(dbc) {
              Priority = pri;
              pgsz = pagesize;
              dbtype = DbType;
          }
  
          #region Internal API
          /* These protected methods do the heavy lifting.  The API methods for
           * Cursor and its subclasses call into them, which allows the API 
           * methods to expose subsets of the arg lists, because some args are
           * optional. */
  
          /* Only BTree and Hash can call this version of Add(). */
          /// <summary>
          /// Protected method for BTree and Hash to insert with KEYFIRST and
          /// KEYLAST.
          /// </summary>
          /// <param name="pair">The key/data pair to add</param>
          /// <param name="loc">Where to add, if adding duplicate data</param>
          protected void Add(KeyValuePair<DatabaseEntry, DatabaseEntry> pair, InsertLocation loc) {
              if (loc == InsertLocation.AFTER)
                  throw new ArgumentException("AFTER may only be specified on Insert().");
              if (loc == InsertLocation.BEFORE)
                  throw new ArgumentException("BEFORE may only be specified on Insert().");
              Put(pair.Key, pair.Value, (loc == InsertLocation.FIRST) ? DbConstants.DB_KEYFIRST : DbConstants.DB_KEYLAST);
          }
          /* Only BTree and Hash can call AddUnique(). */
          /// <summary>
          /// Protected method for BTree and Hash to insert with NODUPDATA.
          /// </summary>
          /// <param name="pair">The key/data pair to add</param>
          protected void AddUnique(KeyValuePair<DatabaseEntry, DatabaseEntry> pair) {
              Put(pair.Key, pair.Value, DbConstants.DB_NODUPDATA);
          }
          /* Only BTree, Hash and Recno can call Insert(). */
          /// <summary>
          /// Protected method for BTree, Hash and Recno to insert with AFTER and
          /// BEFORE.
          /// </summary>
          /// <param name="data">The duplicate data item to add</param>
          /// <param name="loc">
          /// Whether to add the duplicate data before or after the current cursor
          /// position
          /// </param>
          protected void Insert(DatabaseEntry data, InsertLocation loc) {
              if (loc == InsertLocation.FIRST)
                  throw new ArgumentException("FIRST may only be specified on Add().");
              if (loc == InsertLocation.LAST)
                  throw new ArgumentException("LAST may only be specified on Add().");
              DatabaseEntry key = new DatabaseEntry();
              Put(key, data, (loc == InsertLocation.AFTER) ? DbConstants.DB_AFTER : DbConstants.DB_BEFORE);
          }
  
          /* 
           * All flavors of get and put boil down to a call to one of these two
           * methods, just with different flags. 
           */
          /// <summary>
          /// Protected method wrapping DBC->get.
          /// </summary>
          /// <param name="key">The key to retrieve</param>
          /// <param name="data">The data to retrieve</param>
          /// <param name="flags">Modify the behavior of get</param>
          /// <param name="info">The locking configuration to use</param>
          /// <returns>
          /// True if the cursor was positioned successfully, false otherwise.
          /// </returns>
          protected bool Get(DatabaseEntry key, DatabaseEntry data, uint flags, LockingInfo info) {
              flags |= (info == null) ? 0 : info.flags;
  
              try {
                  dbc.get(key, data, flags);
                  Current = new KeyValuePair<DatabaseEntry, DatabaseEntry>(key, data);
                  return true;
              } catch (NotFoundException) {
                  Current = new KeyValuePair<DatabaseEntry, DatabaseEntry>();
                  return false;
              }
          }
          /// <summary>
          /// Protected method wrapping DBC->get for bulk get.
          /// </summary>
          /// <param name="key">The key to retrieve</param>
          /// <param name="data">The data to retrieve</param>
          /// <param name="BufferSize">Size of the bulk buffer</param>
          /// <param name="flags">Modify the behavior of get</param>
          /// <param name="info">The locking configuration to use</param>
          /// <param name="isMultKey">
          /// If true, use DB_MULTIPLE_KEY instead of DB_MULTIPLE
          /// </param>
          /// <returns>
          /// True if the cursor was positioned successfully, false otherwise.
          /// </returns>
          protected bool GetMultiple(DatabaseEntry key, DatabaseEntry data,
              int BufferSize, uint flags, LockingInfo info, bool isMultKey) {
              int datasz = 0;
              bool getboth = false;
  
              if (flags == DbConstants.DB_GET_BOTH ||
                  flags == DbConstants.DB_GET_BOTH_RANGE) {
                  datasz = (int)data.Data.Length;
                  getboth = true;
              }
              flags |= (info == null) ? 0 : info.flags;
              flags |= (isMultKey) ?
                  DbConstants.DB_MULTIPLE_KEY : DbConstants.DB_MULTIPLE;
  
              for (; ; ) {
                  if (getboth) {
                      byte[] udata = new byte[BufferSize];
                      Array.Copy(data.Data, udata, datasz);
                      data.UserData = udata;
                      data.size = (uint)datasz;
                  } else {
  
gt:                     data.UserData = new byte[BufferSize];