private (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation)
        {
            var mdbKey   = new MDBValue();
            var mdbValue = new MDBValue();

            return(mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation), mdbKey, mdbValue);
        }
        /// <summary>
        /// Delete items from a database.
        /// This function removes key/data pairs from the database.
        /// If the database does not support sorted duplicate data items (MDB_DUPSORT) the data parameter is ignored.
        /// If the database supports sorted duplicates and the data parameter is NULL, all of the duplicate data items for the key will be deleted.
        /// Otherwise, if the data parameter is non-NULL only the matching data item will be deleted.
        /// This function will return MDB_NOTFOUND if the specified key/data pair is not in the database.
        /// </summary>
        /// <param name="db">A database handle returned by mdb_dbi_open()</param>
        /// <param name="key">The key to delete from the database</param>
        public unsafe MDBResultCode Delete(LightningDatabase db, ReadOnlySpan <byte> key)
        {
            fixed(byte *ptr = key)
            {
                var mdbKey = new MDBValue(key.Length, ptr);

                return(mdb_del(_handle, db.Handle(), mdbKey));
            }
        }
        private unsafe (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation, ReadOnlySpan <byte> key)
        {
            fixed(byte *keyPtr = key)
            {
                var mdbKey   = new MDBValue(key.Length, keyPtr);
                var mdbValue = new MDBValue();

                return(mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation), mdbKey, mdbValue);
            }
        }
        /// <summary>
        /// Store by cursor.
        /// This function stores key/data pairs into the database. The cursor is positioned at the new item, or on failure usually near it.
        /// Note: Earlier documentation incorrectly said errors would leave the state of the cursor unchanged.
        /// If the function fails for any reason, the state of the cursor will be unchanged.
        /// If the function succeeds and an item is inserted into the database, the cursor is always positioned to refer to the newly inserted item.
        /// </summary>
        /// <param name="key">The key operated on.</param>
        /// <param name="value">The data operated on.</param>
        /// <param name="options">
        /// Options for this operation. This parameter must be set to 0 or one of the values described here.
        ///     CursorPutOptions.Current - overwrite the data of the key/data pair to which the cursor refers with the specified data item. The key parameter is ignored.
        ///     CursorPutOptions.NoDuplicateData - enter the new key/data pair only if it does not already appear in the database. This flag may only be specified if the database was opened with MDB_DUPSORT. The function will return MDB_KEYEXIST if the key/data pair already appears in the database.
        ///     CursorPutOptions.NoOverwrite - enter the new key/data pair only if the key does not already appear in the database. The function will return MDB_KEYEXIST if the key already appears in the database, even if the database supports duplicates (MDB_DUPSORT).
        ///     CursorPutOptions.ReserveSpace - reserve space for data of the given size, but don't copy the given data. Instead, return a pointer to the reserved space, which the caller can fill in later. This saves an extra memcpy if the data is being generated later.
        ///     CursorPutOptions.AppendData - append the given key/data pair to the end of the database. No key comparisons are performed. This option allows fast bulk loading when keys are already known to be in the correct order. Loading unsorted keys with this flag will cause data corruption.
        ///     CursorPutOptions.AppendDuplicateData - as above, but for sorted dup data.
        /// </param>
        /// <returns>Returns <see cref="MDBResultCode"/></returns>
        public unsafe MDBResultCode Put(ReadOnlySpan <byte> key, ReadOnlySpan <byte> value, CursorPutOptions options)
        {
            fixed(byte *keyPtr = key)
            fixed(byte *valPtr = value)
            {
                var mdbKey   = new MDBValue(key.Length, keyPtr);
                var mdbValue = new MDBValue(value.Length, valPtr);

                return(mdb_cursor_put(_handle, mdbKey, mdbValue, options));
            }
        }
        /// <summary>
        /// Get value from a database.
        /// </summary>
        /// <param name="db">The database to query.</param>
        /// <param name="key">A span containing the key to look up.</param>
        /// <returns>Requested value's byte array if exists, or null if not.</returns>
        public unsafe (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(LightningDatabase db, ReadOnlySpan <byte> key)
        {
            if (db == null)
            {
                throw new ArgumentNullException(nameof(db));

                fixed(byte *keyBuffer = key)
                {
                    var mdbKey = new MDBValue(key.Length, keyBuffer);

                    return(mdb_get(_handle, db.Handle(), ref mdbKey, out var mdbValue), mdbKey, mdbValue);
                }
        }
        /// <summary>
        /// Put data into a database.
        /// </summary>
        /// <param name="db">Database.</param>
        /// <param name="key">Key byte array.</param>
        /// <param name="value">Value byte array.</param>
        /// <param name="options">Operation options (optional).</param>
        public unsafe MDBResultCode Put(LightningDatabase db, ReadOnlySpan <byte> key, ReadOnlySpan <byte> value, PutOptions options = PutOptions.None)
        {
            if (db == null)
            {
                throw new ArgumentNullException(nameof(db));

                fixed(byte *keyPtr = key)
                fixed(byte *valuePtr = value)
                {
                    var mdbKey   = new MDBValue(key.Length, keyPtr);
                    var mdbValue = new MDBValue(value.Length, valuePtr);

                    return(mdb_put(_handle, db.Handle(), mdbKey, mdbValue, options));
                }
        }
        /// <summary>
        /// Delete items from a database.
        /// This function removes key/data pairs from the database.
        /// If the database does not support sorted duplicate data items (MDB_DUPSORT) the data parameter is ignored.
        /// If the database supports sorted duplicates and the data parameter is NULL, all of the duplicate data items for the key will be deleted.
        /// Otherwise, if the data parameter is non-NULL only the matching data item will be deleted.
        /// This function will return MDB_NOTFOUND if the specified key/data pair is not in the database.
        /// </summary>
        /// <param name="db">A database handle returned by mdb_dbi_open()</param>
        /// <param name="key">The key to delete from the database</param>
        /// <param name="value">The data to delete (optional)</param>
        public unsafe MDBResultCode Delete(LightningDatabase db, ReadOnlySpan <byte> key, ReadOnlySpan <byte> value)
        {
            if (db == null)
            {
                throw new ArgumentNullException(nameof(db));

                fixed(byte *keyPtr = key)
                fixed(byte *valuePtr = value)
                {
                    var mdbKey = new MDBValue(key.Length, keyPtr);

                    if (value == null)
                    {
                        return(mdb_del(_handle, db.Handle(), mdbKey));
                    }
                    var mdbValue = new MDBValue(value.Length, valuePtr);

                    return(mdb_del(_handle, db.Handle(), mdbKey, mdbValue));
                }
        }
        /// <summary>
        /// Store by cursor.
        /// This function stores key/data pairs into the database.
        /// If the function fails for any reason, the state of the cursor will be unchanged.
        /// If the function succeeds and an item is inserted into the database, the cursor is always positioned to refer to the newly inserted item.
        /// </summary>
        /// <param name="key">The key operated on.</param>
        /// <param name="values">The data items operated on.</param>
        /// <returns>Returns <see cref="MDBResultCode"/></returns>
        public unsafe MDBResultCode Put(byte[] key, byte[][] values)
        {
            const int StackAllocateLimit = 256;                //I just made up a number, this can be much more aggressive -arc

            int overallLength = values.Sum(arr => arr.Length); //probably allocates but boy is it handy...


            //the idea here is to gain some perf by stackallocating the buffer to
            //hold the contiguous keys
            if (overallLength < StackAllocateLimit)
            {
                Span <byte> contiguousValues = stackalloc byte[overallLength];

                return(InnerPutMultiple(contiguousValues));
            }
            else
            {
                fixed(byte *contiguousValuesPtr = new byte[overallLength])
                {
                    Span <byte> contiguousValues = new Span <byte>(contiguousValuesPtr, overallLength);

                    return(InnerPutMultiple(contiguousValues));
                }
            }

            //these local methods could be made static, but the compiler will emit these closures
            //as structs with very little overhead. Also static local functions isn't available
            //until C# 8 so I can't use it anyway...
            MDBResultCode InnerPutMultiple(Span <byte> contiguousValuesBuffer)
            {
                FlattenInfo(contiguousValuesBuffer);
                var contiguousValuesPtr = (byte *)Unsafe.AsPointer(ref contiguousValuesBuffer.GetPinnableReference());

                var mdbValue = new MDBValue(GetSize(), contiguousValuesPtr);
                var mdbCount = new MDBValue(values.Length, (byte *)null);

                Span <MDBValue> dataBuffer = stackalloc MDBValue[2] {
                    mdbValue, mdbCount
                };

                fixed(byte *keyPtr = key)
                {
                    var mdbKey = new MDBValue(key.Length, keyPtr);

                    return(mdb_cursor_put(_handle, ref mdbKey, ref dataBuffer, CursorPutOptions.MultipleData));
                }
            }

            void FlattenInfo(Span <byte> targetBuffer)
            {
                var cursor = targetBuffer;

                foreach (var buffer in values)
                {
                    buffer.CopyTo(cursor);
                    cursor = cursor.Slice(buffer.Length);
                }
            }

            int GetSize()
            {
                if (values.Length == 0 || values[0] == null || values[0].Length == 0)
                {
                    return(0);
                }

                return(values[0].Length);
            }
        }
 private int IsDuplicate(ref MDBValue left, ref MDBValue right)
 {
     return(_duplicatesComparer.Compare(left, right));
 }
 private int Compare(ref MDBValue left, ref MDBValue right)
 {
     return(_comparer.Compare(left, right));
 }