{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
------------------------------------------------------------------------------
-- | This module provides a thin FFI binding to the libtokyotyrant C library
-- shipped with Mikio Hirabayashi's Tokyo
-- Tyrant. ()
--
-- It's intended to be imported qualified, e.g.:
-- @
-- import qualified Database.TokyoTyrant.FFI as TT
-- @
------------------------------------------------------------------------------
module Database.TokyoTyrant.FFI
(
-- * Opening/closing connections
open
, close
-- * Fetching/storing single values from the store
, get
, put
, putKeep
-- * Fetching/storing multiple values from the store
, mget
, mput
-- * Deleting keys
, delete
, vanish
-- * Key prefix search
, fwmkeys
-- * Types
, Connection
) where
import Control.Monad.Error
import Data.List hiding (delete)
import Foreign.C
import Foreign.ForeignPtr
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
------------------------------------------------------------------------------
-- | Open a connection to Tokyo Tyrant.
--
open :: ByteString -- ^ hostname
-> Int -- ^ port
-> IO (Either String Connection)
open h p = do
runErrorT (open' h p)
------------------------------------------------------------------------------
-- | Close a connection to Tokyo Tyrant
--
close :: Connection -> IO ()
close db_ = withForeignPtr (unConnection db_) $ \db -> do
tcrdbclose db >> return ()
------------------------------------------------------------------------------
-- | Get a value from the database
--
get :: Connection -- ^ connection
-> ByteString -- ^ key
-> IO (Either String (Maybe ByteString))
get db_ key = runErrorT action
where
getval :: IO (Maybe ByteString)
getval = withForeignPtr (unConnection db_) $ \db ->
alloca $ \(p_sz::Ptr CInt) ->
B.useAsCStringLen key $ \(ckey,keylen) -> do
cval <- tcrdbget db ckey (toEnum keylen) p_sz
if cval == nullPtr then return Nothing
else do
sz <- peek p_sz
rbs <- B.packCStringLen (cval, fromEnum sz)
free cval
return $ Just rbs
action :: ErrorT String IO (Maybe ByteString)
action = do
mb <- liftIO getval
case mb of
Just _ -> return mb
Nothing -> ErrorT maybeFail
maybeFail :: IO (Either String (Maybe ByteString))
maybeFail = do
withForeignPtr (unConnection db_) $ \db -> do
ecode <- tcrdbecode db >>= return . TConstant
if ecode == errNoRec then
return $ Right Nothing
else do
cerr <- tcrdberrmsg (unTConstant ecode)
peekCString cerr >>= return . Left
------------------------------------------------------------------------------
-- | Store a value in the database (destructive, overwrites any existing
-- value)
--
put :: Connection -- ^ connection
-> ByteString -- ^ key
-> ByteString -- ^ value
-> IO (Either String ())
put (Connection db_) key value = do
B.useAsCStringLen key $ \(ckey,keylen) ->
B.useAsCStringLen value $ \(cvalue,vallen) ->
withForeignPtr db_ $ \db -> do
rval <- tcrdbput db ckey (toEnum keylen) cvalue (toEnum vallen)
checkErr db rval
------------------------------------------------------------------------------
-- | Store a value in the database (non-destructive, does nothing if the key
-- already exists)
--
putKeep :: Connection -- ^ connection
-> ByteString -- ^ key
-> ByteString -- ^ value
-> IO (Either String ())
putKeep (Connection db_) key value = do
B.useAsCStringLen key $ \(ckey,keylen) ->
B.useAsCStringLen value $ \(cvalue,vallen) ->
withForeignPtr db_ $ \db -> do
rval <- tcrdbputkeep db ckey (toEnum keylen) cvalue (toEnum vallen)
checkErr db rval
------------------------------------------------------------------------------
-- | Get multiple values from the database. On success, returns `Right kvps`.
--
mget :: Connection -- ^ connection to DB
-> [ByteString] -- ^ list of keys to fetch
-> IO (Either String [(ByteString,ByteString)])
mget (Connection db_) keys = withForeignPtr db_ $ \db -> do
lst <- bsListToTCList keys
res <- B.useAsCString "getlist" $ \s -> tcrdbmisc db s 0 lst
tclistdel lst
if res == nullPtr
then checkErr' [] db 0
else do
newlst <- tclistToBSList res
return . Right $ uninterleave newlst []
where
uninterleave (a:b:xs) l = uninterleave xs ((a,b):l)
uninterleave _ l = reverse l
------------------------------------------------------------------------------
-- | Put multiple values to the database.
--
mput :: Connection -- ^ connection to DB
-> [(ByteString, ByteString)] -- ^ list of key-value pairs
-> IO (Either String ())
mput (Connection db_) kvps = withForeignPtr db_ $ \db -> do
lst <- bsListToTCList $ interleave kvps
res <- B.useAsCString "putlist" $ \s -> tcrdbmisc db s 0 lst
tclistdel lst
if res == nullPtr
then checkErr db 0
else return $ Right ()
where
interleave = concatMap (\(k,v) -> [k,v])
------------------------------------------------------------------------------
-- | Delete a value from the DB
--
delete :: Connection -- ^ connection
-> ByteString -- ^ key
-> IO (Either String ())
delete (Connection db_) key = do
B.useAsCStringLen key $ \(ckey,keylen) ->
withForeignPtr db_ $ \db -> do
rval <- tcrdbout db ckey (toEnum keylen)
checkErr db rval
------------------------------------------------------------------------------
-- | Delete all KVPs in the database.
vanish :: Connection -> IO (Either String ())
vanish (Connection db_) = withForeignPtr db_ $ \db -> do
ret <- tcrdbvanish db
checkErr db ret
------------------------------------------------------------------------------
-- | Search keys by prefix. Returns a list of matching keys.
--
fwmkeys :: Connection -- ^ connection to DB
-> ByteString -- ^ key prefix
-> Int -- ^ max # of returned keys; negative numbers
-- mean "no limit"
-> IO (Either String [ByteString])
fwmkeys (Connection db_) key limit =
B.useAsCStringLen key $ \(ckey,keylen) ->
withForeignPtr db_ $ \db -> do
lst <- tcrdbfwmkeys db ckey (toEnum keylen) (toEnum (limit))
if lst == nullPtr
then checkErr' [] db 0
else tclistToBSList lst >>= return . Right
------------------------------------------------------------------------------
-- | A Tokyo Tyrant connection type. Wraps the `TCRDB` type from the C
-- library.
newtype Connection = Connection { unConnection :: ForeignPtr () }
------------------------------------------------------------------------------
-- utility functions
------------------------------------------------------------------------------
open' :: ByteString -> Int -> ErrorT String IO Connection
open' host port = do
db <- liftIO $ tcrdbnew
if db == nullPtr then throwError "couldn't allocate DB object"
else return ()
result <- liftIO $ B.useAsCString host
$ \chost -> tcrdbopen db chost (toEnum port)
db' <- liftIO $ newForeignPtr p_tcrdbdel db
ErrorT $ checkErr db result
return $ Connection db'
------------------------------------------------------------------------------
tclistToBSList :: TCLIST -> IO [ByteString]
tclistToBSList lst = do
n <- tclistnum lst
l <- f 0 n []
tclistdel lst
return l
where
getN i = alloca $ \(p_sz :: Ptr CInt) -> do
cstr <- tclistval lst i p_sz
if cstr == nullPtr then
return B.empty
else do
sz <- peek p_sz
B.packCStringLen (cstr, fromEnum sz)
f i n l | i >= n = return $ reverse l
| otherwise = do
bs <- getN i
f (i+1) n (bs:l)
------------------------------------------------------------------------------
bsListToTCList :: [ByteString] -> IO TCLIST
bsListToTCList strs = do
lst <- tclistnew2 . toEnum $ length strs
mapM_ (doOne lst) strs
return lst
where
doOne lst s =
B.useAsCStringLen s $ \(cstr,len) -> do
tclistpush lst cstr (toEnum len)
------------------------------------------------------------------------------
checkErr :: ConnectionPtr -> CBool -> IO (Either String ())
checkErr db res =
if res == 0
then do
ecode <- liftIO $ tcrdbecode db
cerr <- liftIO $ tcrdberrmsg ecode
str <- liftIO $ peekCString cerr
return $ Left str
else
return $ Right ()
------------------------------------------------------------------------------
checkErr' :: a -> ConnectionPtr -> CBool -> IO (Either String a)
checkErr' v db res =
if res == 0
then do
ecode <- liftIO $ tcrdbecode db
cerr <- liftIO $ tcrdberrmsg ecode
str <- liftIO $ peekCString cerr
return $ Left str
else
return $ Right v
------------------------------------------------------------------------------
-- Types
------------------------------------------------------------------------------
type ConnectionPtr = Ptr ()
type CBool = CInt
type TCLIST = Ptr ()
newtype TConstant = TConstant { unTConstant :: CInt }
deriving (Eq, Show)
#include
#{enum TConstant, TConstant
, errSuccess = TTESUCCESS
, errInvalid = TTEINVALID
, errNoHost = TTENOHOST
, errRefused = TTEREFUSED
, errSend = TTESEND
, errRecv = TTERECV
, errKeep = TTEKEEP
, errNoRec = TTENOREC
, errMisc = TTEMISC }
------------------------------------------------------------------------------
-- FFI: list stuff (from libtokyocabinet)
------------------------------------------------------------------------------
foreign import ccall unsafe "tcutil.h tclistdel"
tclistdel :: TCLIST -> IO ()
foreign import ccall unsafe "tcutil.h tclistnew2"
tclistnew2 :: CInt -> IO TCLIST
foreign import ccall unsafe "tcutil.h tclistpush"
tclistpush :: TCLIST -> CString -> CInt -> IO ()
foreign import ccall unsafe "tcutil.h tclistnum"
tclistnum :: TCLIST -> IO CInt
foreign import ccall unsafe "tcutil.h tclistval"
tclistval :: TCLIST -> CInt -> Ptr CInt -> IO CString
------------------------------------------------------------------------------
-- FFI: tyrant stuff (libtokyotyrant)
------------------------------------------------------------------------------
foreign import ccall unsafe "tcrdb.h tcrdberrmsg"
tcrdberrmsg :: CInt -> IO CString
foreign import ccall unsafe "tcrdb.h tcrdbnew"
tcrdbnew :: IO ConnectionPtr
foreign import ccall unsafe "tcrdb.h &tcrdbdel"
p_tcrdbdel :: FunPtr (ConnectionPtr -> IO ())
foreign import ccall unsafe "tcrdb.h tcrdbecode"
tcrdbecode :: ConnectionPtr -> IO CInt
foreign import ccall unsafe "tcrdb.h tcrdbopen"
tcrdbopen :: ConnectionPtr -> CString -> CInt -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbclose"
tcrdbclose :: ConnectionPtr -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbput"
tcrdbput :: ConnectionPtr -> CString -> CInt -> CString -> CInt -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbputkeep"
tcrdbputkeep :: ConnectionPtr -> CString -> CInt -> CString -> CInt -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbout"
tcrdbout :: ConnectionPtr -> CString -> CInt -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbget"
tcrdbget :: ConnectionPtr -> CString -> CInt -> Ptr CInt -> IO CString
foreign import ccall unsafe "tcrdb.h tcrdbfwmkeys"
tcrdbfwmkeys :: ConnectionPtr -> CString -> CInt -> CInt -> IO TCLIST
foreign import ccall unsafe "tcrdb.h tcrdbvanish"
tcrdbvanish :: ConnectionPtr -> IO CBool
foreign import ccall unsafe "tcrdb.h tcrdbmisc"
tcrdbmisc :: ConnectionPtr -> CString -> CInt -> TCLIST -> IO TCLIST