module Hasql.Private.Query
where

import Hasql.Private.Prelude
import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.Private.IO as IO
import qualified Hasql.Private.Connection as Connection
import qualified Hasql.Private.Decoders.Results as Decoders.Results
import qualified Hasql.Private.Encoders.Params as Encoders.Params


-- |
-- An abstraction over parametric queries.
-- 
-- It is composable using
-- the standard interfaces of the category theory,
-- which it has instances of.
-- E.g., here's how you can compose queries
-- using the Arrow notation:
-- 
-- @
-- -- |
-- -- Given an Update query,
-- -- which uses the \@fmap (> 0) 'Decoders.Results.rowsAffected'\@ decoder
-- -- to detect, whether it had any effect,
-- -- and an Insert query,
-- -- produces a query which performs Upsert.
-- composeUpsert :: Query a Bool -> Query a () -> Query a ()
-- composeUpsert update insert =
--   proc params -> do
--     updated <- update -< params
--     if updated
--       then 'returnA' -< ()
--       else insert -< params
-- @
newtype Query a b =
  Query (Kleisli (ReaderT Connection.Connection (EitherT Decoders.Results.Error IO)) a b)
  deriving (Category, Arrow, ArrowChoice, ArrowLoop, ArrowApply)

instance Functor (Query a) where
  {-# INLINE fmap #-}
  fmap =
    (^<<)

instance Profunctor Query where
  {-# INLINE lmap #-}
  lmap =
    (^>>)
  {-# INLINE rmap #-}
  rmap =
    (^<<)

statement :: ByteString -> Encoders.Params.Params a -> Decoders.Results.Results b -> Bool -> Query a b
statement template encoder decoder preparable =
  Query $ Kleisli $ \params -> 
    ReaderT $ \(Connection.Connection pqConnectionRef integerDatetimes registry) -> 
      EitherT $ withMVar pqConnectionRef $ \pqConnection -> do
        r1 <- IO.sendParametricQuery pqConnection integerDatetimes registry template encoder preparable params
        r2 <- IO.getResults pqConnection integerDatetimes decoder
        return $ r1 *> r2