module Hasql.Engine.Statement
  ( Statement (..),
    preparable,
    unpreparable,
    refineResult,
    toSql,
  )
where

import Hasql.Decoders qualified as Decoders
import Hasql.Encoders qualified as Encoders
import Hasql.Engine.Decoders.Result qualified
import Hasql.Platform.Prelude

-- |
-- Specification of a strictly single-statement query, which can be parameterized and prepared.
-- It encapsulates the mapping of parameters and results in association with an SQL template.
--
-- Following is an example of a declaration of a prepared statement with its associated codecs.
--
-- @
-- selectSum :: 'Statement' (Int64, Int64) Int64
-- selectSum =
--   'preparable' sql encoder decoder
--   where
--     sql =
--       \"select ($1 + $2)\"
--     encoder =
--       ('fst' '>$<' Encoders.'Hasql.Encoders.param' (Encoders.'Hasql.Encoders.nonNullable' Encoders.'Hasql.Encoders.int8')) '<>'
--       ('snd' '>$<' Encoders.'Hasql.Encoders.param' (Encoders.'Hasql.Encoders.nonNullable' Encoders.'Hasql.Encoders.int8'))
--     decoder =
--       Decoders.'Hasql.Decoders.singleRow' (Decoders.'Hasql.Decoders.column' (Decoders.'Hasql.Decoders.nonNullable' Decoders.'Hasql.Decoders.int8'))
-- @
--
-- The statement above accepts a product of two parameters of type 'Int64'
-- and produces a single result of type 'Int64'.
data Statement params result
  = Statement
      -- | SQL template.
      --
      -- Must be formatted according to the Postgres standard.
      -- The parameters must be referred to using the positional notation, as in the following:
      -- @$1@, @$2@, @$3@ and etc.
      -- These references must be used in accordance with the order in which
      -- the value encoders are specified in the parameters encoder.
      Text
      -- | Parameters encoder.
      (Encoders.Params params)
      -- | Decoder of result.
      (Decoders.Result result)
      -- | Flag, determining whether it can be prepared.
      --
      -- Set it to 'True' if your application has a limited amount of queries and doesn't generate the SQL dynamically.
      -- This will boost the performance by allowing Postgres to avoid reconstructing the execution plan each time the query gets executed.
      Bool

-- |
-- Construct a preparable statement.
--
-- Use this for statements that will be executed multiple times with different parameters.
-- Preparable statements are cached by PostgreSQL, which avoids reconstructing the execution plan each time.
--
-- Suitable for applications with a limited amount of queries that don't generate SQL dynamically.
preparable ::
  -- | SQL template with parameters in positional notation (@$1@, @$2@, etc.)
  Text ->
  -- | Parameters encoder
  Encoders.Params params ->
  -- | Result decoder
  Decoders.Result result ->
  Statement params result
preparable :: forall params result.
Text -> Params params -> Result result -> Statement params result
preparable Text
sql Params params
encoder Result result
decoder = Text
-> Params params
-> Result result
-> Bool
-> Statement params result
forall params result.
Text
-> Params params
-> Result result
-> Bool
-> Statement params result
Statement Text
sql Params params
encoder Result result
decoder Bool
True

-- |
-- Construct an unpreparable statement.
--
-- Use this for statements that are dynamically generated or executed only once.
-- Unpreparable statements are not cached by PostgreSQL.
--
-- Suitable for dynamic SQL or one-off queries.
unpreparable ::
  -- | SQL template with parameters in positional notation (@$1@, @$2@, etc.)
  Text ->
  -- | Parameters encoder
  Encoders.Params params ->
  -- | Result decoder
  Decoders.Result result ->
  Statement params result
unpreparable :: forall params result.
Text -> Params params -> Result result -> Statement params result
unpreparable Text
sql Params params
encoder Result result
decoder = Text
-> Params params
-> Result result
-> Bool
-> Statement params result
forall params result.
Text
-> Params params
-> Result result
-> Bool
-> Statement params result
Statement Text
sql Params params
encoder Result result
decoder Bool
False

instance Functor (Statement params) where
  {-# INLINE fmap #-}
  fmap :: forall a b. (a -> b) -> Statement params a -> Statement params b
fmap = (a -> b) -> Statement params a -> Statement params b
forall b c a. (b -> c) -> Statement a b -> Statement a c
forall (p :: * -> * -> *) b c a.
Profunctor p =>
(b -> c) -> p a b -> p a c
rmap

instance Filterable (Statement params) where
  {-# INLINE mapMaybe #-}
  mapMaybe :: forall a b.
(a -> Maybe b) -> Statement params a -> Statement params b
mapMaybe a -> Maybe b
filtrator (Statement Text
template Params params
encoder Result a
decoder Bool
preparable) =
    Text -> Params params -> Result b -> Bool -> Statement params b
forall params result.
Text
-> Params params
-> Result result
-> Bool
-> Statement params result
Statement Text
template Params params
encoder ((a -> Maybe b) -> Result a -> Result b
forall a b. (a -> Maybe b) -> Result a -> Result b
forall (f :: * -> *) a b.
Filterable f =>
(a -> Maybe b) -> f a -> f b
mapMaybe a -> Maybe b
filtrator Result a
decoder) Bool
preparable

instance Profunctor Statement where
  {-# INLINE dimap #-}
  dimap :: forall a b c d.
(a -> b) -> (c -> d) -> Statement b c -> Statement a d
dimap a -> b
f1 c -> d
f2 (Statement Text
template Params b
encoder Result c
decoder Bool
preparable) =
    Text -> Params a -> Result d -> Bool -> Statement a d
forall params result.
Text
-> Params params
-> Result result
-> Bool
-> Statement params result
Statement Text
template ((a -> b) -> Params b -> Params a
forall a' a. (a' -> a) -> Params a -> Params a'
forall (f :: * -> *) a' a.
Contravariant f =>
(a' -> a) -> f a -> f a'
contramap a -> b
f1 Params b
encoder) ((c -> d) -> Result c -> Result d
forall a b. (a -> b) -> Result a -> Result b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap c -> d
f2 Result c
decoder) Bool
preparable

-- |
-- Refine the result of a statement,
-- causing the running session to fail with the 'Hasql.Errors.UnexpectedResultStatementError' error in case of a refinement failure.
--
-- This function is especially useful for refining the results of statements produced with
-- <http://hackage.haskell.org/package/hasql-th the \"hasql-th\" library>.
refineResult :: (a -> Either Text b) -> Statement params a -> Statement params b
refineResult :: forall a b params.
(a -> Either Text b) -> Statement params a -> Statement params b
refineResult a -> Either Text b
refiner (Statement Text
template Params params
encoder Result a
decoder Bool
preparable) =
  Text -> Params params -> Result b -> Bool -> Statement params b
forall params result.
Text
-> Params params
-> Result result
-> Bool
-> Statement params result
Statement Text
template Params params
encoder ((a -> Either Text b) -> Result a -> Result b
forall a b. (a -> Either Text b) -> Result a -> Result b
Hasql.Engine.Decoders.Result.refineResult a -> Either Text b
refiner Result a
decoder) Bool
preparable

-- | Extract the SQL template from a statement.
toSql :: Statement params result -> Text
toSql :: forall params result. Statement params result -> Text
toSql (Statement Text
sql Params params
_ Result result
_ Bool
_) = Text
sql