mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 04:51:35 +03:00
2a9bc2354f
* validation support for unions and interfaces * refactor SQL generation logic for improved readability * '/v1/relay' endpoint for relay schema * implement 'Node' interface and top level 'node' field resolver * add relay toggle on graphiql * fix explain api response & index plan id with query type * add hasura mutations to relay * add relay pytests * update CHANGELOG.md Co-authored-by: rakeshkky <12475069+rakeshkky@users.noreply.github.com> Co-authored-by: Rishichandra Wawhal <rishi@hasura.io> Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com>
170 lines
6.0 KiB
Haskell
170 lines
6.0 KiB
Haskell
module Hasura.RQL.DML.Mutation
|
|
( Mutation
|
|
, mkMutation
|
|
, MutationRemoteJoinCtx
|
|
, runMutation
|
|
, executeMutationOutputQuery
|
|
, 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 Network.HTTP.Client as HTTP
|
|
import qualified Network.HTTP.Types as N
|
|
|
|
import qualified Hasura.SQL.DML as S
|
|
|
|
import Hasura.EncJSON
|
|
import Hasura.RQL.DML.Internal
|
|
import Hasura.RQL.DML.RemoteJoin
|
|
import Hasura.RQL.DML.Returning
|
|
import Hasura.RQL.DML.Select
|
|
import Hasura.RQL.Instances ()
|
|
import Hasura.RQL.Types
|
|
import Hasura.Server.Version (HasVersion)
|
|
import Hasura.Session
|
|
import Hasura.SQL.Types
|
|
import Hasura.SQL.Value
|
|
|
|
type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
|
|
|
|
data Mutation
|
|
= Mutation
|
|
{ _mTable :: !QualifiedTable
|
|
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
|
|
, _mOutput :: !MutationOutput
|
|
, _mCols :: ![PGColumnInfo]
|
|
, _mRemoteJoins :: !(Maybe (RemoteJoins, MutationRemoteJoinCtx))
|
|
, _mStrfyNum :: !Bool
|
|
}
|
|
|
|
mkMutation
|
|
:: Maybe MutationRemoteJoinCtx
|
|
-> QualifiedTable
|
|
-> (S.CTE, DS.Seq Q.PrepArg)
|
|
-> MutationOutput
|
|
-> [PGColumnInfo]
|
|
-> Bool
|
|
-> Mutation
|
|
mkMutation ctx table query output' allCols strfyNum =
|
|
let (output, remoteJoins) = getRemoteJoinsMutationOutput output'
|
|
remoteJoinsCtx = (,) <$> remoteJoins <*> ctx
|
|
in Mutation table query output allCols remoteJoinsCtx strfyNum
|
|
|
|
runMutation
|
|
:: (HasVersion, MonadTx m, MonadIO m)
|
|
=> Mutation -> m EncJSON
|
|
runMutation mut =
|
|
bool (mutateAndReturn mut) (mutateAndSel mut) $
|
|
hasNestedFld $ _mOutput mut
|
|
|
|
mutateAndReturn
|
|
:: (HasVersion, MonadTx m, MonadIO m)
|
|
=> Mutation -> m EncJSON
|
|
mutateAndReturn (Mutation qt (cte, p) mutationOutput allCols remoteJoins strfyNum) =
|
|
executeMutationOutputQuery sqlQuery (toList p) remoteJoins
|
|
where
|
|
sqlQuery = Q.fromBuilder $ toSQL $
|
|
mkMutationOutputExp qt allCols Nothing cte mutationOutput strfyNum
|
|
|
|
mutateAndSel
|
|
:: (HasVersion, MonadTx m, MonadIO m)
|
|
=> Mutation -> m EncJSON
|
|
mutateAndSel (Mutation qt q mutationOutput allCols remoteJoins strfyNum) = do
|
|
-- Perform mutation and fetch unique columns
|
|
MutateResp _ columnVals <- liftTx $ 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
|
|
executeMutationOutputQuery (Q.fromBuilder $ toSQL selWith) [] remoteJoins
|
|
|
|
executeMutationOutputQuery
|
|
:: (HasVersion, MonadTx m, MonadIO m)
|
|
=> Q.Query -- ^ SQL query
|
|
-> [Q.PrepArg] -- ^ Prepared params
|
|
-> Maybe (RemoteJoins, MutationRemoteJoinCtx) -- ^ Remote joins context
|
|
-> m EncJSON
|
|
executeMutationOutputQuery query prepArgs = \case
|
|
Nothing ->
|
|
runIdentity . Q.getRow
|
|
<$> liftTx (Q.rawQE dmlTxErrorHandler query prepArgs True)
|
|
Just (remoteJoins, (httpManager, reqHeaders, userInfo)) ->
|
|
executeQueryWithRemoteJoins httpManager reqHeaders userInfo query prepArgs remoteJoins
|
|
|
|
|
|
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, mkAnnColumnFieldAsText 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 $
|
|
AnnSelectG selFlds tabFrom tabPerm noSelectArgs 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
|