// Copyright 2016 FullContact, Inc
// Copyright 2017, 2018 Jason Lingle
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use libc::{self, c_void};
use std::ptr;

use supercow::{NonSyncSupercow, Phantomcow, Supercow};

use crate::dbi::Database;
use crate::env::Environment;
use crate::error::Result;
use crate::mdb_vals::{as_val, from_reserved, from_val, EMPTY_VAL};
use crate::traits::{AsLmdbBytes, FromLmdbBytes, FromReservedLmdbBytes, LmdbRaw};
use crate::tx::assert_sensible_cursor;
use crate::tx::{self, del, put, ConstAccessor, ConstTransaction, WriteAccessor};

#[derive(Debug)]
struct CursorHandle(*mut liblmdb::MDB_cursor);
impl Drop for CursorHandle {
    fn drop(&mut self) {
        unsafe {
            liblmdb::mdb_cursor_close(self.0);
        }
    }
}

/// A cursor into an LMDB database.
///
/// Depending on the context, a cursor's lifetime may be scoped to a
/// transaction or to the whole environment. Its lifetime is also naturally
/// bound to the lifetime of the database it cursors into.
///
/// ## Creation and Ownership
///
/// Cursors are normally created by calling `.cursor()` on the transaction that
/// is to own them. Since a `Cursor` is associated with a `Database`, this also
/// involves passing a reference or other handle to that `Database` to the
/// call.
///
/// For the `Database`, all three ownership models are permitted, though owned
/// likely is not useful. The transaction can be used with borrowed or shared
/// ownership, but note that shared ownership requires having the
/// `CreateCursor` trait imported.
///
/// ### Example — Traditional "keep everything on the stack" style
///
/// This is the simplest way to use `Cursor`, but not always the easiest. Most
/// examples in the documentation use this method. Here, the `Cursor` holds
/// references to the transaction and the database. This makes it somewhat
/// inflexible; for example, you cannot make a structure which owns both the
/// transaction and some set of cursors.
///
/// ```
/// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
/// # fn main() {
/// # let env = create_env();
/// # let db = defdb(&env);
/// # let txn = lmdb::WriteTransaction::new(&env).unwrap();
/// # run(&txn, &mut txn.access(), &db);
/// # }
/// // N.B. Unneeded type and lifetime annotations here for clarity.
/// fn run<'db, 'txn>(txn: &'txn lmdb::WriteTransaction,
///                   access: &'txn mut lmdb::WriteAccessor,
///                   db: &'db lmdb::Database) {
///   let mut cursor: lmdb::Cursor<'txn, 'db> = txn.cursor(db).unwrap();
///   // Do stuff with cursor.
/// # ::std::mem::drop(cursor);
/// }
/// ```
///
/// ### Example — Use reference counting for more flexibility
///
/// ```
/// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
/// use std::sync::Arc;
///
/// // We need to import the `CreateCursor` trait to be able to use shared
/// // mode cursors. `TxExt` also gets us `to_const()` used below.
/// use lmdb::traits::{CreateCursor, TxExt};
///
/// // This approach lets us make this `Context` struct wherein we can pass
/// // around everything our functions might need in one piece as well as
/// // having more flexible transaction lifetimes.
/// struct Context {
///   txn: Arc<lmdb::ConstTransaction<'static>>,
///   cursor: Arc<lmdb::Cursor<'static,'static>>,
/// }
///
/// // N.B. Unneeded type and lifetime annotations here for clarity.
///
/// # fn main() {
/// // Everything at higher levels also needs to have `'static` lifetimes.
/// // Here, we just stuff everything into `Arc`s for simplicity.
/// let env: Arc<lmdb::Environment> = Arc::new(create_env());
/// let db: Arc<lmdb::Database<'static>> = Arc::new(lmdb::Database::open(
///   env.clone(), None, &lmdb::DatabaseOptions::defaults()).unwrap());
///
/// // Now we can make our transaction and cursor and pass them around as one.
/// // Note that you can also use plain `Rc` at this level, too.
/// let txn: Arc<lmdb::WriteTransaction<'static>> =
///   Arc::new(lmdb::WriteTransaction::new(env.clone()).unwrap());
/// let cursor: Arc<lmdb::Cursor<'static, 'static>> = Arc::new(
///   txn.cursor(db.clone()).unwrap());
/// let context = Context { txn: txn.clone().to_const(), cursor: cursor };
/// do_stuff(&context);
///
/// // Drop the context so we can get the `WriteTransaction` back
/// // and commit it.
/// drop(context);
/// let txn = Arc::try_unwrap(txn).unwrap();
/// txn.commit().unwrap();
/// # }
///
/// #[allow(unused_vars)]
/// fn do_stuff(context: &Context) {
///   // do stuff
/// }
/// ```
///
/// ## Lifetime
///
/// A cursor must be strictly outlived by both the transaction that created it
/// and by the database into which it indexes. The two lifetimes are needed to
/// permit a cursor to be disassociated from its transaction and rebound to a
/// later transaction.
///
/// `Cursor` is covariant on both of its lifetimes. If you have an owned
/// `Cursor` as a structure member and don't plan on using the `dissoc_cursor`
/// API, you can use one lifetime parameter to fill both without issue.
///
/// ```rust,no_run
/// # #![allow(dead_code)]
/// # use saferlmdb as lmdb;
/// # fn main() { }
/// #
/// struct CursorOwner<'a> {
///   cursor: lmdb::Cursor<'a, 'a>,
/// }
/// fn covariance<'a, 'txn: 'a, 'db: 'a>(c: lmdb::Cursor<'txn,'db>)
///                                     -> CursorOwner<'a> {
///   let c: lmdb::Cursor<'a, 'a> = c;
///   CursorOwner { cursor: c }
/// }
/// ```
///
/// Note that an `&mut Cursor<'txn,'db>` is naturally _invariant_ on both
/// lifetimes. This means that structures containing `&mut Cursor` or functions
/// taking them as references should generally include both.
///
/// ```rust,no_run
/// # #![allow(dead_code)]
/// # use saferlmdb as lmdb;
/// # fn main() { }
/// #
/// // Write this
/// fn do_stuff_with_cursor<'txn, 'db>(c: &mut lmdb::Cursor<'txn,'db>) {
///   // Stuff
/// }
/// // Not this
/// fn do_stuff_with_cursor_2<'a>(c: &mut lmdb::Cursor<'a,'a>) {
///   // Stuff
/// }
/// ```
///
/// Attempting to unify the lifetimes on a `&mut Cursor` will often work, but
/// can also cause the compiler to infer lifetimes in unfavourable ways.
#[allow(clippy::struct_field_names)]
#[derive(Debug)]
pub struct Cursor<'txn, 'db> {
    cursor: CursorHandle,
    txn: NonSyncSupercow<'txn, ConstTransaction<'txn>>,
    _db: Phantomcow<'db, Database<'db>>,
}

// Used by transactions to construct/query cursors
pub unsafe fn create_cursor<'txn, 'db>(
    raw: *mut liblmdb::MDB_cursor,
    txn: NonSyncSupercow<'txn, ConstTransaction<'txn>>,
    db: Phantomcow<'db, Database<'db>>,
) -> Cursor<'txn, 'db> {
    Cursor {
        cursor: CursorHandle(raw),
        txn,
        _db: db,
    }
}
pub fn txn_ref<'a, 'txn: 'a, 'db>(cursor: &'a Cursor<'txn, 'db>) -> &'a ConstTransaction<'txn> {
    &cursor.txn
}

/// A read-only cursor which has been dissociated from its original
/// transaction, so that it can be rebound later.
///
/// A `StaleCursor` remains bound to the original database.
#[derive(Debug)]
pub struct StaleCursor<'db> {
    cursor: CursorHandle,
    env: NonSyncSupercow<'db, Environment>,
    _db: Phantomcow<'db, Database<'db>>,
}

// Internal
#[allow(clippy::used_underscore_binding)]
pub fn to_stale<'db>(
    cursor: Cursor<'_, 'db>,
    env: NonSyncSupercow<'db, Environment>,
) -> StaleCursor<'db> {
    StaleCursor {
        cursor: cursor.cursor,
        env,
        _db: cursor._db,
    }
}
pub fn env_ref<'a>(cursor: &'a StaleCursor<'_>) -> &'a Environment {
    &cursor.env
}
pub fn stale_cursor_ptr(cursor: &StaleCursor<'_>) -> *mut liblmdb::MDB_cursor {
    cursor.cursor.0
}

macro_rules! cursor_get_0_kv {
    ($(#[$doc:meta])* fn $method:ident, $op:path) => {
        $(#[$doc])*
        #[inline]
        pub fn $method<'access, K : FromLmdbBytes + ?Sized,
                       V : FromLmdbBytes + ?Sized>
            (&mut self, access: &'access ConstAccessor)
             -> Result<(&'access K, &'access V)>
        {
            self.get_0_kv(access, $op)
        }
    }
}

macro_rules! cursor_get_0_v {
    ($(#[$doc:meta])* fn $method:ident, $op:path) => {
        $(#[$doc])*
        #[inline]
        pub fn $method<'access, V : FromLmdbBytes + ?Sized>
            (&mut self, access: &'access ConstAccessor)
             -> Result<&'access V>
        {
            self.get_0_v(access, $op)
        }
    }
}

impl<'txn, 'db> Cursor<'txn, 'db> {
    /// Directly construct a cursor with the given transaction and database
    /// handles.
    ///
    /// This is a low-level function intended only for use by implementations
    /// of the `CreateCursor` trait. (There is nothing less safe about it being
    /// low-level; it's simply inconvenient.)
    pub fn construct(
        txn: NonSyncSupercow<'txn, ConstTransaction<'txn>>,
        db: Supercow<'db, Database<'db>>,
    ) -> Result<Self> {
        tx::assert_same_env(&txn, &db)?;

        let mut raw: *mut liblmdb::MDB_cursor = ptr::null_mut();
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_open(
                tx::txptr(&txn),
                db.as_raw(),
                &raw mut raw
            ));
        }

        Ok(unsafe { create_cursor(raw, txn, Supercow::phantom(db)) })
    }

    /// Directly renew a `StaleCursor` into a functional `Cursor` using the
    /// given database handle.
    ///
    /// This is a low-level function intended only for use by implementations
    /// of the `AssocCursor` trait. (There is nothing less safe about it being
    /// low-level; it's simply inconvenient.)
    ///
    /// It is an error if `txn` is not actually a `ReadTransaction`.
    #[allow(clippy::used_underscore_binding)]
    pub fn from_stale(
        stale: StaleCursor<'db>,
        txn: NonSyncSupercow<'txn, ConstTransaction<'txn>>,
    ) -> Result<Self> {
        tx::assert_in_env(&txn, env_ref(&stale))?;

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_renew(
                tx::txptr(&txn),
                stale_cursor_ptr(&stale)
            ));
        }

        Ok(Cursor {
            cursor: stale.cursor,
            txn,
            _db: stale._db,
        })
    }

    #[inline]
    fn get_0_kv<'access, K: FromLmdbBytes + ?Sized, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<(&'access K, &'access V)> {
        assert_sensible_cursor(access, self)?;

        let mut out_key = EMPTY_VAL;
        let mut out_val = EMPTY_VAL;
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut out_key,
                &raw mut out_val,
                op
            ));
        }

        Ok((from_val(access, &out_key)?, from_val(access, &out_val)?))
    }

    #[inline]
    fn get_0_v<'access, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<&'access V> {
        assert_sensible_cursor(access, self)?;

        let mut null_key = EMPTY_VAL;
        let mut out_val = EMPTY_VAL;
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut null_key,
                &raw mut out_val,
                op
            ));
        }

        from_val(access, &out_val)
    }

    #[inline]
    fn get_kv_0<K: AsLmdbBytes + ?Sized, V: AsLmdbBytes + ?Sized>(
        &mut self,
        key: &K,
        val: &V,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<()> {
        let mut mv_key = as_val(key);
        let mut mv_val = as_val(val);
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut mv_key,
                &raw mut mv_val,
                op
            ));
        }

        Ok(())
    }

    #[inline]
    fn get_kv_v<'access, K: AsLmdbBytes + ?Sized, V: AsLmdbBytes + FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
        val: &V,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<&'access V> {
        assert_sensible_cursor(access, self)?;

        let mut mv_key = as_val(key);
        let mut inout_val = as_val(val);

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut mv_key,
                &raw mut inout_val,
                op
            ));
        }

        from_val(access, &inout_val)
    }

    #[inline]
    fn get_k_v<'access, K: AsLmdbBytes + ?Sized, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<&'access V> {
        assert_sensible_cursor(access, self)?;

        let mut mv_key = as_val(key);
        let mut out_val = EMPTY_VAL;

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut mv_key,
                &raw mut out_val,
                op
            ));
        }

        from_val(access, &out_val)
    }

    #[inline]
    fn get_k_kv<'access, K: AsLmdbBytes + FromLmdbBytes + ?Sized, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
        op: liblmdb::MDB_cursor_op,
    ) -> Result<(&'access K, &'access V)> {
        assert_sensible_cursor(access, self)?;

        let mut inout_key = as_val(key);
        let mut out_val = EMPTY_VAL;

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut inout_key,
                &raw mut out_val,
                op
            ));
        }

        Ok((from_val(access, &inout_key)?, from_val(access, &out_val)?))
    }

    cursor_get_0_kv! {
        /// Positions the cursor at the first key/value pair in the database
        /// and returns that pair.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_FIRST` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Germany", "Berlin", &f.clone()).unwrap();
        ///   access.put(&db, "France", "Paris", &f.clone()).unwrap();
        ///   access.put(&db, "Latvia", "Rīga", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("France", "Paris"), cursor.first(&access).unwrap());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn first, liblmdb::MDB_cursor_op_MDB_FIRST
    }

    cursor_get_0_v! {
        /// Positions the cursor at the first key/value pair whose key is equal
        /// to the current key, returning the value of that pair.
        ///
        /// This only makes sense on `DUPSORT` databases.
        ///
        /// This correspnods to the `mdb_cursor_get` function with the
        /// `MDB_FIRST_DUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Durian", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Fruit", "Orange"), cursor.last(&access).unwrap());
        ///   assert_eq!("Apple", cursor.first_dup::<str>(&access).unwrap());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn first_dup, liblmdb::MDB_cursor_op_MDB_FIRST_DUP
    }

    /// Positions the cursor at the given (key,value) pair.
    ///
    /// This only makes sense on `DUPSORT` databases.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the
    /// `MDB_GET_BOTH` operation.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Durian", &f.clone()).unwrap();
    ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
    ///
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   cursor.seek_kv("Fruit", "Durian").unwrap();
    ///   assert_eq!(("Fruit", "Orange"), cursor.next(&access).unwrap());
    ///   assert!(cursor.seek_kv("Fruit", "Lychee").is_err());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn seek_kv<K: AsLmdbBytes + ?Sized, V: AsLmdbBytes + ?Sized>(
        &mut self,
        key: &K,
        val: &V,
    ) -> Result<()> {
        self.get_kv_0(key, val, liblmdb::MDB_cursor_op_MDB_GET_BOTH)
    }

    /// Positions the cursor at the given key and the "nearest" value to `val`,
    /// that is, the first (according to sorting) item whose key equals `key`
    /// and whose value is greater than or equal to `val`.
    ///
    /// The actual value found is returned.
    ///
    /// This only makes sense on `DUPSORT` databases.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the
    /// `MDB_GET_BOTH_RANGE` operation.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Banana", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Durian", &f.clone()).unwrap();
    ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
    ///
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   assert_eq!("Durian", cursor.seek_k_nearest_v::<str,str>(
    ///     &access, "Fruit", "Durian").unwrap());
    ///   assert_eq!("Orange", cursor.seek_k_nearest_v::<str,str>(
    ///     &access, "Fruit", "Lychee").unwrap());
    ///   assert!(cursor.seek_k_nearest_v::<str,str>(
    ///     &access, "Fruit", "Watermelon").is_err());
    ///   assert_eq!("Banana", cursor.seek_k_nearest_v::<str,str>(
    ///     &access, "Fruit", "Apple").unwrap());
    ///   assert!(cursor.seek_k_nearest_v::<str,str>(
    ///     &access, "Plant", "Tree").is_err());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn seek_k_nearest_v<
        'access,
        K: AsLmdbBytes + ?Sized,
        V: AsLmdbBytes + FromLmdbBytes + ?Sized,
    >(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
        val: &V,
    ) -> Result<&'access V> {
        self.get_kv_v(access, key, val, liblmdb::MDB_cursor_op_MDB_GET_BOTH_RANGE)
    }

    cursor_get_0_kv! {
        /// Returns the current key/value pair under this cursor.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_CURRENT` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Germany", "Berlin", &f.clone()).unwrap();
        ///   access.put(&db, "France", "Paris", &f.clone()).unwrap();
        ///   access.put(&db, "Latvia", "Rīga", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   cursor.seek_k::<str,str>(&access, "Latvia").unwrap();
        ///   assert_eq!(("Latvia", "Rīga"), cursor.get_current(&access).unwrap());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn get_current, liblmdb::MDB_cursor_op_MDB_GET_CURRENT
    }

    /// Returns as many items as possible with the current key from the
    /// current cursor position.
    ///
    /// The cursor is advanced so that `next_multiple()` returns the next
    /// group of items, if any. Note that this does _not_ return the actual
    /// key (which LMDB itself does not return, contrary to documentation).
    ///
    /// The easiest way to use this is for `V` to be a slice of `LmdbRaw`
    /// types.
    ///
    /// This only makes sense on `DUPSORT` databases with `DUPFIXED` set.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the
    /// `MDB_GET_MULTIPLE` operation, except that it does not have a special
    /// case if exactly one value is bound to the key.
    ///
    /// See `saferlmdb::db::Flags::DUPFIXED` for examples of usage.
    #[inline]
    pub fn get_multiple<'access, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
    ) -> Result<&'access V> {
        assert_sensible_cursor(access, self)?;

        let mut null_key = EMPTY_VAL;
        let mut out_val = EMPTY_VAL;
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_get(
                self.cursor.0,
                &raw mut null_key,
                &raw mut out_val,
                liblmdb::MDB_cursor_op_MDB_GET_MULTIPLE
            ));
        }

        if out_val.mv_data.is_null() {
            // LMDB seemingly intentionally returns SUCCESS but a NULL data
            // pointer if there's exactly one element.
            // https://github.com/LMDB/lmdb/blob/0a2622317f189c7062d03d050be6766586a548b2/libraries/liblmdb/mdb.c#L7228
            // Presumably it has something to do with the fact that single
            // values are stored differently than dup values, though it's
            // unclear why it doesn't do what we do here. If we see this
            // condition, simply fall back to MDB_GET_CURRENT which doesn't
            // modify the cursor, so the caller's follow-up call to
            // next_multiple() will still be sound.
            unsafe {
                lmdb_call!(liblmdb::mdb_cursor_get(
                    self.cursor.0,
                    &raw mut null_key,
                    &raw mut out_val,
                    liblmdb::MDB_cursor_op_MDB_GET_CURRENT
                ));
            }
        }

        from_val(access, &out_val)
    }

    cursor_get_0_v! {
        /// Continues fetching items from a cursor positioned by a call to
        /// `get_multiple()`.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_NEXT_MULTIPLE` operation.
        ///
        /// See `saferlmdb::db::Flags::DUPFIXED` for examples of usage.
        fn next_multiple, liblmdb::MDB_cursor_op_MDB_NEXT_MULTIPLE
    }

    cursor_get_0_kv! {
        /// Positions the cursor at the last key/value pair in the database,
        /// and returns that pair.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_LAST` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Germany", "Berlin", &f.clone()).unwrap();
        ///   access.put(&db, "France", "Paris", &f.clone()).unwrap();
        ///   access.put(&db, "Latvia", "Rīga", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Latvia", "Rīga"), cursor.last(&access).unwrap());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn last, liblmdb::MDB_cursor_op_MDB_LAST
    }

    cursor_get_0_v! {
        /// Positions the cursor at the last key/value pair whose key is equal
        /// to the current key.
        ///
        /// This only makes sense on `DUPSORT` databases.
        ///
        /// This correspnods to the `mdb_cursor_get` function with the
        /// `MDB_LAST_DUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Durian", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!("Apple", cursor.seek_k::<str,str>(&access, "Fruit").unwrap());
        ///   assert_eq!("Orange", cursor.last_dup::<str>(&access).unwrap());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn last_dup, liblmdb::MDB_cursor_op_MDB_LAST_DUP
    }

    cursor_get_0_kv! {
        /// Advances the cursor to the key/value pair following this one.
        ///
        /// If the current key has multiple values, this will remain on the
        /// same key unless it is already on the last value.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_NEXT` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Animal", "Badger"), cursor.first(&access).unwrap());
        ///   assert_eq!(("Fruit", "Apple"), cursor.next(&access).unwrap());
        ///   assert_eq!(("Fruit", "Orange"), cursor.next(&access).unwrap());
        ///   assert_eq!(("Veggie", "Carrot"), cursor.next(&access).unwrap());
        ///   assert!(cursor.next::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn next, liblmdb::MDB_cursor_op_MDB_NEXT
    }

    cursor_get_0_kv! {
        /// Advances the cursor to the next value in the current key.
        ///
        /// This only makes sense on `DUPSORT` databases. This call fails if
        /// there are no more values in the current key.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_NEXT_DUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!("Apple", cursor.seek_k::<str,str>(&access, "Fruit").unwrap());
        ///   assert_eq!(("Fruit", "Orange"), cursor.next_dup(&access).unwrap());
        ///   assert!(cursor.next_dup::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn next_dup, liblmdb::MDB_cursor_op_MDB_NEXT_DUP
    }

    cursor_get_0_kv! {
        /// Advances the cursor to the first item of the key following the
        /// current key.
        ///
        /// This is permitted in all databases, but only behaves distinctly
        /// from `next()` in `DUPSORT` databases.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_NEXT_NODUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Animal", "Badger"), cursor.first(&access).unwrap());
        ///   assert_eq!(("Fruit", "Apple"), cursor.next_nodup(&access).unwrap());
        ///   assert_eq!(("Veggie", "Carrot"), cursor.next_nodup(&access).unwrap());
        ///   assert!(cursor.next_nodup::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn next_nodup, liblmdb::MDB_cursor_op_MDB_NEXT_NODUP
    }

    cursor_get_0_kv! {
        /// Retreats the cursor to the previous key/value pair.
        ///
        /// If the current key has multiple values, this will remain on the
        /// same key unless it is already on the first value.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_PREV` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Veggie", "Carrot"), cursor.last(&access).unwrap());
        ///   assert_eq!(("Fruit", "Orange"), cursor.prev(&access).unwrap());
        ///   assert_eq!(("Fruit", "Apple"), cursor.prev(&access).unwrap());
        ///   assert_eq!(("Animal", "Badger"), cursor.prev(&access).unwrap());
        ///   assert!(cursor.prev::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn prev, liblmdb::MDB_cursor_op_MDB_PREV
    }

    cursor_get_0_kv! {
        /// Retreats the cursor to the previous value in the current key.
        ///
        /// This only makes sense on `DUPSORT` databases. This call fails if
        /// there are no prior values in the current key.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_PREV_DUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!("Apple", cursor.seek_k::<str,str>(&access, "Fruit").unwrap());
        ///   assert_eq!(("Fruit", "Orange"), cursor.next_dup(&access).unwrap());
        ///   assert_eq!(("Fruit", "Apple"), cursor.prev_dup(&access).unwrap());
        ///   assert!(cursor.prev_dup::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn prev_dup, liblmdb::MDB_cursor_op_MDB_PREV_DUP
    }

    cursor_get_0_kv! {
        /// Retreats the cursor to the final item of the previous key.
        ///
        /// This is permitted in all databases, but only behaves distinctly
        /// from `prev()` in `DUPSORT` databases.
        ///
        /// This corresponds to the `mdb_cursor_get` function with the
        /// `MDB_PREV_NODUP` operation.
        ///
        /// ## Example
        ///
        /// ```
        /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
        /// # fn main() {
        /// # let env = create_env();
        /// # let db = dupdb(&env);
        /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
        /// {
        ///   let mut access = txn.access();
        ///   let f = lmdb::put::Flags::empty();
        ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
        ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
        ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
        ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
        ///
        ///   let mut cursor = txn.cursor(&db).unwrap();
        ///   assert_eq!(("Veggie", "Carrot"), cursor.last(&access).unwrap());
        ///   assert_eq!(("Fruit", "Orange"), cursor.prev_nodup(&access).unwrap());
        ///   assert_eq!(("Animal", "Badger"), cursor.prev_nodup(&access).unwrap());
        ///   assert!(cursor.prev_nodup::<str,str>(&access).is_err());
        /// }
        /// txn.commit().unwrap();
        /// # }
        /// ```
        fn prev_nodup, liblmdb::MDB_cursor_op_MDB_PREV_NODUP
    }

    /// Positions the cursor at the first item of the given key.
    ///
    /// Returns the value of that item.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the `MDB_SET`
    /// operation.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
    ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
    ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
    ///
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   assert_eq!("Apple", cursor.seek_k::<str,str>(&access, "Fruit").unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn seek_k<'access, K: AsLmdbBytes + ?Sized, V: FromLmdbBytes + ?Sized>(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
    ) -> Result<&'access V> {
        self.get_k_v(access, key, liblmdb::MDB_cursor_op_MDB_SET)
    }

    /// Positions the cursor at the first item of the given key.
    ///
    /// Returns the key and value of that item.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the
    /// `MDB_SET_KEY` operation.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
    ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
    ///   access.put(&db, "Veggie", "Carrot", &f.clone()).unwrap();
    ///
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   assert_eq!(("Fruit", "Apple"), cursor.seek_k_both(&access, "Fruit").unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn seek_k_both<
        'access,
        K: AsLmdbBytes + FromLmdbBytes + ?Sized,
        V: FromLmdbBytes + ?Sized,
    >(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
    ) -> Result<(&'access K, &'access V)> {
        self.get_k_kv(access, key, liblmdb::MDB_cursor_op_MDB_SET_KEY)
    }

    /// Positions the cursor at the first item whose key is greater than or
    /// equal to `key`.
    ///
    /// Return the key and value of that item.
    ///
    /// This corresponds to the `mdb_cursor_get` function with the
    /// `MDB_SET_RANGE` operation.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   access.put(&db, "Fruit", "Apple", &f.clone()).unwrap();
    ///   access.put(&db, "Fruit", "Orange", &f.clone()).unwrap();
    ///   access.put(&db, "Animal", "Badger", &f.clone()).unwrap();
    ///
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   assert_eq!(("Fruit", "Apple"), cursor.seek_range_k(&access, "Fog").unwrap());
    ///   assert!(cursor.seek_range_k::<str,str>(&access, "Veggie").is_err());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn seek_range_k<
        'access,
        K: AsLmdbBytes + FromLmdbBytes + ?Sized,
        V: FromLmdbBytes + ?Sized,
    >(
        &mut self,
        access: &'access ConstAccessor,
        key: &K,
    ) -> Result<(&'access K, &'access V)> {
        self.get_k_kv(access, key, liblmdb::MDB_cursor_op_MDB_SET_RANGE)
    }

    /// Writes a single value through this cursor.
    ///
    /// By default, any item with the same key (if not `DUPSORT`) or any
    /// exactly matching item (if `DUPSORT`) is replaced silently. `flags` can
    /// be used to override this.
    ///
    /// This does not inherently overwrite the current item. See `overwrite()`
    /// for that.
    ///
    /// The cursor is positioned at the new item, or on failure usually near
    /// it.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   cursor.put(&mut access, "Germany", "Berlin", &f.clone()).unwrap();
    ///   assert_eq!(("Germany", "Berlin"), cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn put<K: AsLmdbBytes + ?Sized, V: AsLmdbBytes + ?Sized>(
        &mut self,
        access: &mut WriteAccessor,
        key: &K,
        val: &V,
        flags: &put::Flags,
    ) -> Result<()> {
        assert_sensible_cursor(&*access, self)?;

        let mut mv_key = as_val(key);
        let mut mv_val = as_val(val);

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_put(
                self.cursor.0,
                &raw mut mv_key,
                &raw mut mv_val,
                flags.bits()
            ));
        }

        Ok(())
    }

    /// Overwrites the current item referenced by the cursor.
    ///
    /// `key` must match the key of the current item. If the database is
    /// `DUPSORT`, `val` must still sort into the same position relative to the
    /// other items with the same key.
    ///
    /// This is intended to be used when the new data is the same size as the
    /// old. Otherwise it will simply perform a delete of the old record
    /// followed by an insert.
    ///
    /// The cursor is positioned at the new item, or on failure usually near
    /// it.
    ///
    /// ## Example
    ///
    /// ```
    ///
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = defdb(&env);
    /// use lmdb::unaligned as u;
    ///
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   cursor.put(&mut access, "Fourty-two", &42u32, &f.clone()).unwrap();
    ///   cursor.overwrite(&mut access, "Fourty-two", &54u32, &f.clone()).unwrap();
    ///   assert_eq!(("Fourty-two", u(&54u32)),
    ///              cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn overwrite<K: AsLmdbBytes + ?Sized, V: AsLmdbBytes + ?Sized>(
        &mut self,
        access: &mut WriteAccessor,
        key: &K,
        val: &V,
        flags: &put::Flags,
    ) -> Result<()> {
        assert_sensible_cursor(&*access, self)?;

        let mut mv_key = as_val(key);
        let mut mv_val = as_val(val);

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_put(
                self.cursor.0,
                &raw mut mv_key,
                &raw mut mv_val,
                flags.bits() | liblmdb::MDB_CURRENT
            ));
        }

        Ok(())
    }

    /// Reserves space for an entry with the given key and returns a pointer to
    /// that entry.
    ///
    /// The size of the entry is simply the size of `V`.
    ///
    /// This cannot be used on a `DUPSORT` database.
    ///
    /// The cursor is positioned at the new item, or on failure usually near
    /// it.
    ///
    /// ## Example
    ///
    /// ```
    ///
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// #[repr(C, packed)]
    /// #[derive(Clone,Copy,Debug,PartialEq,Eq)]
    /// struct MyStruct {
    ///   x: i32,
    ///   y: i32,
    /// }
    /// unsafe impl lmdb::traits::LmdbRaw for MyStruct { }
    ///
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = defdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   {
    ///     let v: &mut MyStruct = cursor.reserve(&mut access, "foo", &f.clone()).unwrap();
    ///     // Write directly into the database
    ///     v.x = 42;
    ///     v.y = 56;
    ///   }
    ///
    ///   assert_eq!(("foo", &MyStruct { x: 42, y: 56 }),
    ///              cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn reserve<'access, K: AsLmdbBytes + ?Sized, V: FromReservedLmdbBytes + Sized>(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        flags: &put::Flags,
    ) -> Result<&'access mut V> {
        unsafe { self.reserve_unsized(access, key, size_of::<V>(), flags) }
    }

    /// Reserves space for an entry with the given key and returns a pointer to
    /// an array of values backing that entry.
    ///
    /// The size of the entry is simply the size of `V` times the desired
    /// number of elements.
    ///
    /// This cannot be used on a `DUPSORT` database. (Do not confuse with
    /// `put_multiple`, which does support `DUPSORT` but is not zero-copy.)
    ///
    /// The cursor is positioned at the new item, or on failure usually near
    /// it.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// #[repr(C, packed)]
    /// #[derive(Clone,Copy,Debug,PartialEq,Eq)]
    /// struct MyStruct {
    ///   x: i32,
    ///   y: i32,
    /// }
    /// unsafe impl lmdb::traits::LmdbRaw for MyStruct { }
    ///
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = defdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   {
    ///     let v: &mut [u8] = cursor.reserve_array(&mut access, "foo", 4, &f.clone()).unwrap();
    ///     // Write directly into the database
    ///     v[0] = b'b'; v[1] = b'y'; v[2] = b't'; v[3] = b'e';
    ///   }
    ///
    ///   assert_eq!(("foo", "byte"), cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn reserve_array<'access, K: AsLmdbBytes + ?Sized, V: LmdbRaw>(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        count: usize,
        flags: &put::Flags,
    ) -> Result<&'access mut [V]> {
        unsafe { self.reserve_unsized(access, key, count * size_of::<V>(), flags) }
    }

    /// Reserves space for an entry with the given key and returns a pointer to
    /// that entry.
    ///
    /// This cannot be used on a `DUPSORT` database.
    ///
    /// The cursor is positioned at the new item, or on failure usually near
    /// it.
    ///
    /// ## Safety
    ///
    /// The caller must ensure that `size` is a valid size for `V`.
    pub unsafe fn reserve_unsized<
        'access,
        K: AsLmdbBytes + ?Sized,
        V: FromReservedLmdbBytes + ?Sized,
    >(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        size: usize,
        flags: &put::Flags,
    ) -> Result<&'access mut V> {
        assert_sensible_cursor(&*access, self)?;

        let mut mv_key = as_val(key);
        let mut out_val = EMPTY_VAL;
        out_val.mv_size = size;

        lmdb_call!(liblmdb::mdb_cursor_put(
            self.cursor.0,
            &raw mut mv_key,
            &raw mut out_val,
            flags.bits() | liblmdb::MDB_RESERVE
        ));

        Ok(from_reserved(access, &mut out_val))
    }

    /// Returns a writable reference to the value belonging to the given key in
    /// the database.
    ///
    /// This has all the caveats of both `overwrite()` and `reserve()`.
    ///
    /// ## Updating by mutation
    ///
    /// It is possible to use this call to perform a read-modify-write
    /// operation on the data in the database, provided you are certain that
    /// the value exists with the exact size of `V`, for example if you just
    /// read the value as a `V` using something that requires a particular size
    /// (such as `LmdbRaw`).
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = defdb(&env);
    /// use lmdb::Unaligned as U;
    /// use lmdb::unaligned as u;
    ///
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   cursor.put(&mut access, "count", &1u32, &f.clone()).unwrap();
    ///   {
    ///     let count: &mut U<u32> = cursor.overwrite_in_place(
    ///       &mut access, "count", &f.clone()).unwrap();
    ///     // Directly edit the value in the database
    ///     let count2 = count.get() + 1;
    ///     count.set(count2);
    ///   }
    ///   assert_eq!(("count", u(&2u32)), cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn overwrite_in_place<
        'access,
        K: AsLmdbBytes + ?Sized,
        V: FromReservedLmdbBytes + Sized,
    >(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        flags: &put::Flags,
    ) -> Result<&'access mut V> {
        unsafe { self.overwrite_in_place_unsized(access, key, size_of::<V>(), flags) }
    }

    /// Returns a writable reference to the array of values belonging to the
    /// given key in the database.
    ///
    /// This has all the caveats of both `overwrite()` and `reserve_array()`.
    ///
    /// ## Updating by mutation
    ///
    /// It is possible to use this call to perform a read-modify-write
    /// operation on the data in the database, provided you are certain that
    /// the value exists with the exact size of `V` times `count`.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = defdb(&env);
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   cursor.put(&mut access, "foo", "bar", &f.clone()).unwrap();
    ///   {
    ///     let data: &mut [u8] = cursor.overwrite_in_place_array(
    ///       &mut access, "foo", 3, &f.clone()).unwrap();
    ///     // Directly edit the value in the database
    ///     data[2] = b'z';
    ///   }
    ///   assert_eq!(("foo", "baz"), cursor.get_current(&access).unwrap());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    #[inline]
    pub fn overwrite_in_place_array<'access, K: AsLmdbBytes + ?Sized, V: LmdbRaw>(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        count: usize,
        flags: &put::Flags,
    ) -> Result<&'access mut [V]> {
        unsafe { self.overwrite_in_place_unsized(access, key, count * size_of::<V>(), flags) }
    }

    /// Returns a writable reference to the value belonging to the given key in
    /// the database.
    ///
    /// This has all the caveats of both `overwrite()` and `reserve_unsized()`.
    ///
    /// ## Safety
    ///
    /// The caller must ensure `size` is a valid size of `V`.
    pub unsafe fn overwrite_in_place_unsized<
        'access,
        K: AsLmdbBytes + ?Sized,
        V: FromReservedLmdbBytes + ?Sized,
    >(
        &mut self,
        access: &'access mut WriteAccessor,
        key: &K,
        size: usize,
        flags: &put::Flags,
    ) -> Result<&'access mut V> {
        assert_sensible_cursor(&*access, self)?;

        let mut mv_key = as_val(key);
        let mut out_val = EMPTY_VAL;
        out_val.mv_size = size;

        lmdb_call!(liblmdb::mdb_cursor_put(
            self.cursor.0,
            &raw mut mv_key,
            &raw mut out_val,
            flags.bits() | liblmdb::MDB_RESERVE | liblmdb::MDB_CURRENT
        ));

        Ok(from_reserved(access, &mut out_val))
    }

    /// Stores multiple data elements with the same key in a single request.
    ///
    /// This is only permitted for `DUPFIXED` databases.
    ///
    /// Note that `values` must be a slice of `LmdbRaw`, since this function
    /// needs to know the exact size of each individual item and must be able
    /// to directly reinterpret the slice as a byte array.
    ///
    /// On success, returns the number of items that were actually written.
    ///
    /// ## Warning
    ///
    /// `MDB_MULTIPLE` has historically been rather problematic. Using this
    /// function may result in erratic behaviour on many versions of LMDB.
    ///
    /// ## Example
    ///
    /// ```
    /// # include!(concat!(env!("CARGO_MANIFEST_DIR"),"/src/example_helpers.rs"));
    /// # fn main() {
    /// # let env = create_env();
    /// # let db = dupfixeddb(&env);
    /// use lmdb::Unaligned as U;
    /// use lmdb::unaligned as u;
    ///
    /// let txn = lmdb::WriteTransaction::new(&env).unwrap();
    /// {
    ///   let mut access = txn.access();
    ///   let f = lmdb::put::Flags::empty();
    ///   let mut cursor = txn.cursor(&db).unwrap();
    ///   // XXX Whether this is supposed to be 4 or 3 is unclear.
    ///   assert_eq!(4, cursor.put_multiple(
    ///     &mut access, "bar", &[U::new(0u32), U::new(1u32),
    ///                           U::new(2u32), U::new(1u32)], &f).unwrap());
    /// # // XXX I wanted a lot more assertions here, but I kept running into
    /// # // issues that I think but am not sure are bugs.
    ///
    ///   assert_eq!(("bar", u(&0u32)), cursor.first(&access).unwrap());
    ///   assert_eq!(("bar", u(&1u32)), cursor.next(&access).unwrap());
    ///   assert_eq!(("bar", u(&2u32)), cursor.next(&access).unwrap());
    ///   assert!(cursor.next::<str,U<u32>>(&access).is_err());
    /// }
    /// txn.commit().unwrap();
    /// # }
    /// ```
    pub fn put_multiple<K: AsLmdbBytes + ?Sized, V: LmdbRaw>(
        &mut self,
        access: &mut WriteAccessor,
        key: &K,
        values: &[V],
        flags: &put::Flags,
    ) -> Result<usize> {
        assert_sensible_cursor(&*access, self)?;

        // Some LMDB versions didn't (don't?) handle count=0 correctly
        if values.is_empty() {
            return Ok(0);
        }

        let mut mv_key = as_val(key);
        let mut mv_vals = [
            liblmdb::MDB_val {
                mv_size: size_of::<V>() as libc::size_t,
                mv_data: values.as_lmdb_bytes().as_ptr() as *mut c_void,
            },
            liblmdb::MDB_val {
                mv_size: values.len() as libc::size_t,
                mv_data: ptr::null_mut(),
            },
        ];

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_put(
                self.cursor.0,
                &raw mut mv_key,
                mv_vals.as_mut_ptr(),
                flags.bits() | liblmdb::MDB_MULTIPLE
            ));
        }

        Ok(mv_vals[1].mv_size)
    }

    /// Delete current key/value pair.
    ///
    /// By default, this deletes only the current pair. `flags` can be set to
    /// `NODUPDATA` for `DUPDATA` databases to delete everything with the
    /// current key.
    ///
    /// See `saferlmdb::del::Flags::NODUPDATA` for examples on how `flags` can be used
    /// to control behaviour.
    #[inline]
    pub fn del(&mut self, access: &mut WriteAccessor, flags: &del::Flags) -> Result<()> {
        assert_sensible_cursor(&*access, self)?;

        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_del(self.cursor.0, flags.bits()));
        }

        Ok(())
    }

    /// Return count of duplicates for current key.
    ///
    /// This call is only valid on `DUPSORT` databases.
    #[inline]
    pub fn count(&mut self) -> Result<usize> {
        let mut raw: libc::size_t = 0;
        unsafe {
            lmdb_call!(liblmdb::mdb_cursor_count(self.cursor.0, &raw mut raw));
        }
        Ok(raw as usize)
    }
}

#[cfg(test)]
mod test {
    use crate::dbi::{db, Database, DatabaseOptions};
    use crate::error::LmdbResultExt;
    use crate::test_helpers::*;
    use crate::tx::{put, WriteTransaction};
    use crate::unaligned::unaligned;
    use crate::unaligned::Unaligned as U;

    #[test]
    fn get_multiple_with_one_item() {
        let env = create_env();
        let db = Database::open(
            &env,
            None,
            &DatabaseOptions::new(
                db::Flags::DUPSORT
                    | db::Flags::INTEGERKEY
                    | db::Flags::DUPFIXED
                    | db::Flags::INTEGERDUP
                    | db::Flags::CREATE,
            ),
        )
        .unwrap();
        let txn = WriteTransaction::new(&env).unwrap();
        {
            let key: i32 = 42;
            let val: i32 = 56;

            let mut access = txn.access();
            access.put(&db, &key, &val, &put::Flags::empty()).unwrap();

            let mut cursor = txn.cursor(&db).unwrap();
            cursor
                .seek_k::<U<i32>, U<i32>>(&access, unaligned(&key))
                .unwrap();
            let vals = cursor.get_multiple::<[U<i32>]>(&access).unwrap();
            assert_eq!(1, vals.len());
            assert_eq!(val, vals[0].get());

            assert!(cursor
                .next_multiple::<[U<i32>]>(&access)
                .to_opt()
                .unwrap()
                .is_none());
        }
    }
}
