cql-io 1.0.0 → 1.0.1
raw patch · 11 files changed
+150/−102 lines, 11 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- CHANGELOG +7/−0
- cql-io.cabal +2/−2
- src/Database/CQL/IO.hs +5/−2
- src/Database/CQL/IO/Batch.hs +3/−2
- src/Database/CQL/IO/Client.hs +43/−37
- src/Database/CQL/IO/Cluster/Host.hs +58/−8
- src/Database/CQL/IO/Cluster/Policies.hs +0/−1
- src/Database/CQL/IO/Connection.hs +5/−4
- src/Database/CQL/IO/Connection/Settings.hs +1/−0
- src/Database/CQL/IO/Connection/Socket.hs +1/−0
- src/Database/CQL/IO/Types.hs +25/−46
CHANGELOG view
@@ -1,3 +1,10 @@+1.0.1+------+- Address an issue whereby Cassandra 'Error' responses might be mistakenly+ thrown by the (internal) withRetries function, possibly leading to+ problems with the automatic (re-)preparation of prepared queries upon+ 'Unprepared' server responses.+ 1.0.0 ------ - Add support for CQL V4 binary protocol.
cql-io.cabal view
@@ -1,5 +1,5 @@ name: cql-io-version: 1.0.0+version: 1.0.1 synopsis: Cassandra CQL client. license: MPL-2.0 license-file: LICENSE@@ -79,7 +79,7 @@ build-depends: async >= 2.0 , auto-update >= 0.1- , base >= 4.7 && < 5.0+ , base >= 4.9 && < 5.0 , bytestring >= 0.10 , containers >= 0.5 , cql >= 4.0
src/Database/CQL/IO.hs view
@@ -205,7 +205,10 @@ -- | A type which can be run as a query. class RunQ q where- runQ :: (MonadClient m, Tuple a, Tuple b) => q k a b -> QueryParams a -> m (Response k a b)+ runQ :: (MonadClient m, Tuple a, Tuple b)+ => q k a b+ -> QueryParams a+ -> m (Response k a b) instance RunQ QueryString where runQ q p = request (RqQuery (Query q p))@@ -259,7 +262,7 @@ r <- runQ q p getResult r >>= \case RowsResult _ b -> return b- _ -> throwM $ UnexpectedResponse' r+ _ -> throwM $ UnexpectedResponse r -- | Run a CQL schema query, returning 'SchemaChange' information, if any. schema :: (MonadClient m, Tuple a, RunQ q) => q S a () -> QueryParams a -> m (Maybe SchemaChange)
src/Database/CQL/IO/Batch.hs view
@@ -23,6 +23,7 @@ import Control.Monad.Trans import Control.Monad.Trans.State.Strict import Database.CQL.IO.Client+import Database.CQL.IO.Cluster.Host import Database.CQL.IO.PrepQuery import Database.CQL.IO.Types import Database.CQL.Protocol@@ -38,9 +39,9 @@ batch m = do b <- execStateT (unBatchM m) (Batch BatchLogged [] Quorum Nothing) r <- executeWithPrepare Nothing (RqBatch b :: Raw Request)- getResult r >>= \case+ getResult (hrResponse r) >>= \case VoidResult -> return ()- _ -> throwM $ UnexpectedResponse' r+ _ -> throwM $ UnexpectedResponse (hrResponse r) -- | Add a query to this batch. addQuery :: (Show a, Tuple a, Tuple b) => QueryString W a b -> a -> BatchM ()
src/Database/CQL/IO/Client.hs view
@@ -54,7 +54,7 @@ #if MIN_VERSION_transformers(0,4,0) import Control.Monad.Trans.Except #endif-import Control.Retry (capDelay, exponentialBackoff, rsIterNumber, applyPolicy)+import Control.Retry (capDelay, exponentialBackoff, rsIterNumber) import Control.Retry (recovering) import Data.Foldable (for_, foldrM) import Data.List (find)@@ -235,12 +235,12 @@ request :: (MonadClient m, Tuple a, Tuple b) => Request k a b -> m (Response k a b) request a = liftClient $ do n <- liftIO . hostCount =<< view policy- snd <$> withRetries (requestN n) a+ hrResponse <$> withRetries (requestN n) a -- | Invoke 'request1' up to @n@ times with different hosts if no -- connection is available. May return 'Nothing' if no connection -- is available on any of the tried hosts.-requestN :: (Tuple b, Tuple a) => Word -> Request k a b -> ClientState -> Client (Maybe (Host, Response k a b))+requestN :: (Tuple b, Tuple a) => Word -> Request k a b -> ClientState -> Client (Maybe (HostResponse k a b)) requestN !n a s = do hst <- pickHost (s^.policy) res <- request1 hst a s@@ -252,13 +252,13 @@ -- | Get 'Response' from a single 'Host'. -- May return 'Nothing' if no connection is available.-request1 :: (Tuple a, Tuple b) => Host -> Request k a b -> ClientState -> Client (Maybe (Host, Response k a b))+request1 :: (Tuple a, Tuple b) => Host -> Request k a b -> ClientState -> Client (Maybe (HostResponse k a b)) request1 h a s = do p <- Map.lookup h <$> readTVarIO' (s^.hostmap) case p of Just x -> do result <- with x transaction `catches` handlers- for_ result $ \(_, r) ->+ for_ result $ \(HostResponse _ r) -> for_ (Cql.warnings r) $ \w -> warn $ msg (val "server warning: " +++ w) return result@@ -272,7 +272,7 @@ let x = s^.context.settings.connSettings.compression let v = s^.context.settings.protoVersion r <- parse x <$> C.request c (serialise v x a)- r `seq` return (h, r)+ r `seq` return (HostResponse h r) handlers = [ Handler $ \(e :: ConnectionError) -> onConnectionError h e >> throwM e@@ -283,11 +283,11 @@ -- | Execute the given request. If an 'Unprepared' error is returned, this -- function will automatically try to re-prepare the query and re-execute -- the original request using the same host which was used for re-preparation.-executeWithPrepare :: (Tuple b, Tuple a) => Maybe Host -> Request k a b -> Client (Response k a b)+executeWithPrepare :: (Tuple b, Tuple a) => Maybe Host -> Request k a b -> Client (HostResponse k a b) executeWithPrepare h q = do f <- selectAction h r <- withRetries f q- case snd r of+ case hrResponse r of RsError _ _ (Unprepared _ i) -> do pq <- preparedQueries qs <- atomically' (PQ.lookupQueryString (QueryId i) pq)@@ -296,8 +296,7 @@ Just s -> do (g, _) <- prepare (Just LazyPrepare) (s :: Raw QueryString) executeWithPrepare (Just g) q- RsError _ _ e -> throwM e- x -> return x+ _ -> return r where selectAction Nothing = view policy >>= liftIO . hostCount >>= return . requestN selectAction (Just x) = return (request1 x)@@ -309,8 +308,8 @@ prepare (Just LazyPrepare) qs = do s <- ask n <- liftIO $ hostCount (s^.policy)- (h, r) <- withRetries (requestN n) (RqPrepare (Prepare qs))- (h,) <$> getPreparedQueryId r+ r <- withRetries (requestN n) (RqPrepare (Prepare qs))+ getPreparedQueryId r prepare (Just EagerPrepare) qs = view policy >>= liftIO . current@@ -318,8 +317,8 @@ >>= first where action rq h = do- r <- snd <$> withRetries (request1 h) rq- (h,) <$> getPreparedQueryId r+ r <- withRetries (request1 h) rq+ getPreparedQueryId r first (x:_) = return x first [] = throwM NoHostAvailable@@ -334,7 +333,7 @@ pq <- view prepQueries maybe (new pq) (run Nothing) =<< atomically' (PQ.lookupQueryId q pq) where- run h i = executeWithPrepare h (RqExecute (Execute i p))+ run h i = hrResponse <$> executeWithPrepare h (RqExecute (Execute i p)) new pq = do (h, i) <- prepare (Just LazyPrepare) (PQ.queryString q) atomically' (PQ.insert q i pq)@@ -500,24 +499,31 @@ ----------------------------------------------------------------------------- -- Exception handling +-- [Note: Error responses]+-- Cassandra error responses are locally thrown as 'ResponseError's to achieve+-- a unified handling of retries in the context of a single retry policy,+-- together with other recoverable (i.e. retryable) exceptions. However, this+-- is just an internal technicality for handling retries - generally error+-- responses must not escape this function as exceptions. Deciding if and when+-- to actually throw a 'ResponseError' upon inspection of the 'HostResponse'+-- must be left to the caller. withRetries :: (Tuple a, Tuple b)- => (Request k a b -> ClientState -> Client (Maybe (Host, Response k a b)))+ => (Request k a b -> ClientState -> Client (Maybe (HostResponse k a b))) -> Request k a b- -> Client (Host, Response k a b)+ -> Client (HostResponse k a b) withRetries fn a = do s <- ask let p = s^.context.settings.retrySettings.retryPolicy- recovering p recoverFrom $ \i -> do+ r <- try $ recovering p canRetry $ \i -> do r <- if rsIterNumber i == 0 then fn a s else fn (newRequest s) (adjust s) case r of Nothing -> throwM HostsBusy- Just hr -> case snd hr of- RsError _ _ e -> applyPolicy p i- >>= maybe (return hr) (const (throwM e))- _ -> return hr+ -- [Note: Error responses]+ Just hr -> maybe (return hr) throwM (toResponseError hr)+ return $ either fromResponseError id r where adjust s = let x = s^.context.settings.retrySettings.sendTimeoutChange@@ -536,14 +542,14 @@ RqBatch b -> RqBatch b { batchConsistency = c } _ -> a - recoverFrom =- [ const $ Handler $ \e -> return $ case e of- ReadTimeout {} -> True- WriteTimeout {} -> True- Overloaded {} -> True- Unavailable {} -> True- ServerError {} -> True- _ -> False+ canRetry =+ [ const $ Handler $ \(e :: ResponseError) -> case reCause e of+ ReadTimeout {} -> return True+ WriteTimeout {} -> return True+ Overloaded {} -> return True+ Unavailable {} -> return True+ ServerError {} -> return True+ _ -> return False , const $ Handler $ \(_ :: ConnectionError) -> return True , const $ Handler $ \(_ :: IOException) -> return True , const $ Handler $ \(_ :: HostError) -> return True@@ -664,15 +670,15 @@ getResult :: MonadThrow m => Response k a b -> m (Result k a b) getResult (RsResult _ _ r) = return r getResult (RsError _ _ e) = throwM e-getResult r = throwM $ UnexpectedResponse r+getResult hr = throwM (UnexpectedResponse hr) {-# INLINE getResult #-} -getPreparedQueryId :: MonadThrow m => Response k a b -> m (QueryId k a b)-getPreparedQueryId r = do- rs <- getResult r- case rs of- PreparedResult i _ _ -> return i- _ -> throwM $ UnexpectedResponse r+getPreparedQueryId :: MonadThrow m => HostResponse k a b -> m (Host, QueryId k a b)+getPreparedQueryId hr = do+ r <- getResult (hrResponse hr)+ case r of+ PreparedResult i _ _ -> return (hrHost hr, i)+ _ -> throwM $ UnexpectedResponse (hrResponse hr) {-# INLINE getPreparedQueryId #-} peer2Host :: PortNumber -> Peer -> Host
src/Database/CQL/IO/Cluster/Host.hs view
@@ -2,16 +2,33 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. +{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Database.CQL.IO.Cluster.Host where import Control.Lens ((^.), Lens') import Data.ByteString.Lazy.Char8 (unpack)+import Database.CQL.Protocol (Response (..))+import Data.IP import Data.Text (Text)-import Database.CQL.IO.Types (InetAddr)+import Network.Socket (SockAddr (..), PortNumber) import System.Logger.Message +-- | Host representation.+data Host = Host+ { _hostAddr :: !InetAddr+ , _dataCentre :: !Text+ , _rack :: !Text+ } deriving (Eq, Ord)++-- | A response that is known to originate from a specific+-- host of a cluster.+data HostResponse k a b = HostResponse+ { hrHost :: !Host+ , hrResponse :: !(Response k a b)+ } deriving (Show)+ -- | This event will be passed to a 'Policy' to inform it about -- cluster changes. data HostEvent@@ -20,13 +37,6 @@ | HostUp !InetAddr -- ^ a host has been started | HostDown !InetAddr -- ^ a host has been stopped --- | Host representation.-data Host = Host- { _hostAddr :: !InetAddr- , _dataCentre :: !Text- , _rack :: !Text- } deriving (Eq, Ord)- -- | The IP address and port number of this host. hostAddr :: Lens' Host InetAddr hostAddr f ~(Host a c r) = fmap (\x -> Host x c r) (f a)@@ -47,3 +57,43 @@ instance ToBytes Host where bytes h = h^.dataCentre +++ val ":" +++ h^.rack +++ val ":" +++ h^.hostAddr++-----------------------------------------------------------------------------+-- InetAddr++newtype InetAddr = InetAddr { sockAddr :: SockAddr } deriving (Eq, Ord)++instance Show InetAddr where+ show (InetAddr (SockAddrInet p a)) =+ let i = fromIntegral p :: Int in+ shows (fromHostAddress a) . showString ":" . shows i $ ""+ show (InetAddr (SockAddrInet6 p _ a _)) =+ let i = fromIntegral p :: Int in+ shows (fromHostAddress6 a) . showString ":" . shows i $ ""+ show (InetAddr (SockAddrUnix unix)) = unix+#if MIN_VERSION_network(2,6,1) && !MIN_VERSION_network(3,0,0)+ show (InetAddr (SockAddrCan int32)) = show int32+#endif++instance ToBytes InetAddr where+ bytes (InetAddr (SockAddrInet p a)) =+ let i = fromIntegral p :: Int in+ show (fromHostAddress a) +++ val ":" +++ i+ bytes (InetAddr (SockAddrInet6 p _ a _)) =+ let i = fromIntegral p :: Int in+ show (fromHostAddress6 a) +++ val ":" +++ i+ bytes (InetAddr (SockAddrUnix unix)) = bytes unix+#if MIN_VERSION_network(2,6,1) && !MIN_VERSION_network(3,0,0)+ bytes (InetAddr (SockAddrCan int32)) = bytes int32+#endif++ip2inet :: PortNumber -> IP -> InetAddr+ip2inet p (IPv4 a) = InetAddr $ SockAddrInet p (toHostAddress a)+ip2inet p (IPv6 a) = InetAddr $ SockAddrInet6 p 0 (toHostAddress6 a) 0++inet2ip :: InetAddr -> IP+inet2ip (InetAddr (SockAddrInet _ a)) = IPv4 (fromHostAddress a)+inet2ip (InetAddr (SockAddrInet6 _ _ a _)) = IPv6 (fromHostAddress6 a)+inet2ip _ = error "inet2Ip: not IP4/IP6 address"++
src/Database/CQL/IO/Cluster/Policies.hs view
@@ -17,7 +17,6 @@ import Data.Map.Strict (Map) import Data.Word import Database.CQL.IO.Cluster.Host-import Database.CQL.IO.Types (InetAddr) import System.Random.MWC import Prelude
src/Database/CQL/IO/Connection.hs view
@@ -43,6 +43,7 @@ import Data.Unique import Data.Vector (Vector, (!)) import Database.CQL.Protocol+import Database.CQL.IO.Cluster.Host import Database.CQL.IO.Connection.Socket (Socket) import Database.CQL.IO.Connection.Settings import Database.CQL.IO.Hexdump@@ -272,7 +273,7 @@ res <- request c enc case parse (c^.settings.compression) res :: Raw Response of RsReady _ _ Ready -> c^.eventSig |-> f- other -> throwM (UnexpectedResponse' other)+ other -> throwM (UnexpectedResponse' other) validateSettings :: MonadIO m => Connection -> m () validateSettings c = liftIO $ do@@ -287,7 +288,7 @@ res <- request c (serialise (c^.protocol) noCompression options) case parse noCompression res :: Raw Response of RsSupported _ _ x -> return x- other -> throwM (UnexpectedResponse' other)+ other -> throwM (UnexpectedResponse' other) useKeyspace :: MonadIO m => Connection -> Keyspace -> m () useKeyspace c ks = liftIO $ do@@ -298,7 +299,7 @@ res <- request c (serialise (c^.protocol) cmp req) case parse cmp res :: Raw Response of RsResult _ _ (SetKeyspaceResult _) -> return ()- other -> throwM (UnexpectedResponse' other)+ other -> throwM (UnexpectedResponse' other) query :: forall k a b m. (Tuple a, Tuple b, Show b, MonadIO m) => Connection@@ -312,7 +313,7 @@ res <- request c enc case parse (c^.settings.compression) res :: Response k a b of RsResult _ _ (RowsResult _ b) -> return b- other -> throwM (UnexpectedResponse' other)+ other -> throwM (UnexpectedResponse' other) where params = QueryParams cons False p Nothing Nothing Nothing Nothing
src/Database/CQL/IO/Connection/Settings.hs view
@@ -37,6 +37,7 @@ import Data.HashMap.Strict (HashMap) import Data.Int import Database.CQL.Protocol+import Database.CQL.IO.Cluster.Host import Database.CQL.IO.Types import OpenSSL.Session (SSLContext) import Prelude
src/Database/CQL/IO/Connection/Socket.hs view
@@ -23,6 +23,7 @@ import Data.ByteString.Builder import Data.Maybe (isJust) import Data.Monoid+import Database.CQL.IO.Cluster.Host import Database.CQL.IO.Types import Foreign.C.Types (CInt (..)) import Network.Socket hiding (Stream, Socket, connect, close, recv, send, shutdown)
src/Database/CQL/IO/Types.hs view
@@ -2,7 +2,6 @@ -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. -{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -13,14 +12,13 @@ import Control.Monad.Catch import Data.Hashable-import Data.IP import Data.String import Data.Text (Text) import Data.Typeable import Data.Unique+import Data.UUID+import Database.CQL.IO.Cluster.Host import Database.CQL.Protocol-import Network.Socket (SockAddr (..), PortNumber)-import System.Logger.Message import qualified Data.Text.Lazy as Lazy @@ -39,44 +37,6 @@ hashWithSalt _ (ConnId u) = hashUnique u -------------------------------------------------------------------------------- InetAddr--newtype InetAddr = InetAddr { sockAddr :: SockAddr } deriving (Eq, Ord)--instance Show InetAddr where- show (InetAddr (SockAddrInet p a)) =- let i = fromIntegral p :: Int in- shows (fromHostAddress a) . showString ":" . shows i $ ""- show (InetAddr (SockAddrInet6 p _ a _)) =- let i = fromIntegral p :: Int in- shows (fromHostAddress6 a) . showString ":" . shows i $ ""- show (InetAddr (SockAddrUnix unix)) = unix-#if MIN_VERSION_network(2,6,1) && !MIN_VERSION_network(3,0,0)- show (InetAddr (SockAddrCan int32)) = show int32-#endif--instance ToBytes InetAddr where- bytes (InetAddr (SockAddrInet p a)) =- let i = fromIntegral p :: Int in- show (fromHostAddress a) +++ val ":" +++ i- bytes (InetAddr (SockAddrInet6 p _ a _)) =- let i = fromIntegral p :: Int in- show (fromHostAddress6 a) +++ val ":" +++ i- bytes (InetAddr (SockAddrUnix unix)) = bytes unix-#if MIN_VERSION_network(2,6,1) && !MIN_VERSION_network(3,0,0)- bytes (InetAddr (SockAddrCan int32)) = bytes int32-#endif--ip2inet :: PortNumber -> IP -> InetAddr-ip2inet p (IPv4 a) = InetAddr $ SockAddrInet p (toHostAddress a)-ip2inet p (IPv6 a) = InetAddr $ SockAddrInet6 p 0 (toHostAddress6 a) 0--inet2ip :: InetAddr -> IP-inet2ip (InetAddr (SockAddrInet _ a)) = IPv4 (fromHostAddress a)-inet2ip (InetAddr (SockAddrInet6 _ _ a _)) = IPv6 (fromHostAddress6 a)-inet2ip _ = error "inet2Ip: not IP4/IP6 address"------------------------------------------------------------------------------- -- InvalidSettings data InvalidSettings@@ -102,6 +62,25 @@ show (InternalError e) = "cql-io: internal error: " ++ show e -----------------------------------------------------------------------------+-- ResponseError++data ResponseError = ResponseError+ { reHost :: !Host+ , reTrace :: !(Maybe UUID)+ , reWarn :: ![Text]+ , reCause :: !Error+ } deriving (Show, Typeable)++instance Exception ResponseError++toResponseError :: HostResponse k a b -> Maybe ResponseError+toResponseError (HostResponse h (RsError t w c)) = Just (ResponseError h t w c)+toResponseError _ = Nothing++fromResponseError :: ResponseError -> HostResponse k a b+fromResponseError (ResponseError h t w c) = HostResponse h (RsError t w c)++----------------------------------------------------------------------------- -- HostError data HostError@@ -147,8 +126,8 @@ data NoShow = NoShow deriving Show data UnexpectedResponse where- UnexpectedResponse :: !(Response k a b) -> UnexpectedResponse- UnexpectedResponse' :: Show b => !(Response k a b) -> UnexpectedResponse+ UnexpectedResponse :: !(Response k a b) -> UnexpectedResponse+ UnexpectedResponse' :: Show b => !(Response k a b) -> UnexpectedResponse deriving instance Typeable UnexpectedResponse instance Exception UnexpectedResponse@@ -156,8 +135,8 @@ instance Show UnexpectedResponse where show x = showString "cql-io: unexpected response: " . case x of- UnexpectedResponse r -> shows (f r)- UnexpectedResponse' r -> shows r+ UnexpectedResponse r -> shows (f r)+ UnexpectedResponse' r -> shows r $ "" where f :: Response k a b -> Response k a NoShow