hasql/library/Hasql.hs
Nikita Volkov 8a6a265a72 SCC
2015-11-21 09:59:14 +03:00

137 lines
4.6 KiB
Haskell

module Hasql
(
-- * Connection
Connection,
Settings.Settings(..),
ConnectionError(..),
connect,
disconnect,
-- * Query
Query(..),
ResultsError(..),
ResultError(..),
RowError(..),
query,
)
where
import Hasql.Prelude
import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.PreparedStatementRegistry as PreparedStatementRegistry
import qualified Hasql.Deserialization.Results as ResultsDeserialization
import qualified Hasql.Deserialization as Deserialization
import qualified Hasql.Serialization.Params as ParamsSerialization
import qualified Hasql.Serialization as Serialization
import qualified Hasql.Settings as Settings
import qualified Hasql.IO as IO
-- |
-- A single connection to the database.
data Connection =
Connection !LibPQ.Connection !Bool !PreparedStatementRegistry.PreparedStatementRegistry
data ResultsError =
-- |
-- An error on the client-side,
-- with a message generated by the \"libpq\" library.
-- Usually indicates problems with connection.
ClientError !(Maybe ByteString) |
ResultError !ResultError
deriving (Show, Eq)
data ResultError =
-- |
-- An error reported by the DB. Code, message, details, hint.
--
-- * The SQLSTATE code for the error. The SQLSTATE code identifies the type of error that has occurred;
-- it can be used by front-end applications to perform specific operations (such as error handling)
-- in response to a particular database error.
-- For a list of the possible SQLSTATE codes, see Appendix A.
-- This field is not localizable, and is always present.
--
-- * The primary human-readable error message (typically one line). Always present.
--
-- * Detail: an optional secondary error message carrying more detail about the problem.
-- Might run to multiple lines.
--
-- * Hint: an optional suggestion what to do about the problem.
-- This is intended to differ from detail in that it offers advice (potentially inappropriate)
-- rather than hard facts. Might run to multiple lines.
ServerError !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |
-- |
-- The database returned an unexpected result.
-- Indicates an improper statement or a schema mismatch.
UnexpectedResult !Text |
-- |
-- An error of the row reader, preceded by the index of the row.
RowError !Int !RowError |
-- |
-- An unexpected amount of rows.
UnexpectedAmountOfRows !Int
deriving (Show, Eq)
data RowError =
EndOfInput |
UnexpectedNull |
ValueError !Text
deriving (Show, Eq)
-- |
-- A connection acquistion error.
type ConnectionError =
Maybe ByteString
-- |
-- Acquire a connection using the provided settings.
connect :: Settings.Settings -> IO (Either ConnectionError Connection)
connect settings =
{-# SCC "connect" #-}
runEitherT $ do
pqConnection <- lift (IO.acquireConnection settings)
lift (IO.checkConnectionStatus pqConnection) >>= traverse left
lift (IO.initConnection pqConnection)
integerDatetimes <- lift (IO.getIntegerDatetimes pqConnection)
registry <- lift (IO.acquirePreparedStatementRegistry)
pure (Connection pqConnection integerDatetimes registry)
-- |
-- Release the connection.
disconnect :: Connection -> IO ()
disconnect (Connection pqConnection _ _) =
LibPQ.finish pqConnection
-- |
-- A strictly single-statement query, which can be parameterized and prepared.
--
-- SQL template, params serializer, result deserializer and a flag, determining whether it should be prepared.
--
type Query a b =
(ByteString, Serialization.Params a, Deserialization.Result b, Bool)
-- |
-- Execute a parametric query, producing either a deserialization failure or a successful result.
query :: Connection -> Query a b -> a -> IO (Either ResultsError b)
query (Connection pqConnection integerDatetimes registry) (template, serializer, deserializer, preparable) params =
{-# SCC "query" #-}
fmap (mapLeft coerceResultsError) $ runEitherT $ do
EitherT $ IO.sendParametricQuery pqConnection integerDatetimes registry template (coerceSerializer serializer) preparable params
EitherT $ IO.getResults pqConnection integerDatetimes (coerceDeserializer deserializer)
-- |
-- WARNING: We need to take special care that the structure of
-- the "ResultsDeserialization.Error" type in the public API is an exact copy of
-- "Error", since we're using coercion.
coerceResultsError :: ResultsDeserialization.Error -> ResultsError
coerceResultsError =
unsafeCoerce
coerceDeserializer :: Deserialization.Result a -> ResultsDeserialization.Results a
coerceDeserializer =
unsafeCoerce
coerceSerializer :: Serialization.Params a -> ParamsSerialization.Params a
coerceSerializer =
unsafeCoerce