server: disable caching for actions with forward client headers enabled

Co-authored-by: Lyndon Maydwell <92299+sordina@users.noreply.github.com>
Co-authored-by: Antoine Leblanc <1618949+nicuveo@users.noreply.github.com>
Co-authored-by: Abby Sassel <3883855+sassela@users.noreply.github.com>
Co-authored-by: hasura-bot <30118761+hasura-bot@users.noreply.github.com>
Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com>
Co-authored-by: Ikechukwu Eze <22247592+iykekings@users.noreply.github.com>
Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com>
Co-authored-by: Rishichandra Wawhal <27274869+wawhal@users.noreply.github.com>
Co-authored-by: Naveen Naidu <30195193+Naveenaidu@users.noreply.github.com>
Co-authored-by: Vishnu Bharathi <4211715+scriptnull@users.noreply.github.com>
GitOrigin-RevId: c9a8be3cb607f7767e9d6791717106adf123e3a8
This commit is contained in:
Sameer Kolhar 2021-04-13 12:30:43 +05:30 committed by hasura-bot
parent 17dc201fef
commit b274a2d240
17 changed files with 192 additions and 50 deletions

View File

@ -134,6 +134,7 @@ query {
- server: fix action custom types failing to parse when mutually recursive - server: fix action custom types failing to parse when mutually recursive
- server: fix MSSQL table name descriptions - server: fix MSSQL table name descriptions
- server: emit `postgres-max-connections-error` when max postgres connections are reached - server: emit `postgres-max-connections-error` when max postgres connections are reached
- server: disable caching for actions when "forward-client-headers" option is turned on
- console: allow editing rest endpoints queries and misc ui improvements - console: allow editing rest endpoints queries and misc ui improvements
- console: display collection names and queries from all collections in allowlist - console: display collection names and queries from all collections in allowlist
- cli: match ordering of keys in project metadata files with server metadata - cli: match ordering of keys in project metadata files with server metadata

View File

@ -23,8 +23,9 @@ used) cache, and removed from the cache as needed based on usage.
A query's response can be cached only if the following conditions hold: A query's response can be cached only if the following conditions hold:
- The query does not make use of remote schemas or remote joins - The query does **not** make use of ``remote schemas`` or ``remote joins``.
- The response JSON is under 100KB in size - The query **isn't** an ``Action`` that has ``forward_client_headers`` (see :ref:`ActionDefinition`) set to ``true``.
- The response JSON is **under** 100KB in size
.. admonition:: Support .. admonition:: Support

View File

@ -721,7 +721,7 @@ instance HttpLog PGMetadataStorageApp where
mkHttpAccessLogContext userInfoM reqId waiReq compressedResponse qTime cType headers mkHttpAccessLogContext userInfoM reqId waiReq compressedResponse qTime cType headers
instance MonadExecuteQuery PGMetadataStorageApp where instance MonadExecuteQuery PGMetadataStorageApp where
cacheLookup _ _ = pure ([], Nothing) cacheLookup _ _ _ = pure ([], Nothing)
cacheStore _ _ = pure () cacheStore _ _ = pure ()
instance UserAuthentication (Tracing.TraceT PGMetadataStorageApp) where instance UserAuthentication (Tracing.TraceT PGMetadataStorageApp) where

View File

@ -185,7 +185,7 @@ resolveActionMutationAsync
resolveActionMutationAsync annAction reqHeaders sessionVariables = resolveActionMutationAsync annAction reqHeaders sessionVariables =
insertAction actionName sessionVariables reqHeaders inputArgs insertAction actionName sessionVariables reqHeaders inputArgs
where where
AnnActionMutationAsync actionName inputArgs = annAction AnnActionMutationAsync actionName _ inputArgs = annAction
{- Note: [Resolving async action query] {- Note: [Resolving async action query]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -260,7 +260,7 @@ resolveAsyncActionQuery userInfo annAction =
in RS.AnnSelectG annotatedFields tableFromExp tablePermissions tableArguments stringifyNumerics in RS.AnnSelectG annotatedFields tableFromExp tablePermissions tableArguments stringifyNumerics
where where
AnnActionAsyncQuery _ actionId outputType asyncFields definitionList stringifyNumerics actionSource = annAction AnnActionAsyncQuery _ actionId outputType asyncFields definitionList stringifyNumerics _ actionSource = annAction
idColumn = (unsafePGCol "id", PGUUID) idColumn = (unsafePGCol "id", PGUUID)
responsePayloadColumn = (unsafePGCol "response_payload", PGJSONB) responsePayloadColumn = (unsafePGCol "response_payload", PGJSONB)

View File

@ -23,6 +23,7 @@ import Hasura.GraphQL.Execute.Action.Types (ActionExecutionPlan)
import Hasura.GraphQL.Execute.LiveQuery.Plan import Hasura.GraphQL.Execute.LiveQuery.Plan
import Hasura.GraphQL.Parser hiding (Type) import Hasura.GraphQL.Parser hiding (Type)
import Hasura.RQL.IR.RemoteJoin import Hasura.RQL.IR.RemoteJoin
import Hasura.RQL.Types.Action
import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Common import Hasura.RQL.Types.Common
import Hasura.RQL.Types.Error import Hasura.RQL.Types.Error
@ -100,7 +101,7 @@ data ExecutionStep where
-> ExecutionStep -> ExecutionStep
-- ^ A query to execute against the database -- ^ A query to execute against the database
ExecStepAction ExecStepAction
:: ActionExecutionPlan :: (ActionExecutionPlan, ActionsInfo)
-> ExecutionStep -> ExecutionStep
-- ^ Execute an action -- ^ Execute an action
ExecStepRemote ExecStepRemote

View File

@ -89,11 +89,15 @@ convertMutationSelectionSet env logger gqlContext SQLGenCtx{stringifyNum} userIn
RFRemote remoteField -> do RFRemote remoteField -> do
RemoteFieldG remoteSchemaInfo resolvedRemoteField <- resolveRemoteField userInfo remoteField RemoteFieldG remoteSchemaInfo resolvedRemoteField <- resolveRemoteField userInfo remoteField
pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeMutation $ [G.SelectionField resolvedRemoteField] pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeMutation $ [G.SelectionField resolvedRemoteField]
RFAction action -> RFAction action -> do
ExecStepAction <$> convertMutationAction env logger userInfo manager reqHeaders action (actionName, _fch) <- pure $ case action of
AMSync s -> (_aaeName s, _aaeForwardClientHeaders s)
AMAsync s -> (_aamaName s, _aamaForwardClientHeaders s)
plan <- convertMutationAction env logger userInfo manager reqHeaders action
pure $ ExecStepAction (plan, ActionsInfo actionName _fch) -- `_fch` represents the `forward_client_headers` option from the action
-- definition which is currently being ignored for actions that are mutations
RFRaw s -> RFRaw s ->
pure $ ExecStepRaw s pure $ ExecStepRaw s
return (txs, resolvedSelSet) return (txs, resolvedSelSet)
where where
reportParseErrors errs = case NE.head errs of reportParseErrors errs = case NE.head errs of

View File

@ -86,10 +86,11 @@ convertQuerySelSet env logger gqlContext userInfo manager reqHeaders directives
RFRemote rf -> do RFRemote rf -> do
RemoteFieldG remoteSchemaInfo remoteField <- for rf $ resolveRemoteVariable userInfo RemoteFieldG remoteSchemaInfo remoteField <- for rf $ resolveRemoteVariable userInfo
pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeQuery [G.SelectionField remoteField] pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeQuery [G.SelectionField remoteField]
RFAction a -> RFAction a -> do
pure $ ExecStepAction $ case a of (action, actionName, fch) <- pure $ case a of
AQQuery s -> AEPSync $ resolveActionExecution env logger userInfo s (ActionExecContext manager reqHeaders usrVars) AQQuery s -> (AEPSync $ resolveActionExecution env logger userInfo s (ActionExecContext manager reqHeaders usrVars), _aaeName s, _aaeForwardClientHeaders s)
AQAsync s -> AEPAsyncQuery $ AsyncActionQueryExecutionPlan (_aaaqActionId s) $ resolveAsyncActionQuery userInfo s AQAsync s -> (AEPAsyncQuery $ AsyncActionQueryExecutionPlan (_aaaqActionId s) $ resolveAsyncActionQuery userInfo s, _aaaqName s, _aaaqForwardClientHeaders s)
pure $ ExecStepAction (action, (ActionsInfo actionName fch))
RFRaw r -> RFRaw r ->
pure $ ExecStepRaw r pure $ ExecStepRaw r

View File

@ -55,7 +55,7 @@ actionExecute
-> m (Maybe (FieldParser n (AnnActionExecution 'Postgres (UnpreparedValue 'Postgres)))) -> m (Maybe (FieldParser n (AnnActionExecution 'Postgres (UnpreparedValue 'Postgres))))
actionExecute nonObjectTypeMap actionInfo = runMaybeT do actionExecute nonObjectTypeMap actionInfo = runMaybeT do
roleName <- askRoleName roleName <- askRoleName
guard $ (roleName == adminRoleName || roleName `Map.member` permissions) guard (roleName == adminRoleName || roleName `Map.member` permissions)
let fieldName = unActionName actionName let fieldName = unActionName actionName
description = G.Description <$> comment description = G.Description <$> comment
inputArguments <- lift $ actionInputArguments nonObjectTypeMap $ _adArguments definition inputArguments <- lift $ actionInputArguments nonObjectTypeMap $ _adArguments definition
@ -77,7 +77,7 @@ actionExecute nonObjectTypeMap actionInfo = runMaybeT do
, _aaeSource = getActionSourceInfo outputObject , _aaeSource = getActionSourceInfo outputObject
} }
where where
ActionInfo actionName (outputType, outputObject) definition permissions comment = actionInfo ActionInfo actionName (outputType, outputObject) definition permissions _ comment = actionInfo
-- | actionAsyncMutation is used to execute a asynchronous mutation action. An -- | actionAsyncMutation is used to execute a asynchronous mutation action. An
-- asynchronous action expects the field name and the input arguments to the -- asynchronous action expects the field name and the input arguments to the
@ -97,9 +97,9 @@ actionAsyncMutation nonObjectTypeMap actionInfo = runMaybeT do
let fieldName = unActionName actionName let fieldName = unActionName actionName
description = G.Description <$> comment description = G.Description <$> comment
pure $ P.selection fieldName description inputArguments actionIdParser pure $ P.selection fieldName description inputArguments actionIdParser
<&> AnnActionMutationAsync actionName <&> AnnActionMutationAsync actionName forwardClientHeaders
where where
ActionInfo actionName _ definition permissions comment = actionInfo ActionInfo actionName _ definition permissions forwardClientHeaders comment = actionInfo
-- | actionAsyncQuery is used to query/subscribe to the result of an -- | actionAsyncQuery is used to query/subscribe to the result of an
-- asynchronous mutation action. The only input argument to an -- asynchronous mutation action. The only input argument to an
@ -165,10 +165,11 @@ actionAsyncQuery actionInfo = runMaybeT do
, _aaaqFields = fields , _aaaqFields = fields
, _aaaqDefinitionList = mkDefinitionList outputObject , _aaaqDefinitionList = mkDefinitionList outputObject
, _aaaqStringifyNum = stringifyNum , _aaaqStringifyNum = stringifyNum
, _aaaqForwardClientHeaders = forwardClientHeaders
, _aaaqSource = getActionSourceInfo outputObject , _aaaqSource = getActionSourceInfo outputObject
} }
where where
ActionInfo actionName (outputType, outputObject) definition permissions comment = actionInfo ActionInfo actionName (outputType, outputObject) definition permissions forwardClientHeaders comment = actionInfo
idFieldName = $$(G.litName "id") idFieldName = $$(G.litName "id")
idFieldDescription = "the unique id of an action" idFieldDescription = "the unique id of an action"

View File

@ -86,6 +86,8 @@ class Monad m => MonadExecuteQuery m where
cacheLookup cacheLookup
:: [RemoteSchemaInfo] :: [RemoteSchemaInfo]
-- ^ Used to check if the elaborated query supports caching -- ^ Used to check if the elaborated query supports caching
-> [ActionsInfo]
-- ^ Used to check if actions query supports caching (unsupported if `forward_client_headers` is set)
-> QueryCacheKey -> QueryCacheKey
-- ^ Key that uniquely identifies the result of a query execution -- ^ Key that uniquely identifies the result of a query execution
-> TraceT (ExceptT QErr m) (HTTP.ResponseHeaders, Maybe EncJSON) -> TraceT (ExceptT QErr m) (HTTP.ResponseHeaders, Maybe EncJSON)
@ -112,19 +114,19 @@ class Monad m => MonadExecuteQuery m where
-- ^ Always succeeds -- ^ Always succeeds
instance MonadExecuteQuery m => MonadExecuteQuery (ReaderT r m) where instance MonadExecuteQuery m => MonadExecuteQuery (ReaderT r m) where
cacheLookup a b = hoist (hoist lift) $ cacheLookup a b cacheLookup a b c = hoist (hoist lift) $ cacheLookup a b c
cacheStore a b = hoist (hoist lift) $ cacheStore a b cacheStore a b = hoist (hoist lift) $ cacheStore a b
instance MonadExecuteQuery m => MonadExecuteQuery (ExceptT r m) where instance MonadExecuteQuery m => MonadExecuteQuery (ExceptT r m) where
cacheLookup a b = hoist (hoist lift) $ cacheLookup a b cacheLookup a b c = hoist (hoist lift) $ cacheLookup a b c
cacheStore a b = hoist (hoist lift) $ cacheStore a b cacheStore a b = hoist (hoist lift) $ cacheStore a b
instance MonadExecuteQuery m => MonadExecuteQuery (TraceT m) where instance MonadExecuteQuery m => MonadExecuteQuery (TraceT m) where
cacheLookup a b = hoist (hoist lift) $ cacheLookup a b cacheLookup a b c = hoist (hoist lift) $ cacheLookup a b c
cacheStore a b = hoist (hoist lift) $ cacheStore a b cacheStore a b = hoist (hoist lift) $ cacheStore a b
instance MonadExecuteQuery m => MonadExecuteQuery (MetadataStorageT m) where instance MonadExecuteQuery m => MonadExecuteQuery (MetadataStorageT m) where
cacheLookup a b = hoist (hoist lift) $ cacheLookup a b cacheLookup a b c = hoist (hoist lift) $ cacheLookup a b c
cacheStore a b = hoist (hoist lift) $ cacheStore a b cacheStore a b = hoist (hoist lift) $ cacheStore a b
-- | A partial result, e.g. from a remote schema or postgres, which we'll -- | A partial result, e.g. from a remote schema or postgres, which we'll
@ -222,7 +224,12 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
E.ExecStepDB _headers exists -> E.ExecStepDB _headers exists ->
AB.dispatchAnyBackend @BackendTransport exists EB.getRemoteSchemaInfo AB.dispatchAnyBackend @BackendTransport exists EB.getRemoteSchemaInfo
_ -> [] _ -> []
(responseHeaders, cachedValue) <- Tracing.interpTraceT (liftEitherM . runExceptT) $ cacheLookup remoteJoins cacheKey actionsInfo = foldl getExecStepActionWithActionInfo [] $ OMap.elems $ OMap.filter (\x -> case x of
E.ExecStepAction (_, _) -> True
_ -> False
) queryPlans
(responseHeaders, cachedValue) <- Tracing.interpTraceT (liftEitherM . runExceptT) $ cacheLookup remoteJoins actionsInfo cacheKey
case fmap decodeGQResp cachedValue of case fmap decodeGQResp cachedValue of
Just cachedResponseData -> Just cachedResponseData ->
pure (Telem.Query, 0, Telem.Local, HttpResponse cachedResponseData responseHeaders, normalizedSelectionSet) pure (Telem.Query, 0, Telem.Local, HttpResponse cachedResponseData responseHeaders, normalizedSelectionSet)
@ -244,7 +251,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
return $ ResultsFragment telemTimeIO_DT Telem.Local resp [] return $ ResultsFragment telemTimeIO_DT Telem.Local resp []
E.ExecStepRemote rsi gqlReq -> E.ExecStepRemote rsi gqlReq ->
runRemoteGQ httpManager fieldName rsi gqlReq runRemoteGQ httpManager fieldName rsi gqlReq
E.ExecStepAction aep -> do E.ExecStepAction (aep, _) -> do
(time, (r, _)) <- doQErr $ EA.runActionExecution aep (time, (r, _)) <- doQErr $ EA.runActionExecution aep
pure $ ResultsFragment time Telem.Empty r [] pure $ ResultsFragment time Telem.Empty r []
E.ExecStepRaw json -> E.ExecStepRaw json ->
@ -298,7 +305,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
return $ ResultsFragment telemTimeIO_DT Telem.Local resp responseHeaders return $ ResultsFragment telemTimeIO_DT Telem.Local resp responseHeaders
E.ExecStepRemote rsi gqlReq -> E.ExecStepRemote rsi gqlReq ->
runRemoteGQ httpManager fieldName rsi gqlReq runRemoteGQ httpManager fieldName rsi gqlReq
E.ExecStepAction aep -> do E.ExecStepAction (aep, _) -> do
(time, (r, hdrs)) <- doQErr $ EA.runActionExecution aep (time, (r, hdrs)) <- doQErr $ EA.runActionExecution aep
pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs
E.ExecStepRaw json -> E.ExecStepRaw json ->
@ -314,6 +321,10 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
Telem.recordTimingMetric Telem.RequestDimensions{..} Telem.RequestTimings{..} Telem.recordTimingMetric Telem.RequestDimensions{..} Telem.RequestTimings{..}
return (normalizedSelectionSet, resp) return (normalizedSelectionSet, resp)
where where
getExecStepActionWithActionInfo acc execStep = case execStep of
EB.ExecStepAction (_, actionInfo) -> (actionInfo:acc)
_ -> acc
doQErr = withExceptT Right doQErr = withExceptT Right
forWithKey = flip OMap.traverseWithKey forWithKey = flip OMap.traverseWithKey

View File

@ -385,9 +385,14 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
E.ExecStepDB _remoteHeaders exists -> E.ExecStepDB _remoteHeaders exists ->
AB.dispatchAnyBackend @BackendTransport exists EB.getRemoteSchemaInfo AB.dispatchAnyBackend @BackendTransport exists EB.getRemoteSchemaInfo
_ -> [] _ -> []
actionsInfo = foldl getExecStepActionWithActionInfo [] $ OMap.elems $ OMap.filter (\x -> case x of
E.ExecStepAction (_, _) -> True
_ -> False
) queryPlan
-- We ignore the response headers (containing TTL information) because -- We ignore the response headers (containing TTL information) because
-- WebSockets don't support them. -- WebSockets don't support them.
(_responseHeaders, cachedValue) <- Tracing.interpTraceT (withExceptT mempty) $ cacheLookup remoteJoins cacheKey (_responseHeaders, cachedValue) <- Tracing.interpTraceT (withExceptT mempty) $ cacheLookup remoteJoins actionsInfo cacheKey
case cachedValue of case cachedValue of
Just cachedResponseData -> do Just cachedResponseData -> do
sendSuccResp cachedResponseData $ LQ.LiveQueryMetadata 0 sendSuccResp cachedResponseData $ LQ.LiveQueryMetadata 0
@ -409,7 +414,7 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
return $ ResultsFragment telemTimeIO_DT Telem.Local resp [] return $ ResultsFragment telemTimeIO_DT Telem.Local resp []
E.ExecStepRemote rsi gqlReq -> do E.ExecStepRemote rsi gqlReq -> do
runRemoteGQ fieldName userInfo reqHdrs rsi gqlReq runRemoteGQ fieldName userInfo reqHdrs rsi gqlReq
E.ExecStepAction actionExecPlan -> do E.ExecStepAction (actionExecPlan, _) -> do
(time, (r, _)) <- doQErr $ EA.runActionExecution actionExecPlan (time, (r, _)) <- doQErr $ EA.runActionExecution actionExecPlan
pure $ ResultsFragment time Telem.Empty r [] pure $ ResultsFragment time Telem.Empty r []
E.ExecStepRaw json -> E.ExecStepRaw json ->
@ -458,7 +463,7 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
tx tx
genSql genSql
return $ ResultsFragment telemTimeIO_DT Telem.Local resp [] return $ ResultsFragment telemTimeIO_DT Telem.Local resp []
E.ExecStepAction actionExecPlan -> do E.ExecStepAction (actionExecPlan, _) -> do
(time, (r, hdrs)) <- doQErr $ EA.runActionExecution actionExecPlan (time, (r, hdrs)) <- doQErr $ EA.runActionExecution actionExecPlan
pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs
E.ExecStepRemote rsi gqlReq -> do E.ExecStepRemote rsi gqlReq -> do
@ -522,6 +527,10 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
liftIO $ logOpEv ODStarted (Just requestId) liftIO $ logOpEv ODStarted (Just requestId)
where where
getExecStepActionWithActionInfo acc execStep = case execStep of
E.ExecStepAction (_, actionInfo) -> (actionInfo:acc)
_ -> acc
doQErr = withExceptT Right doQErr = withExceptT Right
forWithKey = flip OMap.traverseWithKey forWithKey = flip OMap.traverseWithKey
@ -652,7 +661,6 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
catchAndIgnore :: ExceptT () m () -> m () catchAndIgnore :: ExceptT () m () -> m ()
catchAndIgnore m = void $ runExceptT m catchAndIgnore m = void $ runExceptT m
onMessage onMessage
:: ( HasVersion :: ( HasVersion
, MonadIO m , MonadIO m

View File

@ -705,8 +705,9 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
runExceptT $ resolveAction env resolvedCustomTypes def scalarsMap runExceptT $ resolveAction env resolvedCustomTypes def scalarsMap
let permissionInfos = map (ActionPermissionInfo . _apmRole) actionPermissions let permissionInfos = map (ActionPermissionInfo . _apmRole) actionPermissions
permissionMap = mapFromL _apiRole permissionInfos permissionMap = mapFromL _apiRole permissionInfos
forwardClientHeaders = _adForwardClientHeaders resolvedDef
outputType = unGraphQLType $ _adOutputType def outputType = unGraphQLType $ _adOutputType def
returnA -< ActionInfo name (outputType, outObject) resolvedDef permissionMap comment) returnA -< ActionInfo name (outputType, outObject) resolvedDef permissionMap forwardClientHeaders comment)
|) addActionContext) |) addActionContext)
|) (mkActionMetadataObject action) |) (mkActionMetadataObject action)

View File

@ -31,6 +31,7 @@ module Hasura.RQL.Types.Action
, aiOutputObject , aiOutputObject
, aiDefinition , aiDefinition
, aiPermissions , aiPermissions
, aiForwardedClientHeaders
, aiComment , aiComment
, defaultActionTimeoutSecs , defaultActionTimeoutSecs
, ActionPermissionInfo(..) , ActionPermissionInfo(..)
@ -61,6 +62,9 @@ module Hasura.RQL.Types.Action
, ActionLogResponse(..) , ActionLogResponse(..)
, ActionLogResponseMap , ActionLogResponseMap
, AsyncActionStatus(..) , AsyncActionStatus(..)
, ActionsInfo(..)
, asiName
, asiForwardClientHeaders
) where ) where
@ -204,11 +208,12 @@ getActionOutputFields =
data ActionInfo data ActionInfo
= ActionInfo = ActionInfo
{ _aiName :: !ActionName { _aiName :: !ActionName
, _aiOutputObject :: !(G.GType, AnnotatedObjectType) , _aiOutputObject :: !(G.GType, AnnotatedObjectType)
, _aiDefinition :: !ResolvedActionDefinition , _aiDefinition :: !ResolvedActionDefinition
, _aiPermissions :: !ActionPermissionMap , _aiPermissions :: !ActionPermissionMap
, _aiComment :: !(Maybe Text) , _aiForwardedClientHeaders :: !Bool
, _aiComment :: !(Maybe Text)
} deriving (Generic) } deriving (Generic)
instance J.ToJSON ActionInfo where instance J.ToJSON ActionInfo where
toJSON = J.genericToJSON hasuraJSON toJSON = J.genericToJSON hasuraJSON
@ -316,8 +321,9 @@ traverseAnnActionExecution f (AnnActionExecution n ot fs p oF dl w h fch sn to s
data AnnActionMutationAsync data AnnActionMutationAsync
= AnnActionMutationAsync = AnnActionMutationAsync
{ _aamaName :: !ActionName { _aamaName :: !ActionName
, _aamaPayload :: !J.Value -- ^ jsonified input arguments , _aamaForwardClientHeaders :: !Bool
, _aamaPayload :: !J.Value -- ^ jsonified input arguments
} deriving (Show, Eq) } deriving (Show, Eq)
data AsyncActionQueryFieldG (b :: BackendType) v data AsyncActionQueryFieldG (b :: BackendType) v
@ -335,21 +341,22 @@ traverseAsyncActionQueryField
traverseAsyncActionQueryField f = \case traverseAsyncActionQueryField f = \case
AsyncTypename t -> pure $ AsyncTypename t AsyncTypename t -> pure $ AsyncTypename t
AsyncOutput fields -> AsyncOutput <$> traverse (traverse $ traverseAnnField f) fields AsyncOutput fields -> AsyncOutput <$> traverse (traverse $ traverseAnnField f) fields
AsyncId -> pure $ AsyncId AsyncId -> pure AsyncId
AsyncCreatedAt -> pure $ AsyncCreatedAt AsyncCreatedAt -> pure AsyncCreatedAt
AsyncErrors -> pure $ AsyncErrors AsyncErrors -> pure AsyncErrors
type AsyncActionQueryFieldsG b v = Fields (AsyncActionQueryFieldG b v) type AsyncActionQueryFieldsG b v = Fields (AsyncActionQueryFieldG b v)
data AnnActionAsyncQuery (b :: BackendType) v data AnnActionAsyncQuery (b :: BackendType) v
= AnnActionAsyncQuery = AnnActionAsyncQuery
{ _aaaqName :: !ActionName { _aaaqName :: !ActionName
, _aaaqActionId :: !ActionId , _aaaqActionId :: !ActionId
, _aaaqOutputType :: !GraphQLType , _aaaqOutputType :: !GraphQLType
, _aaaqFields :: !(AsyncActionQueryFieldsG b v) , _aaaqFields :: !(AsyncActionQueryFieldsG b v)
, _aaaqDefinitionList :: ![(Column b, ScalarType b)] , _aaaqDefinitionList :: ![(Column b, ScalarType b)]
, _aaaqStringifyNum :: !Bool , _aaaqStringifyNum :: !Bool
, _aaaqSource :: !(ActionSourceInfo b) , _aaaqForwardClientHeaders :: !Bool
, _aaaqSource :: !(ActionSourceInfo b)
} }
traverseAnnActionAsyncQuery traverseAnnActionAsyncQuery
@ -357,8 +364,8 @@ traverseAnnActionAsyncQuery
=> (a -> f b) => (a -> f b)
-> AnnActionAsyncQuery backend a -> AnnActionAsyncQuery backend a
-> f (AnnActionAsyncQuery backend b) -> f (AnnActionAsyncQuery backend b)
traverseAnnActionAsyncQuery f (AnnActionAsyncQuery n aid ot fs dl sn s) = traverseAnnActionAsyncQuery f (AnnActionAsyncQuery n aid ot fs dl sn fch s) =
traverse (traverse $ traverseAsyncActionQueryField f) fs <&> \tfs -> AnnActionAsyncQuery n aid ot tfs dl sn s traverse (traverse $ traverseAsyncActionQueryField f) fs <&> \tfs -> AnnActionAsyncQuery n aid ot tfs dl sn fch s
data ActionExecContext data ActionExecContext
= ActionExecContext = ActionExecContext
@ -397,3 +404,11 @@ type ActionLogResponseMap = HashMap ActionId ActionLogResponse
data AsyncActionStatus data AsyncActionStatus
= AASCompleted !J.Value = AASCompleted !J.Value
| AASError !QErr | AASError !QErr
data ActionsInfo
= ActionsInfo
{ _asiName :: !ActionName
, _asiForwardClientHeaders :: !Bool
}
deriving (Show, Eq, Generic)
$(makeLenses ''ActionsInfo)

View File

@ -37,3 +37,45 @@ args:
('Foo', 'Bar'), ('Foo', 'Bar'),
('Baz', 'Qux'), ('Baz', 'Qux'),
('X%20Y', 'Test'); ('X%20Y', 'Test');
- type: run_sql
args:
sql: |
CREATE TABLE "user"(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);
INSERT INTO "user" (name, email) VALUES ('Clarke 1', 'clarke1@gmail.com');
INSERT INTO "user" (name, email) VALUES ('Clarke 2', 'clarke2@gmail.com');
- type: set_custom_types
args:
objects:
- name: EmailResponse
fields:
- name: user
type: String!
- type: create_action
args:
name: get_user_by_email_1 # this action is being created with forward_client_headers set to `true`
definition:
type: query
arguments:
- name: email
type: String!
output_type: EmailResponse
handler: http://127.0.0.1:5593/get-user-by-email
forward_client_headers: true
- type: create_action
args:
name: get_user_by_email_2 # this action is being created with forward_client_headers set to `false`
definition:
type: query
arguments:
- name: email
type: String!
output_type: EmailResponse
handler: http://127.0.0.1:5593/get-user-by-email

View File

@ -4,7 +4,16 @@ args:
args: args:
collection: test_collection collection: test_collection
cascade: false cascade: false
- type: drop_action
args:
name: get_user_by_email_1
- type: drop_action
args:
name: get_user_by_email_2
- type: set_custom_types
args: {}
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
drop table test_table drop table test_table;
drop table user;

View File

@ -0,0 +1,19 @@
- description: Run get_user_by_email_1 query action with valid email, the response should be an error saying that caching is disabled
url: /v1/graphql
status: 400
query:
query: |
query ($email: String!) @cached(ttl: 300) {
get_user_by_email_1(email: $email){
user
}
}
variables:
email: clarke1@gmail.com
response:
errors:
- extensions:
path: $
code: not-supported
message: 'Actions which forward client headers cannot currently be cached'

View File

@ -0,0 +1,20 @@
- description: Run get_user_by_email_2 query action with valid email, the response should be an object and response should be cached
url: /v1/graphql
status: 200
query:
query: |
query ($email: String!) @cached(ttl: 300) {
get_user_by_email_2(email: $email){
user
}
}
variables:
email: clarke2@gmail.com
resp_headers:
Cache-Control: max-age=300
X-Hasura-Query-Cache-Key: 928537e2f3e76a263eaaa45686e0c0bccd2a9b7e
X-Hasura-Query-Family-Cache-Key: ae504392da7b9e98ee4679693b8bc0efd350e0d7
response:
data:
get_user_by_email_2:
user: Clarke 2

View File

@ -50,3 +50,11 @@ class TestQueryCache:
def test_no_variables_in_query_key(self, hge_ctx, transport): def test_no_variables_in_query_key(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/test_no_variables_in_query_key.yaml', transport) check_query_f(hge_ctx, self.dir() + '/test_no_variables_in_query_key.yaml', transport)
self.flushRedis() self.flushRedis()
def test_action_query_with_forward_client_headers_set(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/test_action_query_with_forward_client_headers_set.yaml', transport)
self.flushRedis()
def test_action_query_with_forward_client_header_unset(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/test_action_query_with_forward_client_headers_unset.yaml', transport)
self.flushRedis()