monarch 0.1.0.0 → 0.2.0.0
raw patch · 3 files changed
+739/−102 lines, 3 filesdep +binarydep +conduitdep +network-conduitdep −tokyotyrant-haskellPVP ok
version bump matches the API change (PVP)
Dependencies added: binary, conduit, network-conduit
Dependencies removed: tokyotyrant-haskell
API changes (from Hackage documentation)
- Database.Monarch: delete :: Key -> Monarch ()
- Database.Monarch: fwmkeys :: ByteString -> Maybe Int -> Monarch [Key]
- Database.Monarch: instance MonadError String Monarch
- Database.Monarch: instance MonadReader Connection Monarch
- Database.Monarch: mget :: [Key] -> Monarch [(Key, Value)]
- Database.Monarch: mput :: [(Key, Value)] -> Monarch ()
- Database.Monarch: type Host = ByteString
- Database.Monarch: type Key = ByteString
- Database.Monarch: type Port = Int
- Database.Monarch: type Value = ByteString
+ Database.Monarch: ConnectionRefused :: Code
+ Database.Monarch: ConsistencyChecking :: RestoreOption
+ Database.Monarch: ExistingRecord :: Code
+ Database.Monarch: GlobalLocking :: ExtOption
+ Database.Monarch: HostNotFound :: Code
+ Database.Monarch: InvalidOperation :: Code
+ Database.Monarch: MiscellaneousError :: Code
+ Database.Monarch: NoRecordFound :: Code
+ Database.Monarch: NoUpdateLog :: MiscOption
+ Database.Monarch: ReceiveError :: Code
+ Database.Monarch: RecordLocking :: ExtOption
+ Database.Monarch: SendError :: Code
+ Database.Monarch: Success :: Code
+ Database.Monarch: addDouble :: ByteString -> Double -> Monarch Double
+ Database.Monarch: addInt :: ByteString -> Int -> Monarch Int
+ Database.Monarch: copy :: ByteString -> Monarch ()
+ Database.Monarch: data Code
+ Database.Monarch: data ExtOption
+ Database.Monarch: data MiscOption
+ Database.Monarch: data RestoreOption
+ Database.Monarch: ext :: ByteString -> [ExtOption] -> ByteString -> ByteString -> Monarch ByteString
+ Database.Monarch: forwardMatchingKeys :: ByteString -> Int -> Monarch [ByteString]
+ Database.Monarch: instance BitFlag32 ExtOption
+ Database.Monarch: instance BitFlag32 MiscOption
+ Database.Monarch: instance BitFlag32 RestoreOption
+ Database.Monarch: instance Eq Code
+ Database.Monarch: instance Error Code
+ Database.Monarch: instance MonadError Code Monarch
+ Database.Monarch: instance Show Code
+ Database.Monarch: iterInit :: Monarch ()
+ Database.Monarch: iterNext :: Monarch (Maybe ByteString)
+ Database.Monarch: misc :: ByteString -> [MiscOption] -> [ByteString] -> Monarch [ByteString]
+ Database.Monarch: multipleGet :: [ByteString] -> Monarch [(ByteString, ByteString)]
+ Database.Monarch: optimize :: ByteString -> Monarch ()
+ Database.Monarch: out :: ByteString -> Monarch ()
+ Database.Monarch: putCat :: ByteString -> ByteString -> Monarch ()
+ Database.Monarch: putNoResponse :: ByteString -> ByteString -> Monarch ()
+ Database.Monarch: putShiftLeft :: ByteString -> ByteString -> Int -> Monarch ()
+ Database.Monarch: recordNum :: Monarch Int64
+ Database.Monarch: restore :: Integral a => ByteString -> a -> [RestoreOption] -> Monarch ()
+ Database.Monarch: setMaster :: Integral a => ByteString -> Int -> a -> [RestoreOption] -> Monarch ()
+ Database.Monarch: size :: Monarch Int64
+ Database.Monarch: status :: Monarch ByteString
+ Database.Monarch: sync :: Monarch ()
+ Database.Monarch: valueSize :: ByteString -> Monarch (Maybe Int)
- Database.Monarch: get :: Key -> Monarch (Maybe Value)
+ Database.Monarch: get :: ByteString -> Monarch (Maybe ByteString)
- Database.Monarch: put :: Key -> Value -> Monarch ()
+ Database.Monarch: put :: ByteString -> ByteString -> Monarch ()
- Database.Monarch: putKeep :: Key -> Value -> Monarch ()
+ Database.Monarch: putKeep :: ByteString -> ByteString -> Monarch ()
- Database.Monarch: runMonarch :: MonadIO m => Host -> Port -> Monarch a -> m (Either String a)
+ Database.Monarch: runMonarch :: MonadIO m => String -> Int -> Monarch a -> m (Either Code a)
Files
- Database/Monarch.hs +501/−80
- monarch.cabal +7/−3
- test/test.hs +231/−19
Database/Monarch.hs view
@@ -1,111 +1,532 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}--- | This module makes tokyotyrant-haskell monadic.+-- | This module provide TokyoTyrant monadic access interface.+--+-- TokyoTyrant Original Binary Protocol(<http://fallabs.com/tokyotyrant/spex.html#protocol>) is implemented. module Database.Monarch (- -- * Basic Types- Key, Value, Host, Port- -- * TokyoTyrant DB action monad and its runner- , Monarch, runMonarch- -- * Fetch/store single key/value- , get, put, putKeep- -- * Fetch/store multiple key/value- , mget, mput- -- * Delete key/value- , delete, vanish- -- * Search key prefix- , fwmkeys+ Monarch, runMonarch+ , ExtOption(..), RestoreOption(..), MiscOption(..)+ , Code(..)+ , put, putKeep, putCat, putShiftLeft+ , putNoResponse+ , out+ , get, multipleGet+ , valueSize+ , iterInit, iterNext+ , forwardMatchingKeys+ , addInt, addDouble+ , ext, sync, optimize, vanish, copy, restore+ , setMaster+ , recordNum, size+ , status+ , misc ) where -import qualified Database.TokyoTyrant.FFI as TT-import Data.ByteString+import Data.Bits+import Data.Int+import Data.Conduit+import Data.Conduit.Network+import qualified Data.Conduit.Binary as CB+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS import Control.Applicative import Control.Monad.Reader import Control.Monad.Error-import Control.Exception+import Data.IORef+import qualified Data.Binary as B+import Data.Binary.Put (runPut, putWord32be, putByteString)+import Data.Binary.Get (runGet, getWord32be) -type Key = ByteString+type TTPipe =+ Pipe BS.ByteString BS.ByteString BS.ByteString () IO -type Value = ByteString+-- | Error code+data Code = Success+ | InvalidOperation+ | HostNotFound+ | ConnectionRefused+ | SendError+ | ReceiveError+ | ExistingRecord+ | NoRecordFound+ | MiscellaneousError+ deriving (Eq, Show) -type Host = ByteString+instance Error Code -type Port = Int+class BitFlag32 a where+ fromOption :: a -> Int32 +-- | Options for scripting extension+data ExtOption = RecordLocking -- ^ record locking+ | GlobalLocking -- ^ global locking++instance BitFlag32 ExtOption where+ fromOption RecordLocking = 0x1+ fromOption GlobalLocking = 0x2++-- | Options for restore+data RestoreOption = ConsistencyChecking -- ^ consistency checking++instance BitFlag32 RestoreOption where+ fromOption ConsistencyChecking = 0x1++-- | Options for miscellaneous operation+data MiscOption = NoUpdateLog -- ^ omission of update log++instance BitFlag32 MiscOption where+ fromOption NoUpdateLog = 0x1++toCode :: Int -> Code+toCode 0 = Success+toCode 1 = InvalidOperation+toCode 2 = HostNotFound+toCode 3 = ConnectionRefused+toCode 4 = SendError+toCode 5 = ReceiveError+toCode 6 = ExistingRecord+toCode 7 = NoRecordFound+toCode 9999 = MiscellaneousError+toCode _ = error "Invalid Code"++putMagic :: B.Word8+ -> B.Put+putMagic magic = B.putWord8 0xC8 >> B.putWord8 magic++putOptions :: BitFlag32 option =>+ [option]+ -> B.Put+putOptions = putWord32be . fromIntegral .+ foldl (.|.) 0 . map fromOption++ -- | A monad supporting TokyoTyrant access.-newtype Monarch a = Monarch { unMonarch :: ErrorT String (ReaderT TT.Connection IO) a}+newtype Monarch a =+ Monarch { unMonarch :: ErrorT Code TTPipe a } deriving ( Functor, Applicative, Monad, MonadIO- , MonadReader TT.Connection, MonadError String )+ , MonadError Code ) -- | Run Monarch with TokyoTyrant at target host and port. runMonarch :: MonadIO m =>- Host -- ^ host- -> Port -- ^ port- -> Monarch a -- ^ action- -> m (Either String a)-runMonarch host port monarch = liftIO $ bracket open' close' action+ String+ -> Int+ -> Monarch a+ -> m (Either Code a)+runMonarch host port action =+ liftIO $ do+ result <- liftIO $ newIORef $ Left Success+ let remote = ClientSettings port host+ runTCPClient remote $ client action result+ readIORef result++client :: Monarch a+ -> IORef (Either Code a)+ -> Application IO+client action result src sink = src $$ conduit =$ sink where- action = (return . Left) `either` (runReaderT . runErrorT . unMonarch $ monarch)- open' = TT.open host port- close' = (const $ return ()) `either` TT.close+ conduit = runErrorT (unMonarch action) >>=+ liftIO . writeIORef result -liftMonarch :: (MonadIO m, MonadError a m) => IO (Either a b) -> m b-liftMonarch action = liftIO action >>= either throwError return+lengthBS32 :: BS.ByteString -> B.Word32+lengthBS32 = fromIntegral . BS.length --- | Get a value. If not found, return Nothing.-get :: Key -- ^ key- -> Monarch (Maybe Value)-get key = do- conn <- ask- liftMonarch $ TT.get conn key+fromLBS :: LBS.ByteString -> BS.ByteString+fromLBS = BS.pack . LBS.unpack --- | Destructive store a value.-put :: Key -- ^ key- -> Value -- ^ value+liftMonarch :: TTPipe a+ -> Monarch a+liftMonarch = Monarch . lift++yieldRequest :: B.Put -> Monarch ()+yieldRequest = liftMonarch . yield . fromLBS . runPut++responseCode :: Monarch Code+responseCode =+ liftMonarch CB.head >>=+ maybe (throwError MiscellaneousError)+ (return . toCode . fromIntegral)++parseLBS :: Monarch LBS.ByteString+parseLBS = liftMonarch $+ CB.take 4 >>=+ CB.take . fromIntegral . runGet getWord32be++parseBS :: Monarch BS.ByteString+parseBS = fromLBS <$> parseLBS++parseWord32 :: Monarch B.Word32+parseWord32 = liftMonarch (CB.take 4) >>=+ return . runGet getWord32be++parseInt64 :: Monarch Int64+parseInt64 = liftMonarch (CB.take 8) >>=+ return . runGet (B.get :: B.Get Int64)++parseDouble :: Monarch Double+parseDouble = do+ integ <- fromIntegral <$> parseInt64+ fract <- fromIntegral <$> parseInt64+ return $ integ + fract * 1e-12++parseKeyValue :: Monarch (BS.ByteString, BS.ByteString)+parseKeyValue =+ liftMonarch $ do+ ksiz <- CB.take 4+ vsiz <- CB.take 4+ key <- CB.take . fromIntegral $+ runGet getWord32be ksiz+ value <- CB.take . fromIntegral $+ runGet getWord32be vsiz+ return (fromLBS key, fromLBS value)++communicate :: B.Put+ -> (Code -> Monarch a)+ -> Monarch a+communicate makeRequest makeResponse =+ yieldRequest makeRequest >>+ responseCode >>=+ makeResponse++-- | Store a record.+-- If a record with the same key exists in the database,+-- it is overwritten.+put :: BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value -> Monarch ()-put key value = do- conn <- ask- liftMonarch $ TT.put conn key value+put key value = communicate request response+ where+ request = do+ putMagic 0x10+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putByteString key+ putByteString value+ response Success = return ()+ response code = throwError code --- | Non-Destructive store a value.-putKeep :: Key -- ^ key- -> Value -- ^ value+-- | Store a new record.+-- If a record with the same key exists in the database,+-- this function has no effect.+putKeep :: BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value -> Monarch ()-putKeep key value = do- conn <- ask- liftMonarch $ TT.putKeep conn key value+putKeep key value = communicate request response+ where+ request = do+ putMagic 0x11+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putByteString key+ putByteString value+ response Success = return ()+ response InvalidOperation = return ()+ response code = throwError code --- | Get values.-mget :: [Key] -- ^ keys to get- -> Monarch [(Key, Value)]-mget keys = do- conn <- ask- liftMonarch $ TT.mget conn keys+-- | Concatenate a value at the end of the existing record.+-- If there is no corresponding record, a new record is created.+putCat :: BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value+ -> Monarch ()+putCat key value = communicate request response+ where+ request = do+ putMagic 0x12+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putByteString key+ putByteString value+ response Success = return ()+ response code = throwError code --- | Put values.-mput :: [(Key, Value)] -- ^ key/value pairs to put- -> Monarch ()-mput kvs = do- conn <- ask- liftMonarch $ TT.mput conn kvs+-- | Concatenate a value at the end of the existing record and shift it to the left.+-- If there is no corresponding record, a new record is created.+putShiftLeft :: BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value+ -> Int -- ^ width+ -> Monarch ()+putShiftLeft key value width = communicate request response+ where+ request = do+ putMagic 0x13+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putWord32be $ fromIntegral width+ putByteString key+ putByteString value+ response Success = return ()+ response code = throwError code --- | Delete a value.-delete :: Key -- ^ key to delete- -> Monarch ()-delete key = do- conn <- ask- liftMonarch $ TT.delete conn key+-- | Store a record without response.+-- If a record with the same key exists in the database, it is overwritten.+putNoResponse :: BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value+ -> Monarch ()+putNoResponse key value = yieldRequest request+ where+ request = do+ putMagic 0x18+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putByteString key+ putByteString value --- | Delete all value.+-- | Remove a record.+out :: BS.ByteString -- ^ key+ -> Monarch ()+out key = communicate request response+ where+ request = do+ putMagic 0x20+ putWord32be $ lengthBS32 key+ putByteString key+ response Success = return ()+ response InvalidOperation = return ()+ response code = throwError code++-- | Retrieve a record.+get :: BS.ByteString -- ^ key+ -> Monarch (Maybe BS.ByteString)+get key = communicate request response+ where+ request = do+ putMagic 0x30+ putWord32be $ lengthBS32 key+ putByteString key+ response Success = Just <$> parseBS+ response InvalidOperation = return Nothing+ response code = throwError code++-- | Retrieve records.+multipleGet :: [BS.ByteString] -- ^ keys+ -> Monarch [(BS.ByteString, BS.ByteString)]+multipleGet keys = communicate request response+ where+ request = do+ putMagic 0x31+ putWord32be $ fromIntegral $ length keys+ mapM_ (\key -> do+ putWord32be $ lengthBS32 key+ putByteString key) keys+ response Success = do+ siz <- fromIntegral <$> parseWord32+ sequence $ Prelude.replicate siz parseKeyValue+ response code = throwError code++-- | Get the size of the value of a record.+valueSize :: BS.ByteString -- ^ key+ -> Monarch (Maybe Int)+valueSize key = communicate request response+ where+ request = do+ putMagic 0x38+ putWord32be $ lengthBS32 key+ putByteString key+ response Success = Just . fromIntegral <$> parseWord32+ response InvalidOperation = return Nothing+ response code = throwError code++-- | Initialize the iterator.+iterInit :: Monarch ()+iterInit = communicate request response+ where+ request = putMagic 0x50+ response Success = return ()+ response code = throwError code++-- | Get the next key of the iterator.+-- The iterator can be updated by multiple connections and then it is not assured that every record is traversed.+iterNext :: Monarch (Maybe BS.ByteString)+iterNext = communicate request response+ where+ request = putMagic 0x51+ response Success = Just <$> parseBS+ response InvalidOperation = return Nothing+ response code = throwError code++-- | Get forward matching keys.+forwardMatchingKeys :: BS.ByteString -- ^ key prefix+ -> Int -- ^ maximum number of keys to be fetched+ -> Monarch [BS.ByteString]+forwardMatchingKeys prefix n = communicate request response+ where+ request = do+ putMagic 0x58+ putWord32be $ lengthBS32 prefix+ putWord32be $ fromIntegral n+ putByteString prefix+ response Success = do+ siz <- fromIntegral <$> parseWord32+ sequence $ Prelude.replicate siz parseBS+ response code = throwError code++-- | Add an integer to a record.+-- If the corresponding record exists, the value is treated as an integer and is added to.+-- If no record corresponds, a new record of the additional value is stored.+addInt :: BS.ByteString -- ^ key+ -> Int -- ^ value+ -> Monarch Int+addInt key n = communicate request response+ where+ request = do+ putMagic 0x60+ putWord32be $ lengthBS32 key+ putWord32be $ fromIntegral n+ putByteString key+ response Success = fromIntegral <$> parseWord32+ response code = throwError code++-- | Add a real number to a record.+-- If the corresponding record exists, the value is treated as a real number and is added to.+-- If no record corresponds, a new record of the additional value is stored.+addDouble :: BS.ByteString -- ^ key+ -> Double -- ^ value+ -> Monarch Double+addDouble key n = communicate request response+ where+ request = do+ putMagic 0x61+ putWord32be $ lengthBS32 key+ B.put (truncate n :: Int64)+ B.put (truncate (snd (properFraction n :: (Int,Double)) * 1e12) :: Int64)+ putByteString key+ response Success = parseDouble+ response code = throwError code++-- | Call a function of the script language extension.+ext :: BS.ByteString -- ^ function+ -> [ExtOption] -- ^ option flags+ -> BS.ByteString -- ^ key+ -> BS.ByteString -- ^ value+ -> Monarch BS.ByteString+ext func opts key value = communicate request response+ where+ request = do+ putMagic 0x68+ putWord32be $ lengthBS32 func+ putOptions opts+ putWord32be $ lengthBS32 key+ putWord32be $ lengthBS32 value+ putByteString func+ putByteString key+ putByteString value+ response Success = parseBS+ response code = throwError code++-- | Synchronize updated contents with the file and the device.+sync :: Monarch ()+sync = communicate request response+ where+ request = putMagic 0x70+ response Success = return ()+ response code = throwError code++-- | Optimize the storage.+optimize :: BS.ByteString -- ^ parameter+ -> Monarch ()+optimize param = communicate request response+ where+ request = do+ putMagic 0x71+ putWord32be $ lengthBS32 param+ putByteString param+ response Success = return ()+ response code = throwError code++-- | Remove all records. vanish :: Monarch ()-vanish = do- conn <- ask- liftMonarch $ TT.vanish conn+vanish = communicate request response+ where+ request = putMagic 0x72+ response Success = return ()+ response code = throwError code --- | Search keys by prefix.-fwmkeys :: ByteString -- ^ key prefix- -> Maybe Int -- ^ max # of returned keys;- -- Nothing means no limit- -> Monarch [Key]-fwmkeys key n = do- conn <- ask- liftMonarch $ TT.fwmkeys conn key (maybe (-1) id n)+-- | Copy the database file.+copy :: BS.ByteString -- ^ path+ -> Monarch ()+copy path = communicate request response+ where+ request = do+ putMagic 0x73+ putWord32be $ lengthBS32 path+ putByteString path+ response Success = return ()+ response code = throwError code++-- | Restore the database file from the update log.+restore :: Integral a =>+ BS.ByteString -- ^ path+ -> a -- ^ beginning time stamp in microseconds+ -> [RestoreOption] -- ^ option flags+ -> Monarch ()+restore path usec opts = communicate request response+ where+ request = do+ putMagic 0x74+ putWord32be $ lengthBS32 path+ B.put (fromIntegral usec :: Int64)+ putOptions opts+ putByteString path+ response Success = return ()+ response code = throwError code++-- | Set the replication master.+setMaster :: Integral a =>+ BS.ByteString -- ^ host+ -> Int -- ^ port+ -> a -- ^ beginning time stamp in microseconds+ -> [RestoreOption] -- ^ option flags+ -> Monarch ()+setMaster host port usec opts = communicate request response+ where+ request = do+ putMagic 0x78+ putWord32be $ lengthBS32 host+ putWord32be $ fromIntegral port+ B.put (fromIntegral usec :: Int64)+ putOptions opts+ putByteString host+ response Success = return ()+ response code = throwError code++-- | Get the number of records.+recordNum :: Monarch Int64+recordNum = communicate request response+ where+ request = putMagic 0x80+ response Success = parseInt64+ response code = throwError code++-- | Get the size of the database.+size :: Monarch Int64+size = communicate request response+ where+ request = putMagic 0x81+ response Success = parseInt64+ response code = throwError code++-- | Get the status string of the database.+status :: Monarch BS.ByteString+status = communicate request response+ where+ request = putMagic 0x88+ response Success = parseBS+ response code = throwError code++-- | Call a versatile function for miscellaneous operations.+misc :: BS.ByteString -- ^ function name+ -> [MiscOption] -- ^ option flags+ -> [BS.ByteString] -- ^ arguments+ -> Monarch [BS.ByteString]+misc func opts args = communicate request response+ where+ request = do+ putMagic 0x90+ putWord32be $ lengthBS32 func+ putOptions opts+ putWord32be $ fromIntegral $ length args+ putByteString func+ mapM_ putByteString args+ response Success = do+ siz <- fromIntegral <$> parseWord32+ sequence $ Prelude.replicate siz parseBS+ response code = throwError code
monarch.cabal view
@@ -1,5 +1,5 @@ name: monarch-version: 0.1.0.0+version: 0.2.0.0 synopsis: Monadic interface for TokyoTyrant. description: This package provides simple monadic interface for TokyoTyrant. license: BSD3@@ -17,7 +17,9 @@ build-depends: base ==4.* , mtl ==2.1.* , bytestring ==0.9.*- , tokyotyrant-haskell ==1.0.*+ , conduit ==0.5.*+ , network-conduit ==0.5.*+ , binary ==0.5.* test-suite unit-test hs-source-dirs: test, .@@ -26,7 +28,9 @@ build-depends: base ==4.* , mtl ==2.1.* , bytestring ==0.9.*- , tokyotyrant-haskell ==1.0.*+ , conduit ==0.5.*+ , network-conduit ==0.5.*+ , binary ==0.5.* , hspec ==1.3.* , HUnit ==1.2.*
test/test.hs view
@@ -1,31 +1,243 @@ {-# LANGUAGE OverloadedStrings #-} import Database.Monarch +import Control.Applicative+import Data.List+import Data.ByteString.Char8 () import Test.HUnit import Test.Hspec.Monadic import Test.Hspec.HUnit () main :: IO () main = hspec $ do- describe "TokyoTyrant" $ do- it "get unstored value" caseGetUnstored- it "get already stored value" caseGetStored+ describe "put" $ do+ it "store a record" casePutRecord+ it "overwrite a record if same key exists" casePutOverwriteRecord+ describe "putkeep" $ do+ it "store a new record" casePutKeepNewRecord+ it "has no effect if same key exists" casePutKeepNoEffect+ describe "putcat" $ do+ it "concatenate a value at the end of the existing record" casePutCatRecord+ it "store a new record if there is no corresponding record" casePutCatNewRecord+ describe "putshl" $ do+ it "concatenate a value at the end of the existing record and shift it to the left" casePutShlRecord+ it "store a new record if there is no corresponding record" casePutShlNewRecord+ describe "putnr" $ do+ it "store a record" casePutNrRecord+ it "overwrite a record if same key exists" casePutNrOverwriteRecord+ describe "out" $ do+ it "remove a record" caseOutRecord+ it "no effect if same key not exists" caseOutNoEffect+ describe "get" $ do+ it "retrieve a record" caseGetRecord+ describe "mget" $ do+ it "retrieve records" caseMgetRecords+ describe "vsiz" $ do+ it "get the size of the value of a record" caseVsizRecord+ describe "iterinit" $ do+ it "initialize the iterator" caseIterinit+ describe "iternext" $ do+ it "get the next key of the iterator" caseIternext+ it "invalid if end iterator" caseIternextInvalid+ describe "fwmkeys" $ do+ it "get forward matching keys" caseFwmkeys -caseGetUnstored :: Assertion-caseGetUnstored = do+returns :: (Eq a, Show a) =>+ Monarch a+ -> Either Code a+ -> IO ()+action `returns` expected = do result <- runMonarch "localhost" 1978 $ do- vanish- result <- get "foo"- vanish- return result- result @?= Right Nothing+ vanish+ result <- action+ vanish+ return result+ result @?= expected -caseGetStored :: Assertion-caseGetStored = do- result <- runMonarch "localhost" 1978 $ do- vanish- put "foo" "bar"- result <- get "foo"- vanish- return result- result @?= Right (Just "bar")+casePutRecord :: Assertion+casePutRecord =+ action `returns` Right (Just "bar")+ where+ action = do+ put "foo" "bar"+ get "foo"++casePutOverwriteRecord :: Assertion+casePutOverwriteRecord =+ action `returns` Right (Just "hoge")+ where+ action = do+ put "foo" "bar"+ put "foo" "hoge"+ get "foo"++casePutKeepNewRecord :: Assertion+casePutKeepNewRecord =+ action `returns` Right (Just "hoge")+ where+ action = do+ putKeep "foo" "hoge"+ get "foo"++casePutKeepNoEffect :: Assertion+casePutKeepNoEffect =+ action `returns` Right (Just "bar")+ where+ action = do+ putKeep "foo" "bar"+ putKeep "foo" "hoge"+ get "foo"++casePutCatRecord :: Assertion+casePutCatRecord =+ action `returns` Right (Just "abracadabra")+ where+ action = do+ put "foo" "abra"+ putCat "foo" "cadabra"+ get "foo"++casePutCatNewRecord :: Assertion+casePutCatNewRecord =+ action `returns` Right (Just "cadabra")+ where+ action = do+ putCat "foo" "cadabra"+ get "foo"++casePutShlRecord :: Assertion+casePutShlRecord =+ action `returns` Right (Just "racadabra")+ where+ action = do+ put "foo" "abra"+ putShiftLeft "foo" "cadabra" 9+ get "foo"++casePutShlNewRecord :: Assertion+casePutShlNewRecord =+ action `returns` Right (Just "cadabra")+ where+ action = do+ putShiftLeft "foo" "cadabra" 4+ get "foo"++casePutNrRecord :: Assertion+casePutNrRecord =+ action `returns` Right (Just "bar")+ where+ action = do+ putNoResponse "foo" "bar"+ get "foo"++casePutNrOverwriteRecord :: Assertion+casePutNrOverwriteRecord =+ action `returns` Right (Just "hoge")+ where+ action = do+ putNoResponse "foo" "bar"+ putNoResponse "foo" "hoge"+ get "foo"++caseOutRecord :: Assertion+caseOutRecord =+ action `returns` Right (Just "bar", Nothing)+ where+ action = do+ put "foo" "bar"+ put "hoge" "fuga"+ out "hoge"+ stored <- get "foo"+ unstored <- get "hoge"+ return (stored, unstored)++caseOutNoEffect :: Assertion+caseOutNoEffect =+ action `returns` Right ()+ where+ action = do+ out "hoge"++caseGetRecord :: Assertion+caseGetRecord =+ action `returns` Right (Just "bar", Nothing)+ where+ action = do+ put "foo" "bar"+ stored <- get "foo"+ unstored <- get "bar"+ return (stored, unstored)++caseMgetRecords :: Assertion+caseMgetRecords =+ action `returns` Right [ ("foo", "bar")+ , ("huga", "hoge")+ , ("abra", "cadabra")+ ]+ where+ action = do+ put "foo" "bar"+ put "huga" "hoge"+ put "abra" "cadabra"+ multipleGet [ "foo"+ , "huga"+ , "unstored"+ , "abra"+ ]++caseVsizRecord :: Assertion+caseVsizRecord =+ action `returns` Right (Just 3, Nothing)+ where+ action = do+ put "foo" "bar"+ stored <- valueSize "foo"+ unstored <- valueSize "bar"+ return (stored, unstored)++caseIterinit :: Assertion+caseIterinit =+ action `returns` Right True+ where+ action = do+ put "foo" "bar"+ put "fuga" "hoge"+ put "abra" "cadabra"+ iterInit+ key1 <- iterNext+ _ <- iterNext+ iterInit+ key2 <- iterNext+ return $ key1 == key2++caseIternext :: Assertion+caseIternext =+ action `returns` Right [ Just "abra", Just "foo", Just "fuga" ]+ where+ action = do+ put "foo" "bar"+ put "fuga" "hoge"+ put "abra" "cadabra"+ iterInit+ key1 <- iterNext+ key2 <- iterNext+ key3 <- iterNext+ return $ sort [key1, key2, key3]++caseIternextInvalid :: Assertion+caseIternextInvalid =+ action `returns` Right Nothing+ where+ action = do+ iterNext++caseFwmkeys :: Assertion+caseFwmkeys =+ action `returns` Right [ "abra", "abrac" ]+ where+ action = do+ put "abr" "acadabra"+ put "abra" "cadabra"+ put "abrac" "adabra"+ put "abraca" "dabra"+ sort <$> forwardMatchingKeys "abra" 2