From fdedb6101ae09b311b5300043f77ba8042af28fc Mon Sep 17 00:00:00 2001 From: Vamshi Surabhi Date: Thu, 1 Aug 2019 13:05:41 +0530 Subject: [PATCH] subscriptions can now be explained --- .../Hasura/GraphQL/Execute/LiveQuery.hs | 121 +++++++------ .../GraphQL/Execute/LiveQuery/Multiplexed.hs | 84 +++++++-- server/src-lib/Hasura/GraphQL/Explain.hs | 166 ++++++++++++------ server/src-lib/Hasura/GraphQL/Resolve.hs | 1 + server/stack.yaml | 2 +- server/stack.yaml.lock | 8 +- 6 files changed, 261 insertions(+), 121 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Execute/LiveQuery.hs b/server/src-lib/Hasura/GraphQL/Execute/LiveQuery.hs index fa442f27f8b7b..1f68c7f45bd81 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/LiveQuery.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/LiveQuery.hs @@ -14,7 +14,11 @@ module Hasura.GraphQL.Execute.LiveQuery , initLiveQueriesState , dumpLiveQueriesState + , LiveQueryOpG(..) + , LiveQueryOp + , LiveQueryOpPartial + , getLiveQueryOpPartial , LiveQueryId , addLiveQuery , removeLiveQuery @@ -22,6 +26,7 @@ module Hasura.GraphQL.Execute.LiveQuery , SubsPlan , subsOpFromPlan , subsOpFromPGAST + ) where import Data.Has @@ -30,7 +35,6 @@ import qualified Control.Concurrent.STM as STM import qualified Data.Aeson as J import qualified Data.HashMap.Strict as Map import qualified Data.HashSet as Set -import qualified Data.Text as T import qualified Database.PG.Query as Q import qualified Database.PG.Query.Connection as Q import qualified Language.GraphQL.Draft.Syntax as G @@ -48,7 +52,6 @@ import Hasura.GraphQL.Execute.LiveQuery.Types import Hasura.Prelude import Hasura.RQL.DML.Select (asSingleRowJsonResp) import Hasura.RQL.Types - import Hasura.SQL.Types import Hasura.SQL.Value @@ -95,9 +98,12 @@ initLiveQueriesState (LQOpts mxOpts fallbackOpts) pgExecCtx = do <*> LQF.initLiveQueriesState fallbackOpts return $ LiveQueriesState mxMap fallbackMap pgExecCtx -data LiveQueryOp - = LQMultiplexed !LQM.MxOp - | LQFallback !LQF.FallbackOp +data LiveQueryOpG f m + = LQFallback !f + | LQMultiplexed !m + deriving (Show, Eq) + +type LiveQueryOp = LiveQueryOpG LQF.FallbackOp LQM.MxOp data LiveQueryId = LQIMultiplexed !LQM.LiveQueryId @@ -161,33 +167,56 @@ type TextEncodedVariables -- referring correctly to the values from '_subs' temporary table -- The variables are at _subs.result_vars.variables and -- session variables at _subs.result_vars.user -toMultiplexedQueryVar - :: (MonadState GV.AnnPGVarVals m) - => GR.UnresolvedVal -> m S.SQLExp -toMultiplexedQueryVar = \case - GR.UVPG annPGVal -> - let GR.AnnPGVal varM isNullable colTy colVal = annPGVal - in case (varM, isNullable) of - -- we don't check for nullability as - -- this is only used for reusable plans - -- the check has to be made before this - (Just var, _) -> do - modify $ Map.insert var (colTy, colVal) - return $ fromResVars (PgTypeSimple colTy) - [ "variables" - , G.unName $ G.unVariable var - ] - _ -> return $ toTxtValue colTy colVal - GR.UVSessVar ty sessVar -> - return $ fromResVars ty [ "user", T.toLower sessVar] - GR.UVSQL sqlExp -> return sqlExp +type FallbackOpPartial = (GR.QueryRootFldUnresolved, Set.HashSet G.Variable) +type MultiplexedOpPartial = (GV.VarPGTypes, Q.Query, TextEncodedVariables) + +type LiveQueryOpPartial = LiveQueryOpG FallbackOpPartial MultiplexedOpPartial + +-- | Creates a partial live query operation, used in both +-- analyze and execution of a live query +getLiveQueryOpPartial + :: ( MonadError QErr m + , MonadIO m + ) + + -- | to validate arguments + => PGExecCtx + + -- | variable definitions as seen in the subscription, needed in + -- checking whether the subscription can be multiplexed or not + -> [G.VariableDefinition] + + -- | The partially processed live query field + -> GR.QueryRootFldUnresolved + + -> m LiveQueryOpPartial +getLiveQueryOpPartial pgExecCtx varDefs astUnresolved = do + -- collect the variables (with their types) used inside the subscription + (_, varTypes) <- flip runStateT mempty $ GR.traverseQueryRootFldAST + collectNonNullableVars astUnresolved + + let nonConfirmingVariables = getNonConfirmingVariables varTypes + + -- Can the subscription be multiplexed? + -- Only if all variables are non null and can be prepared + if null nonConfirmingVariables + then do + let (mxQuery, annVarVals) = LQM.resolveToMxQuery astUnresolved + -- We need to ensure that the values provided for variables + -- are correct according to Postgres. Without this check + -- an invalid value for a variable for one instance of the + -- subscription will take down the entire multiplexed query + txtEncodedVars <- validateAnnVarValsOnPg pgExecCtx annVarVals + return $ LQMultiplexed (varTypes, mxQuery, txtEncodedVars) + else + return $ LQFallback (astUnresolved, nonConfirmingVariables) where - fromResVars ty jPath = - flip S.SETyAnn (S.mkTypeAnn ty) $ S.SEOpApp (S.SQLOp "#>>") - [ S.SEQIden $ S.QIden (S.QualIden $ Iden "_subs") - (Iden "result_vars") - , S.SEArray $ map S.SELit jPath - ] + -- get the variables which don't conifrm to the + -- 'non-null scalar' rule + getNonConfirmingVariables usedVariables = + let queryVariables = Set.fromList $ map G._vdVariable varDefs + confirmingVariables = Map.keysSet usedVariables + in queryVariables `Set.difference` confirmingVariables -- | Creates a live query operation and if possible, a reusable plan -- @@ -216,32 +245,18 @@ subsOpFromPGAST subsOpFromPGAST pgExecCtx reqUnparsed varDefs (fldAls, astUnresolved) = do userInfo <- asks getter - -- collect the variables (with their types) used inside the subscription - (_, varTypes) <- flip runStateT mempty $ GR.traverseQueryRootFldAST - collectNonNullableVars astUnresolved + liveQueryOpPartial <- getLiveQueryOpPartial pgExecCtx varDefs astUnresolved - -- Can the subscription be multiplexed? - -- Only if all variables are non null and can be prepared - if Set.fromList (Map.keys varTypes) == allVars - then mkMultiplexedOp userInfo varTypes - else mkFallbackOp userInfo - where - allVars = Set.fromList $ map G._vdVariable varDefs + case liveQueryOpPartial of + LQFallback _ -> mkFallbackOp userInfo + LQMultiplexed (varTypes, mxQuery, txtEncodedVars) -> + mkMultiplexedOp userInfo varTypes mxQuery txtEncodedVars + where -- multiplexed subscription - mkMultiplexedOp userInfo varTypes = do - (astResolved, annVarVals) <- - flip runStateT mempty $ GR.traverseQueryRootFldAST - toMultiplexedQueryVar astUnresolved + mkMultiplexedOp userInfo varTypes mxQuery txtEncodedVars = do let mxOpCtx = LQM.mkMxOpCtx (userRole userInfo) - (GH._grQuery reqUnparsed) fldAls $ - GR.toPGQuery astResolved - - -- We need to ensure that the values provided for variables - -- are correct according to Postgres. Without this check - -- an invalid value for a variable for one instance of the - -- subscription will take down the entire multiplexed query - txtEncodedVars <- validateAnnVarValsOnPg pgExecCtx annVarVals + (GH._grQuery reqUnparsed) fldAls mxQuery let mxOp = (mxOpCtx, userVars userInfo, txtEncodedVars) return (LQMultiplexed mxOp, Just $ SubsPlan mxOpCtx varTypes) diff --git a/server/src-lib/Hasura/GraphQL/Execute/LiveQuery/Multiplexed.hs b/server/src-lib/Hasura/GraphQL/Execute/LiveQuery/Multiplexed.hs index fcc0c821acdce..66f79cfddef52 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/LiveQuery/Multiplexed.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/LiveQuery/Multiplexed.hs @@ -10,6 +10,7 @@ module Hasura.GraphQL.Execute.LiveQuery.Multiplexed , initLiveQueriesState , dumpLiveQueriesState + , resolveToMxQuery , MxOpCtx , mkMxOpCtx , MxOp @@ -17,6 +18,12 @@ module Hasura.GraphQL.Execute.LiveQuery.Multiplexed , LiveQueryId , addLiveQuery , removeLiveQuery + + , RespId + , newRespId + , RespVars + , getRespVars + , mkMxQueryArgs ) where import Data.List (unfoldr) @@ -27,6 +34,7 @@ import qualified Control.Concurrent.Async as A import qualified Control.Concurrent.STM as STM import qualified Data.Aeson.Extended as J import qualified Data.HashMap.Strict as Map +import qualified Data.Text as T import qualified Data.Time.Clock as Clock import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID @@ -37,11 +45,16 @@ import qualified System.Metrics.Distribution as Metrics import Control.Concurrent (threadDelay) +import qualified Hasura.GraphQL.Resolve as GR +import qualified Hasura.GraphQL.Validate as GV +import qualified Hasura.SQL.DML as S + import Hasura.EncJSON import Hasura.GraphQL.Execute.LiveQuery.Types import Hasura.GraphQL.Transport.HTTP.Protocol import Hasura.Prelude import Hasura.RQL.Types +import Hasura.SQL.Types import Hasura.SQL.Value -- remove these when array encoding is merged @@ -255,9 +268,9 @@ data CandidateState -- and the validated, text encoded query variables data MxOpCtx = MxOpCtx - { _mocGroup :: !LQGroup - , _mocAlias :: !G.Alias - , _mocQuery :: !Q.Query + { _mocGroup :: !LQGroup + , _mocAlias :: !G.Alias + , _mocQuery :: !Q.Query } instance J.ToJSON MxOpCtx where @@ -274,14 +287,14 @@ mkMxOpCtx -> G.Alias -> Q.Query -> MxOpCtx mkMxOpCtx role queryTxt als query = - MxOpCtx lqGroup als $ mkMxQuery query + MxOpCtx lqGroup als query where lqGroup = LQGroup role queryTxt -mkMxQuery :: Q.Query -> Q.Query -mkMxQuery baseQuery = +mkMxQuery :: GR.QueryRootFldResolved -> Q.Query +mkMxQuery astResolved = Q.fromText $ mconcat $ map Q.getQueryText $ - [mxQueryPfx, baseQuery, mxQuerySfx] + [mxQueryPfx, GR.toPGQuery astResolved, mxQuerySfx] where mxQueryPfx :: Q.Query mxQueryPfx = @@ -487,6 +500,55 @@ chunks :: Word32 -> [a] -> [[a]] chunks n = takeWhile (not.null) . unfoldr (Just . splitAt (fromIntegral n)) +newtype MxQueryArgs + = MxQueryArgs { _unMxQueryArgs :: (RespIdList, RespVarsList)} + deriving (Q.ToPrepArgs) + +mkMxQueryArgs :: [(RespId, RespVars)] -> MxQueryArgs +mkMxQueryArgs args = + MxQueryArgs (RespIdList respIdL, RespVarsList respVarsL) + where + (respIdL, respVarsL) = unzip args + +toMultiplexedQueryVar + :: (MonadState GV.AnnPGVarVals m) + => GR.UnresolvedVal -> m S.SQLExp +toMultiplexedQueryVar = \case + GR.UVPG annPGVal -> + let GR.AnnPGVal varM isNullable colTy colVal = annPGVal + in case (varM, isNullable) of + -- we don't check for nullability as + -- this is only used for reusable plans + -- the check has to be made before this + (Just var, _) -> do + modify $ Map.insert var (colTy, colVal) + return $ fromResVars (PgTypeSimple colTy) + [ "variables" + , G.unName $ G.unVariable var + ] + _ -> return $ toTxtValue colTy colVal + GR.UVSessVar ty sessVar -> + return $ fromResVars ty [ "user", T.toLower sessVar] + GR.UVSQL sqlExp -> return sqlExp + where + fromResVars ty jPath = + flip S.SETyAnn (S.mkTypeAnn ty) $ S.SEOpApp (S.SQLOp "#>>") + [ S.SEQIden $ S.QIden (S.QualIden $ Iden "_subs") + (Iden "result_vars") + , S.SEArray $ map S.SELit jPath + ] + +resolveToMxQuery + :: GR.QueryRootFldUnresolved + -> (Q.Query, GV.AnnPGVarVals) +resolveToMxQuery astUnresolved = + (mxQuery, annVarVals) + where + mxQuery = mkMxQuery astResolved + (astResolved, annVarVals) = + flip runState mempty $ GR.traverseQueryRootFldAST + toMultiplexedQueryVar astUnresolved + pollQuery :: RefetchMetrics -> BatchSize @@ -505,7 +567,7 @@ pollQuery metrics batchSize pgExecCtx handler = do mapM (STM.atomically . getCandidateSnapshot) candidates let queryVarsBatches = chunks (unBatchSize batchSize) $ - getQueryVars candidateSnapshotMap + getQueryVars candidateSnapshotMap snapshotFinish <- Clock.getCurrentTime Metrics.add (_rmSnapshot metrics) $ @@ -514,7 +576,7 @@ pollQuery metrics batchSize pgExecCtx handler = do queryInit <- Clock.getCurrentTime mxRes <- runExceptT $ runLazyTx' pgExecCtx $ liftTx $ Q.listQE defaultTxErrorHandler - pgQuery (mkMxQueryPrepArgs queryVars) True + pgQuery (mkMxQueryArgs queryVars) True queryFinish <- Clock.getCurrentTime Metrics.add (_rmQuery metrics) $ realToFrac $ Clock.diffUTCTime queryFinish queryInit @@ -547,10 +609,6 @@ pollQuery metrics batchSize pgExecCtx handler = do getQueryVars candidateSnapshotMap = Map.toList $ fmap _csRespVars candidateSnapshotMap - mkMxQueryPrepArgs l = - let (respIdL, respVarL) = unzip l - in (RespIdList respIdL, RespVarsList respVarL) - getCandidateOperations candidateSnapshotMap = \case Left e -> -- TODO: this is internal error diff --git a/server/src-lib/Hasura/GraphQL/Explain.hs b/server/src-lib/Hasura/GraphQL/Explain.hs index d9923035e06cd..a5955d2b6742e 100644 --- a/server/src-lib/Hasura/GraphQL/Explain.hs +++ b/server/src-lib/Hasura/GraphQL/Explain.hs @@ -3,12 +3,12 @@ module Hasura.GraphQL.Explain , 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 +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 import Hasura.EncJSON import Hasura.GraphQL.Context @@ -20,11 +20,13 @@ import Hasura.RQL.Types import Hasura.SQL.Types import Hasura.SQL.Value -import qualified Hasura.GraphQL.Execute as E -import qualified Hasura.GraphQL.Resolve as RS -import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH -import qualified Hasura.GraphQL.Validate as GV -import qualified Hasura.SQL.DML as S +import qualified Hasura.GraphQL.Execute as E +import qualified Hasura.GraphQL.Execute.LiveQuery as EL +import qualified Hasura.GraphQL.Execute.LiveQuery.Multiplexed as ELM +import qualified Hasura.GraphQL.Resolve as RS +import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH +import qualified Hasura.GraphQL.Validate as GV +import qualified Hasura.SQL.DML as S data GQLExplain = GQLExplain @@ -36,14 +38,14 @@ $(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True} ''GQLExplain ) -data FieldPlan - = FieldPlan - { _fpField :: !G.Name - , _fpSql :: !(Maybe Text) - , _fpPlan :: !(Maybe [Text]) +data QueryFieldPlan + = QueryFieldPlan + { _qfpField :: !G.Name + , _qfpSql :: !(Maybe Text) + , _qfpPlan :: !(Maybe [Text]) } deriving (Show, Eq) -$(J.deriveJSON (J.aesonDrop 3 J.camelCase) ''FieldPlan) +$(J.deriveJSON (J.aesonDrop 3 J.camelCase) ''QueryFieldPlan) type Explain r = (ReaderT r (Except QErr)) @@ -79,32 +81,49 @@ getSessVarVal userInfo sessVar = rn = userRole userInfo usrVars = userVars userInfo -explainField - :: (MonadTx m) - => UserInfo -> GCtx -> SQLGenCtx -> Field -> m FieldPlan -explainField userInfo gCtx sqlGenCtx fld = - case fName of - "__type" -> return $ FieldPlan fName Nothing Nothing - "__schema" -> return $ FieldPlan fName Nothing Nothing - "__typename" -> return $ FieldPlan fName Nothing Nothing - _ -> do - unresolvedAST <- - runExplain (opCtxMap, userInfo, fldMap, orderByCtx, sqlGenCtx) $ - RS.queryFldToPGAST fld - resolvedAST <- RS.traverseQueryRootFldAST (resolveVal userInfo) - unresolvedAST - let txtSQL = Q.getQueryText $ RS.toPGQuery resolvedAST - withExplain = "EXPLAIN (FORMAT TEXT) " <> txtSQL - planLines <- liftTx $ map runIdentity <$> - Q.listQE dmlTxErrorHandler (Q.fromText withExplain) () True - return $ FieldPlan fName (Just txtSQL) $ Just planLines +getUnresolvedAST + :: (MonadError QErr m) + => UserInfo -> GCtx -> SQLGenCtx + -> Field -> m RS.QueryRootFldUnresolved +getUnresolvedAST userInfo gCtx sqlGenCtx fld = + runExplain (opCtxMap, userInfo, fldMap, orderByCtx, sqlGenCtx) $ + RS.queryFldToPGAST fld where - fName = _fName fld - opCtxMap = _gOpCtxMap gCtx fldMap = _gFields gCtx orderByCtx = _gOrdByCtx gCtx +explainHasuraField + :: (MonadTx m) + => UserInfo -> RS.QueryRootFldUnresolved + -> m (Text, [Text]) +explainHasuraField userInfo unresolvedAST = do + resolvedAST <- RS.traverseQueryRootFldAST (resolveVal userInfo) + unresolvedAST + explainPgQuery (RS.toPGQuery resolvedAST) () + +explainPgQuery + :: (MonadTx m, Q.ToPrepArgs a) + => Q.Query + -> a + -> m (Text, [Text]) +explainPgQuery query args = do + let txtSQL = Q.getQueryText query + withExplain = "EXPLAIN (FORMAT TEXT) " <> txtSQL + planLines <- liftTx $ map runIdentity <$> + Q.listQE dmlTxErrorHandler (Q.fromText withExplain) args True + return (txtSQL, planLines) + +data SubscriptionExplainOutput + = SubscriptionExplainOutput + { _seoIsMultiplexable :: !Bool + -- only exists when subscription is not multiplexed + , _seoReason :: !(Maybe Text) + , _seoSql :: !Text + , _seoPlan :: ![Text] + } deriving (Show, Eq) +$(J.deriveJSON (J.aesonDrop 4 J.camelCase) ''SubscriptionExplainOutput) + explainGQLQuery :: (MonadError QErr m, MonadIO m) => PGExecCtx @@ -113,23 +132,70 @@ explainGQLQuery -> Bool -> GQLExplain -> m EncJSON -explainGQLQuery pgExecCtx sc sqlGenCtx enableAL (GQLExplain query userVarsRaw) = do - execPlan <- E.getExecPlanPartial userInfo sc enableAL query - (gCtx, rootSelSet) <- case execPlan of - E.GExPHasura (gCtx, rootSelSet, _) -> - return (gCtx, rootSelSet) - E.GExPRemote _ _ -> +explainGQLQuery pgExecCtx sc sqlGenCtx enableAllowList + (GQLExplain query userVarsRaw) = do + + execPlan <- E.getExecPlanPartial userInfo sc enableAllowList query + (gCtx, rootSelSet, varDefs) <- case execPlan of + E.GExPHasura ctx -> return ctx + E.GExPRemote _ _ -> throw400 InvalidParams "only hasura queries can be explained" case rootSelSet of GV.RQuery selSet -> do - let tx = mapM (explainField userInfo gCtx sqlGenCtx) (toList selSet) - plans <- liftIO (runExceptT $ runLazyTx pgExecCtx tx) >>= liftEither + let tx = mapM (explainQueryField gCtx) (toList selSet) + plans <- runTx tx return $ encJFromJValue plans GV.RMutation _ -> - throw400 InvalidParams "only queries can be explained" - GV.RSubscription _ -> - throw400 InvalidParams "only queries can be explained" - + throw400 InvalidParams "only queries/subscriptions can be explained" + GV.RSubscription subsField -> + encJFromJValue <$> explainSubscriptionField gCtx subsField varDefs where usrVars = mkUserVars $ maybe [] Map.toList userVarsRaw userInfo = mkUserInfo (fromMaybe adminRole $ roleFromVars usrVars) usrVars + + runTx :: (MonadIO m, MonadError QErr m) => LazyTx QErr a -> m a + runTx tx = liftIO (runExceptT $ runLazyTx pgExecCtx tx) >>= liftEither + + explainQueryField :: (MonadTx m) => GCtx -> Field -> m QueryFieldPlan + explainQueryField gCtx fld = + case fName of + "__type" -> return $ QueryFieldPlan fName Nothing Nothing + "__schema" -> return $ QueryFieldPlan fName Nothing Nothing + "__typename" -> return $ QueryFieldPlan fName Nothing Nothing + _ -> do + unresolvedAST <- getUnresolvedAST userInfo gCtx sqlGenCtx fld + (txtSQL, planLines) <- explainHasuraField userInfo unresolvedAST + return $ QueryFieldPlan fName (Just txtSQL) $ Just planLines + where + fName = _fName fld + + explainSubscriptionField + :: (MonadIO m, MonadError QErr m) + => GCtx -> Field -> [G.VariableDefinition] -> m SubscriptionExplainOutput + explainSubscriptionField gCtx subsField varDefs = do + + -- we only partially resolve the live query operation so that + -- we have the necessary information to explain the underlying queries + astUnresolved <- getUnresolvedAST userInfo gCtx sqlGenCtx subsField + liveQueryOpPartial <- EL.getLiveQueryOpPartial pgExecCtx varDefs astUnresolved + + (isMultiPlexed, reasonM, explainTx) <- case liveQueryOpPartial of + + -- in case of fallback, the output will be the same as that of the query + EL.LQFallback (_, nonConfirmingVariables) -> do + let reason = "the following variables are not non-nullable scalars: " <> + GV.showVars (toList nonConfirmingVariables) + return (False, Just reason, explainHasuraField userInfo astUnresolved) + + -- in case of multiplexed, we show the multiplexed query that is + -- used for all such subscirptions, but it is 'explain analyz'ed with + -- only the arguments of the given subscription + EL.LQMultiplexed (_, mxQuery, txtEncodedVars) -> do + -- every mx subscription needs a unique id + respId <- liftIO ELM.newRespId + let respVars = ELM.getRespVars usrVars txtEncodedVars + mxQueryArgs = ELM.mkMxQueryArgs [(respId, respVars)] + explainTx = explainPgQuery mxQuery mxQueryArgs + return (True, Nothing, explainTx) + (txtSQL, planLines) <- runTx explainTx + return $ SubscriptionExplainOutput isMultiPlexed reasonM txtSQL planLines diff --git a/server/src-lib/Hasura/GraphQL/Resolve.hs b/server/src-lib/Hasura/GraphQL/Resolve.hs index f6d77c44ea859..e44944daf369b 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve.hs @@ -2,6 +2,7 @@ module Hasura.GraphQL.Resolve ( mutFldToTx , queryFldToPGAST , RS.traverseQueryRootFldAST + , RS.QueryRootFldResolved , RS.toPGQuery , UnresolvedVal(..) , AnnPGVal(..) diff --git a/server/stack.yaml b/server/stack.yaml index c345d585f0425..9d126fb7140c5 100644 --- a/server/stack.yaml +++ b/server/stack.yaml @@ -2,7 +2,7 @@ # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) # resolver: lts-10.8 -resolver: lts-13.20 +resolver: lts-13.30 # Local packages, usually specified by relative directory name packages: - '.' diff --git a/server/stack.yaml.lock b/server/stack.yaml.lock index 5367dd13598e0..cf1d18dfc6269 100644 --- a/server/stack.yaml.lock +++ b/server/stack.yaml.lock @@ -97,7 +97,7 @@ packages: hackage: Spock-core-0.13.0.0 snapshots: - completed: - size: 498167 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/20.yaml - sha256: cda928d57b257a5f17bcad796843c9daa674fef47d600dbea3aa7b0e49d64a11 - original: lts-13.20 + size: 500539 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/30.yaml + sha256: 59ad6b944c9903847fecdc1d4815e8500c1f9999d80fd1b4d2d66e408faec44b + original: lts-13.30