mirror of
https://github.com/nikita-volkov/hasql.git
synced 2024-12-29 13:33:03 +03:00
160 lines
5.7 KiB
Haskell
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
|