packages feed

squeal-postgresql-qq-0.1.0.0: src/Squeal/QuasiQuotes/Update.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE ViewPatterns #-}

-- | Description: Translate update statements.
module Squeal.QuasiQuotes.Update (
  toSquealUpdate,
) where

import Data.Text (Text)
import Control.Monad (when)
import Data.Maybe (isJust)
import Language.Haskell.TH.Syntax (Exp(AppE, ConE, LabelE, VarE), Q)
import Prelude
  ( Applicative(pure), Foldable(foldr), Maybe(Just, Nothing), MonadFail(fail)
  , Traversable(mapM), ($), (<$>)
  )
import Squeal.QuasiQuotes.Common
  ( getIdentText, renderPGTAExpr, renderPGTTableRef, renderPGTTargetList
  )
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as Text
import qualified PostgresqlSyntax.Ast as PGT_AST
import qualified Squeal.PostgreSQL as S


toSquealUpdate :: PGT_AST.UpdateStmt -> Q Exp
toSquealUpdate
  ( PGT_AST.UpdateStmt
      maybeWithClause
      relationExprOptAlias
      setClauseList
      maybeFromClause
      maybeWhereClause
      maybeReturningClause
    ) = do
    when (isJust maybeWithClause) $
      fail "WITH clauses are not supported in UPDATE statements yet."
    targetTableExp <- renderPGTRelationExprOptAlias' relationExprOptAlias

    setClauseExp <- renderPGTSetClauseList setClauseList

    usingClauseExp <-
      case maybeFromClause of
        Nothing -> pure $ ConE 'S.NoUsing
        Just fromClause -> AppE (ConE 'S.Using) <$> renderPGTTableRef fromClause

    whereConditionExp <-
      case maybeWhereClause of
        Nothing ->
          fail
            "UPDATE statements must have a WHERE clause for safety. Use WHERE TRUE to update all rows."
        Just (PGT_AST.ExprWhereOrCurrentClause whereAExpr) -> renderPGTAExpr whereAExpr
        Just (PGT_AST.CursorWhereOrCurrentClause _) -> fail "WHERE CURRENT OF is not supported."

    returningClauseExp <-
      case maybeReturningClause of
        Nothing -> pure $ ConE 'S.Returning_ `AppE` ConE 'S.Nil
        Just returningClause -> AppE (ConE 'S.Returning) <$> renderPGTTargetList returningClause

    pure $
      ( VarE 'S.update
          `AppE` targetTableExp
          `AppE` setClauseExp
          `AppE` usingClauseExp
          `AppE` whereConditionExp
          `AppE` returningClauseExp
      )


renderPGTRelationExprOptAlias' :: PGT_AST.RelationExprOptAlias -> Q Exp
renderPGTRelationExprOptAlias' (PGT_AST.RelationExprOptAlias relationExpr maybeAlias) = do
  (tableName, schemaName) <-
    case relationExpr of
      PGT_AST.SimpleRelationExpr (PGT_AST.SimpleQualifiedName ident) _ ->
        pure (getIdentText ident, Nothing)
      PGT_AST.SimpleRelationExpr
        ( PGT_AST.IndirectedQualifiedName
            schemaIdent
            (PGT_AST.AttrNameIndirectionEl tableIdent NE.:| [])
          )
        _ ->
          pure (getIdentText tableIdent, Just (getIdentText schemaIdent))
      _ -> fail "Unsupported relation expression in UPDATE statement."

  let
    aliasName :: Text
    aliasName =
      case maybeAlias of
        Just (_, colId) -> getIdentText colId
        Nothing -> tableName

    qualifiedAlias :: Exp
    qualifiedAlias =
      case schemaName of
        Nothing -> LabelE (Text.unpack tableName)
        Just schema ->
          VarE '(S.!)
            `AppE` LabelE (Text.unpack schema)
            `AppE` LabelE (Text.unpack tableName)

  pure $ VarE 'S.as `AppE` qualifiedAlias `AppE` LabelE (Text.unpack aliasName)


renderPGTSetClauseList :: PGT_AST.SetClauseList -> Q Exp
renderPGTSetClauseList setClauses = do
  renderedItems <- mapM renderPGTSetClause (NE.toList setClauses)
  pure $
    foldr
      (\item acc -> ConE '(S.:*) `AppE` item `AppE` acc)
      (ConE 'S.Nil)
      renderedItems


renderPGTSetClause :: PGT_AST.SetClause -> Q Exp
renderPGTSetClause = \case
  PGT_AST.TargetSetClause (PGT_AST.SetTarget colId maybeIndirection) aExpr -> do
    when (isJust maybeIndirection) $
      fail "UPDATE SET with indirection (e.g., array access) is not supported."
    let colNameStr = Text.unpack (getIdentText colId)
    renderedExpr <- renderPGTAExpr aExpr
    pure $
      VarE 'S.as
        `AppE` (ConE 'S.Set `AppE` renderedExpr)
        `AppE` LabelE colNameStr
  PGT_AST.TargetListSetClause _ _ ->
    fail
      "UPDATE with multiple SET targets (e.g. (col1, col2) = (val1, val2)) is not yet supported."