2021-02-12 06:04:09 +03:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
module Hasura.Backends.Postgres.Instances.Execute () where
|
2021-02-12 06:04:09 +03:00
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
|
2021-04-13 14:10:08 +03:00
|
|
|
import qualified Control.Monad.Trans.Control as MT
|
2021-02-20 16:45:49 +03:00
|
|
|
import qualified Data.Environment as Env
|
|
|
|
import qualified Data.HashSet as Set
|
|
|
|
import qualified Data.Sequence as Seq
|
2021-04-13 14:10:08 +03:00
|
|
|
import qualified Database.PG.Query as Q
|
2021-02-20 16:45:49 +03:00
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import qualified Network.HTTP.Client as HTTP
|
|
|
|
import qualified Network.HTTP.Types as HTTP
|
|
|
|
|
|
|
|
import qualified Hasura.Backends.Postgres.Execute.LiveQuery as PGL
|
|
|
|
import qualified Hasura.Backends.Postgres.Execute.Mutation as PGE
|
|
|
|
import qualified Hasura.RQL.IR.Delete as IR
|
|
|
|
import qualified Hasura.RQL.IR.Insert as IR
|
|
|
|
import qualified Hasura.RQL.IR.Returning as IR
|
|
|
|
import qualified Hasura.RQL.IR.Select as IR
|
|
|
|
import qualified Hasura.RQL.IR.Update as IR
|
2021-03-15 16:02:58 +03:00
|
|
|
import qualified Hasura.SQL.AnyBackend as AB
|
2021-02-20 16:45:49 +03:00
|
|
|
import qualified Hasura.Tracing as Tracing
|
2021-02-12 06:04:09 +03:00
|
|
|
|
|
|
|
import Hasura.Backends.Postgres.Connection
|
|
|
|
import Hasura.EncJSON
|
|
|
|
import Hasura.GraphQL.Context
|
|
|
|
import Hasura.GraphQL.Execute.Backend
|
|
|
|
import Hasura.GraphQL.Execute.Common
|
|
|
|
import Hasura.GraphQL.Execute.Insert
|
2021-02-20 16:45:49 +03:00
|
|
|
import Hasura.GraphQL.Execute.LiveQuery.Plan
|
2021-02-12 06:04:09 +03:00
|
|
|
import Hasura.GraphQL.Execute.Prepare
|
|
|
|
import Hasura.GraphQL.Parser
|
2021-04-13 14:10:08 +03:00
|
|
|
import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
|
2021-02-12 06:04:09 +03:00
|
|
|
import Hasura.RQL.Types
|
2021-02-20 16:45:49 +03:00
|
|
|
import Hasura.Server.Version (HasVersion)
|
2021-02-12 06:04:09 +03:00
|
|
|
import Hasura.Session
|
|
|
|
|
|
|
|
|
2021-02-20 16:45:49 +03:00
|
|
|
instance BackendExecute 'Postgres where
|
|
|
|
type PreparedQuery 'Postgres = PreparedSql
|
|
|
|
type MultiplexedQuery 'Postgres = PGL.MultiplexedQuery
|
|
|
|
type ExecutionMonad 'Postgres = Tracing.TraceT (LazyTxT QErr IO)
|
2021-02-12 06:04:09 +03:00
|
|
|
getRemoteJoins = concatMap (toList . snd) . maybe [] toList . _psRemoteJoins
|
|
|
|
|
|
|
|
mkDBQueryPlan = pgDBQueryPlan
|
|
|
|
mkDBMutationPlan = pgDBMutationPlan
|
2021-02-20 16:45:49 +03:00
|
|
|
mkDBSubscriptionPlan = pgDBSubscriptionPlan
|
2021-04-13 14:10:08 +03:00
|
|
|
mkDBQueryExplain = pgDBQueryExplain
|
|
|
|
mkLiveQueryExplain = pgDBLiveQueryExplain
|
2021-02-12 06:04:09 +03:00
|
|
|
|
|
|
|
|
|
|
|
-- query
|
|
|
|
|
|
|
|
pgDBQueryPlan
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m .
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> UserInfo
|
|
|
|
-> [G.Directive G.Name]
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-12 06:04:09 +03:00
|
|
|
-> SourceConfig 'Postgres
|
|
|
|
-> QueryDB 'Postgres (UnpreparedValue 'Postgres)
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m ExecutionStep
|
2021-04-01 23:40:31 +03:00
|
|
|
pgDBQueryPlan env manager reqHeaders userInfo _directives sourceName sourceConfig qrf = do
|
2021-02-12 06:04:09 +03:00
|
|
|
(preparedQuery, PlanningSt _ _ planVals expectedVariables) <- flip runStateT initPlanningSt $ traverseQueryDB prepareWithPlan qrf
|
|
|
|
validateSessionVariables expectedVariables $ _uiSession userInfo
|
|
|
|
let (action, preparedSQL) = mkCurPlanTx env manager reqHeaders userInfo $ irToRootFieldPlan planVals preparedQuery
|
2021-03-15 16:02:58 +03:00
|
|
|
pure
|
|
|
|
$ ExecStepDB []
|
|
|
|
. AB.mkAnyBackend
|
2021-04-01 23:40:31 +03:00
|
|
|
$ DBStepInfo sourceName sourceConfig preparedSQL action
|
2021-02-12 06:04:09 +03:00
|
|
|
|
2021-04-13 14:10:08 +03:00
|
|
|
pgDBQueryExplain
|
|
|
|
:: forall m
|
|
|
|
. ( MonadError QErr m
|
|
|
|
)
|
|
|
|
=> G.Name
|
|
|
|
-> UserInfo
|
|
|
|
-> SourceName
|
|
|
|
-> SourceConfig 'Postgres
|
|
|
|
-> QueryDB 'Postgres (UnpreparedValue 'Postgres)
|
|
|
|
-> m (AB.AnyBackend DBStepInfo)
|
|
|
|
pgDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
|
|
|
|
preparedQuery <- traverseQueryDB (resolveUnpreparedValue userInfo) qrf
|
|
|
|
let PreparedSql querySQL _ remoteJoins = irToRootFieldPlan mempty preparedQuery
|
|
|
|
textSQL = Q.getQueryText querySQL
|
|
|
|
-- CAREFUL!: an `EXPLAIN ANALYZE` here would actually *execute* this
|
|
|
|
-- query, maybe resulting in privilege escalation:
|
|
|
|
withExplain = "EXPLAIN (FORMAT TEXT) " <> textSQL
|
|
|
|
-- Reject if query contains any remote joins
|
|
|
|
when (remoteJoins /= mempty) $
|
|
|
|
throw400 NotSupported "Remote relationships are not allowed in explain query"
|
|
|
|
let action = liftTx $
|
|
|
|
Q.listQE dmlTxErrorHandler (Q.fromText withExplain) () True <&> \planList ->
|
|
|
|
encJFromJValue $ ExplainPlan fieldName (Just textSQL) (Just $ map runIdentity planList)
|
|
|
|
pure
|
|
|
|
$ AB.mkAnyBackend
|
|
|
|
$ DBStepInfo sourceName sourceConfig Nothing action
|
|
|
|
|
|
|
|
pgDBLiveQueryExplain
|
|
|
|
:: ( MonadError QErr m
|
|
|
|
, MonadIO m
|
|
|
|
, MT.MonadBaseControl IO m
|
|
|
|
)
|
|
|
|
=> LiveQueryPlan 'Postgres (MultiplexedQuery 'Postgres) -> m LiveQueryPlanExplanation
|
|
|
|
pgDBLiveQueryExplain plan = do
|
|
|
|
let parameterizedPlan = _lqpParameterizedPlan plan
|
|
|
|
pgExecCtx = _pscExecCtx $ _lqpSourceConfig plan
|
|
|
|
queryText = Q.getQueryText . PGL.unMultiplexedQuery $ _plqpQuery parameterizedPlan
|
|
|
|
-- CAREFUL!: an `EXPLAIN ANALYZE` here would actually *execute* this
|
|
|
|
-- query, maybe resulting in privilege escalation:
|
|
|
|
explainQuery = Q.fromText $ "EXPLAIN (FORMAT TEXT) " <> queryText
|
|
|
|
cohortId <- newCohortId
|
|
|
|
explanationLines <- liftEitherM $ runExceptT $ runLazyTx pgExecCtx Q.ReadOnly $
|
|
|
|
map runIdentity <$> PGL.executeQuery explainQuery [(cohortId, _lqpVariables plan)]
|
|
|
|
pure $ LiveQueryPlanExplanation queryText explanationLines $ _lqpVariables plan
|
2021-02-12 06:04:09 +03:00
|
|
|
|
|
|
|
-- mutation
|
|
|
|
|
|
|
|
convertDelete
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m .
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> SessionVariables
|
|
|
|
-> PGE.MutationRemoteJoinCtx
|
|
|
|
-> IR.AnnDelG 'Postgres (UnpreparedValue 'Postgres)
|
|
|
|
-> Bool
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m (Tracing.TraceT (LazyTxT QErr IO) EncJSON)
|
2021-02-12 06:04:09 +03:00
|
|
|
convertDelete env usrVars remoteJoinCtx deleteOperation stringifyNum = do
|
|
|
|
let (preparedDelete, expectedVariables) = flip runState Set.empty $ IR.traverseAnnDel prepareWithoutPlan deleteOperation
|
|
|
|
validateSessionVariables expectedVariables usrVars
|
|
|
|
pure $ PGE.execDeleteQuery env stringifyNum (Just remoteJoinCtx) (preparedDelete, Seq.empty)
|
|
|
|
|
|
|
|
convertUpdate
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m.
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> SessionVariables
|
|
|
|
-> PGE.MutationRemoteJoinCtx
|
|
|
|
-> IR.AnnUpdG 'Postgres (UnpreparedValue 'Postgres)
|
|
|
|
-> Bool
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m (Tracing.TraceT (LazyTxT QErr IO) EncJSON)
|
2021-02-12 06:04:09 +03:00
|
|
|
convertUpdate env usrVars remoteJoinCtx updateOperation stringifyNum = do
|
|
|
|
let (preparedUpdate, expectedVariables) = flip runState Set.empty $ IR.traverseAnnUpd prepareWithoutPlan updateOperation
|
|
|
|
if null $ IR.uqp1OpExps updateOperation
|
|
|
|
then pure $ pure $ IR.buildEmptyMutResp $ IR.uqp1Output preparedUpdate
|
|
|
|
else do
|
|
|
|
validateSessionVariables expectedVariables usrVars
|
|
|
|
pure $ PGE.execUpdateQuery env stringifyNum (Just remoteJoinCtx) (preparedUpdate, Seq.empty)
|
|
|
|
|
|
|
|
convertInsert
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m.
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> SessionVariables
|
|
|
|
-> PGE.MutationRemoteJoinCtx
|
|
|
|
-> IR.AnnInsert 'Postgres (UnpreparedValue 'Postgres)
|
|
|
|
-> Bool
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m (Tracing.TraceT (LazyTxT QErr IO) EncJSON)
|
2021-02-12 06:04:09 +03:00
|
|
|
convertInsert env usrVars remoteJoinCtx insertOperation stringifyNum = do
|
|
|
|
let (preparedInsert, expectedVariables) = flip runState Set.empty $ traverseAnnInsert prepareWithoutPlan insertOperation
|
|
|
|
validateSessionVariables expectedVariables usrVars
|
|
|
|
pure $ convertToSQLTransaction env preparedInsert remoteJoinCtx Seq.empty stringifyNum
|
|
|
|
|
|
|
|
-- | A pared-down version of 'Query.convertQuerySelSet', for use in execution of
|
|
|
|
-- special case of SQL function mutations (see 'MDBFunction').
|
|
|
|
convertFunction
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m.
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> UserInfo
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> HTTP.RequestHeaders
|
|
|
|
-> JsonAggSelect
|
|
|
|
-> IR.AnnSimpleSelG 'Postgres (UnpreparedValue 'Postgres)
|
|
|
|
-- ^ VOLATILE function as 'SelectExp'
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m (Tracing.TraceT (LazyTxT QErr IO) EncJSON)
|
2021-02-12 06:04:09 +03:00
|
|
|
convertFunction env userInfo manager reqHeaders jsonAggSelect unpreparedQuery = do
|
|
|
|
-- Transform the RQL AST into a prepared SQL query
|
|
|
|
(preparedQuery, PlanningSt _ _ planVals expectedVariables)
|
|
|
|
<- flip runStateT initPlanningSt
|
|
|
|
$ IR.traverseAnnSimpleSelect prepareWithPlan unpreparedQuery
|
|
|
|
validateSessionVariables expectedVariables $ _uiSession userInfo
|
|
|
|
let queryResultFn =
|
|
|
|
case jsonAggSelect of
|
|
|
|
JASMultipleRows -> QDBMultipleRows
|
|
|
|
JASSingleObject -> QDBSingleRow
|
|
|
|
pure $!
|
|
|
|
fst $ -- forget (Maybe PreparedSql)
|
|
|
|
mkCurPlanTx env manager reqHeaders userInfo $
|
|
|
|
irToRootFieldPlan planVals $ queryResultFn preparedQuery
|
|
|
|
|
|
|
|
pgDBMutationPlan
|
2021-02-20 16:45:49 +03:00
|
|
|
:: forall m.
|
2021-02-12 06:04:09 +03:00
|
|
|
( MonadError QErr m
|
|
|
|
, HasVersion
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> UserInfo
|
|
|
|
-> Bool
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-12 06:04:09 +03:00
|
|
|
-> SourceConfig 'Postgres
|
|
|
|
-> MutationDB 'Postgres (UnpreparedValue 'Postgres)
|
2021-02-20 16:45:49 +03:00
|
|
|
-> m ExecutionStep
|
2021-04-01 23:40:31 +03:00
|
|
|
pgDBMutationPlan env manager reqHeaders userInfo stringifyNum sourceName sourceConfig mrf =
|
2021-03-15 16:02:58 +03:00
|
|
|
go <$> case mrf of
|
2021-02-12 06:04:09 +03:00
|
|
|
MDBInsert s -> convertInsert env userSession remoteJoinCtx s stringifyNum
|
|
|
|
MDBUpdate s -> convertUpdate env userSession remoteJoinCtx s stringifyNum
|
|
|
|
MDBDelete s -> convertDelete env userSession remoteJoinCtx s stringifyNum
|
|
|
|
MDBFunction returnsSet s -> convertFunction env userInfo manager reqHeaders returnsSet s
|
|
|
|
where
|
|
|
|
userSession = _uiSession userInfo
|
|
|
|
remoteJoinCtx = (manager, reqHeaders, userInfo)
|
2021-04-01 23:40:31 +03:00
|
|
|
go = ExecStepDB [] . AB.mkAnyBackend . DBStepInfo sourceName sourceConfig Nothing
|
|
|
|
|
2021-02-20 16:45:49 +03:00
|
|
|
|
2021-04-08 11:25:11 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
-- subscription
|
2021-02-20 16:45:49 +03:00
|
|
|
|
|
|
|
pgDBSubscriptionPlan
|
2021-02-23 20:37:27 +03:00
|
|
|
:: forall m.
|
|
|
|
( MonadError QErr m
|
|
|
|
, MonadIO m
|
|
|
|
)
|
2021-02-20 16:45:49 +03:00
|
|
|
=> UserInfo
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-20 16:45:49 +03:00
|
|
|
-> SourceConfig 'Postgres
|
|
|
|
-> InsOrdHashMap G.Name (QueryDB 'Postgres (UnpreparedValue 'Postgres))
|
|
|
|
-> m (LiveQueryPlan 'Postgres (MultiplexedQuery 'Postgres))
|
2021-04-01 23:40:31 +03:00
|
|
|
pgDBSubscriptionPlan userInfo _sourceName sourceConfig unpreparedAST = do
|
2021-02-20 16:45:49 +03:00
|
|
|
(preparedAST, PGL.QueryParametersInfo{..}) <- flip runStateT mempty $
|
|
|
|
for unpreparedAST $ traverseQueryDB PGL.resolveMultiplexedValue
|
|
|
|
let multiplexedQuery = PGL.mkMultiplexedQuery preparedAST
|
|
|
|
roleName = _uiRole userInfo
|
|
|
|
parameterizedPlan = ParameterizedLiveQueryPlan roleName multiplexedQuery
|
|
|
|
|
|
|
|
-- We need to ensure that the values provided for variables are correct according to Postgres.
|
|
|
|
-- Without this check an invalid value for a variable for one instance of the subscription will
|
|
|
|
-- take down the entire multiplexed query.
|
|
|
|
validatedQueryVars <- PGL.validateVariables (_pscExecCtx sourceConfig) _qpiReusableVariableValues
|
|
|
|
validatedSyntheticVars <- PGL.validateVariables (_pscExecCtx sourceConfig) $ toList _qpiSyntheticVariableValues
|
|
|
|
|
|
|
|
-- TODO validatedQueryVars validatedSyntheticVars
|
|
|
|
let cohortVariables = mkCohortVariables
|
|
|
|
_qpiReferencedSessionVariables
|
|
|
|
(_uiSession userInfo)
|
|
|
|
validatedQueryVars
|
|
|
|
validatedSyntheticVars
|
|
|
|
|
|
|
|
pure $ LiveQueryPlan parameterizedPlan sourceConfig cohortVariables
|