module Data.BerkeleyDB.Internal where
import Foreign.C.Error
import Foreign.C
import Foreign
import System.IO.Unsafe
import Data.Char
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as Char8
import qualified Data.ByteString.Unsafe as B
import qualified Data.ByteString.Internal as B
data DB
data DBT
data DBC
data ENV
type DBTYPE = Word32
data Flag
= AutoCommit
| Create
| Excl
| Multiversion
| NoMMap
| ReadOnly
| ReadUncommitted
| Thread
| Truncate
deriving (Read, Show, Enum, Eq, Ord)
data DbType
= BTree
| Hash
| Recno
| Queue
| UnknownType
fromDbType BTree = 1
fromDbType Hash = 2
fromDbType Recno = 3
fromDbType Queue = 4
fromDbType UnknownType = 5
fromFlag AutoCommit = 256
fromFlag Create = 1
fromFlag Excl = 64
fromFlag Multiversion = 4
fromFlag _ = 0
fromFlags = foldr (.|.) 0 . map fromFlag
data SyncOption = Sync | NoSync
fromSyncOption Sync = 0
fromSyncOption NoSync = 21
data PutFlag
= Append
| NoDupData
| NoOverwrite
deriving (Read, Show, Enum, Eq, Ord)
fromPutFlag Append = 2
fromPutFlag NoDupData = 19
fromPutFlag NoOverwrite = 20
fromPutFlags = foldr (.|.) 0 . map fromPutFlag
data DbFlag
= Consume
| ConsumeWait
| Multiple
| Duplicates
| SortDuplicates
| Next
fromDbFlag Consume = 4
fromDbFlag ConsumeWait = 5
fromDbFlag Multiple = 2048
fromDbFlag Duplicates = 16
fromDbFlag SortDuplicates = 4
fromDbFlag Next = 16
fromDbFlags = foldr (.|.) 0 . map fromDbFlag
type Object = B.ByteString
foreign import ccall unsafe db_env_create
:: Ptr (Ptr ENV) -> CUInt -> IO CInt
createEnv :: IO (Ptr ENV)
createEnv = alloca $ \tmp ->
do throwErrnoIf (/=0) "db_env_create" $ db_env_create tmp 0
peek tmp
foreign import ccall unsafe hs_env_open
:: Ptr ENV -> CString -> CUInt -> CInt -> IO CInt
foreign import ccall unsafe "db_create" c_create ::
Ptr (Ptr DB) -> Ptr ENV -> Word32 -> IO CInt
create :: IO (Ptr DB)
create = alloca $ \tmp ->
do
throwErrnoIf (/=0) "create" $ c_create tmp nullPtr 0
peek tmp
foreign import ccall unsafe "hs_open" c_open ::
Ptr DB -> Ptr () -> CString -> CString -> DBTYPE -> Word32 -> CInt -> IO CInt
open :: Maybe FilePath -> Maybe String -> DbType -> [Flag] -> IO (Ptr DB)
open mbFile mbDatabase dbType flags
= do ptr <- create
setFlags ptr [Duplicates]
maybeWith withCString mbFile $ \cfile ->
maybeWith withCString mbDatabase $ \cdatabase ->
throwErrnoIf (/=0) "open" $
c_open ptr nullPtr cfile cdatabase (fromDbType dbType) (fromFlags flags) 0
return ptr
foreign import ccall unsafe "hs_close" c_close :: Ptr DB -> Word32 -> IO CInt
close :: Ptr DB -> SyncOption -> IO ()
close db sync
= do throwErrnoIf (/=0) "close" $ c_close db (fromSyncOption sync)
return ()
foreign import ccall unsafe "&hs_gc_close" closePtr :: FunPtr (Ptr DB -> IO ())
foreign import ccall unsafe "hs_clear_dbt" clear_dbt :: Ptr DBT -> IO ()
withDBT :: Object -> (Ptr DBT -> IO a) -> IO a
withDBT object fn
= allocaBytes (28) $ \dbt ->
do clear_dbt dbt
B.unsafeUseAsCStringLen object $ \(ptr,len) ->
do (\hsc_ptr -> pokeByteOff hsc_ptr 0) dbt ptr
(\hsc_ptr -> pokeByteOff hsc_ptr 4) dbt (fromIntegral len :: CInt)
fn dbt
foreign import ccall unsafe "hs_put" c_put ::
Ptr DB -> Ptr () -> Ptr DBT -> Ptr DBT -> Word32 -> IO CInt
put :: Ptr DB -> Object -> Object -> [PutFlag] -> IO ()
put db key value flags
= withDBT key $ \dbtKey ->
withDBT value $ \dbtValue ->
do throwErrnoIf (/=0) "put" $
c_put db nullPtr dbtKey dbtValue (fromPutFlags flags)
return ()
foreign import ccall unsafe "hs_get" c_get ::
Ptr DB -> Ptr () -> Ptr DBT -> Ptr DBT -> Word32 -> IO CInt
get :: Ptr DB -> Object -> [DbFlag] -> IO (Maybe Object)
get db object flags
= withDBT object $ \keyDbt ->
withDBT nullObject $ \dataDbt ->
do (\hsc_ptr -> pokeByteOff hsc_ptr 24) keyDbt (16 :: Word32)
ret <- c_get db nullPtr keyDbt dataDbt (fromDbFlags flags)
case ret of
30988 -> return Nothing
0 -> do ptr <- (\hsc_ptr -> peekByteOff hsc_ptr 0) dataDbt
size <- (\hsc_ptr -> peekByteOff hsc_ptr 4) dataDbt :: IO CInt
bs <- B.unsafePackCStringFinalizer ptr (fromIntegral size) (free ptr)
return $ Just bs
_ -> throwErrno "get"
getMany :: Ptr DB -> Object -> [DbFlag] -> IO [Object]
getMany db object flags
= withDBT object $ \keyDbt ->
withDBT nullObject $ \dataDbt ->
dbtGetMany dataDbt (c_get db nullPtr keyDbt dataDbt (fromDbFlags (Multiple:flags)))
dbtGetMany dataDbt fn
= let loop size =
do ptr <- mallocBytes size
(\hsc_ptr -> pokeByteOff hsc_ptr 0) dataDbt ptr
(\hsc_ptr -> pokeByteOff hsc_ptr 24) dataDbt (1024 :: Word32)
(\hsc_ptr -> pokeByteOff hsc_ptr 8) dataDbt (fromIntegral size :: Word32)
ret <- fn
case ret of
30988 -> free ptr >> return []
30999 -> free ptr >> loop (size*2)
0 -> do ulen <- fmap fromIntegral ((\hsc_ptr -> peekByteOff hsc_ptr 8) dataDbt :: IO Word32)
fptr <- newForeignPtr finalizerFree ptr
let intPtr = castPtr ptr
let walker n ls = do offset <- peekByteOff intPtr (ulen (4) * (n+1)) :: IO Word32
len <- peekByteOff intPtr (ulen (4) * (n+2)) :: IO Word32
if offset == 1 || (offset == 0 && len == 0)
then return ls
else let bs = B.fromForeignPtr fptr (fromIntegral offset) (fromIntegral len)
in walker (n+2) (bs:ls)
walker 0 []
_ -> throwErrno "getMany"
in loop (1024*sizeOf (undefined :: Int))
foreign import ccall unsafe hs_set_flags :: Ptr DB -> Word32 -> IO CInt
setFlags :: Ptr DB -> [DbFlag] -> IO ()
setFlags ptr flags
= do throwErrnoIf (/=0) "setFlags" $ hs_set_flags ptr (fromDbFlags flags)
return ()
foreign import ccall unsafe "hs_cursor"
hs_cursor :: Ptr DB -> Ptr () -> Ptr (Ptr DBC) -> Word32 -> IO CInt
foreign import ccall unsafe "hs_cursor_get"
hs_cursor_get :: Ptr DBC -> Ptr DBT -> Ptr DBT -> Word32 -> IO CInt
foreign import ccall unsafe "hs_cursor_close"
hs_cursor_close :: Ptr DBC -> IO CInt
closeCursor :: Ptr DBC -> IO ()
closeCursor ptr = do throwErrnoIf (/=0) "hs_cursor_close" $ hs_cursor_close ptr
return ()
newCursor :: Ptr DB -> IO (Ptr DBC)
newCursor db
= alloca $ \dbcPtr -> do throwErrnoIf (/=0) "hs_cursor" $ hs_cursor db nullPtr dbcPtr 0
peek dbcPtr
getAtCursor :: Ptr DBC -> [DbFlag] -> IO (Maybe (Object,[Object]))
getAtCursor dbc flags
= withDBT nullObject $ \keyDbt ->
withDBT nullObject $ \dataDbt ->
do clear_dbt keyDbt
(\hsc_ptr -> pokeByteOff hsc_ptr 24) keyDbt (16 :: Word32)
objs <- dbtGetMany dataDbt $ hs_cursor_get dbc keyDbt dataDbt (fromDbFlags $ Multiple:flags)
if null objs then return Nothing else do
ptr <- (\hsc_ptr -> peekByteOff hsc_ptr 0) keyDbt
size <- (\hsc_ptr -> peekByteOff hsc_ptr 4) keyDbt :: IO CInt
keyObject <- B.unsafePackCStringFinalizer ptr (fromIntegral size) (free ptr)
let pp = B.take 40 . Char8.filter isPrint
return (Just (keyObject,objs))
nullObject = B.fromForeignPtr B.nullForeignPtr 0 0