107 lines
3.4 KiB
Haskell
107 lines
3.4 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
module Database.PostgreSQL.Opium
|
|
-- * Queries
|
|
--
|
|
-- Functions for performing queries. @fetch@ retrieves rows, @execute@ doesn't.
|
|
-- The 'Connection' parameter comes last to facilitate currying for implicitly passing in the connection, e.g. from some framework's connection pool.
|
|
--
|
|
-- | TODO: Add @newtype Query = Query Text@ with @IsString@ instance to make constructing query strings at run time harder.
|
|
( fetch
|
|
, fetch_
|
|
, execute
|
|
, execute_
|
|
-- * Classes to Implement
|
|
, FromRow (..)
|
|
, FromField (..)
|
|
, ToParamList (..)
|
|
, ToField (..)
|
|
-- * Utility Stuff
|
|
, Error (..)
|
|
, ErrorPosition (..)
|
|
, RawField (..)
|
|
)
|
|
where
|
|
|
|
import Control.Monad (unless, void)
|
|
import Control.Monad.IO.Class (liftIO)
|
|
import Control.Monad.Trans.Except (ExceptT (..), except, runExceptT, throwE)
|
|
import Data.Functor.Identity (Identity (..))
|
|
import Data.Proxy (Proxy (..))
|
|
import Data.Text (Text)
|
|
import Database.PostgreSQL.LibPQ
|
|
( Connection
|
|
, Result
|
|
)
|
|
|
|
import qualified Data.Text.Encoding as Encoding
|
|
import qualified Database.PostgreSQL.LibPQ as LibPQ
|
|
|
|
import Database.PostgreSQL.Opium.Error (Error (..), ErrorPosition (..))
|
|
import Database.PostgreSQL.Opium.FromField (FromField (..), RawField (..))
|
|
import Database.PostgreSQL.Opium.FromRow (FromRow (..), ColumnTable)
|
|
import Database.PostgreSQL.Opium.ToField (ToField (..))
|
|
import Database.PostgreSQL.Opium.ToParamList (ToParamList (..))
|
|
|
|
class RowContainer c where
|
|
extract :: FromRow a => Result -> ColumnTable -> ExceptT Error IO (c a)
|
|
|
|
instance RowContainer [] where
|
|
extract result columnTable = do
|
|
nRows <- liftIO $ LibPQ.ntuples result
|
|
mapM (ExceptT . fromRow result columnTable) [0..nRows - 1]
|
|
|
|
instance RowContainer Identity where
|
|
extract result columnTable = do
|
|
nRows <- liftIO $ LibPQ.ntuples result
|
|
unless (nRows == 1) $ throwE ErrorNotExactlyOneRow
|
|
Identity <$> ExceptT (fromRow result columnTable 0)
|
|
|
|
-- The order of the type parameters is important, because it is more common to use type applications for providing the row type and row container type.
|
|
fetch
|
|
:: forall a b c. (ToParamList c, FromRow a, RowContainer b)
|
|
=> Text
|
|
-> c
|
|
-> Connection
|
|
-> IO (Either Error (b a))
|
|
fetch query params conn = runExceptT $ do
|
|
result <- execParams conn query params
|
|
columnTable <- ExceptT $ getColumnTable @a Proxy result
|
|
extract result columnTable
|
|
|
|
fetch_ :: forall a c. (FromRow a, RowContainer c) => Text -> Connection -> IO (Either Error (c a))
|
|
fetch_ query = fetch query ()
|
|
|
|
execute
|
|
:: forall a. ToParamList a
|
|
=> Text
|
|
-> a
|
|
-> Connection
|
|
-> IO (Either Error ())
|
|
execute query params conn = runExceptT $ void $ execParams conn query params
|
|
|
|
execute_ :: Text -> Connection -> IO (Either Error ())
|
|
execute_ query = execute query ()
|
|
|
|
execParams
|
|
:: ToParamList a
|
|
=> Connection
|
|
-> Text
|
|
-> a
|
|
-> ExceptT Error IO Result
|
|
execParams conn query params = do
|
|
let queryBytes = Encoding.encodeUtf8 query
|
|
liftIO (LibPQ.execParams conn queryBytes (toParamList params) LibPQ.Binary) >>= \case
|
|
Nothing ->
|
|
except $ Left ErrorNoResult
|
|
Just result -> do
|
|
status <- liftIO $ LibPQ.resultStatus result
|
|
mbMessage <- liftIO $ LibPQ.resultErrorMessage result
|
|
case mbMessage of
|
|
Just "" -> pure result
|
|
Nothing -> pure result
|
|
Just message -> except $ Left $ ErrorInvalidResult status $ Encoding.decodeUtf8 message
|