graphql-engine/server/src-lib/Hasura/RQL/DML/Mutation.hs
Rakesh Emmadi 5f274b5527 fix mutation returning when relationships are present (fix #1576) (#1703)
If returning field contains nested selections then mutation is performed in two steps
1. Mutation is performed with returning columns of any primary key and unique constraints
2. returning fields are queried on rows returned by selecting from table by filtering with column values returned in Step 1.

Since mutation takes two courses based on selecting relations in returning field, it is hard to maintain sequence of prepared arguments (PrepArg) generated while resolving returning field. So, we're using txtConverter instead of prepare to resolve mutation fields.
2019-03-07 15:54:07 +05:30

109 lines
3.7 KiB
Haskell

module Hasura.RQL.DML.Mutation
( Mutation(..)
, runMutation
, mutateAndFetchCols
)
where
import qualified Data.Sequence as DS
import Hasura.Prelude
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
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
data Mutation
= Mutation
{ _mTable :: !QualifiedTable
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
, _mFields :: !MutFlds
, _mUniqCols :: !(Maybe [PGColInfo])
, _mStrfyNum :: !Bool
} deriving (Show, Eq)
runMutation :: Mutation -> Q.TxE QErr RespBody
runMutation mut =
bool (mutateAndReturn mut) (mutateAndSel mut) $
hasNestedFld $ _mFields mut
mutateAndReturn :: Mutation -> Q.TxE QErr RespBody
mutateAndReturn (Mutation qt (cte, p) mutFlds _ strfyNum) =
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith)
(toList p) True
where
selWith = mkSelWith qt cte mutFlds False strfyNum
mutateAndSel :: Mutation -> Q.TxE QErr RespBody
mutateAndSel (Mutation qt q mutFlds mUniqCols strfyNum) = do
uniqCols <- onNothing mUniqCols $
throw500 "uniqCols not found in mutateAndSel"
let colMap = Map.fromList $ flip map uniqCols $
\ci -> (pgiName ci, ci)
-- Perform mutation and fetch unique columns
MutateResp _ colVals <- mutateAndFetchCols qt uniqCols q strfyNum
colExps <- mapM (colValToColExp colMap) colVals
let selWhere = S.mkBoolExpWithColVal mkQIdenExp colExps
selCTE = S.CTESelect $
S.mkSelect
{ S.selExtr = [S.selectStar]
, S.selFrom = Just $ S.FromExp [S.FISimple qt Nothing]
, S.selWhere = Just $ S.WhereFrag selWhere
}
selWith = mkSelWith qt selCTE mutFlds False strfyNum
-- Perform select query and fetch returning fields
runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder $ toSQL selWith) [] True
where
colValToColExp colMap colVal =
fmap Map.fromList $ forM (Map.toList colVal) $
\(pgCol, val) -> do
colInfo <- onNothing (Map.lookup pgCol colMap) $
throw500 "colInfo not found; colValToColExp"
sqlExp <- runAesonParser (convToTxt (pgiType colInfo)) val
return (pgCol, sqlExp)
mkQIdenExp col =
S.SEQIden $ S.QIden (S.mkQual qt) $ Iden $ getPGColTxt col
mutateAndFetchCols
:: QualifiedTable
-> [PGColInfo]
-> (S.CTE, DS.Seq Q.PrepArg)
-> Bool
-> Q.TxE QErr MutateResp
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 = TableFrom qt $ Just aliasIden
tabPerm = TablePerm annBoolExpTrue Nothing
selFlds = flip map cols $
\ci -> (fromPGCol $ pgiName ci, FCol 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 False $
AnnSelG selFlds tabFrom tabPerm noTableArgs strfyNum