{-# LANGUAGE AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications, DataKinds, TypeFamilies, PolyKinds, ConstraintKinds, TypeOperators, BangPatterns, FlexibleContexts #-}
{-|
Module: IHP.Fetch.Statement
Description: Prepared statements for fetch operations
Copyright: (c) digitally induced GmbH, 2025

Internal module containing hasql 'Statement' definitions shared by
'IHP.Fetch' and 'IHP.FetchPipelined'.
-}
module IHP.Fetch.Statement
( buildQueryListStatement
, buildQueryVectorStatement
, buildQueryMaybeStatement
, buildCountStatement
, buildExistsStatement
) where

import Prelude
import IHP.ModelSupport (Table(..), GetModelByTableName)
import IHP.Hasql.FromRow (FromRowHasql(..))
import qualified Hasql.Statement as Hasql
import qualified Hasql.Decoders as Decoders
import IHP.QueryBuilder.Types (QueryBuilder(..), SQLQuery(..))
import IHP.QueryBuilder.Compiler (buildQuery)
import IHP.QueryBuilder.HasqlCompiler (buildStatement, buildWrappedStatement)
import GHC.TypeLits (KnownSymbol)
import Data.Int (Int64)
import Data.Vector (Vector)

-- | Build a statement that fetches all rows matching a query builder.
buildQueryListStatement :: forall model table.
    ( Table model
    , model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model
    ) => QueryBuilder table -> Hasql.Statement () [model]
buildQueryListStatement :: forall model (table :: Symbol).
(Table model, model ~ GetModelByTableName table, KnownSymbol table,
 FromRowHasql model) =>
QueryBuilder table -> Statement () [model]
buildQueryListStatement !QueryBuilder table
queryBuilder =
    SQLQuery -> Result [model] -> Statement () [model]
forall a. SQLQuery -> Result a -> Statement () a
buildStatement (QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder) (Row model -> Result [model]
forall a. Row a -> Result [a]
Decoders.rowList (forall a. FromRowHasql a => Row a
hasqlRowDecoder @model))

-- | Like 'buildQueryListStatement', but returns a 'Vector' instead of a list.
buildQueryVectorStatement :: forall model table.
    ( Table model
    , model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model
    ) => QueryBuilder table -> Hasql.Statement () (Vector model)
buildQueryVectorStatement :: forall model (table :: Symbol).
(Table model, model ~ GetModelByTableName table, KnownSymbol table,
 FromRowHasql model) =>
QueryBuilder table -> Statement () (Vector model)
buildQueryVectorStatement !QueryBuilder table
queryBuilder =
    SQLQuery -> Result (Vector model) -> Statement () (Vector model)
forall a. SQLQuery -> Result a -> Statement () a
buildStatement (QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder) (Row model -> Result (Vector model)
forall a. Row a -> Result (Vector a)
Decoders.rowVector (forall a. FromRowHasql a => Row a
hasqlRowDecoder @model))

-- | Build a statement that fetches at most one row (adds LIMIT 1).
buildQueryMaybeStatement :: forall model table.
    ( Table model
    , model ~ GetModelByTableName table, KnownSymbol table, FromRowHasql model
    ) => QueryBuilder table -> Hasql.Statement () (Maybe model)
buildQueryMaybeStatement :: forall model (table :: Symbol).
(Table model, model ~ GetModelByTableName table, KnownSymbol table,
 FromRowHasql model) =>
QueryBuilder table -> Statement () (Maybe model)
buildQueryMaybeStatement !QueryBuilder table
queryBuilder =
    SQLQuery -> Result (Maybe model) -> Statement () (Maybe model)
forall a. SQLQuery -> Result a -> Statement () a
buildStatement ((QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder) { limitClause = Just 1 }) (Row model -> Result (Maybe model)
forall a. Row a -> Result (Maybe a)
Decoders.rowMaybe (forall a. FromRowHasql a => Row a
hasqlRowDecoder @model))

-- | Build a @SELECT COUNT(*)@ statement wrapping a query builder.
buildCountStatement :: forall table.
    ( KnownSymbol table
    ) => QueryBuilder table -> Hasql.Statement () Int64
buildCountStatement :: forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> Statement () Int64
buildCountStatement !QueryBuilder table
queryBuilder =
    Text -> SQLQuery -> Text -> Result Int64 -> Statement () Int64
forall a. Text -> SQLQuery -> Text -> Result a -> Statement () a
buildWrappedStatement Text
"SELECT COUNT(*) FROM (" (QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder) Text
") AS _count_values" (Row Int64 -> Result Int64
forall a. Row a -> Result a
Decoders.singleRow (NullableOrNot Value Int64 -> Row Int64
forall a. NullableOrNot Value a -> Row a
Decoders.column (Value Int64 -> NullableOrNot Value Int64
forall (decoder :: * -> *) a. decoder a -> NullableOrNot decoder a
Decoders.nonNullable Value Int64
Decoders.int8)))

-- | Build a @SELECT EXISTS@ statement wrapping a query builder.
buildExistsStatement :: forall table.
    ( KnownSymbol table
    ) => QueryBuilder table -> Hasql.Statement () Bool
buildExistsStatement :: forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> Statement () Bool
buildExistsStatement !QueryBuilder table
queryBuilder =
    Text -> SQLQuery -> Text -> Result Bool -> Statement () Bool
forall a. Text -> SQLQuery -> Text -> Result a -> Statement () a
buildWrappedStatement Text
"SELECT EXISTS (" (QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder) Text
") AS _exists_values" (Row Bool -> Result Bool
forall a. Row a -> Result a
Decoders.singleRow (NullableOrNot Value Bool -> Row Bool
forall a. NullableOrNot Value a -> Row a
Decoders.column (Value Bool -> NullableOrNot Value Bool
forall (decoder :: * -> *) a. decoder a -> NullableOrNot decoder a
Decoders.nonNullable Value Bool
Decoders.bool)))