Added error-reporting mechanism for backends
Getty Ritter
9 years ago
| 12 | 12 | import qualified Data.Map.Strict as M |
| 13 | 13 | import Data.Serialize (Serialize, encode) |
| 14 | 14 | import Database.Tansu (Tansu) |
| 15 |
import Database.Tansu.Internal |
|
| 15 | import Database.Tansu.Internal | |
| 16 | 16 | |
| 17 | 17 | type Table = M.Map ByteString ByteString |
| 18 | 18 | |
| 19 | 19 | ephemeralRunTransaction :: MVar () -> IO a -> IO a |
| 20 | 20 | ephemeralRunTransaction lock comp = withMVar lock $ \ () -> comp |
| 21 | 21 | |
| 22 | ephemeralSet :: IORef Table -> ByteString -> ByteString -> IO () | |
| 23 | ephemeralSet table key val = modifyIORef table (M.insert key val) | |
| 22 | ephemeralSet :: IORef Table -> ByteString -> ByteString -> IO (Either TansuError ()) | |
| 23 | ephemeralSet table key val = | |
| 24 | fmap return $ modifyIORef table (M.insert key val) | |
| 24 | 25 | |
| 25 | ephemeralGet :: IORef Table -> ByteString -> IO (Maybe ByteString) | |
| 26 | ephemeralGet table key = M.lookup key `fmap` readIORef table | |
| 26 | ephemeralGet :: IORef Table -> ByteString -> IO (Either TansuError ByteString) | |
| 27 | ephemeralGet table key = do | |
| 28 | rs <- M.lookup key `fmap` readIORef table | |
| 29 | case rs of | |
| 30 | Just x -> return (return x) | |
| 31 | Nothing -> return (Left (KeyNotFound key)) | |
| 27 | 32 | |
| 28 | ephemeralDel :: IORef Table -> ByteString -> IO () | |
| 29 | ephemeralDel table key = modifyIORef table (M.delete key) | |
| 33 | ephemeralDel :: IORef Table -> ByteString -> IO (Either TansuError ()) | |
| 34 | ephemeralDel table key = | |
| 35 | fmap return $ modifyIORef table (M.delete key) | |
| 30 | 36 | |
| 31 | 37 | -- | An 'EphemeralDb' is just an in-memory map, with no way of saving it. |
| 32 | 38 | -- It is intended to be used for testing Tansu code. |
| 1 | 1 | module Database.Tansu.Backend.Filesystem (withFilesystemDb) where |
| 2 | 2 | |
| 3 | import Control.Exception | |
| 3 | 4 | import Data.ByteString (ByteString) |
| 4 | 5 | import Data.ByteString.Base64 |
| 5 | 6 | import qualified Data.ByteString.Char8 as BS |
| 6 |
import Database.Tansu.Internal |
|
| 7 | import Database.Tansu.Internal | |
| 7 | 8 | import System.Directory ( createDirectoryIfMissing |
| 8 | 9 | , doesFileExist |
| 9 | 10 | , removeFile |
| 11 | 12 | import System.FileLock (SharedExclusive(Exclusive), withFileLock) |
| 12 | 13 | import System.FilePath.Posix ((</>)) |
| 13 | 14 | |
| 14 |
|
|
| 15 | catchIO :: IO a -> IO (Either TansuError a) | |
| 16 | catchIO mote = fmap return mote `catch` go | |
| 17 | where go :: IOException -> IO (Either TansuError a) | |
| 18 | go = return . Left . OtherError . show | |
| 19 | ||
| 20 | filePathSet :: FilePath -> ByteString -> ByteString -> IO (Either TansuError ()) | |
| 15 | 21 | filePathSet path key val = do |
| 16 | 22 | let keyPath = path </> BS.unpack (encode key) |
| 17 |
|
|
| 23 | catchIO $ BS.writeFile keyPath val | |
| 18 | 24 | |
| 19 |
filePathGet :: FilePath -> ByteString -> IO ( |
|
| 25 | filePathGet :: FilePath -> ByteString -> IO (Either TansuError ByteString) | |
| 20 | 26 | filePathGet path key = do |
| 21 | 27 | let keyPath = path </> BS.unpack (encode key) |
| 22 | 28 | exists <- doesFileExist keyPath |
| 23 | 29 | if exists |
| 24 | then Just `fmap` BS.readFile keyPath | |
| 25 | else return Nothing | |
| 30 | then Right `fmap` BS.readFile keyPath | |
| 31 | else return (Left (KeyNotFound key)) | |
| 26 | 32 | |
| 27 |
filePathDel :: FilePath -> ByteString -> IO ( |
|
| 33 | filePathDel :: FilePath -> ByteString -> IO (Either TansuError ()) | |
| 28 | 34 | filePathDel path key = do |
| 29 | 35 | let keyPath = path </> BS.unpack (encode key) |
| 30 |
|
|
| 36 | catchIO $ removeFile keyPath | |
| 31 | 37 | |
| 32 | 38 | filePathLock :: FilePath -> IO a -> IO a |
| 33 | 39 | filePathLock path comp = do |
| 24 | 24 | -- is exposed by the "Database.Tansu.Internal" module so that |
| 25 | 25 | -- other libraries can implement new storage backends. |
| 26 | 26 | data TansuDb = TansuDb |
| 27 | { dbSet :: ByteString -> ByteString -> IO () | |
| 28 | , dbGet :: ByteString -> IO (Maybe ByteString) | |
| 29 |
|
|
| 27 | { dbSet :: ByteString -> ByteString -> IO (Either TansuError ()) | |
| 28 | , dbGet :: ByteString -> IO (Either TansuError ByteString) | |
| 29 | , dbDel :: ByteString -> IO (Either TansuError ()) | |
| 30 | 30 | , dbRunTransaction :: forall a. IO a -> IO a |
| 31 | 31 | } |
| 43 | 43 | instance Monad (Tansu k v) where |
| 44 | 44 | Tansu x >>= f = Tansu (x >>= runTansu . f) |
| 45 | 45 | |
| 46 | propagate :: IO (Either TansuError a) -> Tansu k v a | |
| 47 | propagate mote = do | |
| 48 | rs <- Tansu $ inBase $ mote | |
| 49 | case rs of | |
| 50 | Left err -> Tansu $ raise err | |
| 51 | Right x -> return x | |
| 52 | ||
| 46 | 53 | -- | Sets the value for a key to a value. |
| 47 | 54 | set :: (Serialize k, Serialize v) => k -> v -> Tansu k v () |
| 48 | 55 | set key val = do |
| 49 | 56 | db <- Tansu ask |
| 50 |
|
|
| 57 | propagate $ dbSet db (encode key) (encode val) | |
| 51 | 58 | |
| 52 | 59 | -- | Sets the value for a key to a value. A convenience operator |
| 53 | 60 | -- that is identical to 'set'. |
| 73 | 80 | db <- Tansu ask |
| 74 | 81 | result <- Tansu $ inBase $ dbGet db (encode key) |
| 75 | 82 | case result of |
| 76 | Nothing -> return Nothing | |
| 77 | Just bs -> case decode bs of | |
| 83 | Left (KeyNotFound _) -> return Nothing | |
| 84 | Left err -> Tansu (raise err) | |
| 85 | Right bs -> case decode bs of | |
| 78 | 86 | Right val' -> return (Just val') |
| 79 | 87 | Left err -> Tansu (raise (DecodeError err)) |
| 80 | 88 | |
| 81 | 89 | del :: (Serialize k) => k -> Tansu k v () |
| 82 | 90 | del key = do |
| 83 | 91 | db <- Tansu ask |
| 84 |
|
|
| 92 | propagate $ dbDel db (encode key) | |
| 85 | 93 | |
| 86 | 94 | -- | Given a storage backend and a 'Tansu' computation, execute the |
| 87 | 95 | -- sequence of 'get' and 'set' commands and produce either the value |