graphql-engine/server/src-lib/Hasura/RQL/DML/Mutation.hs

129 lines
4.6 KiB
Haskell

module Hasura.RQL.DML.Mutation
( Mutation(..)
, runMutation
, mutateAndFetchCols
, mkSelCTEFromColVals
)
where
import Hasura.Prelude
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as DS
import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
import Hasura.EncJSON
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Returning
import Hasura.RQL.DML.Select
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
data Mutation
= Mutation
{ _mTable :: !QualifiedTable
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
, _mOutput :: !MutationOutput
, _mCols :: ![PGColumnInfo]
, _mStrfyNum :: !Bool
} deriving (Show, Eq)
runMutation :: Mutation -> Q.TxE QErr EncJSON
runMutation mut =
bool (mutateAndReturn mut) (mutateAndSel mut) $
hasNestedFld $ _mOutput mut
mutateAndReturn :: Mutation -> Q.TxE QErr EncJSON
mutateAndReturn (Mutation qt (cte, p) mutationOutput allCols strfyNum) =
encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith)
(toList p) True
where
selWith = mkMutationOutputExp qt allCols Nothing cte mutationOutput strfyNum
mutateAndSel :: Mutation -> Q.TxE QErr EncJSON
mutateAndSel (Mutation qt q mutationOutput allCols strfyNum) = do
-- Perform mutation and fetch unique columns
MutateResp _ columnVals <- mutateAndFetchCols qt allCols q strfyNum
selCTE <- mkSelCTEFromColVals qt allCols columnVals
let selWith = mkMutationOutputExp qt allCols Nothing selCTE mutationOutput strfyNum
-- Perform select query and fetch returning fields
encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith) [] True
mutateAndFetchCols
:: QualifiedTable
-> [PGColumnInfo]
-> (S.CTE, DS.Seq Q.PrepArg)
-> Bool
-> Q.TxE QErr (MutateResp TxtEncodedPGVal)
mutateAndFetchCols qt cols (cte, p) strfyNum =
Q.getAltJ . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sql) (toList p) True
where
aliasIden = Iden $ qualObjectToText qt <> "__mutation_result"
tabFrom = FromIden aliasIden
tabPerm = TablePerm annBoolExpTrue Nothing
selFlds = flip map cols $
\ci -> (fromPGCol $ pgiColumn ci, mkAnnColFieldAsText ci)
sql = toSQL selectWith
selectWith = S.SelectWith [(S.Alias aliasIden, cte)] select
select = S.mkSelect {S.selExtr = [S.Extractor extrExp Nothing]}
extrExp = S.applyJsonBuildObj
[ S.SELit "affected_rows", affRowsSel
, S.SELit "returning_columns", colSel
]
affRowsSel = S.SESelect $
S.mkSelect
{ S.selExtr = [S.Extractor S.countStar Nothing]
, S.selFrom = Just $ S.FromExp [S.FIIden aliasIden]
}
colSel = S.SESelect $ mkSQLSelect JASMultipleRows $
AnnSelG selFlds tabFrom tabPerm noTableArgs strfyNum
-- | Note:- Using sorted columns is necessary to enable casting the rows returned by VALUES expression to table type.
-- For example, let's consider the table, `CREATE TABLE test (id serial primary key, name text not null, age int)`.
-- The generated values expression should be in order of columns;
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
mkSelCTEFromColVals
:: (MonadError QErr m)
=> QualifiedTable -> [PGColumnInfo] -> [ColumnValues TxtEncodedPGVal] -> m S.CTE
mkSelCTEFromColVals qt allCols colVals =
S.CTESelect <$> case colVals of
[] -> return selNoRows
_ -> do
tuples <- mapM mkTupsFromColVal colVals
let fromItem = S.FIValues (S.ValuesExp tuples) (S.Alias rowAlias) Nothing
return S.mkSelect
{ S.selExtr = [extractor]
, S.selFrom = Just $ S.FromExp [fromItem]
}
where
rowAlias = Iden "row"
extractor = S.selectStar' $ S.QualIden rowAlias $ Just $ S.TypeAnn $ toSQLTxt qt
sortedCols = sortCols allCols
mkTupsFromColVal colVal =
fmap S.TupleExp $ forM sortedCols $ \ci -> do
let pgCol = pgiColumn ci
val <- onNothing (Map.lookup pgCol colVal) $
throw500 $ "column " <> pgCol <<> " not found in returning values"
pure $ txtEncodedToSQLExp (pgiType ci) val
selNoRows =
S.mkSelect { S.selExtr = [S.selectStar]
, S.selFrom = Just $ S.mkSimpleFromExp qt
, S.selWhere = Just $ S.WhereFrag $ S.BELit False
}
txtEncodedToSQLExp colTy = \case
TENull -> S.SENull
TELit textValue ->
S.withTyAnn (unsafePGColumnToRepresentation colTy) $ S.SELit textValue