module Database.LevelDB.Internal
(
DB (..)
, Comparator'
, FilterPolicy'
, Options' (..)
, unsafeClose
, freeCReadOpts
, freeComparator
, freeFilterPolicy
, freeOpts
, mkCReadOpts
, mkComparator
, mkCompareFun
, mkCreateFilterFun
, mkFilterPolicy
, mkKeyMayMatchFun
, mkOpts
, withCWriteOpts
, withCReadOpts
, throwIfErr
, cSizeToInt
, intToCSize
, intToCInt
, cIntToInt
, boolToNum
)
where
import Control.Applicative ((<$>))
import Control.Exception (bracket, finally, onException, throwIO)
import Control.Monad (when)
import Data.ByteString (ByteString)
import Data.IORef
import Foreign
import Foreign.C.String (peekCString, withCString)
import Foreign.C.Types (CInt, CSize)
import Database.LevelDB.C
import Database.LevelDB.Types
import qualified Data.ByteString as BS
data DB = DB LevelDBPtr Options' (IORef Bool)
instance Eq DB where
(DB pt1 _ _) == (DB pt2 _ _) = pt1 == pt2
data Comparator' = Comparator' (FunPtr CompareFun)
(FunPtr Destructor)
(FunPtr NameFun)
ComparatorPtr
data FilterPolicy' = FilterPolicy' (FunPtr CreateFilterFun)
(FunPtr KeyMayMatchFun)
(FunPtr Destructor)
(FunPtr NameFun)
FilterPolicyPtr
data Options' = Options'
{ _optsPtr :: !OptionsPtr
, _cachePtr :: !(Maybe CachePtr)
, _comp :: !(Maybe Comparator')
, _fpPtr :: !(Maybe (Either FilterPolicyPtr FilterPolicy'))
}
unsafeClose :: DB -> IO ()
unsafeClose (DB db_ptr opts_ptr ref) = do
alive <- modify ref ((,) False)
when alive $
c_leveldb_close db_ptr `finally` freeOpts opts_ptr
modify :: IORef a -> (a -> (a,b)) -> IO b
#if MIN_VERSION_base(4,6,0)
modify = atomicModifyIORef'
#else
modify ref f = do
b <- atomicModifyIORef ref
(\x -> let (a, b) = f x
in (a, a `seq` b))
b `seq` return b
#endif
mkOpts :: Options -> IO Options'
mkOpts Options{..} = do
opts_ptr <- c_leveldb_options_create
c_leveldb_options_set_block_restart_interval opts_ptr
$ intToCInt blockRestartInterval
c_leveldb_options_set_block_size opts_ptr
$ intToCSize blockSize
c_leveldb_options_set_compression opts_ptr
$ ccompression compression
c_leveldb_options_set_create_if_missing opts_ptr
$ boolToNum createIfMissing
c_leveldb_options_set_error_if_exists opts_ptr
$ boolToNum errorIfExists
c_leveldb_options_set_max_open_files opts_ptr
$ intToCInt maxOpenFiles
c_leveldb_options_set_paranoid_checks opts_ptr
$ boolToNum paranoidChecks
c_leveldb_options_set_write_buffer_size opts_ptr
$ intToCSize writeBufferSize
cache <- maybeSetCache opts_ptr cacheSize
cmp <- maybeSetCmp opts_ptr comparator
fp <- maybeSetFilterPolicy opts_ptr filterPolicy
return (Options' opts_ptr cache cmp fp)
where
ccompression NoCompression = noCompression
ccompression Snappy = snappyCompression
maybeSetCache :: OptionsPtr -> Int -> IO (Maybe CachePtr)
maybeSetCache opts_ptr size =
if size <= 0
then return Nothing
else do
cache_ptr <- c_leveldb_cache_create_lru $ intToCSize size
c_leveldb_options_set_cache opts_ptr cache_ptr
return . Just $ cache_ptr
maybeSetCmp :: OptionsPtr -> Maybe Comparator -> IO (Maybe Comparator')
maybeSetCmp opts_ptr (Just mcmp) = Just <$> setcmp opts_ptr mcmp
maybeSetCmp _ Nothing = return Nothing
setcmp :: OptionsPtr -> Comparator -> IO Comparator'
setcmp opts_ptr (Comparator cmp) = do
cmp'@(Comparator' _ _ _ cmp_ptr) <- mkComparator "user-defined" cmp
c_leveldb_options_set_comparator opts_ptr cmp_ptr
return cmp'
maybeSetFilterPolicy :: OptionsPtr
-> Maybe (Either BloomFilter FilterPolicy)
-> IO (Maybe (Either FilterPolicyPtr FilterPolicy'))
maybeSetFilterPolicy _ Nothing =
return Nothing
maybeSetFilterPolicy opts_ptr (Just (Left (BloomFilter bloom_ptr))) = do
c_leveldb_options_set_filter_policy opts_ptr bloom_ptr
return Nothing
maybeSetFilterPolicy opts_ptr (Just (Right fp)) = do
fp'@(FilterPolicy' _ _ _ _ fp_ptr) <- mkFilterPolicy fp
c_leveldb_options_set_filter_policy opts_ptr fp_ptr
return . Just . Right $ fp'
freeOpts :: Options' -> IO ()
freeOpts (Options' opts_ptr mcache_ptr mcmp_ptr mfp) = do
c_leveldb_options_destroy opts_ptr
maybe (return ()) c_leveldb_cache_destroy mcache_ptr
maybe (return ()) freeComparator mcmp_ptr
maybe (return ())
(either c_leveldb_filterpolicy_destroy freeFilterPolicy)
mfp
return ()
withCWriteOpts :: WriteOptions -> (WriteOptionsPtr -> IO a) -> IO a
withCWriteOpts WriteOptions{..} = bracket mkCWriteOpts freeCWriteOpts
where
mkCWriteOpts = do
opts_ptr <- c_leveldb_writeoptions_create
onException
(c_leveldb_writeoptions_set_sync opts_ptr $ boolToNum sync)
(c_leveldb_writeoptions_destroy opts_ptr)
return opts_ptr
freeCWriteOpts = c_leveldb_writeoptions_destroy
mkCompareFun :: (ByteString -> ByteString -> Ordering) -> CompareFun
mkCompareFun cmp = cmp'
where
cmp' _ a alen b blen = do
a' <- BS.packCStringLen (a, fromInteger . toInteger $ alen)
b' <- BS.packCStringLen (b, fromInteger . toInteger $ blen)
return $ case cmp a' b' of
EQ -> 0
GT -> 1
LT -> 1
mkComparator :: String -> (ByteString -> ByteString -> Ordering) -> IO Comparator'
mkComparator name f =
withCString name $ \cs -> do
ccmpfun <- mkCmp . mkCompareFun $ f
cdest <- mkDest $ const ()
cname <- mkName $ const cs
ccmp <- c_leveldb_comparator_create nullPtr cdest ccmpfun cname
return $ Comparator' ccmpfun cdest cname ccmp
freeComparator :: Comparator' -> IO ()
freeComparator (Comparator' ccmpfun cdest cname ccmp) = do
c_leveldb_comparator_destroy ccmp
freeHaskellFunPtr ccmpfun
freeHaskellFunPtr cdest
freeHaskellFunPtr cname
mkCreateFilterFun :: ([ByteString] -> ByteString) -> CreateFilterFun
mkCreateFilterFun f = f'
where
f' _ ks ks_lens n_ks flen = do
let n_ks' = fromInteger . toInteger $ n_ks
ks' <- peekArray n_ks' ks
ks_lens' <- peekArray n_ks' ks_lens
keys <- mapM bstr (zip ks' ks_lens')
let res = f keys
poke flen (fromIntegral . BS.length $ res)
BS.useAsCString res $ \cstr -> return cstr
bstr (x,len) = BS.packCStringLen (x, fromInteger . toInteger $ len)
mkKeyMayMatchFun :: (ByteString -> ByteString -> Bool) -> KeyMayMatchFun
mkKeyMayMatchFun g = g'
where
g' _ k klen f flen = do
k' <- BS.packCStringLen (k, fromInteger . toInteger $ klen)
f' <- BS.packCStringLen (f, fromInteger . toInteger $ flen)
return . boolToNum $ g k' f'
mkFilterPolicy :: FilterPolicy -> IO FilterPolicy'
mkFilterPolicy FilterPolicy{..} =
withCString fpName $ \cs -> do
cname <- mkName $ const cs
cdest <- mkDest $ const ()
ccffun <- mkCF . mkCreateFilterFun $ createFilter
ckmfun <- mkKMM . mkKeyMayMatchFun $ keyMayMatch
cfp <- c_leveldb_filterpolicy_create nullPtr cdest ccffun ckmfun cname
return $ FilterPolicy' ccffun ckmfun cdest cname cfp
freeFilterPolicy :: FilterPolicy' -> IO ()
freeFilterPolicy (FilterPolicy' ccffun ckmfun cdest cname cfp) = do
c_leveldb_filterpolicy_destroy cfp
freeHaskellFunPtr ccffun
freeHaskellFunPtr ckmfun
freeHaskellFunPtr cdest
freeHaskellFunPtr cname
mkCReadOpts :: ReadOptions -> IO ReadOptionsPtr
mkCReadOpts ReadOptions{..} = do
opts_ptr <- c_leveldb_readoptions_create
flip onException (c_leveldb_readoptions_destroy opts_ptr) $ do
c_leveldb_readoptions_set_verify_checksums opts_ptr $ boolToNum verifyCheckSums
c_leveldb_readoptions_set_fill_cache opts_ptr $ boolToNum fillCache
case useSnapshot of
Just (Snapshot snap_ptr) -> c_leveldb_readoptions_set_snapshot opts_ptr snap_ptr
Nothing -> return ()
return opts_ptr
freeCReadOpts :: ReadOptionsPtr -> IO ()
freeCReadOpts = c_leveldb_readoptions_destroy
withCReadOpts :: ReadOptions -> (ReadOptionsPtr -> IO a) -> IO a
withCReadOpts opts = bracket (mkCReadOpts opts) freeCReadOpts
throwIfErr :: String -> (ErrPtr -> IO a) -> IO a
throwIfErr s f = alloca $ \err_ptr -> do
poke err_ptr nullPtr
res <- f err_ptr
erra <- peek err_ptr
when (erra /= nullPtr) $ do
err <- peekCString erra
throwIO $ userError $ s ++ ": " ++ err
return res
cSizeToInt :: CSize -> Int
cSizeToInt = fromIntegral
intToCSize :: Int -> CSize
intToCSize = fromIntegral
intToCInt :: Int -> CInt
intToCInt = fromIntegral
cIntToInt :: CInt -> Int
cIntToInt = fromIntegral
boolToNum :: Num b => Bool -> b
boolToNum True = fromIntegral (1 :: Int)
boolToNum False = fromIntegral (0 :: Int)