mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
1abb1dee69
https://github.com/hasura/graphql-engine-mono/pull/1740 GitOrigin-RevId: e807952058243a97f67cd9969fa434933a08652f
118 lines
5.1 KiB
Haskell
118 lines
5.1 KiB
Haskell
module Hasura.GraphQL.Explain
|
|
( explainGQLQuery
|
|
, GQLExplain
|
|
) where
|
|
|
|
import Hasura.Prelude
|
|
|
|
import qualified Data.Aeson as J
|
|
import qualified Data.Aeson.TH as J
|
|
import qualified Data.HashMap.Strict as Map
|
|
import qualified Data.HashMap.Strict.InsOrd as OMap
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
|
|
|
import qualified Hasura.GraphQL.Execute as E
|
|
import qualified Hasura.GraphQL.Execute.Action as E
|
|
import qualified Hasura.GraphQL.Execute.Query as E
|
|
import qualified Hasura.GraphQL.Execute.RemoteJoin.Collect as RJ
|
|
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
|
|
import qualified Hasura.SQL.AnyBackend as AB
|
|
|
|
import Hasura.Base.Error
|
|
import Hasura.EncJSON
|
|
import Hasura.GraphQL.Execute.Backend
|
|
import Hasura.GraphQL.Execute.Instances ()
|
|
import Hasura.GraphQL.ParameterizedQueryHash
|
|
import Hasura.GraphQL.Parser
|
|
import Hasura.GraphQL.Transport.Backend
|
|
import Hasura.GraphQL.Transport.Instances ()
|
|
import Hasura.Metadata.Class
|
|
import Hasura.RQL.IR
|
|
import Hasura.RQL.Types
|
|
import Hasura.Session
|
|
|
|
|
|
data GQLExplain
|
|
= GQLExplain
|
|
{ _gqeQuery :: !GH.GQLReqParsed
|
|
, _gqeUser :: !(Maybe (Map.HashMap Text Text))
|
|
, _gqeIsRelay :: !(Maybe Bool)
|
|
} deriving (Show, Eq)
|
|
|
|
$(J.deriveJSON hasuraJSON{J.omitNothingFields=True}
|
|
''GQLExplain
|
|
)
|
|
|
|
-- NOTE: This function has a 'MonadTrace' constraint in master, but we don't need it
|
|
-- here. We should evaluate if we need it here.
|
|
explainQueryField
|
|
:: ( MonadError QErr m
|
|
, MonadIO m
|
|
)
|
|
=> UserInfo
|
|
-> G.Name
|
|
-> QueryRootField UnpreparedValue
|
|
-> m EncJSON
|
|
explainQueryField userInfo fieldName rootField = do
|
|
case rootField of
|
|
RFRemote _ -> throw400 InvalidParams "only hasura queries can be explained"
|
|
RFAction _ -> throw400 InvalidParams "query actions cannot be explained"
|
|
RFRaw _ -> pure $ encJFromJValue $ ExplainPlan fieldName Nothing Nothing
|
|
RFDB sourceName exists -> do
|
|
step <- AB.dispatchAnyBackend @BackendExecute exists
|
|
\(SourceConfigWith sourceConfig (QDBR db)) -> do
|
|
let (newDB, remoteJoins) = RJ.getRemoteJoins db
|
|
unless (isNothing remoteJoins) $
|
|
throw400 InvalidParams "queries with remote relationships cannot be explained"
|
|
mkDBQueryExplain fieldName userInfo sourceName sourceConfig newDB
|
|
AB.dispatchAnyBackend @BackendTransport step runDBQueryExplain
|
|
|
|
|
|
explainGQLQuery
|
|
:: forall m
|
|
. ( MonadError QErr m
|
|
, MonadIO m
|
|
, MonadBaseControl IO m
|
|
, MonadMetadataStorage (MetadataStorageT m)
|
|
, MonadQueryTags m
|
|
)
|
|
=> SchemaCache
|
|
-> GQLExplain
|
|
-> m EncJSON
|
|
explainGQLQuery sc (GQLExplain query userVarsRaw maybeIsRelay) = do
|
|
-- NOTE!: we will be executing what follows as though admin role. See e.g. notes in explainField:
|
|
userInfo <-
|
|
mkUserInfo (URBFromSessionVariablesFallback adminRoleName) UAdminSecretSent
|
|
sessionVariables
|
|
-- we don't need to check in allow list as we consider it an admin endpoint
|
|
(graphQLContext, queryParts) <- E.getExecPlanPartial userInfo sc queryType query
|
|
case queryParts of
|
|
G.TypedOperationDefinition G.OperationTypeQuery _ varDefs directives inlinedSelSet -> do
|
|
(unpreparedQueries, _, _) <-
|
|
E.parseGraphQLQuery graphQLContext varDefs (GH._grVariables query) directives inlinedSelSet
|
|
-- TODO: validate directives here
|
|
encJFromList <$>
|
|
for (OMap.toList unpreparedQueries) (uncurry (explainQueryField userInfo))
|
|
|
|
G.TypedOperationDefinition G.OperationTypeMutation _ _ _ _ ->
|
|
throw400 InvalidParams "only queries can be explained"
|
|
|
|
G.TypedOperationDefinition G.OperationTypeSubscription _ varDefs directives inlinedSelSet -> do
|
|
(unpreparedQueries, _, normalizedSelectionSet) <- E.parseGraphQLQuery graphQLContext varDefs (GH._grVariables query) directives inlinedSelSet
|
|
let parameterizedQueryHash = calculateParameterizedQueryHash normalizedSelectionSet
|
|
-- TODO: validate directives here
|
|
-- query-tags are not necessary for EXPLAIN API
|
|
validSubscription <- E.buildSubscriptionPlan userInfo unpreparedQueries parameterizedQueryHash emptyQueryTagsConfig
|
|
case validSubscription of
|
|
E.SEAsyncActionsWithNoRelationships _ -> throw400 NotSupported "async action query fields without relationships to table cannot be explained"
|
|
E.SEOnSourceDB actionIds liveQueryBuilder -> do
|
|
actionLogResponseMap <- fst <$> E.fetchActionLogResponses actionIds
|
|
(_, E.LQP exists) <- liftEitherM $ liftIO $ runExceptT $ liveQueryBuilder actionLogResponseMap
|
|
AB.dispatchAnyBackend @BackendExecute exists \(E.MultiplexedLiveQueryPlan execPlan) ->
|
|
encJFromJValue <$> mkLiveQueryExplain execPlan
|
|
where
|
|
queryType = bool E.QueryHasura E.QueryRelay $ Just True == maybeIsRelay
|
|
sessionVariables = mkSessionVariablesText $ fromMaybe mempty userVarsRaw
|