hasql/library/Hasql.hs

206 lines
6.6 KiB
Haskell
Raw Normal View History

2015-11-21 16:51:38 +03:00
-- |
-- This module provides a low-level effectful API dealing with connections to the database.
--
-- The API is completely disinfected from exceptions. All error-reporting is explicit and is presented using the 'Either' type.
2015-11-14 13:58:30 +03:00
module Hasql
2015-11-08 21:09:42 +03:00
(
2015-11-21 17:46:36 +03:00
-- * Connection settings
Settings.Settings(..),
Settings.settings,
-- * Connection
2015-11-08 21:09:42 +03:00
Connection,
2015-11-16 20:03:33 +03:00
ConnectionError(..),
connect,
disconnect,
-- * Query
Query(..),
2015-11-08 21:09:42 +03:00
ResultsError(..),
ResultError(..),
RowError(..),
2015-11-16 20:03:33 +03:00
query,
2015-11-08 21:09:42 +03:00
)
where
import Hasql.Prelude
import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.PreparedStatementRegistry as PreparedStatementRegistry
2015-11-21 13:36:01 +03:00
import qualified Hasql.Decoding.Results as ResultsDecoding
import qualified Hasql.Decoding as Decoding
import qualified Hasql.Encoding.Params as ParamsEncoding
import qualified Hasql.Encoding as Encoding
2015-11-08 21:09:42 +03:00
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
2015-11-21 17:52:25 +03:00
-- |
-- An error of the result-decoder.
2015-11-08 21:09:42 +03:00
data ResultsError =
-- |
-- An error on the client-side,
-- with a message generated by the \"libpq\" library.
-- Usually indicates problems with connection.
2015-11-08 21:18:59 +03:00
ClientError !(Maybe ByteString) |
2015-11-21 17:52:25 +03:00
-- |
-- Decoder error details.
2015-11-08 21:09:42 +03:00
ResultError !ResultError
2015-11-15 12:10:28 +03:00
deriving (Show, Eq)
2015-11-08 21:09:42 +03:00
2015-11-21 17:52:25 +03:00
-- |
-- Decoder error details.
2015-11-08 21:09:42 +03:00
data ResultError =
-- |
2015-11-22 09:30:59 +03:00
-- An error reported by the DB.
-- Consists of the following: Code, message, details, hint.
2015-11-08 21:09:42 +03:00
--
2015-11-22 09:30:59 +03:00
-- * __Code__.
-- The SQLSTATE code for the error.
-- It's recommended to use
-- <http://hackage.haskell.org/package/postgresql-error-codes the "postgresql-error-codes" package>
-- to work with those.
2015-11-08 21:09:42 +03:00
--
2015-11-22 09:30:59 +03:00
-- * __Message__.
-- The primary human-readable error message (typically one line). Always present.
2015-11-08 21:09:42 +03:00
--
2015-11-22 09:30:59 +03:00
-- * __Details__.
-- An optional secondary error message carrying more detail about the problem.
2015-11-08 21:09:42 +03:00
-- Might run to multiple lines.
--
2015-11-22 09:30:59 +03:00
-- * __Hint__.
-- An optional suggestion on what to do about the problem.
2015-11-08 21:09:42 +03:00
-- This is intended to differ from detail in that it offers advice (potentially inappropriate)
2015-11-22 09:30:59 +03:00
-- rather than hard facts.
-- Might run to multiple lines.
2015-11-08 21:18:59 +03:00
ServerError !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |
2015-11-08 21:09:42 +03:00
-- |
-- 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
2015-11-15 12:10:28 +03:00
deriving (Show, Eq)
2015-11-08 21:09:42 +03:00
2015-11-21 17:52:25 +03:00
-- |
-- An error during the decoding of a specific row.
2015-11-08 21:09:42 +03:00
data RowError =
2015-11-21 17:52:25 +03:00
-- |
-- Appears on the attempt to parse more columns than there are in the result.
2015-11-08 21:09:42 +03:00
EndOfInput |
2015-11-21 17:52:25 +03:00
-- |
-- Appears on the attempt to parse a @NULL@ as some value.
2015-11-08 21:09:42 +03:00
UnexpectedNull |
2015-11-21 17:52:25 +03:00
-- |
-- Appears when a wrong value parser is used.
-- Comes with the error details.
2015-11-08 21:09:42 +03:00
ValueError !Text
2015-11-15 12:10:28 +03:00
deriving (Show, Eq)
2015-11-08 21:09:42 +03:00
-- |
2015-11-21 17:52:25 +03:00
-- Possible details of the connection acquistion error.
2015-11-16 20:03:33 +03:00
type ConnectionError =
2015-11-15 12:10:28 +03:00
Maybe ByteString
2015-11-08 21:09:42 +03:00
-- |
-- Acquire a connection using the provided settings.
2015-11-16 20:03:33 +03:00
connect :: Settings.Settings -> IO (Either ConnectionError Connection)
connect settings =
2015-11-21 09:58:43 +03:00
{-# SCC "connect" #-}
2015-11-08 21:09:42 +03:00
runEitherT $ do
pqConnection <- lift (IO.acquireConnection settings)
2015-11-15 12:10:28 +03:00
lift (IO.checkConnectionStatus pqConnection) >>= traverse left
2015-11-08 21:09:42 +03:00
lift (IO.initConnection pqConnection)
integerDatetimes <- lift (IO.getIntegerDatetimes pqConnection)
registry <- lift (IO.acquirePreparedStatementRegistry)
pure (Connection pqConnection integerDatetimes registry)
-- |
-- Release the connection.
2015-11-16 20:03:33 +03:00
disconnect :: Connection -> IO ()
disconnect (Connection pqConnection _ _) =
2015-11-08 21:09:42 +03:00
LibPQ.finish pqConnection
2015-11-14 13:58:30 +03:00
-- |
2015-11-21 17:55:53 +03:00
-- A specification of a strictly single-statement query, which can be parameterized and prepared.
2015-11-14 13:58:30 +03:00
--
2015-11-21 17:03:37 +03:00
-- Consists of the following:
2015-11-14 13:58:30 +03:00
--
2015-11-21 17:03:37 +03:00
-- * SQL template,
-- * params encoder,
-- * result decoder,
-- * a flag, determining whether it should be prepared.
--
-- The SQL template must be formatted according to Postgres' standard,
-- with any non-ASCII characters of the template must be encoded using UTF-8.
-- According to the format,
-- parameters must be referred to using the positional notation, as in the following:
-- @$1@, @$2@, @$3@ and etc.
-- Those references must be used to refer to the values of the 'Encoding.Params' encoder.
--
-- Following is an example of the declaration of a prepared statement with its associated codecs.
--
-- @
-- selectSum :: Hasql.'Hasql.Query' (Int64, Int64) Int64
-- selectSum =
-- Hasql.'Hasql.Query' sql encoder decoder True
-- where
-- sql =
-- "select ($1 + $2)"
-- encoder =
2015-11-21 18:03:29 +03:00
-- 'contramap' 'fst' (Hasql.Encoding.'Hasql.Encoding.value' Hasql.Encoding.'Hasql.Encoding.int8') '<>'
2015-11-21 17:03:37 +03:00
-- 'contramap' 'snd' (Hasql.Encoding.'Hasql.Encoding.value' Hasql.Encoding.'Hasql.Encoding.int8')
-- decoder =
-- Hasql.Decoding.'Hasql.Decoding.singleRow' (Hasql.Decoding.'Hasql.Decoding.value' Hasql.Decoding.'Hasql.Decoding.int8')
-- @
--
-- The statement above accepts a product of two parameters of type 'Int64'
-- and results in a single result of type 'Int64'.
--
data Query a b =
Query !ByteString !(Encoding.Params a) !(Decoding.Result b) !Bool
deriving (Functor)
instance Profunctor Query where
2015-11-21 17:54:46 +03:00
{-# INLINE lmap #-}
2015-11-21 17:03:37 +03:00
lmap f (Query p1 p2 p3 p4) =
Query p1 (contramap f p2) p3 p4
2015-11-21 17:54:46 +03:00
{-# INLINE rmap #-}
2015-11-21 17:03:37 +03:00
rmap f (Query p1 p2 p3 p4) =
Query p1 p2 (fmap f p3) p4
2015-11-21 17:54:46 +03:00
{-# INLINE dimap #-}
2015-11-21 17:03:37 +03:00
dimap f1 f2 (Query p1 p2 p3 p4) =
Query p1 (contramap f1 p2) (fmap f2 p3) p4
2015-11-14 13:58:30 +03:00
2015-11-08 21:09:42 +03:00
-- |
-- Execute a parametric query, producing either a deserialization failure or a successful result.
query :: Connection -> Query a b -> a -> IO (Either ResultsError b)
2015-11-21 17:03:37 +03:00
query (Connection pqConnection integerDatetimes registry) (Query template encoder decoder preparable) params =
2015-11-21 09:58:43 +03:00
{-# SCC "query" #-}
2015-11-08 21:09:42 +03:00
fmap (mapLeft coerceResultsError) $ runEitherT $ do
2015-11-21 13:36:01 +03:00
EitherT $ IO.sendParametricQuery pqConnection integerDatetimes registry template (coerceEncoder encoder) preparable params
EitherT $ IO.getResults pqConnection integerDatetimes (coerceDecoder decoder)
2015-11-08 21:09:42 +03:00
-- |
-- WARNING: We need to take special care that the structure of
2015-11-21 13:36:01 +03:00
-- the "ResultsDecoding.Error" type in the public API is an exact copy of
2015-11-08 21:09:42 +03:00
-- "Error", since we're using coercion.
2015-11-21 13:36:01 +03:00
coerceResultsError :: ResultsDecoding.Error -> ResultsError
2015-11-08 21:09:42 +03:00
coerceResultsError =
unsafeCoerce
2015-11-21 13:36:01 +03:00
coerceDecoder :: Decoding.Result a -> ResultsDecoding.Results a
coerceDecoder =
2015-11-08 21:09:42 +03:00
unsafeCoerce
2015-11-21 13:36:01 +03:00
coerceEncoder :: Encoding.Params a -> ParamsEncoding.Params a
coerceEncoder =
2015-11-08 21:09:42 +03:00
unsafeCoerce