hedis-pile (empty) → 0.1.0
raw patch · 6 files changed
+251/−0 lines, 6 filesdep +HUnitdep +QuickCheckdep +basesetup-changed
Dependencies added: HUnit, QuickCheck, base, bytestring, hedis, hedis-pile, hedis-tags, lifted-base, test-framework, test-framework-hunit, test-framework-quickcheck2, transformers
Files
- LICENSE +25/−0
- Setup.hs +2/−0
- hedis-pile.cabal +53/−0
- src/Database/Redis/Pile.hs +89/−0
- test/Database/Redis/Test/Pile.hs +70/−0
- test/Main.hs +12/−0
+ LICENSE view
@@ -0,0 +1,25 @@+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2011, John Lenz. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain
+ hedis-pile.cabal view
@@ -0,0 +1,53 @@+name: hedis-pile +version: 0.1.0 +cabal-version: >= 1.8 +build-type: Simple +stability: Experimental +license: BSD3 +license-file: LICENSE +synopsis: Caching mandatory data with Redis +description: Solution for caching mandatory data with Redis. +category: Database +author: Alexander Dorofeev <aka.spin@gmail.com> +maintainer: Alexander Dorofeev <aka.spin@gmail.com> +homepage: https://github.com/akaspin/hedis-pile + +source-repository head + type: git + location: git://github.com/akaspin/hedis-pile.git + +library + hs-source-dirs: src + build-depends: + base >= 4 && < 5, + bytestring >= 0.9 && < 0.10, + hedis >= 0.3 && < 0.4, + hedis-tags >= 0.1, + transformers >= 0.2 && < 0.3 + ghc-options: -Wall + exposed-modules: Database.Redis.Pile + +test-suite test + type: exitcode-stdio-1.0 + x-uses-tf: true + hs-source-dirs: test + build-depends: + base >= 4, + HUnit >= 1.2 && < 2, + QuickCheck >= 2.4, + test-framework >= 0.4.1, + test-framework-quickcheck2, + test-framework-hunit, + + hedis-pile, + + -- from lib + bytestring >= 0.9 && < 0.10, + hedis >= 0.3 && < 0.4, + + lifted-base, + transformers >= 0.2 && < 0.3 + ghc-options: -Wall -rtsopts -threaded + main-is: Main.hs + other-modules: Database.Redis.Test.Pile +
+ src/Database/Redis/Pile.hs view
@@ -0,0 +1,89 @@+-- | Solution for caching mandatory data with Redis. +-- +-- In many cases, requires not just pick up or put the data into the cache. +-- As a rule, data are required. +-- +-- ... check the cache ... if the value is missing, run the calculations ... +-- put value to cache ... Tedious +-- +-- Solution is quite simple - collapse all of these steps in one operation. +module Database.Redis.Pile ( + pile +) where + +import qualified Data.ByteString as B + +import Control.Monad.IO.Class (liftIO) +import Control.Monad (void) + +import qualified Database.Redis as R +import qualified Database.Redis.Tags as RT + +-- | Stores computation results in Redis as hashSet. Computation fires only +-- if data absent in cache. Of course, to refresh the data, they must first +-- remove from the cache. +-- +-- Computation controls all that will be stored in the cache. To do this, +-- except the results of computation, it may return optional @TTL@ in +-- seconds (Redis convention) and tags for key. About tags see +-- "Database.Redis.Tags". +-- +-- Instead get all data from cache, optional parameter allows simply make +-- sure that cache holds HashSet with needed field with needed value. If this +-- is so, 'pile' return 'Nothing'. +-- +-- > conn <- connect defaultConnectInfo +-- > runRedis conn $ do +-- > -- do it +-- > r <- pile "mykey" (Just ("etag", "etag")) $ +-- > return ([("etag", "etag"), ("val", "myval")], Nothing, Nothing) +-- > liftIO $ print r +-- > -- Just [("etag", "etag"), ("val", "myval")] +-- > +-- > -- once again +-- > r <- pile "mykey" (Just ("etag", "etag")) $ +-- > return ([("etag", "etag"), ("val", "myval")], Nothing, Nothing) +-- > liftIO $ print r +-- > -- Nothing +-- > +-- > -- and again without expect +-- > r <- pile "mykey" Nothing $ +-- > return ([("etag", "etag"), ("val", "myval")], Nothing, Nothing) +-- > liftIO $ print r +-- > -- Just [("etag", "etag"), ("val", "myval")] +pile :: + B.ByteString + -- ^ Key in cache. + -> Maybe (B.ByteString, B.ByteString) + -- ^ Optional expect field. + -> IO ([(B.ByteString, B.ByteString)], + Maybe Integer, + Maybe (B.ByteString, [B.ByteString])) + -- ^ Computation that returns data and + -- optional TTL and tags. + -> R.Redis (Maybe [(B.ByteString, B.ByteString)]) +pile key (Just (ef, ev)) f = do + e <- R.hget key ef + case e of + Right (Just ev') | ev' == ev -> return Nothing + | otherwise -> pile key Nothing f + _ -> pile key Nothing f +pile key Nothing f = do + d <- R.hgetall key + case d of + Right [] -> runF + Right r -> return $ Just r + _ -> runF + where + runF = do + (r, ke, t) <- liftIO f + void $ R.hmset key r + setExpire ke + setTags t + return $ Just r + where + setExpire Nothing = return () + setExpire (Just ke) = void $ R.expire key ke + setTags Nothing = return () + setTags (Just (p, ts)) = RT.markTags [key] p ts +
+ test/Database/Redis/Test/Pile.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} + +module Database.Redis.Test.Pile (tests) where + +import Test.Framework (testGroup, mutuallyExclusive, Test) +import Test.Framework.Providers.HUnit (testCase) +import Test.HUnit (Assertion, (@=?)) + +import Control.Monad (void) +import Control.Monad.IO.Class (liftIO) +import Control.Exception.Lifted (bracket_) + +import qualified Data.ByteString as B + +import qualified Database.Redis as R +import qualified Database.Redis.Pile as RP + +tests :: Test +tests = mutuallyExclusive $ testGroup "Pile" [ + testCase "New Data" caseNewData, + testCase "Stored data" caseStoredData + ] + +caseNewData :: Assertion +caseNewData = bracket_ + setup + teardown + $ runInRedis $ do + r <- RP.pile (allPrefix "two") Nothing $ + return (allData, Nothing, Nothing) + liftIO $ Just allData @=? r + void $ RP.pile (allPrefix "three") Nothing $ + return (allData, Just 15, Just ("piletest:", ["one"])) + ex <- R.ttl (allPrefix "three") + liftIO $ Right 15 @=? ex + +caseStoredData :: Assertion +caseStoredData = bracket_ + setup + teardown + $ runInRedis $ do + r <- RP.pile (allPrefix "one") Nothing $ + return (allData, Nothing, Nothing) + liftIO $ Just allData @=? r + r' <- RP.pile (allPrefix "one") (Just ("etag", "etag")) $ + return (allData, Nothing, Nothing) + liftIO $ Nothing @=? r' + +runInRedis :: forall b. R.Redis b -> IO b +runInRedis a = do + conn <- R.connect R.defaultConnectInfo + R.runRedis conn a + +setup :: IO () +setup = runInRedis $ + void $ R.hmset "piletest:one" allData + +-- | Purge all keys with 'allPrefix' +teardown :: IO () +teardown = runInRedis $ do + a <- R.keys $ allPrefix "*" + _ <- either undefined R.del a + return () + +allPrefix :: B.ByteString -> B.ByteString +allPrefix = B.append "piletest:" + +allData :: [(B.ByteString, B.ByteString)] +allData = [("etag", "etag"), ("anydata", "anydata")]
+ test/Main.hs view
@@ -0,0 +1,12 @@+ +module Main (main) where + +import Test.Framework (defaultMain) + +import qualified Database.Redis.Test.Pile + +main :: IO () +main = defaultMain [ + Database.Redis.Test.Pile.tests + ] +