packages feed

riak 0.3.2.1 → 0.4.0.0

raw patch · 11 files changed

+360/−80 lines, 11 filesdep ~aesondep ~attoparsecPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, attoparsec

API changes (from Hackage documentation)

+ Network.Riak: class Show a => Resolvable a
+ Network.Riak: modify :: (FromJSON a, ToJSON a, Resolvable a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO (a, b)) -> IO (a, b)
+ Network.Riak: modify_ :: (FromJSON a, ToJSON a, Resolvable a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO a) -> IO a
+ Network.Riak: resolve :: Resolvable a => a -> a -> a
+ Network.Riak.JSON.Resolvable: RetriesExceeded :: ResolutionFailure
+ Network.Riak.JSON.Resolvable: class Show a => Resolvable a
+ Network.Riak.JSON.Resolvable: data ResolutionFailure
+ Network.Riak.JSON.Resolvable: modify :: (FromJSON a, ToJSON a, Resolvable a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO (a, b)) -> IO (a, b)
+ Network.Riak.JSON.Resolvable: modify_ :: (FromJSON a, ToJSON a, Resolvable a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO a) -> IO a
+ Network.Riak.JSON.Resolvable: resolve :: Resolvable a => a -> a -> a
+ Network.Riak.Resolvable: RetriesExceeded :: ResolutionFailure
+ Network.Riak.Resolvable: data ResolutionFailure
+ Network.Riak.Value.Resolvable: RetriesExceeded :: ResolutionFailure
+ Network.Riak.Value.Resolvable: class Show a => Resolvable a
+ Network.Riak.Value.Resolvable: data ResolutionFailure
+ Network.Riak.Value.Resolvable: modify :: (Resolvable a, IsContent a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO (a, b)) -> IO (a, b)
+ Network.Riak.Value.Resolvable: modify_ :: (Resolvable a, IsContent a) => Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO a) -> IO a
+ Network.Riak.Value.Resolvable: resolve :: Resolvable a => a -> a -> a
- Network.Riak: put :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
+ Network.Riak: put :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
- Network.Riak: putMany :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO [(c, VClock)]
+ Network.Riak: putMany :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO [(c, VClock)]
- Network.Riak.JSON.Resolvable: put :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
+ Network.Riak.JSON.Resolvable: put :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
- Network.Riak.JSON.Resolvable: putMany :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO [(c, VClock)]
+ Network.Riak.JSON.Resolvable: putMany :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO [(c, VClock)]
- Network.Riak.JSON.Resolvable: putMany_ :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO ()
+ Network.Riak.JSON.Resolvable: putMany_ :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW -> IO ()
- Network.Riak.JSON.Resolvable: put_ :: (Eq c, FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO ()
+ Network.Riak.JSON.Resolvable: put_ :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW -> IO ()
- Network.Riak.Resolvable: class (Eq a, Show a) => Resolvable a
+ Network.Riak.Resolvable: class Show a => Resolvable a
- Network.Riak.Value.Resolvable: put :: (Eq a, Resolvable a, IsContent a) => Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW -> IO (a, VClock)
+ Network.Riak.Value.Resolvable: put :: (Resolvable a, IsContent a) => Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW -> IO (a, VClock)
- Network.Riak.Value.Resolvable: putMany :: (Eq a, Resolvable a, IsContent a) => Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW -> IO [(a, VClock)]
+ Network.Riak.Value.Resolvable: putMany :: (Resolvable a, IsContent a) => Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW -> IO [(a, VClock)]
- Network.Riak.Value.Resolvable: putMany_ :: (Eq a, Resolvable a, IsContent a) => Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW -> IO ()
+ Network.Riak.Value.Resolvable: putMany_ :: (Resolvable a, IsContent a) => Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW -> IO ()
- Network.Riak.Value.Resolvable: put_ :: (Eq a, Resolvable a, IsContent a) => Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW -> IO ()
+ Network.Riak.Value.Resolvable: put_ :: (Resolvable a, IsContent a) => Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW -> IO ()

Files

riak.cabal view
@@ -1,5 +1,5 @@ name:                riak-version:             0.3.2.1+version:             0.4.0.0 synopsis:            A Haskell client for the Riak decentralized data store description:   A Haskell client library for the Riak decentralized data@@ -79,8 +79,8 @@     Network.Riak.Types.Internal      build-depends:       -    aeson >= 0.2.0.0,-    attoparsec >= 0.8.5.0,+    aeson >= 0.3.2.4,+    attoparsec >= 0.8.5.3,     base == 4.*,     binary,     blaze-builder,
src/Network/Riak.hs view
@@ -50,11 +50,15 @@     , getServerInfo     -- * Data management     , Quorum(..)+    , Resolvable(..)     , get     , getMany+    , modify+    , modify_+    , delete+    -- ** Low-level modification functions     , put     , putMany-    , delete     -- * Metadata     , listBuckets     , foldKeys@@ -65,4 +69,5 @@     ) where  import Network.Riak.Basic hiding (get, put, put_)-import Network.Riak.JSON.Resolvable (get, getMany, put, putMany)+import Network.Riak.JSON.Resolvable (get, getMany, modify, modify_, put, putMany)+import Network.Riak.Resolvable (Resolvable(..))
src/Network/Riak/Basic.hs view
@@ -71,8 +71,8 @@ getServerInfo :: Connection -> IO ServerInfo getServerInfo conn = exchange conn Req.getServerInfo --- | Retrieve up a value.  This may return multiple conflicting--- siblings.  Choosing among them is your responsibility.+-- | Retrieve a value.  This may return multiple conflicting siblings.+-- Choosing among them is your responsibility. get :: Connection -> T.Bucket -> T.Key -> R     -> IO (Maybe (Seq.Seq Content, VClock)) get conn bucket key r = Resp.get <$> exchangeMaybe conn (Req.get bucket key r)@@ -80,6 +80,11 @@ -- | Store a single value.  This may return multiple conflicting -- siblings.  Choosing among them, and storing a new value, is your -- responsibility.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored. put :: Connection -> T.Bucket -> T.Key -> Maybe T.VClock     -> Content -> W -> DW     -> IO (Seq.Seq Content, VClock)@@ -88,6 +93,11 @@  -- | Store a single value, without the possibility of conflict -- resolution.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored, and you will not be notified. put_ :: Connection -> T.Bucket -> T.Key -> Maybe T.VClock      -> Content -> W -> DW      -> IO ()
src/Network/Riak/Connection/Pool.hs view
@@ -179,7 +179,7 @@ -- -- It probably goes without saying that you should never call -- 'disconnect' on a connection, as doing so will cause a subsequent--- user, expecting the connection to be valid, to throw an exception.+-- user (who expects the connection to be valid) to throw an exception. withConnection :: Pool -> (Connection -> IO a) -> IO a withConnection Pool{..} act = do   i <- ((`mod` numStripes) . hash) <$> myThreadId
src/Network/Riak/JSON.hs view
@@ -54,6 +54,8 @@     toContent (J a) = V.toContent (toJSON a)     {-# INLINE toContent #-} +-- | Retrieve a value.  This may return multiple conflicting siblings.+-- Choosing among them is your responsibility. get :: (FromJSON c, ToJSON c) => Connection -> Bucket -> Key -> R     -> IO (Maybe ([c], VClock)) get conn bucket key r = fmap convert <$> V.get conn bucket key r@@ -62,25 +64,55 @@     -> IO [Maybe ([c], VClock)] getMany conn bucket ks r = map (fmap convert) <$> V.getMany conn bucket ks r +-- | Store a single value.  This may return multiple conflicting+-- siblings.  Choosing among them, and storing a new value, is your+-- responsibility.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored. put :: (FromJSON c, ToJSON c) =>        Connection -> Bucket -> Key -> Maybe VClock -> c     -> W -> DW -> IO ([c], VClock) put conn bucket key mvclock val w dw =     convert <$> V.put conn bucket key mvclock (json val) w dw +-- | Store a single value, without the possibility of conflict+-- resolution.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored, and you will not be notified. put_ :: (FromJSON c, ToJSON c) =>        Connection -> Bucket -> Key -> Maybe VClock -> c     -> W -> DW -> IO () put_ conn bucket key mvclock val w dw =     V.put_ conn bucket key mvclock (json val) w dw +-- | Store many values.  This may return multiple conflicting siblings+-- for each value stored.  Choosing among them, and storing a new+-- value in each case, is your responsibility.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored. putMany :: (FromJSON c, ToJSON c) =>-       Connection -> Bucket -> [(Key, Maybe VClock, c)]-    -> W -> DW -> IO [([c], VClock)]+           Connection -> Bucket -> [(Key, Maybe VClock, c)]+        -> W -> DW -> IO [([c], VClock)] putMany conn bucket puts w dw =     map convert <$> V.putMany conn bucket (map f puts) w dw   where f (k,v,c) = (k,v,json c) +-- | Store many values, without the possibility of conflict+-- resolution.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored, and you will not be notified. putMany_ :: (FromJSON c, ToJSON c) =>             Connection -> Bucket -> [(Key, Maybe VClock, c)]          -> W -> DW -> IO ()
src/Network/Riak/JSON/Resolvable.hs view
@@ -8,16 +8,22 @@ -- -- This module allows storage and retrieval of JSON-encoded data. ----- Functions automatically resolve conflicts using 'Resolvable' instances.--- For instance, if a 'get' returns three siblings, a winner will be--- chosen using 'mconcat'.  If a 'put' results in a conflict, a winner--- will be chosen using 'mconcat', and the winner will be 'put'; this--- will be repeated until no conflict occurs.+-- Functions automatically resolve conflicts using 'Resolvable'+-- instances.  For instance, if a 'get' returns three siblings, a+-- winner will be chosen using 'resolve'.  If a 'put' results in a+-- conflict, a winner will be chosen using 'resolve', and the winner+-- will be 'put'; this will be repeated until either no conflict+-- occurs or the process has been repeated too many times.  module Network.Riak.JSON.Resolvable     (-      get+      Resolvable(..)+    , ResolutionFailure(..)+    , get     , getMany+    , modify+    , modify_+    -- * Low-level modification functions     , put     , put_     , putMany@@ -25,36 +31,77 @@     ) where  import Data.Aeson.Types (FromJSON(..), ToJSON(..))-import Network.Riak.Resolvable.Internal (Resolvable)+import Network.Riak.Resolvable.Internal (ResolutionFailure(..), Resolvable(..)) import Network.Riak.Types.Internal hiding (MessageTag(..)) import qualified Network.Riak.JSON as J import qualified Network.Riak.Resolvable.Internal as R --- | Retrieve a single value.  If conflicting values are returned, the--- 'Resolvable' is used to choose a winner.+-- | Retrieve a single value.  If conflicting values are returned,+-- 'resolve' is used to choose a winner. get :: (FromJSON c, ToJSON c, Resolvable c) =>        Connection -> Bucket -> Key -> R -> IO (Maybe (c, VClock)) get = R.get J.get {-# INLINE get #-}  -- | Retrieve multiple values.  If conflicting values are returned for--- a key, the 'Resolvable' is used to choose a winner.+-- a key, 'resolve' is used to choose a winner. getMany :: (FromJSON c, ToJSON c, Resolvable c)            => Connection -> Bucket -> [Key] -> R -> IO [Maybe (c, VClock)] getMany = R.getMany J.getMany {-# INLINE getMany #-} +-- | Modify a single value.  The value, if any, is retrieved using+-- 'get'; conflict resolution is performed if necessary.  The+-- modification function is called on the resulting value, and its+-- result is stored using 'put', which may again perform conflict+-- resolution.+--+-- The result of this function is whatever was returned by 'put',+-- along with the auxiliary value returned by the modification+-- function.+--+-- If the 'put' phase of this function gives up due to apparently+-- being stuck in a conflict resolution loop, it will throw a+-- 'ResolutionFailure' exception.+modify :: (FromJSON a, ToJSON a, Resolvable a) =>+          Connection -> Bucket -> Key -> R -> W -> DW+       -> (Maybe a -> IO (a,b))+       -- ^ Modification function.  Called with 'Just' the value if+       -- the key is present, 'Nothing' otherwise.+       -> IO (a,b)+modify = R.modify J.get J.put+{-# INLINE modify #-}++-- | Modify a single value.  The value, if any, is retrieved using+-- 'get'; conflict resolution is performed if necessary.  The+-- modification function is called on the resulting value, and its+-- result is stored using 'put', which may again perform conflict+-- resolution.+--+-- The result of this function is whatever was returned by 'put'.+--+-- If the 'put' phase of this function gives up due to apparently+-- being stuck in a conflict resolution loop, it will throw a+-- 'ResolutionFailure' exception.+modify_ :: (FromJSON a, ToJSON a, Resolvable a) =>+           Connection -> Bucket -> Key -> R -> W -> DW+        -> (Maybe a -> IO a) -> IO a+modify_ = R.modify_ J.get J.put+{-# INLINE modify_ #-}+ -- | Store a single value, automatically resolving any vector clock -- conflicts that arise.  A single invocation of this function may -- involve several roundtrips to the server to resolve conflicts. ----- If a conflict arises, a winner will be chosen using 'mconcat', and+-- If a conflict arises, a winner will be chosen using 'resolve', and -- the winner will be stored; this will be repeated until no conflict--- occurs.+-- occurs or a (fairly large) number of retries has been attempted+-- without success. ----- The final value to be stored at the end of any conflict resolution--- is returned.-put :: (Eq c, FromJSON c, ToJSON c, Resolvable c) =>+-- If this function gives up due to apparently being stuck in a+-- conflict resolution loop, it will throw a 'ResolutionFailure'+-- exception.+put :: (FromJSON c, ToJSON c, Resolvable c) =>        Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW     -> IO (c, VClock) put = R.put J.put@@ -64,10 +111,15 @@ -- conflicts that arise.  A single invocation of this function may -- involve several roundtrips to the server to resolve conflicts. ----- If a conflict arises, a winner will be chosen using 'mconcat', and+-- If a conflict arises, a winner will be chosen using 'resolve', and -- the winner will be stored; this will be repeated until no conflict--- occurs.-put_ :: (Eq c, FromJSON c, ToJSON c, Resolvable c) =>+-- occurs or a (fairly large) number of retries has been attempted+-- without success.+--+-- If this function gives up due to apparently being stuck in a+-- conflict resolution loop, it will throw a 'ResolutionFailure'+-- exception.+put_ :: (FromJSON c, ToJSON c, Resolvable c) =>         Connection -> Bucket -> Key -> Maybe VClock -> c -> W -> DW      -> IO () put_ = R.put_ J.put@@ -78,12 +130,16 @@ -- roundtrips to the server to resolve conflicts. -- -- If any conflicts arise, a winner will be chosen in each case using--- 'mconcat', and the winners will be stored; this will be repeated--- until no conflicts occur.+-- 'resolve', and the winners will be stored; this will be repeated+-- until either no conflicts occur or a (fairly large) number of+-- retries has been attempted without success. -- -- For each original value to be stored, the final value that was -- stored at the end of any conflict resolution is returned.-putMany :: (Eq c, FromJSON c, ToJSON c, Resolvable c) =>+--+-- If this function gives up due to apparently being stuck in a loop,+-- it will throw a 'ResolutionFailure' exception.+putMany :: (FromJSON c, ToJSON c, Resolvable c) =>            Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW         -> IO [(c, VClock)] putMany = R.putMany J.putMany@@ -94,9 +150,13 @@ -- roundtrips to the server to resolve conflicts. -- -- If any conflicts arise, a winner will be chosen in each case using--- 'mconcat', and the winners will be stored; this will be repeated--- until no conflicts occur.-putMany_ :: (Eq c, FromJSON c, ToJSON c, Resolvable c) =>+-- 'resolve', and the winners will be stored; this will be repeated+-- until either no conflicts occur or a (fairly large) number of+-- retries has been attempted without success.+--+-- If this function gives up due to apparently being stuck in a loop,+-- it will throw a 'ResolutionFailure' exception.+putMany_ :: (FromJSON c, ToJSON c, Resolvable c) =>             Connection -> Bucket -> [(Key, Maybe VClock, c)] -> W -> DW          -> IO () putMany_ = R.putMany_ J.putMany
src/Network/Riak/Resolvable.hs view
@@ -12,6 +12,8 @@     (       Resolvable(..)     , ResolvableMonoid(..)+    , ResolutionFailure(..)     ) where -import Network.Riak.Resolvable.Internal (Resolvable(..), ResolvableMonoid(..))+import Network.Riak.Resolvable.Internal+    (ResolutionFailure(..), Resolvable(..), ResolvableMonoid(..))
src/Network/Riak/Resolvable/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE BangPatterns, DeriveDataTypeable, GeneralizedNewtypeDeriving #-} -- | -- Module:      Network.Riak.Resolvable.Internal -- Copyright:   (c) 2011 MailRank, Inc.@@ -8,31 +8,57 @@ -- Portability: portable -- -- Storage and retrieval of data with automatic conflict resolution.+--+-- The 'put' and 'putMany' functions will attempt to perform automatic+-- conflict resolution a large number of times.  If they give up due+-- to apparently being stuck in a loop, they will throw a+-- 'ResolutionFailure' exception.  module Network.Riak.Resolvable.Internal     (       Resolvable(..)     , ResolvableMonoid(..)+    , ResolutionFailure(..)     , get     , getMany+    , modify+    , modify_     , put     , put_     , putMany     , putMany_     ) where +import Control.Applicative ((<$>)) import Control.Arrow (first)+import Control.Exception (Exception, throwIO) import Control.Monad (unless) import Data.Aeson.Types (FromJSON, ToJSON) import Data.Data (Data) import Data.Either (partitionEithers) import Data.Function (on) import Data.List (foldl', sortBy)+import Data.Maybe (isJust) import Data.Monoid (Monoid(mappend)) import Data.Typeable (Typeable) import Network.Riak.Debug (debugValues) import Network.Riak.Types.Internal hiding (MessageTag(..)) +-- | Automated conflict resolution failed.+data ResolutionFailure = RetriesExceeded+    -- ^ Too many attempts were made to resolve a conflict, with each+    -- attempt resulting in another conflict.+    --+    -- The number of retries that the library will attempt is high+    -- (64). This makes it extremely unlikely that this exception will+    -- be thrown during normal application operation.  Instead, this+    -- exception is most likely to be thrown as a result of a bug in+    -- your application code, for example if your 'resolve' function+    -- is misbehaving.+                         deriving (Eq, Show, Typeable)++instance Exception ResolutionFailure+ -- | A type that can automatically resolve a vector clock conflict -- between two or more versions of a value. --@@ -48,7 +74,7 @@ -- If several conflicting siblings are found, 'resolve' will be -- applied over all of them using a fold, to yield a single -- \"winner\".-class (Eq a, Show a) => Resolvable a where+class (Show a) => Resolvable a where     -- | Resolve a conflict between two values.     resolve :: a -> a -> a @@ -67,9 +93,10 @@     resolve _          b        = b     {-# INLINE resolve #-} -get :: (Resolvable a) =>-       (Connection -> Bucket -> Key -> R -> IO (Maybe ([a], VClock)))-       -> (Connection -> Bucket -> Key -> R -> IO (Maybe (a, VClock)))+type Get a = Connection -> Bucket -> Key -> R -> IO (Maybe ([a], VClock))++get :: (Resolvable a) => Get a+    -> (Connection -> Bucket -> Key -> R -> IO (Maybe (a, VClock))) get doGet conn bucket key r =     fmap (first resolveMany) `fmap` doGet conn bucket key r {-# INLINE get #-}@@ -81,22 +108,40 @@     map (fmap (first resolveMany)) `fmap` doGet conn b ks r {-# INLINE getMany #-} -put :: (Resolvable a) =>-       (Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW-                   -> IO ([a], VClock))+-- If Riak receives a put request with no vclock, and the given+-- bucket+key already exists, it will treat the missing vclock as+-- stale, ignore the put request, and send back whatever values it+-- currently knows about.  The same problem will arise if we send a+-- vclock that really is stale, but that's much less likely to occur.+-- We handle the missing-vclock case in the single-body-response case+-- of both put and putMany below, but we do not (can not?) handle the+-- stale-vclock case.++type Put a = Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW+           -> IO ([a], VClock)++put :: (Resolvable a) => Put a     -> Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW     -> IO (a, VClock) put doPut conn bucket key mvclock0 val0 w dw = do-  let go val mvclock1 = do-        (xs, vclock) <- doPut conn bucket key mvclock1 val w dw+  let go !i val mvclock+         | i == maxRetries = throwIO RetriesExceeded+         | otherwise       = do+        (xs, vclock) <- doPut conn bucket key mvclock val w dw         case xs of-          []             -> return (val, vclock) -- not observed in the wild-          [v] | v == val -> return (val, vclock)-          ys             -> do debugValues "put" "conflict" ys-                               go (resolveMany' val ys) (Just vclock)-  go val0 mvclock0+          [x] | i > 0 || isJust mvclock -> return (x, vclock)+          (_:_) -> do debugValues "put" "conflict" xs+                      go (i+1) (resolveMany' val xs) (Just vclock)+          []    -> unexError "Network.Riak.Resolvable" "put"+                   "received empty response from server"+  go (0::Int) val0 mvclock0 {-# INLINE put #-} +-- | The maximum number of times to retry conflict resolution.+maxRetries :: Int+maxRetries = 64+{-# INLINE maxRetries #-}+ put_ :: (Resolvable a) =>         (Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW                     -> IO ([a], VClock))@@ -106,24 +151,47 @@     put doPut conn bucket key mvclock0 val0 w dw >> return () {-# INLINE put_ #-} +modify :: (Resolvable a) => Get a -> Put a+       -> Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO (a,b))+       -> IO (a,b)+modify doGet doPut conn bucket key r w dw act = do+  a0 <- get doGet conn bucket key r+  (a,b) <- act (fst <$> a0)+  (a',_) <- put doPut conn bucket key (snd <$> a0) a w dw+  return (a',b)+{-# INLINE modify #-}++modify_ :: (Resolvable a) => Get a -> Put a+        -> Connection -> Bucket -> Key -> R -> W -> DW -> (Maybe a -> IO a)+        -> IO a+modify_ doGet doPut conn bucket key r w dw act = do+  a0 <- get doGet conn bucket key r+  a <- act (fst <$> a0)+  fst <$> put doPut conn bucket key (snd <$> a0) a w dw+{-# INLINE modify_ #-}+ putMany :: (Resolvable a) =>            (Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW                        -> IO [([a], VClock)])         -> Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW         -> IO [(a, VClock)]-putMany doPut conn bucket puts0 w dw = go [] . zip [(0::Int)..] $ puts0 where-  go acc [] = return . map snd . sortBy (compare `on` fst) $ acc-  go acc puts = do+putMany doPut conn bucket puts0 w dw = go (0::Int) [] . zip [(0::Int)..] $ puts0+ where+  go _ acc [] = return . map snd . sortBy (compare `on` fst) $ acc+  go !i acc puts+      | i == maxRetries = throwIO RetriesExceeded+      | otherwise = do     rs <- doPut conn bucket (map snd puts) w dw     let (conflicts, ok) = partitionEithers $ zipWith mush puts rs     unless (null conflicts) $       debugValues "putMany" "conflicts" conflicts-    go (ok++acc) conflicts-  mush (i,(k,_,c)) (cs,v) =+    go (i+1) (ok++acc) conflicts+  mush (i,(k,mv,c)) (cs,v) =       case cs of-        []           -> Right (i,(c,v)) -- not observed in the wild-        [x] | x == c -> Right (i,(c,v))-        _            -> Left (i,(k,Just v, resolveMany' c cs))+        [x] | i > 0 || isJust mv -> Right (i,(x,v))+        (_:_) -> Left (i,(k,Just v, resolveMany' c cs))+        []    -> unexError "Network.Riak.Resolvable" "put"+                 "received empty response from server" {-# INLINE putMany #-}  putMany_ :: (Resolvable a) =>
src/Network/Riak/Types/Internal.hs view
@@ -22,6 +22,7 @@     , RiakException(excModule, excFunction, excMessage)     , netError     , typeError+    , unexError     -- * Data types     , Bucket     , Key@@ -84,13 +85,19 @@       excModule :: String     , excFunction :: String     , excMessage :: String-    } deriving (Eq, Typeable)+    } | UnexpectedResponse {+      excModule :: String+    , excFunction :: String+    , excMessage :: String+    }deriving (Eq, Typeable)  showRiakException :: RiakException -> String showRiakException exc@NetException{..} =     "Riak network error " ++ formatRiakException exc showRiakException exc@TypeException{..} =     "Riak type error " ++ formatRiakException exc+showRiakException exc@UnexpectedResponse{..} =+    "Riak server sent unexpected response " ++ formatRiakException exc  formatRiakException :: RiakException -> String formatRiakException exc =@@ -106,6 +113,9 @@  typeError :: String -> String -> String -> a typeError modu func msg = throw (TypeException modu func msg)++unexError :: String -> String -> String -> a+unexError modu func msg = throw (UnexpectedResponse modu func msg)  instance Show Connection where     show conn = show "Connection " ++ host c ++ ":" ++ port c
src/Network/Riak/Value.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings, RecordWildCards, StandaloneDeriving #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings, RecordWildCards,+    StandaloneDeriving #-}  -- | -- Module:      Network.Riak.Value@@ -79,12 +80,28 @@  deriving instance (IsContent a) => IsContent (ResolvableMonoid a) +-- | Store a single value.  This may return multiple conflicting+-- siblings.  Choosing among them, and storing a new value, is your+-- responsibility.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored. put :: (IsContent c) => Connection -> Bucket -> Key -> Maybe VClock -> c     -> W -> DW -> IO ([c], VClock) put conn bucket key mvclock val w dw =   putResp =<< exchange conn               (Req.put bucket key mvclock (toContent val) w dw True) +-- | Store many values.  This may return multiple conflicting siblings+-- for each value stored.  Choosing among them, and storing a new+-- value in each case, is your responsibility.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored. putMany :: (IsContent c) => Connection -> Bucket -> [(Key, Maybe VClock, c)]         -> W -> DW -> IO [([c], VClock)] putMany conn b puts w dw =@@ -98,16 +115,32 @@       c <- convert content       return (c, VClock s) +-- | Store a single value, without the possibility of conflict+-- resolution.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored, and you will not be notified. put_ :: (IsContent c) => Connection -> Bucket -> Key -> Maybe VClock -> c     -> W -> DW -> IO () put_ conn bucket key mvclock val w dw =   exchange_ conn (Req.put bucket key mvclock (toContent val) w dw False) +-- | Store many values, without the possibility of conflict+-- resolution.+--+-- You should /only/ supply 'Nothing' as a 'T.VClock' if you are sure+-- that the given bucket+key combination does not already exist.  If+-- you omit a 'T.VClock' but the bucket+key /does/ exist, your value+-- will not be stored, and you will not be notified. putMany_ :: (IsContent c) => Connection -> Bucket -> [(Key, Maybe VClock, c)]          -> W -> DW -> IO () putMany_ conn b puts w dw =   pipeline_ conn . map (\(k,v,c) -> Req.put b k v (toContent c) w dw False) $ puts +-- | Retrieve a value.  This may return multiple conflicting siblings.+-- Choosing among them is your responsibility. get :: (IsContent c) => Connection -> Bucket -> Key -> R     -> IO (Maybe ([c], VClock)) get conn bucket key r = getResp =<< exchangeMaybe conn (Req.get bucket key r)
src/Network/Riak/Value/Resolvable.hs view
@@ -10,24 +10,30 @@ -- 'V.IsContent' typeclass.  This provides access to more of Riak's -- storage features than JSON, e.g. links. ----- Functions automatically resolve conflicts using 'Resolvable' instances.--- For instance, if a 'get' returns three siblings, a winner will be--- chosen using 'mconcat'.  If a 'put' results in a conflict, a winner--- will be chosen using 'mconcat', and the winner will be 'put'; this--- will be repeated until no conflict occurs.+-- Functions automatically resolve conflicts using 'Resolvable'+-- instances.  For instance, if a 'get' returns three siblings, a+-- winner will be chosen using 'resolve'.  If a 'put' results in a+-- conflict, a winner will be chosen using 'resolve', and the winner+-- will be 'put'; this will be repeated until either no conflict+-- occurs or the process has been repeated too many times.  module Network.Riak.Value.Resolvable     (       V.IsContent(..)+    , Resolvable(..)+    , ResolutionFailure(..)     , get     , getMany+    , modify+    , modify_+    -- * Low-level modification functions     , put     , put_     , putMany     , putMany_     ) where -import Network.Riak.Resolvable.Internal (Resolvable)+import Network.Riak.Resolvable.Internal (ResolutionFailure(..), Resolvable(..)) import Network.Riak.Types.Internal hiding (MessageTag(..)) import qualified Network.Riak.Resolvable.Internal as R import qualified Network.Riak.Value as V@@ -46,17 +52,58 @@ getMany = R.getMany V.getMany {-# INLINE getMany #-} +-- | Modify a single value.  The value, if any, is retrieved using+-- 'get'; conflict resolution is performed if necessary.  The+-- modification function is called on the resulting value, and its+-- result is stored using 'put', which may again perform conflict+-- resolution.+--+-- The result of this function is whatever was returned by 'put',+-- along with the auxiliary value returned by the modification+-- function.+--+-- If the 'put' phase of this function gives up due to apparently+-- being stuck in a conflict resolution loop, it will throw a+-- 'ResolutionFailure' exception.+modify :: (Resolvable a, V.IsContent a) =>+          Connection -> Bucket -> Key -> R -> W -> DW+       -> (Maybe a -> IO (a,b))+       -- ^ Modification function.  Called with 'Just' the value if+       -- the key is present, 'Nothing' otherwise.+       -> IO (a,b)+modify = R.modify V.get V.put+{-# INLINE modify #-}++-- | Modify a single value.  The value, if any, is retrieved using+-- 'get'; conflict resolution is performed if necessary.  The+-- modification function is called on the resulting value, and its+-- result is stored using 'put', which may again perform conflict+-- resolution.+--+-- The result of this function is whatever was returned by 'put'.+--+-- If the 'put' phase of this function gives up due to apparently+-- being stuck in a conflict resolution loop, it will throw a+-- 'ResolutionFailure' exception.+modify_ :: (Resolvable a, V.IsContent a) =>+           Connection -> Bucket -> Key -> R -> W -> DW+        -> (Maybe a -> IO a) -> IO a+modify_ = R.modify_ V.get V.put+{-# INLINE modify_ #-}+ -- | Store a single value, automatically resolving any vector clock -- conflicts that arise.  A single invocation of this function may -- involve several roundtrips to the server to resolve conflicts. ----- If a conflict arises, a winner will be chosen using 'mconcat', and+-- If a conflict arises, a winner will be chosen using 'resolve', and -- the winner will be stored; this will be repeated until no conflict--- occurs.+-- occurs or a (fairly large) number of retries has been attempted+-- without success. ----- The final value to be stored at the end of any conflict resolution--- is returned.-put :: (Eq a, Resolvable a, V.IsContent a) =>+-- If this function gives up due to apparently being stuck in a+-- conflict resolution loop, it will throw a 'ResolutionFailure'+-- exception.+put :: (Resolvable a, V.IsContent a) =>        Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW     -> IO (a, VClock) put = R.put V.put @@ -66,10 +113,15 @@ -- conflicts that arise.  A single invocation of this function may -- involve several roundtrips to the server to resolve conflicts. ----- If a conflict arises, a winner will be chosen using 'mconcat', and+-- If a conflict arises, a winner will be chosen using 'resolve', and -- the winner will be stored; this will be repeated until no conflict--- occurs.-put_ :: (Eq a, Resolvable a, V.IsContent a) =>+-- occurs or a (fairly large) number of retries has been attempted+-- without success.+--+-- If this function gives up due to apparently being stuck in a+-- conflict resolution loop, it will throw a 'ResolutionFailure'+-- exception.+put_ :: (Resolvable a, V.IsContent a) =>         Connection -> Bucket -> Key -> Maybe VClock -> a -> W -> DW      -> IO () put_ = R.put_ V.put @@ -80,12 +132,16 @@ -- roundtrips to the server to resolve conflicts. -- -- If any conflicts arise, a winner will be chosen in each case using--- 'mconcat', and the winners will be stored; this will be repeated--- until no conflicts occur.+-- 'resolve', and the winners will be stored; this will be repeated+-- until either no conflicts occur or a (fairly large) number of+-- retries has been attempted without success. -- -- For each original value to be stored, the final value that was -- stored at the end of any conflict resolution is returned.-putMany :: (Eq a, Resolvable a, V.IsContent a) =>+--+-- If this function gives up due to apparently being stuck in a loop,+-- it will throw a 'ResolutionFailure' exception.+putMany :: (Resolvable a, V.IsContent a) =>            Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW         -> IO [(a, VClock)] putMany = R.putMany V.putMany@@ -96,9 +152,13 @@ -- roundtrips to the server to resolve conflicts. -- -- If any conflicts arise, a winner will be chosen in each case using--- 'mconcat', and the winners will be stored; this will be repeated--- until no conflicts occur.-putMany_ :: (Eq a, Resolvable a, V.IsContent a) =>+-- 'resolve', and the winners will be stored; this will be repeated+-- until either no conflicts occur or a (fairly large) number of+-- retries has been attempted without success.+--+-- If this function gives up due to apparently being stuck in a loop,+-- it will throw a 'ResolutionFailure' exception.+putMany_ :: (Resolvable a, V.IsContent a) =>             Connection -> Bucket -> [(Key, Maybe VClock, a)] -> W -> DW -> IO () putMany_ = R.putMany_ V.putMany {-# INLINE putMany_ #-}