Refactored Tansu to include a basic non-serialized version and a serialized, typed wrapper
Getty Ritter
8 years ago
1 | module Database.Tansu.Monad where | |
2 | ||
3 | import Data.ByteString (ByteString) | |
4 | import MonadLib ( ReaderT | |
5 | , ExceptionT | |
6 | , ask | |
7 | , raise | |
8 | , try | |
9 | , inBase | |
10 | , runExceptionT | |
11 | , runReaderT | |
12 | ) | |
13 | import Database.Tansu.Internal | |
14 | ||
15 | type TansuM a = ReaderT TansuDb (ExceptionT TansuError IO) a | |
16 | ||
17 | propagate :: IO (Either TansuError a) -> TansuM a | |
18 | propagate mote = do | |
19 | rs <- inBase $ mote | |
20 | case rs of | |
21 | Left err -> raise err | |
22 | Right x -> return x | |
23 | ||
24 | setInternal :: ByteString -> ByteString -> TansuM () | |
25 | setInternal key val = do | |
26 | db <- ask | |
27 | propagate $ dbSet db key val | |
28 | ||
29 | getInternal :: ByteString -> TansuM ByteString | |
30 | getInternal key = do | |
31 | db <- ask | |
32 | propagate $ dbGet db key | |
33 | ||
34 | delInternal :: ByteString -> TansuM () | |
35 | delInternal key = do | |
36 | db <- ask | |
37 | propagate $ dbDel db key | |
38 | ||
39 | runInternal :: TansuDb -> TansuM a -> IO (Either TansuError a) | |
40 | runInternal db mote = | |
41 | dbRunTransaction db (runExceptionT (runReaderT db mote)) |
1 | {-# LANGUAGE RankNTypes #-} | |
2 | ||
3 | module Database.Tansu.Serialize | |
4 | ( -- * The 'TansuS' monad | |
5 | TansuS | |
6 | , TansuDbS | |
7 | , TansuError(..) | |
8 | -- * 'TansuS' Operations | |
9 | , get | |
10 | , getMb | |
11 | , set | |
12 | , (=:) | |
13 | , del | |
14 | -- * Running a 'TansuS' operation | |
15 | , runS | |
16 | , serialized | |
17 | ) where | |
18 | ||
19 | import Data.ByteString (ByteString) | |
20 | import Data.Serialize (Serialize, encode, decode) | |
21 | import MonadLib (raise, try) | |
22 | ||
23 | import Database.Tansu.Monad | |
24 | import Database.Tansu.Internal | |
25 | ||
26 | newtype TansuDbS k v = TansuDbS TansuDb | |
27 | ||
28 | serialized :: (TansuDbS k v -> r) -> (TansuDb -> r) | |
29 | serialized func = func . TansuDbS | |
30 | ||
31 | newtype TansuS k v a = TansuS { runTansuS :: TansuM a } | |
32 | ||
33 | instance Functor (TansuS k v) where | |
34 | fmap f (TansuS t) = TansuS (fmap f t) | |
35 | ||
36 | instance Applicative (TansuS k v) where | |
37 | pure = TansuS . pure | |
38 | TansuS f <*> TansuS x = TansuS (f <*> x) | |
39 | ||
40 | instance Monad (TansuS k v) where | |
41 | TansuS x >>= f = TansuS (x >>= runTansuS . f) | |
42 | ||
43 | -- | Sets the value for a key to a value. | |
44 | set :: (Serialize k, Serialize v) => k -> v -> TansuS k v () | |
45 | set key val = TansuS $ setInternal (encode key) (encode val) | |
46 | ||
47 | -- | Sets the value for a key to a value. A convenience operator | |
48 | -- that is identical to 'set'. | |
49 | (=:) :: (Serialize k, Serialize v) => k -> v -> TansuS k v () | |
50 | (=:) = set | |
51 | ||
52 | -- | Gets a value for a given key. The resulting 'Tansu' computation | |
53 | -- will fail if the key is not present in the storage backend. | |
54 | get :: (Serialize k, Serialize v) => k -> TansuS k v v | |
55 | get key = do | |
56 | bs <- TansuS $ getInternal (encode key) | |
57 | case (decode bs) of | |
58 | Left err -> TansuS (raise (DecodeError err)) | |
59 | Right val -> return val | |
60 | ||
61 | -- | Gets a value for a given key. If the key is not present, this | |
62 | -- computation will return 'Nothing' instead. Other errors, such | |
63 | -- as problems decoding the serialized value or difficulties | |
64 | -- communicating with the storage backend, will still cause the | |
65 | -- 'Tansu' computation to fail. | |
66 | getMb :: (Serialize k, Serialize v) => k -> TansuS k v (Maybe v) | |
67 | getMb key = do | |
68 | rs <- TansuS $ try $ runTansuS $ get key | |
69 | case rs of | |
70 | Left (KeyNotFound _) -> return Nothing | |
71 | Left err -> TansuS (raise err) | |
72 | Right val -> return (Just val) | |
73 | ||
74 | del :: (Serialize k) => k -> TansuS k v () | |
75 | del key = TansuS $ delInternal (encode key) | |
76 | ||
77 | -- | Given a storage backend and a 'Tansu' computation, execute the | |
78 | -- sequence of 'get' and 'set' commands and produce either the value | |
79 | -- or the error encountered while running the computation. | |
80 | runS :: TansuDbS k v -> TansuS k v a -> IO (Either TansuError a) | |
81 | runS (TansuDbS db) (TansuS mote) = runInternal db mote |
1 | module Main where | |
2 | ||
3 | import Control.Monad (void) | |
4 | import Database.Tansu | |
5 | import Database.Tansu.Backend.Filesystem | |
6 | import Database.Tansu.Backend.Ephemeral | |
7 | ||
8 | main :: IO () | |
9 | main = do | |
10 | putStrLn "Testing filesystem db" | |
11 | withFilesystemDb "sample.db" sample | |
12 | ||
13 | putStrLn "Testing ephemeral db" | |
14 | withNewEphemeralDb sample | |
15 | ||
16 | sample :: TansuDb String String -> IO () | |
17 | sample db = do | |
18 | putStrLn "Populating test database" | |
19 | run db $ do | |
20 | "one" =: "un" | |
21 | "two" =: "du" | |
22 | "three" =: "tri" | |
23 | "four" =: "kvar" | |
24 | ||
25 | putStr "looking up key 'three': " | |
26 | rs <- run db $ get "three" | |
27 | case rs of | |
28 | Right val -> putStrLn val | |
29 | Left _ -> putStrLn "...not in the database." | |
30 | ||
31 | putStr "looking up key 'five': " | |
32 | rs <- run db $ get "five" | |
33 | case rs of | |
34 | Right val -> putStrLn val | |
35 | Left _ -> putStrLn "...not in the database." |
1 | module Main where | |
2 | ||
3 | import Control.Monad (void) | |
4 | import Database.Tansu | |
5 | import Database.Tansu.Backend.Filesystem | |
6 | import Database.Tansu.Backend.Ephemeral | |
7 | ||
8 | main :: IO () | |
9 | main = do | |
10 | putStrLn "Testing filesystem db" | |
11 | withFilesystemDb "sample.db" sample | |
12 | ||
13 | putStrLn "Testing ephemeral db" | |
14 | withNewEphemeralDb sample | |
15 | ||
16 | sample :: TansuDb String String -> IO () | |
17 | sample db = do | |
18 | putStrLn "Populating test database" | |
19 | run db $ do | |
20 | "one" =: "un" | |
21 | "two" =: "du" | |
22 | "three" =: "tri" | |
23 | "four" =: "kvar" | |
24 | ||
25 | putStr "looking up key 'three': " | |
26 | rs <- run db $ get "three" | |
27 | case rs of | |
28 | Right val -> putStrLn val | |
29 | Left _ -> putStrLn "...not in the database." | |
30 | ||
31 | putStr "looking up key 'five': " | |
32 | rs <- run db $ get "five" | |
33 | case rs of | |
34 | Right val -> putStrLn val | |
35 | Left _ -> putStrLn "...not in the database." |
1 | {-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} | |
2 | ||
3 | module Main where | |
4 | ||
5 | import Data.Serialize (Serialize) | |
6 | import GHC.Generics (Generic) | |
7 | ||
8 | import Database.Tansu.Serialize ((=:), get, runS, serialized) | |
9 | import Database.Tansu.Backend.Filesystem (withFilesystemDb) | |
10 | ||
11 | data Person = Person | |
12 | { name :: String | |
13 | , age :: Int | |
14 | } deriving (Eq, Show, Generic, Serialize) | |
15 | ||
16 | main :: IO () | |
17 | main = withFilesystemDb "sample.db" $ serialized $ \ db -> do | |
18 | runS db $ do | |
19 | "alex" =: Person "alex" 33 | |
20 | "blake" =: Person "blake" 22 | |
21 | ||
22 | Right age <- runS db (age `fmap` get "blake") | |
23 | putStrLn $ "Blake's age is " ++ show age |