riak 0.7.2.1 → 0.8.0.0
raw patch · 11 files changed
+212/−27 lines, 11 filesdep +HUnitdep +test-framework-hunitdep ~basedep ~riak-protobufdep ~textPVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, test-framework-hunit
Dependency ranges changed: base, riak-protobuf, text
API changes (from Hackage documentation)
+ Network.Riak: IndexBin :: !Index -> !ByteString -> IndexValue
+ Network.Riak: IndexInt :: !Index -> !Int -> IndexValue
+ Network.Riak: IndexQueryExactBin :: !Index -> !ByteString -> IndexQuery
+ Network.Riak: IndexQueryExactInt :: !Index -> !Int -> IndexQuery
+ Network.Riak: IndexQueryRangeBin :: !Index -> !ByteString -> !ByteString -> IndexQuery
+ Network.Riak: IndexQueryRangeInt :: !Index -> !Int -> !Int -> IndexQuery
+ Network.Riak: addIndexes :: [IndexValue] -> Content -> Content
+ Network.Riak: data IndexQuery
+ Network.Riak: data IndexValue
+ Network.Riak: getByIndex :: Connection -> Bucket -> IndexQuery -> IO [Key]
+ Network.Riak: putIndexed :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> [IndexValue] -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
+ Network.Riak.JSON: putIndexed :: (FromJSON c, ToJSON c) => Connection -> Bucket -> Key -> [IndexValue] -> Maybe VClock -> c -> W -> DW -> IO ([c], VClock)
+ Network.Riak.JSON.Resolvable: putIndexed :: (FromJSON c, ToJSON c, Resolvable c) => Connection -> Bucket -> Key -> [IndexValue] -> Maybe VClock -> c -> W -> DW -> IO (c, VClock)
+ Network.Riak.Request: data IndexRequest :: *
+ Network.Riak.Request: getByIndex :: Bucket -> IndexQuery -> IndexRequest
+ Network.Riak.Types: IndexBin :: !Index -> !ByteString -> IndexValue
+ Network.Riak.Types: IndexInt :: !Index -> !Int -> IndexValue
+ Network.Riak.Types: IndexQueryExactBin :: !Index -> !ByteString -> IndexQuery
+ Network.Riak.Types: IndexQueryExactInt :: !Index -> !Int -> IndexQuery
+ Network.Riak.Types: IndexQueryRangeBin :: !Index -> !ByteString -> !ByteString -> IndexQuery
+ Network.Riak.Types: IndexQueryRangeInt :: !Index -> !Int -> !Int -> IndexQuery
+ Network.Riak.Types: IndexRequest :: MessageTag
+ Network.Riak.Types: IndexResponse :: MessageTag
+ Network.Riak.Types: data IndexQuery
+ Network.Riak.Types: data IndexValue
+ Network.Riak.Value: addIndexes :: [IndexValue] -> Content -> Content
+ Network.Riak.Value: getByIndex :: Connection -> Bucket -> IndexQuery -> IO [Key]
+ Network.Riak.Value: putIndexed :: IsContent c => Connection -> Bucket -> Key -> [IndexValue] -> Maybe VClock -> c -> W -> DW -> IO ([c], VClock)
Files
- riak.cabal +16/−8
- src/Network/Riak.hs +12/−2
- src/Network/Riak/JSON.hs +9/−0
- src/Network/Riak/JSON/Resolvable.hs +12/−0
- src/Network/Riak/Request.hs +41/−1
- src/Network/Riak/Resolvable/Internal.hs +1/−1
- src/Network/Riak/Tag.hs +19/−1
- src/Network/Riak/Types.hs +2/−0
- src/Network/Riak/Types/Internal.hs +21/−0
- src/Network/Riak/Value.hs +38/−0
- tests/Properties.hs +41/−14
riak.cabal view
@@ -1,5 +1,5 @@ name: riak-version: 0.7.2.1+version: 0.8.0.0 synopsis: A Haskell client for the Riak decentralized data store description: A Haskell client library for the Riak decentralized data@@ -49,7 +49,12 @@ flag developer description: operate in developer mode default: False+ manual: True +flag test2i+ description: test secondary indexes (requires default riak engine to have that support)+ default: False+ library hs-source-dirs: src @@ -92,7 +97,7 @@ protocol-buffers-fork == 2.0.*, pureMD5, random,- riak-protobuf == 0.18.*,+ riak-protobuf == 0.19.*, text >= 0.11.0.6, time @@ -112,14 +117,17 @@ hs-source-dirs: tests ghc-options: -Wall + if flag(test2i)+ cpp-options: -DTEST2I+ build-depends:- riak, base,+ riak, bytestring,+ containers,+ HUnit, QuickCheck, test-framework,- test-framework-quickcheck2--source-repository head- type: git- location: http://github.com/markhibberd/riak-haskell-client+ test-framework-hunit,+ test-framework-quickcheck2,+ text
src/Network/Riak.hs view
@@ -34,7 +34,9 @@ -- This is the most demanding module to work with, as you must encode -- and decode data yourself, and handle all conflict resolution -- yourself.-+--+-- A short getting started guide is available at <http://docs.basho.com/riak/latest/dev/taste-of-riak/haskell/>+-- module Network.Riak ( -- * Client configuration and identification@@ -53,11 +55,14 @@ , Resolvable(..) , get , getMany+ , getByIndex+ , addIndexes , modify , modify_ , delete -- ** Low-level modification functions , put+ , putIndexed , putMany -- * Metadata , listBuckets@@ -66,8 +71,13 @@ , setBucket -- * Map/reduce , mapReduce+ -- * Types+ , IndexQuery(..)+ , IndexValue(..) ) where import Network.Riak.Basic hiding (get, put, put_)-import Network.Riak.JSON.Resolvable (get, getMany, modify, modify_, put, putMany)+import Network.Riak.JSON.Resolvable (get, getMany, modify, modify_, put, putIndexed, putMany) import Network.Riak.Resolvable (Resolvable(..))+import Network.Riak.Value (getByIndex, addIndexes)+import Network.Riak.Types.Internal (IndexQuery(..), IndexValue(..))
src/Network/Riak/JSON.hs view
@@ -20,6 +20,7 @@ , get , getMany , put+ , putIndexed , put_ , putMany , putMany_@@ -77,6 +78,14 @@ -> 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 indexed.+putIndexed :: (FromJSON c, ToJSON c)+ => Connection -> Bucket -> Key -> [IndexValue]+ -> Maybe VClock -> c+ -> W -> DW -> IO ([c], VClock)+putIndexed conn bucket key ixs mvclock val w dw =+ convert <$> V.putIndexed conn bucket key ixs mvclock (json val) w dw -- | Store a single value, without the possibility of conflict -- resolution.
src/Network/Riak/JSON/Resolvable.hs view
@@ -25,6 +25,7 @@ , modify_ -- * Low-level modification functions , put+ , putIndexed , put_ , putMany , putMany_@@ -106,6 +107,17 @@ -> IO (c, VClock) put = R.put J.put {-# INLINE put #-}++-- | Store a single value indexed.+putIndexed :: (FromJSON c, ToJSON c, Resolvable c)+ => Connection -> Bucket -> Key -> [IndexValue]+ -> Maybe VClock -> c -> W -> DW+ -> IO (c, VClock)+putIndexed c b k ixs vc cont w' dw' =+ R.put (\conn bucket key mvclock val w dw ->+ J.putIndexed conn bucket key ixs mvclock val w dw)+ c b k vc cont w' dw'+{-# INLINE putIndexed #-} -- | Store a single value, automatically resolving any vector clock -- conflicts that arise. A single invocation of this function may
src/Network/Riak/Request.hs view
@@ -24,6 +24,8 @@ -- * Data management , Get.GetRequest , get+ , getByIndex+ , Index.IndexRequest , Put.PutRequest , put , Del.DeleteRequest@@ -45,7 +47,9 @@ ) where import Control.Applicative ((<$>))-import Network.Riak.Protocol.BucketProps+import qualified Data.ByteString.Char8 as B8+import Data.Monoid+import Network.Riak.Protocol.BucketProps hiding (r,rw) import Network.Riak.Protocol.Content import Network.Riak.Protocol.GetClientIDRequest import Network.Riak.Protocol.GetServerInfoRequest@@ -58,6 +62,8 @@ import qualified Network.Riak.Protocol.Link as Link import qualified Network.Riak.Protocol.GetBucketRequest as GetBucket import qualified Network.Riak.Protocol.GetRequest as Get+import qualified Network.Riak.Protocol.IndexRequest as Index+import qualified Network.Riak.Protocol.IndexRequest.IndexQueryType as IndexQueryType import qualified Network.Riak.Protocol.ListKeysRequest as Keys import qualified Network.Riak.Protocol.PutRequest as Put import qualified Network.Riak.Protocol.SetBucketRequest as SetBucket@@ -93,6 +99,40 @@ , Get.n_val = Nothing } {-# INLINE get #-}++-- | Create a secondary index request. Bucket, key and index names and+-- values are URL-escaped.+getByIndex :: Bucket -> IndexQuery+ -> Index.IndexRequest+getByIndex bucket q =+ case q of+ IndexQueryExactInt index key ->+ req (index <> "_int") (showIntKey key)+ IndexQueryType.Eq Nothing Nothing+ IndexQueryExactBin index key ->+ req (index <> "_bin") (showBsKey $ key)+ IndexQueryType.Eq Nothing Nothing+ IndexQueryRangeInt index from to ->+ req (index <> "_int") Nothing+ IndexQueryType.Range (showIntKey from) (showIntKey to)+ IndexQueryRangeBin index from to ->+ req (index <> "_bin") Nothing+ IndexQueryType.Range (showBsKey from) (showBsKey to)+ where+ showIntKey = Just . escape . B8.pack . show+ showBsKey = Just . escape+ req i k qt rmin rmax =+ Index.IndexRequest { Index.bucket = escape bucket+ , Index.index = escape i+ , Index.qtype = qt+ , Index.key = k+ , Index.range_min = rmin+ , Index.range_max = rmax+ , Index.return_terms = Nothing+ , Index.stream = Nothing+ , Index.max_results = Nothing+ , Index.continuation = Nothing+ , Index.timeout = Nothing } -- | Create a put request. The bucket and key names are URL-escaped. -- Any 'Link' values inside the 'Content' are assumed to have been
src/Network/Riak/Resolvable/Internal.hs view
@@ -186,7 +186,7 @@ unless (null conflicts) $ debugValues "putMany" "conflicts" conflicts go (i+1) (ok++acc) conflicts- mush (i,(k,mv,c)) (cs,v) =+ mush (i,(k,mv,_)) (cs,v) = case cs of [x] | i > 0 || isJust mv -> Right (i,(x,v)) (_:_) -> Left (i,(k,Just v, resolveMany cs))
src/Network/Riak/Tag.hs view
@@ -26,6 +26,8 @@ import Network.Riak.Protocol.GetClientIDResponse import Network.Riak.Protocol.GetRequest import Network.Riak.Protocol.GetResponse+import Network.Riak.Protocol.IndexRequest+import Network.Riak.Protocol.IndexResponse import Network.Riak.Protocol.GetServerInfoRequest import Network.Riak.Protocol.ListBucketsRequest import Network.Riak.Protocol.ListBucketsResponse@@ -100,18 +102,34 @@ messageTag _ = Types.GetRequest {-# INLINE messageTag #-} +instance Tagged IndexRequest where+ messageTag _ = Types.IndexRequest+ {-# INLINE messageTag #-}+ instance Request GetRequest where expectedResponse _ = Types.GetResponse {-# INLINE expectedResponse #-} +instance Request IndexRequest where+ expectedResponse _ = Types.IndexResponse+ {-# INLINE expectedResponse #-}+ instance Tagged GetResponse where messageTag _ = Types.GetResponse {-# INLINE messageTag #-} +instance Tagged IndexResponse where+ messageTag _ = Types.IndexResponse+ {-# INLINE messageTag #-}+ instance Response GetResponse +instance Response IndexResponse+ instance Exchange GetRequest GetResponse +instance Exchange IndexRequest IndexResponse+ instance Tagged PutRequest where messageTag _ = Types.PutRequest {-# INLINE messageTag #-}@@ -213,7 +231,7 @@ getTag :: Get MessageTag getTag = do n <- getWord8- if n > 24+ if n > 26 then moduleError "getTag" $ "invalid riak message code: " ++ show n else return . toEnum . fromIntegral $ n {-# INLINE getTag #-}
src/Network/Riak/Types.hs view
@@ -35,6 +35,8 @@ , Exchange , MessageTag(..) , Tagged(..)+ , IndexValue(..)+ , IndexQuery(..) ) where import Network.Riak.Types.Internal
src/Network/Riak/Types/Internal.hs view
@@ -26,6 +26,9 @@ -- * Data types , Bucket , Key+ , Index+ , IndexQuery(..)+ , IndexValue(..) , Tag , VClock(..) , Job(..)@@ -130,6 +133,22 @@ -- buckets. type Key = ByteString +-- | Name of a secondary index+type Index = ByteString++-- | Index query. Can be exact or range, int or bin. Index name should+-- not contain the "_bin" or "_int" part, since it's determined from+-- data constructor.+data IndexQuery = IndexQueryExactInt !Index !Int+ | IndexQueryExactBin !Index !ByteString+ | IndexQueryRangeInt !Index !Int !Int+ | IndexQueryRangeBin !Index !ByteString !ByteString+ deriving (Show, Eq)++data IndexValue = IndexInt !Index !Int+ | IndexBin !Index !ByteString+ deriving (Show, Eq)+ -- | An application-specific identifier for a link. See -- <http://wiki.basho.com/Links.html> for details. type Tag = ByteString@@ -166,6 +185,8 @@ | SetBucketResponse | MapReduceRequest | MapReduceResponse+ | IndexRequest+ | IndexResponse deriving (Eq, Show, Enum, Typeable) -- | Messages are tagged.
src/Network/Riak/Value.hs view
@@ -21,7 +21,10 @@ , fromContent , get , getMany+ , getByIndex+ , addIndexes , put+ , putIndexed , put_ , putMany , putMany_@@ -30,17 +33,21 @@ import Control.Applicative import Data.Aeson.Types (Parser, Result(..), parse) import Data.Foldable (toList)+import Data.Monoid ((<>)) import Network.Riak.Connection.Internal import Network.Riak.Protocol.Content (Content(..)) import Network.Riak.Protocol.GetResponse (GetResponse(..))+import Network.Riak.Protocol.IndexResponse (IndexResponse(..)) import Network.Riak.Protocol.PutResponse (PutResponse(..)) import Network.Riak.Resolvable (ResolvableMonoid(..)) import Network.Riak.Response (unescapeLinks) import Network.Riak.Types.Internal hiding (MessageTag(..))+import qualified Network.Riak.Protocol.Pair as Pair import qualified Data.Aeson.Parser as Aeson import qualified Data.Aeson.Types as Aeson import qualified Data.Attoparsec.Lazy as A import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as CL8 import qualified Data.Sequence as Seq import qualified Network.Riak.Content as C import qualified Network.Riak.Request as Req@@ -80,6 +87,15 @@ deriving instance (IsContent a) => IsContent (ResolvableMonoid a) +-- | Add indexes to a content value for a further put request.+addIndexes :: [IndexValue] -> Content -> Content+addIndexes ix c =+ c { C.indexes = Seq.fromList . map toPair $ ix }+ where+ toPair (IndexInt k v) = Pair.Pair (k <> "_int")+ (Just . CL8.pack . show $ v)+ toPair (IndexBin k v) = Pair.Pair (k <> "_bin") (Just v)+ -- | Store a single value. This may return multiple conflicting -- siblings. Choosing among them, and storing a new value, is your -- responsibility.@@ -94,6 +110,16 @@ putResp =<< exchange conn (Req.put bucket key mvclock (toContent val) w dw True) +-- | Store an indexed value.+putIndexed :: (IsContent c) => Connection -> Bucket -> Key+ -> [IndexValue]+ -> Maybe VClock -> c+ -> W -> DW -> IO ([c], VClock)+putIndexed conn b k inds mvclock val w dw =+ putResp =<< exchange conn+ (Req.put b k mvclock (addIndexes inds (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.@@ -145,6 +171,12 @@ -> IO (Maybe ([c], VClock)) get conn bucket key r = getResp =<< exchangeMaybe conn (Req.get bucket key r) +-- | Retrieve list of keys matching some index query.+getByIndex :: Connection -> Bucket -> IndexQuery+ -> IO [Key]+getByIndex conn b indq =+ getByIndexResp =<< exchangeMaybe conn (Req.getByIndex b indq)+ getMany :: (IsContent c) => Connection -> Bucket -> [Key] -> R -> IO [Maybe ([c], VClock)] getMany conn b ks r =@@ -157,6 +189,12 @@ c <- convert content return $ Just (c, VClock s) _ -> return Nothing++getByIndexResp :: Maybe IndexResponse -> IO [Key]+getByIndexResp resp =+ case resp of+ Just (IndexResponse keys _ _ _) -> return (toList keys)+ Nothing -> return [] convert :: IsContent v => Seq.Seq Content -> IO [v] convert = go [] [] . toList
tests/Properties.hs view
@@ -1,20 +1,30 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+ import Control.Applicative ((<$>)) import Control.Exception (finally) import Control.Monad (forM_) import Data.IORef (IORef, modifyIORef, newIORef, readIORef)+import Data.Text (Text)+import Network.Riak (getByIndex) import Network.Riak.Connection (defaultClient) import Network.Riak.Connection.Pool (Pool, create, withConnection) import Network.Riak.Content (binary)-import Network.Riak.Types (Bucket, Key, Quorum(..))+import Network.Riak.Types (Bucket, Key, Quorum(..), IndexValue(..),+ IndexQuery(..))+import Network.Riak.Resolvable (ResolvableMonoid(..)) import System.IO.Unsafe (unsafePerformIO)-import Test.Framework (defaultMain, testGroup)+import qualified Test.HUnit as HU+import Test.Framework (defaultMain, Test) import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.Framework.Providers.HUnit (testCase) import Test.QuickCheck (Arbitrary(..), (==>))+import Test.QuickCheck.Property (Property) import Test.QuickCheck.Monadic (assert, monadicIO, run)+import qualified Data.Map as M import qualified Data.ByteString.Lazy as L import qualified Network.Riak.Basic as B import qualified Network.Riak.JSON as J-import qualified Network.Riak.Value as V instance Arbitrary L.ByteString where arbitrary = L.pack `fmap` arbitrary@@ -28,21 +38,38 @@ pool = unsafePerformIO $ create defaultClient 1 1 1 -t_put_get b k v = notempty b && notempty k ==> monadicIO $ assert . uncurry (==) =<< run act- where- act = withConnection pool $ \c -> do- modifyIORef cruft ((b,k):)- p <- Just <$> B.put c b k Nothing (binary v) Default Default- r <- B.get c b k Default- return (p,r)- notempty = not . L.null+t_put_get :: Bucket -> Key -> L.ByteString -> Property+t_put_get b k v =+ notempty b && notempty k ==> monadicIO $ assert . uncurry (==) =<< run act+ where+ act = withConnection pool $ \c -> do+ modifyIORef cruft ((b,k):)+ p <- Just <$> B.put c b k Nothing (binary v) Default Default+ r <- B.get c b k Default+ return (p,r)+ notempty = not . L.null +t_indexed_put_get :: HU.Assertion+t_indexed_put_get = withConnection pool $ \c -> do+ let b = "riak-haskell-client-test"+ k = "test"+ _ <- J.putIndexed c b k [(IndexInt "someindex" 135)] Nothing+ (RM (M.fromList [("somekey", "someval")] :: M.Map Text Text))+ Default Default+ keys <- getByIndex c b (IndexQueryExactInt "someindex" 135)+ HU.assertEqual "" ["test"] keys++main :: IO () main = defaultMain tests `finally` cleanup where cleanup = withConnection pool $ \c -> do bks <- readIORef cruft forM_ bks $ \(b,k) -> B.delete c b k Default -tests = [- testProperty "t_put_get" t_put_get- ]+tests :: [Test]+tests =+ [ testProperty "t_put_get" t_put_get+#ifdef TEST2I+ , testCase "t_indexed_put_get" t_indexed_put_get+#endif+ ]