{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

Functions for working with executable @SELECT@ statements. The 'Select' type is
a value that can be passed around and executed later. The 'Select' is directly
associated with how to decode the rows returned by the query. This means it
can be safely executed via 'executeSelect' and used to decode the rows. It is a
lower-level API than the entity select functions in
"Orville.PostgreSQL.Execution.EntityOperations", but not as primitive as
"Orville.PostgreSQL.Expr.Query".

@since 1.0.0.0
-}
module Orville.PostgreSQL.Execution.Select
  ( Select
  , executeSelect
  , useSelect
  , selectToQueryExpr
  , selectTable
  , selectMarshalledColumns
  , rawSelectQueryExpr
  )
where

import qualified Orville.PostgreSQL.Execution.Execute as Execute
import qualified Orville.PostgreSQL.Execution.QueryType as QueryType
import qualified Orville.PostgreSQL.Execution.SelectOptions as SelectOptions
import qualified Orville.PostgreSQL.Expr as Expr
import Orville.PostgreSQL.Marshall.SqlMarshaller (AnnotatedSqlMarshaller, marshallerDerivedColumns, unannotatedSqlMarshaller)
import qualified Orville.PostgreSQL.Monad as Monad
import Orville.PostgreSQL.Schema (TableDefinition, tableMarshaller, tableName)

{- |
  Represents a @SELECT@ statement that can be executed against a database. A
  'Select' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that will be
  used to decode the database result set when it is executed.

@since 1.0.0.0
-}
data Select readEntity where
  Select :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.QueryExpr -> Select readEntity

{- |
  Extracts the query that will be run when the select is executed. Normally you
  don't want to extract the query and run it yourself, but this function is
  useful to view the query for debugging or query explanation.

@since 1.0.0.0
-}
selectToQueryExpr :: Select readEntity -> Expr.QueryExpr
selectToQueryExpr :: forall readEntity. Select readEntity -> QueryExpr
selectToQueryExpr (Select AnnotatedSqlMarshaller writeEntity readEntity
_ QueryExpr
query) = QueryExpr
query

{- |
  Executes the database query for the 'Select' and uses its
  'Orville.PostgreSQL.SqlMarshaller' to decode the result set.

@since 1.0.0.0
-}
executeSelect :: Monad.MonadOrville m => Select row -> m [row]
executeSelect :: forall (m :: * -> *) row. MonadOrville m => Select row -> m [row]
executeSelect =
  (forall writeEntity.
 QueryExpr -> AnnotatedSqlMarshaller writeEntity row -> m [row])
-> Select row -> m [row]
forall readEntity a.
(forall writeEntity.
 QueryExpr -> AnnotatedSqlMarshaller writeEntity readEntity -> a)
-> Select readEntity -> a
useSelect (QueryType
-> QueryExpr -> AnnotatedSqlMarshaller writeEntity row -> m [row]
forall (m :: * -> *) sql writeEntity readEntity.
(MonadOrville m, SqlExpression sql) =>
QueryType
-> sql
-> AnnotatedSqlMarshaller writeEntity readEntity
-> m [readEntity]
Execute.executeAndDecode QueryType
QueryType.SelectQuery)

{- |
  Runs a function allowing it to use the data elements containted within the
  'Select' it pleases. The marshaller that the function is provided can be use
  to decode results from the query.

@since 1.0.0.0
-}
useSelect ::
  ( forall writeEntity.
    Expr.QueryExpr ->
    AnnotatedSqlMarshaller writeEntity readEntity ->
    a
  ) ->
  Select readEntity ->
  a
useSelect :: forall readEntity a.
(forall writeEntity.
 QueryExpr -> AnnotatedSqlMarshaller writeEntity readEntity -> a)
-> Select readEntity -> a
useSelect forall writeEntity.
QueryExpr -> AnnotatedSqlMarshaller writeEntity readEntity -> a
f (Select AnnotatedSqlMarshaller writeEntity readEntity
marshaller QueryExpr
query) =
  QueryExpr -> AnnotatedSqlMarshaller writeEntity readEntity -> a
forall writeEntity.
QueryExpr -> AnnotatedSqlMarshaller writeEntity readEntity -> a
f QueryExpr
query AnnotatedSqlMarshaller writeEntity readEntity
marshaller

{- |
  Builds a 'Select' that will select all the columns described in the
  'TableDefinition'. This is the safest way to build a 'Select', because table
  name and columns are all read from the 'TableDefinition'. If the table is
  being managed with Orville auto-migrations, this will match the schema in the
  database.

@since 1.0.0.0
-}
selectTable ::
  TableDefinition key writeEntity readEntity ->
  SelectOptions.SelectOptions ->
  Select readEntity
selectTable :: forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> SelectOptions -> Select readEntity
selectTable TableDefinition key writeEntity readEntity
tableDef =
  AnnotatedSqlMarshaller writeEntity readEntity
-> Qualified TableName -> SelectOptions -> Select readEntity
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> Qualified TableName -> SelectOptions -> Select readEntity
selectMarshalledColumns (TableDefinition key writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
tableMarshaller TableDefinition key writeEntity readEntity
tableDef) (TableDefinition key writeEntity readEntity -> Qualified TableName
forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity -> Qualified TableName
tableName TableDefinition key writeEntity readEntity
tableDef)

{- |
  Builds a 'Select' that will select the columns described by the marshaller
  from the specified table. It is up to the caller to ensure that the columns
  in the marshaller make sense for the table.

  This function is useful for querying a subset of table columns using a custom
  marshaller.

@since 1.0.0.0
-}
selectMarshalledColumns ::
  AnnotatedSqlMarshaller writeEntity readEntity ->
  Expr.Qualified Expr.TableName ->
  SelectOptions.SelectOptions ->
  Select readEntity
selectMarshalledColumns :: forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> Qualified TableName -> SelectOptions -> Select readEntity
selectMarshalledColumns AnnotatedSqlMarshaller writeEntity readEntity
marshaller Qualified TableName
qualifiedTableName SelectOptions
selectOptions =
  AnnotatedSqlMarshaller writeEntity readEntity
-> QueryExpr -> Select readEntity
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> QueryExpr -> Select readEntity
rawSelectQueryExpr AnnotatedSqlMarshaller writeEntity readEntity
marshaller (QueryExpr -> Select readEntity) -> QueryExpr -> Select readEntity
forall a b. (a -> b) -> a -> b
$
    SelectList -> TableReferenceList -> SelectOptions -> QueryExpr
SelectOptions.selectOptionsQueryExpr
      ([DerivedColumn] -> SelectList
Expr.selectDerivedColumns (SqlMarshaller writeEntity readEntity -> [DerivedColumn]
forall writeEntity readEntity.
SqlMarshaller writeEntity readEntity -> [DerivedColumn]
marshallerDerivedColumns (SqlMarshaller writeEntity readEntity -> [DerivedColumn])
-> (AnnotatedSqlMarshaller writeEntity readEntity
    -> SqlMarshaller writeEntity readEntity)
-> AnnotatedSqlMarshaller writeEntity readEntity
-> [DerivedColumn]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnnotatedSqlMarshaller writeEntity readEntity
-> SqlMarshaller writeEntity readEntity
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> SqlMarshaller writeEntity readEntity
unannotatedSqlMarshaller (AnnotatedSqlMarshaller writeEntity readEntity -> [DerivedColumn])
-> AnnotatedSqlMarshaller writeEntity readEntity -> [DerivedColumn]
forall a b. (a -> b) -> a -> b
$ AnnotatedSqlMarshaller writeEntity readEntity
marshaller))
      (Qualified TableName -> TableReferenceList
Expr.referencesTable Qualified TableName
qualifiedTableName)
      SelectOptions
selectOptions

{- |
  Builds a 'Select' that will execute the specified query and use the given
  'Orville.PostgreSQL.SqlMarshaller' to decode it. It is up to the caller to
  ensure that the given 'Expr.QueryExpr' makes sense and produces a result set
  that the 'Orville.PostgreSQL.SqlMarshaller' can decode.

  This is the lowest level of escape hatch available for 'Select'. The caller
  can build any query that Orville supports using the expression-building
  functions, or use @RawSql.fromRawSql@ to build a raw 'Expr.QueryExpr'.

@since 1.0.0.0
-}
rawSelectQueryExpr ::
  AnnotatedSqlMarshaller writeEntity readEntity ->
  Expr.QueryExpr ->
  Select readEntity
rawSelectQueryExpr :: forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> QueryExpr -> Select readEntity
rawSelectQueryExpr = AnnotatedSqlMarshaller writeEntity readEntity
-> QueryExpr -> Select readEntity
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> QueryExpr -> Select readEntity
Select