about done.
This commit is contained in:
parent
2e0469483a
commit
6b682aa965
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.cabal-sandbox
|
||||||
|
cabal.sandbox.config
|
||||||
|
dist
|
||||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: haskell
|
||||||
|
ghc:
|
||||||
|
- 7.8
|
||||||
|
- 7.6
|
||||||
20
LICENSE
Normal file
20
LICENSE
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2014 Hirotomo Moriwaki
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
30
memcached-binary.cabal
Normal file
30
memcached-binary.cabal
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: memcached-binary
|
||||||
|
version: 0.1.0
|
||||||
|
synopsis: memcached client using binary protocol.
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Hirotomo Moriwaki
|
||||||
|
maintainer: philopon.dependence@gmail.com
|
||||||
|
copyright: (c) 2014 Hirotomo Moriwaki
|
||||||
|
category: Database
|
||||||
|
build-type: Simple
|
||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules: Database.Memcached.Binary
|
||||||
|
Database.Memcached.Binary.Exception
|
||||||
|
Database.Memcached.Binary.Types
|
||||||
|
Database.Memcached.Binary.Internal
|
||||||
|
Database.Memcached.Binary.Internal.Definition
|
||||||
|
build-depends: base >=4.7 && <4.8
|
||||||
|
, bytestring >=0.10 && <0.11
|
||||||
|
, network >=2.6 && <2.7
|
||||||
|
, storable-endian >=0.2 && <0.3
|
||||||
|
, data-default-class >=0.0 && <0.1
|
||||||
|
, resource-pool >=0.2 && <0.3
|
||||||
|
, containers >=0.5 && <0.6
|
||||||
|
, unordered-containers >=0.2 && <0.3
|
||||||
|
, time >=1.4 && <1.5
|
||||||
|
ghc-options: -Wall -O2
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
||||||
291
src/Database/Memcached/Binary.hs
Normal file
291
src/Database/Memcached/Binary.hs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
||||||
|
|
||||||
|
module Database.Memcached.Binary
|
||||||
|
( -- * connection
|
||||||
|
I.Connection, I.withConnection, I.connect, I.close
|
||||||
|
-- * get
|
||||||
|
, get, get_
|
||||||
|
, get', get'_
|
||||||
|
, getMaybe, getMaybe_
|
||||||
|
-- * set
|
||||||
|
, set, add, replace
|
||||||
|
, set', add', replace'
|
||||||
|
-- * delete
|
||||||
|
, delete, delete'
|
||||||
|
-- * increment/decrement
|
||||||
|
, increment, decrement
|
||||||
|
, increment', decrement'
|
||||||
|
-- * flush
|
||||||
|
, flushAll, flushAll'
|
||||||
|
-- * version
|
||||||
|
, version, version'
|
||||||
|
-- * noOp
|
||||||
|
, noOp, noOp'
|
||||||
|
-- * append/prepend
|
||||||
|
, append, prepend
|
||||||
|
, append', prepend'
|
||||||
|
-- * touch
|
||||||
|
, touch, getAndTouch, getAndTouch_, getMaybeAndTouch
|
||||||
|
, touch', getAndTouch', getAndTouch'_, getMaybeAndTouch_
|
||||||
|
-- * modify
|
||||||
|
, modify , modify_
|
||||||
|
-- * reexports
|
||||||
|
, module Database.Memcached.Binary.Types
|
||||||
|
-- | def
|
||||||
|
, module Data.Default.Class
|
||||||
|
-- | PortID(..)
|
||||||
|
, module Network
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Exception
|
||||||
|
import Network(PortID(..))
|
||||||
|
|
||||||
|
import Data.Default.Class(def)
|
||||||
|
|
||||||
|
import qualified Data.ByteString as S
|
||||||
|
|
||||||
|
import Database.Memcached.Binary.Types
|
||||||
|
import Database.Memcached.Binary.Exception
|
||||||
|
import Database.Memcached.Binary.Internal.Definition
|
||||||
|
import qualified Database.Memcached.Binary.Internal as I
|
||||||
|
|
||||||
|
failureIO :: I.Failure a
|
||||||
|
failureIO w m = throwIO $ MemcachedException w m
|
||||||
|
|
||||||
|
failureMaybe :: I.Failure (Maybe a)
|
||||||
|
failureMaybe _ _ = return Nothing
|
||||||
|
|
||||||
|
failureBool :: I.Failure Bool
|
||||||
|
failureBool _ _ = return False
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | get value and flags. if error occured, throw MemcachedException.
|
||||||
|
get :: Key -> I.Connection -> IO (Flags, Value)
|
||||||
|
get = I.useConnection . I.get (\_ f v -> return (f,v)) failureIO
|
||||||
|
|
||||||
|
-- | get value and flags. if error occured, return Nothing.
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- get' == getMaybe
|
||||||
|
-- @
|
||||||
|
getMaybe, get' :: Key -> I.Connection -> IO (Maybe (Flags, Value))
|
||||||
|
getMaybe = I.useConnection . I.get (\_ f v -> return $ Just (f,v)) failureMaybe
|
||||||
|
get' = getMaybe
|
||||||
|
|
||||||
|
-- | get value. if error occured, throw MemcachedException.
|
||||||
|
get_ :: Key -> I.Connection -> IO Value
|
||||||
|
get_ = I.useConnection . I.get (\_ _ v -> return v) failureIO
|
||||||
|
|
||||||
|
-- | get value. if error occured, return Nothing.
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- get'_ == getMaybe_
|
||||||
|
-- @
|
||||||
|
getMaybe_, get'_ :: Key -> I.Connection -> IO (Maybe Value)
|
||||||
|
getMaybe_ = I.useConnection . I.get (\_ _ v -> return $ Just v) failureMaybe
|
||||||
|
get'_ = getMaybe_
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
setAddReplace :: OpCode -> Flags -> Expiry
|
||||||
|
-> Key -> Value -> I.Connection -> IO ()
|
||||||
|
setAddReplace op = \f e key value -> I.useConnection $
|
||||||
|
I.setAddReplace (const $ return ()) failureIO op (CAS 0) key value f e
|
||||||
|
|
||||||
|
-- | set value. if error occured, throw MemcachedException.
|
||||||
|
set :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO ()
|
||||||
|
set = setAddReplace opSet
|
||||||
|
|
||||||
|
-- | add value. if error occured, throw MemcachedException.
|
||||||
|
add :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO ()
|
||||||
|
add = setAddReplace opAdd
|
||||||
|
|
||||||
|
-- | replace value. if error occured, throw MemcachedException.
|
||||||
|
replace :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO ()
|
||||||
|
replace = setAddReplace opReplace
|
||||||
|
|
||||||
|
setAddReplace' :: OpCode -> Flags -> Expiry
|
||||||
|
-> Key -> Value -> I.Connection -> IO Bool
|
||||||
|
setAddReplace' op = \f e key value -> I.useConnection $
|
||||||
|
I.setAddReplace (const $ return True) failureBool op (CAS 0) key value f e
|
||||||
|
|
||||||
|
|
||||||
|
-- | set value. if error occured, return False.
|
||||||
|
set' :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO Bool
|
||||||
|
set' = setAddReplace' opSet
|
||||||
|
|
||||||
|
-- | add value. if error occured, return False.
|
||||||
|
add' :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO Bool
|
||||||
|
add' = setAddReplace' opAdd
|
||||||
|
|
||||||
|
-- | replace value. if error occured, return False.
|
||||||
|
replace' :: Flags -> Expiry -> Key -> Value -> I.Connection -> IO Bool
|
||||||
|
replace' = setAddReplace' opReplace
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | delete value. if error occured, throw MemcachedException.
|
||||||
|
delete :: Key -> I.Connection -> IO ()
|
||||||
|
delete = I.useConnection . I.delete (\_ -> return ()) failureIO (CAS 0)
|
||||||
|
|
||||||
|
-- | delete value. if error occured, return False.
|
||||||
|
delete' :: Key -> I.Connection -> IO Bool
|
||||||
|
delete' = I.useConnection . I.delete (\_ -> return True) failureBool (CAS 0)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | modify value in transaction. if error occured, throw MemcachedException.
|
||||||
|
modify :: Expiry -> Key -> (Flags -> Value -> (Flags, Value, a))
|
||||||
|
-> I.Connection -> IO a
|
||||||
|
modify e key fn = I.useConnection $ \h ->
|
||||||
|
I.get (\c f v ->
|
||||||
|
let (f', v', r) = fn f v
|
||||||
|
in I.setAddReplace (const $ return r) failureIO opSet c key v' f' e h
|
||||||
|
) failureIO key h
|
||||||
|
|
||||||
|
-- | modify value in transaction. if error occured, throw MemcachedException.
|
||||||
|
modify_ :: Expiry
|
||||||
|
-> Key -> (Flags -> Value -> (Flags, Value))
|
||||||
|
-> I.Connection -> IO ()
|
||||||
|
modify_ e key fn = I.useConnection $ \h ->
|
||||||
|
I.get (\c f v ->
|
||||||
|
let (f', v') = fn f v
|
||||||
|
in I.setAddReplace (const $ return ()) failureIO opSet c key v' f' e h
|
||||||
|
) failureIO key h
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
incrDecr :: OpCode -> Expiry
|
||||||
|
-> Key -> Delta -> Initial -> I.Connection -> IO Counter
|
||||||
|
incrDecr op = \e k d i -> I.useConnection $
|
||||||
|
I.incrDecr (\_ w -> return w) failureIO op (CAS 0) k d i e
|
||||||
|
|
||||||
|
incrDecr' :: OpCode -> Expiry
|
||||||
|
-> Key -> Delta -> Initial -> I.Connection -> IO (Maybe Counter)
|
||||||
|
incrDecr' op e k d i = I.useConnection $
|
||||||
|
I.incrDecr (\_ w -> return $ Just w) failureMaybe op (CAS 0) k d i e
|
||||||
|
|
||||||
|
|
||||||
|
-- | increment value. if error occured, throw MemcachedException.
|
||||||
|
increment :: Expiry -> Key -> Delta -> Initial -> I.Connection -> IO Counter
|
||||||
|
increment = incrDecr opIncrement
|
||||||
|
|
||||||
|
-- | decrement value. if error occured, throw MemcachedException.
|
||||||
|
decrement :: Expiry -> Key -> Delta -> Initial -> I.Connection -> IO Counter
|
||||||
|
decrement = incrDecr opDecrement
|
||||||
|
|
||||||
|
-- | increment value. if error occured, return Nothing.
|
||||||
|
increment' :: Expiry -> Key -> Delta -> Initial
|
||||||
|
-> I.Connection -> IO (Maybe Counter)
|
||||||
|
increment' = incrDecr' opIncrement
|
||||||
|
|
||||||
|
-- | decrement value. if error occured, return Nothing.
|
||||||
|
decrement' :: Expiry -> Key -> Delta -> Initial
|
||||||
|
-> I.Connection -> IO (Maybe Counter)
|
||||||
|
decrement' = incrDecr' opDecrement
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | flush all value. if error occured, throw MemcachedException.
|
||||||
|
flushAll :: I.Connection -> IO ()
|
||||||
|
flushAll = I.useConnection $ I.flushAll (return ()) failureIO
|
||||||
|
|
||||||
|
-- | flush all value. if error occured, return False.
|
||||||
|
flushAll' :: I.Connection -> IO Bool
|
||||||
|
flushAll' = I.useConnection $ I.flushAll (return True) failureBool
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | get version string. if error occured, throw MemcachedException.
|
||||||
|
version :: I.Connection -> IO S.ByteString
|
||||||
|
version = I.useConnection $ I.version return failureIO
|
||||||
|
|
||||||
|
-- | get version string. if error occured, return False.
|
||||||
|
version' :: I.Connection -> IO (Maybe S.ByteString)
|
||||||
|
version' = I.useConnection $ I.version (return . Just) failureMaybe
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | noop(use for keepalive). if error occured, throw MemcachedException.
|
||||||
|
noOp :: I.Connection -> IO ()
|
||||||
|
noOp = I.useConnection $ I.noOp (return ()) failureIO
|
||||||
|
|
||||||
|
-- | noop(use for keepalive). if error occured, return False.
|
||||||
|
noOp' :: I.Connection -> IO Bool
|
||||||
|
noOp' = I.useConnection $ I.noOp (return True) failureBool
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
appendPrepend :: OpCode -> Key -> Value -> I.Connection -> IO ()
|
||||||
|
appendPrepend o = \k v -> I.useConnection $
|
||||||
|
I.appendPrepend (\_ -> return ()) failureIO o (CAS 0) k v
|
||||||
|
|
||||||
|
appendPrepend' :: OpCode -> Key -> Value -> I.Connection -> IO Bool
|
||||||
|
appendPrepend' o = \k v -> I.useConnection $
|
||||||
|
I.appendPrepend (\_ -> return False) failureBool o (CAS 0) k v
|
||||||
|
|
||||||
|
-- | apeend value. if error occured, throw MemcachedException.
|
||||||
|
append :: Key -> Value -> I.Connection -> IO ()
|
||||||
|
append = appendPrepend opAppend
|
||||||
|
|
||||||
|
-- | prepend value. if error occured, throw MemcachedException.
|
||||||
|
prepend :: Key -> Value -> I.Connection -> IO ()
|
||||||
|
prepend = appendPrepend opPrepend
|
||||||
|
|
||||||
|
-- | append value. if error occured, return False.
|
||||||
|
append' :: Key -> Value -> I.Connection -> IO Bool
|
||||||
|
append' = appendPrepend' opAppend
|
||||||
|
|
||||||
|
-- | preppend value. if error occured, return False.
|
||||||
|
prepend' :: Key -> Value -> I.Connection -> IO Bool
|
||||||
|
prepend' = appendPrepend' opPrepend
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | change expiry. if error occured, throw MemcachedException.
|
||||||
|
touch :: Key -> Expiry -> I.Connection -> IO ()
|
||||||
|
touch k e = I.useConnection $
|
||||||
|
I.touch (\_ _ _ -> return ()) failureIO opTouch k e
|
||||||
|
|
||||||
|
-- | change expiry. if error occured, return False.
|
||||||
|
touch' :: Key -> Expiry -> I.Connection -> IO Bool
|
||||||
|
touch' k e = I.useConnection $
|
||||||
|
I.touch (\_ _ _ -> return True) failureBool opTouch k e
|
||||||
|
|
||||||
|
-- | get value and flags, then change expiry.
|
||||||
|
-- if error occured, throw MemcachedException.
|
||||||
|
getAndTouch :: Key -> Expiry -> I.Connection -> IO (Flags, Value)
|
||||||
|
getAndTouch k e = I.useConnection $
|
||||||
|
I.touch (\_ f v -> return (f,v)) failureIO opGAT k e
|
||||||
|
|
||||||
|
-- | get value and flags, then change expiry.
|
||||||
|
-- if error occured, return Nothing.
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- getMaybeAndTouch == getAndTouch'
|
||||||
|
-- @
|
||||||
|
getAndTouch', getMaybeAndTouch
|
||||||
|
:: Key -> Expiry -> I.Connection -> IO (Maybe (Flags, Value))
|
||||||
|
getAndTouch' k e = I.useConnection $
|
||||||
|
I.touch (\_ f v -> return $ Just (f,v)) failureMaybe opGAT k e
|
||||||
|
getMaybeAndTouch = getAndTouch'
|
||||||
|
|
||||||
|
-- | get value then change expiry.
|
||||||
|
-- if error occured, throw MemcachedException.
|
||||||
|
getAndTouch_ :: Key -> Expiry -> I.Connection -> IO Value
|
||||||
|
getAndTouch_ k e = I.useConnection $
|
||||||
|
I.touch (\_ _ v -> return v) failureIO opGAT k e
|
||||||
|
|
||||||
|
-- | get value then change expiry.
|
||||||
|
-- if error occured, return Nothing.
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- getMaybeAndTouch_ == getAndTouch'_
|
||||||
|
-- @
|
||||||
|
getAndTouch'_, getMaybeAndTouch_
|
||||||
|
:: Key -> Expiry -> I.Connection -> IO (Maybe Value)
|
||||||
|
getAndTouch'_ k e = I.useConnection $
|
||||||
|
I.touch (\_ _ v -> return $ Just v) failureMaybe opGAT k e
|
||||||
|
getMaybeAndTouch_ = getAndTouch'_
|
||||||
36
src/Database/Memcached/Binary/Exception.hs
Normal file
36
src/Database/Memcached/Binary/Exception.hs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
|
module Database.Memcached.Binary.Exception where
|
||||||
|
|
||||||
|
import Control.Exception
|
||||||
|
import Data.Word
|
||||||
|
import Data.Typeable
|
||||||
|
import qualified Data.ByteString as S
|
||||||
|
|
||||||
|
data MemcachedException = MemcachedException
|
||||||
|
{-# UNPACK #-} !Word16 {-# UNPACK #-} !S.ByteString
|
||||||
|
deriving (Show, Typeable)
|
||||||
|
|
||||||
|
instance Exception MemcachedException
|
||||||
|
|
||||||
|
#define defExceptionP(n,w) n :: MemcachedException -> Bool;\
|
||||||
|
n (MemcachedException i _) = i == w
|
||||||
|
|
||||||
|
defExceptionP(isKeyNotFound , 0x01)
|
||||||
|
defExceptionP(isKeyExists , 0x02)
|
||||||
|
defExceptionP(isValueTooLarge , 0x03)
|
||||||
|
defExceptionP(isInvalidArguments , 0x04)
|
||||||
|
defExceptionP(isItemNotStored , 0x05)
|
||||||
|
defExceptionP(isIncrDecrOnNonNumeric , 0x06)
|
||||||
|
defExceptionP(isVBucketBelongsToAnotherServer , 0x07)
|
||||||
|
defExceptionP(isAuthenticationError , 0x08)
|
||||||
|
defExceptionP(isAuthenticationContinue , 0x09)
|
||||||
|
defExceptionP(isUnknownCommand , 0x81)
|
||||||
|
defExceptionP(isOutOfMemory , 0x82)
|
||||||
|
defExceptionP(isNotSupported , 0x83)
|
||||||
|
defExceptionP(isInternalError , 0x84)
|
||||||
|
defExceptionP(isBusy , 0x85)
|
||||||
|
defExceptionP(isTemporaryFailure , 0x86)
|
||||||
|
|
||||||
|
#undef defExceptionP
|
||||||
307
src/Database/Memcached/Binary/Internal.hs
Normal file
307
src/Database/Memcached/Binary/Internal.hs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE Rank2Types #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
||||||
|
|
||||||
|
module Database.Memcached.Binary.Internal where
|
||||||
|
|
||||||
|
import Network
|
||||||
|
|
||||||
|
import Foreign.Ptr
|
||||||
|
import Foreign.Storable
|
||||||
|
import Foreign.Marshal.Utils
|
||||||
|
import Foreign.Marshal.Alloc
|
||||||
|
|
||||||
|
import System.IO
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Exception
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
|
||||||
|
import Data.Word
|
||||||
|
import Data.Pool
|
||||||
|
import Data.Storable.Endian
|
||||||
|
import qualified Data.HashMap.Strict as H
|
||||||
|
import qualified Data.ByteString as S
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
import qualified Data.ByteString.Unsafe as S
|
||||||
|
|
||||||
|
import Database.Memcached.Binary.Types
|
||||||
|
import Database.Memcached.Binary.Exception
|
||||||
|
import Database.Memcached.Binary.Internal.Definition
|
||||||
|
|
||||||
|
data Connection
|
||||||
|
= Connection (MVar Handle)
|
||||||
|
| ConnectionPool (Pool Handle)
|
||||||
|
|
||||||
|
withConnection :: ConnectInfo -> (Connection -> IO a) -> IO a
|
||||||
|
withConnection i m = withSocketsDo $ bracket (connect i) close m
|
||||||
|
|
||||||
|
connect :: ConnectInfo -> IO Connection
|
||||||
|
connect i =
|
||||||
|
if numConnection i == 1
|
||||||
|
then fmap Connection $ connect' i >>= newMVar
|
||||||
|
else fmap ConnectionPool $
|
||||||
|
createPool (connect' i) (\h -> quit h >> hClose h) 1
|
||||||
|
(connectionIdleTime i) (numConnection i)
|
||||||
|
|
||||||
|
connect' :: ConnectInfo -> IO Handle
|
||||||
|
connect' i = loop (connectAuth i)
|
||||||
|
where
|
||||||
|
loop [] = do
|
||||||
|
connectTo (connectHost i) (connectPort i)
|
||||||
|
|
||||||
|
loop [a] = do
|
||||||
|
h <- connectTo (connectHost i) (connectPort i)
|
||||||
|
auth a (\_ -> return h) (\w m -> throwIO $ MemcachedException w m) h
|
||||||
|
|
||||||
|
loop (a:as) = do
|
||||||
|
h <- connectTo (connectHost i) (connectPort i)
|
||||||
|
handle (\(_::IOError) -> loop as) $
|
||||||
|
auth a (\_ -> return h) (\_ _ -> loop as) h
|
||||||
|
|
||||||
|
close :: Connection -> IO ()
|
||||||
|
close (Connection mv) = do
|
||||||
|
h <- swapMVar mv (error "connection already closed")
|
||||||
|
quit h
|
||||||
|
hClose h
|
||||||
|
close _ = return ()
|
||||||
|
|
||||||
|
useConnection :: (Handle -> IO a) -> Connection -> IO a
|
||||||
|
useConnection f (Connection mv) = withMVar mv f
|
||||||
|
useConnection f (ConnectionPool p) = withResource p f
|
||||||
|
|
||||||
|
pokeWord8 :: Ptr a -> Word8 -> IO ()
|
||||||
|
pokeWord8 = poke . castPtr
|
||||||
|
|
||||||
|
pokeWord16be :: Ptr a -> Word16 -> IO ()
|
||||||
|
pokeWord16be p w = poke (castPtr p) (BE w)
|
||||||
|
|
||||||
|
pokeWord32be :: Ptr a -> Word32 -> IO ()
|
||||||
|
pokeWord32be p w = poke (castPtr p) (BE w)
|
||||||
|
|
||||||
|
pokeWord64be :: Ptr a -> Word64 -> IO ()
|
||||||
|
pokeWord64be p w = poke (castPtr p) (BE w)
|
||||||
|
|
||||||
|
peekWord8 :: Ptr a -> IO Word8
|
||||||
|
peekWord8 = peek . castPtr
|
||||||
|
|
||||||
|
peekWord16be :: Ptr a -> IO Word16
|
||||||
|
peekWord16be p = peek (castPtr p) >>= \(BE w) -> return w
|
||||||
|
|
||||||
|
peekWord32be :: Ptr a -> IO Word32
|
||||||
|
peekWord32be p = peek (castPtr p) >>= \(BE w) -> return w
|
||||||
|
|
||||||
|
peekWord64be :: Ptr a -> IO Word64
|
||||||
|
peekWord64be p = peek (castPtr p) >>= \(BE w) -> return w
|
||||||
|
|
||||||
|
pokeByteString :: Ptr a -> S.ByteString -> IO ()
|
||||||
|
pokeByteString p v =
|
||||||
|
S.unsafeUseAsCString v $ \cstr ->
|
||||||
|
copyBytes (castPtr p) cstr (S.length v)
|
||||||
|
|
||||||
|
pokeLazyByteString :: Ptr a -> L.ByteString -> IO ()
|
||||||
|
pokeLazyByteString p v =
|
||||||
|
void $ L.foldlChunks (\mi s -> mi >>= \i -> do
|
||||||
|
pokeByteString (plusPtr p i) s
|
||||||
|
return $ i + S.length s
|
||||||
|
) (return 0) v
|
||||||
|
|
||||||
|
data Header
|
||||||
|
data Request
|
||||||
|
|
||||||
|
mallocRequest :: OpCode -> Key -> Word8 -> (Ptr Request -> IO ())
|
||||||
|
-> Int -> (Ptr Request -> IO ()) -> Word32 -> CAS -> IO (Ptr Request)
|
||||||
|
mallocRequest (OpCode o) key elen epoke vlen vpoke opaque (CAS cas) = do
|
||||||
|
let tlen = S.length key + fromIntegral elen + vlen
|
||||||
|
p <- mallocBytes (24 + fromIntegral tlen)
|
||||||
|
pokeWord8 p 0x80
|
||||||
|
pokeWord8 (plusPtr p 1) o
|
||||||
|
pokeWord16be (plusPtr p 2) (fromIntegral $ S.length key)
|
||||||
|
pokeWord8 (plusPtr p 4) elen
|
||||||
|
pokeWord8 (plusPtr p 5) 0x00
|
||||||
|
pokeWord16be (plusPtr p 6) 0x00
|
||||||
|
pokeWord32be (plusPtr p 8) (fromIntegral tlen)
|
||||||
|
pokeWord32be (plusPtr p 12) opaque
|
||||||
|
pokeWord64be (plusPtr p 16) cas
|
||||||
|
epoke (plusPtr p 24)
|
||||||
|
pokeByteString (plusPtr p $ 24 + fromIntegral elen) key
|
||||||
|
vpoke (plusPtr p $ 24 + fromIntegral elen + S.length key)
|
||||||
|
return p
|
||||||
|
{-# INLINE mallocRequest #-}
|
||||||
|
|
||||||
|
sendRequest :: OpCode -> Key -> Word8 -> (Ptr Request -> IO ())
|
||||||
|
-> Int -> (Ptr Request -> IO ()) -> Word32 -> CAS -> Handle -> IO ()
|
||||||
|
sendRequest op key elen epoke vlen vpoke opaque cas h =
|
||||||
|
bracket (mallocRequest op key elen epoke vlen vpoke opaque cas) free $ \req -> do
|
||||||
|
hPutBuf h req (24 + S.length key + fromIntegral elen + vlen)
|
||||||
|
hFlush h
|
||||||
|
{-# INLINE sendRequest #-}
|
||||||
|
|
||||||
|
type Failure a = Word16 -> S.ByteString -> IO a
|
||||||
|
|
||||||
|
peekResponse :: (Ptr Header -> IO a) -> Failure a -> Handle -> IO a
|
||||||
|
peekResponse success failure h = bracket (mallocBytes 24) free $ \p ->
|
||||||
|
hGetBuf h p 24 >> peekWord16be (plusPtr p 6) >>= \st ->
|
||||||
|
if st == 0
|
||||||
|
then success p
|
||||||
|
else do
|
||||||
|
bl <- peekWord32be (plusPtr p 8)
|
||||||
|
failure st =<< S.hGet h (fromIntegral bl)
|
||||||
|
{-# INLINE peekResponse #-}
|
||||||
|
|
||||||
|
withRequest :: OpCode -> Key -> Word8 -> (Ptr Request -> IO ())
|
||||||
|
-> Int -> (Ptr Request -> IO ()) -> CAS
|
||||||
|
-> (Handle -> Ptr Header -> IO a) -> Failure a -> Handle -> IO a
|
||||||
|
withRequest op key elen epoke vlen vpoke cas success failure h = do
|
||||||
|
sendRequest op key elen epoke vlen vpoke 0 cas h
|
||||||
|
peekResponse (success h) failure h
|
||||||
|
|
||||||
|
getExtraLength :: Ptr Header -> IO Word8
|
||||||
|
getExtraLength p = peekWord8 (plusPtr p 4)
|
||||||
|
|
||||||
|
getKeyLength :: Ptr Header -> IO Word16
|
||||||
|
getKeyLength p = peekWord16be (plusPtr p 2)
|
||||||
|
|
||||||
|
getTotalLength :: Ptr Header -> IO Word32
|
||||||
|
getTotalLength p = peekWord32be (plusPtr p 8)
|
||||||
|
|
||||||
|
getCAS :: Ptr Header -> IO CAS
|
||||||
|
getCAS p = fmap CAS $ peekWord64be (plusPtr p 16)
|
||||||
|
|
||||||
|
getOpaque :: Ptr Header -> IO Word32
|
||||||
|
getOpaque p = peekWord32be (plusPtr p 12)
|
||||||
|
|
||||||
|
nop :: Ptr Request -> IO ()
|
||||||
|
nop _ = return ()
|
||||||
|
|
||||||
|
inspectResponse :: Handle -> Ptr Header
|
||||||
|
-> IO (S.ByteString, S.ByteString, L.ByteString)
|
||||||
|
inspectResponse h p = do
|
||||||
|
el <- getExtraLength p
|
||||||
|
kl <- getKeyLength p
|
||||||
|
tl <- getTotalLength p
|
||||||
|
e <- S.hGet h $ fromIntegral el
|
||||||
|
k <- S.hGet h $ fromIntegral kl
|
||||||
|
v <- L.hGet h $ fromIntegral tl - fromIntegral el - fromIntegral kl
|
||||||
|
return (e,k,v)
|
||||||
|
|
||||||
|
getSuccessCallback :: (CAS -> Flags -> Value -> IO a)
|
||||||
|
-> Handle -> Ptr Header -> IO a
|
||||||
|
getSuccessCallback success h p = do
|
||||||
|
elen <- getExtraLength p
|
||||||
|
tlen <- getTotalLength p
|
||||||
|
cas <- getCAS p
|
||||||
|
void $ hGetBuf h p 4
|
||||||
|
flags <- peekWord32be p
|
||||||
|
value <- L.hGet h (fromIntegral tlen - fromIntegral elen)
|
||||||
|
success cas flags value
|
||||||
|
|
||||||
|
get :: (CAS -> Flags -> Value -> IO a) -> Failure a
|
||||||
|
-> Key -> Handle -> IO a
|
||||||
|
get success failure key =
|
||||||
|
withRequest opGet key 0 nop 0 nop (CAS 0)
|
||||||
|
(getSuccessCallback success) failure
|
||||||
|
|
||||||
|
setAddReplace :: (CAS -> IO a) -> Failure a -> OpCode -> CAS
|
||||||
|
-> Key -> Value -> Flags -> Expiry -> Handle -> IO a
|
||||||
|
setAddReplace success failure o cas key value flags expiry = withRequest o key
|
||||||
|
8 (\p -> pokeWord32be p flags >> pokeWord32be (plusPtr p 4) expiry)
|
||||||
|
(fromIntegral $ L.length value) (flip pokeLazyByteString value) cas (\_ p -> getCAS p >>= success) failure
|
||||||
|
|
||||||
|
delete :: (CAS -> IO a) -> Failure a -> CAS -> Key -> Handle -> IO a
|
||||||
|
delete success failure cas key =
|
||||||
|
withRequest opDelete key 0 nop 0 nop cas (\_ p -> getCAS p >>= success) failure
|
||||||
|
|
||||||
|
incrDecr :: (CAS -> Word64 -> IO a) -> Failure a -> OpCode -> CAS
|
||||||
|
-> Key -> Delta -> Initial -> Expiry -> Handle -> IO a
|
||||||
|
incrDecr success failure op cas key delta initial expiry =
|
||||||
|
withRequest op key 20 extra 0 nop cas success' failure
|
||||||
|
where
|
||||||
|
extra p = do
|
||||||
|
pokeWord64be p delta
|
||||||
|
pokeWord64be (plusPtr p 8) initial
|
||||||
|
pokeWord32be (plusPtr p 16) expiry
|
||||||
|
|
||||||
|
success' h p = do
|
||||||
|
c <- getCAS p
|
||||||
|
void $ hGetBuf h p 8
|
||||||
|
peekWord64be p >>= success c
|
||||||
|
|
||||||
|
quit :: Handle -> IO ()
|
||||||
|
quit h = do
|
||||||
|
sendRequest opQuit "" 0 nop 0 nop 0 (CAS 0) h
|
||||||
|
peekResponse (\_ -> return ()) (\_ _ -> return ()) h
|
||||||
|
|
||||||
|
flushAll :: IO a -> Failure a -> Handle -> IO a
|
||||||
|
flushAll success =
|
||||||
|
withRequest opFlush "" 0 nop 0 nop (CAS 0) (\_ _ -> success)
|
||||||
|
|
||||||
|
flushWithin :: IO a -> Failure a -> Expiry -> Handle -> IO a
|
||||||
|
flushWithin success failure w =
|
||||||
|
withRequest opFlush "" 4 (flip pokeWord32be w) 0 nop (CAS 0)
|
||||||
|
(\_ _ -> success) failure
|
||||||
|
|
||||||
|
noOp :: IO a -> Failure a -> Handle -> IO a
|
||||||
|
noOp success =
|
||||||
|
withRequest opNoOp "" 0 nop 0 nop (CAS 0) (\_ _ -> success)
|
||||||
|
|
||||||
|
version :: (S.ByteString -> IO a) -> Failure a -> Handle -> IO a
|
||||||
|
version success =
|
||||||
|
withRequest opVersion "" 0 nop 0 nop (CAS 0)
|
||||||
|
(\h p -> getTotalLength p >>= S.hGet h . fromIntegral >>= success)
|
||||||
|
|
||||||
|
appendPrepend :: (CAS -> IO a) -> Failure a -> OpCode -> CAS
|
||||||
|
-> Key -> Value -> Handle -> IO a
|
||||||
|
appendPrepend success failure op cas key value = withRequest op key 0 nop
|
||||||
|
(fromIntegral $ L.length value) (flip pokeLazyByteString value)
|
||||||
|
cas (\_ -> getCAS >=> success) failure
|
||||||
|
|
||||||
|
stats :: Handle -> IO (H.HashMap S.ByteString S.ByteString)
|
||||||
|
stats h = loop H.empty
|
||||||
|
where
|
||||||
|
loop m = do
|
||||||
|
sendRequest opStat "" 0 nop 0 nop 0 (CAS 0) h
|
||||||
|
peekResponse (success m) (\w s -> throwIO $ MemcachedException w s) h
|
||||||
|
|
||||||
|
success m p = getTotalLength p >>= \tl ->
|
||||||
|
if tl == 0
|
||||||
|
then return m
|
||||||
|
else do
|
||||||
|
kl <- getKeyLength p
|
||||||
|
k <- S.hGet h (fromIntegral kl)
|
||||||
|
v <- S.hGet h (fromIntegral tl - fromIntegral kl)
|
||||||
|
loop (H.insert k v m)
|
||||||
|
|
||||||
|
verbosity :: IO a -> Failure a -> Word32 -> Handle -> IO a
|
||||||
|
verbosity success failure v = withRequest opVerbosity ""
|
||||||
|
4 (flip pokeWord32be v) 0 nop (CAS 0) (\_ _ -> success) failure
|
||||||
|
|
||||||
|
touch :: (CAS -> Flags -> Value -> IO a) -> Failure a -> OpCode
|
||||||
|
-> Key -> Expiry -> Handle -> IO a
|
||||||
|
touch success failure op key e =
|
||||||
|
withRequest op key 4 (flip pokeWord32be e) 0 nop (CAS 0)
|
||||||
|
(getSuccessCallback success) failure
|
||||||
|
|
||||||
|
saslListMechs :: (S.ByteString -> IO a) -> Failure a
|
||||||
|
-> Handle -> IO a
|
||||||
|
saslListMechs success failure =
|
||||||
|
withRequest opSaslListMechs "" 0 nop 0 nop (CAS 0)
|
||||||
|
(\h p -> getTotalLength p >>= S.hGet h . fromIntegral >>= success)
|
||||||
|
failure
|
||||||
|
|
||||||
|
auth :: Auth -> (S.ByteString -> IO a) -> Failure a -> Handle -> IO a
|
||||||
|
auth (Plain u w) success next h = do
|
||||||
|
sendRequest opSaslAuth "PLAIN" 0 nop (S.length u + S.length w + 2) pokeCred 0 (CAS 0) h
|
||||||
|
peekResponse consumeResponse next h
|
||||||
|
where
|
||||||
|
ul = S.length u
|
||||||
|
pokeCred p = do
|
||||||
|
pokeWord8 p 0
|
||||||
|
pokeByteString (plusPtr p 1) u
|
||||||
|
pokeWord8 (plusPtr p $ ul + 1) 0
|
||||||
|
pokeByteString (plusPtr p $ ul + 2) w
|
||||||
|
|
||||||
|
consumeResponse p = do
|
||||||
|
l <- getTotalLength p
|
||||||
|
success =<< S.hGet h (fromIntegral l)
|
||||||
76
src/Database/Memcached/Binary/Internal/Definition.hs
Normal file
76
src/Database/Memcached/Binary/Internal/Definition.hs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
|
module Database.Memcached.Binary.Internal.Definition where
|
||||||
|
|
||||||
|
import Foreign.Storable
|
||||||
|
import Data.Word
|
||||||
|
|
||||||
|
newtype OpCode = OpCode Word8 deriving (Storable)
|
||||||
|
|
||||||
|
#define defOpCode(n,w) n :: OpCode; n = OpCode w
|
||||||
|
|
||||||
|
defOpCode(opGet , 0x00)
|
||||||
|
defOpCode(opSet , 0x01)
|
||||||
|
defOpCode(opAdd , 0x02)
|
||||||
|
defOpCode(opReplace , 0x03)
|
||||||
|
defOpCode(opDelete , 0x04)
|
||||||
|
defOpCode(opIncrement , 0x05)
|
||||||
|
defOpCode(opDecrement , 0x06)
|
||||||
|
defOpCode(opQuit , 0x07)
|
||||||
|
defOpCode(opFlush , 0x08)
|
||||||
|
defOpCode(opGetQ , 0x09)
|
||||||
|
defOpCode(opNoOp , 0x0a)
|
||||||
|
defOpCode(opVersion , 0x0b)
|
||||||
|
defOpCode(opGetK , 0x0c)
|
||||||
|
defOpCode(opGetKQ , 0x0d)
|
||||||
|
defOpCode(opAppend , 0x0e)
|
||||||
|
defOpCode(opPrepend , 0x0f)
|
||||||
|
|
||||||
|
defOpCode(opStat , 0x10)
|
||||||
|
defOpCode(opSetQ , 0x11)
|
||||||
|
defOpCode(opAddQ , 0x12)
|
||||||
|
defOpCode(opReplaceQ , 0x13)
|
||||||
|
defOpCode(opDeleteQ , 0x14)
|
||||||
|
defOpCode(opIncrementQ , 0x15)
|
||||||
|
defOpCode(opDecrementQ , 0x16)
|
||||||
|
defOpCode(opQuitQ , 0x17)
|
||||||
|
defOpCode(opFlushQ , 0x18)
|
||||||
|
defOpCode(opAppendQ , 0x19)
|
||||||
|
defOpCode(opPrependQ , 0x1a)
|
||||||
|
defOpCode(opVerbosity , 0x1b)
|
||||||
|
defOpCode(opTouch , 0x1c)
|
||||||
|
defOpCode(opGAT , 0x1d)
|
||||||
|
defOpCode(opGATQ , 0x1e)
|
||||||
|
|
||||||
|
defOpCode(opSaslListMechs , 0x20)
|
||||||
|
defOpCode(opSaslAuth , 0x21)
|
||||||
|
defOpCode(opSaslStep , 0x22)
|
||||||
|
|
||||||
|
defOpCode(opRGet , 0x30)
|
||||||
|
defOpCode(opRSet , 0x31)
|
||||||
|
defOpCode(opRSetQ , 0x32)
|
||||||
|
defOpCode(opRAppend , 0x33)
|
||||||
|
defOpCode(opRAppendQ , 0x34)
|
||||||
|
defOpCode(opRPrepend , 0x35)
|
||||||
|
defOpCode(opRPrependQ , 0x36)
|
||||||
|
defOpCode(opRDelete , 0x37)
|
||||||
|
defOpCode(opRDeleteQ , 0x38)
|
||||||
|
defOpCode(opRIncr , 0x39)
|
||||||
|
defOpCode(opRIncrQ , 0x3a)
|
||||||
|
defOpCode(opRDecr , 0x3b)
|
||||||
|
defOpCode(opRDecrQ , 0x3c)
|
||||||
|
defOpCode(opSetVBucket , 0x3d)
|
||||||
|
defOpCode(opGetVBucket , 0x3e)
|
||||||
|
defOpCode(opDelVBucket , 0x3f)
|
||||||
|
|
||||||
|
defOpCode(opTAPConnect , 0x40)
|
||||||
|
defOpCode(opTAPMutation , 0x41)
|
||||||
|
defOpCode(opTAPDelete , 0x42)
|
||||||
|
defOpCode(opTAPFlush , 0x43)
|
||||||
|
defOpCode(opTAPOpaque , 0x44)
|
||||||
|
defOpCode(opTAPVBucketSet , 0x45)
|
||||||
|
defOpCode(opTAPCheckpointStart, 0x46)
|
||||||
|
defOpCode(opTAPCheckpointEnd , 0x47)
|
||||||
|
|
||||||
|
#undef defOpCode
|
||||||
38
src/Database/Memcached/Binary/Types.hs
Normal file
38
src/Database/Memcached/Binary/Types.hs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module Database.Memcached.Binary.Types where
|
||||||
|
|
||||||
|
import Network
|
||||||
|
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Word
|
||||||
|
import Data.Default.Class
|
||||||
|
import qualified Data.ByteString as S
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
|
||||||
|
type User = S.ByteString
|
||||||
|
type Password = S.ByteString
|
||||||
|
|
||||||
|
data Auth
|
||||||
|
= Plain User Password
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
data ConnectInfo = ConnectInfo
|
||||||
|
{ connectHost :: HostName
|
||||||
|
, connectPort :: PortID
|
||||||
|
, connectAuth :: [Auth]
|
||||||
|
, numConnection :: Int
|
||||||
|
, connectionIdleTime :: NominalDiffTime
|
||||||
|
} deriving Show
|
||||||
|
|
||||||
|
instance Default ConnectInfo where
|
||||||
|
def = ConnectInfo "localhost" (PortNumber 11211) [] 1 20
|
||||||
|
|
||||||
|
type Flags = Word32
|
||||||
|
type Key = S.ByteString
|
||||||
|
type Value = L.ByteString
|
||||||
|
type Expiry = Word32
|
||||||
|
|
||||||
|
newtype CAS = CAS Word64 deriving (Show)
|
||||||
|
|
||||||
|
type Delta = Word64
|
||||||
|
type Initial = Word64
|
||||||
|
type Counter = Word64
|
||||||
Loading…
Reference in New Issue
Block a user