2019-03-25 21:25:25 +03:00
|
|
|
module Hasura.GraphQL.Execute
|
|
|
|
( GQExecPlan(..)
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
, ExecPlanPartial
|
|
|
|
, getExecPlanPartial
|
|
|
|
|
|
|
|
, ExecOp(..)
|
|
|
|
, ExecPlanResolved
|
|
|
|
, getResolvedExecPlan
|
2019-03-25 21:25:25 +03:00
|
|
|
, execRemoteGQ
|
2019-09-30 22:50:57 +03:00
|
|
|
, getSubsOp
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
, EP.PlanCache
|
2019-11-25 20:12:23 +03:00
|
|
|
, EP.mkPlanCacheOptions
|
|
|
|
, EP.PlanCacheOptions
|
2019-04-17 12:48:41 +03:00
|
|
|
, EP.initPlanCache
|
|
|
|
, EP.clearPlanCache
|
|
|
|
, EP.dumpPlanCache
|
2019-07-11 08:37:06 +03:00
|
|
|
|
|
|
|
, ExecutionCtx(..)
|
2019-03-25 21:25:25 +03:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Control.Exception (try)
|
|
|
|
import Control.Lens
|
2019-04-17 12:48:41 +03:00
|
|
|
import Data.Has
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-05-16 09:13:25 +03:00
|
|
|
import qualified Data.Aeson as J
|
2019-03-25 21:25:25 +03:00
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
|
|
import qualified Data.HashSet as Set
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import qualified Network.HTTP.Client as HTTP
|
|
|
|
import qualified Network.HTTP.Types as N
|
|
|
|
import qualified Network.Wreq as Wreq
|
|
|
|
|
|
|
|
import Hasura.EncJSON
|
2019-04-17 12:48:41 +03:00
|
|
|
import Hasura.GraphQL.Context
|
2019-07-11 08:37:06 +03:00
|
|
|
import Hasura.GraphQL.Logging
|
2020-04-24 10:55:51 +03:00
|
|
|
import Hasura.GraphQL.Resolve.Action
|
2019-04-17 12:48:41 +03:00
|
|
|
import Hasura.GraphQL.Resolve.Context
|
2019-03-25 21:25:25 +03:00
|
|
|
import Hasura.GraphQL.Schema
|
|
|
|
import Hasura.GraphQL.Transport.HTTP.Protocol
|
2019-04-17 12:48:41 +03:00
|
|
|
import Hasura.GraphQL.Validate.Types
|
2019-03-25 21:25:25 +03:00
|
|
|
import Hasura.HTTP
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.DDL.Headers
|
|
|
|
import Hasura.RQL.Types
|
2020-03-20 09:46:45 +03:00
|
|
|
import Hasura.Server.Utils (RequestId, mkClientHeadersForward,
|
|
|
|
mkSetCookieHeaders)
|
2020-01-23 00:55:55 +03:00
|
|
|
import Hasura.Server.Version (HasVersion)
|
2020-04-24 12:10:53 +03:00
|
|
|
import Hasura.Session
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
import qualified Hasura.GraphQL.Execute.LiveQuery as EL
|
|
|
|
import qualified Hasura.GraphQL.Execute.Plan as EP
|
|
|
|
import qualified Hasura.GraphQL.Execute.Query as EQ
|
|
|
|
import qualified Hasura.GraphQL.Resolve as GR
|
2019-03-25 21:25:25 +03:00
|
|
|
import qualified Hasura.GraphQL.Validate as VQ
|
|
|
|
import qualified Hasura.GraphQL.Validate.Types as VT
|
2019-07-11 08:37:06 +03:00
|
|
|
import qualified Hasura.Logging as L
|
2020-01-16 04:56:57 +03:00
|
|
|
import qualified Hasura.Server.Telemetry.Counters as Telem
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
-- The current execution plan of a graphql operation, it is
|
|
|
|
-- currently, either local pg execution or a remote execution
|
|
|
|
--
|
|
|
|
-- The 'a' is parameterised so this AST can represent
|
|
|
|
-- intermediate passes
|
|
|
|
data GQExecPlan a
|
|
|
|
= GExPHasura !a
|
2019-03-25 21:25:25 +03:00
|
|
|
| GExPRemote !RemoteSchemaInfo !G.TypedOperationDefinition
|
2019-04-17 12:48:41 +03:00
|
|
|
deriving (Functor, Foldable, Traversable)
|
|
|
|
|
2019-07-11 08:37:06 +03:00
|
|
|
-- | Execution context
|
|
|
|
data ExecutionCtx
|
|
|
|
= ExecutionCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
{ _ecxLogger :: !(L.Logger L.Hasura)
|
2019-07-11 08:37:06 +03:00
|
|
|
, _ecxSqlGenCtx :: !SQLGenCtx
|
|
|
|
, _ecxPgExecCtx :: !PGExecCtx
|
|
|
|
, _ecxPlanCache :: !EP.PlanCache
|
|
|
|
, _ecxSchemaCache :: !SchemaCache
|
|
|
|
, _ecxSchemaCacheVer :: !SchemaCacheVer
|
|
|
|
, _ecxHttpManager :: !HTTP.Manager
|
|
|
|
, _ecxEnableAllowList :: !Bool
|
|
|
|
}
|
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
-- Enforces the current limitation
|
|
|
|
assertSameLocationNodes
|
|
|
|
:: (MonadError QErr m) => [VT.TypeLoc] -> m VT.TypeLoc
|
|
|
|
assertSameLocationNodes typeLocs =
|
|
|
|
case Set.toList (Set.fromList typeLocs) of
|
|
|
|
-- this shouldn't happen
|
2019-07-08 08:51:41 +03:00
|
|
|
[] -> return VT.TLHasuraType
|
2019-04-17 12:48:41 +03:00
|
|
|
[loc] -> return loc
|
|
|
|
_ -> throw400 NotSupported msg
|
|
|
|
where
|
|
|
|
msg = "cannot mix top level fields from two different graphql servers"
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
-- TODO: we should fix this function asap
|
|
|
|
-- as this will fail when there is a fragment at the top level
|
|
|
|
getTopLevelNodes :: G.TypedOperationDefinition -> [G.Name]
|
|
|
|
getTopLevelNodes opDef =
|
|
|
|
mapMaybe f $ G._todSelectionSet opDef
|
|
|
|
where
|
|
|
|
f = \case
|
|
|
|
G.SelectionField fld -> Just $ G._fName fld
|
|
|
|
G.SelectionFragmentSpread _ -> Nothing
|
|
|
|
G.SelectionInlineFragment _ -> Nothing
|
|
|
|
|
|
|
|
gatherTypeLocs :: GCtx -> [G.Name] -> [VT.TypeLoc]
|
|
|
|
gatherTypeLocs gCtx nodes =
|
|
|
|
catMaybes $ flip map nodes $ \node ->
|
|
|
|
VT._fiLoc <$> Map.lookup node schemaNodes
|
|
|
|
where
|
|
|
|
schemaNodes =
|
|
|
|
let qr = VT._otiFields $ _gQueryRoot gCtx
|
|
|
|
mr = VT._otiFields <$> _gMutRoot gCtx
|
|
|
|
in maybe qr (Map.union qr) mr
|
|
|
|
|
|
|
|
-- This is for when the graphql query is validated
|
2019-09-14 09:01:06 +03:00
|
|
|
type ExecPlanPartial = GQExecPlan (GCtx, VQ.RootSelSet)
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
getExecPlanPartial
|
2019-10-16 17:33:34 +03:00
|
|
|
:: (MonadReusability m, MonadError QErr m)
|
2019-03-25 21:25:25 +03:00
|
|
|
=> UserInfo
|
|
|
|
-> SchemaCache
|
2019-05-16 09:13:25 +03:00
|
|
|
-> Bool
|
2019-04-17 12:48:41 +03:00
|
|
|
-> GQLReqParsed
|
|
|
|
-> m ExecPlanPartial
|
2019-05-16 09:13:25 +03:00
|
|
|
getExecPlanPartial userInfo sc enableAL req = do
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-05-16 09:13:25 +03:00
|
|
|
-- check if query is in allowlist
|
|
|
|
when enableAL checkQueryInAllowlist
|
|
|
|
|
2020-04-24 12:10:53 +03:00
|
|
|
let gCtx = getGCtx (_uiBackendOnlyFieldAccess userInfo) sc roleName
|
2019-03-25 21:25:25 +03:00
|
|
|
queryParts <- flip runReaderT gCtx $ VQ.getQueryParts req
|
|
|
|
|
|
|
|
let opDef = VQ.qpOpDef queryParts
|
|
|
|
topLevelNodes = getTopLevelNodes opDef
|
|
|
|
-- gather TypeLoc of topLevelNodes
|
|
|
|
typeLocs = gatherTypeLocs gCtx topLevelNodes
|
|
|
|
|
|
|
|
-- see if they are all the same
|
|
|
|
typeLoc <- assertSameLocationNodes typeLocs
|
|
|
|
|
|
|
|
case typeLoc of
|
2019-07-08 08:51:41 +03:00
|
|
|
VT.TLHasuraType -> do
|
2019-09-14 09:01:06 +03:00
|
|
|
rootSelSet <- runReaderT (VQ.validateGQ queryParts) gCtx
|
|
|
|
return $ GExPHasura (gCtx, rootSelSet)
|
2019-07-08 08:51:41 +03:00
|
|
|
VT.TLRemoteType _ rsi ->
|
2019-03-25 21:25:25 +03:00
|
|
|
return $ GExPRemote rsi opDef
|
2020-02-13 20:38:23 +03:00
|
|
|
VT.TLCustom ->
|
|
|
|
throw500 "unexpected custom type for top level field"
|
2019-03-25 21:25:25 +03:00
|
|
|
where
|
2020-04-24 12:10:53 +03:00
|
|
|
roleName = _uiRole userInfo
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-05-16 09:13:25 +03:00
|
|
|
checkQueryInAllowlist =
|
|
|
|
-- only for non-admin roles
|
2020-04-24 12:10:53 +03:00
|
|
|
when (roleName /= adminRoleName) $ do
|
2019-05-16 09:13:25 +03:00
|
|
|
let notInAllowlist =
|
|
|
|
not $ VQ.isQueryInAllowlist (_grQuery req) (scAllowlist sc)
|
|
|
|
when notInAllowlist $ modifyQErr modErr $ throwVE "query is not allowed"
|
|
|
|
|
|
|
|
modErr e =
|
|
|
|
let msg = "query is not in any of the allowlists"
|
|
|
|
in e{qeInternal = Just $ J.object [ "message" J..= J.String msg]}
|
|
|
|
|
2019-07-11 08:37:06 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
-- An execution operation, in case of
|
|
|
|
-- queries and mutations it is just a transaction
|
|
|
|
-- to be executed
|
|
|
|
data ExecOp
|
2019-07-11 08:37:06 +03:00
|
|
|
= ExOpQuery !LazyRespTx !(Maybe EQ.GeneratedSqlMap)
|
2020-03-20 09:46:45 +03:00
|
|
|
| ExOpMutation !N.ResponseHeaders !LazyRespTx
|
2019-08-28 15:19:21 +03:00
|
|
|
| ExOpSubs !EL.LiveQueryPlan
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
-- The graphql query is resolved into an execution operation
|
2020-03-20 09:46:45 +03:00
|
|
|
type ExecPlanResolved = GQExecPlan ExecOp
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
getResolvedExecPlan
|
2020-02-13 20:38:23 +03:00
|
|
|
:: (HasVersion, MonadError QErr m, MonadIO m)
|
2019-04-17 12:48:41 +03:00
|
|
|
=> PGExecCtx
|
|
|
|
-> EP.PlanCache
|
|
|
|
-> UserInfo
|
|
|
|
-> SQLGenCtx
|
2019-05-16 09:13:25 +03:00
|
|
|
-> Bool
|
2019-04-17 12:48:41 +03:00
|
|
|
-> SchemaCache
|
|
|
|
-> SchemaCacheVer
|
2020-02-13 20:38:23 +03:00
|
|
|
-> HTTP.Manager
|
|
|
|
-> [N.Header]
|
2019-04-17 12:48:41 +03:00
|
|
|
-> GQLReqUnparsed
|
2020-01-16 04:56:57 +03:00
|
|
|
-> m (Telem.CacheHit, ExecPlanResolved)
|
2019-04-17 12:48:41 +03:00
|
|
|
getResolvedExecPlan pgExecCtx planCache userInfo sqlGenCtx
|
2020-02-13 20:38:23 +03:00
|
|
|
enableAL sc scVer httpManager reqHeaders reqUnparsed = do
|
2020-04-24 12:10:53 +03:00
|
|
|
planM <- liftIO $ EP.getPlan scVer (_uiRole userInfo)
|
2019-04-17 12:48:41 +03:00
|
|
|
opNameM queryStr planCache
|
2020-04-24 12:10:53 +03:00
|
|
|
let usrVars = _uiSession userInfo
|
2019-04-17 12:48:41 +03:00
|
|
|
case planM of
|
|
|
|
-- plans are only for queries and subscriptions
|
2020-01-16 04:56:57 +03:00
|
|
|
Just plan -> (Telem.Hit,) . GExPHasura <$> case plan of
|
2019-07-11 08:37:06 +03:00
|
|
|
EP.RPQuery queryPlan -> do
|
|
|
|
(tx, genSql) <- EQ.queryOpFromPlan usrVars queryVars queryPlan
|
|
|
|
return $ ExOpQuery tx (Just genSql)
|
2019-04-17 12:48:41 +03:00
|
|
|
EP.RPSubs subsPlan ->
|
2019-08-28 15:19:21 +03:00
|
|
|
ExOpSubs <$> EL.reuseLiveQueryPlan pgExecCtx usrVars queryVars subsPlan
|
2020-01-16 04:56:57 +03:00
|
|
|
Nothing -> (Telem.Miss,) <$> noExistingPlan
|
2019-04-17 12:48:41 +03:00
|
|
|
where
|
|
|
|
GQLReq opNameM queryStr queryVars = reqUnparsed
|
|
|
|
addPlanToCache plan =
|
2020-04-24 12:10:53 +03:00
|
|
|
liftIO $ EP.addPlan scVer (_uiRole userInfo)
|
2019-04-17 12:48:41 +03:00
|
|
|
opNameM queryStr plan planCache
|
|
|
|
noExistingPlan = do
|
2019-08-28 15:19:21 +03:00
|
|
|
req <- toParsed reqUnparsed
|
2019-10-16 17:33:34 +03:00
|
|
|
(partialExecPlan, queryReusability) <- runReusabilityT $
|
|
|
|
getExecPlanPartial userInfo sc enableAL req
|
2019-09-14 09:01:06 +03:00
|
|
|
forM partialExecPlan $ \(gCtx, rootSelSet) ->
|
2019-04-17 12:48:41 +03:00
|
|
|
case rootSelSet of
|
2020-03-20 09:46:45 +03:00
|
|
|
VQ.RMutation selSet -> do
|
|
|
|
(tx, respHeaders) <- getMutOp gCtx sqlGenCtx userInfo httpManager reqHeaders selSet
|
|
|
|
pure $ ExOpMutation respHeaders tx
|
2019-04-17 12:48:41 +03:00
|
|
|
VQ.RQuery selSet -> do
|
2020-04-16 10:25:19 +03:00
|
|
|
(queryTx, plan, genSql) <- getQueryOp gCtx sqlGenCtx userInfo queryReusability (allowQueryActionExecuter httpManager reqHeaders) selSet
|
2019-08-28 15:19:21 +03:00
|
|
|
traverse_ (addPlanToCache . EP.RPQuery) plan
|
2019-07-11 08:37:06 +03:00
|
|
|
return $ ExOpQuery queryTx (Just genSql)
|
2019-04-17 12:48:41 +03:00
|
|
|
VQ.RSubscription fld -> do
|
2020-04-16 10:25:19 +03:00
|
|
|
(lqOp, plan) <- getSubsOp pgExecCtx gCtx sqlGenCtx userInfo queryReusability (restrictActionExecuter "query actions cannot be run as a subscription") fld
|
2019-08-28 15:19:21 +03:00
|
|
|
traverse_ (addPlanToCache . EP.RPSubs) plan
|
2019-04-17 12:48:41 +03:00
|
|
|
return $ ExOpSubs lqOp
|
|
|
|
|
|
|
|
-- Monad for resolving a hasura query/mutation
|
|
|
|
type E m =
|
|
|
|
ReaderT ( UserInfo
|
2019-07-23 14:12:59 +03:00
|
|
|
, QueryCtxMap
|
|
|
|
, MutationCtxMap
|
2019-04-17 12:48:41 +03:00
|
|
|
, TypeMap
|
|
|
|
, FieldMap
|
|
|
|
, OrdByCtx
|
|
|
|
, InsCtxMap
|
|
|
|
, SQLGenCtx
|
|
|
|
) (ExceptT QErr m)
|
|
|
|
|
|
|
|
runE
|
|
|
|
:: (MonadError QErr m)
|
|
|
|
=> GCtx
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> UserInfo
|
|
|
|
-> E m a
|
|
|
|
-> m a
|
|
|
|
runE ctx sqlGenCtx userInfo action = do
|
|
|
|
res <- runExceptT $ runReaderT action
|
2019-07-23 14:12:59 +03:00
|
|
|
(userInfo, queryCtxMap, mutationCtxMap, typeMap, fldMap, ordByCtx, insCtxMap, sqlGenCtx)
|
2019-04-17 12:48:41 +03:00
|
|
|
either throwError return res
|
|
|
|
where
|
2019-07-23 14:12:59 +03:00
|
|
|
queryCtxMap = _gQueryCtxMap ctx
|
|
|
|
mutationCtxMap = _gMutationCtxMap ctx
|
2019-04-17 12:48:41 +03:00
|
|
|
typeMap = _gTypes ctx
|
|
|
|
fldMap = _gFields ctx
|
|
|
|
ordByCtx = _gOrdByCtx ctx
|
|
|
|
insCtxMap = _gInsCtxMap ctx
|
|
|
|
|
|
|
|
getQueryOp
|
2020-04-16 10:25:19 +03:00
|
|
|
:: ( HasVersion
|
|
|
|
, MonadError QErr m
|
|
|
|
, MonadIO m)
|
2019-04-17 12:48:41 +03:00
|
|
|
=> GCtx
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> UserInfo
|
2019-10-16 17:33:34 +03:00
|
|
|
-> QueryReusability
|
2020-04-16 10:25:19 +03:00
|
|
|
-> QueryActionExecuter
|
2019-04-17 12:48:41 +03:00
|
|
|
-> VQ.SelSet
|
2019-07-11 08:37:06 +03:00
|
|
|
-> m (LazyRespTx, Maybe EQ.ReusableQueryPlan, EQ.GeneratedSqlMap)
|
2020-04-16 10:25:19 +03:00
|
|
|
getQueryOp gCtx sqlGenCtx userInfo queryReusability actionExecuter selSet =
|
|
|
|
runE gCtx sqlGenCtx userInfo $ EQ.convertQuerySelSet queryReusability selSet actionExecuter
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
resolveMutSelSet
|
2020-02-13 20:38:23 +03:00
|
|
|
:: ( HasVersion
|
|
|
|
, MonadError QErr m
|
2019-04-17 12:48:41 +03:00
|
|
|
, MonadReader r m
|
|
|
|
, Has UserInfo r
|
2019-07-23 14:12:59 +03:00
|
|
|
, Has MutationCtxMap r
|
2019-04-17 12:48:41 +03:00
|
|
|
, Has FieldMap r
|
|
|
|
, Has OrdByCtx r
|
|
|
|
, Has SQLGenCtx r
|
|
|
|
, Has InsCtxMap r
|
2020-02-13 20:38:23 +03:00
|
|
|
, Has HTTP.Manager r
|
|
|
|
, Has [N.Header] r
|
|
|
|
, MonadIO m
|
2019-04-17 12:48:41 +03:00
|
|
|
)
|
|
|
|
=> VQ.SelSet
|
2020-03-20 09:46:45 +03:00
|
|
|
-> m (LazyRespTx, N.ResponseHeaders)
|
2019-04-17 12:48:41 +03:00
|
|
|
resolveMutSelSet fields = do
|
|
|
|
aliasedTxs <- forM (toList fields) $ \fld -> do
|
|
|
|
fldRespTx <- case VQ._fName fld of
|
2020-04-24 12:10:53 +03:00
|
|
|
"__typename" -> return (return $ encJFromJValue mutationRootNamedType, [])
|
2020-03-20 09:46:45 +03:00
|
|
|
_ -> evalReusabilityT $ GR.mutFldToTx fld
|
2019-04-17 12:48:41 +03:00
|
|
|
return (G.unName $ G.unAlias $ VQ._fAlias fld, fldRespTx)
|
|
|
|
|
|
|
|
-- combines all transactions into a single transaction
|
2020-03-20 09:46:45 +03:00
|
|
|
return (liftTx $ toSingleTx aliasedTxs, concatMap (snd . snd) aliasedTxs)
|
2019-04-17 12:48:41 +03:00
|
|
|
where
|
|
|
|
-- A list of aliased transactions for eg
|
|
|
|
-- [("f1", Tx r1), ("f2", Tx r2)]
|
|
|
|
-- are converted into a single transaction as follows
|
|
|
|
-- Tx {"f1": r1, "f2": r2}
|
2020-02-13 20:38:23 +03:00
|
|
|
-- toSingleTx :: [(Text, LazyRespTx)] -> LazyRespTx
|
2019-04-17 12:48:41 +03:00
|
|
|
toSingleTx aliasedTxs =
|
|
|
|
fmap encJFromAssocList $
|
2020-03-20 09:46:45 +03:00
|
|
|
forM aliasedTxs $ \(al, (tx, _)) -> (,) al <$> tx
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
getMutOp
|
2020-02-13 20:38:23 +03:00
|
|
|
:: (HasVersion, MonadError QErr m, MonadIO m)
|
2019-04-17 12:48:41 +03:00
|
|
|
=> GCtx
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> UserInfo
|
2020-02-13 20:38:23 +03:00
|
|
|
-> HTTP.Manager
|
|
|
|
-> [N.Header]
|
2019-04-17 12:48:41 +03:00
|
|
|
-> VQ.SelSet
|
2020-03-20 09:46:45 +03:00
|
|
|
-> m (LazyRespTx, N.ResponseHeaders)
|
2020-02-13 20:38:23 +03:00
|
|
|
getMutOp ctx sqlGenCtx userInfo manager reqHeaders selSet =
|
2020-03-20 09:46:45 +03:00
|
|
|
peelReaderT $ resolveMutSelSet selSet
|
2020-02-13 20:38:23 +03:00
|
|
|
where
|
2020-03-20 09:46:45 +03:00
|
|
|
peelReaderT action =
|
|
|
|
runReaderT action
|
2020-02-13 20:38:23 +03:00
|
|
|
( userInfo, queryCtxMap, mutationCtxMap
|
|
|
|
, typeMap, fldMap, ordByCtx, insCtxMap, sqlGenCtx
|
|
|
|
, manager, reqHeaders
|
|
|
|
)
|
|
|
|
where
|
|
|
|
queryCtxMap = _gQueryCtxMap ctx
|
|
|
|
mutationCtxMap = _gMutationCtxMap ctx
|
|
|
|
typeMap = _gTypes ctx
|
|
|
|
fldMap = _gFields ctx
|
|
|
|
ordByCtx = _gOrdByCtx ctx
|
|
|
|
insCtxMap = _gInsCtxMap ctx
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
getSubsOp
|
|
|
|
:: ( MonadError QErr m
|
|
|
|
, MonadIO m
|
2020-04-16 10:25:19 +03:00
|
|
|
, HasVersion
|
2019-04-17 12:48:41 +03:00
|
|
|
)
|
|
|
|
=> PGExecCtx
|
|
|
|
-> GCtx
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> UserInfo
|
2019-10-16 17:33:34 +03:00
|
|
|
-> QueryReusability
|
2020-04-16 10:25:19 +03:00
|
|
|
-> QueryActionExecuter
|
2020-05-13 11:09:44 +03:00
|
|
|
-> VQ.SelSet
|
2019-08-28 15:19:21 +03:00
|
|
|
-> m (EL.LiveQueryPlan, Maybe EL.ReusableLiveQueryPlan)
|
2020-05-13 11:09:44 +03:00
|
|
|
getSubsOp pgExecCtx gCtx sqlGenCtx userInfo queryReusability actionExecuter fields =
|
|
|
|
runE gCtx sqlGenCtx userInfo $ EL.buildLiveQueryPlan pgExecCtx queryReusability actionExecuter fields
|
|
|
|
-- runE gCtx sqlGenCtx userInfo $ getSubsOpM pgExecCtx queryReusability fld actionExecuter
|
2019-04-17 12:48:41 +03:00
|
|
|
|
2019-03-25 21:25:25 +03:00
|
|
|
execRemoteGQ
|
2020-01-23 00:55:55 +03:00
|
|
|
:: ( HasVersion
|
|
|
|
, MonadIO m
|
2019-07-11 08:37:06 +03:00
|
|
|
, MonadError QErr m
|
|
|
|
, MonadReader ExecutionCtx m
|
|
|
|
)
|
|
|
|
=> RequestId
|
2019-03-25 21:25:25 +03:00
|
|
|
-> UserInfo
|
|
|
|
-> [N.Header]
|
2019-07-11 08:37:06 +03:00
|
|
|
-> GQLReqUnparsed
|
2019-03-25 21:25:25 +03:00
|
|
|
-> RemoteSchemaInfo
|
|
|
|
-> G.TypedOperationDefinition
|
2020-01-16 04:56:57 +03:00
|
|
|
-> m (DiffTime, HttpResponse EncJSON)
|
|
|
|
-- ^ Also returns time spent in http request, for telemetry.
|
2019-07-11 08:37:06 +03:00
|
|
|
execRemoteGQ reqId userInfo reqHdrs q rsi opDef = do
|
|
|
|
execCtx <- ask
|
|
|
|
let logger = _ecxLogger execCtx
|
|
|
|
manager = _ecxHttpManager execCtx
|
|
|
|
opTy = G._todType opDef
|
2019-03-25 21:25:25 +03:00
|
|
|
when (opTy == G.OperationTypeSubscription) $
|
|
|
|
throw400 NotSupported "subscription to remote server is not supported"
|
2020-02-13 20:38:23 +03:00
|
|
|
confHdrs <- makeHeadersFromConf hdrConf
|
|
|
|
let clientHdrs = bool [] (mkClientHeadersForward reqHdrs) fwdClientHdrs
|
2019-05-06 14:27:34 +03:00
|
|
|
-- filter out duplicate headers
|
2020-02-13 20:38:23 +03:00
|
|
|
-- priority: conf headers > resolved userinfo vars > client headers
|
2019-05-06 14:27:34 +03:00
|
|
|
hdrMaps = [ Map.fromList confHdrs
|
|
|
|
, Map.fromList userInfoToHdrs
|
|
|
|
, Map.fromList clientHdrs
|
|
|
|
]
|
2019-08-23 11:57:19 +03:00
|
|
|
headers = Map.toList $ foldr Map.union Map.empty hdrMaps
|
|
|
|
finalHeaders = addDefaultHeaders headers
|
|
|
|
initReqE <- liftIO $ try $ HTTP.parseRequest (show url)
|
|
|
|
initReq <- either httpThrow pure initReqE
|
|
|
|
let req = initReq
|
|
|
|
{ HTTP.method = "POST"
|
|
|
|
, HTTP.requestHeaders = finalHeaders
|
|
|
|
, HTTP.requestBody = HTTP.RequestBodyLBS (J.encode q)
|
|
|
|
, HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000)
|
|
|
|
}
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
L.unLogger logger $ QueryLog q Nothing reqId
|
2020-01-16 04:56:57 +03:00
|
|
|
(time, res) <- withElapsedTime $ liftIO $ try $ HTTP.httpLbs req manager
|
2019-03-25 21:25:25 +03:00
|
|
|
resp <- either httpThrow return res
|
2020-03-20 09:46:45 +03:00
|
|
|
let !httpResp = HttpResponse (encJFromLBS $ resp ^. Wreq.responseBody) $ mkSetCookieHeaders resp
|
2020-01-16 04:56:57 +03:00
|
|
|
return (time, httpResp)
|
2019-03-25 21:25:25 +03:00
|
|
|
|
|
|
|
where
|
2019-08-23 11:57:19 +03:00
|
|
|
RemoteSchemaInfo url hdrConf fwdClientHdrs timeout = rsi
|
2019-03-25 21:25:25 +03:00
|
|
|
httpThrow :: (MonadError QErr m) => HTTP.HttpException -> m a
|
2019-08-20 05:18:04 +03:00
|
|
|
httpThrow = \case
|
|
|
|
HTTP.HttpExceptionRequest _req content -> throw500 $ T.pack . show $ content
|
|
|
|
HTTP.InvalidUrlException _url reason -> throw500 $ T.pack . show $ reason
|
2019-03-25 21:25:25 +03:00
|
|
|
|
2020-04-24 12:10:53 +03:00
|
|
|
userInfoToHdrs = sessionVariablesToHeaders $ _uiSession userInfo
|