Haddocks for everything!
Getty Ritter
9 years ago
| 1 | 1 | module Database.Tansu.Backend.Ephemeral |
| 2 |
( |
|
| 2 | ( EphemeralDb | |
| 3 | , withEphemeralDb | |
| 4 | , withNewEphemeralDb | |
| 5 | , createEphemeralDb | |
| 6 | ) where | |
| 3 | 7 | |
| 4 | 8 | import Control.Concurrent.MVar |
| 5 | 9 | import Data.ByteString (ByteString) |
| 6 | 10 | import Data.IORef |
| 11 | import Data.Map.Strict (Map) | |
| 7 | 12 | import qualified Data.Map.Strict as M |
| 13 | import Data.Serialize (Serialize, encode) | |
| 14 | import Database.Tansu (Tansu) | |
| 8 | 15 | import Database.Tansu.Internal (TansuDb(..)) |
| 9 | 16 | |
| 10 | 17 | type Table = M.Map ByteString ByteString |
| 18 | 25 | ephemeralGet :: IORef Table -> ByteString -> IO (Maybe ByteString) |
| 19 | 26 | ephemeralGet table key = M.lookup key `fmap` readIORef table |
| 20 | 27 | |
| 21 | withEphemeralDb :: (TansuDb -> IO a) -> IO a | |
| 22 | withEphemeralDb comp = do | |
| 28 | -- | An 'EphemeralDb' is just an in-memory map, with no way of saving it. | |
| 29 | -- It is intended to be used for testing Tansu code. | |
| 30 | newtype EphemeralDb = EDB { fromEDB :: Table } | |
| 31 | ||
| 32 | -- | Create an in-memory table to use for testing. | |
| 33 | createEphemeralDb :: (Serialize k, Serialize v) => [(k, v)] -> EphemeralDb | |
| 34 | createEphemeralDb ls = EDB (M.fromList [ (encode k, encode v) | (k, v) <- ls ]) | |
| 35 | ||
| 36 | -- | Run a 'Tansu' operation with an empty in-memory table. | |
| 37 | withNewEphemeralDb :: (TansuDb -> IO a) -> IO a | |
| 38 | withNewEphemeralDb = withEphemeralDb $ EDB M.empty | |
| 39 | ||
| 40 | -- | Run a 'Tansu' operation with an existing in-memory table. | |
| 41 | withEphemeralDb :: EphemeralDb -> (TansuDb -> IO a) -> IO a | |
| 42 | withEphemeralDb init comp = do | |
| 23 | 43 | lock <- newMVar () |
| 24 |
table <- newIORef |
|
| 44 | table <- newIORef (fromEDB init) | |
| 25 | 45 | comp $ TansuDb |
| 26 | 46 | { dbRunTransaction = ephemeralRunTransaction lock |
| 27 | 47 | , dbSet = ephemeralSet table |
| 25 | 25 | filePathLock path comp = do |
| 26 | 26 | withFileLock (path </> ".lock") Exclusive (const comp) |
| 27 | 27 | |
| 28 | -- | Use a local directory as a key-value store. Each key-value | |
| 29 | -- pair is represented as a file whose filename is the | |
| 30 | -- Base64-encoded serialized key, and whose content is the | |
| 31 | -- raw serialized value. Locking is done using the | |
| 32 | -- @.lock@ file in the specified directory, but note that | |
| 33 | -- file locking is not a guaranteed way of ensuring exclusion, | |
| 34 | -- and that the files themselves are not locked in any way. | |
| 28 | 35 | withFilesystemDb :: FilePath -> (TansuDb -> IO a) -> IO a |
| 29 | 36 | withFilesystemDb path comp = do |
| 30 | 37 | createDirectoryIfMissing True path |
| 6 | 6 | |
| 7 | 7 | import Data.ByteString (ByteString) |
| 8 | 8 | |
| 9 | -- | The 'TansuError' type enumerates the possible errors that | |
| 10 | -- can arise in the process of running a sequence of database | |
| 11 | -- accesses. Backends should attempt to use the most informative | |
| 12 | -- error available, but the 'OtherError' constructor is provided | |
| 13 | -- as a catch-all if none of the other constructors are | |
| 14 | -- descriptive of the error in question. | |
| 9 | 15 | data TansuError |
| 10 | 16 | = KeyNotFound ByteString |
| 11 | 17 | | DecodeError String |
| 18 | | AccessError String | |
| 19 | | OtherError String | |
| 12 | 20 | deriving (Eq, Show) |
| 13 | 21 | |
| 22 | -- | The data representation of a Tansu backend. Users of the library | |
| 23 | -- should treat this as an abstract type, but the full definition | |
| 24 | -- is exposed by the "Database.Tansu.Internal" module so that | |
| 25 | -- other libraries can implement new storage backends. | |
| 14 | 26 | data TansuDb = TansuDb |
| 15 | 27 | { dbSet :: ByteString -> ByteString -> IO () |
| 16 | 28 | , dbGet :: ByteString -> IO (Maybe ByteString) |
| 1 |
module Database.Tansu ( |
|
| 1 | module Database.Tansu ( -- * The 'Tansu' monad | |
| 2 | Tansu | |
| 2 | 3 | , TansuDb |
| 3 | 4 | , TansuError(..) |
| 5 | -- * 'Tansu' Operations | |
| 4 | 6 | , get |
| 5 | 7 | , getMb |
| 6 | 8 | , set |
| 22 | 24 | |
| 23 | 25 | type TansuM a = ReaderT TansuDb (ExceptionT TansuError IO) a |
| 24 | 26 | |
| 27 | -- | The 'Tansu' type is a monad which represents some sequence | |
| 28 | -- of 'get' and 'set' operations. Ideally, backends should | |
| 29 | -- make some guarantee about the atomicity of a given | |
| 30 | -- 'Tansu' computation, but you should consult the documentation | |
| 31 | -- about a given backend to make sure that holds. | |
| 25 | 32 | newtype Tansu a = Tansu { runTansu :: TansuM a } |
| 26 | 33 | |
| 27 | 34 | instance Functor Tansu where |
| 34 | 41 | instance Monad Tansu where |
| 35 | 42 | Tansu x >>= f = Tansu (x >>= runTansu . f) |
| 36 | 43 | |
| 44 | -- | Sets the value for a key to a value. | |
| 37 | 45 | set :: (Serialize k, Serialize v) => k -> v -> Tansu () |
| 38 | 46 | set key val = do |
| 39 | 47 | db <- Tansu ask |
| 40 | 48 | Tansu $ inBase $ dbSet db (encode key) (encode val) |
| 41 | 49 | |
| 50 | -- | Sets the value for a key to a value. A convenience operator | |
| 51 | -- that is identical to 'set'. | |
| 42 | 52 | (=:) :: (Serialize k, Serialize v) => k -> v -> Tansu () |
| 43 | 53 | (=:) = set |
| 44 | 54 | |
| 55 | -- | Gets a value for a given key. The resulting 'Tansu' computation | |
| 56 | -- will fail if the key is not present in the storage backend. | |
| 45 | 57 | get :: (Serialize k, Serialize v) => k -> Tansu v |
| 46 | 58 | get key = do |
| 47 | 59 | result <- getMb key |
| 49 | 61 | Just val -> return val |
| 50 | 62 | Nothing -> Tansu (raise (KeyNotFound (encode key))) |
| 51 | 63 | |
| 64 | -- | Gets a value for a given key. If the key is not present, this | |
| 65 | -- computation will return 'Nothing' instead. Other errors, such | |
| 66 | -- as problems decoding the serialized value or difficulties | |
| 67 | -- communicating with the storage backend, will still cause the | |
| 68 | -- 'Tansu' computation to fail. | |
| 52 | 69 | getMb :: (Serialize k, Serialize v) => k -> Tansu (Maybe v) |
| 53 | 70 | getMb key = do |
| 54 | 71 | db <- Tansu ask |
| 59 | 76 | Right val' -> return (Just val') |
| 60 | 77 | Left err -> Tansu (raise (DecodeError err)) |
| 61 | 78 | |
| 79 | -- | Given a storage backend and a 'Tansu' computation, execute the | |
| 80 | -- sequence of 'get' and 'set' commands and produce either the value | |
| 81 | -- or the error encountered while running the computation. | |
| 62 | 82 | run :: TansuDb -> Tansu a -> IO (Either TansuError a) |
| 63 | 83 | run db (Tansu comp) = |
| 64 | 84 | dbRunTransaction db (runExceptionT (runReaderT db comp)) |