{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-| Low-level implementation of the storage backend. -} module Avers.Storage.Backend ( parseValue , parseDatum , runQuery , runQueryDatum , runQuerySingleSelection , runQueryCollect , existsDocument , lookupDocument , insertDocument , upsertDocument , deleteDocument ) where import Prelude hiding (lookup) import Control.Monad.Except import Control.Monad.State import Data.Monoid import Data.Aeson (Value, Result(..)) import Data.Aeson.Types (parse, parseEither) import Data.Pool import qualified Data.Text as T import qualified Data.HashMap.Strict as HMS import qualified Data.Vector as V import qualified Database.RethinkDB as R import Database.RethinkDB.TH import Avers.TH import Avers.Types import Avers.Storage.Expressions parseValue :: (FromJSON a, MonadError AversError m) => Value -> m a parseValue value = case parseEither parseJSON value of Left e -> parseError value (T.pack e) Right x -> return x parseDatum :: (R.FromDatum a, MonadError AversError m) => R.Datum -> m a parseDatum datum = case parse R.parseDatum datum of Error e -> parseError (toJSON datum) (T.pack e) Success x -> return x data WriteResponse = WriteResponse { writeResponseInserted :: Int , writeResponseDeleted :: Int , writeResponseReplaced :: Int , writeResponseUnchanged :: Int , writeResponseSkipped :: Int , writeResponseErrors :: Int } deriving (Show) checkWriteResponse :: Maybe R.Datum -> Avers () checkWriteResponse resp = do case resp of Nothing -> strErr "Error" Just x -> case parseEither R.parseDatum x of Left err -> strErr err Right WriteResponse{..} -> if writeResponseErrors == 0 then return () else databaseError $ "Errors during write operation: " <> T.pack (show resp) mergePk :: (R.ToDatum a, Pk a) => a -> R.Object mergePk doc = HMS.insert primaryKeyField (R.String $ toPk doc) hms where (R.Object hms) = R.toDatum doc runQuerySingleSelection :: (R.FromDatum a) => R.Exp R.SingleSelection -> Avers a runQuerySingleSelection query = do res <- runQuery query case res of Nothing -> documentNotFound "..." Just x -> parseDatum x runQueryDatum :: (R.FromDatum a) => R.Exp R.Datum -> Avers a runQueryDatum query = do res <- runQuery query parseDatum res runQuery :: (R.FromResponse (R.Result a)) => R.Exp a -> Avers (R.Result a) runQuery query = do pool <- gets databaseHandlePool res <- liftIO $ withResource pool $ \handle -> do R.run handle query case res of Left e -> databaseError (T.pack $ show e) Right r -> return r runQueryCollect :: (R.FromDatum a, R.IsSequence e, R.Result e ~ R.Sequence a) => R.Exp e -> Avers (V.Vector a) runQueryCollect query = do pool <- gets databaseHandlePool res <- liftIO $ withResource pool $ \handle -> do r0 <- R.run handle query case r0 of Left e -> return $ Left e Right x -> R.collect handle x case res of Left e -> databaseError (T.pack $ show e) Right r -> return r existsDocument :: (Pk k) => R.Exp R.Table -> k -> Avers Bool existsDocument table key = do res <- runQuery $ R.IsEmpty $ R.Filter (primaryKeyEqE (toPk key)) table return $ not res lookupDocument :: (Pk k, R.FromDatum a) => R.Exp R.Table -> k -> Avers (Maybe a) lookupDocument table key = do res <- runQuery $ R.Get table (R.lift (toPk key)) maybe (return Nothing) parseDatum res insertDocument :: (R.ToDatum a, Pk a) => R.Exp R.Table -> a -> Avers () insertDocument table doc = do resp <- runQuery $ R.InsertObject R.CRError table (mergePk doc) checkWriteResponse $ Just $ R.Object resp deleteDocument :: (Pk k) => R.Exp R.Table -> k -> Avers () deleteDocument table key = do resp <- runQuery $ R.Delete $ R.Get table (R.lift $ toPk key) checkWriteResponse $ Just $ R.Object resp upsertDocument :: (R.ToDatum a, Pk a) => R.Exp R.Table -> a -> Avers () upsertDocument table doc = do resp <- runQuery $ R.InsertObject R.CRReplace table (mergePk doc) checkWriteResponse $ Just $ R.Object resp $(deriveDatum (deriveJSONOptions "writeResponse") ''WriteResponse)