packages feed

libmdbx-0.2.0.0: src/Mdbx/FFI.chs

{-|
Module      : Mdbx.FFI
Copyright   : (c) 2021 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Low level bindings to libmdbx functions.
-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Mdbx.FFI (
  -- * Environment
  MdbxEnvFlags(..),
  MdbxEnv(..),
  MdbxEnvMode(..),
  mdbx_env_create,
  mdbx_env_set_geometry,
  mdbx_env_open,
  mdbx_env_close,

  -- * Transaction
  MdbxTxnFlags(..),
  MdbxTxn(..),
  mdbx_txn_begin,
  mdbx_txn_commit,
  mdbx_txn_abort,
  mdbx_txn_env,

  -- * Database
  MdbxDbFlags(..),
  MdbxDbi(..),
  mdbx_dbi_open,
  mdbx_dbi_close,

  -- * Data
  MdbxVal(..),
  emptyMdbxVal,

  -- * CRUD
  MdbxPutFlags(..),
  mdbx_put,
  mdbx_get,
  mdbx_del,

  -- * Cursor
  MdbxCursorOp(..),
  MdbxCursor(..),
  mdbx_cursor_open,
  mdbx_cursor_close,
  mdbx_cursor_del,
  mdbx_cursor_get,
  mdbx_cursor_put,
  mdbx_cursor_count,
  mdbx_strerror,
  mdbx_cmp,

  -- * Error
  MdbxError(..),

  -- * Conversion for types used in keys
  mdbx_key_from_float,
  mdbx_key_from_double,
  mdbx_key_from_int32,
  mdbx_key_from_int64,

  mdbx_float_from_key,
  mdbx_double_from_key,
  mdbx_int32_from_key,
  mdbx_int64_from_key
) where

import GHC.Float (castWord32ToFloat, castWord64ToDouble)
import Foreign
import Foreign.C

#include "mdbx.h"

-- Common
{# typedef size_t CSize #}

-- | Error codes for the different operations.
{# enum MDBX_error_t as MdbxError {underscoreToCase} deriving (Show, Eq, Ord) #}

-- Environment

-- | Environment object, needed for all the operations.
{# pointer *MDBX_env as MdbxEnv newtype #}
deriving instance Storable MdbxEnv

-- | Flags for opening an environment.
data MdbxEnvFlags
  = MdbxEnvDefaults
  | MdbxSyncDurable
  | MdbxNosubdir
  | MdbxSafeNosync
  | MdbxMapasync
  | MdbxRdonly
  | MdbxNometasync
  | MdbxWritemap
  | MdbxUtterlyNosync
  | MdbxNotls
  | MdbxExclusive
  | MdbxNordahead
  | MdbxNomeminit
  | MdbxCoalesce
  | MdbxLiforeclaim
  | MdbxPageperturb
  | MdbxAccede
  deriving (Show, Eq, Ord)

--{# enum MDBX_env_flags_t as MdbxEnvFlags {underscoreToCase} deriving (Show, Eq, Ord) #}
{# enum MDBX_env_flags_t as MdbxEnvFlags nocode {underscoreToCase} #}

-- | UNIX permissions to set on created files. Zero value means to open existing, but do not create.
type MdbxEnvMode = {# type mdbx_mode_t  #}
{# typedef mdbx_mode_t MdbxEnvMode #}

-- Opening and closing.

-- | Creates an environment. Represents a database in the file system.
{# fun unsafe mdbx_env_create {alloca- `MdbxEnv' peek*} -> `Int' #}

{--
Haddock ignores these:

- size_lower: Minimum size of the database in bytes.
- size_now: Current size of the database in bytes.
- size_upper: Maximum size of the database in bytes.
- growth_step: Step growth size of the database in bytes. Must be greater than
  zero to allow for growth.
- shrink_threshold: Step shrink size of the database in bytes. Must be greater
  than zero to allow for shrinkage and lower than growth_step to avoid shrinking
  after growth.
- pagesize: Page size of the database in bytes.
--}

{-|
Sets geometry of an environment. All the parameters can receive -1 to keep the
current value. Receives (expressed in bytes): size_lower, size_now, size_upper,
growth_step, shrink_threshold, pagesize.
-}
{# fun unsafe mdbx_env_set_geometry {`MdbxEnv', `Int', `Int', `Int', `Int', `Int', `Int'} -> `Int' #}

-- | Opens an environment. Receives name, flags and mode.
{# fun unsafe mdbx_env_open {`MdbxEnv', `String', bitMask`[MdbxEnvFlags]', `MdbxEnvMode'} -> `Int' #}

-- | Closes an environment.
{# fun unsafe mdbx_env_close {`MdbxEnv'} -> `Int' #}

{-- Closes an env (env, dontSync) --}
-- Fails under ghci
-- {# fun unsafe mdbx_env_close_ex {`MdbxEnv', `Bool'} -> `Int' #}

-- Transaction

-- | Transaction instance. Needed for all operations with data, even reading.
{# pointer *MDBX_txn as MdbxTxn newtype #}
deriving instance Storable MdbxTxn

-- | Flags for a transaction.
data MdbxTxnFlags
  = MdbxTxnReadwrite
  | MdbxTxnNosync
  | MdbxTxnRdonly
  | MdbxTxnNometasync
  | MdbxTxnRdonlyPrepare
  | MdbxTxnTry
  deriving (Show, Eq, Ord)

-- {# enum MDBX_txn_flags_t as MdbxTxnFlags {underscoreToCase} deriving (Show, Eq, Ord) #}
{# enum MDBX_txn_flags_t as MdbxTxnFlags nocode {underscoreToCase} #}

{-|
Begins a new transaction.

Arguments:

- Environment.
- Parent transaction, or Nothing.
- Flags.
-}
{# fun unsafe mdbx_txn_begin {`MdbxEnv', maybeTxn* `Maybe MdbxTxn', bitMask`[MdbxTxnFlags]', alloca- `MdbxTxn' peek*} -> `Int' #}

-- | Commits a transaction.
{# fun unsafe mdbx_txn_commit {`MdbxTxn'} -> `Int' #}

-- | Aborts a transaction.
{# fun unsafe mdbx_txn_abort {`MdbxTxn'} -> `Int' #}

-- | Gets the environment from a transaction.
{# fun unsafe mdbx_txn_env {`MdbxTxn'} -> `MdbxEnv' #}

-- MdbxEnv* mdbx_txn_env	(	const MdbxTxn * 	txn	)

-- Database

-- | Database instance. Represents a logical table in the database.
type MdbxDbi = {# type MDBX_dbi #}
{# typedef MDBX_dbi MdbxDbi #}

-- | Flags for a database.
data MdbxDbFlags
  = MdbxDbDefaults
  | MdbxReversekey
  | MdbxDupsort
  | MdbxIntegerkey
  | MdbxDupfixed
  | MdbxIntegerdup
  | MdbxReversedup
  | MdbxCreate
  | MdbxDbAccede
  deriving (Show, Eq, Ord)

--{# enum MDBX_db_flags_t as MdbxDbFlags {underscoreToCase} deriving (Show, Eq, Ord) #}
{# enum MDBX_db_flags_t as MdbxDbFlags nocode {underscoreToCase} #}

{-|
Opens a database.

Arguments:

- Transaction.
- Name.
- Flags.
-}
{# fun unsafe mdbx_dbi_open {`MdbxTxn', maybeString* `Maybe String', bitMask`[MdbxDbFlags]', alloca- `MdbxDbi' peek*} -> `Int' #}

-- | Closes a database.
{# fun unsafe mdbx_dbi_close {`MdbxEnv', `MdbxDbi'} -> `Int' #}

-- CRUD

-- | Binary blob representing a key or value in the database.
data MdbxVal = MdbxVal {
  mvlSize :: {-# UNPACK #-} !{# type size_t #},
  mvlData :: {-# UNPACK #-} !(Ptr ())
} deriving (Eq, Show)

--instance Show MdbxVal where
--  show (MdbxVal sz dt) = "MdbxVal { size = " ++ show sz ++ ", data = " ++ show dt ++ " }"

instance Storable MdbxVal where
  sizeOf _ = {# sizeof MDBX_val #}
  alignment _ = {# alignof MDBX_val #}
  peek ptr = do
    sz <- {# get MDBX_val->iov_len #} ptr
    pd <- {# get MDBX_val->iov_base #} ptr
    return $! MdbxVal sz pd
  poke ptr (MdbxVal sz pd) = do
    {# set MDBX_val->iov_len #} ptr sz
    {# set MDBX_val->iov_base #} ptr pd

-- | Sample empty value
emptyMdbxVal :: MdbxVal
emptyMdbxVal = MdbxVal 0 nullPtr

{# pointer *MDBX_val as MdbxValPtr -> MdbxVal #}

-- | Flags for all data related operations.
data MdbxPutFlags
  = MdbxUpsert
  | MdbxNooverwrite
  | MdbxNodupdata
  | MdbxCurrent
  | MdbxAlldups
  | MdbxReserve
  | MdbxAppend
  | MdbxAppenddup
  | MdbxMultiple
  deriving (Show, Eq, Ord)

--{# enum MDBX_put_flags_t as MdbxPutFlags {underscoreToCase} deriving (Show, Eq, Ord) #}
{# enum MDBX_put_flags_t as MdbxPutFlags nocode {underscoreToCase} #}

{-|
Stores a key/value pair.

Arguments:

- Transaction.
- Database.
- Key.
- Value.
-}
{# fun unsafe mdbx_put {`MdbxTxn', `MdbxDbi', with* `MdbxVal', with* `MdbxVal', bitMask`[MdbxPutFlags]'} -> `Int' #}

{-|
Gets a value with the given key.

Arguments:

- Transaction.
- Database.
- Key.
-}
{# fun unsafe mdbx_get {`MdbxTxn', `MdbxDbi', with* `MdbxVal', alloca- `MdbxVal' peek*} -> `Int' #}

{-|
Gets a value with the given key.

Arguments:

- Transaction.
- Database.
- Key.
-}
{# fun unsafe mdbx_del {`MdbxTxn', `MdbxDbi', with* `MdbxVal', withMaybe* `Maybe MdbxVal'} -> `Int' #}

-- Cursor

-- | Cursor instance. Used for efficient navigation in a database.
{# pointer *MDBX_cursor as MdbxCursor newtype #}
deriving instance Storable MdbxCursor

-- | Flags for cursor operations.
data MdbxCursorOp
  = MdbxFirst
  | MdbxFirstDup
  | MdbxGetBoth
  | MdbxGetBothRange
  | MdbxGetCurrent
  | MdbxGetMultiple
  | MdbxLast
  | MdbxLastDup
  | MdbxNext
  | MdbxNextDup
  | MdbxNextMultiple
  | MdbxNextNodup
  | MdbxPrev
  | MdbxPrevDup
  | MdbxPrevNodup
  | MdbxSet
  | MdbxSetKey
  | MdbxSetRange
  | MdbxPrevMultiple
  | MdbxSetLowerbound
  deriving (Show, Eq, Ord)

--{# enum MDBX_cursor_op as MdbxCursorOp {underscoreToCase} deriving (Show, Eq, Ord) #}
{# enum MDBX_cursor_op as MdbxCursorOp nocode {underscoreToCase} #}

{-|
Opens a new cursor.

Arguments:

- Transaction.
- Database.
-}
{# fun unsafe mdbx_cursor_open {`MdbxTxn', `MdbxDbi', alloca- `MdbxCursor' peek*} -> `Int' #}

-- | Closes a cursor.
{# fun unsafe mdbx_cursor_close {`MdbxCursor'} -> `()' #}

-- | Removes the current key/value pair.
{# fun unsafe mdbx_cursor_del {`MdbxCursor', bitMask`[MdbxPutFlags]'} -> `Int' #}

-- | Returns the current key/value pair.
{# fun unsafe mdbx_cursor_get {`MdbxCursor', with* `MdbxVal' peek*, alloca- `MdbxVal' peek*, `MdbxCursorOp'} -> `Int' #}

{-| Sets the value for the current key.

Arguments:

- Cursor.
- Key.
- Value.
- FLags.
-}
{# fun unsafe mdbx_cursor_put {`MdbxCursor', with* `MdbxVal', with* `MdbxVal', bitMask`[MdbxPutFlags]'} -> `Int' #}

-- | Returns the count of duplicates in the current key.
{# fun unsafe mdbx_cursor_count {`MdbxCursor', alloca- `CSize' peek*} -> `Int' #}

-- Helpers

-- | Returns the description of a given error number.
{# fun unsafe mdbx_strerror {`Int'} -> `String' #}

-- | Compares two values as a binary blob.
{# fun unsafe mdbx_cmp {`MdbxTxn', `MdbxDbi', with* `MdbxVal', with* `MdbxVal'} -> `Int' #}

bitMask :: Enum a => [a] -> C2HSImp.CInt
bitMask = foldl (.|.) 0 . fmap (fromIntegral . fromEnum)

maybeString :: Maybe String -> (Ptr CChar -> IO c) -> IO c
maybeString Nothing fn = fn nullPtr
maybeString (Just val) fn = withCString val fn

maybeTxn :: Maybe MdbxTxn -> (MdbxTxn -> IO c) -> IO c
maybeTxn Nothing fn = fn (MdbxTxn nullPtr)
maybeTxn (Just val) fn = fn val

withMaybe :: Storable a => Maybe a -> (Ptr a -> IO c) -> IO c
withMaybe Nothing fn = fn nullPtr
withMaybe (Just val) fn = with val fn

-- Conversion for types used in keys

-- | Converts a Float value to an unsigned Word that can be used by libmdbx's compare function.
{# fun pure mdbx_key_from_float {`Float'} -> `Word32' #}
-- | Converts a Double value to an unsigned Word that can be used by libmdbx's compare function.
{# fun pure mdbx_key_from_double {`Double'} -> `Word64' #}
-- | Converts a 32bits signed Int value to an unsigned Word that can be used by libmdbx's compare function.
{# fun pure mdbx_key_from_int32 {`Int32'} -> `Word32' #}
-- | Converts a 64bits signed Int value to an unsigned Word that can be used by libmdbx's compare function.
{# fun pure mdbx_key_from_int64 {`Int64'} -> `Word64' #}

-- These functions are re-implemented locally because they are not exported by libmdbx.

-- | Converts an unsigned Word to a Float value.
mdbx_float_from_key :: Word32 -> Float
mdbx_float_from_key val = value where
  intVal :: Int32 = fromIntegral val
  value
    | intVal < 0 = castWord32ToFloat $ val + 0x80000000
    | otherwise = castWord32ToFloat $ 0xffffFFFF - val

-- | Converts an unsigned Word to a Double value.
mdbx_double_from_key :: Word64 -> Double
mdbx_double_from_key val = value where
  intVal :: Int64 = fromIntegral val
  value
    | intVal < 0 = castWord64ToDouble $ val + 0x8000000000000000
    | otherwise = castWord64ToDouble $ 0xffffFFFFffffFFFF - val

-- | Converts an unsigned Word value to a 32bits signed Int.
mdbx_int32_from_key :: Word32 -> Int32
mdbx_int32_from_key val = fromIntegral $ val - 0x80000000;

-- | Converts an unsigned Word value to a 64bits signed Int.
mdbx_int64_from_key :: Word64 -> Int64
mdbx_int64_from_key val = fromIntegral $ val - 0x8000000000000000;