{-# LANGUAGE GADTs #-}

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

Functions for working with executable @UPDATE@ statements. The 'Update' type is
a value that can be passed around and executed later. The 'Update' is directly
associated with the presence of a returning clause and how to decode any rows
returned by that clause. This means it can be safely executed via
'executeUpdate' or 'executeUpdateReturnEntities' as appropriate. It is a
lower-level API than the entity update functions in
"Orville.PostgreSQL.Execution.EntityOperations", but not as primitive as
"Orville.PostgreSQL.Expr.Update".

@since 1.0.0.0
-}
module Orville.PostgreSQL.Execution.Update
  ( Update
  , updateToUpdateExpr
  , executeUpdate
  , executeUpdateReturnEntities
  , updateToTableReturning
  , updateToTable
  , updateToTableFieldsReturning
  , updateToTableFields
  , rawUpdateExpr
  )
where

import Data.List.NonEmpty (NonEmpty, nonEmpty)

import qualified Orville.PostgreSQL.Execution.Execute as Execute
import qualified Orville.PostgreSQL.Execution.QueryType as QueryType
import Orville.PostgreSQL.Execution.ReturningOption (NoReturningClause, ReturningClause, ReturningOption (WithReturning, WithoutReturning))
import qualified Orville.PostgreSQL.Expr as Expr
import Orville.PostgreSQL.Marshall (AnnotatedSqlMarshaller, marshallEntityToSetClauses, unannotatedSqlMarshaller)
import qualified Orville.PostgreSQL.Monad as Monad
import Orville.PostgreSQL.Schema (HasKey, TableDefinition, mkTableReturningClause, primaryKeyEquals, tableMarshaller, tableName, tablePrimaryKey)

{- |
  Represents an @UPDATE@ statement that can be executed against a database. An
  'Update' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that, when the
  update returns data from the database, will be used to decode the database
  result set when it is executed.

@since 1.0.0.0
-}
data Update readEntity returningClause where
  UpdateNoReturning :: Expr.UpdateExpr -> Update readEntity NoReturningClause
  UpdateReturning :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.UpdateExpr -> Update readEntity ReturningClause

{- |
  Extracts the query that will be run when the update 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
-}
updateToUpdateExpr :: Update readEntity returningClause -> Expr.UpdateExpr
updateToUpdateExpr :: forall readEntity returningClause.
Update readEntity returningClause -> UpdateExpr
updateToUpdateExpr (UpdateNoReturning UpdateExpr
expr) = UpdateExpr
expr
updateToUpdateExpr (UpdateReturning AnnotatedSqlMarshaller writeEntity readEntity
_ UpdateExpr
expr) = UpdateExpr
expr

{- |
  Executes the database query for the 'Update' and returns the number of
  affected rows.

@since 1.0.0.0
-}
executeUpdate :: Monad.MonadOrville m => Update readEntity returningClause -> m Int
executeUpdate :: forall (m :: * -> *) readEntity returningClause.
MonadOrville m =>
Update readEntity returningClause -> m Int
executeUpdate =
  QueryType -> UpdateExpr -> m Int
forall (m :: * -> *) sql.
(MonadOrville m, SqlExpression sql) =>
QueryType -> sql -> m Int
Execute.executeAndReturnAffectedRows QueryType
QueryType.UpdateQuery (UpdateExpr -> m Int)
-> (Update readEntity returningClause -> UpdateExpr)
-> Update readEntity returningClause
-> m Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Update readEntity returningClause -> UpdateExpr
forall readEntity returningClause.
Update readEntity returningClause -> UpdateExpr
updateToUpdateExpr

{- |
  Executes the database query for the 'Update' and uses its
  'AnnotatedSqlMarshaller' to decode any rows that were just updated, as
  returned via a RETURNING clause.

@since 1.0.0.0
-}
executeUpdateReturnEntities :: Monad.MonadOrville m => Update readEntity ReturningClause -> m [readEntity]
executeUpdateReturnEntities :: forall (m :: * -> *) readEntity.
MonadOrville m =>
Update readEntity ReturningClause -> m [readEntity]
executeUpdateReturnEntities (UpdateReturning AnnotatedSqlMarshaller writeEntity readEntity
marshaller UpdateExpr
expr) =
  QueryType
-> UpdateExpr
-> AnnotatedSqlMarshaller writeEntity readEntity
-> m [readEntity]
forall (m :: * -> *) sql writeEntity readEntity.
(MonadOrville m, SqlExpression sql) =>
QueryType
-> sql
-> AnnotatedSqlMarshaller writeEntity readEntity
-> m [readEntity]
Execute.executeAndDecode QueryType
QueryType.UpdateQuery UpdateExpr
expr AnnotatedSqlMarshaller writeEntity readEntity
marshaller

{- |
  Builds an 'Update' that will update all of the writable columns described in
  the 'TableDefinition' without returning the data as seen by the database.

  This function returns 'Nothing' if the 'TableDefinition' has no columns,
  which would otherwise generate and 'Update' with invalid SQL syntax.

@since 1.0.0.0
-}
updateToTable ::
  TableDefinition (HasKey key) writeEntity readEntity ->
  key ->
  writeEntity ->
  Maybe (Update readEntity NoReturningClause)
updateToTable :: forall key writeEntity readEntity.
TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity NoReturningClause)
updateToTable =
  ReturningOption NoReturningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity NoReturningClause)
forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity returningClause)
updateTable ReturningOption NoReturningClause
WithoutReturning

{- |
  Builds an 'Update' that will update all of the writable columns described in
  the 'TableDefinition' and return the data as seen by the database. This is
  useful for getting database-managed columns such as auto-incrementing
  identifiers and sequences.

  This function returns 'Nothing' if the 'TableDefinition' has no columns,
  which would otherwise generate an 'Update' with invalid SQL syntax.

@since 1.0.0.0
-}
updateToTableReturning ::
  TableDefinition (HasKey key) writeEntity readEntity ->
  key ->
  writeEntity ->
  Maybe (Update readEntity ReturningClause)
updateToTableReturning :: forall key writeEntity readEntity.
TableDefinition (HasKey key) writeEntity readEntity
-> key -> writeEntity -> Maybe (Update readEntity ReturningClause)
updateToTableReturning =
  ReturningOption ReturningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity ReturningClause)
forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity returningClause)
updateTable ReturningOption ReturningClause
WithReturning

{- |
  Builds an 'Update' that will apply the specified column set clauses to rows
  within the specified table without returning the data as seen by the database.

@since 1.0.0.0
-}
updateToTableFields ::
  TableDefinition key writeEntity readEntity ->
  NonEmpty Expr.SetClause ->
  Maybe Expr.BooleanExpr ->
  Update readEntity NoReturningClause
updateToTableFields :: forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity NoReturningClause
updateToTableFields =
  ReturningOption NoReturningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity NoReturningClause
forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity returningClause
updateFields ReturningOption NoReturningClause
WithoutReturning

{- |
  Builds an 'Update' that will apply the specified column set clauses to rows
  within the specified table and return the updated version of any rows affected by
  the update state by using a @RETURNING@ clause.

@since 1.0.0.0
-}
updateToTableFieldsReturning ::
  TableDefinition key writeEntity readEntity ->
  NonEmpty Expr.SetClause ->
  Maybe Expr.BooleanExpr ->
  Update readEntity ReturningClause
updateToTableFieldsReturning :: forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity ReturningClause
updateToTableFieldsReturning =
  ReturningOption ReturningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity ReturningClause
forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity returningClause
updateFields ReturningOption ReturningClause
WithReturning

{- |
  Builds an 'Update' that will execute the specified query and use the given 'AnnotatedSqlMarshaller' to
  decode it. It is up to the caller to ensure that the given 'Expr.UpdateExpr' makes sense and
  produces a value that can be stored, as well as returning a result that the 'AnnotatedSqlMarshaller' can
  decode.

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

@since 1.0.0.0
-}
rawUpdateExpr :: ReturningOption returningClause -> AnnotatedSqlMarshaller writeEntity readEntity -> Expr.UpdateExpr -> Update readEntity returningClause
rawUpdateExpr :: forall returningClause writeEntity readEntity.
ReturningOption returningClause
-> AnnotatedSqlMarshaller writeEntity readEntity
-> UpdateExpr
-> Update readEntity returningClause
rawUpdateExpr ReturningOption returningClause
WithReturning AnnotatedSqlMarshaller writeEntity readEntity
marshaller = AnnotatedSqlMarshaller writeEntity readEntity
-> UpdateExpr -> Update readEntity ReturningClause
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> UpdateExpr -> Update readEntity ReturningClause
UpdateReturning AnnotatedSqlMarshaller writeEntity readEntity
marshaller
rawUpdateExpr ReturningOption returningClause
WithoutReturning AnnotatedSqlMarshaller writeEntity readEntity
_ = UpdateExpr -> Update readEntity returningClause
UpdateExpr -> Update readEntity NoReturningClause
forall readEntity.
UpdateExpr -> Update readEntity NoReturningClause
UpdateNoReturning

-- an internal helper function for creating an update with a given
-- `ReturningOption` to a single entity in a table, setting all the
-- columns found in the table's SQL marshaller.
updateTable ::
  ReturningOption returningClause ->
  TableDefinition (HasKey key) writeEntity readEntity ->
  key ->
  writeEntity ->
  Maybe (Update readEntity returningClause)
updateTable :: forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> key
-> writeEntity
-> Maybe (Update readEntity returningClause)
updateTable ReturningOption returningClause
returningOption TableDefinition (HasKey key) writeEntity readEntity
tableDef key
key writeEntity
writeEntity = do
  NonEmpty SetClause
setClauses <-
    [SetClause] -> Maybe (NonEmpty SetClause)
forall a. [a] -> Maybe (NonEmpty a)
nonEmpty ([SetClause] -> Maybe (NonEmpty SetClause))
-> [SetClause] -> Maybe (NonEmpty SetClause)
forall a b. (a -> b) -> a -> b
$
      SqlMarshaller writeEntity readEntity -> writeEntity -> [SetClause]
forall writeEntity readEntity.
SqlMarshaller writeEntity readEntity -> writeEntity -> [SetClause]
marshallEntityToSetClauses
        (AnnotatedSqlMarshaller writeEntity readEntity
-> SqlMarshaller writeEntity readEntity
forall writeEntity readEntity.
AnnotatedSqlMarshaller writeEntity readEntity
-> SqlMarshaller writeEntity readEntity
unannotatedSqlMarshaller (AnnotatedSqlMarshaller writeEntity readEntity
 -> SqlMarshaller writeEntity readEntity)
-> AnnotatedSqlMarshaller writeEntity readEntity
-> SqlMarshaller writeEntity readEntity
forall a b. (a -> b) -> a -> b
$ TableDefinition (HasKey key) writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
tableMarshaller TableDefinition (HasKey key) writeEntity readEntity
tableDef)
        writeEntity
writeEntity

  let
    isEntityKey :: BooleanExpr
isEntityKey =
      PrimaryKey key -> key -> BooleanExpr
forall key. PrimaryKey key -> key -> BooleanExpr
primaryKeyEquals
        (TableDefinition (HasKey key) writeEntity readEntity
-> PrimaryKey key
forall key writeEntity readEntity.
TableDefinition (HasKey key) writeEntity readEntity
-> PrimaryKey key
tablePrimaryKey TableDefinition (HasKey key) writeEntity readEntity
tableDef)
        key
key
  Update readEntity returningClause
-> Maybe (Update readEntity returningClause)
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Update readEntity returningClause
 -> Maybe (Update readEntity returningClause))
-> Update readEntity returningClause
-> Maybe (Update readEntity returningClause)
forall a b. (a -> b) -> a -> b
$
    ReturningOption returningClause
-> TableDefinition (HasKey key) writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity returningClause
forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity returningClause
updateFields
      ReturningOption returningClause
returningOption
      TableDefinition (HasKey key) writeEntity readEntity
tableDef
      NonEmpty SetClause
setClauses
      (BooleanExpr -> Maybe BooleanExpr
forall a. a -> Maybe a
Just BooleanExpr
isEntityKey)

-- an internal helper function for creating an update with a given
-- `ReturningOption` to update the specified columns.
updateFields ::
  ReturningOption returningClause ->
  TableDefinition key writeEntity readEntity ->
  NonEmpty Expr.SetClause ->
  Maybe Expr.BooleanExpr ->
  Update readEntity returningClause
updateFields :: forall returningClause key writeEntity readEntity.
ReturningOption returningClause
-> TableDefinition key writeEntity readEntity
-> NonEmpty SetClause
-> Maybe BooleanExpr
-> Update readEntity returningClause
updateFields ReturningOption returningClause
returingOption TableDefinition key writeEntity readEntity
tableDef NonEmpty SetClause
setClauses Maybe BooleanExpr
mbWhereCondition =
  let
    whereClause :: Maybe WhereClause
whereClause =
      (BooleanExpr -> WhereClause)
-> Maybe BooleanExpr -> Maybe WhereClause
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BooleanExpr -> WhereClause
Expr.whereClause Maybe BooleanExpr
mbWhereCondition
  in
    ReturningOption returningClause
-> AnnotatedSqlMarshaller writeEntity readEntity
-> UpdateExpr
-> Update readEntity returningClause
forall returningClause writeEntity readEntity.
ReturningOption returningClause
-> AnnotatedSqlMarshaller writeEntity readEntity
-> UpdateExpr
-> Update readEntity returningClause
rawUpdateExpr ReturningOption returningClause
returingOption (TableDefinition key writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity
-> AnnotatedSqlMarshaller writeEntity readEntity
tableMarshaller TableDefinition key writeEntity readEntity
tableDef) (UpdateExpr -> Update readEntity returningClause)
-> UpdateExpr -> Update readEntity returningClause
forall a b. (a -> b) -> a -> b
$
      Qualified TableName
-> SetClauseList
-> Maybe WhereClause
-> Maybe ReturningExpr
-> UpdateExpr
Expr.updateExpr
        (TableDefinition key writeEntity readEntity -> Qualified TableName
forall key writeEntity readEntity.
TableDefinition key writeEntity readEntity -> Qualified TableName
tableName TableDefinition key writeEntity readEntity
tableDef)
        (NonEmpty SetClause -> SetClauseList
Expr.setClauseList NonEmpty SetClause
setClauses)
        Maybe WhereClause
whereClause
        (ReturningOption returningClause
-> TableDefinition key writeEntity readEntity
-> Maybe ReturningExpr
forall returningClause key writeEntity readEntty.
ReturningOption returningClause
-> TableDefinition key writeEntity readEntty -> Maybe ReturningExpr
mkTableReturningClause ReturningOption returningClause
returingOption TableDefinition key writeEntity readEntity
tableDef)