Skip to content

Commit e9fffe1

Browse files
wip
1 parent b1cfe40 commit e9fffe1

File tree

5 files changed

+160
-94
lines changed

5 files changed

+160
-94
lines changed

server/src-lib/Hasura/GraphQL/Execute.hs

Lines changed: 120 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
module Hasura.GraphQL.Execute
2-
( GQExecPlan(..)
3-
, GQFieldExecPlan(..)
2+
( GQExecPlanPartial(..)
3+
, GQFieldPartialPlan(..)
4+
, GQFieldResolvedPlan(..)
45

5-
, ExecPlanPartial
66
, getExecPlanPartial
77

88
, ExecOp(..)
9-
, ExecPlanResolved
109
, getResolvedExecPlan
1110
, execRemoteGQ
1211
, getSubsOp
@@ -61,12 +60,19 @@ import qualified Hasura.Logging as L
6160
--
6261
-- The 'a' is parameterised so this AST can represent
6362
-- intermediate passes
64-
data GQFieldExecPlan a
65-
= GExPHasura !a
66-
| GExPRemote !RemoteSchemaInfo !VQ.Field
67-
deriving (Functor, Foldable, Traversable)
68-
69-
type GQExecPlan a = [GQFieldExecPlan a]
63+
data GQFieldPartialPlan
64+
= GQFieldPartialHasura !(GCtx, VQ.Field)
65+
| GQFieldPartialRemote !RemoteSchemaInfo !VQ.Field
66+
67+
data GQFieldResolvedPlan
68+
= GQFieldResolvedHasura !ExecOp
69+
| GQFieldResolvedRemote !RemoteSchemaInfo !G.OperationType !VQ.Field
70+
71+
data GQExecPlanPartial
72+
= GQExecPlanPartial
73+
{ execOpType :: G.OperationType
74+
, execFieldPlans :: Seq.Seq GQFieldPartialPlan
75+
}
7076

7177
-- | Execution context
7278
data ExecutionCtx
@@ -81,41 +87,65 @@ data ExecutionCtx
8187
, _ecxEnableAllowList :: !Bool
8288
}
8389

84-
-- This is for when the graphql query is validated
85-
type ExecPlanPartial = [GQFieldExecPlan (GCtx, VQ.Field)]
86-
8790
getExecPlanPartial
8891
:: (MonadError QErr m)
8992
=> UserInfo
9093
-> SchemaCache
9194
-> Bool
9295
-> GQLReqParsed
93-
-> m ExecPlanPartial
94-
getExecPlanPartial userInfo sc enableAL req = do
95-
96+
-> m GQExecPlanPartial
97+
getExecPlanPartial userInfo sc enableAL req
9698
-- check if query is in allowlist
99+
= do
97100
when enableAL checkQueryInAllowlist
98-
99-
(gCtx, _) <- flip runStateT sc $ getGCtx role gCtxRoleMap
101+
(gCtx, _) <- flip runStateT sc $ getGCtx role gCtxRoleMap
100102
queryParts <- flip runReaderT gCtx $ VQ.getQueryParts req
101-
103+
let remoteSchemas = scRemoteSchemas sc
102104
rootSelSet <- runReaderT (VQ.validateGQ queryParts) gCtx
103-
generatePlan rootSelSet
105+
runReaderT (generatePlan rootSelSet) (gCtx, remoteSchemas)
104106
where
105-
generatePlan = undefined
107+
generatePlan ::
108+
(MonadError QErr m, MonadReader (GCtx, RemoteSchemaMap) m)
109+
=> VQ.RootSelSet
110+
-> m GQExecPlanPartial
111+
generatePlan =
112+
\case
113+
VQ.RQuery selSet ->
114+
(GQExecPlanPartial G.OperationTypeQuery) <$>
115+
(mapM generateFieldPlan selSet)
116+
VQ.RMutation selSet ->
117+
(GQExecPlanPartial G.OperationTypeMutation) <$>
118+
(mapM generateFieldPlan selSet)
119+
VQ.RSubscription field ->
120+
(GQExecPlanPartial G.OperationTypeMutation) <$>
121+
(mapM generateFieldPlan (Seq.singleton field))
122+
generateFieldPlan ::
123+
(MonadError QErr m, MonadReader (GCtx, RemoteSchemaMap) m)
124+
=> VQ.Field
125+
-> m GQFieldPartialPlan
126+
generateFieldPlan field =
127+
case VQ._fSource field of
128+
TLHasuraType -> do
129+
(gCtx, _) <- ask
130+
pure $ GQFieldPartialHasura (gCtx, field)
131+
TLRemoteType rsName -> do
132+
(_, rsMap) <- ask
133+
rsCtx <-
134+
onNothing (Map.lookup rsName rsMap) $
135+
throw500 "remote schema not found"
136+
pure $ GQFieldPartialRemote (rscInfo rsCtx) field
106137
role = userRole userInfo
107138
gCtxRoleMap = scGCtxMap sc
108-
109-
checkQueryInAllowlist =
139+
checkQueryInAllowlist
110140
-- only for non-admin roles
141+
=
111142
when (role /= adminRole) $ do
112143
let notInAllowlist =
113144
not $ VQ.isQueryInAllowlist (_grQuery req) (scAllowlist sc)
114145
when notInAllowlist $ modifyQErr modErr $ throwVE "query is not allowed"
115-
116146
modErr e =
117147
let msg = "query is not in any of the allowlists"
118-
in e{qeInternal = Just $ J.object [ "message" J..= J.String msg]}
148+
in e {qeInternal = Just $ J.object ["message" J..= J.String msg]}
119149

120150

121151
-- An execution operation, in case of
@@ -126,10 +156,6 @@ data ExecOp
126156
| ExOpMutation !LazyRespTx
127157
| ExOpSubs !EL.LiveQueryPlan
128158

129-
-- The graphql query is resolved into an execution operation
130-
type ExecPlanResolved
131-
= GQExecPlan ExecOp
132-
133159
getResolvedExecPlan
134160
:: (MonadError QErr m, MonadIO m)
135161
=> PGExecCtx
@@ -140,41 +166,63 @@ getResolvedExecPlan
140166
-> SchemaCache
141167
-> SchemaCacheVer
142168
-> GQLReqUnparsed
143-
-> m ExecPlanResolved
144-
getResolvedExecPlan pgExecCtx planCache userInfo sqlGenCtx
145-
enableAL sc scVer reqUnparsed = do
146-
planM <- liftIO $ EP.getPlan scVer (userRole userInfo)
147-
opNameM queryStr planCache
169+
-> m (Seq.Seq GQFieldResolvedPlan)
170+
getResolvedExecPlan pgExecCtx planCache userInfo sqlGenCtx enableAL sc scVer reqUnparsed = do
171+
planM <-
172+
liftIO $ EP.getPlan scVer (userRole userInfo) opNameM queryStr planCache
148173
let usrVars = userVars userInfo
149-
case planM of
174+
case planM
150175
-- plans are only for queries and subscriptions
151-
Just plan -> GExPHasura <$> case plan of
152-
EP.RPQuery queryPlan -> do
153-
(tx, genSql) <- EQ.queryOpFromPlan usrVars queryVars queryPlan
154-
return $ ExOpQuery tx (Just genSql)
155-
EP.RPSubs subsPlan ->
156-
ExOpSubs <$> EL.reuseLiveQueryPlan pgExecCtx usrVars queryVars subsPlan
176+
of
177+
Just plan ->
178+
case plan of
179+
EP.RPQuery queryPlan -> do
180+
(tx, genSql) <- EQ.queryOpFromPlan usrVars queryVars queryPlan
181+
let queryOp = ExOpQuery tx (Just genSql)
182+
pure $ pure $ GQFieldResolvedHasura queryOp
183+
EP.RPSubs subsPlan -> do
184+
subOp <-
185+
ExOpSubs <$>
186+
EL.reuseLiveQueryPlan pgExecCtx usrVars queryVars subsPlan
187+
pure $ pure $ GQFieldResolvedHasura subOp
157188
Nothing -> noExistingPlan
158189
where
159190
GQLReq opNameM queryStr queryVars = reqUnparsed
160191
addPlanToCache plan =
161-
liftIO $ EP.addPlan scVer (userRole userInfo)
162-
opNameM queryStr plan planCache
192+
liftIO $
193+
EP.addPlan scVer (userRole userInfo) opNameM queryStr plan planCache
163194
noExistingPlan = do
164195
req <- toParsed reqUnparsed
165-
partialExecPlan <- getExecPlanPartial userInfo sc enableAL req
166-
forM partialExecPlan $ \(gCtx, rootSelSet) ->
167-
case rootSelSet of
168-
VQ.RMutation selSet ->
169-
ExOpMutation <$> getMutOp gCtx sqlGenCtx userInfo selSet
170-
VQ.RQuery selSet -> do
171-
(queryTx, plan, genSql) <- getQueryOp gCtx sqlGenCtx userInfo selSet
172-
traverse_ (addPlanToCache . EP.RPQuery) plan
173-
return $ ExOpQuery queryTx (Just genSql)
174-
VQ.RSubscription fld -> do
175-
(lqOp, plan) <- getSubsOp pgExecCtx gCtx sqlGenCtx userInfo fld
176-
traverse_ (addPlanToCache . EP.RPSubs) plan
177-
return $ ExOpSubs lqOp
196+
(GQExecPlanPartial opType fieldPlans) <-
197+
getExecPlanPartial userInfo sc enableAL req
198+
case opType of
199+
G.OperationTypeQuery ->
200+
forM fieldPlans $ \case
201+
GQFieldPartialHasura (gCtx, field) -> do
202+
(queryTx, plan, genSql) <-
203+
getQueryOp gCtx sqlGenCtx userInfo (Seq.singleton field)
204+
traverse_ (addPlanToCache . EP.RPQuery) plan
205+
(return . GQFieldResolvedHasura) $ ExOpQuery queryTx (Just genSql)
206+
GQFieldPartialRemote rsInfo field ->
207+
return $ GQFieldResolvedRemote rsInfo G.OperationTypeQuery field
208+
G.OperationTypeMutation ->
209+
forM fieldPlans $ \case
210+
GQFieldPartialHasura (gCtx, field) -> do
211+
mutationTx <-
212+
getMutOp gCtx sqlGenCtx userInfo (Seq.singleton field)
213+
(return . GQFieldResolvedHasura) $ ExOpMutation mutationTx
214+
GQFieldPartialRemote rsInfo field ->
215+
return $
216+
GQFieldResolvedRemote rsInfo G.OperationTypeMutation field
217+
G.OperationTypeSubscription ->
218+
forM fieldPlans $ \case
219+
GQFieldPartialHasura (gCtx, field) -> do
220+
(lqOp, plan) <- getSubsOp pgExecCtx gCtx sqlGenCtx userInfo field
221+
traverse_ (addPlanToCache . EP.RPSubs) plan
222+
(return . GQFieldResolvedHasura) $ ExOpSubs lqOp
223+
GQFieldPartialRemote rsInfo field ->
224+
return $
225+
GQFieldResolvedRemote rsInfo G.OperationTypeSubscription field
178226

179227
-- Monad for resolving a hasura query/mutation
180228
type E m =
@@ -303,18 +351,19 @@ execRemoteGQ
303351
=> RequestId
304352
-> UserInfo
305353
-> [N.Header]
306-
-> GQLReqUnparsed
307354
-> RemoteSchemaInfo
308-
-> G.TypedOperationDefinition
355+
-> G.OperationType
356+
-> VQ.SelSet
309357
-> m (HttpResponse EncJSON)
310-
execRemoteGQ reqId userInfo reqHdrs q rsi opDef = do
358+
execRemoteGQ reqId userInfo reqHdrs rsi opType selSet = do
311359
execCtx <- ask
312360
let logger = _ecxLogger execCtx
313361
manager = _ecxHttpManager execCtx
314-
opTy = G._todType opDef
315-
when (opTy == G.OperationTypeSubscription) $
362+
when (opType == G.OperationTypeSubscription) $
316363
throw400 NotSupported "subscription to remote server is not supported"
317364
hdrs <- getHeadersFromConf hdrConf
365+
gqlReq <- fieldsToRequest opType selSet
366+
let body = encJToLBS (encJFromJValue gqlReq)
318367
let confHdrs = map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) hdrs
319368
clientHdrs = bool [] filteredHeaders fwdClientHdrs
320369
-- filter out duplicate headers
@@ -330,11 +379,11 @@ execRemoteGQ reqId userInfo reqHdrs q rsi opDef = do
330379
let req = initReq
331380
{ HTTP.method = "POST"
332381
, HTTP.requestHeaders = finalHeaders
333-
, HTTP.requestBody = HTTP.RequestBodyLBS (J.encode q)
382+
, HTTP.requestBody = HTTP.RequestBodyLBS body
334383
, HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000)
335384
}
336385

337-
liftIO $ logGraphqlQuery logger $ QueryLog q Nothing reqId
386+
-- liftIO $ logGraphqlQuery logger $ QueryLog q Nothing reqId
338387
res <- liftIO $ try $ HTTP.httpLbs req manager
339388
resp <- either httpThrow return res
340389
let cookieHdrs = getCookieHdr (resp ^.. Wreq.responseHeader "Set-Cookie")
@@ -361,3 +410,11 @@ execRemoteGQ reqId userInfo reqHdrs q rsi opDef = do
361410

362411
mkRespHeaders hdrs =
363412
map (\(k, v) -> Header (bsToTxt $ CI.original k, bsToTxt v)) hdrs
413+
414+
415+
fieldsToRequest
416+
:: (MonadIO m, MonadError QErr m)
417+
=> G.OperationType
418+
-> Seq.Seq VQ.Field
419+
-> m GQLReqParsed
420+
fieldsToRequest = undefined

server/src-lib/Hasura/GraphQL/Explain.hs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,22 @@ explainGQLQuery
114114
-> GQLExplain
115115
-> m EncJSON
116116
explainGQLQuery pgExecCtx sc sqlGenCtx enableAL (GQLExplain query userVarsRaw) = do
117-
execPlan <- E.getExecPlanPartial userInfo sc enableAL query
118-
(gCtx, rootSelSet) <- case execPlan of
119-
E.GExPHasura (gCtx, rootSelSet) ->
120-
return (gCtx, rootSelSet)
121-
E.GExPRemote _ _ ->
122-
throw400 InvalidParams "only hasura queries can be explained"
123-
case rootSelSet of
124-
GV.RQuery selSet ->
125-
runInTx $ encJFromJValue <$> traverse (explainField userInfo gCtx sqlGenCtx) (toList selSet)
126-
GV.RMutation _ ->
127-
throw400 InvalidParams "only queries can be explained"
128-
GV.RSubscription rootField -> do
129-
(plan, _) <- E.getSubsOp pgExecCtx gCtx sqlGenCtx userInfo rootField
130-
runInTx $ encJFromJValue <$> E.explainLiveQueryPlan plan
131-
where
132-
usrVars = mkUserVars $ maybe [] Map.toList userVarsRaw
133-
userInfo = mkUserInfo (fromMaybe adminRole $ roleFromVars usrVars) usrVars
134-
runInTx = liftEither <=< liftIO . runExceptT . runLazyTx pgExecCtx
117+
undefined
118+
-- E.GQExecPlanPartial opType fieldPlans <- E.getExecPlanPartial userInfo sc enableAL query
119+
-- (gCtx, rootSelSet) <- case execPlan of
120+
-- E.GExPHasura (gCtx, rootSelSet) ->
121+
-- return (gCtx, rootSelSet)
122+
-- E.GExPRemote {} ->
123+
-- throw400 InvalidParams "only hasura queries can be explained"
124+
-- case rootSelSet of
125+
-- GV.RQuery selSet ->
126+
-- runInTx $ encJFromJValue <$> traverse (explainField userInfo gCtx sqlGenCtx) (toList selSet)
127+
-- GV.RMutation _ ->
128+
-- throw400 InvalidParams "only queries can be explained"
129+
-- GV.RSubscription rootField -> do
130+
-- (plan, _) <- E.getSubsOp pgExecCtx gCtx sqlGenCtx userInfo rootField
131+
-- runInTx $ encJFromJValue <$> E.explainLiveQueryPlan plan
132+
-- where
133+
-- usrVars = mkUserVars $ maybe [] Map.toList userVarsRaw
134+
-- userInfo = mkUserInfo (fromMaybe adminRole $ roleFromVars usrVars) usrVars
135+
-- runInTx = liftEither <=< liftIO . runExceptT . runLazyTx pgExecCtx

server/src-lib/Hasura/GraphQL/Transport/HTTP.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Hasura.GraphQL.Transport.HTTP
22
( runGQ
33
) where
44

5+
import qualified Data.Sequence as Seq
56
import qualified Network.HTTP.Types as N
67

78
import Hasura.EncJSON
@@ -28,11 +29,15 @@ runGQ reqId userInfo reqHdrs req = do
2829
E.ExecutionCtx _ sqlGenCtx pgExecCtx planCache sc scVer _ enableAL <- ask
2930
fieldPlans <- E.getResolvedExecPlan pgExecCtx planCache
3031
userInfo sqlGenCtx enableAL sc scVer req
31-
forM fieldPlans (\execPlan -> case execPlan of
32-
E.GExPHasura resolvedOp ->
32+
fieldResps <- forM fieldPlans $ \case
33+
E.GQFieldResolvedHasura resolvedOp ->
3334
flip HttpResponse Nothing <$> runHasuraGQ reqId req userInfo resolvedOp
34-
E.GExPRemote rsi rootSelSet ->
35-
E.execRemoteGQ reqId userInfo reqHdrs req rsi undefined)
35+
E.GQFieldResolvedRemote rsi opType field ->
36+
E.execRemoteGQ reqId userInfo reqHdrs rsi opType (Seq.singleton field)
37+
-- pure $ mergeResponses fieldResps
38+
pure $ head (toList fieldResps)
39+
where
40+
mergeResponses = undefined
3641

3742
runHasuraGQ
3843
:: ( MonadIO m

server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import qualified Data.ByteString.Lazy as BL
1717
import qualified Data.CaseInsensitive as CI
1818
import qualified Data.HashMap.Strict as Map
1919
import qualified Data.IORef as IORef
20+
import qualified Data.Sequence as Seq
2021
import qualified Data.Text as T
2122
import qualified Data.Text.Encoding as TE
2223
import qualified Data.Time.Clock as TC
@@ -46,6 +47,7 @@ import Hasura.Server.Utils (RequestId,
4647
import qualified Hasura.GraphQL.Execute as E
4748
import qualified Hasura.GraphQL.Execute.LiveQuery as LQ
4849
import qualified Hasura.GraphQL.Transport.WebSocket.Server as WS
50+
import qualified Hasura.GraphQL.Validate as VQ
4951
import qualified Hasura.Logging as L
5052

5153

@@ -282,11 +284,11 @@ onStart serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
282284
let execCtx = E.ExecutionCtx logger sqlGenCtx pgExecCtx
283285
planCache sc scVer httpMgr enableAL
284286

285-
case execPlan of
286-
E.GExPHasura resolvedOp ->
287+
forM_ execPlan $ \case
288+
E.GQFieldResolvedHasura resolvedOp ->
287289
runHasuraGQ requestId q userInfo resolvedOp
288-
E.GExPRemote rsi rootSelSet ->
289-
runRemoteGQ execCtx requestId userInfo reqHdrs undefined rsi
290+
E.GQFieldResolvedRemote rsi opType field ->
291+
runRemoteGQ execCtx requestId userInfo reqHdrs rsi opType (Seq.singleton field)
290292
where
291293
runHasuraGQ :: RequestId -> GQLReqUnparsed -> UserInfo -> E.ExecOp
292294
-> ExceptT () IO ()
@@ -313,16 +315,16 @@ onStart serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
313315
sendCompleted (Just reqId)
314316

315317
runRemoteGQ :: E.ExecutionCtx -> RequestId -> UserInfo -> [H.Header]
316-
-> G.TypedOperationDefinition -> RemoteSchemaInfo
318+
-> RemoteSchemaInfo -> G.OperationType -> VQ.SelSet
317319
-> ExceptT () IO ()
318-
runRemoteGQ execCtx reqId userInfo reqHdrs opDef rsi = do
319-
when (G._todType opDef == G.OperationTypeSubscription) $
320+
runRemoteGQ execCtx reqId userInfo reqHdrs rsi opType selSet = do
321+
when (opType == G.OperationTypeSubscription) $
320322
withComplete $ preExecErr reqId $
321323
err400 NotSupported "subscription to remote server is not supported"
322324

323325
-- if it's not a subscription, use HTTP to execute the query on the remote
324326
resp <- runExceptT $ flip runReaderT execCtx $
325-
E.execRemoteGQ reqId userInfo reqHdrs q rsi opDef
327+
E.execRemoteGQ reqId userInfo reqHdrs rsi opType selSet
326328
either (postExecErr reqId) (sendRemoteResp reqId . _hrBody) resp
327329
sendCompleted (Just reqId)
328330

0 commit comments

Comments
 (0)