2018-10-19 05:15:28 +03:00
|
|
|
module Hasura.GraphQL.Explain
|
|
|
|
( explainGQLQuery
|
|
|
|
, GQLExplain
|
|
|
|
) where
|
|
|
|
|
|
|
|
import qualified Data.Aeson as J
|
|
|
|
import qualified Data.Aeson.Casing as J
|
|
|
|
import qualified Data.Aeson.TH as J
|
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
|
|
import qualified Database.PG.Query as Q
|
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
|
2019-03-18 19:22:21 +03:00
|
|
|
import Hasura.EncJSON
|
2019-03-25 21:25:25 +03:00
|
|
|
import Hasura.GraphQL.Context
|
2020-04-24 12:10:53 +03:00
|
|
|
import Hasura.GraphQL.Resolve.Action
|
2019-12-13 00:46:33 +03:00
|
|
|
import Hasura.GraphQL.Validate.Types (evalReusabilityT, runReusabilityT)
|
2018-10-19 05:15:28 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.DML.Internal
|
|
|
|
import Hasura.RQL.Types
|
2020-04-24 12:10:53 +03:00
|
|
|
import Hasura.Server.Version (HasVersion)
|
|
|
|
import Hasura.Session
|
2018-10-19 05:15:28 +03:00
|
|
|
import Hasura.SQL.Types
|
2019-04-17 12:48:41 +03:00
|
|
|
import Hasura.SQL.Value
|
2018-10-19 05:15:28 +03:00
|
|
|
|
2019-03-25 21:25:25 +03:00
|
|
|
import qualified Hasura.GraphQL.Execute as E
|
2019-09-30 22:50:57 +03:00
|
|
|
import qualified Hasura.GraphQL.Execute.LiveQuery as E
|
2019-04-17 12:48:41 +03:00
|
|
|
import qualified Hasura.GraphQL.Resolve as RS
|
2018-10-19 05:15:28 +03:00
|
|
|
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
|
|
|
|
import qualified Hasura.GraphQL.Validate as GV
|
2019-04-17 12:48:41 +03:00
|
|
|
import qualified Hasura.SQL.DML as S
|
2018-10-19 05:15:28 +03:00
|
|
|
|
|
|
|
data GQLExplain
|
|
|
|
= GQLExplain
|
2019-04-17 12:48:41 +03:00
|
|
|
{ _gqeQuery :: !GH.GQLReqParsed
|
2018-10-26 18:57:22 +03:00
|
|
|
, _gqeUser :: !(Maybe (Map.HashMap Text Text))
|
2018-10-19 05:15:28 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
$(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
|
|
|
|
''GQLExplain
|
|
|
|
)
|
|
|
|
|
|
|
|
data FieldPlan
|
|
|
|
= FieldPlan
|
|
|
|
{ _fpField :: !G.Name
|
|
|
|
, _fpSql :: !(Maybe Text)
|
|
|
|
, _fpPlan :: !(Maybe [Text])
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
$(J.deriveJSON (J.aesonDrop 3 J.camelCase) ''FieldPlan)
|
|
|
|
|
2020-04-16 10:25:19 +03:00
|
|
|
type Explain r m =
|
|
|
|
(ReaderT r (ExceptT QErr m))
|
2018-10-19 05:15:28 +03:00
|
|
|
|
|
|
|
runExplain
|
|
|
|
:: (MonadError QErr m)
|
2020-04-16 10:25:19 +03:00
|
|
|
=> r -> Explain r m a -> m a
|
2018-10-19 05:15:28 +03:00
|
|
|
runExplain ctx m =
|
2020-04-16 10:25:19 +03:00
|
|
|
either throwError return =<< runExceptT (runReaderT m ctx)
|
2018-10-19 05:15:28 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
resolveVal
|
|
|
|
:: (MonadError QErr m)
|
2019-08-09 12:19:17 +03:00
|
|
|
=> UserInfo -> RS.UnresolvedVal -> m S.SQLExp
|
2019-04-17 12:48:41 +03:00
|
|
|
resolveVal userInfo = \case
|
|
|
|
RS.UVPG annPGVal ->
|
2019-08-09 12:19:17 +03:00
|
|
|
RS.txtConverter annPGVal
|
2019-07-10 13:19:58 +03:00
|
|
|
RS.UVSessVar ty sessVar -> do
|
|
|
|
sessVarVal <- S.SELit <$> getSessVarVal userInfo sessVar
|
|
|
|
return $ flip S.SETyAnn (S.mkTypeAnn ty) $ case ty of
|
2019-08-29 16:07:05 +03:00
|
|
|
PGTypeScalar colTy -> withConstructorFn colTy sessVarVal
|
2019-07-22 15:47:13 +03:00
|
|
|
PGTypeArray _ -> sessVarVal
|
2019-04-17 12:48:41 +03:00
|
|
|
RS.UVSQL sqlExp -> return sqlExp
|
2020-04-24 12:10:53 +03:00
|
|
|
RS.UVSession -> pure $ sessionInfoJsonExp $ _uiSession userInfo
|
2019-04-17 12:48:41 +03:00
|
|
|
|
|
|
|
getSessVarVal
|
|
|
|
:: (MonadError QErr m)
|
2020-04-24 12:10:53 +03:00
|
|
|
=> UserInfo -> SessionVariable -> m Text
|
2019-04-17 12:48:41 +03:00
|
|
|
getSessVarVal userInfo sessVar =
|
2020-04-24 12:10:53 +03:00
|
|
|
onNothing (getSessionVariableValue sessVar sessionVariables) $
|
2019-04-17 12:48:41 +03:00
|
|
|
throw400 UnexpectedPayload $
|
|
|
|
"missing required session variable for role " <> rn <<>
|
2020-04-24 12:10:53 +03:00
|
|
|
" : " <> sessionVariableToText sessVar
|
2019-04-17 12:48:41 +03:00
|
|
|
where
|
2020-04-24 12:10:53 +03:00
|
|
|
rn = _uiRole userInfo
|
|
|
|
sessionVariables = _uiSession userInfo
|
2019-04-17 12:48:41 +03:00
|
|
|
|
2018-10-19 05:15:28 +03:00
|
|
|
explainField
|
2020-04-16 10:25:19 +03:00
|
|
|
:: (MonadError QErr m, MonadTx m, HasVersion, MonadIO m)
|
|
|
|
=> UserInfo
|
|
|
|
-> GCtx
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> QueryActionExecuter
|
|
|
|
-> GV.Field
|
|
|
|
-> m FieldPlan
|
|
|
|
explainField userInfo gCtx sqlGenCtx actionExecuter fld =
|
2018-10-19 05:15:28 +03:00
|
|
|
case fName of
|
|
|
|
"__type" -> return $ FieldPlan fName Nothing Nothing
|
|
|
|
"__schema" -> return $ FieldPlan fName Nothing Nothing
|
|
|
|
"__typename" -> return $ FieldPlan fName Nothing Nothing
|
|
|
|
_ -> do
|
2019-04-17 12:48:41 +03:00
|
|
|
unresolvedAST <-
|
2019-07-23 14:12:59 +03:00
|
|
|
runExplain (queryCtxMap, userInfo, fldMap, orderByCtx, sqlGenCtx) $
|
2020-04-16 10:25:19 +03:00
|
|
|
evalReusabilityT $ RS.queryFldToPGAST fld actionExecuter
|
|
|
|
resolvedAST <- RS.traverseQueryRootFldAST (resolveVal userInfo) unresolvedAST
|
2019-04-17 12:48:41 +03:00
|
|
|
let txtSQL = Q.getQueryText $ RS.toPGQuery resolvedAST
|
2019-01-25 06:31:54 +03:00
|
|
|
withExplain = "EXPLAIN (FORMAT TEXT) " <> txtSQL
|
2018-10-19 05:15:28 +03:00
|
|
|
planLines <- liftTx $ map runIdentity <$>
|
2020-04-16 10:25:19 +03:00
|
|
|
Q.listQE dmlTxErrorHandler (Q.fromText withExplain) () True
|
2019-01-25 06:31:54 +03:00
|
|
|
return $ FieldPlan fName (Just txtSQL) $ Just planLines
|
2018-10-19 05:15:28 +03:00
|
|
|
where
|
2019-08-09 12:19:17 +03:00
|
|
|
fName = GV._fName fld
|
2019-01-17 09:21:38 +03:00
|
|
|
|
2019-07-23 14:12:59 +03:00
|
|
|
queryCtxMap = _gQueryCtxMap gCtx
|
2018-10-19 05:15:28 +03:00
|
|
|
fldMap = _gFields gCtx
|
2018-10-26 14:57:33 +03:00
|
|
|
orderByCtx = _gOrdByCtx gCtx
|
2018-10-19 05:15:28 +03:00
|
|
|
|
|
|
|
explainGQLQuery
|
2020-04-16 10:25:19 +03:00
|
|
|
:: (MonadError QErr m, MonadIO m,HasVersion)
|
2019-04-17 12:48:41 +03:00
|
|
|
=> PGExecCtx
|
2018-11-23 16:02:46 +03:00
|
|
|
-> SchemaCache
|
2019-03-01 14:45:04 +03:00
|
|
|
-> SQLGenCtx
|
2019-05-16 09:13:25 +03:00
|
|
|
-> Bool
|
2020-04-16 10:25:19 +03:00
|
|
|
-> QueryActionExecuter
|
2018-10-19 05:15:28 +03:00
|
|
|
-> GQLExplain
|
2019-03-18 19:22:21 +03:00
|
|
|
-> m EncJSON
|
2020-04-16 10:25:19 +03:00
|
|
|
explainGQLQuery pgExecCtx sc sqlGenCtx enableAL actionExecuter (GQLExplain query userVarsRaw) = do
|
2020-05-05 22:57:17 +03:00
|
|
|
userInfo <- mkUserInfo (URBPreDetermined adminRoleName) UAdminSecretSent sessionVariables
|
2019-10-16 17:33:34 +03:00
|
|
|
(execPlan, queryReusability) <- runReusabilityT $
|
|
|
|
E.getExecPlanPartial userInfo sc enableAL query
|
2019-03-25 21:25:25 +03:00
|
|
|
(gCtx, rootSelSet) <- case execPlan of
|
2019-09-14 09:01:06 +03:00
|
|
|
E.GExPHasura (gCtx, rootSelSet) ->
|
2019-03-25 21:25:25 +03:00
|
|
|
return (gCtx, rootSelSet)
|
|
|
|
E.GExPRemote _ _ ->
|
|
|
|
throw400 InvalidParams "only hasura queries can be explained"
|
|
|
|
case rootSelSet of
|
2019-09-30 22:50:57 +03:00
|
|
|
GV.RQuery selSet ->
|
2020-04-16 10:25:19 +03:00
|
|
|
runInTx $ encJFromJValue <$> traverse (explainField userInfo gCtx sqlGenCtx actionExecuter)
|
|
|
|
(toList selSet)
|
2019-03-25 21:25:25 +03:00
|
|
|
GV.RMutation _ ->
|
|
|
|
throw400 InvalidParams "only queries can be explained"
|
2019-09-30 22:50:57 +03:00
|
|
|
GV.RSubscription rootField -> do
|
2020-04-16 10:25:19 +03:00
|
|
|
(plan, _) <- E.getSubsOp pgExecCtx gCtx sqlGenCtx userInfo queryReusability actionExecuter rootField
|
2019-09-30 22:50:57 +03:00
|
|
|
runInTx $ encJFromJValue <$> E.explainLiveQueryPlan plan
|
2018-10-19 05:15:28 +03:00
|
|
|
where
|
2020-04-24 12:10:53 +03:00
|
|
|
sessionVariables = mkSessionVariablesText $ maybe [] Map.toList userVarsRaw
|
2019-11-15 03:20:18 +03:00
|
|
|
runInTx = liftEither <=< liftIO . runExceptT . runLazyTx pgExecCtx Q.ReadOnly
|