{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, RecordWildCards,
MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, CPP,
DeriveDataTypeable #-}
module Database.Redis.Core (
Connection(..), ConnectError(..), connect, checkedConnect, disconnect,
withConnect, withCheckedConnect,
ConnectInfo(..), defaultConnectInfo,
Redis(), runRedis, unRedis, reRedis,
RedisCtx(..), MonadRedis(..),
send, recv, sendRequest,
auth, select, ping
) where
import Prelude
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Exception
import Control.Monad.Reader
import qualified Data.ByteString as B
import Data.IORef
import Data.Pool
import Data.Time
import Data.Typeable
import qualified Network.Socket as NS
import Network.TLS (ClientParams)
import Database.Redis.Protocol
import qualified Database.Redis.ProtocolPipelining as PP
import Database.Redis.Types
newtype Redis a = Redis (ReaderT RedisEnv IO a)
deriving (Monad, MonadIO, Functor, Applicative)
data RedisEnv = Env { envConn :: PP.Connection, envLastReply :: IORef Reply }
class (MonadRedis m) => RedisCtx m f | m -> f where
returnDecode :: RedisResult a => Reply -> m (f a)
instance RedisCtx Redis (Either Reply) where
returnDecode = return . decode
class (Monad m) => MonadRedis m where
liftRedis :: Redis a -> m a
instance MonadRedis Redis where
liftRedis = id
runRedis :: Connection -> Redis a -> IO a
runRedis (Conn pool) redis =
withResource pool $ \conn -> runRedisInternal conn redis
unRedis :: Redis a -> ReaderT RedisEnv IO a
unRedis (Redis r) = r
reRedis :: ReaderT RedisEnv IO a -> Redis a
reRedis r = Redis r
runRedisInternal :: PP.Connection -> Redis a -> IO a
runRedisInternal conn (Redis redis) = do
ref <- newIORef (SingleLine "nobody will ever see this")
r <- runReaderT redis (Env conn ref)
readIORef ref >>= (`seq` return ())
return r
setLastReply :: Reply -> ReaderT RedisEnv IO ()
setLastReply r = do
ref <- asks envLastReply
lift (writeIORef ref r)
recv :: (MonadRedis m) => m Reply
recv = liftRedis $ Redis $ do
conn <- asks envConn
r <- liftIO (PP.recv conn)
setLastReply r
return r
send :: (MonadRedis m) => [B.ByteString] -> m ()
send req = liftRedis $ Redis $ do
conn <- asks envConn
liftIO $ PP.send conn (renderRequest req)
sendRequest :: (RedisCtx m f, RedisResult a)
=> [B.ByteString] -> m (f a)
sendRequest req = do
r' <- liftRedis $ Redis $ do
conn <- asks envConn
r <- liftIO $ PP.request conn (renderRequest req)
setLastReply r
return r
returnDecode r'
newtype Connection = Conn (Pool PP.Connection)
data ConnectInfo = ConnInfo
{ connectHost :: NS.HostName
, connectPort :: PP.PortID
, connectAuth :: Maybe B.ByteString
, connectDatabase :: Integer
, connectMaxConnections :: Int
, connectMaxIdleTime :: NominalDiffTime
, connectTimeout :: Maybe NominalDiffTime
, connectTLSParams :: Maybe ClientParams
} deriving Show
data ConnectError = ConnectAuthError Reply
| ConnectSelectError Reply
deriving (Eq, Show, Typeable)
instance Exception ConnectError
defaultConnectInfo :: ConnectInfo
defaultConnectInfo = ConnInfo
{ connectHost = "localhost"
, connectPort = PP.PortNumber 6379
, connectAuth = Nothing
, connectDatabase = 0
, connectMaxConnections = 50
, connectMaxIdleTime = 30
, connectTimeout = Nothing
, connectTLSParams = Nothing
}
connect :: ConnectInfo -> IO Connection
connect ConnInfo{..} = Conn <$>
createPool create destroy 1 connectMaxIdleTime connectMaxConnections
where
create = do
let timeoutOptUs =
round . (1000000 *) <$> connectTimeout
conn <- PP.connect connectHost connectPort timeoutOptUs
conn' <- case connectTLSParams of
Nothing -> return conn
Just tlsParams -> PP.enableTLS tlsParams conn
PP.beginReceiving conn'
runRedisInternal conn' $ do
case connectAuth of
Nothing -> return ()
Just pass -> do
resp <- auth pass
case resp of
Left r -> liftIO $ throwIO $ ConnectAuthError r
_ -> return ()
when (connectDatabase /= 0) $ do
resp <- select connectDatabase
case resp of
Left r -> liftIO $ throwIO $ ConnectSelectError r
_ -> return ()
return conn'
destroy = PP.disconnect
checkedConnect :: ConnectInfo -> IO Connection
checkedConnect connInfo = do
conn <- connect connInfo
runRedis conn $ void ping
return conn
disconnect :: Connection -> IO ()
disconnect (Conn pool) = destroyAllResources pool
withConnect :: ConnectInfo -> (Connection -> IO c) -> IO c
withConnect connInfo = bracket (connect connInfo) disconnect
withCheckedConnect :: ConnectInfo -> (Connection -> IO c) -> IO c
withCheckedConnect connInfo = bracket (checkedConnect connInfo) disconnect
auth
:: B.ByteString
-> Redis (Either Reply Status)
auth password = sendRequest ["AUTH", password]
select
:: RedisCtx m f
=> Integer
-> m (f Status)
select ix = sendRequest ["SELECT", encode ix]
ping
:: (RedisCtx m f)
=> m (f Status)
ping = sendRequest (["PING"] )