content: {-|
  Module: Squeal.PostgreSQL.Update
  Description: update statements
  Copyright: (c) Eitan Chatav, 2019
  Maintainer: eitan@morphism.tech
  Stability: experimental
  
  update statements
  -}
  
  {-# LANGUAGE
      DeriveGeneric
    , DerivingStrategies
    , FlexibleContexts
    , FlexibleInstances
    , GADTs
    , GeneralizedNewtypeDeriving
    , LambdaCase
    , MultiParamTypeClasses
    , OverloadedStrings
    , PatternSynonyms
    , QuantifiedConstraints
    , RankNTypes
    , ScopedTypeVariables
    , TypeApplications
    , TypeFamilies
    , DataKinds
    , PolyKinds
    , TypeOperators
    , UndecidableInstances
  #-}
  
  module Squeal.PostgreSQL.Manipulation.Update
    ( -- * Update
      update
    , update_
    ) where
  
  import Data.ByteString hiding (foldr)
  import GHC.TypeLits
  
  import qualified Generics.SOP as SOP
  
  import Squeal.PostgreSQL.Type.Alias
  import Squeal.PostgreSQL.Expression
  import Squeal.PostgreSQL.Expression.Default
  import Squeal.PostgreSQL.Expression.Logic
  import Squeal.PostgreSQL.Manipulation
  import Squeal.PostgreSQL.Type.List
  import Squeal.PostgreSQL.Render
  import Squeal.PostgreSQL.Type.Schema
  
  -- $setup
  -- >>> import Squeal.PostgreSQL
  
  renderUpdate
    :: (forall x. RenderSQL (expr x))
    => Aliased (Optional expr) ty
    -> ByteString
  renderUpdate (expr `As` col) = renderSQL col <+> "=" <+> renderSQL expr
  
  {-----------------------------------------
  UPDATE statements
  -----------------------------------------}
  
  {- | An `update` command changes the values of the specified columns
  in all rows that satisfy the condition.
  
  >>> type Columns = '["col1" ::: 'Def :=> 'NotNull 'PGint4, "col2" ::: 'NoDef :=> 'NotNull 'PGint4]
  >>> type Schema = '["tab1" ::: 'Table ('[] :=> Columns), "tab2" ::: 'Table ('[] :=> Columns)]
  >>> :{
  let
    manp :: Manipulation with (Public Schema) '[]
      '["col1" ::: 'NotNull 'PGint4,
        "col2" ::: 'NotNull 'PGint4]
    manp = update
      (#tab1 `as` #t1)
      (Set (2 + #t2 ! #col2) `as` #col1)
      (Using (table (#tab2 `as` #t2)))
      (#t1 ! #col1 ./= #t2 ! #col2)
      (Returning (#t1 & DotStar))
  in printSQL manp
  :}
  UPDATE "tab1" AS "t1" SET "col1" = ((2 :: int4) + "t2"."col2") FROM "tab2" AS "t2" WHERE ("t1"."col1" <> "t2"."col2") RETURNING "t1".*
  -}
  update
    :: ( Has sch db schema
       , Has tab0 schema ('Table table)
       , Updatable table updates
       , SOP.SListI row )
    => Aliased (QualifiedAlias sch) (tab ::: tab0) -- ^ table to update
    -> NP (Aliased (Optional (Expression 'Ungrouped '[] with db params (tab ::: TableToRow table ': from)))) updates
    -- ^ update expressions, modified values to replace old values
    -> UsingClause with db params from
    -- ^ FROM A table expression allowing columns from other tables to appear
    -- in the WHERE condition and update expressions.
    -> Condition  'Ungrouped '[] with db params (tab ::: TableToRow table ': from)
    -- ^ WHERE condition under which to perform update on a row
    -> ReturningClause with db params (tab ::: TableToRow table ': from) row -- ^ results to return
    -> Manipulation with db params row
  update (tab0 `As` tab) columns using wh returning = UnsafeManipulation $
    "UPDATE"
    <+> renderSQL tab0 <+> "AS" <+> renderSQL tab
    <+> "SET"
    <+> renderCommaSeparated renderUpdate columns
    <> case using of
      NoUsing -> ""
      Using tables -> " FROM" <+> renderSQL tables
    <+> "WHERE" <+> renderSQL wh
    <> renderSQL returning
  
  {- | Update a row returning `Nil`.
  
  >>> type Columns = '["col1" ::: 'Def :=> 'NotNull 'PGint4, "col2" ::: 'NoDef :=> 'NotNull 'PGint4]
  >>> type Schema = '["tab" ::: 'Table ('[] :=> Columns)]
  >>> :{
  let
    manp :: Manipulation with (Public Schema) '[] '[]
    manp = update_ #tab (Set 2 `as` #col1) (#col1 ./= #col2)
  in printSQL manp
  :}
  UPDATE "tab" AS "tab" SET "col1" = (2 :: int4) WHERE ("col1" <> "col2")
  -}
  update_
    :: ( Has sch db schema
       , Has tab0 schema ('Table table)
       , KnownSymbol tab
       , Updatable table updates )
    => Aliased (QualifiedAlias sch) (tab ::: tab0) -- ^ table to update
    -> NP (Aliased (Optional (Expression 'Ungrouped '[] with db params '[tab ::: TableToRow table]))) updates
    -- ^ modified values to replace old values
    -> Condition  'Ungrouped '[] with db params '[tab ::: TableToRow table]
    -- ^ condition under which to perform update on a row
    -> Manipulation with db params '[]
  update_ tab columns wh = update tab columns NoUsing wh (Returning_ Nil)
  