From b274a2d240ac2f613a635265be890a0e27b9bd58 Mon Sep 17 00:00:00 2001 From: Sameer Kolhar Date: Tue, 13 Apr 2021 12:30:43 +0530 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + docs/graphql/cloud/response-caching.rst | 5 +- server/src-lib/Hasura/App.hs | 2 +- .../src-lib/Hasura/GraphQL/Execute/Action.hs | 4 +- .../src-lib/Hasura/GraphQL/Execute/Backend.hs | 3 +- .../Hasura/GraphQL/Execute/Mutation.hs | 10 ++-- .../src-lib/Hasura/GraphQL/Execute/Query.hs | 9 ++-- .../src-lib/Hasura/GraphQL/Schema/Action.hs | 11 ++-- .../src-lib/Hasura/GraphQL/Transport/HTTP.hs | 25 ++++++--- .../Hasura/GraphQL/Transport/WebSocket.hs | 16 ++++-- server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs | 3 +- server/src-lib/Hasura/RQL/Types/Action.hs | 53 ++++++++++++------- .../tests-py/queries/query_cache/setup.yaml | 42 +++++++++++++++ .../queries/query_cache/teardown.yaml | 11 +++- ...query_with_forward_client_headers_set.yaml | 19 +++++++ ...ery_with_forward_client_headers_unset.yaml | 20 +++++++ server/tests-py/test_query_cache.py | 8 +++ 17 files changed, 192 insertions(+), 50 deletions(-) create mode 100644 server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_set.yaml create mode 100644 server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_unset.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index c769144d507..bb3944b0693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ query { - server: fix action custom types failing to parse when mutually recursive - server: fix MSSQL table name descriptions - 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: display collection names and queries from all collections in allowlist - cli: match ordering of keys in project metadata files with server metadata diff --git a/docs/graphql/cloud/response-caching.rst b/docs/graphql/cloud/response-caching.rst index 0d8b9f1ddc0..fb611a3b194 100644 --- a/docs/graphql/cloud/response-caching.rst +++ b/docs/graphql/cloud/response-caching.rst @@ -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: -- The query does not make use of remote schemas or remote joins -- The response JSON is under 100KB in size +- The query does **not** make use of ``remote schemas`` or ``remote joins``. +- 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 diff --git a/server/src-lib/Hasura/App.hs b/server/src-lib/Hasura/App.hs index 3653f8245ce..f48f50f9025 100644 --- a/server/src-lib/Hasura/App.hs +++ b/server/src-lib/Hasura/App.hs @@ -721,7 +721,7 @@ instance HttpLog PGMetadataStorageApp where mkHttpAccessLogContext userInfoM reqId waiReq compressedResponse qTime cType headers instance MonadExecuteQuery PGMetadataStorageApp where - cacheLookup _ _ = pure ([], Nothing) + cacheLookup _ _ _ = pure ([], Nothing) cacheStore _ _ = pure () instance UserAuthentication (Tracing.TraceT PGMetadataStorageApp) where diff --git a/server/src-lib/Hasura/GraphQL/Execute/Action.hs b/server/src-lib/Hasura/GraphQL/Execute/Action.hs index a7eb0fae916..fb489588ea9 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Action.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Action.hs @@ -185,7 +185,7 @@ resolveActionMutationAsync resolveActionMutationAsync annAction reqHeaders sessionVariables = insertAction actionName sessionVariables reqHeaders inputArgs where - AnnActionMutationAsync actionName inputArgs = annAction + AnnActionMutationAsync actionName _ inputArgs = annAction {- Note: [Resolving async action query] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -260,7 +260,7 @@ resolveAsyncActionQuery userInfo annAction = in RS.AnnSelectG annotatedFields tableFromExp tablePermissions tableArguments stringifyNumerics where - AnnActionAsyncQuery _ actionId outputType asyncFields definitionList stringifyNumerics actionSource = annAction + AnnActionAsyncQuery _ actionId outputType asyncFields definitionList stringifyNumerics _ actionSource = annAction idColumn = (unsafePGCol "id", PGUUID) responsePayloadColumn = (unsafePGCol "response_payload", PGJSONB) diff --git a/server/src-lib/Hasura/GraphQL/Execute/Backend.hs b/server/src-lib/Hasura/GraphQL/Execute/Backend.hs index d019a041ff7..94dc130928f 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Backend.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Backend.hs @@ -23,6 +23,7 @@ import Hasura.GraphQL.Execute.Action.Types (ActionExecutionPlan) import Hasura.GraphQL.Execute.LiveQuery.Plan import Hasura.GraphQL.Parser hiding (Type) import Hasura.RQL.IR.RemoteJoin +import Hasura.RQL.Types.Action import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Common import Hasura.RQL.Types.Error @@ -100,7 +101,7 @@ data ExecutionStep where -> ExecutionStep -- ^ A query to execute against the database ExecStepAction - :: ActionExecutionPlan + :: (ActionExecutionPlan, ActionsInfo) -> ExecutionStep -- ^ Execute an action ExecStepRemote diff --git a/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs b/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs index 34536e96c81..f7174669fc0 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs @@ -89,11 +89,15 @@ convertMutationSelectionSet env logger gqlContext SQLGenCtx{stringifyNum} userIn RFRemote remoteField -> do RemoteFieldG remoteSchemaInfo resolvedRemoteField <- resolveRemoteField userInfo remoteField pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeMutation $ [G.SelectionField resolvedRemoteField] - RFAction action -> - ExecStepAction <$> convertMutationAction env logger userInfo manager reqHeaders action + RFAction action -> do + (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 -> pure $ ExecStepRaw s - return (txs, resolvedSelSet) where reportParseErrors errs = case NE.head errs of diff --git a/server/src-lib/Hasura/GraphQL/Execute/Query.hs b/server/src-lib/Hasura/GraphQL/Execute/Query.hs index d823434bb1e..a485c767ea4 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Query.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Query.hs @@ -86,10 +86,11 @@ convertQuerySelSet env logger gqlContext userInfo manager reqHeaders directives RFRemote rf -> do RemoteFieldG remoteSchemaInfo remoteField <- for rf $ resolveRemoteVariable userInfo pure $ buildExecStepRemote remoteSchemaInfo G.OperationTypeQuery [G.SelectionField remoteField] - RFAction a -> - pure $ ExecStepAction $ case a of - AQQuery s -> AEPSync $ resolveActionExecution env logger userInfo s (ActionExecContext manager reqHeaders usrVars) - AQAsync s -> AEPAsyncQuery $ AsyncActionQueryExecutionPlan (_aaaqActionId s) $ resolveAsyncActionQuery userInfo s + RFAction a -> do + (action, actionName, fch) <- pure $ case a of + 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, _aaaqName s, _aaaqForwardClientHeaders s) + pure $ ExecStepAction (action, (ActionsInfo actionName fch)) RFRaw r -> pure $ ExecStepRaw r diff --git a/server/src-lib/Hasura/GraphQL/Schema/Action.hs b/server/src-lib/Hasura/GraphQL/Schema/Action.hs index d12f5d3acc8..92c86faf15b 100644 --- a/server/src-lib/Hasura/GraphQL/Schema/Action.hs +++ b/server/src-lib/Hasura/GraphQL/Schema/Action.hs @@ -55,7 +55,7 @@ actionExecute -> m (Maybe (FieldParser n (AnnActionExecution 'Postgres (UnpreparedValue 'Postgres)))) actionExecute nonObjectTypeMap actionInfo = runMaybeT do roleName <- askRoleName - guard $ (roleName == adminRoleName || roleName `Map.member` permissions) + guard (roleName == adminRoleName || roleName `Map.member` permissions) let fieldName = unActionName actionName description = G.Description <$> comment inputArguments <- lift $ actionInputArguments nonObjectTypeMap $ _adArguments definition @@ -77,7 +77,7 @@ actionExecute nonObjectTypeMap actionInfo = runMaybeT do , _aaeSource = getActionSourceInfo outputObject } 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 -- 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 description = G.Description <$> comment pure $ P.selection fieldName description inputArguments actionIdParser - <&> AnnActionMutationAsync actionName + <&> AnnActionMutationAsync actionName forwardClientHeaders 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 -- asynchronous mutation action. The only input argument to an @@ -165,10 +165,11 @@ actionAsyncQuery actionInfo = runMaybeT do , _aaaqFields = fields , _aaaqDefinitionList = mkDefinitionList outputObject , _aaaqStringifyNum = stringifyNum + , _aaaqForwardClientHeaders = forwardClientHeaders , _aaaqSource = getActionSourceInfo outputObject } where - ActionInfo actionName (outputType, outputObject) definition permissions comment = actionInfo + ActionInfo actionName (outputType, outputObject) definition permissions forwardClientHeaders comment = actionInfo idFieldName = $$(G.litName "id") idFieldDescription = "the unique id of an action" diff --git a/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs b/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs index 0464650e83d..aa73e8662df 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs @@ -86,6 +86,8 @@ class Monad m => MonadExecuteQuery m where cacheLookup :: [RemoteSchemaInfo] -- ^ 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 -- ^ Key that uniquely identifies the result of a query execution -> TraceT (ExceptT QErr m) (HTTP.ResponseHeaders, Maybe EncJSON) @@ -112,19 +114,19 @@ class Monad m => MonadExecuteQuery m where -- ^ Always succeeds 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 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 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 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 -- | 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 -> 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 Just cachedResponseData -> 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 [] E.ExecStepRemote rsi gqlReq -> runRemoteGQ httpManager fieldName rsi gqlReq - E.ExecStepAction aep -> do + E.ExecStepAction (aep, _) -> do (time, (r, _)) <- doQErr $ EA.runActionExecution aep pure $ ResultsFragment time Telem.Empty r [] 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 E.ExecStepRemote rsi gqlReq -> runRemoteGQ httpManager fieldName rsi gqlReq - E.ExecStepAction aep -> do + E.ExecStepAction (aep, _) -> do (time, (r, hdrs)) <- doQErr $ EA.runActionExecution aep pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs E.ExecStepRaw json -> @@ -314,6 +321,10 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do Telem.recordTimingMetric Telem.RequestDimensions{..} Telem.RequestTimings{..} return (normalizedSelectionSet, resp) where + getExecStepActionWithActionInfo acc execStep = case execStep of + EB.ExecStepAction (_, actionInfo) -> (actionInfo:acc) + _ -> acc + doQErr = withExceptT Right forWithKey = flip OMap.traverseWithKey diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs index 65e83014b1f..130b0ed8256 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs @@ -385,9 +385,14 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do E.ExecStepDB _remoteHeaders exists -> 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 -- 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 Just cachedResponseData -> do 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 [] E.ExecStepRemote rsi gqlReq -> do runRemoteGQ fieldName userInfo reqHdrs rsi gqlReq - E.ExecStepAction actionExecPlan -> do + E.ExecStepAction (actionExecPlan, _) -> do (time, (r, _)) <- doQErr $ EA.runActionExecution actionExecPlan pure $ ResultsFragment time Telem.Empty r [] E.ExecStepRaw json -> @@ -458,7 +463,7 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do tx genSql return $ ResultsFragment telemTimeIO_DT Telem.Local resp [] - E.ExecStepAction actionExecPlan -> do + E.ExecStepAction (actionExecPlan, _) -> do (time, (r, hdrs)) <- doQErr $ EA.runActionExecution actionExecPlan pure $ ResultsFragment time Telem.Empty r $ fromMaybe [] hdrs E.ExecStepRemote rsi gqlReq -> do @@ -522,6 +527,10 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do liftIO $ logOpEv ODStarted (Just requestId) where + getExecStepActionWithActionInfo acc execStep = case execStep of + E.ExecStepAction (_, actionInfo) -> (actionInfo:acc) + _ -> acc + doQErr = withExceptT Right forWithKey = flip OMap.traverseWithKey @@ -652,7 +661,6 @@ onStart env serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do catchAndIgnore :: ExceptT () m () -> m () catchAndIgnore m = void $ runExceptT m - onMessage :: ( HasVersion , MonadIO m diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs index fe274957992..92f4aab7e7c 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs @@ -705,8 +705,9 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do runExceptT $ resolveAction env resolvedCustomTypes def scalarsMap let permissionInfos = map (ActionPermissionInfo . _apmRole) actionPermissions permissionMap = mapFromL _apiRole permissionInfos + forwardClientHeaders = _adForwardClientHeaders resolvedDef outputType = unGraphQLType $ _adOutputType def - returnA -< ActionInfo name (outputType, outObject) resolvedDef permissionMap comment) + returnA -< ActionInfo name (outputType, outObject) resolvedDef permissionMap forwardClientHeaders comment) |) addActionContext) |) (mkActionMetadataObject action) diff --git a/server/src-lib/Hasura/RQL/Types/Action.hs b/server/src-lib/Hasura/RQL/Types/Action.hs index 1a3d293b494..c0851f23751 100644 --- a/server/src-lib/Hasura/RQL/Types/Action.hs +++ b/server/src-lib/Hasura/RQL/Types/Action.hs @@ -31,6 +31,7 @@ module Hasura.RQL.Types.Action , aiOutputObject , aiDefinition , aiPermissions + , aiForwardedClientHeaders , aiComment , defaultActionTimeoutSecs , ActionPermissionInfo(..) @@ -61,6 +62,9 @@ module Hasura.RQL.Types.Action , ActionLogResponse(..) , ActionLogResponseMap , AsyncActionStatus(..) + , ActionsInfo(..) + , asiName + , asiForwardClientHeaders ) where @@ -204,11 +208,12 @@ getActionOutputFields = data ActionInfo = ActionInfo - { _aiName :: !ActionName - , _aiOutputObject :: !(G.GType, AnnotatedObjectType) - , _aiDefinition :: !ResolvedActionDefinition - , _aiPermissions :: !ActionPermissionMap - , _aiComment :: !(Maybe Text) + { _aiName :: !ActionName + , _aiOutputObject :: !(G.GType, AnnotatedObjectType) + , _aiDefinition :: !ResolvedActionDefinition + , _aiPermissions :: !ActionPermissionMap + , _aiForwardedClientHeaders :: !Bool + , _aiComment :: !(Maybe Text) } deriving (Generic) instance J.ToJSON ActionInfo where 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 = AnnActionMutationAsync - { _aamaName :: !ActionName - , _aamaPayload :: !J.Value -- ^ jsonified input arguments + { _aamaName :: !ActionName + , _aamaForwardClientHeaders :: !Bool + , _aamaPayload :: !J.Value -- ^ jsonified input arguments } deriving (Show, Eq) data AsyncActionQueryFieldG (b :: BackendType) v @@ -335,21 +341,22 @@ traverseAsyncActionQueryField traverseAsyncActionQueryField f = \case AsyncTypename t -> pure $ AsyncTypename t AsyncOutput fields -> AsyncOutput <$> traverse (traverse $ traverseAnnField f) fields - AsyncId -> pure $ AsyncId - AsyncCreatedAt -> pure $ AsyncCreatedAt - AsyncErrors -> pure $ AsyncErrors + AsyncId -> pure AsyncId + AsyncCreatedAt -> pure AsyncCreatedAt + AsyncErrors -> pure AsyncErrors type AsyncActionQueryFieldsG b v = Fields (AsyncActionQueryFieldG b v) data AnnActionAsyncQuery (b :: BackendType) v = AnnActionAsyncQuery - { _aaaqName :: !ActionName - , _aaaqActionId :: !ActionId - , _aaaqOutputType :: !GraphQLType - , _aaaqFields :: !(AsyncActionQueryFieldsG b v) - , _aaaqDefinitionList :: ![(Column b, ScalarType b)] - , _aaaqStringifyNum :: !Bool - , _aaaqSource :: !(ActionSourceInfo b) + { _aaaqName :: !ActionName + , _aaaqActionId :: !ActionId + , _aaaqOutputType :: !GraphQLType + , _aaaqFields :: !(AsyncActionQueryFieldsG b v) + , _aaaqDefinitionList :: ![(Column b, ScalarType b)] + , _aaaqStringifyNum :: !Bool + , _aaaqForwardClientHeaders :: !Bool + , _aaaqSource :: !(ActionSourceInfo b) } traverseAnnActionAsyncQuery @@ -357,8 +364,8 @@ traverseAnnActionAsyncQuery => (a -> f b) -> AnnActionAsyncQuery backend a -> f (AnnActionAsyncQuery backend b) -traverseAnnActionAsyncQuery f (AnnActionAsyncQuery n aid ot fs dl sn s) = - traverse (traverse $ traverseAsyncActionQueryField f) fs <&> \tfs -> AnnActionAsyncQuery n aid ot tfs 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 fch s data ActionExecContext = ActionExecContext @@ -397,3 +404,11 @@ type ActionLogResponseMap = HashMap ActionId ActionLogResponse data AsyncActionStatus = AASCompleted !J.Value | AASError !QErr + +data ActionsInfo + = ActionsInfo + { _asiName :: !ActionName + , _asiForwardClientHeaders :: !Bool + } + deriving (Show, Eq, Generic) +$(makeLenses ''ActionsInfo) diff --git a/server/tests-py/queries/query_cache/setup.yaml b/server/tests-py/queries/query_cache/setup.yaml index 0b4d479bd96..6c157b8eeaf 100644 --- a/server/tests-py/queries/query_cache/setup.yaml +++ b/server/tests-py/queries/query_cache/setup.yaml @@ -37,3 +37,45 @@ args: ('Foo', 'Bar'), ('Baz', 'Qux'), ('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 diff --git a/server/tests-py/queries/query_cache/teardown.yaml b/server/tests-py/queries/query_cache/teardown.yaml index 85bb52b8761..affeabefdbb 100644 --- a/server/tests-py/queries/query_cache/teardown.yaml +++ b/server/tests-py/queries/query_cache/teardown.yaml @@ -4,7 +4,16 @@ args: args: collection: test_collection 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 args: sql: | - drop table test_table + drop table test_table; + drop table user; diff --git a/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_set.yaml b/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_set.yaml new file mode 100644 index 00000000000..7196550bf4c --- /dev/null +++ b/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_set.yaml @@ -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' diff --git a/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_unset.yaml b/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_unset.yaml new file mode 100644 index 00000000000..9bcb208cdbc --- /dev/null +++ b/server/tests-py/queries/query_cache/test_action_query_with_forward_client_headers_unset.yaml @@ -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 diff --git a/server/tests-py/test_query_cache.py b/server/tests-py/test_query_cache.py index c0918dc1653..cdeea32ef3a 100644 --- a/server/tests-py/test_query_cache.py +++ b/server/tests-py/test_query_cache.py @@ -50,3 +50,11 @@ class TestQueryCache: 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) 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()