2021-02-23 20:37:27 +03:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
|
2021-05-21 14:37:34 +03:00
|
|
|
module Hasura.Backends.MSSQL.Instances.Execute
|
|
|
|
(
|
|
|
|
MultiplexedQuery'(..),
|
|
|
|
PreparedQuery'(..),
|
|
|
|
multiplexRootReselect,
|
|
|
|
queryEnvJson
|
|
|
|
)
|
|
|
|
where
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
|
2021-04-20 19:57:14 +03:00
|
|
|
import qualified Data.Aeson.Extended as J
|
2021-02-23 20:37:27 +03:00
|
|
|
import qualified Data.Environment as Env
|
2021-05-21 14:37:34 +03:00
|
|
|
import qualified Data.HashSet as Set
|
2021-04-20 19:57:14 +03:00
|
|
|
import qualified Data.List.NonEmpty as NE
|
|
|
|
import qualified Data.Text.Extended as T
|
2021-02-23 20:37:27 +03:00
|
|
|
import qualified Database.ODBC.SQLServer as ODBC
|
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import qualified Network.HTTP.Client as HTTP
|
|
|
|
import qualified Network.HTTP.Types as HTTP
|
|
|
|
|
2021-03-15 16:02:58 +03:00
|
|
|
import qualified Hasura.SQL.AnyBackend as AB
|
|
|
|
|
2021-02-25 21:15:55 +03:00
|
|
|
import Hasura.Backends.MSSQL.Connection
|
2021-04-20 19:57:14 +03:00
|
|
|
import Hasura.Backends.MSSQL.FromIr as TSQL
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.Backends.MSSQL.Plan
|
2021-04-20 19:57:14 +03:00
|
|
|
import Hasura.Backends.MSSQL.SQL.Value (toTxtEncodedVal)
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.Backends.MSSQL.ToQuery
|
2021-04-20 19:57:14 +03:00
|
|
|
import Hasura.Backends.MSSQL.Types as TSQL
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.EncJSON
|
|
|
|
import Hasura.GraphQL.Context
|
|
|
|
import Hasura.GraphQL.Execute.Backend
|
|
|
|
import Hasura.GraphQL.Execute.LiveQuery.Plan
|
|
|
|
import Hasura.GraphQL.Parser
|
|
|
|
import Hasura.RQL.Types
|
|
|
|
import Hasura.Session
|
|
|
|
|
|
|
|
|
|
|
|
instance BackendExecute 'MSSQL where
|
2021-05-21 14:37:34 +03:00
|
|
|
type PreparedQuery 'MSSQL = PreparedQuery'
|
2021-04-20 19:57:14 +03:00
|
|
|
type MultiplexedQuery 'MSSQL = MultiplexedQuery'
|
2021-02-25 21:15:55 +03:00
|
|
|
type ExecutionMonad 'MSSQL = ExceptT QErr IO
|
2021-02-23 20:37:27 +03:00
|
|
|
getRemoteJoins = const []
|
|
|
|
|
|
|
|
mkDBQueryPlan = msDBQueryPlan
|
|
|
|
mkDBMutationPlan = msDBMutationPlan
|
|
|
|
mkDBSubscriptionPlan = msDBSubscriptionPlan
|
2021-04-13 14:10:08 +03:00
|
|
|
mkDBQueryExplain = msDBQueryExplain
|
|
|
|
mkLiveQueryExplain = msDBLiveQueryExplain
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
|
2021-05-21 14:37:34 +03:00
|
|
|
-- Prepared query
|
|
|
|
|
|
|
|
data PreparedQuery' = PreparedQuery'
|
|
|
|
{ pqQueryString :: Text
|
|
|
|
, pqGraphQlEnv :: PrepareState
|
|
|
|
, pqSession :: SessionVariables
|
|
|
|
}
|
|
|
|
|
|
|
|
-- | Render as a JSON object the variables that have been collected from an RQL
|
|
|
|
-- expression.
|
|
|
|
queryEnvJson :: PrepareState -> SessionVariables -> J.Value
|
|
|
|
queryEnvJson (PrepareState posArgs namedArgs requiredSessionVars) sessionVars =
|
|
|
|
let sessionVarValues = filterSessionVariables (\k _ -> Set.member k requiredSessionVars) sessionVars
|
|
|
|
in J.object
|
|
|
|
[ "session" J..= sessionVarValues
|
|
|
|
, "namedArguments" J..= toTxtEncodedVal namedArgs
|
|
|
|
, "positionalArguments" J..= toTxtEncodedVal posArgs
|
|
|
|
]
|
|
|
|
|
2021-04-20 19:57:14 +03:00
|
|
|
-- multiplexed query
|
|
|
|
newtype MultiplexedQuery' = MultiplexedQuery' Reselect
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2021-04-20 19:57:14 +03:00
|
|
|
instance T.ToTxt MultiplexedQuery' where
|
|
|
|
toTxt (MultiplexedQuery' reselect) = T.toTxt $ toQueryPretty $ fromReselect reselect
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
|
|
|
|
-- query
|
|
|
|
|
|
|
|
msDBQueryPlan
|
|
|
|
:: forall m.
|
|
|
|
( MonadError QErr m
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> UserInfo
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-23 20:37:27 +03:00
|
|
|
-> SourceConfig 'MSSQL
|
|
|
|
-> QueryDB 'MSSQL (UnpreparedValue 'MSSQL)
|
|
|
|
-> m ExecutionStep
|
2021-05-20 13:03:02 +03:00
|
|
|
msDBQueryPlan _env _manager _reqHeaders userInfo sourceName sourceConfig qrf = do
|
2021-05-21 14:37:34 +03:00
|
|
|
let sessionVariables = _uiSession userInfo
|
|
|
|
(statement, queryEnv) <- planQuery sessionVariables qrf
|
|
|
|
let selectWithEnv = joinEnv statement sessionVariables queryEnv
|
|
|
|
printer = fromSelect selectWithEnv
|
|
|
|
queryString = ODBC.renderQuery $ toQueryPretty printer
|
2021-02-25 21:15:55 +03:00
|
|
|
pool = _mscConnectionPool sourceConfig
|
2021-05-21 14:37:34 +03:00
|
|
|
odbcQuery = encJFromText <$> runJSONPathQuery pool (toQueryFlat printer)
|
2021-03-15 16:02:58 +03:00
|
|
|
pure
|
|
|
|
$ ExecStepDB []
|
|
|
|
. AB.mkAnyBackend
|
2021-05-21 14:37:34 +03:00
|
|
|
$ DBStepInfo @'MSSQL sourceName sourceConfig (Just $ PreparedQuery' queryString queryEnv sessionVariables) odbcQuery
|
|
|
|
|
|
|
|
joinEnv :: Select -> SessionVariables -> PrepareState -> Select
|
|
|
|
joinEnv querySelect sessionVars prepState =
|
|
|
|
querySelect
|
|
|
|
-- We *prepend* the variables of 'prepState' to the list of joins to make
|
|
|
|
-- them available in the @where@ clause as well as subsequent queries nested
|
|
|
|
-- in @join@ clauses.
|
|
|
|
{ selectJoins = [prepJoin] <> selectJoins querySelect }
|
|
|
|
|
|
|
|
where
|
|
|
|
prepJoin :: Join
|
|
|
|
prepJoin = Join
|
|
|
|
{ joinSource = JoinSelect $ (select
|
|
|
|
(
|
|
|
|
FromOpenJson
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
OpenJson
|
|
|
|
{ openJsonExpression =
|
|
|
|
ValueExpression (ODBC.TextValue $ lbsToTxt $ J.encode $ queryEnvJson prepState sessionVars)
|
|
|
|
, openJsonWith =
|
|
|
|
NE.fromList
|
|
|
|
[ JsonField "session" Nothing
|
|
|
|
, JsonField "namedArguments" Nothing
|
|
|
|
, JsonField "positionalArguments" Nothing
|
|
|
|
]
|
|
|
|
}
|
|
|
|
, aliasedAlias = rowAlias
|
|
|
|
}
|
|
|
|
)) {selectProjections = [ StarProjection ]
|
|
|
|
}
|
|
|
|
,joinJoinAlias =
|
|
|
|
JoinAlias
|
|
|
|
{ joinAliasEntity = rowAlias
|
|
|
|
, joinAliasField = Nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
select :: From -> Select
|
|
|
|
select from =
|
|
|
|
Select
|
|
|
|
{ selectFrom = from
|
|
|
|
, selectTop = NoTop
|
|
|
|
, selectProjections = []
|
|
|
|
, selectJoins = []
|
|
|
|
, selectWhere = Where []
|
|
|
|
, selectOrderBy = Nothing
|
|
|
|
, selectFor = NoFor
|
|
|
|
, selectOffset = Nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2021-05-11 13:04:38 +03:00
|
|
|
runShowplan
|
|
|
|
:: ODBC.Query -> ODBC.Connection -> IO [Text]
|
|
|
|
runShowplan query conn = do
|
|
|
|
ODBC.exec conn "SET SHOWPLAN_TEXT ON"
|
|
|
|
texts <- ODBC.query conn query
|
|
|
|
ODBC.exec conn "SET SHOWPLAN_TEXT OFF"
|
|
|
|
-- we don't need to use 'finally' here - if an exception occurs,
|
|
|
|
-- the connection is removed from the resource pool in 'withResource'.
|
|
|
|
pure texts
|
|
|
|
|
2021-04-13 14:10:08 +03:00
|
|
|
msDBQueryExplain
|
2021-04-20 19:57:14 +03:00
|
|
|
:: MonadError QErr m
|
2021-04-13 14:10:08 +03:00
|
|
|
=> G.Name
|
|
|
|
-> UserInfo
|
|
|
|
-> SourceName
|
|
|
|
-> SourceConfig 'MSSQL
|
|
|
|
-> QueryDB 'MSSQL (UnpreparedValue 'MSSQL)
|
|
|
|
-> m (AB.AnyBackend DBStepInfo)
|
|
|
|
msDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
|
2021-05-21 14:37:34 +03:00
|
|
|
let sessionVariables = _uiSession userInfo
|
|
|
|
(statement, queryEnv) <- planQuery sessionVariables qrf
|
|
|
|
let selectWithEnv = joinEnv statement sessionVariables queryEnv
|
|
|
|
query = toQueryPretty (fromSelect selectWithEnv)
|
|
|
|
queryString = ODBC.renderQuery $ query
|
|
|
|
pool = _mscConnectionPool sourceConfig
|
|
|
|
odbcQuery =
|
2021-05-11 13:04:38 +03:00
|
|
|
withMSSQLPool
|
|
|
|
pool
|
|
|
|
(\conn -> do
|
|
|
|
showplan <- runShowplan query conn
|
|
|
|
pure (encJFromJValue $
|
|
|
|
ExplainPlan
|
|
|
|
fieldName
|
|
|
|
(Just queryString)
|
|
|
|
(Just showplan)))
|
2021-04-13 14:10:08 +03:00
|
|
|
pure
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ DBStepInfo @'MSSQL sourceName sourceConfig Nothing odbcQuery
|
2021-04-13 14:10:08 +03:00
|
|
|
|
|
|
|
msDBLiveQueryExplain
|
2021-05-11 13:04:38 +03:00
|
|
|
:: (MonadIO m, MonadError QErr m)
|
2021-04-13 14:10:08 +03:00
|
|
|
=> LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL) -> m LiveQueryPlanExplanation
|
2021-05-11 13:04:38 +03:00
|
|
|
msDBLiveQueryExplain (LiveQueryPlan plan sourceConfig variables) = do
|
|
|
|
let (MultiplexedQuery' reselect) = _plqpQuery plan
|
|
|
|
query = toQueryPretty $ fromSelect $ multiplexRootReselect [(dummyCohortId, variables)] reselect
|
|
|
|
pool = _mscConnectionPool sourceConfig
|
|
|
|
explainInfo <- withMSSQLPool pool (runShowplan query)
|
|
|
|
pure $ LiveQueryPlanExplanation (T.toTxt query) explainInfo variables
|
2021-04-20 19:57:14 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Producing the correct SQL-level list comprehension to multiplex a query
|
|
|
|
|
|
|
|
-- Problem description:
|
|
|
|
--
|
|
|
|
-- Generate a query that repeats the same query N times but with
|
|
|
|
-- certain slots replaced:
|
|
|
|
--
|
|
|
|
-- [ Select x y | (x,y) <- [..] ]
|
|
|
|
--
|
|
|
|
|
|
|
|
multiplexRootReselect
|
|
|
|
:: [(CohortId, CohortVariables)]
|
|
|
|
-> TSQL.Reselect
|
|
|
|
-> TSQL.Select
|
|
|
|
multiplexRootReselect variables rootReselect =
|
|
|
|
Select
|
|
|
|
{ selectTop = NoTop
|
|
|
|
, selectProjections =
|
|
|
|
[ FieldNameProjection
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
TSQL.FieldName
|
|
|
|
{fieldNameEntity = rowAlias, fieldName = resultIdAlias}
|
|
|
|
, aliasedAlias = resultIdAlias
|
|
|
|
}
|
|
|
|
, ExpressionProjection
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
ColumnExpression
|
|
|
|
(TSQL.FieldName
|
|
|
|
{ fieldNameEntity = resultAlias
|
|
|
|
, fieldName = TSQL.jsonFieldName
|
|
|
|
})
|
|
|
|
, aliasedAlias = resultAlias
|
|
|
|
}
|
|
|
|
]
|
|
|
|
, selectFrom =
|
|
|
|
FromOpenJson
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
OpenJson
|
|
|
|
{ openJsonExpression =
|
|
|
|
ValueExpression (ODBC.TextValue $ lbsToTxt $ J.encode variables)
|
|
|
|
, openJsonWith =
|
|
|
|
NE.fromList
|
|
|
|
[ UuidField resultIdAlias (Just $ IndexPath RootPath 0)
|
|
|
|
, JsonField resultVarsAlias (Just $ IndexPath RootPath 1)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
, aliasedAlias = rowAlias
|
|
|
|
}
|
|
|
|
, selectJoins =
|
|
|
|
[ Join
|
|
|
|
{ joinSource = JoinReselect rootReselect
|
|
|
|
, joinJoinAlias =
|
|
|
|
JoinAlias
|
|
|
|
{ joinAliasEntity = resultAlias
|
|
|
|
, joinAliasField = Just TSQL.jsonFieldName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
, selectWhere = Where mempty
|
|
|
|
, selectFor =
|
|
|
|
JsonFor ForJson {jsonCardinality = JsonArray, jsonRoot = NoRoot}
|
|
|
|
, selectOrderBy = Nothing
|
|
|
|
, selectOffset = Nothing
|
|
|
|
}
|
|
|
|
|
2021-04-13 14:10:08 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
-- mutation
|
|
|
|
|
|
|
|
msDBMutationPlan
|
|
|
|
:: forall m.
|
|
|
|
( MonadError QErr m
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> UserInfo
|
|
|
|
-> Bool
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-23 20:37:27 +03:00
|
|
|
-> SourceConfig 'MSSQL
|
|
|
|
-> MutationDB 'MSSQL (UnpreparedValue 'MSSQL)
|
|
|
|
-> m ExecutionStep
|
2021-04-01 23:40:31 +03:00
|
|
|
msDBMutationPlan _env _manager _reqHeaders _userInfo _stringifyNum _sourceName _sourceConfig _mrf =
|
2021-02-23 20:37:27 +03:00
|
|
|
throw500 "mutations are not supported in MSSQL; this should be unreachable"
|
|
|
|
|
|
|
|
-- subscription
|
|
|
|
|
|
|
|
msDBSubscriptionPlan
|
|
|
|
:: forall m.
|
|
|
|
( MonadError QErr m
|
|
|
|
)
|
|
|
|
=> UserInfo
|
2021-04-01 23:40:31 +03:00
|
|
|
-> SourceName
|
2021-02-23 20:37:27 +03:00
|
|
|
-> SourceConfig 'MSSQL
|
|
|
|
-> InsOrdHashMap G.Name (QueryDB 'MSSQL (UnpreparedValue 'MSSQL))
|
|
|
|
-> m (LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL))
|
2021-04-20 19:57:14 +03:00
|
|
|
msDBSubscriptionPlan UserInfo {_uiSession, _uiRole} _sourceName sourceConfig rootFields = do
|
2021-05-21 14:37:34 +03:00
|
|
|
(reselect, prepareState) <- planSubscription rootFields _uiSession
|
|
|
|
|
|
|
|
let cohortVariables = prepareStateCohortVariables _uiSession prepareState
|
|
|
|
parameterizedPlan = ParameterizedLiveQueryPlan _uiRole $ MultiplexedQuery' reselect
|
|
|
|
|
|
|
|
pure
|
|
|
|
$ LiveQueryPlan parameterizedPlan sourceConfig cohortVariables
|
|
|
|
|
|
|
|
prepareStateCohortVariables :: SessionVariables -> PrepareState -> CohortVariables
|
|
|
|
prepareStateCohortVariables session prepState =
|
|
|
|
let PrepareState{sessionVariables, namedArguments, positionalArguments} = prepState
|
2021-04-20 19:57:14 +03:00
|
|
|
-- TODO: call MSSQL validateVariables
|
2021-05-21 14:37:34 +03:00
|
|
|
-- (see https://github.com/hasura/graphql-engine-mono/issues/1210)
|
2021-04-20 19:57:14 +03:00
|
|
|
-- We need to ensure that the values provided for variables are correct according to MSSQL.
|
|
|
|
-- Without this check an invalid value for a variable for one instance of the subscription will
|
|
|
|
-- take down the entire multiplexed query.
|
2021-05-21 14:37:34 +03:00
|
|
|
in mkCohortVariables
|
2021-04-20 19:57:14 +03:00
|
|
|
sessionVariables
|
2021-05-21 14:37:34 +03:00
|
|
|
session
|
2021-04-20 19:57:14 +03:00
|
|
|
(toTxtEncodedVal namedArguments)
|
|
|
|
(toTxtEncodedVal positionalArguments)
|