{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Database.PostgreSQL.Store.Result -- Copyright: (c) Ole Krüger 2015-2016 -- License: BSD3 -- Maintainer: Ole Krüger module Database.PostgreSQL.Store.Result ( -- * Result processor ResultError (..), ResultProcessor, processResult, processOneResult, skipColumn, unpackColumn ) where import Control.Monad import Control.Monad.Trans import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Except import qualified Database.PostgreSQL.LibPQ as P import Database.PostgreSQL.Store.Columns -- | Error that occured during result processing data ResultError = TooFewColumnsError P.Column -- ^ Occurs when you're trying to access a column that does not exist. | UnpackError P.Row P.Column P.Oid P.Format -- ^ The value at a given row and column could not be unpacked. deriving (Show, Eq) -- | Result processor newtype ResultProcessor a = ResultProcessor (StateT P.Column (ReaderT (P.Result, P.Row, P.Column) (ExceptT ResultError IO)) a) deriving (Functor, Applicative, Monad, MonadIO, MonadError ResultError) -- | Move cursor to the next column. skipColumn :: ResultProcessor () skipColumn = ResultProcessor (modify (+ 1)) -- | Unpack the current column and move the cursor to the next column. unpackColumn :: (Column a) => ResultProcessor a unpackColumn = do -- Gather information col <- ResultProcessor get (res, row, numCol) <- ResultProcessor ask -- Make sure we're not trying to unpack a non-existing column when (col >= numCol) (throwError (TooFewColumnsError numCol)) -- Retrieve column-specific information (typ, mbData) <- liftIO $ (,) <$> P.ftype res col <*> P.getvalue' res row col -- Try to unpack the value case unpack (maybe NullValue (Value typ) mbData) of Just ret -> ResultProcessor (put (col + 1) >> pure ret) Nothing -> throwError (UnpackError row col typ P.Text) -- | Process the entire result set. processResult :: P.Result -> ResultProcessor a -> ExceptT ResultError IO [a] processResult res (ResultProcessor proc) = do (rows, cols) <- liftIO ((,) <$> P.ntuples res <*> P.nfields res) -- Iterate over each row number and run the row processor forM [0 .. rows - 1] $ \ row -> runReaderT (evalStateT proc 0) (res, row, cols) -- | Process one row of the result set. processOneResult :: P.Result -> ResultProcessor a -> ExceptT ResultError IO (Maybe a) processOneResult res (ResultProcessor proc) = do (rows, cols) <- liftIO ((,) <$> P.ntuples res <*> P.nfields res) if rows > 0 then Just <$> runReaderT (evalStateT proc 0) (res, 0, cols) else pure Nothing