graphql-engine/server/src-lib/Hasura/GraphQL/Resolve.hs
Vamshi Surabhi ce243f5899
multiplexed subscriptions (#1934)
* add types to represent unparsed http gql requests

This will help when we add caching of frequently used ASTs

* query plan caching

* move livequery to execute

* add multiplexed module

* session variable can be customised depending on the context

Previously the value was always "current_setting('hasura.user')"

* get rid of typemap requirement in reusable plan

* subscriptions are multiplexed when possible

* use lazytx for introspection to avoid acquiring a pg connection

* refactor to make execute a completely decoupled module

* don't issue a transaction for a query

* don't use current setting for explained sql

* move postgres related types to a different module

* validate variableValues on postgres before multiplexing subs

* don't user current_setting for queries over ws

* plan_cache is only visible when developer flag is enabled

* introduce 'batch size' when multiplexing subscriptions

* bump stackage to 13.16

* fix schema_stitching test case error code

* store hashes instead of actual responses for subscriptions

* internal api to dump subscriptions state

* remove PlanCache from SchemaCacheRef

* allow live query options to be configured on server startup

* capture metrics for multiplexed subscriptions

* more metrics captured for multiplexed subs

* switch to tvar based hashmap for faster snapshotting

* livequery modules do not expose internal details

* fix typo in live query env vars

* switch to hasura's pg-client-hs
2019-04-17 15:18:41 +05:30

138 lines
4.1 KiB
Haskell

module Hasura.GraphQL.Resolve
( mutFldToTx
, queryFldToPGAST
, RS.traverseQueryRootFldAST
, RS.toPGQuery
, UnresolvedVal(..)
, AnnPGVal(..)
, RS.QueryRootFldUnresolved
, resolveValPrep
, queryFldToSQL
, schemaR
, typeR
) where
import Data.Has
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
import Hasura.GraphQL.Context
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Resolve.Introspect
import Hasura.GraphQL.Validate.Field
import Hasura.Prelude
import Hasura.RQL.DML.Internal (sessVarFromCurrentSetting)
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Hasura.GraphQL.Resolve.Insert as RI
import qualified Hasura.GraphQL.Resolve.Mutation as RM
import qualified Hasura.GraphQL.Resolve.Select as RS
validateHdrs
:: (Foldable t, QErrM m) => UserInfo -> t Text -> m ()
validateHdrs userInfo hdrs = do
let receivedVars = userVars userInfo
forM_ hdrs $ \hdr ->
unless (isJust $ getVarVal hdr receivedVars) $
throw400 NotFound $ hdr <<> " header is expected but not found"
queryFldToPGAST
:: ( MonadError QErr m, MonadReader r m, Has FieldMap r
, Has OrdByCtx r, Has SQLGenCtx r, Has UserInfo r
, Has OpCtxMap r
)
=> Field
-> m RS.QueryRootFldUnresolved
queryFldToPGAST fld = do
opCtx <- getOpCtx $ _fName fld
userInfo <- asks getter
case opCtx of
OCSelect ctx -> do
validateHdrs userInfo (_socHeaders ctx)
RS.convertSelect ctx fld
OCSelectPkey ctx -> do
validateHdrs userInfo (_spocHeaders ctx)
RS.convertSelectByPKey ctx fld
OCSelectAgg ctx -> do
validateHdrs userInfo (_socHeaders ctx)
RS.convertAggSelect ctx fld
OCFuncQuery ctx -> do
validateHdrs userInfo (_fqocHeaders ctx)
RS.convertFuncQuerySimple ctx fld
OCFuncAggQuery ctx -> do
validateHdrs userInfo (_fqocHeaders ctx)
RS.convertFuncQueryAgg ctx fld
OCInsert _ ->
throw500 "unexpected OCInsert for query field context"
OCUpdate _ ->
throw500 "unexpected OCUpdate for query field context"
OCDelete _ ->
throw500 "unexpected OCDelete for query field context"
queryFldToSQL
:: ( MonadError QErr m, MonadReader r m, Has FieldMap r
, Has OrdByCtx r, Has SQLGenCtx r, Has UserInfo r
, Has OpCtxMap r
)
=> PrepFn m
-> Field
-> m Q.Query
queryFldToSQL fn fld = do
pgAST <- queryFldToPGAST fld
resolvedAST <- flip RS.traverseQueryRootFldAST pgAST $ \case
UVPG annPGVal -> fn annPGVal
UVSQL sqlExp -> return sqlExp
UVSessVar colTy sessVar -> sessVarFromCurrentSetting colTy sessVar
return $ RS.toPGQuery resolvedAST
mutFldToTx
:: ( MonadError QErr m
, MonadReader r m
, Has UserInfo r
, Has OpCtxMap r
, Has FieldMap r
, Has OrdByCtx r
, Has SQLGenCtx r
, Has InsCtxMap r
)
=> Field
-> m RespTx
mutFldToTx fld = do
userInfo <- asks getter
opCtx <- getOpCtx $ _fName fld
case opCtx of
OCInsert ctx -> do
let roleName = userRole userInfo
validateHdrs userInfo (_iocHeaders ctx)
RI.convertInsert roleName (_iocTable ctx) fld
OCUpdate ctx -> do
validateHdrs userInfo (_uocHeaders ctx)
RM.convertUpdate ctx fld
OCDelete ctx -> do
validateHdrs userInfo (_docHeaders ctx)
RM.convertDelete ctx fld
OCSelect _ ->
throw500 "unexpected query field context for a mutation field"
OCSelectPkey _ ->
throw500 "unexpected query field context for a mutation field"
OCSelectAgg _ ->
throw500 "unexpected query field context for a mutation field"
OCFuncQuery _ ->
throw500 "unexpected query field context for a mutation field"
OCFuncAggQuery _ ->
throw500 "unexpected query field context for a mutation field"
getOpCtx
:: ( MonadError QErr m
, MonadReader r m
, Has OpCtxMap r
)
=> G.Name -> m OpCtx
getOpCtx f = do
opCtxMap <- asks getter
onNothing (Map.lookup f opCtxMap) $ throw500 $
"lookup failed: opctx: " <> showName f