{-# LANGUAGE ScopedTypeVariables #-} -- | A raw, low level interface to the memcache protocol. -- -- The various operations are represented in full as they appear at the -- protocol level and so aren't generally well suited for application use. -- Instead, applications should use Database.Memcache.Client which presents a -- higher level API suited for application use. module Database.Memcache.Protocol ( get, gat, touch, set, set', add, replace, delete, increment, decrement, append, prepend, StatResults, stats, flush, noop, version, quit ) where import Database.Memcache.Errors import Database.Memcache.Server import Database.Memcache.Types import Database.Memcache.Wire import qualified Control.Exception as E import Control.Monad import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Word import qualified Network.Socket as N -- XXX: Structure Vs. Args? -- i.e., -- replace :: Server -> Key -> Value -> Flags -> Expiration -> Version -> IO (Maybe Version) -- vs. -- replace :: Server -> Request -> IO (Maybe Version) -- -- Request -- { key :: Key -- , value :: Value -- , flags :: Flags -- , exp :: Expiration -- , ver :: Version -- } -- -- Using a structure would allow easy defaults... -- get :: Server -> Key -> IO (Maybe (Value, Flags, Version)) get c k = do let msg = emptyReq { reqOp = ReqGet Loud NoKey k } r <- sendRecv c msg (v, f) <- case resOp r of ResGet Loud v f -> return (v, f) _ -> throwIncorrectRes r "GET" case resStatus r of NoError -> return $ Just (v, f, resCas r) ErrKeyNotFound -> return Nothing rs -> throwStatus rs gat :: Server -> Key -> Expiration -> IO (Maybe (Value, Flags, Version)) gat c k e = do let msg = emptyReq { reqOp = ReqGAT Loud NoKey k (SETouch e) } r <- sendRecv c msg (v, f) <- case resOp r of ResGAT Loud v f -> return (v, f) _ -> throwIncorrectRes r "GAT" case resStatus r of NoError -> return $ Just (v, f, resCas r) ErrKeyNotFound -> return Nothing rs -> throwStatus rs touch :: Server -> Key -> Expiration -> IO (Maybe Version) touch c k e = do let msg = emptyReq { reqOp = ReqTouch k (SETouch e) } r <- sendRecv c msg when (resOp r /= ResTouch) $ throwIncorrectRes r "TOUCH" case resStatus r of NoError -> return $ Just (resCas r) ErrKeyNotFound -> return Nothing rs -> throwStatus rs set :: Server -> Key -> Value -> Flags -> Expiration -> IO Version set c k v f e = do let msg = emptyReq { reqOp = ReqSet Loud k v (SESet f e) } r <- sendRecv c msg when (resOp r /= ResSet Loud) $ throwIncorrectRes r "SET" case resStatus r of NoError -> return $ resCas r rs -> throwStatus rs -- XXX: Use a return type like: Return = OK Version | NotFound | NotVersion? set' :: Server -> Key -> Value -> Flags -> Expiration -> Version -> IO (Maybe Version) set' c k v f e ver = do let msg = emptyReq { reqOp = ReqSet Loud k v (SESet f e), reqCas = ver } r <- sendRecv c msg when (resOp r /= ResSet Loud) $ throwIncorrectRes r "SET" case resStatus r of NoError -> return $ Just (resCas r) -- version specified and key doesn't exist... ErrKeyNotFound -> return Nothing -- version specified and doesn't match key... ErrKeyExists -> return Nothing rs -> throwStatus rs add :: Server -> Key -> Value -> Flags -> Expiration -> IO (Maybe Version) add c k v f e = do let msg = emptyReq { reqOp = ReqAdd Loud k v (SESet f e) } r <- sendRecv c msg when (resOp r /= ResAdd Loud) $ throwIncorrectRes r "ADD" case resStatus r of NoError -> return $ Just (resCas r) ErrKeyExists -> return Nothing rs -> throwStatus rs replace :: Server -> Key -> Value -> Flags -> Expiration -> Version -> IO (Maybe Version) replace c k v f e ver = do let msg = emptyReq { reqOp = ReqReplace Loud k v (SESet f e), reqCas = ver } r <- sendRecv c msg when (resOp r /= ResReplace Loud) $ throwIncorrectRes r "REPLACE" case resStatus r of NoError -> return $ Just (resCas r) -- replace only applies to an existing key... ErrKeyNotFound -> return Nothing -- version specified and doesn't match key... ErrKeyExists -> return Nothing rs -> throwStatus rs delete :: Server -> Key -> Version -> IO Bool delete c k ver = do let msg = emptyReq { reqOp = ReqDelete Loud k, reqCas = ver } r <- sendRecv c msg when (resOp r /= ResDelete Loud) $ throwIncorrectRes r "DELETE" case resStatus r of NoError -> return True -- delete only applies to an existing key... ErrKeyNotFound -> return False -- version specified and doesn't match key... ErrKeyExists -> return False rs -> throwStatus rs increment :: Server -> Key -> Initial -> Delta -> Expiration -> Version -> IO (Maybe (Word64, Version)) increment c k i d e ver = do let msg = emptyReq { reqOp = ReqIncrement Loud k (SEIncr i d e), reqCas = ver } r <- sendRecv c msg n <- case resOp r of ResIncrement Loud n -> return n _ -> throwIncorrectRes r "INCREMENT" case resStatus r of NoError -> return $ Just (n, resCas r) ErrKeyNotFound -> return Nothing ErrKeyExists -> return Nothing rs -> throwStatus rs decrement :: Server -> Key -> Initial -> Delta -> Expiration -> Version -> IO (Maybe (Word64, Version)) decrement c k i d e ver = do let msg = emptyReq { reqOp = ReqDecrement Loud k (SEIncr i d e), reqCas = ver } r <- sendRecv c msg n <- case resOp r of ResDecrement Loud n -> return n _ -> throwIncorrectRes r "DECREMENT" case resStatus r of NoError -> return $ Just (n, resCas r) ErrKeyNotFound -> return Nothing ErrKeyExists -> return Nothing rs -> throwStatus rs append :: Server -> Key -> Value -> Version -> IO (Maybe Version) append c k v ver = do let msg = emptyReq { reqOp = ReqAppend Loud k v, reqCas = ver } r <- sendRecv c msg when (resOp r /= ResAppend Loud) $ throwIncorrectRes r "APPEND" case resStatus r of NoError -> return $ Just (resCas r) ErrKeyNotFound -> return Nothing rs -> throwStatus rs prepend :: Server -> Key -> Value -> Version -> IO (Maybe Version) prepend c k v ver = do let msg = emptyReq { reqOp = ReqPrepend Loud k v, reqCas = ver } r <- sendRecv c msg when (resOp r /= ResPrepend Loud) $ throwIncorrectRes r "PREPEND" case resStatus r of NoError -> return $ Just (resCas r) ErrKeyNotFound -> return Nothing rs -> throwStatus rs flush :: Server -> Maybe Expiration -> IO () flush c e = do let e' = SETouch `fmap` e msg = emptyReq { reqOp = ReqFlush Loud e' } r <- sendRecv c msg when (resOp r /= ResFlush Loud) $ throwIncorrectRes r "FLUSH" case resStatus r of NoError -> return () rs -> throwStatus rs noop :: Server -> IO () noop c = do let msg = emptyReq { reqOp = ReqNoop } r <- sendRecv c msg when (resOp r /= ResNoop) $ throwIncorrectRes r "NOOP" case resStatus r of NoError -> return () rs -> throwStatus rs version :: Server -> IO ByteString version c = do let msg = emptyReq { reqOp = ReqVersion } r <- sendRecv c msg v <- case resOp r of ResVersion v -> return v _ -> throwIncorrectRes r "VERSION" case resStatus r of NoError -> return v rs -> throwStatus rs -- | StatResults are a list of key-value pairs. type StatResults = [(ByteString, ByteString)] -- XXX: Should this be Maybe? Does wrong key return error or just empty -- results? stats :: Server -> Maybe Key -> IO (Maybe StatResults) stats c key = withSocket c $ \s -> do let msg = emptyReq { reqOp = ReqStat key } send s msg getAllStats s [] where getAllStats s xs = do r <- recv s (k, v) <- case resOp r of ResStat k v -> return (k, v) _ -> throwIncorrectRes r "STATS" case resStatus r of NoError | B.null k && B.null v -> return $ Just xs | otherwise -> getAllStats s $ (k, v):xs ErrKeyNotFound -> return Nothing rs -> throwStatus rs quit :: Server -> IO () quit c = do -- TODO: not clear if waiting for a reply matters withSocket c $ \s -> sendClose s `E.catch` consumeError close c where consumeError (_ ::E.SomeException) = return () sendClose s = do let msg = emptyReq { reqOp = ReqQuit Loud } send s msg N.shutdown s N.ShutdownSend r <- recv s when (resOp r /= ResQuit Loud) $ throwIncorrectRes r "QUIT" case resStatus r of NoError -> return () rs -> throwStatus rs