hasql/library/Hasql.hs
2015-11-15 10:29:53 +03:00

160 lines
5.7 KiB
Haskell

module Hasql
(
-- * Connection management
Connection,
Settings.Settings(..),
acquire,
release,
-- * Query execution
ParametricQuery(..),
NonparametricQuery(..),
executeParametricQuery,
executeNonparametricQuery,
-- * Errors
AcquisitionError(..),
ResultsError(..),
ResultError(..),
RowError(..),
)
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)
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)
data RowError =
EndOfInput |
UnexpectedNull |
ValueError !Text
deriving (Show)
-- |
-- A connection acquistion error.
data AcquisitionError =
-- | Some errors during connection.
BadConnectionStatus !(Maybe ByteString) |
-- | The server is running a too old version of Postgres.
UnsupportedVersion !Int
deriving (Show)
-- |
-- Acquire a connection using the provided settings.
acquire :: Settings.Settings -> IO (Either AcquisitionError Connection)
acquire settings =
runEitherT $ do
pqConnection <- lift (IO.acquireConnection settings)
lift (IO.checkConnectionStatus pqConnection) >>= traverse (left . BadConnectionStatus)
lift (IO.checkServerVersion pqConnection) >>= traverse (left . UnsupportedVersion)
lift (IO.initConnection pqConnection)
integerDatetimes <- lift (IO.getIntegerDatetimes pqConnection)
registry <- lift (IO.acquirePreparedStatementRegistry)
pure (Connection pqConnection integerDatetimes registry)
-- |
-- Release the connection.
release :: Connection -> IO ()
release (Connection pqConnection _ _) =
LibPQ.finish pqConnection
-- |
-- A strictly single-statement query, which can be parameterized and prepared.
--
-- SQL template, params serializer, results deserializer and a flag, determining whether it should be prepared.
--
type ParametricQuery a b =
(ByteString, Serialization.Params a, Deserialization.Results b, Bool)
-- |
-- A non-parameterizable and non-preparable query,
-- which however can contain multiple statements.
--
-- SQL, results deserializer.
--
type NonparametricQuery a =
(ByteString, Deserialization.Results a)
-- |
-- Execute a parametric query, producing either a deserialization failure or a successful result.
executeParametricQuery :: Connection -> ParametricQuery a b -> a -> IO (Either ResultsError b)
executeParametricQuery (Connection pqConnection integerDatetimes registry) (template, serializer, deserializer, preparable) params =
fmap (mapLeft coerceResultsError) $ runEitherT $ do
EitherT $ IO.sendParametricQuery pqConnection integerDatetimes registry template (coerceSerializer serializer) preparable params
EitherT $ IO.getResults pqConnection integerDatetimes (coerceDeserializer deserializer)
-- |
-- Execute a non-parametric query, producing either a deserialization failure or a successful result.
executeNonparametricQuery :: Connection -> NonparametricQuery a -> IO (Either ResultsError a)
executeNonparametricQuery (Connection pqConnection integerDatetimes registry) (sql, deserializer) =
fmap (mapLeft coerceResultsError) $ runEitherT $ do
EitherT $ IO.sendNonparametricQuery pqConnection sql
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.Results a -> ResultsDeserialization.Results a
coerceDeserializer =
unsafeCoerce
coerceSerializer :: Serialization.Params a -> ParamsSerialization.Params a
coerceSerializer =
unsafeCoerce