packages feed

orville-postgresql-1.1.0.0: src/Orville/PostgreSQL/Execution/Update.hs

{-# 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 (UpdateNoReturning expr) = expr
updateToUpdateExpr (UpdateReturning _ expr) = 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 =
  Execute.executeAndReturnAffectedRows QueryType.UpdateQuery . 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 (UpdateReturning marshaller expr) =
  Execute.executeAndDecode QueryType.UpdateQuery expr 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 =
  updateTable 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 =
  updateTable 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 =
  updateFields 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 =
  updateFields 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 WithReturning marshaller = UpdateReturning marshaller
rawUpdateExpr WithoutReturning _ = 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 returningOption tableDef key writeEntity = do
  setClauses <-
    nonEmpty $
      marshallEntityToSetClauses
        (unannotatedSqlMarshaller $ tableMarshaller tableDef)
        writeEntity

  let
    isEntityKey =
      primaryKeyEquals
        (tablePrimaryKey tableDef)
        key
  pure $
    updateFields
      returningOption
      tableDef
      setClauses
      (Just 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 returingOption tableDef setClauses mbWhereCondition =
  let
    whereClause =
      fmap Expr.whereClause mbWhereCondition
  in
    rawUpdateExpr returingOption (tableMarshaller tableDef) $
      Expr.updateExpr
        (tableName tableDef)
        Nothing
        (Expr.setClauseList setClauses)
        Nothing
        whereClause
        (mkTableReturningClause returingOption tableDef)