diff --git a/console/src/components/Main/Main.js b/console/src/components/Main/Main.js
index f736258c187f5..38032690ebb1d 100644
--- a/console/src/components/Main/Main.js
+++ b/console/src/components/Main/Main.js
@@ -312,30 +312,30 @@ class Main extends React.Component {
{!this.state.loveConsentState.isDismissed
? [
-
-

- {/*
*/}
-
,
-
-
-
-
- {/*
+
+

+ {/*
*/}
+
,
+
+
+
+
+ {/*

*/}
-
- {/*
+
+ {/*
Love GraphQL Engine? Shout it from the rooftops!
@@ -355,37 +355,37 @@ class Main extends React.Component {
*/}
-
-
-
+
+
-
Roses are red,
Violets are blue;
-
+
Star us on Github,
-
+
To make our go
wooooo!
-
-
-
-
-
-

-
-
+
+ {/*
*/}
-
-
-
-
-
-

-
-
+
+
+
-
- ,
- ]
+ ,
+ ]
: null}
diff --git a/console/src/components/Services/Data/Metadata/Metadata.js b/console/src/components/Services/Data/Metadata/Metadata.js
index 38843d71dcdc7..f43c9d74cdab5 100644
--- a/console/src/components/Services/Data/Metadata/Metadata.js
+++ b/console/src/components/Services/Data/Metadata/Metadata.js
@@ -88,32 +88,32 @@ class Metadata extends Component {
{this.state.showMetadata
? [
-
-
Reload metadata
-
+
+
Reload metadata
+
Refresh Hasura metadata, typically required if you have
changed the underlying postgres.
-
-
,
-
-
-
,
-
-
Clear access key (logout)
-
+
+
,
+
+
+
,
+
+
Clear access key (logout)
+
The console caches the access key (HASURA_GRAPHQL_ACCESS_KEY)
in the browser. You can clear this cache to force a prompt for
the access key when the console is accessed next using this
browser.
-
-
,
-
-
-
,
- ]
+
+
,
+
+
+
,
+ ]
: null}
);
diff --git a/console/src/components/Services/Data/Notification.js b/console/src/components/Services/Data/Notification.js
index 18c6057d60df1..0bbf486f486d5 100644
--- a/console/src/components/Services/Data/Notification.js
+++ b/console/src/components/Services/Data/Notification.js
@@ -74,42 +74,42 @@ const showErrorNotification = (title, message, reqBody, error) => {
message: modMessage,
action: finalJson
? {
- label: 'Details',
- callback: () => {
- dispatch(
- showNotification({
- level: 'error',
- title,
- message: modMessage,
- dismissible: 'button',
- children: [
-
-
{
- e.preventDefault();
- expandClicked(finalJson);
- }}
- className={styles.aceBlockExpand + ' fa fa-expand'}
- />
-
- {refreshBtn}
- ,
- ],
- })
- );
- },
- }
+ label: 'Details',
+ callback: () => {
+ dispatch(
+ showNotification({
+ level: 'error',
+ title,
+ message: modMessage,
+ dismissible: 'button',
+ children: [
+
+
{
+ e.preventDefault();
+ expandClicked(finalJson);
+ }}
+ className={styles.aceBlockExpand + ' fa fa-expand'}
+ />
+
+ {refreshBtn}
+ ,
+ ],
+ })
+ );
+ },
+ }
: null,
})
);
diff --git a/console/src/components/Services/Data/TableRelationships/Actions.js b/console/src/components/Services/Data/TableRelationships/Actions.js
index 2e8820424c6d0..4d97f454a78f2 100644
--- a/console/src/components/Services/Data/TableRelationships/Actions.js
+++ b/console/src/components/Services/Data/TableRelationships/Actions.js
@@ -61,11 +61,11 @@ const deleteRelMigrate = (tableName, relName, lcol, rtable, rcol, isObjRel) => (
using: isObjRel
? { foreign_key_constraint_on: lcol }
: {
- foreign_key_constraint_on: {
- table: { name: rtable, schema: currentSchema },
- column: rcol,
+ foreign_key_constraint_on: {
+ table: { name: rtable, schema: currentSchema },
+ column: rcol,
+ },
},
- },
},
},
];
@@ -109,11 +109,11 @@ const addRelNewFromStateMigrate = () => (dispatch, getState) => {
using: isObjRel
? { foreign_key_constraint_on: state.lcol }
: {
- foreign_key_constraint_on: {
- table: { name: state.rTable, schema: currentSchema },
- column: state.rcol,
+ foreign_key_constraint_on: {
+ table: { name: state.rTable, schema: currentSchema },
+ column: state.rcol,
+ },
},
- },
},
},
];
@@ -398,11 +398,11 @@ const autoAddRelName = obj => (dispatch, getState) => {
using: isObjRel
? { foreign_key_constraint_on: obj.lcol }
: {
- foreign_key_constraint_on: {
- table: { name: obj.rTable, schema: currentSchema },
- column: obj.rcol,
+ foreign_key_constraint_on: {
+ table: { name: obj.rTable, schema: currentSchema },
+ column: obj.rcol,
+ },
},
- },
},
},
];
diff --git a/console/src/components/Services/EventTrigger/Notification.js b/console/src/components/Services/EventTrigger/Notification.js
index 69e265fff5b9c..e592e5174733d 100644
--- a/console/src/components/Services/EventTrigger/Notification.js
+++ b/console/src/components/Services/EventTrigger/Notification.js
@@ -74,42 +74,42 @@ const showErrorNotification = (title, message, reqBody, error) => {
message: modMessage,
action: reqBody
? {
- label: 'Details',
- callback: () => {
- dispatch(
- showNotification({
- level: 'error',
- title,
- message: modMessage,
- dismissible: 'button',
- children: [
-
-
{
- e.preventDefault();
- expandClicked(finalJson);
- }}
- className={styles.aceBlockExpand + ' fa fa-expand'}
- />
-
- {refreshBtn}
- ,
- ],
- })
- );
- },
- }
+ label: 'Details',
+ callback: () => {
+ dispatch(
+ showNotification({
+ level: 'error',
+ title,
+ message: modMessage,
+ dismissible: 'button',
+ children: [
+
+
{
+ e.preventDefault();
+ expandClicked(finalJson);
+ }}
+ className={styles.aceBlockExpand + ' fa fa-expand'}
+ />
+
+ {refreshBtn}
+ ,
+ ],
+ })
+ );
+ },
+ }
: null,
})
);
diff --git a/console/src/components/Services/EventTrigger/ProcessedEvents/FilterActions.js b/console/src/components/Services/EventTrigger/ProcessedEvents/FilterActions.js
index 76ebb6c958349..83b05d09f7a73 100644
--- a/console/src/components/Services/EventTrigger/ProcessedEvents/FilterActions.js
+++ b/console/src/components/Services/EventTrigger/ProcessedEvents/FilterActions.js
@@ -64,7 +64,10 @@ const runQuery = triggerSchema => {
if (newQuery.order_by.length === 0) {
delete newQuery.order_by;
}
- dispatch({ type: 'ProcessedEvents/V_SET_QUERY_OPTS', queryStuff: newQuery });
+ dispatch({
+ type: 'ProcessedEvents/V_SET_QUERY_OPTS',
+ queryStuff: newQuery,
+ });
dispatch(vMakeRequest());
};
};
diff --git a/console/src/components/Services/EventTrigger/TableCommon/RedeliverEvent.js b/console/src/components/Services/EventTrigger/TableCommon/RedeliverEvent.js
index 5d95e90e0916f..5d5194b33cbf7 100644
--- a/console/src/components/Services/EventTrigger/TableCommon/RedeliverEvent.js
+++ b/console/src/components/Services/EventTrigger/TableCommon/RedeliverEvent.js
@@ -221,10 +221,10 @@ class RedeliverEvent extends Component {
value={
log.eventInvocations[0]
? JSON.stringify(
- log.eventInvocations[0].request,
- null,
- 4
- )
+ log.eventInvocations[0].request,
+ null,
+ 4
+ )
: ''
}
minLines={8}
diff --git a/server/graphql-engine.cabal b/server/graphql-engine.cabal
index d5a792eb3c647..60b86f24dd0e0 100644
--- a/server/graphql-engine.cabal
+++ b/server/graphql-engine.cabal
@@ -130,6 +130,9 @@ library
-- regex related
, regex-compat
+ -- caching related
+ , psqueues
+ , binary
exposed-modules: Hasura.Server.App
, Hasura.Server.Auth
@@ -184,6 +187,12 @@ library
, Hasura.GraphQL.Validate.Field
, Hasura.GraphQL.Validate.InputValue
, Hasura.GraphQL.Resolve
+ , Hasura.GraphQL.LRUCache
+
+ , Hasura.GraphQL.Execute
+ , Hasura.GraphQL.Execute.Plan
+ , Hasura.GraphQL.Execute.QueryCache
+
, Hasura.GraphQL.Resolve.LiveQuery
, Hasura.GraphQL.Resolve.BoolExp
, Hasura.GraphQL.Resolve.Context
@@ -251,7 +260,7 @@ executable graphql-engine
if flag(developer)
ghc-prof-options: -rtsopts -fprof-auto -fno-prof-count-entries
- ghc-options: -O2 -Wall -threaded
+ ghc-options: -O2 -Wall -rtsopts -with-rtsopts=-N -threaded
test-suite graphql-engine-test
type: exitcode-stdio-1.0
diff --git a/server/src-exec/Main.hs b/server/src-exec/Main.hs
index 5b7a320798b97..423bc9ea69b22 100644
--- a/server/src-exec/Main.hs
+++ b/server/src-exec/Main.hs
@@ -25,11 +25,13 @@ import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Client.TLS as HTTP
import qualified Network.Wai.Handler.Warp as Warp
+import Hasura.GraphQL.Execute as GE
import Hasura.Events.Lib
-import Hasura.Logging (LoggerCtx, defaultLoggerSettings,
+import Hasura.Logging (defaultLoggerSettings, mkLoggerCtx, LoggerCtx, defaultLoggerSettings,
mkLogger, mkLoggerCtx)
import Hasura.Prelude
import Hasura.RQL.DDL.Metadata (fetchMetadata)
+import Hasura.RQL.Types (encJToLBS)
import Hasura.Server.App (mkWaiApp)
import Hasura.Server.Auth
import Hasura.Server.CheckUpdates (checkForUpdates)
@@ -189,8 +191,9 @@ main = do
migrate ci
prepareEvents ci
pool <- Q.initPGPool ci cp
+ planCache <- GE.initQueryCache
putStrLn $ "server: running on port " ++ show port
- (app, cacheRef) <- mkWaiApp isoL mRootDir loggerCtx pool httpManager am finalCorsCfg enableConsole
+ (app, cacheRef) <- mkWaiApp isoL mRootDir loggerCtx pool httpManager planCache am finalCorsCfg enableConsole
let warpSettings = Warp.setPort port Warp.defaultSettings
-- Warp.setHost "*" Warp.defaultSettings
@@ -216,7 +219,7 @@ main = do
ROExecute -> do
queryBs <- BL.getContents
res <- runTx ci $ execQuery queryBs
- either ((>> exitFailure) . printJSON) BLC.putStrLn res
+ either ((>> exitFailure) . printJSON) (BLC.putStrLn . encJToLBS) res
where
runTx ci tx = do
pool <- getMinimalPool ci
diff --git a/server/src-exec/Ops.hs b/server/src-exec/Ops.hs
index b19049aeaf2ef..20800d0429af0 100644
--- a/server/src-exec/Ops.hs
+++ b/server/src-exec/Ops.hs
@@ -227,7 +227,7 @@ migrateCatalog migrationTime = do
"upgraded_on" = $2
|] (curCatalogVer, migrationTime) False
-execQuery :: BL.ByteString -> Q.TxE QErr BL.ByteString
+execQuery :: BL.ByteString -> Q.TxE QErr EncJSON
execQuery queryBs = do
query <- case A.decode queryBs of
Just jVal -> decodeValue jVal
diff --git a/server/src-lib/Hasura/Events/Lib.hs b/server/src-lib/Hasura/Events/Lib.hs
index d3badc13a9a7d..bb6585402fdb0 100644
--- a/server/src-lib/Hasura/Events/Lib.hs
+++ b/server/src-lib/Hasura/Events/Lib.hs
@@ -22,11 +22,12 @@ import Data.Aeson.Casing
import Data.Aeson.TH
import Data.Has
import Data.Int (Int64)
-import Data.IORef (IORef, readIORef)
+import Data.IORef (readIORef)
import Data.Time.Clock
import Hasura.Events.HTTP
import Hasura.Prelude
import Hasura.RQL.Types
+import Hasura.Server.App (CacheRef)
import Hasura.SQL.Types
import qualified Control.Concurrent.STM.TQueue as TQ
@@ -40,15 +41,12 @@ import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Time.Clock as Time
import qualified Database.PG.Query as Q
-import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.Logging as L
import qualified Network.HTTP.Types as N
import qualified Network.Wreq as W
import qualified Network.Wreq.Session as WS
-type CacheRef = IORef (SchemaCache, GS.GCtxMap)
-
newtype EventInternalErr
= EventInternalErr QErr
deriving (Show, Eq)
@@ -117,7 +115,9 @@ initEventEngineCtx maxT pollI = do
c <- newTVar 0
return $ EventEngineCtx q c maxT pollI
-processEventQueue :: L.LoggerCtx -> WS.Session -> Q.PGPool -> CacheRef -> EventEngineCtx -> IO ()
+processEventQueue
+ :: L.LoggerCtx -> WS.Session -> Q.PGPool
+ -> CacheRef -> EventEngineCtx -> IO ()
processEventQueue logctx httpSess pool cacheRef eectx = do
putStrLn "event_trigger: starting workers"
threads <- mapM async [pollThread , consumeThread]
@@ -197,8 +197,8 @@ processEvent pool e = do
)
=> m (Either QErr ())
checkError = do
- cacheRef::CacheRef <- asks getter
- (cache, _) <- liftIO $ readIORef cacheRef
+ cacheRef :: CacheRef <- asks getter
+ (_, cache, _) <- liftIO $ readIORef cacheRef
let eti = getEventTriggerInfoFromEvent cache e
retryConfM = etiRetryConf <$> eti
retryConf = fromMaybe (RetryConf 0 10) retryConfM
@@ -219,7 +219,7 @@ tryWebhook
tryWebhook pool e = do
logger:: HLogger <- asks getter
cacheRef::CacheRef <- asks getter
- (cache, _) <- liftIO $ readIORef cacheRef
+ (_, cache, _) <- liftIO $ readIORef cacheRef
let meti = getEventTriggerInfoFromEvent cache e
case meti of
Nothing -> return $ Left $ HOther "table or event-trigger not found"
diff --git a/server/src-lib/Hasura/GraphQL/Execute.hs b/server/src-lib/Hasura/GraphQL/Execute.hs
new file mode 100644
index 0000000000000..9a22373c55fc1
--- /dev/null
+++ b/server/src-lib/Hasura/GraphQL/Execute.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE TupleSections #-}
+
+module Hasura.GraphQL.Execute
+ ( reqToTx
+ , EC.QueryCache
+ , EC.initQueryCache
+ , EC.clearQueryCache
+
+ , EP.QueryPlan(..)
+ , EP.PGPlan(..)
+ , EP.RootFieldPlan
+ ) where
+
+import Data.Word (Word64)
+import Hasura.Prelude
+
+import qualified Database.PG.Query as Q
+import qualified Language.GraphQL.Draft.Syntax as G
+
+import Hasura.GraphQL.Schema
+import Hasura.GraphQL.Transport.HTTP.Protocol
+import Hasura.RQL.Types
+
+import qualified Hasura.GraphQL.Execute.Plan as EP
+import qualified Hasura.GraphQL.Execute.QueryCache as EC
+import qualified Hasura.GraphQL.Resolve as R
+import qualified Hasura.GraphQL.Validate as VQ
+
+reqToTx
+ :: (MonadIO m, MonadError QErr m)
+ => UserInfo
+ -> Word64 -- schema version
+ -> GCtxMap
+ -> EC.QueryCache
+ -> GQLReqUnparsed
+ -> m (G.OperationType, GQLReqParsed, Q.TxE QErr EncJSON)
+reqToTx userInfo schemaVer gCtxMap queryCache unParsedReq = do
+ astM <- liftIO $ EC.getAST (_grQuery unParsedReq) queryCache
+ req <- maybe (toParsed unParsedReq)
+ (\ast -> return $ unParsedReq { _grQuery = ast}) astM
+
+ queryPlanM <- liftIO $ EC.getPlan schemaVer (userRole userInfo)
+ req queryCache
+ case queryPlanM of
+ Just queryPlan -> do
+ (isSubs, tx) <- flip runReaderT gCtx $
+ EP.mkNewQueryTx (_grVariables req) queryPlan
+ let opTy = bool G.OperationTypeQuery G.OperationTypeSubscription isSubs
+ return (opTy, req, tx)
+ Nothing -> do
+ (varDefs, opTy, fields) <- runReaderT (VQ.validateGQ req) gCtx
+ (opTy, req,) <$> case opTy of
+ G.OperationTypeMutation ->
+ return $ R.resolveSelSet userInfo gCtx opTy fields
+ _ -> do
+ let isSubs = opTy == G.OperationTypeSubscription
+ queryPlan <- EP.QueryPlan isSubs varDefs <$>
+ R.resolveQuerySelSet userInfo gCtx fields
+ when (EP.isReusable queryPlan) $
+ liftIO $ EC.addPlan schemaVer (userRole userInfo) req
+ queryPlan queryCache
+ return $ EP.mkCurPlanTx queryPlan
+ where
+ gCtx = getGCtx (userRole userInfo) gCtxMap
diff --git a/server/src-lib/Hasura/GraphQL/Execute/Plan.hs b/server/src-lib/Hasura/GraphQL/Execute/Plan.hs
new file mode 100644
index 0000000000000..b1aceef080991
--- /dev/null
+++ b/server/src-lib/Hasura/GraphQL/Execute/Plan.hs
@@ -0,0 +1,117 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hasura.GraphQL.Execute.Plan
+ ( RootFieldPlan(..)
+ , PGPlan(..)
+ , PlanVariables
+ , PrepArgMap
+ , QueryPlan(..)
+ , isReusable
+ , mkNewQueryTx
+ , mkCurPlanTx
+ ) where
+
+import Data.Has
+import Hasura.Prelude
+
+import qualified Data.HashMap.Strict as Map
+import qualified Data.HashSet as Set
+import qualified Data.IntMap as IntMap
+import qualified Database.PG.Query as Q
+import qualified Language.GraphQL.Draft.Syntax as G
+
+import Hasura.GraphQL.Resolve.InputValue
+import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
+import qualified Hasura.GraphQL.Validate as GV
+import Hasura.GraphQL.Validate.Types
+import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
+import Hasura.RQL.Types
+import Hasura.SQL.Value
+
+data RootFieldPlan
+ = RFPRaw !EncJSON
+ | RFPPostgres !PGPlan
+
+type PlanVariables = Map.HashMap G.Variable Int
+type PrepArgMap = IntMap.IntMap Q.PrepArg
+
+data PGPlan
+ = PGPlan
+ { _ppQuery :: !Q.Query
+ , _ppVariables :: !PlanVariables
+ , _ppPrepared :: !PrepArgMap
+ }
+
+data QueryPlan
+ = QueryPlan
+ { _qpIsSubscription :: !Bool
+ , _qpVariables :: ![G.VariableDefinition]
+ , _qpFldPlans :: ![(G.Alias, RootFieldPlan)]
+ }
+
+isReusable :: QueryPlan -> Bool
+isReusable (QueryPlan _ vars fldPlans) =
+ all fldPlanReusable $ map snd fldPlans
+ where
+ allVars = Set.fromList $ map G._vdVariable vars
+
+ -- this is quite aggressive, we can improve this by
+ -- computing used variables in each field
+ allUsed fldPlanVars =
+ Set.null $ Set.difference allVars $ Set.fromList fldPlanVars
+
+ fldPlanReusable = \case
+ RFPRaw _ -> True
+ RFPPostgres pgPlan -> allUsed $ Map.keys $ _ppVariables pgPlan
+
+withPlan
+ :: PGPlan -> AnnVarVals -> Q.TxE QErr EncJSON
+withPlan (PGPlan q reqVars prepMap) annVars = do
+ prepMap' <- foldM getVar prepMap (Map.toList reqVars)
+ let args = IntMap.elems prepMap'
+ encJFromBS . runIdentity . Q.getRow <$>
+ Q.rawQE dmlTxErrorHandler q args True
+ where
+ getVar accum (var, prepNo) = do
+ let varName = G.unName $ G.unVariable var
+ annVal <- onNothing (Map.lookup var annVars) $
+ throw500 $ "missing variable in annVars : " <> varName
+ (_, _, _, colVal) <- asPGColVal annVal
+ let prepVal = binEncoder colVal
+ return $ IntMap.insert prepNo prepVal accum
+
+-- use the existing plan and new variables to create a pg query
+mkNewQueryTx
+ :: (MonadError QErr m, MonadReader r m, Has TypeMap r)
+ => Maybe GH.VariableValues
+ -> QueryPlan
+ -> m (Bool, Q.TxE QErr EncJSON)
+mkNewQueryTx varValsM (QueryPlan isSubs varDefs fldPlans) = do
+ annVars <- GV.getAnnVarVals varDefs varVals
+ let tx = fmap encJFromAL $ forM fldPlans $ \(alias, fldPlan) -> do
+ fldResp <- case fldPlan of
+ RFPRaw resp -> return resp
+ RFPPostgres pgPlan -> withPlan pgPlan annVars
+ return (G.unName $ G.unAlias alias, fldResp)
+ return (isSubs, tx)
+ where
+ varVals = fromMaybe Map.empty varValsM
+
+-- turn the current plan into a transaction
+mkCurPlanTx
+ :: QueryPlan
+ -> Q.TxE QErr EncJSON
+mkCurPlanTx (QueryPlan _ _ fldPlans) =
+ fmap encJFromAL $ forM fldPlans $ \(alias, fldPlan) -> do
+ fldResp <- case fldPlan of
+ RFPRaw resp -> return resp
+ RFPPostgres pgPlan -> planTx pgPlan
+ return (G.unName $ G.unAlias alias, fldResp)
+ where
+ planTx (PGPlan q _ prepMap) = do
+ let args = IntMap.elems prepMap
+ encJFromBS . runIdentity . Q.getRow
+ <$> Q.rawQE dmlTxErrorHandler q args True
diff --git a/server/src-lib/Hasura/GraphQL/Execute/QueryCache.hs b/server/src-lib/Hasura/GraphQL/Execute/QueryCache.hs
new file mode 100644
index 0000000000000..d2f5b9221767c
--- /dev/null
+++ b/server/src-lib/Hasura/GraphQL/Execute/QueryCache.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Hasura.GraphQL.Execute.QueryCache
+ ( QueryCache
+ , getAST
+ , addAST
+ , getPlan
+ , addPlan
+ , initQueryCache
+ , clearQueryCache
+ ) where
+
+import Data.Word (Word64)
+
+import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
+
+import qualified Hasura.GraphQL.Execute.Plan as EP
+import qualified Hasura.GraphQL.LRUCache as LRU
+import Hasura.Prelude
+import Hasura.RQL.Types
+
+type PlanCache =
+ LRU.LRUCache
+ (Word64, RoleName, Maybe GH.OperationName, GH.GQLExecDoc)
+ EP.QueryPlan
+
+initPlanCache :: IO PlanCache
+initPlanCache = LRU.initLRUCache 100
+
+type ASTCache =
+ LRU.LRUCache Text GH.GQLExecDoc
+
+initASTCache :: IO ASTCache
+initASTCache = LRU.initLRUCache 100
+
+newtype QueryCache
+ = QueryCache (ASTCache, PlanCache)
+
+initQueryCache :: IO QueryCache
+initQueryCache =
+ fmap QueryCache $ (,) <$> initASTCache <*> initPlanCache
+
+getAST
+ :: Text -> QueryCache -> IO (Maybe GH.GQLExecDoc)
+getAST q queryCache =
+ LRU.lookup astCache q
+ where
+ QueryCache (astCache, _) = queryCache
+
+addAST
+ :: Text -> GH.GQLExecDoc -> QueryCache -> IO ()
+addAST q ast queryCache =
+ LRU.insert astCache q ast
+ where
+ QueryCache (astCache, _) = queryCache
+
+getPlan
+ :: Word64 -> RoleName -> GH.GQLReqParsed
+ -> QueryCache -> IO (Maybe EP.QueryPlan)
+getPlan schemaVer rn (GH.GQLReq opNameM q _) queryCache =
+ LRU.lookup planCache (schemaVer, rn, opNameM, q)
+ where
+ QueryCache (_, planCache) = queryCache
+
+addPlan
+ :: Word64 -> RoleName -> GH.GQLReqParsed
+ -> EP.QueryPlan -> QueryCache -> IO ()
+addPlan schemaVer rn (GH.GQLReq opNameM q _) queryPlan queryCache =
+ LRU.insert planCache (schemaVer, rn, opNameM, q) queryPlan
+ where
+ QueryCache (_, planCache) = queryCache
+
+clearQueryCache :: QueryCache -> IO ()
+clearQueryCache (QueryCache (astCache, planCache)) =
+ LRU.clearLRUCache astCache >> LRU.clearLRUCache planCache
diff --git a/server/src-lib/Hasura/GraphQL/LRUCache.hs b/server/src-lib/Hasura/GraphQL/LRUCache.hs
new file mode 100644
index 0000000000000..6e140013de424
--- /dev/null
+++ b/server/src-lib/Hasura/GraphQL/LRUCache.hs
@@ -0,0 +1,154 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Hasura.GraphQL.LRUCache
+ ( LRUCache
+ , initLRUCache
+ , clearLRUCache
+ , lookup
+ , insert
+ ) where
+
+import Control.Concurrent (getNumCapabilities,
+ myThreadId,
+ threadCapability)
+import Data.Foldable (Foldable)
+import qualified Data.HashPSQ as HashPSQ
+import qualified Data.IORef as IORef
+import Data.Maybe (isNothing)
+import Data.Traversable (Traversable)
+import qualified Data.Vector as V
+import Data.Word (Word64)
+
+import Hasura.Prelude
+
+----------------------------------------------
+
+-- | Logical time at which an element was last accessed.
+type Priority = Word64
+
+-- | LC cache based on hashing.
+data LocalCache k v
+ = LocalCache
+ { lcCapacity :: !Word64
+ , lcSize :: !Word64
+ , lcTick :: !Priority
+ , lcQueue :: !(HashPSQ.HashPSQ k Priority v)
+ } deriving (Eq,Show,Functor,Foldable,Traversable)
+
+-- | Create an empty 'LruCache' of the given size.
+empty
+ :: Word64 -> LocalCache k v
+empty capacity =
+ LocalCache
+ { lcCapacity = capacity
+ , lcSize = 0
+ , lcTick = 0
+ , lcQueue = HashPSQ.empty
+ }
+
+trim :: (Hashable k, Ord k) => LocalCache k v -> LocalCache k v
+trim c
+ | lcTick c == maxBound = empty (lcCapacity c)
+ | lcSize c > lcCapacity c =
+ c { lcSize = lcSize c - 1
+ , lcQueue = HashPSQ.deleteMin (lcQueue c)
+ }
+ | otherwise = c
+
+-- | Insert an element into the 'LruCache'.
+insertLocal :: (Hashable k, Ord k) => k -> v -> LocalCache k v -> LocalCache k v
+insertLocal key val c =
+ trim $!
+ let (mbOldVal,queue) = HashPSQ.insertView key (lcTick c) val (lcQueue c)
+ in c { lcSize = if isNothing mbOldVal
+ then lcSize c + 1
+ else lcSize c
+ , lcTick = lcTick c + 1
+ , lcQueue = queue
+ }
+
+-- | Lookup an element in an 'LruCache' and mark it as the least
+-- recently accessed.
+lookupLocal :: (Hashable k, Ord k) => k -> LocalCache k v -> Maybe (v, LocalCache k v)
+lookupLocal k c =
+ case HashPSQ.alter lookupAndBump k (lcQueue c) of
+ (Nothing, _) -> Nothing
+ (Just x, q) ->
+ let !c' = trim $ c { lcTick = lcTick c + 1, lcQueue = q}
+ in Just (x, c')
+ where
+ lookupAndBump Nothing = (Nothing, Nothing)
+ lookupAndBump (Just (_, x)) = (Just x, Just (lcTick c, x))
+
+-- | Store a LC cache in an 'IORef to be able to conveniently update it.
+newtype LocalCacheRef k v = LocalCacheRef (IORef.IORef (LocalCache k v))
+
+-- | Create a new LC cache of the given size.
+initLocalCache :: Word64 -> IO (LocalCacheRef k v)
+initLocalCache capacity = LocalCacheRef <$> IORef.newIORef (empty capacity)
+
+clearIO :: LocalCacheRef k v -> IO ()
+clearIO (LocalCacheRef ref)=
+ IORef.atomicModifyIORef' ref $
+ \c -> (empty $ lcCapacity c, ())
+
+-- | Return the cached result of the action or, in the case of a cache
+-- miss, execute the action and insertLocal it in the cache.
+lookupIO :: (Hashable k, Ord k) => LocalCacheRef k v -> k -> IO (Maybe v)
+lookupIO (LocalCacheRef ref) k =
+ IORef.atomicModifyIORef' ref $ \c -> case lookupLocal k c of
+ Nothing -> (c, Nothing)
+ Just (v, c') -> (c', Just v)
+
+insertIO :: (Hashable k, Ord k) => LocalCacheRef k v -> k -> v -> IO ()
+insertIO (LocalCacheRef ref) k v =
+ IORef.atomicModifyIORef' ref $ \c -> (insertLocal k v c, ())
+
+-- | Using a stripe of multiple handles can improve the performance in
+-- the case of concurrent accesses since several handles can be
+-- accessed in parallel.
+newtype LRUCache k v = LRUCache (V.Vector (LocalCacheRef k v))
+
+-- | Create a new 'StripedHandle' with the given number of stripes and
+-- the given capacity for each stripe.
+initLRUCache :: Word64 -> IO (LRUCache k v)
+initLRUCache capacityPerStripe = do
+ capabilities <- getNumCapabilities
+ LRUCache <$> V.replicateM capabilities (initLocalCache capacityPerStripe)
+
+clearLRUCache :: LRUCache k v -> IO ()
+clearLRUCache (LRUCache caches) =
+ V.mapM_ clearIO caches
+
+{-# INLINE getLocal #-}
+getLocal :: LRUCache k v -> IO (LocalCacheRef k v)
+getLocal (LRUCache handles) = do
+
+ (i, _) <- myThreadId >>= threadCapability
+
+ -- The number of capability could be dynamically changed.
+ -- So, let's check the upper boundary of the vector
+ let lim = V.length handles
+ j | i < lim = i
+ | otherwise = i `mod` lim
+
+ return $ handles V.! j
+
+-- | Striped version of 'cached'.
+insert
+ :: (Hashable k, Ord k) => LRUCache k v -> k -> v -> IO ()
+insert striped k v = do
+ localHandle <- getLocal striped
+ insertIO localHandle k v
+
+lookup :: (Hashable k, Ord k) => LRUCache k v -> k -> IO (Maybe v)
+lookup striped k = do
+ localHandle <- getLocal striped
+ lookupIO localHandle k
diff --git a/server/src-lib/Hasura/GraphQL/Resolve.hs b/server/src-lib/Hasura/GraphQL/Resolve.hs
index 12f69a6cd00f7..1ff664ea0e380 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve.hs
@@ -5,12 +5,12 @@
module Hasura.GraphQL.Resolve
( resolveSelSet
+ , resolveQuerySelSet
) where
import Hasura.Prelude
import qualified Data.Aeson as J
-import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
@@ -18,26 +18,25 @@ import qualified Language.GraphQL.Draft.Syntax as G
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Resolve.Introspect
import Hasura.GraphQL.Schema
-import Hasura.GraphQL.Transport.HTTP.Protocol
import Hasura.GraphQL.Validate.Field
import Hasura.RQL.Types
import Hasura.SQL.Types
+import qualified Hasura.GraphQL.Execute.Plan as Plan
import qualified Hasura.GraphQL.Resolve.Insert as RI
import qualified Hasura.GraphQL.Resolve.Mutation as RM
import qualified Hasura.GraphQL.Resolve.Select as RS
-- {-# SCC buildTx #-}
-buildTx :: UserInfo -> GCtx -> Field -> Q.TxE QErr BL.ByteString
+buildTx :: UserInfo -> GCtx -> Field -> Q.TxE QErr EncJSON
buildTx userInfo gCtx fld = do
opCxt <- getOpCtx $ _fName fld
join $ fmap fst $ runConvert (fldMap, orderByCtx, insCtxMap) $ case opCxt of
- OCSelect tn permFilter permLimit hdrs ->
- validateHdrs hdrs >> RS.convertSelect tn permFilter permLimit fld
-
- OCSelectPkey tn permFilter hdrs ->
- validateHdrs hdrs >> RS.convertSelectByPKey tn permFilter fld
+ -- OCSelect tn permFilter permLimit hdrs ->
+ -- validateHdrs hdrs >> RS.convertSelect tn permFilter permLimit fld
+ -- OCSelectPkey tn permFilter hdrs ->
+ -- validateHdrs hdrs >> RS.convertSelectByPKey tn permFilter fld
-- RS.convertSelect tn permFilter fld
OCInsert tn hdrs ->
validateHdrs hdrs >> RI.convertInsert roleName tn fld
@@ -48,6 +47,8 @@ buildTx userInfo gCtx fld = do
OCDelete tn permFilter hdrs ->
validateHdrs hdrs >> RM.convertDelete tn permFilter fld
-- RM.convertDelete tn permFilter fld
+ OCSelect {} -> throw500 "unexpected OCSelect for a mutation root field"
+ OCSelectPkey {} -> throw500 "unexpected OCSelectPkey for a mutation root field"
where
roleName = userRole userInfo
opCtxMap = _gOpCtxMap gCtx
@@ -65,17 +66,65 @@ buildTx userInfo gCtx fld = do
unless (Map.member hdr receivedHdrs) $
throw400 NotFound $ hdr <<> " header is expected but not found"
+resolveQuerySelSet
+ :: (MonadError QErr m)
+ => UserInfo
+ -> GCtx
+ -> SelSet
+ -> m [(G.Alias, Plan.RootFieldPlan)]
+resolveQuerySelSet userInfo gCtx fields =
+ forM (toList fields) $ \fld -> do
+ fldResp <- resolveQueryFld userInfo gCtx fld
+ return (_fAlias fld, fldResp)
+
+resolveQueryFld
+ :: (MonadError QErr m)
+ => UserInfo
+ -> GCtx
+ -> Field
+ -> m Plan.RootFieldPlan
+resolveQueryFld userInfo gCtx fld =
+ case _fName fld of
+ "__type" -> Plan.RFPRaw . encJFromJ <$> runReaderT (typeR fld) gCtx
+ "__schema" -> Plan.RFPRaw . encJFromJ <$> runReaderT (schemaR fld) gCtx
+ "__typename" -> return $ Plan.RFPRaw $ encJFromJ queryRoot
+ _ -> do
+ opCxt <- getOpCtx $ _fName fld
+ RS.runPlanM (fldMap, orderByCtx) $ case opCxt of
+ OCSelect tn permFilter permLimit hdrs ->
+ validateHdrs hdrs >> RS.convertSelect2 tn permFilter permLimit fld
+ OCSelectPkey tn permFilter hdrs ->
+ validateHdrs hdrs >> RS.convertSelectByPKey tn permFilter fld
+ _ -> throw500 "expecting OCSelect for a query root field"
+ where
+ opCtxMap = _gOpCtxMap gCtx
+ fldMap = _gFields gCtx
+ orderByCtx = _gOrdByEnums gCtx
+
+ getOpCtx f =
+ onNothing (Map.lookup f opCtxMap) $ throw500 $
+ "lookup failed: opctx: " <> showName f
+
+ validateHdrs hdrs = do
+ let receivedHdrs = userHeaders userInfo
+ forM_ hdrs $ \hdr ->
+ unless (Map.member hdr receivedHdrs) $
+ throw400 NotFound $ hdr <<> " header is expected but not found"
+
+ queryRoot :: Text
+ queryRoot = "query_root"
+
-- {-# SCC resolveFld #-}
resolveFld
:: UserInfo -> GCtx
-> G.OperationType
-> Field
- -> Q.TxE QErr BL.ByteString
+ -> Q.TxE QErr EncJSON
resolveFld userInfo gCtx opTy fld =
case _fName fld of
- "__type" -> J.encode <$> runReaderT (typeR fld) gCtx
- "__schema" -> J.encode <$> runReaderT (schemaR fld) gCtx
- "__typename" -> return $ J.encode $ mkRootTypeName opTy
+ "__type" -> encJFromLBS . J.encode <$> runReaderT (typeR fld) gCtx
+ "__schema" -> encJFromLBS . J.encode <$> runReaderT (schemaR fld) gCtx
+ "__typename" -> return $ encJFromLBS . J.encode $ mkRootTypeName opTy
_ -> buildTx userInfo gCtx fld
where
mkRootTypeName :: G.OperationType -> Text
@@ -88,8 +137,8 @@ resolveSelSet
:: UserInfo -> GCtx
-> G.OperationType
-> SelSet
- -> Q.TxE QErr BL.ByteString
+ -> Q.TxE QErr EncJSON
resolveSelSet userInfo gCtx opTy fields =
- fmap mkJSONObj $ forM (toList fields) $ \fld -> do
+ fmap encJFromAL $ forM (toList fields) $ \fld -> do
fldResp <- resolveFld userInfo gCtx opTy fld
return (G.unName $ G.unAlias $ _fAlias fld, fldResp)
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs
index 1b00ccd7b0642..15db72f1a31aa 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs
@@ -8,6 +8,7 @@ module Hasura.GraphQL.Resolve.BoolExp
( parseBoolExp
, pgColValToBoolExp
, convertBoolExp
+ , convertBoolExpG
, prepare
) where
@@ -29,9 +30,11 @@ import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
+type OpExp = RA.OpExpG AnnPGVal
+
parseOpExps
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
- => AnnGValue -> m [RA.OpExp]
+ => AnnInpVal -> m [OpExp]
parseOpExps annVal = do
opExpsM <- flip withObjectM annVal $ \nt objM -> forM objM $ \obj ->
forM (Map.toList obj) $ \(k, v) -> case k of
@@ -72,16 +75,18 @@ parseOpExps annVal = do
<> showName k
return $ map RA.OEVal $ catMaybes $ fromMaybe [] opExpsM
where
- resolveIsNull v = case v of
+ resolveIsNull v = case _aivValue v of
AGScalar _ Nothing -> return Nothing
AGScalar _ (Just (PGValBoolean b)) ->
return $ Just $ bool RA.ANISNOTNULL RA.ANISNULL b
AGScalar _ _ -> throw500 "boolean value is expected"
_ -> tyMismatch "pgvalue" v
+type AnnVal = RA.AnnValO AnnPGVal
+
parseAsEqOp
:: (MonadError QErr m)
- => AnnGValue -> m [RA.OpExp]
+ => AnnInpVal -> m [OpExp]
parseAsEqOp annVal = do
annValOpExp <- RA.AEQ <$> asPGColVal annVal
return [RA.OEVal annValOpExp]
@@ -90,9 +95,9 @@ parseColExp
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
=> G.NamedType
-> G.Name
- -> AnnGValue
- -> (AnnGValue -> m [RA.OpExp])
- -> m RA.AnnVal
+ -> AnnInpVal
+ -> (AnnInpVal -> m [OpExp])
+ -> m AnnVal
parseColExp nt n val expParser = do
fldInfo <- getFldInfo nt n
case fldInfo of
@@ -103,8 +108,8 @@ parseColExp nt n val expParser = do
parseBoolExp
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
- => AnnGValue
- -> m (GBoolExp RA.AnnVal)
+ => AnnInpVal
+ -> m (GBoolExp AnnVal)
parseBoolExp annGVal = do
boolExpsM <-
flip withObjectM annGVal
@@ -117,24 +122,35 @@ parseBoolExp annGVal = do
convertBoolExp
:: QualifiedTable
- -> AnnGValue
+ -> AnnInpVal
-> Convert (GBoolExp RG.AnnSQLBoolExp)
-convertBoolExp tn whereArg = do
+convertBoolExp =
+ convertBoolExpG prepare
+
+convertBoolExpG
+ :: (MonadError QErr m, MonadReader r m, Has FieldMap r)
+ => (AnnPGVal -> m S.SQLExp)
+ -> QualifiedTable
+ -> AnnInpVal
+ -> m (GBoolExp RG.AnnSQLBoolExp)
+convertBoolExpG f tn whereArg = do
whereExp <- parseBoolExp whereArg
- RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp
+ RG.convBoolRhs (RG.mkBoolExpBuilder f) (S.mkQual tn) whereExp
-type PGColValMap = Map.HashMap G.Name AnnGValue
+type PGColValMap = Map.HashMap G.Name AnnInpVal
pgColValToBoolExp
- :: QualifiedTable
+ :: (MonadError QErr m, MonadReader r m, Has FieldMap r)
+ => (AnnPGVal -> m S.SQLExp)
+ -> QualifiedTable
-> PGColValMap
- -> Convert (GBoolExp RG.AnnSQLBoolExp)
-pgColValToBoolExp tn colValMap = do
+ -> m (GBoolExp RG.AnnSQLBoolExp)
+pgColValToBoolExp f tn colValMap = do
colExps <- forM colVals $ \(name, val) -> do
- (ty, _) <- asPGColVal val
+ (_, _, ty, _) <- asPGColVal val
let namedTy = mkScalarTy ty
BoolCol <$> parseColExp namedTy name val parseAsEqOp
let whereExp = BoolAnd colExps
- RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp
+ RG.convBoolRhs (RG.mkBoolExpBuilder f) (S.mkQual tn) whereExp
where
colVals = Map.toList colValMap
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Context.hs b/server/src-lib/Hasura/GraphQL/Resolve/Context.hs
index e2ec659ba8186..6eecef7b8fb7b 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Context.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Context.hs
@@ -27,6 +27,7 @@ module Hasura.GraphQL.Resolve.Context
, Convert
, runConvert
, prepare
+ , prepare'
, module Hasura.GraphQL.Utils
) where
@@ -36,12 +37,12 @@ import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
-import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
+import Hasura.GraphQL.Resolve.InputValue
import Hasura.GraphQL.Utils
import Hasura.GraphQL.Validate.Field
import Hasura.GraphQL.Validate.Types
@@ -73,7 +74,7 @@ data NullsOrder
| NLast
deriving (Show, Eq)
-type RespTx = Q.TxE QErr BL.ByteString
+type RespTx = Q.TxE QErr EncJSON
-- context needed for sql generation
type OrdByResolveCtxElem = RS.AnnOrderByItem
@@ -117,7 +118,7 @@ getArg
:: (MonadError QErr m)
=> ArgsMap
-> G.Name
- -> m AnnGValue
+ -> m AnnInpVal
getArg args arg =
onNothing (Map.lookup arg args) $
throw500 $ "missing argument: " <> showName arg
@@ -136,7 +137,7 @@ withArg
:: (MonadError QErr m)
=> ArgsMap
-> G.Name
- -> (AnnGValue -> m a)
+ -> (AnnInpVal -> m a)
-> m a
withArg args arg f = prependArgsInPath $ nameAsPath arg $
getArg args arg >>= f
@@ -145,7 +146,7 @@ withArgM
:: (MonadError QErr m)
=> ArgsMap
-> G.Name
- -> (AnnGValue -> m a)
+ -> (AnnInpVal -> m a)
-> m (Maybe a)
withArgM args arg f = prependArgsInPath $ nameAsPath arg $
mapM f $ Map.lookup arg args
@@ -156,9 +157,15 @@ type Convert =
StateT PrepArgs (ReaderT (FieldMap, OrdByResolveCtx, InsCtxMap) (Except QErr))
prepare
+ :: (MonadState PrepArgs m)
+ => AnnPGVal -> m S.SQLExp
+prepare (_, _, colTy, colVal) =
+ prepare' (colTy, colVal)
+
+prepare'
:: (MonadState PrepArgs m)
=> (PGColType, PGColValue) -> m S.SQLExp
-prepare (colTy, colVal) = do
+prepare' (colTy, colVal) = do
preparedArgs <- get
put (preparedArgs Seq.|> binEncoder colVal)
return $ toPrepParam (Seq.length preparedArgs + 1) colTy
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/InputValue.hs b/server/src-lib/Hasura/GraphQL/Resolve/InputValue.hs
index 806f2fcc5c6c6..0ad3c556c0df6 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/InputValue.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/InputValue.hs
@@ -1,5 +1,4 @@
{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
@@ -18,6 +17,7 @@ module Hasura.GraphQL.Resolve.InputValue
, asArray
, withArrayM
, parseMany
+ , AnnPGVal
, asPGColText
) where
@@ -40,93 +40,97 @@ withNotNull nt v =
"unexpected null for a value of type " <> showNamedTy nt
tyMismatch
- :: (MonadError QErr m) => Text -> AnnGValue -> m a
+ :: (MonadError QErr m) => Text -> AnnInpVal -> m a
tyMismatch expectedTy v =
throw500 $ "expected " <> expectedTy <> ", found " <>
- getAnnInpValKind v <> " for value of type " <>
- G.showGT (getAnnInpValTy v)
+ getAnnInpValKind (_aivValue v) <> " for value of type " <>
+ G.showGT (_aivType v)
+
+-- maybe variable, is nullable, col type and value
+type AnnPGVal = (Maybe G.Variable, Bool, PGColType, PGColValue)
asPGColValM
:: (MonadError QErr m)
- => AnnGValue -> m (Maybe (PGColType, PGColValue))
-asPGColValM = \case
- AGScalar colTy valM -> return $ fmap (colTy,) valM
- v -> tyMismatch "pgvalue" v
+ => AnnInpVal -> m (Maybe AnnPGVal)
+asPGColValM v = case _aivValue v of
+ AGScalar colTy valM ->
+ return $ fmap (_aivVariable v, G.isNullable (_aivType v), colTy,) valM
+ _ -> tyMismatch "pgvalue" v
asPGColVal
:: (MonadError QErr m)
- => AnnGValue -> m (PGColType, PGColValue)
-asPGColVal = \case
- AGScalar colTy (Just val) -> return (colTy, val)
+ => AnnInpVal -> m AnnPGVal
+asPGColVal v = case _aivValue v of
+ AGScalar colTy (Just val) ->
+ return (_aivVariable v, G.isNullable (_aivType v), colTy, val)
AGScalar colTy Nothing ->
- throw500 $ "unexpected null for ty"
+ throw500 $ "unexpected null for ty "
<> T.pack (show colTy)
- v -> tyMismatch "pgvalue" v
+ _ -> tyMismatch "pgvalue" v
asEnumVal
:: (MonadError QErr m)
- => AnnGValue -> m (G.NamedType, G.EnumValue)
-asEnumVal = \case
+ => AnnInpVal -> m (G.NamedType, G.EnumValue)
+asEnumVal v = case _aivValue v of
AGEnum ty (Just val) -> return (ty, val)
AGEnum ty Nothing ->
throw500 $ "unexpected null for ty " <> showNamedTy ty
- v -> tyMismatch "enum" v
+ _ -> tyMismatch "enum" v
withObject
:: (MonadError QErr m)
- => (G.NamedType -> AnnGObject -> m a) -> AnnGValue -> m a
-withObject fn v = case v of
+ => (G.NamedType -> AnnGObject -> m a) -> AnnInpVal -> m a
+withObject fn v = case _aivValue v of
AGObject nt (Just obj) -> fn nt obj
- AGObject nt Nothing ->
- throw500 $ "unexpected null for ty"
- <> G.showGT (G.TypeNamed nt)
+ AGObject _ Nothing ->
+ throw500 $ "unexpected null for ty " <> G.showGT (_aivType v)
_ -> tyMismatch "object" v
asObject
:: (MonadError QErr m)
- => AnnGValue -> m AnnGObject
+ => AnnInpVal -> m AnnGObject
asObject = withObject (\_ o -> return o)
withObjectM
:: (MonadError QErr m)
- => (G.NamedType -> Maybe AnnGObject -> m a) -> AnnGValue -> m a
-withObjectM fn v = case v of
+ => (G.NamedType -> Maybe AnnGObject -> m a) -> AnnInpVal -> m a
+withObjectM fn v = case _aivValue v of
AGObject nt objM -> fn nt objM
_ -> tyMismatch "object" v
withArrayM
:: (MonadError QErr m)
- => (G.ListType -> Maybe [AnnGValue] -> m a) -> AnnGValue -> m a
-withArrayM fn v = case v of
+ => (G.ListType -> Maybe [AnnInpVal] -> m a) -> AnnInpVal -> m a
+withArrayM fn v = case _aivValue v of
AGArray lt listM -> fn lt listM
_ -> tyMismatch "array" v
withArray
:: (MonadError QErr m)
- => (G.ListType -> [AnnGValue] -> m a) -> AnnGValue -> m a
-withArray fn v = case v of
+ => (G.ListType -> [AnnInpVal] -> m a) -> AnnInpVal -> m a
+withArray fn v = case _aivValue v of
AGArray lt (Just l) -> fn lt l
- AGArray lt Nothing -> throw500 $ "unexpected null for ty"
- <> G.showGT (G.TypeList lt)
+ AGArray _ Nothing -> throw500 $ "unexpected null for ty "
+ <> G.showGT (_aivType v)
_ -> tyMismatch "array" v
asArray
:: (MonadError QErr m)
- => AnnGValue -> m [AnnGValue]
+ => AnnInpVal -> m [AnnInpVal]
asArray = withArray (\_ vals -> return vals)
parseMany
:: (MonadError QErr m)
- => (AnnGValue -> m a) -> AnnGValue -> m (Maybe [a])
-parseMany fn v = case v of
+ => (AnnInpVal -> m a) -> AnnInpVal -> m (Maybe [a])
+parseMany fn v = case _aivValue v of
AGArray _ arrM -> mapM (mapM fn) arrM
_ -> tyMismatch "array" v
asPGColText
:: (MonadError QErr m)
- => AnnGValue -> m Text
+ => AnnInpVal -> m Text
asPGColText val = do
- (_, pgColVal) <- asPGColVal val
+ (_, _, _, pgColVal) <- asPGColVal val
case pgColVal of
PGValText t -> return t
_ -> throw500 "expecting text for asPGColText"
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
index ef6aa36451379..0bda411a40f3a 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
@@ -1,6 +1,7 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
module Hasura.GraphQL.Resolve.Insert
(convertInsert)
@@ -11,13 +12,10 @@ import Data.Has
import Data.List (intersect, union)
import Hasura.Prelude
-import qualified Data.Aeson as J
-import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as Map
import qualified Data.HashSet as Set
import qualified Data.Sequence as Seq
import qualified Data.Text as T
-import qualified Data.Vector as V
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Database.PG.Query as Q
@@ -29,7 +27,6 @@ import qualified Hasura.RQL.GBoolExp as RB
import qualified Hasura.SQL.DML as S
-import Hasura.GraphQL.Resolve.BoolExp
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Resolve.InputValue
import Hasura.GraphQL.Resolve.Mutation
@@ -44,7 +41,7 @@ import Hasura.SQL.Value
data RelData a
= RelData
{ _rdInsObj :: a
- , _rdConflictClause :: !(Maybe AnnGValue)
+ , _rdConflictClause :: !(Maybe AnnInpVal)
} deriving (Show, Eq)
type ObjRelData = RelData AnnGObject
@@ -101,7 +98,7 @@ parseUpdCols obj = withPathK "update_columns" $
parseOnConflict
:: (MonadError QErr m)
- => [PGCol] -> AnnGValue -> m RI.ConflictClauseP1
+ => [PGCol] -> AnnInpVal -> m RI.ConflictClauseP1
parseOnConflict inpCols val = withPathK "on_conflict" $
flip withObject val $ \_ obj -> do
actionM <- parseAction obj
@@ -121,20 +118,21 @@ parseRelObj
parseRelObj annObj = do
let conflictClauseM = Map.lookup "on_conflict" annObj
dataVal <- onNothing (Map.lookup "data" annObj) $ throw500 "\"data\" object not found"
- case dataVal of
+ case _aivValue dataVal of
AGObject _ (Just obj) -> return $ Left $ RelData obj conflictClauseM
AGArray _ (Just vals) -> do
objs <- forM vals asObject
return $ Right $ RelData objs conflictClauseM
_ -> throw500 "unexpected type for \"data\""
-toSQLExps :: (MonadError QErr m, MonadState PrepArgs m)
- => [(PGCol, AnnGValue)] -> m [(PGCol, S.SQLExp)]
-toSQLExps cols =
- forM cols $ \(c, v) -> do
- prepExpM <- asPGColValM v >>= mapM prepare
- let prepExp = fromMaybe (S.SEUnsafe "NULL") prepExpM
- return (c, prepExp)
+toSQLExps
+ :: (MonadState PrepArgs m)
+ => [(PGCol, PGColType, PGColValue)] -> m [(PGCol, S.SQLExp)]
+toSQLExps =
+ mapM f
+ where
+ f (col, colTy, colVal) =
+ (col,) <$> prepare' (colTy, colVal)
mkSQLRow :: [PGCol] -> [(PGCol, S.SQLExp)] -> [S.SQLExp]
mkSQLRow tableCols withPGCol =
@@ -143,7 +141,8 @@ mkSQLRow tableCols withPGCol =
defVals = Map.fromList $ zip tableCols (repeat $ S.SEUnsafe "DEFAULT")
mkInsertQ :: QualifiedTable
- -> Maybe RI.ConflictClauseP1 -> [(PGCol, AnnGValue)]
+ -> Maybe RI.ConflictClauseP1
+ -> [(PGCol, PGColType, PGColValue)]
-> [PGCol] -> RoleName
-> Q.TxE QErr WithExp
mkInsertQ vn onConflictM insCols tableCols role = do
@@ -165,10 +164,11 @@ fetchColsAndRels
, [(RelName, ObjRelData)] -- ^ object relations
, [(RelName, ArrRelData)] -- ^ array relations
)
-fetchColsAndRels annObj = foldrM go ([], [], []) $ Map.toList annObj
+fetchColsAndRels annObj =
+ foldrM go ([], [], []) $ Map.toList annObj
where
go (gName, annVal) (cols, objRels, arrRels) =
- case annVal of
+ case _aivValue annVal of
AGScalar colty mColVal -> do
let col = PGCol $ G.unName gName
colVal = fromMaybe (PGNull colty) mColVal
@@ -262,7 +262,7 @@ insertArrRel role insCtxMap insCtx relInfo resCols relData =
resBS <- insertMultipleObjects role insCtxMap tn insCtx
insObjs addCols mutFlds onConflictM True
- resObj <- decodeFromBS resBS
+ resObj <- decodeFromBS $ encJToLBS resBS
onNothing (Map.lookup ("affected_rows" :: T.Text) resObj) $
throw500 "affected_rows not returned in array rel insert"
where
@@ -307,7 +307,7 @@ insertObj
-> AnnGObject -- ^ object to be inserted
-> InsCtx -- ^ required insert context
-> [PGColWithValue] -- ^ additional fields
- -> Maybe AnnGValue -- ^ on conflict context
+ -> Maybe AnnInpVal -- ^ on conflict context
-> T.Text -- ^ error path
-> Q.TxE QErr (Int, WithExp)
insertObj role insCtxMap tn annObj ctx addCols onConflictValM errP = do
@@ -328,7 +328,7 @@ insertObj role insCtxMap tn annObj ctx addCols onConflictValM errP = do
objRelDeterminedCols = concatMap snd objInsRes
objRelInsCols = mkPGColWithTypeAndVal tableColInfos objRelDeterminedCols
addInsCols = mkPGColWithTypeAndVal tableColInfos addCols
- finalInsCols = map pgColToAnnGVal (cols <> objRelInsCols <> addInsCols)
+ finalInsCols = cols <> objRelInsCols <> addInsCols
-- fetch array rel deps Cols
processedArrRels <- processArrRel insCtxMap arrRels relInfoMap
@@ -337,7 +337,7 @@ insertObj role insCtxMap tn annObj ctx addCols onConflictValM errP = do
let arrDepCols = concatMap (\(a, _, _, _) -> a) processedArrRels
arrDepColsWithInfo = getColInfos arrDepCols tableColInfos
- onConflictM <- forM onConflictValM $ parseOnConflict (map fst finalInsCols)
+ onConflictM <- forM onConflictValM $ parseOnConflict (map _1 finalInsCols)
-- calculate affected rows
let anyRowsAffected = not $ or $ fmap RI.isDoNothing onConflictM
@@ -381,7 +381,7 @@ mkBoolExp
=> QualifiedTable -> [(PGColInfo, PGColValue)]
-> m (GBoolExp RG.AnnSQLBoolExp)
mkBoolExp tn colInfoVals =
- RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) boolExp
+ RG.convBoolRhs (RG.mkBoolExpBuilder prepare') (S.mkQual tn) boolExp
where
boolExp = BoolAnd $ map (BoolCol . uncurry f) colInfoVals
f ci@(PGColInfo _ colTy _) colVal =
@@ -406,14 +406,14 @@ execWithExp
:: QualifiedTable
-> WithExp
-> AnnSelFlds
- -> Q.TxE QErr RespBody
+ -> Q.TxE QErr EncJSON
execWithExp tn (withExp, args) annFlds = do
let annSel = RS.AnnSel annFlds tn frmItemM
(S.BELit True) Nothing RS.noTableArgs
sqlSel = RS.mkSQLSelect True annSel
selWith = S.SelectWith [(alias, withExp)] sqlSel
sqlBuilder = toSQL selWith
- runIdentity . Q.getRow
+ encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sqlBuilder) (toList args) True
where
alias = S.Alias $ Iden $ snakeCaseTable tn <> "__rel_insert_result"
@@ -426,7 +426,7 @@ insertAndRetCols
-> Q.TxE QErr [PGColWithValue]
insertAndRetCols tn withExp retCols = do
resBS <- execWithExp tn withExp annSelFlds
- resObj <- decodeFromBS resBS
+ resObj <- decodeFromBS $ encJToLBS resBS
forM retCols $ \(PGColInfo col colty _) -> do
val <- onNothing (Map.lookup (getPGColTxt col) resObj) $
throw500 $ "column " <> col <<> "not returned by postgres"
@@ -440,12 +440,10 @@ buildReturningResp
:: QualifiedTable
-> [WithExp]
-> AnnSelFlds
- -> Q.TxE QErr RespBody
-buildReturningResp tn withExps annFlds = do
- respList <- forM withExps $ \withExp ->
+ -> Q.TxE QErr EncJSON
+buildReturningResp tn withExps annFlds =
+ fmap encJFromL $ forM withExps $ \withExp ->
execWithExp tn withExp annFlds
- let bsVector = V.fromList respList
- return $ BB.toLazyByteString $ RR.encodeJSONVector BB.lazyByteString bsVector
-- | insert multiple Objects in postgres
insertMultipleObjects
@@ -456,9 +454,9 @@ insertMultipleObjects
-> [AnnGObject] -- ^ objects to be inserted
-> [PGColWithValue] -- ^ additional fields
-> RR.MutFlds -- ^ returning fields
- -> Maybe AnnGValue -- ^ On Conflict Clause
+ -> Maybe AnnInpVal -- ^ On Conflict Clause
-> Bool -- ^ is an Array relation
- -> Q.TxE QErr RespBody
+ -> Q.TxE QErr EncJSON
insertMultipleObjects role insCtxMap tn ctx insObjs
addCols mutFlds onConflictValM isArrRel
= do
@@ -494,7 +492,7 @@ insertMultipleObjects role insCtxMap tn ctx insObjs
withAddCols = flip map insCols $ union addColsWithType
(sqlRows, prepArgs) <- flip runStateT Seq.Empty $ do
- rowsWithCol <- mapM (toSQLExps . map pgColToAnnGVal) withAddCols
+ rowsWithCol <- mapM toSQLExps withAddCols
return $ map (mkSQLRow tableCols) rowsWithCol
let insQP1 = RI.InsertQueryP1 tn vn tableCols sqlRows onConflictM mutFlds
@@ -514,14 +512,13 @@ insertMultipleObjects role insCtxMap tn ctx insObjs
-- when it is a array relation perform insert
-- and return calculated affected rows
when isArrRel $ void $ buildReturningResp tn withExps []
- return $ J.toJSON affRows
- RR.MExp txt -> return $ J.toJSON txt
+ return $ encJFromJ affRows
+ RR.MExp txt -> return $ encJFromJ txt
RR.MRet annSel -> do
let annFlds = RS._asFields annSel
- bs <- buildReturningResp tn withExps annFlds
- decodeFromBS bs
+ buildReturningResp tn withExps annFlds
return (t, jsonVal)
- return $ J.encode $ Map.fromList respTups
+ return $ encJFromAL respTups
prefixErrPath :: (MonadError QErr m) => Field -> m a -> m a
prefixErrPath fld =
@@ -571,12 +568,6 @@ mkPGColWithTypeAndVal pgColInfos pgColWithVal =
(\ci (c, _) -> pgiName ci == c)
(\ci (c, v) -> (c, pgiType ci, v))
-pgColToAnnGVal
- :: (PGCol, PGColType, PGColValue)
- -> (PGCol, AnnGValue)
-pgColToAnnGVal (col, colTy, colVal) =
- (col, pgColValToAnnGVal colTy colVal)
-
_1 :: (a, b, c) -> a
_1 (x, _, _) = x
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Introspect.hs b/server/src-lib/Hasura/GraphQL/Resolve/Introspect.hs
index 1cae7ef246797..7ee6fc8240154 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Introspect.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Introspect.hs
@@ -4,6 +4,7 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
module Hasura.GraphQL.Resolve.Introspect
( schemaR
@@ -152,21 +153,6 @@ listTypeR (G.ListType ty) fld =
"ofType" -> J.toJSON <$> gtypeR ty subFld
_ -> return J.Null
--- 4.5.2.8
-nonNullR
- :: ( MonadReader r m, Has TypeMap r
- , MonadError QErr m)
- => G.NonNullType -> Field -> m J.Object
-nonNullR nnt fld =
- withSubFields (_fSelSet fld) $ \subFld ->
- case _fName subFld of
- "__typename" -> retJT "__Type"
- "kind" -> retJ TKNON_NULL
- "ofType" -> case nnt of
- G.NonNullTypeNamed nt -> J.toJSON <$> namedTypeR nt subFld
- G.NonNullTypeList lt -> J.toJSON <$> listTypeR lt subFld
- _ -> return J.Null
-
namedTypeR
:: ( MonadReader r m, Has TypeMap r
, MonadError QErr m)
@@ -257,15 +243,37 @@ showDirLoc = \case
G.DLExecutable edl -> T.pack $ drop 3 $ show edl
G.DLTypeSystem tsdl -> T.pack $ drop 4 $ show tsdl
+-- 4.5.2.8
+nonNullR
+ :: ( MonadReader r m, Has TypeMap r
+ , MonadError QErr m)
+ => Either G.NamedType G.ListType -> Field -> m J.Object
+nonNullR tyE fld =
+ withSubFields (_fSelSet fld) $ \subFld ->
+ case _fName subFld of
+ "__typename" -> retJT "__Type"
+ "kind" -> retJ TKNON_NULL
+ "ofType" -> J.toJSON <$> gtypeR' tyE subFld
+ _ -> return J.Null
+
+gtypeR'
+ :: ( MonadReader r m, Has TypeMap r
+ , MonadError QErr m)
+ => Either G.NamedType G.ListType -> Field -> m J.Object
+gtypeR' ty fld =
+ either (`namedTypeR` fld) (`listTypeR` fld) ty
+
gtypeR
:: ( MonadReader r m, Has TypeMap r
, MonadError QErr m)
=> G.GType -> Field -> m J.Object
-gtypeR ty fld =
- case ty of
- G.TypeList lt -> listTypeR lt fld
- G.TypeNonNull nnt -> nonNullR nnt fld
- G.TypeNamed nt -> namedTypeR nt fld
+gtypeR ty fld = do
+ let tyE = case ty of
+ G.TypeNamed _ nt -> Left nt
+ G.TypeList _ lt -> Right lt
+ if G.isNullable ty
+ then gtypeR' tyE fld
+ else nonNullR tyE fld
schemaR
:: ( MonadReader r m, Has TypeMap r
@@ -291,7 +299,7 @@ typeR
=> Field -> m J.Value
typeR fld = do
name <- withArg args "name" $ \arg -> do
- (_, pgColVal) <- asPGColVal arg
+ (_, _, _, pgColVal) <- asPGColVal arg
case pgColVal of
PGValText t -> return t
_ -> throw500 "expecting string for name arg of __type"
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/LiveQuery.hs b/server/src-lib/Hasura/GraphQL/Resolve/LiveQuery.hs
index a0c9d17cea42a..447f79fd66920 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/LiveQuery.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/LiveQuery.hs
@@ -11,7 +11,6 @@ module Hasura.GraphQL.Resolve.LiveQuery
import qualified Control.Concurrent.Async as A
import qualified Control.Concurrent.STM as STM
-import qualified Data.ByteString.Lazy as BL
import qualified ListT
import qualified STMContainers.Map as STMMap
@@ -25,7 +24,7 @@ import Hasura.RQL.Types
data LiveQuery
= LiveQuery
{ _lqUser :: !UserInfo
- , _lqRequest :: !GraphQLRequest
+ , _lqRequest :: !GQLReqParsed
} deriving (Show, Eq, Generic)
instance Hashable LiveQuery
@@ -52,7 +51,7 @@ type LiveQueryMap k = STMMap.Map LiveQuery (LQHandler k, ThreadTM)
newLiveQueryMap :: STM.STM (LiveQueryMap k)
newLiveQueryMap = STMMap.new
-type TxRunner = RespTx -> IO (Either QErr BL.ByteString)
+type TxRunner = RespTx -> IO (Either QErr EncJSON)
removeLiveQuery
:: (Eq k, Hashable k)
@@ -145,8 +144,8 @@ pollQuery runTx (LQHandler respTx respTV curOpsTV newOpsTV) = do
res <- runTx respTx
let resp = case res of
- Left e -> GQExecError [encodeGQErr False e]
- Right bs -> GQSuccess bs
+ Left e -> GQExecError [encodeGQErr False e]
+ Right encJ -> GQSuccess $ encJToLBS encJ
-- extract the current and new operations
(curOps, newOps) <- STM.atomically $ do
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs b/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
index 818543801eb35..8eae9f9d23afd 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
@@ -41,7 +41,7 @@ withSelSet selSet f =
convertReturning
:: QualifiedTable -> G.NamedType -> SelSet -> Convert RS.AnnSel
convertReturning qt ty selSet = do
- annFlds <- fromSelSet ty selSet
+ annFlds <- fromSelSet prepare ty selSet
return $ RS.AnnSel annFlds qt (Just frmItem)
(S.BELit True) Nothing RS.noTableArgs
where
@@ -58,7 +58,7 @@ convertMutResp qt ty selSet =
convertRowObj
:: (MonadError QErr m, MonadState PrepArgs m)
- => AnnGValue
+ => AnnInpVal
-> m [(PGCol, S.SQLExp)]
convertRowObj val =
flip withObject val $ \_ obj -> forM (Map.toList obj) $ \(k, v) -> do
@@ -82,22 +82,25 @@ lhsExpOp op annTy (col, e) =
convObjWithOp
:: (MonadError QErr m)
- => ApplySQLOp -> AnnGValue -> m [(PGCol, S.SQLExp)]
+ => ApplySQLOp -> AnnInpVal -> m [(PGCol, S.SQLExp)]
convObjWithOp opFn val =
flip withObject val $ \_ obj -> forM (Map.toList obj) $ \(k, v) -> do
- (_, colVal) <- asPGColVal v
+ (_, _, _, colVal) <- asPGColVal v
let pgCol = PGCol $ G.unName k
encVal = txtEncoder colVal
sqlExp = opFn (pgCol, encVal)
return (pgCol, sqlExp)
+fourth :: (a, b, c, d) -> d
+fourth (_, _, _, d) = d
+
convDeleteAtPathObj
:: (MonadError QErr m)
- => AnnGValue -> m [(PGCol, S.SQLExp)]
+ => AnnInpVal -> m [(PGCol, S.SQLExp)]
convDeleteAtPathObj val =
flip withObject val $ \_ obj -> forM (Map.toList obj) $ \(k, v) -> do
vals <- flip withArray v $ \_ annVals -> mapM asPGColVal annVals
- let valExps = map (txtEncoder . snd) vals
+ let valExps = map (txtEncoder . fourth) vals
pgCol = PGCol $ G.unName k
annEncVal = S.SETyAnn (S.SEArray valExps) S.textArrType
sqlExp = S.SEOpApp S.jsonbDeleteAtPathOp
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs
index 6e36d145c5781..562be14d1e3e0 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Select.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Select.hs
@@ -6,7 +6,8 @@
{-# LANGUAGE TupleSections #-}
module Hasura.GraphQL.Resolve.Select
- ( convertSelect
+ ( convertSelect2
+ , runPlanM
, convertSelectByPKey
, fromSelSet
, fieldAsPath
@@ -16,12 +17,15 @@ import Data.Has
import Hasura.Prelude
import qualified Data.HashMap.Strict as Map
+import qualified Data.IntMap as IntMap
+import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.RQL.DML.Select as RS
import qualified Hasura.SQL.DML as S
+import qualified Hasura.GraphQL.Execute.Plan as Plan
import Hasura.GraphQL.Resolve.BoolExp
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Resolve.InputValue
@@ -32,11 +36,70 @@ import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
+data PlanningSt
+ = PlanningSt
+ { _psArgNumber :: !Int
+ , _psVariables :: !Plan.PlanVariables
+ , _psPrepped :: !Plan.PrepArgMap
+ }
+
+initPlanningSt :: PlanningSt
+initPlanningSt = PlanningSt 1 Map.empty IntMap.empty
+
+type PlanM =
+ StateT PlanningSt (ReaderT (FieldMap, OrdByResolveCtx) (Except QErr))
+
+runPlanM
+ :: (MonadError QErr m)
+ => (FieldMap, OrdByResolveCtx) -> PlanM Q.Query -> m Plan.RootFieldPlan
+runPlanM ctx m = do
+ (q, PlanningSt _ vars prepped) <- either throwError return $
+ runExcept $ runReaderT (runStateT m initPlanningSt) ctx
+ return $ Plan.RFPPostgres $ Plan.PGPlan q vars prepped
+
+getVarArgNum
+ :: (MonadState PlanningSt m)
+ => G.Variable -> m Int
+getVarArgNum var = do
+ PlanningSt curArgNum vars prepped <- get
+ case Map.lookup var vars of
+ Just argNum -> return argNum
+ Nothing -> do
+ put $ PlanningSt (curArgNum + 1) (Map.insert var curArgNum vars) prepped
+ return curArgNum
+
+addPrepArg
+ :: (MonadState PlanningSt m)
+ => Int -> Q.PrepArg -> m ()
+addPrepArg argNum arg = do
+ PlanningSt curArgNum vars prepped <- get
+ put $ PlanningSt curArgNum vars $ IntMap.insert argNum arg prepped
+
+getNextArgNum
+ :: (MonadState PlanningSt m)
+ => m Int
+getNextArgNum = do
+ PlanningSt curArgNum vars prepped <- get
+ put $ PlanningSt (curArgNum + 1) vars prepped
+ return curArgNum
+
+prepare2
+ :: (MonadState PlanningSt m)
+ => AnnPGVal -> m S.SQLExp
+prepare2 (varM, isNullable, colTy, colVal) = do
+ argNum <- case (varM, isNullable) of
+ (Just var, False) -> getVarArgNum var
+ _ -> getNextArgNum
+ addPrepArg argNum $ binEncoder colVal
+ return $ toPrepParam argNum colTy
+
fromSelSet
- :: G.NamedType
+ :: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
+ => (AnnPGVal -> m S.SQLExp)
+ -> G.NamedType
-> SelSet
- -> Convert [(FieldName, RS.AnnFld)]
-fromSelSet fldTy flds =
+ -> m [(FieldName, RS.AnnFld)]
+fromSelSet f fldTy flds =
forM (toList flds) $ \fld -> do
let fldName = _fName fld
let rqlFldName = FieldName $ G.unName $ G.unAlias $ _fAlias fld
@@ -48,7 +111,7 @@ fromSelSet fldTy flds =
Left colInfo -> return $ RS.FCol colInfo
Right (relInfo, tableFilter, tableLimit, _) -> do
let relTN = riRTable relInfo
- relSelData <- fromField relTN tableFilter tableLimit fld
+ relSelData <- fromField f relTN tableFilter tableLimit fld
let annRel = RS.AnnRel (riName relInfo) (riType relInfo)
(riMapping relInfo) relSelData
return $ RS.FRel annRel
@@ -57,19 +120,23 @@ fieldAsPath :: (MonadError QErr m) => Field -> m a -> m a
fieldAsPath fld = nameAsPath $ _fName fld
parseTableArgs
- :: QualifiedTable -> ArgsMap -> Convert RS.TableArgs
-parseTableArgs tn args = do
- whereExpM <- withArgM args "where" $ convertBoolExp tn
+ :: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
+ => (AnnPGVal -> m S.SQLExp)
+ -> QualifiedTable -> ArgsMap -> m RS.TableArgs
+parseTableArgs f tn args = do
+ whereExpM <- withArgM args "where" $ convertBoolExpG f tn
ordByExpM <- withArgM args "order_by" parseOrderBy
limitExpM <- withArgM args "limit" parseLimit
- offsetExpM <- withArgM args "offset" $ asPGColVal >=> prepare
+ offsetExpM <- withArgM args "offset" $ asPGColVal >=> f
return $ RS.TableArgs whereExpM ordByExpM limitExpM offsetExpM
fromField
- :: QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> Convert RS.AnnSel
-fromField tn permFilter permLimitM fld = fieldAsPath fld $ do
- tableArgs <- parseTableArgs tn args
- annFlds <- fromSelSet (_fType fld) $ _fSelSet fld
+ :: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
+ => (AnnPGVal -> m S.SQLExp)
+ -> QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> m RS.AnnSel
+fromField f tn permFilter permLimitM fld = fieldAsPath fld $ do
+ tableArgs <- parseTableArgs f tn args
+ annFlds <- fromSelSet f (_fType fld) $ _fSelSet fld
return $ RS.AnnSel annFlds tn Nothing permFilter permLimitM tableArgs
where
args = _fArguments fld
@@ -92,41 +159,48 @@ parseOrderBy
, MonadReader r m
, Has OrdByResolveCtx r
)
- => AnnGValue -> m [RS.AnnOrderByItem]
+ => AnnInpVal -> m [RS.AnnOrderByItem]
parseOrderBy v = do
enums <- withArray (const $ mapM asEnumVal) v
mapM (uncurry getEnumInfo) enums
-parseLimit :: ( MonadError QErr m ) => AnnGValue -> m Int
+parseLimit :: ( MonadError QErr m ) => AnnInpVal -> m Int
parseLimit v = do
- (_, pgColVal) <- asPGColVal v
+ (_, _, _, pgColVal) <- asPGColVal v
limit <- maybe noIntErr return $ pgColValueToInt pgColVal
-- validate int value
onlyPositiveInt limit
return limit
where
- noIntErr = throw400 Unexpected "expecting Integer value for \"limit\""
+ noIntErr = throwVE "expecting Integer value for \"limit\""
+
+-- convertSelect
+-- :: QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> PlanM RespTx
+-- convertSelect qt permFilter permLimit fld = do
+-- selData <- withPathK "selectionSet" $
+-- fromField qt permFilter permLimit fld
+-- prepArgs <- get
+-- return $ RS.selectP2 False (selData, prepArgs)
+
+convertSelect2
+ :: QualifiedTable -> S.BoolExp -> Maybe Int -> Field
+ -> PlanM Q.Query
+convertSelect2 qt permFilter permLimit fld = do
+ selData <- withPathK "selectionSet" $
+ fromField prepare2 qt permFilter permLimit fld
+ return $ Q.fromBuilder $ toSQL $ RS.mkSQLSelect False selData
fromFieldByPKey
- :: QualifiedTable -> S.BoolExp -> Field -> Convert RS.AnnSel
+ :: QualifiedTable -> S.BoolExp -> Field -> PlanM RS.AnnSel
fromFieldByPKey tn permFilter fld = fieldAsPath fld $ do
- boolExp <- pgColValToBoolExp tn $ _fArguments fld
- annFlds <- fromSelSet (_fType fld) $ _fSelSet fld
+ boolExp <- pgColValToBoolExp prepare2 tn $ _fArguments fld
+ annFlds <- fromSelSet prepare2 (_fType fld) $ _fSelSet fld
return $ RS.AnnSel annFlds tn Nothing permFilter Nothing $
RS.noTableArgs { RS._taWhere = Just boolExp}
-convertSelect
- :: QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> Convert RespTx
-convertSelect qt permFilter permLimit fld = do
- selData <- withPathK "selectionSet" $
- fromField qt permFilter permLimit fld
- prepArgs <- get
- return $ RS.selectP2 False (selData, prepArgs)
-
convertSelectByPKey
- :: QualifiedTable -> S.BoolExp -> Field -> Convert RespTx
+ :: QualifiedTable -> S.BoolExp -> Field -> PlanM Q.Query
convertSelectByPKey qt permFilter fld = do
selData <- withPathK "selectionSet" $
fromFieldByPKey qt permFilter fld
- prepArgs <- get
- return $ RS.selectP2 True (selData, prepArgs)
+ return $ Q.fromBuilder $ toSQL $ RS.mkSQLSelect True selData
diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs
index 708131f9c7be0..a84b4a8ff4e3f 100644
--- a/server/src-lib/Hasura/GraphQL/Schema.hs
+++ b/server/src-lib/Hasura/GraphQL/Schema.hs
@@ -183,7 +183,7 @@ mkCompExpInp colTy =
, map (mk $ G.toLT colScalarTy) listOps
, bool [] (map (mk $ mkScalarTy PGText) stringOps) isStringTy
, bool [] (map jsonbOpToInpVal jsonbOps) isJsonbTy
- , [InpValInfo Nothing "_is_null" $ G.TypeNamed $ G.NamedType "Boolean"]
+ , [InpValInfo Nothing "_is_null" $ G.toGT $ G.NamedType "Boolean"]
]
where
tyDesc = mconcat
@@ -253,7 +253,7 @@ mkPGColFld (PGColInfo colName colTy isNullable) =
n = G.Name $ getPGColTxt colName
ty = bool notNullTy nullTy isNullable
scalarTy = mkScalarTy colTy
- notNullTy = G.toGT $ G.toNT scalarTy
+ notNullTy = G.toNT scalarTy
nullTy = G.toGT scalarTy
-- where: table_bool_exp
@@ -297,7 +297,7 @@ mkRelFld (RelInfo rn rTy _ remTab _ isManual) isNullable = case rTy of
Map.empty
objRelTy
where
- objRelTy = bool (G.toGT $ G.toNT relTabTy) (G.toGT relTabTy) isObjRelNullable
+ objRelTy = bool (G.toNT relTabTy) (G.toGT relTabTy) isObjRelNullable
isObjRelNullable = isManual || isNullable
relTabTy = mkTableTy remTab
@@ -385,12 +385,12 @@ mkMutRespObj tn sel =
"response of any mutation on the table " <>> tn
affectedRowsFld =
ObjFldInfo (Just desc) "affected_rows" Map.empty $
- G.toGT $ G.toNT $ mkScalarTy PGInteger
+ G.toNT $ mkScalarTy PGInteger
where
desc = "number of affected rows by the mutation"
returningFld =
ObjFldInfo (Just desc) "returning" Map.empty $
- G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn
+ G.toNT $ G.toLT $ G.toNT $ mkTableTy tn
where
desc = "data of the affected rows by the mutation"
diff --git a/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs b/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs
index 37a3212a25716..a0c1d3f6f9905 100644
--- a/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs
+++ b/server/src-lib/Hasura/GraphQL/Transport/HTTP.hs
@@ -6,9 +6,9 @@ module Hasura.GraphQL.Transport.HTTP
( runGQ
) where
+import Data.Word (Word64)
import Hasura.Prelude
-import qualified Data.ByteString.Lazy as BL
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
@@ -16,25 +16,26 @@ import Hasura.GraphQL.Schema
import Hasura.GraphQL.Transport.HTTP.Protocol
import Hasura.RQL.Types
-import qualified Hasura.GraphQL.Resolve as R
-import qualified Hasura.GraphQL.Validate as VQ
+import qualified Hasura.GraphQL.Execute as E
import qualified Hasura.Server.Query as RQ
runGQ
:: (MonadIO m, MonadError QErr m)
=> Q.PGPool -> Q.TxIsolation
- -> UserInfo -> GCtxMap
- -> GraphQLRequest
- -> m BL.ByteString
-runGQ pool isoL userInfo gCtxMap req = do
- (opTy, fields) <- runReaderT (VQ.validateGQ req) gCtx
- when (opTy == G.OperationTypeSubscription) $ throw400 UnexpectedPayload
+ -> UserInfo
+ -> Word64
+ -> GCtxMap
+ -> E.QueryCache
+ -> GQLReqUnparsed
+ -> m EncJSON
+runGQ pool isoL userInfo schemaVer gCtxMap planCache req = do
+ (opTy, _, tx) <- E.reqToTx userInfo schemaVer gCtxMap planCache req
+ when (opTy == G.OperationTypeSubscription) $
+ throw400 UnexpectedPayload
"subscriptions are not supported over HTTP, use websockets instead"
- let tx = R.resolveSelSet userInfo gCtx opTy fields
resp <- liftIO (runExceptT $ runTx tx) >>= liftEither
- return $ encodeGQResp $ GQSuccess resp
+ return $ encodeGQResp $ GQSuccess $ encJToLBS resp
where
- gCtx = getGCtx (userRole userInfo) gCtxMap
runTx tx =
Q.runTx pool (isoL, Nothing) $
RQ.setHeadersTx userInfo >> tx
diff --git a/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs b/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs
index c7df6a2dc8820..f7cddf13eac49 100644
--- a/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs
+++ b/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs
@@ -1,4 +1,5 @@
{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
@@ -6,14 +7,15 @@
{-# LANGUAGE TemplateHaskell #-}
module Hasura.GraphQL.Transport.HTTP.Protocol
- ( GraphQLRequest(..)
- , GraphQLQuery(..)
+ ( GQLReq(..)
+ , GQLReqUnparsed
+ , GQLReqParsed
+ , toParsed
+ , GQLExecDoc(..)
, OperationName(..)
, VariableValues
, encodeGQErr
- , encodeJSONObject
, encodeGQResp
- , mkJSONObj
, GQResp(..)
, isExecError
) where
@@ -23,60 +25,66 @@ import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
-import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
-import qualified Data.Text.Encoding as TE
-import qualified Data.Vector as V
import qualified Language.GraphQL.Draft.Parser as G
import qualified Language.GraphQL.Draft.Syntax as G
+import Hasura.GraphQL.Utils
import Hasura.RQL.Types
-newtype GraphQLQuery
- = GraphQLQuery { unGraphQLQuery :: [G.ExecutableDefinition] }
- deriving (Show, Eq, Hashable)
+newtype GQLExecDoc
+ = GQLExecDoc { unGQLExecDoc :: [G.ExecutableDefinition] }
+ deriving (Ord, Show, Eq, Hashable)
-instance J.FromJSON GraphQLQuery where
- parseJSON = J.withText "GraphQLQuery" $ \t ->
+instance J.FromJSON GQLExecDoc where
+ parseJSON = J.withText "GQLExecDoc" $ \t ->
case G.parseExecutableDoc t of
Left _ -> fail "parsing the graphql query failed"
- Right q -> return $ GraphQLQuery $ G.getExecutableDefinitions q
+ Right q -> return $ GQLExecDoc $ G.getExecutableDefinitions q
-instance J.ToJSON GraphQLQuery where
+instance J.ToJSON GQLExecDoc where
-- TODO, add pretty printer in graphql-parser
- toJSON _ = J.String "toJSON not implemented for GraphQLQuery"
+ toJSON _ = J.String "toJSON not implemented for GQLExecDoc"
newtype OperationName
= OperationName { _unOperationName :: G.Name }
- deriving (Show, Eq, Hashable, J.ToJSON)
+ deriving (Ord, Show, Eq, Hashable, J.ToJSON)
instance J.FromJSON OperationName where
parseJSON v = OperationName . G.Name <$> J.parseJSON v
type VariableValues = Map.HashMap G.Variable J.Value
-data GraphQLRequest
- = GraphQLRequest
+data GQLReq a
+ = GQLReq
{ _grOperationName :: !(Maybe OperationName)
- , _grQuery :: !GraphQLQuery
+ , _grQuery :: !a
, _grVariables :: !(Maybe VariableValues)
} deriving (Show, Eq, Generic)
$(J.deriveJSON (J.aesonDrop 3 J.camelCase){J.omitNothingFields=True}
- ''GraphQLRequest
+ ''GQLReq
)
-instance Hashable GraphQLRequest
+instance (Hashable a) => Hashable (GQLReq a)
+
+type GQLReqUnparsed = GQLReq Text
+type GQLReqParsed = GQLReq GQLExecDoc
+
+toParsed :: (MonadError QErr m ) => GQLReqUnparsed -> m GQLReqParsed
+toParsed req = case G.parseExecutableDoc $ _grQuery req of
+ Left _ -> withPathK "query" $ throwVE "not a valid graphql query"
+ Right a -> return $ req { _grQuery = GQLExecDoc $ G.getExecutableDefinitions a }
encodeGQErr :: Bool -> QErr -> J.Value
encodeGQErr includeInternal qErr =
J.object [ "errors" J..= [encodeQErr includeInternal qErr]]
data GQResp
- = GQSuccess BL.ByteString
- | GQPreExecError [J.Value]
- | GQExecError [J.Value]
+ = GQSuccess !BL.ByteString
+ | GQPreExecError ![J.Value]
+ | GQExecError ![J.Value]
deriving (Show, Eq)
isExecError :: GQResp -> Bool
@@ -84,24 +92,11 @@ isExecError = \case
GQExecError _ -> True
_ -> False
-encodeJSONObject :: V.Vector (Text, BL.ByteString) -> BB.Builder
-encodeJSONObject xs
- | V.null xs = BB.char7 '{' <> BB.char7 '}'
- | otherwise = BB.char7 '{' <> builder' (V.unsafeHead xs) <>
- V.foldr go (BB.char7 '}') (V.unsafeTail xs)
- where
- go v b = BB.char7 ',' <> builder' v <> b
- -- builds "key":value from (key,value)
- builder' (t, v) =
- BB.char7 '"' <> TE.encodeUtf8Builder t <> BB.string7 "\":"
- <> BB.lazyByteString v
-
-encodeGQResp :: GQResp -> BL.ByteString
+encodeGQResp :: GQResp -> EncJSON
encodeGQResp gqResp =
- mkJSONObj $ case gqResp of
- GQSuccess r -> [("data", r)]
- GQPreExecError e -> [("errors", J.encode e)]
- GQExecError e -> [("data", "null"), ("errors", J.encode e)]
-
-mkJSONObj :: [(Text, BL.ByteString)] -> BL.ByteString
-mkJSONObj = BB.toLazyByteString . encodeJSONObject . V.fromList
+ encJFromAL $ case gqResp of
+ GQSuccess r -> [("data", encJFromLBS r)]
+ GQPreExecError e -> [("errors", encJFromLBS $ J.encode e)]
+ GQExecError e -> [ ("data", "null")
+ , ("errors", encJFromLBS $ J.encode e)
+ ]
diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs
index b4f6bcfe53a1e..71b7ab430daa8 100644
--- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs
+++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs
@@ -28,15 +28,15 @@ import qualified STMContainers.Map as STMMap
import Control.Concurrent (threadDelay)
import qualified Data.IORef as IORef
+import Data.Word (Word64)
-import Hasura.GraphQL.Resolve (resolveSelSet)
+import qualified Hasura.GraphQL.Execute as E
import Hasura.GraphQL.Resolve.Context (RespTx)
import qualified Hasura.GraphQL.Resolve.LiveQuery as LQ
-import Hasura.GraphQL.Schema (GCtxMap, getGCtx)
+import Hasura.GraphQL.Schema (GCtxMap)
import Hasura.GraphQL.Transport.HTTP.Protocol
import Hasura.GraphQL.Transport.WebSocket.Protocol
import qualified Hasura.GraphQL.Transport.WebSocket.Server as WS
-import Hasura.GraphQL.Validate (validateGQ)
import qualified Hasura.Logging as L
import Hasura.Prelude
import Hasura.RQL.Types
@@ -47,7 +47,7 @@ import qualified Hasura.Server.Query as RQ
-- uniquely identifies an operation
type GOperationId = (WS.WSId, OperationId)
-type TxRunner = RespTx -> IO (Either QErr BL.ByteString)
+type TxRunner = RespTx -> IO (Either QErr EncJSON)
type OperationMap
= STMMap.Map OperationId LQ.LiveQuery
@@ -117,12 +117,13 @@ instance L.ToEngineLog WSLog where
data WSServerEnv
= WSServerEnv
- { _wseLogger :: !L.Logger
- , _wseServer :: !WSServer
- , _wseRunTx :: !TxRunner
- , _wseLiveQMap :: !LiveQueryMap
- , _wseGCtxMap :: !(IORef.IORef (SchemaCache, GCtxMap))
- , _wseHManager :: !H.Manager
+ { _wseLogger :: !L.Logger
+ , _wseServer :: !WSServer
+ , _wseRunTx :: !TxRunner
+ , _wseLiveQMap :: !LiveQueryMap
+ , _wseGCtxMap :: !(IORef.IORef (Word64, SchemaCache, GCtxMap))
+ , _wseHManager :: !H.Manager
+ , _wsePlanCache :: !E.QueryCache
}
onConn :: L.Logger -> WS.OnConnH WSConnData
@@ -176,29 +177,27 @@ onStart serverEnv wsConn msg@(StartMsg opId q) = catchAndSend $ do
throwError $ SMConnErr err
-- validate and build tx
- gCtxMap <- fmap snd $ liftIO $ IORef.readIORef gCtxMapRef
- let gCtx = getGCtx (userRole userInfo) gCtxMap
- (opTy, fields) <- withExceptT preExecErr $ loggingQErr $
- runReaderT (validateGQ q) gCtx
- let qTx = RQ.setHeadersTx userInfo >>
- resolveSelSet userInfo gCtx opTy fields
+ (schemaVer, _, gCtxMap) <- liftIO $ IORef.readIORef gCtxMapRef
+ (opTy, parsedQ, qTx) <- withExceptT preExecErr $ loggingQErr $
+ E.reqToTx userInfo schemaVer gCtxMap planCache q
+
+ let txWHdrs = RQ.setHeadersTx userInfo >> qTx
case opTy of
- G.OperationTypeSubscription -> do
- let lq = LQ.LiveQuery userInfo q
- liftIO $ STM.atomically $ STMMap.insert lq opId opMap
- liftIO $ LQ.addLiveQuery runTx lqMap lq
- qTx (wsId, opId) liveQOnChange
- liftIO $ logger $ WSLog wsId $ ESubscription opId SDStarted
+ G.OperationTypeSubscription -> liftIO $ do
+ let lq = LQ.LiveQuery userInfo parsedQ
+ STM.atomically $ STMMap.insert lq opId opMap
+ LQ.addLiveQuery runTx lqMap lq txWHdrs (wsId, opId) liveQOnChange
+ logger $ WSLog wsId $ ESubscription opId SDStarted
_ -> withExceptT postExecErr $ loggingQErr $ do
- resp <- ExceptT $ runTx qTx
- sendMsg wsConn $ SMData $ DataMsg opId $ GQSuccess resp
+ resp <- ExceptT $ runTx txWHdrs
+ sendMsg wsConn $ SMData $ DataMsg opId $ GQSuccess $ encJToLBS resp
sendMsg wsConn $ SMComplete $ CompletionMsg opId
liftIO $ logger $ WSLog wsId $ EOperation opId ODCompleted
where
- (WSServerEnv (L.Logger logger) _ runTx lqMap gCtxMapRef _) = serverEnv
+ (WSServerEnv (L.Logger logger) _ runTx lqMap gCtxMapRef _ planCache) = serverEnv
wsId = WS.getWSId wsConn
(WSConnData userInfoR opMap) = WS.getData wsConn
@@ -296,12 +295,15 @@ onClose (L.Logger logger) lqMap _ wsConn = do
createWSServerEnv
:: L.Logger
- -> H.Manager -> IORef.IORef (SchemaCache, GCtxMap)
+ -> H.Manager
+ -> E.QueryCache
+ -> IORef.IORef (Word64, SchemaCache, GCtxMap)
-> TxRunner -> IO WSServerEnv
-createWSServerEnv logger httpManager gCtxMapRef runTx = do
+createWSServerEnv logger httpManager planCache gCtxMapRef runTx = do
(wsServer, lqMap) <-
STM.atomically $ (,) <$> WS.createWSServer logger <*> LQ.newLiveQueryMap
- return $ WSServerEnv logger wsServer runTx lqMap gCtxMapRef httpManager
+ return $ WSServerEnv logger wsServer runTx
+ lqMap gCtxMapRef httpManager planCache
createWSServerApp :: AuthMode -> WSServerEnv -> WS.ServerApp
createWSServerApp authMode serverEnv =
diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Protocol.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Protocol.hs
index 914f37da98e7c..4a091e2afa200 100644
--- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Protocol.hs
+++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Protocol.hs
@@ -25,6 +25,7 @@ import qualified Data.HashMap.Strict as Map
import Hasura.GraphQL.Transport.HTTP.Protocol
import Hasura.Prelude
+import Hasura.RQL.Types
newtype OperationId
= OperationId { unOperationId :: Text }
@@ -33,7 +34,7 @@ newtype OperationId
data StartMsg
= StartMsg
{ _smId :: !OperationId
- , _smPayload :: !GraphQLRequest
+ , _smPayload :: !GQLReqUnparsed
} deriving (Show, Eq)
$(J.deriveJSON (J.aesonDrop 3 J.snakeCase) ''StartMsg)
@@ -72,7 +73,7 @@ data DataMsg
= DataMsg
{ _dmId :: !OperationId
, _dmPayload :: !GQResp
- } deriving (Show, Eq)
+ }
data ErrorMsg
= ErrorMsg
@@ -95,7 +96,6 @@ data ServerMsg
| SMData !DataMsg
| SMErr !ErrorMsg
| SMComplete !CompletionMsg
- deriving (Show, Eq)
data ServerMsgType
= SMT_GQL_CONNECTION_ACK
@@ -120,7 +120,7 @@ instance J.ToJSON ServerMsgType where
encodeServerMsg :: ServerMsg -> BL.ByteString
encodeServerMsg msg =
- mkJSONObj $ case msg of
+ encJToLBS $ encJFromAL $ case msg of
SMConnAck ->
[encTy SMT_GQL_CONNECTION_ACK]
@@ -130,25 +130,25 @@ encodeServerMsg msg =
SMConnErr connErr ->
[ encTy SMT_GQL_CONNECTION_ERROR
- , ("payload", J.encode connErr)
+ , ("payload", encJFromJ connErr)
]
SMData (DataMsg opId payload) ->
[ encTy SMT_GQL_DATA
- , ("id", J.encode opId)
+ , ("id", encJFromJ opId)
, ("payload", encodeGQResp payload)
]
SMErr (ErrorMsg opId payload) ->
[ encTy SMT_GQL_ERROR
- , ("id", J.encode opId)
- , ("payload", J.encode payload)
+ , ("id", encJFromJ opId)
+ , ("payload", encJFromJ payload)
]
SMComplete compMsg ->
[ encTy SMT_GQL_COMPLETE
- , ("id", J.encode $ unCompletionMsg compMsg)
+ , ("id", encJFromJ $ unCompletionMsg compMsg)
]
where
- encTy ty = ("type", J.encode ty)
+ encTy ty = ("type", encJFromJ ty)
diff --git a/server/src-lib/Hasura/GraphQL/Utils.hs b/server/src-lib/Hasura/GraphQL/Utils.hs
index a23513c84ab81..8f33addab0273 100644
--- a/server/src-lib/Hasura/GraphQL/Utils.hs
+++ b/server/src-lib/Hasura/GraphQL/Utils.hs
@@ -18,8 +18,8 @@ module Hasura.GraphQL.Utils
, isValidName
) where
-import Hasura.RQL.Types
import Hasura.Prelude
+import Hasura.RQL.Types
import qualified Data.HashMap.Strict as Map
import qualified Data.List.NonEmpty as NE
@@ -42,14 +42,10 @@ showNamedTy nt =
getBaseTy :: G.GType -> G.NamedType
getBaseTy = \case
- G.TypeNamed n -> n
- G.TypeList lt -> getBaseTyL lt
- G.TypeNonNull nnt -> getBaseTyNN nnt
+ G.TypeNamed _ n -> n
+ G.TypeList _ lt -> getBaseTyL lt
where
getBaseTyL = getBaseTy . G.unListType
- getBaseTyNN = \case
- G.NonNullTypeList lt -> getBaseTyL lt
- G.NonNullTypeNamed n -> n
mapFromL :: (Eq k, Hashable k) => (a -> k) -> [a] -> Map.HashMap k a
mapFromL f l =
diff --git a/server/src-lib/Hasura/GraphQL/Validate.hs b/server/src-lib/Hasura/GraphQL/Validate.hs
index d9bea05c44dd4..a0afa80fda929 100644
--- a/server/src-lib/Hasura/GraphQL/Validate.hs
+++ b/server/src-lib/Hasura/GraphQL/Validate.hs
@@ -4,7 +4,7 @@
module Hasura.GraphQL.Validate
( validateGQ
- , GraphQLRequest
+ , getAnnVarVals
) where
import Data.Has
@@ -73,7 +73,7 @@ getAnnVarVals varDefsL inpVals = do
-- check that the variable is defined on input types
when (isObjTy baseTyInfo) $ throwVE $ objTyErrMsg baseTy
- let defM' = bool (defM <|> Just G.VCNull) defM $ G.isNotNull ty
+ let defM' = bool defM (defM <|> Just G.VCNull) $ G.isNullable ty
annDefM <- withPathK "defaultValue" $
mapM (validateInputValue constValueParser ty) defM'
let inpValM = Map.lookup var inpVals
@@ -105,9 +105,9 @@ validateFrag (G.FragmentDefinition n onTy dirs selSet) = do
-- {-# SCC validateGQ #-}
validateGQ
:: (MonadError QErr m, MonadReader GCtx m)
- => GraphQLRequest
- -> m (G.OperationType, SelSet)
-validateGQ (GraphQLRequest opNameM q varValsM) = do
+ => GQLReqParsed
+ -> m ([G.VariableDefinition], G.OperationType, SelSet)
+validateGQ (GQLReq opNameM q varValsM) = do
-- get the operation that needs to be evaluated
opDef <- getTypedOp opNameM selSets opDefs
@@ -121,8 +121,10 @@ validateGQ (GraphQLRequest opNameM q varValsM) = do
G.OperationTypeSubscription ->
onNothing (_gSubRoot ctx) $ throwVE "no subscriptions exist"
+ let varDefs = G._todVariableDefinitions opDef
+
-- annotate the variables of this operation
- annVarVals <- getAnnVarVals (G._todVariableDefinitions opDef) $
+ annVarVals <- getAnnVarVals varDefs $
fromMaybe Map.empty varValsM
-- annotate the fragments
@@ -136,6 +138,7 @@ validateGQ (GraphQLRequest opNameM q varValsM) = do
selSet <- flip runReaderT valCtx $ denormSelSet [] opRoot $
G._todSelectionSet opDef
- return (G._todType opDef, selSet)
+
+ return (varDefs, G._todType opDef, selSet)
where
- (selSets, opDefs, fragDefsL) = G.partitionExDefs $ unGraphQLQuery q
+ (selSets, opDefs, fragDefsL) = G.partitionExDefs $ unGQLExecDoc q
diff --git a/server/src-lib/Hasura/GraphQL/Validate/Field.hs b/server/src-lib/Hasura/GraphQL/Validate/Field.hs
index a29a9ea9a9b87..94f7aa8356b3d 100644
--- a/server/src-lib/Hasura/GraphQL/Validate/Field.hs
+++ b/server/src-lib/Hasura/GraphQL/Validate/Field.hs
@@ -48,7 +48,7 @@ data TypedOperation
, _toSelectionSet :: ![Field]
} deriving (Show, Eq)
-type ArgsMap = Map.HashMap G.Name AnnGValue
+type ArgsMap = Map.HashMap G.Name AnnInpVal
type SelSet = Seq.Seq Field
@@ -145,7 +145,7 @@ withDirectives dirs act = do
getIfArg m = do
val <- onNothing (Map.lookup "if" m) $ throw500
"missing if argument in the directive"
- case val of
+ case _aivValue val of
AGScalar _ (Just (PGValBoolean v)) -> return v
_ -> throw500 "did not find boolean scalar for if argument"
@@ -174,14 +174,14 @@ processArgs
, MonadError QErr m)
=> ParamMap
-> [G.Argument]
- -> m (Map.HashMap G.Name AnnGValue)
+ -> m ArgsMap
processArgs fldParams argsL = do
args <- onLeft (mkMapWith G._aName argsL) $ \dups ->
throwVE $ "the following arguments are defined more than once: " <>
showNames dups
- let requiredParams = Map.filter (G.isNotNull . _iviType) fldParams
+ let requiredParams = Map.filter (not . G.isNullable . _iviType) fldParams
inpArgs <- forM args $ \(G.Argument argName argVal) ->
withPathK (G.unName argName) $ do
diff --git a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs
index 13df3866ccc0f..c96906a857edc 100644
--- a/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs
+++ b/server/src-lib/Hasura/GraphQL/Validate/InputValue.hs
@@ -26,7 +26,7 @@ import Hasura.GraphQL.Validate.Types
import Hasura.RQL.Types
import Hasura.SQL.Value
-newtype P a = P { unP :: Maybe (Either AnnGValue a)}
+newtype P a = P { unP :: Maybe (Either (G.Variable, AnnInpVal) a)}
pNull :: (Monad m) => m (P a)
pNull = return $ P Nothing
@@ -37,7 +37,7 @@ pVal = return . P . Just . Right
resolveVar
:: ( MonadError QErr m
, MonadReader ValidationCtx m)
- => G.Variable -> m AnnGValue
+ => G.Variable -> m AnnInpVal
resolveVar var = do
varVals <- _vcVarVals <$> ask
-- TODO typecheck
@@ -45,24 +45,24 @@ resolveVar var = do
throwVE $ "no such variable defined in the operation: "
<> showName (G.unVariable var)
where
- typeCheck expectedTy actualTy = case (expectedTy, actualTy) of
- -- named types
- (G.TypeNamed eTy, G.TypeNamed aTy) -> eTy == aTy
- -- non null type can be expected for a null type
- (G.TypeNamed eTy, G.TypeNonNull (G.NonNullTypeNamed aTy)) -> eTy == aTy
-
- -- list types
- (G.TypeList eTy, G.TypeList aTy) ->
- typeCheck (G.unListType eTy) (G.unListType aTy)
- (G.TypeList eTy, G.TypeNonNull (G.NonNullTypeList aTy)) ->
- typeCheck (G.unListType eTy) (G.unListType aTy)
-
- -- non null types
- (G.TypeNonNull (G.NonNullTypeList eTy), G.TypeNonNull (G.NonNullTypeList aTy)) ->
- typeCheck (G.unListType eTy) (G.unListType aTy)
- (G.TypeNonNull (G.NonNullTypeNamed eTy), G.TypeNonNull (G.NonNullTypeNamed aTy)) ->
- eTy == aTy
- (_, _) -> False
+ -- typeCheck expectedTy actualTy = case (expectedTy, actualTy) of
+ -- -- named types
+ -- (G.TypeNamed eTy, G.TypeNamed aTy) -> eTy == aTy
+ -- -- non null type can be expected for a null type
+ -- (G.TypeNamed eTy, G.TypeNonNull (G.NonNullTypeNamed aTy)) -> eTy == aTy
+
+ -- -- list types
+ -- (G.TypeList eTy, G.TypeList aTy) ->
+ -- typeCheck (G.unListType eTy) (G.unListType aTy)
+ -- (G.TypeList eTy, G.TypeNonNull (G.NonNullTypeList aTy)) ->
+ -- typeCheck (G.unListType eTy) (G.unListType aTy)
+
+ -- -- non null types
+ -- (G.TypeNonNull (G.NonNullTypeList eTy), G.TypeNonNull (G.NonNullTypeList aTy)) ->
+ -- typeCheck (G.unListType eTy) (G.unListType aTy)
+ -- (G.TypeNonNull (G.NonNullTypeNamed eTy), G.TypeNonNull (G.NonNullTypeNamed aTy)) ->
+ -- eTy == aTy
+ -- (_, _) -> False
pVar
:: ( MonadError QErr m
@@ -70,7 +70,7 @@ pVar
=> G.Variable -> m (P a)
pVar var = do
annInpVal <- resolveVar var
- return . P . Just . Left $ annInpVal
+ return . P . Just . Left $ (var, annInpVal)
data InputValueParser a m
= InputValueParser
@@ -177,7 +177,7 @@ validateObject valParser tyInfo flds = do
\(fldName, inpValInfo) -> do
let fldValM = Map.lookup fldName fldMap
ty = _iviType inpValInfo
- isNotNull = G.isNotNull ty
+ isNotNull = not $ G.isNullable ty
when (isNothing fldValM && isNotNull) $ throwVE $
"field " <> G.unName fldName <> " of type " <> G.showGT ty
<> " is required, but not found"
@@ -193,8 +193,8 @@ validateNamedTypeVal
:: ( MonadReader r m, Has TypeMap r
, MonadError QErr m)
=> InputValueParser a m
- -> G.NamedType -> a -> m AnnGValue
-validateNamedTypeVal inpValParser nt val = do
+ -> (G.Nullability, G.NamedType) -> a -> m AnnInpVal
+validateNamedTypeVal inpValParser (nullability, nt) val = do
tyInfo <- getTyInfo nt
case tyInfo of
-- this should never happen
@@ -202,13 +202,13 @@ validateNamedTypeVal inpValParser nt val = do
throw500 $ "unexpected object type info for: "
<> showNamedTy nt
TIInpObj ioti ->
- withParsed (getObject inpValParser) val $
+ withParsed gType (getObject inpValParser) val $
fmap (AGObject nt) . mapM (validateObject inpValParser ioti)
TIEnum eti ->
- withParsed (getEnum inpValParser) val $
+ withParsed gType (getEnum inpValParser) val $
fmap (AGEnum nt) . mapM (validateEnum eti)
TIScalar (ScalarTyInfo _ pgColTy) ->
- withParsed (getScalar inpValParser) val $
+ withParsed gType (getScalar inpValParser) val $
fmap (AGScalar pgColTy) . mapM (validateScalar pgColTy)
where
validateEnum enumTyInfo enumVal =
@@ -217,56 +217,49 @@ validateNamedTypeVal inpValParser nt val = do
else throwVE $ "unexpected value " <>
showName (G.unEnumValue enumVal) <>
" for enum: " <> showNamedTy nt
+
validateScalar pgColTy =
runAesonParser (parsePGValue pgColTy)
+ gType = G.TypeNamed nullability nt
+
validateList
:: (MonadError QErr m, MonadReader r m, Has TypeMap r)
=> InputValueParser a m
- -> G.ListType
+ -> (G.Nullability, G.ListType)
-> a
- -> m AnnGValue
-validateList inpValParser listTy val =
- withParsed (getList inpValParser) val $ \lM -> do
+ -> m AnnInpVal
+validateList inpValParser (nullability, listTy) val =
+ withParsed ty (getList inpValParser) val $ \lM -> do
let baseTy = G.unListType listTy
AGArray listTy <$>
mapM (indexedMapM (validateInputValue inpValParser baseTy)) lM
-
-validateNonNull
- :: (MonadError QErr m, MonadReader r m, Has TypeMap r)
- => InputValueParser a m
- -> G.NonNullType
- -> a
- -> m AnnGValue
-validateNonNull inpValParser nonNullTy val = do
- parsedVal <- case nonNullTy of
- G.NonNullTypeNamed nt -> validateNamedTypeVal inpValParser nt val
- G.NonNullTypeList lt -> validateList inpValParser lt val
- when (hasNullVal parsedVal) $
- throwVE $ "unexpected null value for type: " <> G.showGT (G.TypeNonNull nonNullTy)
- return parsedVal
+ where
+ ty = G.TypeList nullability listTy
validateInputValue
:: (MonadError QErr m, MonadReader r m, Has TypeMap r)
=> InputValueParser a m
-> G.GType
-> a
- -> m AnnGValue
+ -> m AnnInpVal
validateInputValue inpValParser ty val =
case ty of
- G.TypeNamed nt -> validateNamedTypeVal inpValParser nt val
- G.TypeList lt -> validateList inpValParser lt val
- G.TypeNonNull nnt -> validateNonNull inpValParser nnt val
+ G.TypeNamed nullability nt ->
+ validateNamedTypeVal inpValParser (nullability, nt) val
+ G.TypeList nullability lt ->
+ validateList inpValParser (nullability, lt) val
withParsed
:: (Monad m)
- => (val -> m (P specificVal))
+ => G.GType
+ -> (val -> m (P specificVal))
-> val
-> (Maybe specificVal -> m AnnGValue)
- -> m AnnGValue
-withParsed valParser val fn = do
+ -> m AnnInpVal
+withParsed ty valParser val fn = do
parsedVal <- valParser val
case unP parsedVal of
- Nothing -> fn Nothing
- Just (Right a) -> fn $ Just a
- Just (Left annVal) -> return annVal
+ Nothing -> AnnInpVal ty Nothing <$> fn Nothing
+ Just (Right v) -> AnnInpVal ty Nothing <$> fn (Just v)
+ Just (Left (var, v)) -> return $ v { _aivVariable = Just var }
diff --git a/server/src-lib/Hasura/GraphQL/Validate/Types.hs b/server/src-lib/Hasura/GraphQL/Validate/Types.hs
index 2f216bf5196a4..5452bd919d23f 100644
--- a/server/src-lib/Hasura/GraphQL/Validate/Types.hs
+++ b/server/src-lib/Hasura/GraphQL/Validate/Types.hs
@@ -36,17 +36,20 @@ module Hasura.GraphQL.Validate.Types
, fromTyDefQ
, fromSchemaDocQ
, TypeMap
+ , AnnInpVal(..)
, AnnGValue(..)
, AnnGObject
, hasNullVal
, getAnnInpValKind
- , getAnnInpValTy
+ -- , getAnnInpValTy
, module Hasura.GraphQL.Utils
) where
import Hasura.Prelude
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 Data.Text as T
import qualified Language.GraphQL.Draft.Syntax as G
@@ -125,7 +128,7 @@ mkObjTyInfo descM ty flds =
typenameFld :: ObjFldInfo
typenameFld =
ObjFldInfo (Just desc) "__typename" Map.empty $
- G.toGT $ G.toNT $ G.NamedType "String"
+ G.toNT $ G.NamedType "String"
where
desc = "The name of the current Object type at runtime"
@@ -252,7 +255,7 @@ defaultDirectives =
where
mkDirective n = DirectiveInfo Nothing n args dirLocs
args = Map.singleton "if" $ InpValInfo Nothing "if" $
- G.TypeNamed $ G.NamedType $ G.Name "Boolean"
+ G.toGT $ G.NamedType $ G.Name "Boolean"
dirLocs = map G.DLExecutable
[G.EDLFIELD, G.EDLFRAGMENT_SPREAD, G.EDLINLINE_FRAGMENT]
@@ -269,15 +272,22 @@ data FragDef
type FragDefMap = Map.HashMap G.Name FragDef
type AnnVarVals =
- Map.HashMap G.Variable AnnGValue
+ Map.HashMap G.Variable AnnInpVal
-type AnnGObject = Map.HashMap G.Name AnnGValue
+data AnnInpVal
+ = AnnInpVal
+ { _aivType :: !G.GType
+ , _aivVariable :: !(Maybe G.Variable)
+ , _aivValue :: !AnnGValue
+ } deriving (Show, Eq)
+
+type AnnGObject = Map.HashMap G.Name AnnInpVal
data AnnGValue
= AGScalar !PGColType !(Maybe PGColValue)
| AGEnum !G.NamedType !(Maybe G.EnumValue)
| AGObject !G.NamedType !(Maybe AnnGObject)
- | AGArray !G.ListType !(Maybe [AnnGValue])
+ | AGArray !G.ListType !(Maybe [AnnInpVal])
deriving (Show, Eq)
instance J.ToJSON AnnGValue where
@@ -286,6 +296,9 @@ instance J.ToJSON AnnGValue where
-- J.
-- J.toJSON [J.toJSON ty, J.toJSON valM]
+$(J.deriveToJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
+ ''AnnInpVal
+ )
pgColValToAnnGVal :: PGColType -> PGColValue -> AnnGValue
pgColValToAnnGVal colTy colVal = AGScalar colTy $ Just colVal
@@ -304,9 +317,9 @@ getAnnInpValKind = \case
AGObject _ _ -> "object"
AGArray _ _ -> "array"
-getAnnInpValTy :: AnnGValue -> G.GType
-getAnnInpValTy = \case
- AGScalar pct _ -> G.TypeNamed $ G.NamedType $ G.Name $ T.pack $ show pct
- AGEnum nt _ -> G.TypeNamed nt
- AGObject nt _ -> G.TypeNamed nt
- AGArray nt _ -> G.TypeList nt
+-- getAnnInpValTy :: AnnGValue -> G.GType
+-- getAnnInpValTy = \case
+-- AGScalar pct _ -> G.TypeNamed $ G.NamedType $ G.Name $ T.pack $ show pct
+-- AGEnum nt _ -> G.TypeNamed nt
+-- AGObject nt _ -> G.TypeNamed nt
+-- AGArray nt _ -> G.TypeList nt
diff --git a/server/src-lib/Hasura/RQL/DDL/Metadata.hs b/server/src-lib/Hasura/RQL/DDL/Metadata.hs
index 7083dfe9d3a38..72137e5f1f1b3 100644
--- a/server/src-lib/Hasura/RQL/DDL/Metadata.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Metadata.hs
@@ -189,7 +189,7 @@ applyQP1 (ReplaceMetadata tables templates) = do
getDups l =
l L.\\ HS.toList (HS.fromList l)
-applyQP2 :: (UserInfoM m, P2C m) => ReplaceMetadata -> m RespBody
+applyQP2 :: (UserInfoM m, P2C m) => ReplaceMetadata -> m EncJSON
applyQP2 (ReplaceMetadata tables templates) = do
defaultSchemaCache <- liftTx $ clearMetadata >> DT.buildSchemaCache
@@ -367,7 +367,7 @@ instance HDBQuery ExportMetadata where
type Phase1Res ExportMetadata = ()
phaseOne _ = adminOnly
- phaseTwo _ _ = encode <$> liftTx fetchMetadata
+ phaseTwo _ _ = encJFromJ <$> liftTx fetchMetadata
schemaCachePolicy = SCPNoChange
@@ -409,6 +409,6 @@ instance HDBQuery DumpInternalState where
phaseOne _ = adminOnly
phaseTwo _ _ =
- encode <$> askSchemaCache
+ encJFromJ <$> askSchemaCache
schemaCachePolicy = SCPNoChange
diff --git a/server/src-lib/Hasura/RQL/DDL/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Permission.hs
index fb94fcb34b037..41b44a09e8383 100644
--- a/server/src-lib/Hasura/RQL/DDL/Permission.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Permission.hs
@@ -351,7 +351,7 @@ setPermCommentP1 (SetPermComment qt rn pt _) = do
PTUpdate -> assertPermDefined rn PAUpdate tabInfo
PTDelete -> assertPermDefined rn PADelete tabInfo
-setPermCommentP2 :: (P2C m) => SetPermComment -> m RespBody
+setPermCommentP2 :: (P2C m) => SetPermComment -> m EncJSON
setPermCommentP2 apc = do
liftTx $ setPermCommentTx apc
return successMsg
diff --git a/server/src-lib/Hasura/RQL/DDL/QueryTemplate.hs b/server/src-lib/Hasura/RQL/DDL/QueryTemplate.hs
index 190dff51c0b0c..a3b239fec341a 100644
--- a/server/src-lib/Hasura/RQL/DDL/QueryTemplate.hs
+++ b/server/src-lib/Hasura/RQL/DDL/QueryTemplate.hs
@@ -149,7 +149,7 @@ addQTemplateToCatalog (CreateQueryTemplate qtName qtDef mComment) =
createQueryTemplateP2
:: (P2C m)
- => CreateQueryTemplate -> QueryTemplateInfo -> m RespBody
+ => CreateQueryTemplate -> QueryTemplateInfo -> m EncJSON
createQueryTemplateP2 cqt qti = do
addQTemplateToCache qti
liftTx $ addQTemplateToCatalog cqt
@@ -207,7 +207,7 @@ setQueryTemplateCommentP1 (SetQueryTemplateComment qtn _) = do
adminOnly
void $ askQTemplateInfo qtn
-setQueryTemplateCommentP2 :: (P2C m) => SetQueryTemplateComment -> m RespBody
+setQueryTemplateCommentP2 :: (P2C m) => SetQueryTemplateComment -> m EncJSON
setQueryTemplateCommentP2 apc = do
liftTx $ setQueryTemplateCommentTx apc
return successMsg
diff --git a/server/src-lib/Hasura/RQL/DDL/Relationship.hs b/server/src-lib/Hasura/RQL/DDL/Relationship.hs
index fb07fe58d081c..289ec16f54800 100644
--- a/server/src-lib/Hasura/RQL/DDL/Relationship.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Relationship.hs
@@ -214,7 +214,7 @@ objRelP2 qt rd@(RelDef rn ru comment) = do
objRelP2Setup qt rd
liftTx $ persistRel qt rn ObjRel (toJSON ru) comment
-createObjRelP2 :: (P2C m) => CreateObjRel -> m RespBody
+createObjRelP2 :: (P2C m) => CreateObjRel -> m EncJSON
createObjRelP2 (WithTable qt rd) = do
objRelP2 qt rd
return successMsg
@@ -310,7 +310,7 @@ arrRelP2 qt rd@(RelDef rn u comment) = do
arrRelP2Setup qt rd
liftTx $ persistRel qt rn ArrRel (toJSON u) comment
-createArrRelP2 :: (P2C m) => CreateArrRel -> m RespBody
+createArrRelP2 :: (P2C m) => CreateArrRel -> m EncJSON
createArrRelP2 (WithTable qt rd) = do
arrRelP2 qt rd
return successMsg
@@ -351,7 +351,7 @@ purgeRelDep (SOTableObj tn (TOPerm rn pt)) =
purgeRelDep d = throw500 $ "unexpected dependency of relationship : "
<> reportSchemaObj d
-dropRelP2 :: (P2C m) => DropRel -> [SchemaObjId] -> m RespBody
+dropRelP2 :: (P2C m) => DropRel -> [SchemaObjId] -> m EncJSON
dropRelP2 (DropRel qt rn _) depObjs = do
mapM_ purgeRelDep depObjs
delFldFromCache (fromRel rn) qt
@@ -394,7 +394,7 @@ setRelCommentP1 (SetRelComment qt rn _) = do
tabInfo <- askTabInfo qt
void $ askRelType (tiFieldInfoMap tabInfo) rn ""
-setRelCommentP2 :: (P2C m) => SetRelComment -> m RespBody
+setRelCommentP2 :: (P2C m) => SetRelComment -> m EncJSON
setRelCommentP2 arc = do
liftTx $ setRelComment arc
return successMsg
diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
index ad7544c2673b9..0d90b1e20041e 100644
--- a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
@@ -111,7 +111,7 @@ trackExistingTableOrViewP2Setup tn isSystemDefined = do
addTableToCache ti
trackExistingTableOrViewP2
- :: (P2C m) => QualifiedTable -> Bool -> m RespBody
+ :: (P2C m) => QualifiedTable -> Bool -> m EncJSON
trackExistingTableOrViewP2 vn isSystemDefined = do
trackExistingTableOrViewP2Setup vn isSystemDefined
liftTx $ Q.catchE defaultTxErrorHandler $
@@ -241,7 +241,7 @@ unTrackExistingTableOrViewP1 ut@(UntrackTable vn _) = do
"view/table already untracked : " <>> vn
unTrackExistingTableOrViewP2 :: (P2C m)
- => UntrackTable -> TableInfo -> m RespBody
+ => UntrackTable -> TableInfo -> m EncJSON
unTrackExistingTableOrViewP2 (UntrackTable vn cascade) tableInfo = do
sc <- askSchemaCache
@@ -411,7 +411,7 @@ data RunSQLRes
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''RunSQLRes)
-runSqlP2 :: (P2C m) => RunSQL -> m RespBody
+runSqlP2 :: (P2C m) => RunSQL -> m EncJSON
runSqlP2 (RunSQL t cascade) = do
-- Drop hdb_views so no interference is caused to the sql query
@@ -459,7 +459,7 @@ runSqlP2 (RunSQL t cascade) = do
trid = etiId eti
liftTx $ mkTriggerQ trid trn tn cols (TriggerOpsDef insert update delete)
- return $ encode (res :: RunSQLRes)
+ return $ encJFromJ (res :: RunSQLRes)
where
rawSqlErrHandler :: Q.PGTxErr -> QErr
diff --git a/server/src-lib/Hasura/RQL/DDL/Subscribe.hs b/server/src-lib/Hasura/RQL/DDL/Subscribe.hs
index 8cff68084b8c5..2549d2a3d110f 100644
--- a/server/src-lib/Hasura/RQL/DDL/Subscribe.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Subscribe.hs
@@ -234,7 +234,7 @@ subTableP1 (CreateEventTriggerQuery name qt insert update delete retryConf webho
subTableP2 :: (P2C m) => QualifiedTable -> Bool -> EventTriggerDef -> m ()
subTableP2 qt replace q@(EventTriggerDef name def webhook rconf mheaders) = do
- allCols <- (getCols . tiFieldInfoMap) <$> askTabInfo qt
+ allCols <- getCols . tiFieldInfoMap <$> askTabInfo qt
trid <- if replace
then do
delEventTriggerFromCache qt name
@@ -245,7 +245,7 @@ subTableP2 qt replace q@(EventTriggerDef name def webhook rconf mheaders) = do
headers <- getHeadersFromConf headerConfs
addEventTriggerToCache qt trid name def rconf webhook headers
-subTableP2shim :: (P2C m) => (QualifiedTable, Bool, EventTriggerDef) -> m RespBody
+subTableP2shim :: (P2C m) => (QualifiedTable, Bool, EventTriggerDef) -> m EncJSON
subTableP2shim (qt, replace, etdef) = do
subTableP2 qt replace etdef
return successMsg
@@ -262,7 +262,7 @@ unsubTableP1 (DeleteEventTriggerQuery name) = do
ti <- askTabInfoFromTrigger name
return $ tiName ti
-unsubTableP2 :: (P2C m) => QualifiedTable -> DeleteEventTriggerQuery -> m RespBody
+unsubTableP2 :: (P2C m) => QualifiedTable -> DeleteEventTriggerQuery -> m EncJSON
unsubTableP2 qt (DeleteEventTriggerQuery name) = do
delEventTriggerFromCache qt name
liftTx $ delEventTriggerFromCatalog name
@@ -274,7 +274,7 @@ instance HDBQuery DeleteEventTriggerQuery where
phaseTwo q qt = unsubTableP2 qt q
schemaCachePolicy = SCPReload
-deliverEvent :: (P2C m) => DeliverEventQuery -> m RespBody
+deliverEvent :: (P2C m) => DeliverEventQuery -> m EncJSON
deliverEvent (DeliverEventQuery eventId) = do
_ <- liftTx $ fetchEvent eventId
liftTx $ markForDelivery eventId
diff --git a/server/src-lib/Hasura/RQL/DML/Count.hs b/server/src-lib/Hasura/RQL/DML/Count.hs
index a95a543e61dfd..af28ed7e3eb39 100644
--- a/server/src-lib/Hasura/RQL/DML/Count.hs
+++ b/server/src-lib/Hasura/RQL/DML/Count.hs
@@ -1,14 +1,15 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
module Hasura.RQL.DML.Count where
import Data.Aeson
import Instances.TH.Lift ()
-import qualified Data.ByteString.Builder as BB
+import qualified Data.Binary.Builder as BB
import qualified Data.Sequence as DS
import Hasura.Prelude
@@ -103,14 +104,14 @@ countP1 prepValBuilder (CountQuery qt mDistCols mWhere) = do
relInDistColsErr =
"Relationships can't be used in \"distinct\"."
-countP2 :: (P2C m) => (CountQueryP1, DS.Seq Q.PrepArg) -> m RespBody
+countP2 :: (P2C m) => (CountQueryP1, DS.Seq Q.PrepArg) -> m EncJSON
countP2 (u, p) = do
qRes <- liftTx $ Q.rawQE dmlTxErrorHandler (Q.fromBuilder countSQL) (toList p) True
- return $ BB.toLazyByteString $ encodeCount qRes
+ return $ encJFromB $ encodeCount qRes
where
countSQL = toSQL $ mkSQLCount u
- encodeCount (Q.SingleRow (Identity c)) =
- BB.byteString "{\"count\":" <> BB.intDec c <> BB.char7 '}'
+ encodeCount (Q.SingleRow (Identity (c::Int))) =
+ "{\"count\":" <> BB.putStringUtf8 (show c) <> "}"
instance HDBQuery CountQuery where
diff --git a/server/src-lib/Hasura/RQL/DML/Delete.hs b/server/src-lib/Hasura/RQL/DML/Delete.hs
index 9423007492dc6..9a042d925692c 100644
--- a/server/src-lib/Hasura/RQL/DML/Delete.hs
+++ b/server/src-lib/Hasura/RQL/DML/Delete.hs
@@ -85,9 +85,9 @@ convDeleteQuery prepValBuilder (DeleteQuery tableName rqlBE mRetCols) = do
convDelQ :: DeleteQuery -> P1 (DeleteQueryP1, DS.Seq Q.PrepArg)
convDelQ delQ = flip runStateT DS.empty $ convDeleteQuery binRHSBuilder delQ
-deleteP2 :: (DeleteQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
+deleteP2 :: (DeleteQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
deleteP2 (u, p) =
- runIdentity . Q.getRow
+ encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder deleteSQL) (toList p) True
where
deleteSQL = toSQL $ mkSQLDelete u
diff --git a/server/src-lib/Hasura/RQL/DML/Explain.hs b/server/src-lib/Hasura/RQL/DML/Explain.hs
index 5e6e745b8b66c..bd6c8e6caa11c 100644
--- a/server/src-lib/Hasura/RQL/DML/Explain.hs
+++ b/server/src-lib/Hasura/RQL/DML/Explain.hs
@@ -39,11 +39,11 @@ $(deriveJSON (aesonDrop 2 camelCase) ''ExplainResp)
phaseOneExplain :: SelectQuery -> P1 AnnSel
phaseOneExplain = convSelectQuery txtRHSBuilder
-phaseTwoExplain :: (P2C m) => AnnSel -> m RespBody
+phaseTwoExplain :: (P2C m) => AnnSel -> m EncJSON
phaseTwoExplain sel = do
planResp <- liftTx $ runIdentity . Q.getRow <$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder withExplain) [] True
plans <- decodeBS planResp
- return $ encode $ ExplainResp selectSQLT plans
+ return $ encJFromLBS $ encode $ ExplainResp selectSQLT plans
where
selectSQL = toSQL $ mkSQLSelect False sel
explainSQL = BB.string7 "EXPLAIN (FORMAT JSON) "
diff --git a/server/src-lib/Hasura/RQL/DML/Insert.hs b/server/src-lib/Hasura/RQL/DML/Insert.hs
index 50b1b23e24252..a6eed45b351be 100644
--- a/server/src-lib/Hasura/RQL/DML/Insert.hs
+++ b/server/src-lib/Hasura/RQL/DML/Insert.hs
@@ -137,7 +137,7 @@ buildConflictClause tableInfo inpCols (OnConflict mTCol mTCons act) =
let tableConsNames = map tcName $ tiConstraints tableInfo
withPathK "constraint" $
unless (c `elem` tableConsNames) $
- throw400 Unexpected $ "constraint " <> getConstraintTxt c
+ throw400 UnexpectedPayload $ "constraint " <> getConstraintTxt c
<<> " for table " <> tiName tableInfo
<<> " does not exist"
@@ -207,9 +207,9 @@ convInsQ insQ =
flip runStateT DS.empty $ convInsertQuery
(withPathK "objects" . decodeInsObjs) binRHSBuilder insQ
-insertP2 :: (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
+insertP2 :: (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
insertP2 (u, p) =
- runIdentity . Q.getRow
+ encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder insertSQL) (toList p) True
where
insertSQL = toSQL $ mkSQLInsert u
@@ -219,7 +219,7 @@ data ConflictCtx
| CCDoNothing !(Maybe ConstraintName)
deriving (Show, Eq)
-nonAdminInsert :: (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
+nonAdminInsert :: (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
nonAdminInsert (insQueryP1, args) = do
conflictCtxM <- mapM extractConflictCtx conflictClauseP1
setConflictCtx conflictCtxM
diff --git a/server/src-lib/Hasura/RQL/DML/QueryTemplate.hs b/server/src-lib/Hasura/RQL/DML/QueryTemplate.hs
index e6403e5afd167..9c8b2a727bc1d 100644
--- a/server/src-lib/Hasura/RQL/DML/QueryTemplate.hs
+++ b/server/src-lib/Hasura/RQL/DML/QueryTemplate.hs
@@ -10,7 +10,6 @@ module Hasura.RQL.DML.QueryTemplate where
import Hasura.Prelude
import Hasura.RQL.DDL.QueryTemplate
import Hasura.RQL.DML.Internal
-import Hasura.RQL.DML.Returning (encodeJSONVector)
import Hasura.RQL.GBoolExp (txtRHSBuilder)
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
@@ -30,10 +29,8 @@ import Data.Aeson.Types
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Lift)
-import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as M
import qualified Data.Sequence as DS
-import qualified Data.Vector as V
type TemplateArgs = M.HashMap TemplateParam Value
@@ -128,7 +125,7 @@ execQueryTemplateP1 (ExecQueryTemplate qtn args) = do
(QueryTemplateInfo _ qt _) <- askQTemplateInfo qtn
convQT args qt
-execQueryTP2 :: (P2C m) => QueryTProc -> m RespBody
+execQueryTP2 :: (P2C m) => QueryTProc -> m EncJSON
execQueryTP2 qtProc = case qtProc of
QTPInsert qp -> liftTx $ R.insertP2 qp
QTPSelect qp -> liftTx $ R.selectP2 False qp
@@ -137,8 +134,7 @@ execQueryTP2 qtProc = case qtProc of
QTPCount qp -> R.countP2 qp
QTPBulk qps -> do
respList <- mapM execQueryTP2 qps
- let bsVector = V.fromList respList
- return $ BB.toLazyByteString $ encodeJSONVector BB.lazyByteString bsVector
+ return $ encJFromL respList
instance HDBQuery ExecQueryTemplate where
diff --git a/server/src-lib/Hasura/RQL/DML/Select.hs b/server/src-lib/Hasura/RQL/DML/Select.hs
index 7a909d487e296..b99ce4a244702 100644
--- a/server/src-lib/Hasura/RQL/DML/Select.hs
+++ b/server/src-lib/Hasura/RQL/DML/Select.hs
@@ -645,10 +645,9 @@ convSelectQuery prepArgBuilder (DMLQuery qt selQ) = do
validateHeaders $ spiRequiredHeaders selPermInfo
convSelectQ (tiFieldInfoMap tabInfo) selPermInfo extSelQ prepArgBuilder
--- selectP2 :: (P2C m) => (SelectQueryP1, DS.Seq Q.PrepArg) -> m RespBody
-selectP2 :: Bool -> (AnnSel, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
+selectP2 :: Bool -> (AnnSel, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
selectP2 asSingleObject (sel, p) =
- runIdentity . Q.getRow
+ encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder selectSQL) (toList p) True
where
selectSQL = toSQL $ mkSQLSelect asSingleObject sel
diff --git a/server/src-lib/Hasura/RQL/DML/Update.hs b/server/src-lib/Hasura/RQL/DML/Update.hs
index fbf894d499769..ea10b744d913b 100644
--- a/server/src-lib/Hasura/RQL/DML/Update.hs
+++ b/server/src-lib/Hasura/RQL/DML/Update.hs
@@ -167,9 +167,9 @@ convUpdateQuery f uq = do
convUpdQ :: UpdateQuery -> P1 (UpdateQueryP1, DS.Seq Q.PrepArg)
convUpdQ updQ = flip runStateT DS.empty $ convUpdateQuery binRHSBuilder updQ
-updateP2 :: (UpdateQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
+updateP2 :: (UpdateQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
updateP2 (u, p) =
- runIdentity . Q.getRow
+ encJFromBS . runIdentity . Q.getRow
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder updateSQL) (toList p) True
where
updateSQL = toSQL $ mkSQLUpdate u
diff --git a/server/src-lib/Hasura/RQL/Types.hs b/server/src-lib/Hasura/RQL/Types.hs
index 78e8b899382c2..73a20f91a1d09 100644
--- a/server/src-lib/Hasura/RQL/Types.hs
+++ b/server/src-lib/Hasura/RQL/Types.hs
@@ -1,9 +1,11 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
module Hasura.RQL.Types
( HasSchemaCache(..)
@@ -16,9 +18,21 @@ module Hasura.RQL.Types
, P1C
, MonadTx(..)
, UserInfoM(..)
- , RespBody
, P2C
- -- , P2Res
+
+ , EncJSON(..)
+
+ , encJToLBS
+
+ , encJFromB
+ , encJFromJ
+ , encJFromC
+ , encJFromT
+ , encJFromBS
+ , encJFromLBS
+ , encJFromL
+ , encJFromAL
+
, liftP1
, runP1
, successMsg
@@ -60,9 +74,12 @@ import qualified Database.PG.Query as Q
import Data.Aeson
+import qualified Data.Binary.Builder as BB
+import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
class ProvidesFieldInfoMap r where
getFieldInfoMap :: QualifiedTable -> r -> Maybe FieldInfoMap
@@ -91,7 +108,7 @@ class HDBQuery q where
phaseOne :: q -> P1 (Phase1Res q)
-- Hit Postgres
- phaseTwo :: q -> Phase1Res q -> P2 BL.ByteString
+ phaseTwo :: q -> Phase1Res q -> P2 EncJSON
schemaCachePolicy :: SchemaCachePolicy q
@@ -107,7 +124,58 @@ schemaCachePolicyToBool SCPNoChange = False
getSchemaCachePolicy :: (HDBQuery a) => a -> SchemaCachePolicy a
getSchemaCachePolicy _ = schemaCachePolicy
-type RespBody = BL.ByteString
+-- encoded json
+newtype EncJSON
+ = EncJSON { unEncJSON :: BB.Builder }
+ deriving (Semigroup, Monoid, IsString)
+
+encJToLBS :: EncJSON -> BL.ByteString
+encJToLBS = BB.toLazyByteString . unEncJSON
+
+encJFromB :: BB.Builder -> EncJSON
+encJFromB = EncJSON
+{-# INLINE encJFromB #-}
+
+encJFromBS :: B.ByteString -> EncJSON
+encJFromBS = EncJSON . BB.fromByteString
+{-# INLINE encJFromBS #-}
+
+encJFromLBS :: BL.ByteString -> EncJSON
+encJFromLBS = EncJSON . BB.fromLazyByteString
+{-# INLINE encJFromLBS #-}
+
+encJFromJ :: ToJSON a => a -> EncJSON
+encJFromJ = encJFromLBS . encode
+{-# INLINE encJFromJ #-}
+
+encJFromC :: Char -> EncJSON
+encJFromC = EncJSON . BB.putCharUtf8
+{-# INLINE encJFromC #-}
+
+encJFromT :: Text -> EncJSON
+encJFromT = encJFromBS . TE.encodeUtf8
+{-# INLINE encJFromT #-}
+
+encJFromL :: [EncJSON] -> EncJSON
+encJFromL = \case
+ [] -> "[]"
+ x:xs -> encJFromC '['
+ <> x
+ <> foldr go (encJFromC ']') xs
+ where go v b = encJFromC ',' <> v <> b
+
+-- from association list
+encJFromAL :: [(Text, EncJSON)] -> EncJSON
+encJFromAL = \case
+ [] -> "{}"
+ x:xs -> encJFromC '{'
+ <> builder' x
+ <> foldr go (encJFromC '}') xs
+ where
+ go v b = encJFromC ',' <> builder' v <> b
+ -- builds "key":value from (key,value)
+ builder' (t, v) =
+ encJFromC '"' <> encJFromT t <> encJFromT "\":" <> v
queryModifiesSchema :: (HDBQuery q) => q -> Bool
queryModifiesSchema =
@@ -295,7 +363,7 @@ defaultTxErrorHandler txe =
let e = err500 PostgresError "postgres query error"
in e {qeInternal = Just $ toJSON txe}
-successMsg :: BL.ByteString
+successMsg :: EncJSON
successMsg = "{\"message\":\"success\"}"
type HeaderObj = M.HashMap T.Text T.Text
diff --git a/server/src-lib/Hasura/RQL/Types/Error.hs b/server/src-lib/Hasura/RQL/Types/Error.hs
index 0613006118324..70001a9463cb6 100644
--- a/server/src-lib/Hasura/RQL/Types/Error.hs
+++ b/server/src-lib/Hasura/RQL/Types/Error.hs
@@ -62,7 +62,7 @@ data Code
| ConstraintError
| PermissionError
| NotFound
- | Unexpected
+ | InternalError
| UnexpectedPayload
| NoUpdate
| AlreadyTracked
@@ -101,7 +101,7 @@ instance Show Code where
show ConstraintError = "constraint-error"
show PermissionError = "permission-error"
show NotFound = "not-found"
- show Unexpected = "unexpected"
+ show InternalError = "internal-error"
show UnexpectedPayload = "unexpected-payload"
show NoUpdate = "no-update"
show InvalidParams = "invalid-params"
@@ -196,7 +196,7 @@ throw401 :: (QErrM m) => T.Text -> m a
throw401 t = throwError $ err401 AccessDenied t
throw500 :: (QErrM m) => T.Text -> m a
-throw500 t = throwError $ err500 Unexpected t
+throw500 t = throwError $ err500 InternalError t
modifyQErr :: (QErrM m)
=> (QErr -> QErr) -> m a -> m a
diff --git a/server/src-lib/Hasura/RQL/Types/Permission.hs b/server/src-lib/Hasura/RQL/Types/Permission.hs
index a9ce05b8dbc83..ac7181957ba85 100644
--- a/server/src-lib/Hasura/RQL/Types/Permission.hs
+++ b/server/src-lib/Hasura/RQL/Types/Permission.hs
@@ -33,7 +33,7 @@ import qualified PostgreSQL.Binary.Decoding as PD
newtype RoleName
= RoleName {getRoleTxt :: T.Text}
- deriving ( Show, Eq, Hashable, FromJSONKey, ToJSONKey, FromJSON
+ deriving ( Show, Eq, Ord, Hashable, FromJSONKey, ToJSONKey, FromJSON
, ToJSON, Q.FromCol, Q.ToPrepArg, Lift)
instance DQuote RoleName where
diff --git a/server/src-lib/Hasura/Server/App.hs b/server/src-lib/Hasura/Server/App.hs
index 70b75ff23e404..c56c56277d2b7 100644
--- a/server/src-lib/Hasura/Server/App.hs
+++ b/server/src-lib/Hasura/Server/App.hs
@@ -6,6 +6,7 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
module Hasura.Server.App where
@@ -19,6 +20,7 @@ import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import Data.Time.Clock (UTCTime,
getCurrentTime)
+import Data.Word (Word64)
import Network.Wai (requestHeaders,
strictRequestBody)
import qualified Text.Mustache as M
@@ -56,6 +58,7 @@ import Hasura.Server.Version
import Hasura.SQL.Types
+import qualified Hasura.GraphQL.Execute as E
consoleTmplt :: M.Template
consoleTmplt = $(M.embedSingleTemplate "src-rsr/console.html")
@@ -77,13 +80,14 @@ mkConsoleHTML authMode =
data ServerCtx
= ServerCtx
- { scIsolation :: Q.TxIsolation
- , scPGPool :: Q.PGPool
- , scLogger :: L.Logger
- , scCacheRef :: IORef (SchemaCache, GS.GCtxMap)
- , scCacheLock :: MVar ()
- , scAuthMode :: AuthMode
- , scManager :: HTTP.Manager
+ { scIsolation :: Q.TxIsolation
+ , scPGPool :: Q.PGPool
+ , scLogger :: L.Logger
+ , scCacheRef :: CacheRef
+ , scCacheLock :: MVar ()
+ , scAuthMode :: AuthMode
+ , scManager :: HTTP.Manager
+ , scQueryCache :: E.QueryCache
}
data HandlerCtx
@@ -118,8 +122,8 @@ buildQCtx :: Handler QCtx
buildQCtx = do
scRef <- scCacheRef . hcServerCtx <$> ask
userInfo <- asks hcUser
- cache <- liftIO $ readIORef scRef
- return $ QCtx userInfo $ fst cache
+ (_, schemaCache, _) <- liftIO $ readIORef scRef
+ return $ QCtx userInfo schemaCache
logResult
:: (MonadIO m)
@@ -140,7 +144,7 @@ mkSpockAction
:: (MonadIO m)
=> (Bool -> QErr -> Value)
-> ServerCtx
- -> Handler BL.ByteString
+ -> Handler EncJSON
-> ActionT m ()
mkSpockAction qErrEncoder serverCtx handler = do
req <- request
@@ -158,9 +162,11 @@ mkSpockAction qErrEncoder serverCtx handler = do
result <- liftIO $ runReaderT (runExceptT handler) handlerState
t2 <- liftIO getCurrentTime -- for measuring response time purposes
+ let resLBS = encJToLBS <$> result
+
-- log result
- logResult req reqBody serverCtx result $ Just (t1, t2)
- either (qErrToResp $ userRole userInfo == adminRole) resToResp result
+ logResult req reqBody serverCtx resLBS $ Just (t1, t2)
+ either (qErrToResp $ userRole userInfo == adminRole) resToResp resLBS
where
logger = L.unLogger $ scLogger serverCtx
@@ -205,37 +211,42 @@ withLock lk action = do
-- headers = M.toList $ rqleHeaders expQuery
-- userInfo = UserInfo role headers
-v1QueryHandler :: RQLQuery -> Handler BL.ByteString
+v1QueryHandler :: RQLQuery -> Handler EncJSON
v1QueryHandler query = do
lk <- scCacheLock . hcServerCtx <$> ask
- bool (fst <$> dbAction) (withLock lk dbActionReload) $
+ bool (s3 <$> dbAction) (withLock lk dbActionReload) $
queryNeedsReload query
where
+ s3 (_, b, _) = b
-- Hit postgres
dbAction = do
userInfo <- asks hcUser
scRef <- scCacheRef . hcServerCtx <$> ask
- schemaCache <- liftIO $ readIORef scRef
+ (curVersion, schemaCache, _) <- liftIO $ readIORef scRef
pool <- scPGPool . hcServerCtx <$> ask
isoL <- scIsolation . hcServerCtx <$> ask
- runQuery pool isoL userInfo (fst schemaCache) query
+ (resp, newSc) <- runQuery pool isoL userInfo schemaCache query
+ return (curVersion, resp, newSc)
-- Also update the schema cache
dbActionReload = do
- (resp, newSc) <- dbAction
+ (curVersion, resp, newSc) <- dbAction
newGCtxMap <- GS.mkGCtxMap $ scTables newSc
scRef <- scCacheRef . hcServerCtx <$> ask
- liftIO $ writeIORef scRef (newSc, newGCtxMap)
+ liftIO $ writeIORef scRef (curVersion + 1, newSc, newGCtxMap)
+ queryCache <- scQueryCache . hcServerCtx <$> ask
+ liftIO $ E.clearQueryCache queryCache
return resp
-v1Alpha1GQHandler :: GH.GraphQLRequest -> Handler BL.ByteString
+v1Alpha1GQHandler :: GH.GQLReqUnparsed -> Handler EncJSON
v1Alpha1GQHandler query = do
userInfo <- asks hcUser
scRef <- scCacheRef . hcServerCtx <$> ask
- cache <- liftIO $ readIORef scRef
+ (schemaVer, _, gqlCache) <- liftIO $ readIORef scRef
pool <- scPGPool . hcServerCtx <$> ask
isoL <- scIsolation . hcServerCtx <$> ask
- GH.runGQ pool isoL userInfo (snd cache) query
+ planCache <- scQueryCache . hcServerCtx <$> ask
+ GH.runGQ pool isoL userInfo schemaVer gqlCache planCache query
-- v1Alpha1GQSchemaHandler :: Handler BL.ByteString
-- v1Alpha1GQSchemaHandler = do
@@ -264,7 +275,7 @@ queryParsers =
q <- decodeValue val
return $ f q
-legacyQueryHandler :: TableName -> T.Text -> Handler BL.ByteString
+legacyQueryHandler :: TableName -> T.Text -> Handler EncJSON
legacyQueryHandler tn queryType =
case M.lookup queryType queryParsers of
Just queryParser -> getQueryParser queryParser qt >>= v1QueryHandler
@@ -272,36 +283,41 @@ legacyQueryHandler tn queryType =
where
qt = QualifiedTable publicSchema tn
+type CacheRef = IORef (Word64, SchemaCache, GS.GCtxMap)
+
mkWaiApp
:: Q.TxIsolation
-> Maybe String
-> L.LoggerCtx
-> Q.PGPool
-> HTTP.Manager
+ -> E.QueryCache
-> AuthMode
-> CorsConfig
-> Bool
- -> IO (Wai.Application, IORef (SchemaCache, GS.GCtxMap))
-mkWaiApp isoLevel mRootDir loggerCtx pool httpManager mode corsCfg enableConsole = do
+ -> IO (Wai.Application, CacheRef)
+mkWaiApp isoLevel mRootDir loggerCtx pool httpManager planCache mode corsCfg enableConsole = do
cacheRef <- do
pgResp <- liftIO $ runExceptT $ Q.runTx pool (Q.Serializable, Nothing) $ do
Q.catchE defaultTxErrorHandler initStateTx
sc <- buildSchemaCache
- (,) sc <$> GS.mkGCtxMap (scTables sc)
+ (0, sc,) <$> GS.mkGCtxMap (scTables sc)
either initErrExit return pgResp >>= newIORef
cacheLock <- newMVar ()
let serverCtx =
ServerCtx isoLevel pool (L.mkLogger loggerCtx) cacheRef
- cacheLock mode httpManager
+ cacheLock mode httpManager planCache
spockApp <- spockAsApp $ spockT id $
httpApp mRootDir corsCfg serverCtx enableConsole
let runTx tx = runExceptT $ Q.runTx pool (isoLevel, Nothing) tx
- wsServerEnv <- WS.createWSServerEnv (scLogger serverCtx) httpManager cacheRef runTx
+ wsServerEnv <- WS.createWSServerEnv (scLogger serverCtx) httpManager
+ planCache cacheRef runTx
+
let wsServerApp = WS.createWSServerApp mode wsServerEnv
return (WS.websocketsOr WS.defaultConnectionOptions wsServerApp spockApp, cacheRef)
diff --git a/server/src-lib/Hasura/Server/Query.hs b/server/src-lib/Hasura/Server/Query.hs
index 79df2b936670f..09de7f6a002af 100644
--- a/server/src-lib/Hasura/Server/Query.hs
+++ b/server/src-lib/Hasura/Server/Query.hs
@@ -11,11 +11,9 @@ import Data.Aeson.TH
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.ByteString.Builder as BB
-import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Text as T
-import qualified Data.Vector as V
import Hasura.Prelude
import Hasura.RQL.DDL.Metadata
@@ -25,27 +23,12 @@ import Hasura.RQL.DDL.Relationship
import Hasura.RQL.DDL.Schema.Table
import Hasura.RQL.DML.Explain
import Hasura.RQL.DML.QueryTemplate
-import Hasura.RQL.DML.Returning (encodeJSONVector)
import Hasura.RQL.Types
import Hasura.Server.Utils
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
--- data QueryWithTxId
--- = QueryWithTxId
--- { qtTxId :: !(Maybe TxId)
--- , qtQuery :: !RQLQuery
--- } deriving (Show, Eq)
-
--- instance FromJSON QueryWithTxId where
--- parseJSON v@(Object o) =
--- QueryWithTxId
--- <$> o .:! "transaction_id"
--- <*> parseJSON v
--- parseJSON _ =
--- fail "expecting on object for query"
-
data RQLQuery
= RQAddExistingTableOrView !TrackTable
| RQTrackTable !TrackTable
@@ -105,7 +88,7 @@ buildTx
=> UserInfo
-> SchemaCache
-> q
- -> Either QErr (Q.TxE QErr (BL.ByteString, SchemaCache))
+ -> Either QErr (Q.TxE QErr (EncJSON, SchemaCache))
buildTx userInfo sc q = do
p1Res <- withPathK "args" $ runP1 qEnv $ phaseOne q
return $ flip runReaderT (qcUserInfo qEnv) $
@@ -117,7 +100,7 @@ runQuery
:: (MonadIO m, MonadError QErr m)
=> Q.PGPool -> Q.TxIsolation
-> UserInfo -> SchemaCache
- -> RQLQuery -> m (BL.ByteString, SchemaCache)
+ -> RQLQuery -> m (EncJSON, SchemaCache)
runQuery pool isoL userInfo sc query = do
tx <- liftEither $ buildTxAny userInfo sc query
res <- liftIO $ runExceptT $ Q.runTx pool (isoL, Nothing) $
@@ -128,7 +111,7 @@ buildExplainTx
:: UserInfo
-> SchemaCache
-> SelectQuery
- -> Either QErr (Q.TxE QErr BL.ByteString)
+ -> Either QErr (Q.TxE QErr EncJSON)
buildExplainTx userInfo sc q = do
p1Res <- withPathK "query" $ runP1 qEnv $ phaseOneExplain q
res <- return $ flip runReaderT (qcUserInfo qEnv) $
@@ -140,7 +123,7 @@ buildExplainTx userInfo sc q = do
runExplainQuery
:: Q.PGPool -> Q.TxIsolation
-> UserInfo -> SchemaCache
- -> SelectQuery -> ExceptT QErr IO BL.ByteString
+ -> SelectQuery -> ExceptT QErr IO EncJSON
runExplainQuery pool isoL userInfo sc query = do
tx <- liftEither $ buildExplainTx userInfo sc query
Q.runTx pool (isoL, Nothing) $ setHeadersTx userInfo >> tx
@@ -196,7 +179,7 @@ queryNeedsReload qi = case qi of
buildTxAny :: UserInfo
-> SchemaCache
-> RQLQuery
- -> Either QErr (Q.TxE QErr (BL.ByteString, SchemaCache))
+ -> Either QErr (Q.TxE QErr (EncJSON, SchemaCache))
buildTxAny userInfo sc rq = case rq of
RQAddExistingTableOrView q -> buildTx userInfo sc q
RQTrackTable q -> buildTx userInfo sc q
@@ -250,8 +233,7 @@ buildTxAny userInfo sc rq = case rq of
in
return $ withPathK "args" $ do
(respList, finalSc) <- indexedFoldM f (Seq.empty, sc) qs
- let bsVector = V.fromList $ toList respList
- return ( BB.toLazyByteString $ encodeJSONVector BB.lazyByteString bsVector
+ return ( encJFromL $ toList respList
, finalSc
)
diff --git a/server/stack.yaml b/server/stack.yaml
index 4b4e3f36afda6..3cb710f1080d6 100644
--- a/server/stack.yaml
+++ b/server/stack.yaml
@@ -2,23 +2,23 @@
# 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-12.4
+resolver: lts-12.7
# Local packages, usually specified by relative directory name
packages:
- '.'
# - '../../graphql-parser-hs'
+# - '../../pg-client-hs'
# - extra-libs/aeson
# - extra-libs/logger/wai-logger
# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
extra-deps:
# use https URLs so that build systems can clone these repos
-# - graphql-api-0.3.0
- git: https://github.com/hasura/pg-client-hs.git
commit: 7978e04f24790f18f06a67fe6065f470abc1c764
- git: https://github.com/hasura/graphql-parser-hs.git
- commit: eae59812ec537b3756c3ddb5f59a7cc59508869b
+ commit: 076d3894cd3977aea8119c9131ccc44b0b19f63e
- git: https://github.com/tdammers/ginger.git
commit: 435c2774963050da04ce9a3369755beac87fbb16
- Spock-core-0.13.0.0
diff --git a/server/test/Main.hs b/server/test/Main.hs
index 02b51db6ff134..c8cfb2ec45d9c 100644
--- a/server/test/Main.hs
+++ b/server/test/Main.hs
@@ -15,6 +15,7 @@ import qualified Data.Aeson as J
import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Database.PG.Query as Q
+import Hasura.GraphQL.Execute as GE
import qualified Hasura.Logging as L
import Hasura.Prelude
import Hasura.Server.App (mkWaiApp)
@@ -45,11 +46,11 @@ ravenApp :: L.LoggerCtx -> PGQ.PGPool -> IO Application
ravenApp loggerCtx pool = do
let corsCfg = CorsConfigG "*" False -- cors is enabled
httpManager <- HTTP.newManager HTTP.tlsManagerSettings
+ planCache <- GE.initQueryCache
-- spockAsApp $ spockT id $ app Q.Serializable Nothing rlogger pool AMNoAuth corsCfg True -- no access key and no webhook
- (app, _) <- mkWaiApp Q.Serializable Nothing loggerCtx pool httpManager AMNoAuth corsCfg True -- no access key and no webhook
+ (app, _) <- mkWaiApp Q.Serializable Nothing loggerCtx pool httpManager planCache AMNoAuth corsCfg True -- no access key and no webhook
return app
-
main :: IO ()
main = do
-- parse CLI flags for connection params