2021-02-23 20:37:27 +03:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
|
2022-01-11 01:54:51 +03:00
|
|
|
-- | MSSQL Instances Execute
|
|
|
|
--
|
|
|
|
-- Defines a 'BackendExecute' type class instance for MSSQL.
|
2022-01-03 20:16:24 +03:00
|
|
|
--
|
|
|
|
-- This module implements the needed functionality for implementing a 'BackendExecute'
|
|
|
|
-- instance for MSSQL, which defines an interface for translating a root field into an execution plan
|
|
|
|
-- and interacting with a database.
|
|
|
|
--
|
|
|
|
-- This module includes the MSSQL implementation of queries, mutations, and more.
|
2021-05-21 14:37:34 +03:00
|
|
|
module Hasura.Backends.MSSQL.Instances.Execute
|
|
|
|
( MultiplexedQuery' (..),
|
|
|
|
multiplexRootReselect,
|
|
|
|
)
|
|
|
|
where
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2023-08-17 15:49:09 +03:00
|
|
|
import Control.Exception.Lifted (bracket_)
|
2021-10-22 17:49:15 +03:00
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
2021-04-20 19:57:14 +03:00
|
|
|
import Data.Aeson.Extended qualified as J
|
2023-06-25 16:46:35 +03:00
|
|
|
import Data.Environment qualified as Env
|
2023-04-26 18:42:13 +03:00
|
|
|
import Data.HashMap.Strict qualified as HashMap
|
2023-04-27 10:41:55 +03:00
|
|
|
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
|
2021-06-08 06:50:24 +03:00
|
|
|
import Data.HashSet qualified as Set
|
2021-04-20 19:57:14 +03:00
|
|
|
import Data.List.NonEmpty qualified as NE
|
2023-10-03 08:15:07 +03:00
|
|
|
import Data.Text.Extended
|
2021-04-20 19:57:14 +03:00
|
|
|
import Data.Text.Extended qualified as T
|
2022-01-14 17:08:17 +03:00
|
|
|
import Database.MSSQL.Transaction qualified as Tx
|
2021-02-23 20:37:27 +03:00
|
|
|
import Database.ODBC.SQLServer qualified as ODBC
|
2021-02-25 21:15:55 +03:00
|
|
|
import Hasura.Backends.MSSQL.Connection
|
2022-01-06 12:49:03 +03:00
|
|
|
import Hasura.Backends.MSSQL.Execute.Delete
|
|
|
|
import Hasura.Backends.MSSQL.Execute.Insert
|
2022-04-28 22:33:33 +03:00
|
|
|
import Hasura.Backends.MSSQL.Execute.QueryTags
|
2022-01-06 12:49:03 +03:00
|
|
|
import Hasura.Backends.MSSQL.Execute.Update
|
2022-03-10 13:33:55 +03:00
|
|
|
import Hasura.Backends.MSSQL.FromIr.Constants (jsonFieldName)
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.Backends.MSSQL.Plan
|
2022-02-07 17:11:49 +03:00
|
|
|
import Hasura.Backends.MSSQL.SQL.Error
|
2021-06-08 06:50:24 +03:00
|
|
|
import Hasura.Backends.MSSQL.SQL.Value (txtEncodedColVal)
|
2021-10-01 15:52:19 +03:00
|
|
|
import Hasura.Backends.MSSQL.ToQuery as TQ
|
2021-11-26 16:47:12 +03:00
|
|
|
import Hasura.Backends.MSSQL.Types.Internal 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.Execute.Backend
|
2022-03-21 13:39:49 +03:00
|
|
|
import Hasura.GraphQL.Execute.Subscription.Plan
|
2021-10-29 17:42:07 +03:00
|
|
|
import Hasura.GraphQL.Namespace (RootFieldAlias (..), RootFieldMap)
|
2023-06-28 13:18:09 +03:00
|
|
|
import Hasura.GraphQL.Parser.Variable qualified as G
|
2023-06-25 16:46:35 +03:00
|
|
|
import Hasura.Logging qualified as L
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.Prelude
|
2022-04-28 22:33:33 +03:00
|
|
|
import Hasura.QueryTags (QueryTagsComment)
|
2021-06-11 06:26:50 +03:00
|
|
|
import Hasura.RQL.IR
|
2023-10-03 08:15:07 +03:00
|
|
|
import Hasura.RQL.IR.ModelInformation
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend as RQLTypes
|
2023-04-24 21:35:48 +03:00
|
|
|
import Hasura.RQL.Types.BackendType
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.RQL.Types.Column qualified as RQLColumn
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Common as RQLTypes
|
2023-04-24 18:17:15 +03:00
|
|
|
import Hasura.RQL.Types.Schema.Options qualified as Options
|
2021-03-15 16:02:58 +03:00
|
|
|
import Hasura.SQL.AnyBackend qualified as AB
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.Session
|
|
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
2023-06-25 16:46:35 +03:00
|
|
|
import Network.HTTP.Client as HTTP
|
2023-01-25 10:12:53 +03:00
|
|
|
import Network.HTTP.Types qualified as HTTP
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
instance BackendExecute 'MSSQL where
|
2021-06-01 13:04:29 +03:00
|
|
|
type PreparedQuery 'MSSQL = Text
|
2021-04-20 19:57:14 +03:00
|
|
|
type MultiplexedQuery 'MSSQL = MultiplexedQuery'
|
Allow backend execution to happen on the base app monad.
### Description
Each Backend executes queries against the database in a slightly different stack: Postgres uses its own `TXeT`, MSSQL uses a variant of it, BigQuery is simply in `ExceptT QErr IO`... To accommodate those variations, we had originally introduced an `ExecutionMonad b` type family in `BackendExecute`, allowing each backend to describe its own stack. It was then up to that backend's `BackendTransport` instance to implement running said stack, and converting the result back into our main app monad.
However, this was not without complications: `TraceT` is one of them: as it usually needs to be on the top of the stack, converting from one stack to the other implies the use `interpTraceT`, which is quite monstrous. Furthermore, as part of the Entitlement Services work, we're trying to move to a "Services" architecture in which the entire engine runs in one base monad, that delegates features and dependencies to monad constraints; and as a result we'd like to minimize the number of different monad stacks we have to maintain and translate from and to in the codebase.
To improve things, this PR changes `ExecutionMonad b` from an _absolute_ stack to a _relative_ one: i.e.: what needs to be stacked on top of our base monad for the execution. In `Transport`, we then only need to pop the top of the stack, and voila. This greatly simplifies the implementation of the backends, as there's no longer any need to do any stack transformation: MySQL's implementation becomes a `runIdentityT`! This also removes most mentions of `TraceT` from the execution code since it's no longer required: we can rely on the base monad's existing `MonadTrace` constraint.
To continue encapsulating monadic actions in `DBStepInfo` and avoid threading a bunch of `forall` all over the place, this PR introduces a small local helper: `OnBaseMonad`. One only downside of all this is that this requires adding `MonadBaseControl IO m` constraint all over the place: previously, we would run directly on `IO` and lift, and would therefore not need to bring that constraint all the way.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7789
GitOrigin-RevId: e9b2e431c5c47fa9851abf87545c0415ff6d1a12
2023-02-09 17:38:33 +03:00
|
|
|
type ExecutionMonad 'MSSQL = ExceptT QErr
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
mkDBQueryPlan = msDBQueryPlan
|
|
|
|
mkDBMutationPlan = msDBMutationPlan
|
2022-04-07 17:41:43 +03:00
|
|
|
mkLiveQuerySubscriptionPlan = msDBLiveQuerySubscriptionPlan
|
2023-01-25 10:12:53 +03:00
|
|
|
mkDBStreamingSubscriptionPlan _ _ _ _ _ _ = throw500 "Streaming subscriptions are not supported for MS-SQL sources yet"
|
2021-04-13 14:10:08 +03:00
|
|
|
mkDBQueryExplain = msDBQueryExplain
|
2022-03-21 13:39:49 +03:00
|
|
|
mkSubscriptionExplain = msDBSubscriptionExplain
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2021-09-22 13:43:05 +03:00
|
|
|
mkDBRemoteRelationshipPlan =
|
|
|
|
msDBRemoteRelationshipPlan
|
|
|
|
|
2022-01-03 20:16:24 +03:00
|
|
|
-- * Multiplexed query
|
2021-05-21 14:37:34 +03:00
|
|
|
|
2022-04-28 22:33:33 +03:00
|
|
|
data MultiplexedQuery' = MultiplexedQuery'
|
|
|
|
{ reselect :: Reselect,
|
|
|
|
subscriptionQueryTagsComment :: QueryTagsComment
|
|
|
|
}
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2021-04-20 19:57:14 +03:00
|
|
|
instance T.ToTxt MultiplexedQuery' where
|
2022-04-28 22:33:33 +03:00
|
|
|
toTxt (MultiplexedQuery' reselect queryTags) =
|
|
|
|
T.toTxt $ toQueryPretty (fromReselect reselect) `withQueryTags` queryTags
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2022-01-03 20:16:24 +03:00
|
|
|
-- * Query
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
msDBQueryPlan ::
|
|
|
|
forall m.
|
2022-04-28 22:33:33 +03:00
|
|
|
( MonadError QErr m,
|
|
|
|
MonadReader QueryTagsComment m
|
2021-02-23 20:37:27 +03:00
|
|
|
) =>
|
2021-06-11 06:26:50 +03:00
|
|
|
UserInfo ->
|
2021-04-01 23:40:31 +03:00
|
|
|
SourceName ->
|
2021-02-23 20:37:27 +03:00
|
|
|
SourceConfig 'MSSQL ->
|
2021-12-07 16:12:02 +03:00
|
|
|
QueryDB 'MSSQL Void (UnpreparedValue 'MSSQL) ->
|
2023-01-25 10:12:53 +03:00
|
|
|
[HTTP.Header] ->
|
|
|
|
Maybe G.Name ->
|
2023-10-03 08:15:07 +03:00
|
|
|
m (DBStepInfo 'MSSQL, [ModelInfoPart])
|
2023-04-13 04:29:15 +03:00
|
|
|
msDBQueryPlan userInfo sourceName sourceConfig qrf _ _ = do
|
2021-05-21 14:37:34 +03:00
|
|
|
let sessionVariables = _uiSession userInfo
|
2023-04-26 00:04:29 +03:00
|
|
|
QueryWithDDL {qwdBeforeSteps, qwdAfterSteps, qwdQuery = statement} <- planQuery sessionVariables qrf
|
2022-04-28 22:33:33 +03:00
|
|
|
queryTags <- ask
|
2023-03-27 19:54:27 +03:00
|
|
|
|
2022-04-28 22:33:33 +03:00
|
|
|
-- Append Query tags comment to the select statement
|
|
|
|
let printer = fromSelect statement `withQueryTagsPrinter` queryTags
|
|
|
|
queryString = ODBC.renderQuery (toQueryPretty printer)
|
2023-10-03 08:15:07 +03:00
|
|
|
modelNames <- irToModelInfoGen sourceName ModelSourceTypeMSSQL qrf
|
|
|
|
let modelInfo = getModelInfoPartfromModelNames modelNames (ModelOperationType G.OperationTypeQuery)
|
2023-01-25 10:12:53 +03:00
|
|
|
|
2023-10-03 08:15:07 +03:00
|
|
|
pure $ (DBStepInfo @'MSSQL sourceName sourceConfig (Just queryString) (runSelectQuery printer qwdBeforeSteps qwdAfterSteps) (), modelInfo)
|
2022-01-14 17:08:17 +03:00
|
|
|
where
|
2023-04-26 00:04:29 +03:00
|
|
|
runSelectQuery queryPrinter beforeSteps afterSteps = OnBaseMonad do
|
|
|
|
let queryTx = do
|
|
|
|
let executeStep = Tx.unitQueryE defaultMSSQLTxErrorHandler . toQueryFlat . TQ.fromTempTableDDL
|
|
|
|
traverse_ executeStep beforeSteps
|
|
|
|
result <- encJFromText <$> Tx.singleRowQueryE defaultMSSQLTxErrorHandler (toQueryFlat queryPrinter)
|
|
|
|
traverse_ executeStep afterSteps
|
|
|
|
pure result
|
2023-03-14 14:32:20 +03:00
|
|
|
mssqlRunReadOnly (_mscExecCtx sourceConfig) (fmap withNoStatistics queryTx)
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2023-08-17 15:49:09 +03:00
|
|
|
-- Runs the query in "SHOWPLAN_TEXT" mode, which instead plans the query, but
|
|
|
|
-- does not execute it.
|
|
|
|
--
|
|
|
|
-- This does not work for prepared statements, so we have to use a different
|
|
|
|
-- translation strategy. We first convert the query to an unprepared style,
|
|
|
|
-- populating it with references to variables instead of the values themselves.
|
|
|
|
--
|
|
|
|
-- We then declare all these variables, but do not set their values, and run the
|
|
|
|
-- query in SHOWPLAN_TEXT mode. We have to do this as a single transaction, so
|
|
|
|
-- we concatenate them all together.
|
|
|
|
--
|
|
|
|
-- The variables are all declared as type `NVARCHAR(MAX)`, which seems to work
|
|
|
|
-- even when we need to use them in another context, e.g. an integer.
|
2021-05-11 13:04:38 +03:00
|
|
|
runShowplan ::
|
2023-08-17 15:49:09 +03:00
|
|
|
(MonadIO m, MonadBaseControl IO m) =>
|
2022-01-14 17:08:17 +03:00
|
|
|
ODBC.Query ->
|
|
|
|
Tx.TxET QErr m [Text]
|
2022-02-07 17:11:49 +03:00
|
|
|
runShowplan query = Tx.withTxET defaultMSSQLTxErrorHandler do
|
2023-08-17 15:49:09 +03:00
|
|
|
bracket_ setShowplanOn setShowplanOff
|
|
|
|
. Tx.multiRowQuery
|
|
|
|
. ODBC.rawUnescapedText
|
|
|
|
$ mconcat paramDeclarations
|
|
|
|
<> "\n-- QUERY START --\n"
|
|
|
|
<> unparameterizedQueryWithVariables
|
|
|
|
<> "\n-- QUERY END --\n"
|
|
|
|
where
|
|
|
|
setShowplanOn = Tx.unitQuery "SET SHOWPLAN_TEXT ON"
|
|
|
|
setShowplanOff = Tx.unitQuery "SET SHOWPLAN_TEXT OFF"
|
|
|
|
paramDeclarations =
|
|
|
|
zipWith
|
|
|
|
(\paramIndex paramType -> "DECLARE @" <> tshow paramIndex <> " " <> paramType <> ";\n")
|
|
|
|
[1 :: Int ..]
|
|
|
|
(reverse reversedParameterTypes)
|
|
|
|
-- we build up the SQL and parameter type list with a counter so we can use
|
|
|
|
-- a fresh variable for each parameter
|
|
|
|
unparameterizedQueryWithVariables :: Text
|
|
|
|
reversedParameterTypes :: [Text]
|
|
|
|
(unparameterizedQueryWithVariables, _, reversedParameterTypes) =
|
|
|
|
foldl
|
|
|
|
( \(text, paramIndex, reversedParamTypes) part -> case part of
|
|
|
|
ODBC.TextPart t -> (text <> t, paramIndex, reversedParamTypes)
|
|
|
|
ODBC.ValuePart v ->
|
|
|
|
case paramType of
|
|
|
|
Just t -> (text <> "@" <> tshow paramIndex, paramIndex + 1, t : reversedParamTypes)
|
|
|
|
Nothing -> (text <> ODBC.renderValue v, paramIndex, reversedParamTypes)
|
|
|
|
where
|
|
|
|
-- Copying from the ODBC library, we only use parameters for
|
|
|
|
-- a couple of types; everything else is inlined into the query.
|
|
|
|
paramType = case v of
|
|
|
|
ODBC.TextValue {} -> Just "VARCHAR(MAX)"
|
|
|
|
ODBC.BinaryValue {} -> Just "VARBINARY(MAX)"
|
|
|
|
_ -> Nothing
|
|
|
|
)
|
|
|
|
("", 1 :: Int, [])
|
|
|
|
(ODBC.queryParts query)
|
2021-05-11 13:04:38 +03:00
|
|
|
|
2021-04-13 14:10:08 +03:00
|
|
|
msDBQueryExplain ::
|
2023-05-24 16:51:56 +03:00
|
|
|
(MonadError QErr m) =>
|
2021-10-29 17:42:07 +03:00
|
|
|
RootFieldAlias ->
|
2021-04-13 14:10:08 +03:00
|
|
|
UserInfo ->
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig 'MSSQL ->
|
2021-12-07 16:12:02 +03:00
|
|
|
QueryDB 'MSSQL Void (UnpreparedValue 'MSSQL) ->
|
2023-01-25 10:12:53 +03:00
|
|
|
[HTTP.Header] ->
|
|
|
|
Maybe G.Name ->
|
2021-04-13 14:10:08 +03:00
|
|
|
m (AB.AnyBackend DBStepInfo)
|
2023-01-25 10:12:53 +03:00
|
|
|
msDBQueryExplain fieldName userInfo sourceName sourceConfig qrf _ _ = do
|
2021-05-21 14:37:34 +03:00
|
|
|
let sessionVariables = _uiSession userInfo
|
2023-08-17 15:49:09 +03:00
|
|
|
queryPlan <- planQuery sessionVariables qrf
|
|
|
|
select <-
|
|
|
|
case queryPlan of
|
|
|
|
QueryWithDDL [] s [] -> pure s
|
|
|
|
_ -> throw400 NotSupported "queries which require multiple steps cannot be explained"
|
|
|
|
let query = toQueryPretty (fromSelect select)
|
2021-10-01 15:52:19 +03:00
|
|
|
queryString = ODBC.renderQuery query
|
2023-05-24 16:51:56 +03:00
|
|
|
odbcQuery = OnBaseMonad
|
|
|
|
$ mssqlRunReadOnly
|
2022-01-04 14:53:50 +03:00
|
|
|
(_mscExecCtx sourceConfig)
|
2022-01-14 17:08:17 +03:00
|
|
|
do
|
|
|
|
showplan <- runShowplan query
|
2023-05-24 16:51:56 +03:00
|
|
|
pure
|
|
|
|
$ withNoStatistics
|
|
|
|
$ encJFromJValue
|
|
|
|
$ ExplainPlan
|
|
|
|
fieldName
|
|
|
|
(Just queryString)
|
|
|
|
(Just showplan)
|
|
|
|
pure
|
|
|
|
$ AB.mkAnyBackend
|
|
|
|
$ DBStepInfo @'MSSQL sourceName sourceConfig Nothing odbcQuery ()
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2022-03-21 13:39:49 +03:00
|
|
|
msDBSubscriptionExplain ::
|
2021-10-22 17:49:15 +03:00
|
|
|
(MonadIO m, MonadBaseControl IO m, MonadError QErr m) =>
|
2022-03-21 13:39:49 +03:00
|
|
|
SubscriptionQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL) ->
|
|
|
|
m SubscriptionQueryPlanExplanation
|
2023-01-25 10:12:53 +03:00
|
|
|
msDBSubscriptionExplain (SubscriptionQueryPlan plan sourceConfig cohortId _dynamicConnection variables _) = do
|
2022-04-28 22:33:33 +03:00
|
|
|
let (MultiplexedQuery' reselect _queryTags) = _plqpQuery plan
|
2022-12-22 20:08:04 +03:00
|
|
|
query = toQueryPretty $ fromSelect $ multiplexRootReselect [(cohortId, variables)] reselect
|
2022-01-04 14:53:50 +03:00
|
|
|
mssqlExecCtx = (_mscExecCtx sourceConfig)
|
2022-01-14 17:08:17 +03:00
|
|
|
explainInfo <- liftEitherM $ runExceptT $ (mssqlRunReadOnly mssqlExecCtx) (runShowplan query)
|
2022-03-21 13:39:49 +03:00
|
|
|
pure $ SubscriptionQueryPlanExplanation (T.toTxt query) explainInfo variables
|
2021-04-20 19:57:14 +03:00
|
|
|
|
2022-03-21 15:14:52 +03:00
|
|
|
-- | Producing the correct SQL-level list comprehension to multiplex a query
|
2021-04-20 19:57:14 +03:00
|
|
|
-- Problem description:
|
|
|
|
--
|
|
|
|
-- Generate a query that repeats the same query N times but with
|
|
|
|
-- certain slots replaced:
|
|
|
|
--
|
|
|
|
-- [ Select x y | (x,y) <- [..] ]
|
|
|
|
--
|
2022-03-21 15:14:52 +03:00
|
|
|
-- Caution: Be aware that this query has a @FOR JSON@ clause at the top-level
|
|
|
|
-- and hence its results may be split up across multiple rows. Use
|
|
|
|
-- 'Database.MSSQL.Transaction.forJsonQueryE' to handle this.
|
2021-04-20 19:57:14 +03:00
|
|
|
multiplexRootReselect ::
|
|
|
|
[(CohortId, CohortVariables)] ->
|
|
|
|
TSQL.Reselect ->
|
|
|
|
TSQL.Select
|
|
|
|
multiplexRootReselect variables rootReselect =
|
2021-10-01 15:52:19 +03:00
|
|
|
emptySelect
|
2021-04-20 19:57:14 +03:00
|
|
|
{ selectTop = NoTop,
|
|
|
|
selectProjections =
|
|
|
|
[ FieldNameProjection
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
TSQL.FieldName
|
|
|
|
{ fieldNameEntity = rowAlias,
|
|
|
|
fieldName = resultIdAlias
|
|
|
|
},
|
|
|
|
aliasedAlias = resultIdAlias
|
|
|
|
},
|
|
|
|
ExpressionProjection
|
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
ColumnExpression
|
|
|
|
( TSQL.FieldName
|
|
|
|
{ fieldNameEntity = resultAlias,
|
2022-03-10 13:33:55 +03:00
|
|
|
fieldName = jsonFieldName
|
2021-04-20 19:57:14 +03:00
|
|
|
}
|
|
|
|
),
|
|
|
|
aliasedAlias = resultAlias
|
|
|
|
}
|
|
|
|
],
|
|
|
|
selectFrom =
|
2023-05-24 16:51:56 +03:00
|
|
|
Just
|
|
|
|
$ FromOpenJson
|
2021-04-20 19:57:14 +03:00
|
|
|
Aliased
|
|
|
|
{ aliasedThing =
|
|
|
|
OpenJson
|
|
|
|
{ openJsonExpression =
|
|
|
|
ValueExpression (ODBC.TextValue $ lbsToTxt $ J.encode variables),
|
2021-07-08 23:49:10 +03:00
|
|
|
openJsonWith =
|
2023-05-24 16:51:56 +03:00
|
|
|
Just
|
|
|
|
$ NE.fromList
|
2022-04-06 10:18:59 +03:00
|
|
|
[ ScalarField GuidType DataLengthUnspecified resultIdAlias (Just $ IndexPath RootPath 0),
|
2021-04-20 19:57:14 +03:00
|
|
|
JsonField resultVarsAlias (Just $ IndexPath RootPath 1)
|
|
|
|
]
|
|
|
|
},
|
|
|
|
aliasedAlias = rowAlias
|
|
|
|
},
|
|
|
|
selectJoins =
|
|
|
|
[ Join
|
|
|
|
{ joinSource = JoinReselect rootReselect,
|
2023-06-05 12:24:52 +03:00
|
|
|
joinWhere = mempty,
|
2021-04-20 19:57:14 +03:00
|
|
|
joinJoinAlias =
|
|
|
|
JoinAlias
|
|
|
|
{ joinAliasEntity = resultAlias,
|
2022-03-10 13:33:55 +03:00
|
|
|
joinAliasField = Just jsonFieldName
|
2021-04-20 19:57:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
selectWhere = Where mempty,
|
|
|
|
selectFor =
|
|
|
|
JsonFor ForJson {jsonCardinality = JsonArray, jsonRoot = NoRoot},
|
|
|
|
selectOrderBy = Nothing,
|
|
|
|
selectOffset = Nothing
|
|
|
|
}
|
|
|
|
|
2022-01-03 20:16:24 +03:00
|
|
|
-- * Mutation
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
msDBMutationPlan ::
|
|
|
|
forall m.
|
2022-04-28 22:33:33 +03:00
|
|
|
( MonadError QErr m,
|
|
|
|
MonadReader QueryTagsComment m
|
2021-02-23 20:37:27 +03:00
|
|
|
) =>
|
2023-06-25 16:46:35 +03:00
|
|
|
Env.Environment ->
|
|
|
|
HTTP.Manager ->
|
|
|
|
L.Logger L.Hasura ->
|
2021-06-11 06:26:50 +03:00
|
|
|
UserInfo ->
|
2022-07-14 20:57:28 +03:00
|
|
|
Options.StringifyNumbers ->
|
2021-04-01 23:40:31 +03:00
|
|
|
SourceName ->
|
2021-02-23 20:37:27 +03:00
|
|
|
SourceConfig 'MSSQL ->
|
2021-12-07 16:12:02 +03:00
|
|
|
MutationDB 'MSSQL Void (UnpreparedValue 'MSSQL) ->
|
2023-01-25 10:12:53 +03:00
|
|
|
[HTTP.Header] ->
|
|
|
|
Maybe G.Name ->
|
2023-06-28 13:18:09 +03:00
|
|
|
Maybe (HashMap G.Name (G.Value G.Variable)) ->
|
2023-10-03 08:15:07 +03:00
|
|
|
m (DBStepInfo 'MSSQL, [ModelInfoPart])
|
2023-07-04 16:21:46 +03:00
|
|
|
msDBMutationPlan _env _manager _logger userInfo stringifyNum sourceName sourceConfig mrf _headers _gName _maybeSelSetArgs = do
|
2021-10-01 15:52:19 +03:00
|
|
|
go <$> case mrf of
|
2023-10-03 08:15:07 +03:00
|
|
|
MDBInsert annInsert -> executeInsert userInfo stringifyNum sourceName ModelSourceTypeMSSQL sourceConfig annInsert
|
|
|
|
MDBDelete annDelete -> executeDelete userInfo stringifyNum sourceName ModelSourceTypeMSSQL sourceConfig annDelete
|
|
|
|
MDBUpdate annUpdate -> executeUpdate userInfo stringifyNum sourceName ModelSourceTypeMSSQL sourceConfig annUpdate
|
2021-10-01 15:52:19 +03:00
|
|
|
MDBFunction {} -> throw400 NotSupported "function mutations are not supported in MSSQL"
|
|
|
|
where
|
2023-10-03 08:15:07 +03:00
|
|
|
modelInfoList v = getModelInfoPartfromModelNames (snd v) (ModelOperationType G.OperationTypeMutation)
|
|
|
|
|
|
|
|
go v = (DBStepInfo @'MSSQL sourceName sourceConfig Nothing (fmap withNoStatistics (fst v)) (), modelInfoList v)
|
2021-10-01 15:52:19 +03:00
|
|
|
|
2022-01-03 20:16:24 +03:00
|
|
|
-- * Subscription
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2022-04-07 17:41:43 +03:00
|
|
|
msDBLiveQuerySubscriptionPlan ::
|
2021-02-23 20:37:27 +03:00
|
|
|
forall m.
|
|
|
|
( MonadError QErr m,
|
2021-10-22 17:49:15 +03:00
|
|
|
MonadIO m,
|
2022-04-28 22:33:33 +03:00
|
|
|
MonadBaseControl IO m,
|
|
|
|
MonadReader QueryTagsComment m
|
2021-02-23 20:37:27 +03:00
|
|
|
) =>
|
|
|
|
UserInfo ->
|
2021-04-01 23:40:31 +03:00
|
|
|
SourceName ->
|
2021-02-23 20:37:27 +03:00
|
|
|
SourceConfig 'MSSQL ->
|
2021-10-29 17:42:07 +03:00
|
|
|
Maybe G.Name ->
|
2021-12-07 16:12:02 +03:00
|
|
|
RootFieldMap (QueryDB 'MSSQL Void (UnpreparedValue 'MSSQL)) ->
|
2023-01-25 10:12:53 +03:00
|
|
|
[HTTP.Header] ->
|
|
|
|
Maybe G.Name ->
|
2023-10-03 08:15:07 +03:00
|
|
|
m (SubscriptionQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL), [ModelInfoPart])
|
|
|
|
msDBLiveQuerySubscriptionPlan UserInfo {_uiSession, _uiRole} sourceName sourceConfig namespace rootFields _ _ = do
|
2023-04-27 10:41:55 +03:00
|
|
|
(reselect, prepareState) <- planSubscription (InsOrdHashMap.mapKeys _rfaAlias rootFields) _uiSession
|
2021-06-08 06:50:24 +03:00
|
|
|
cohortVariables <- prepareStateCohortVariables sourceConfig _uiSession prepareState
|
2022-04-28 22:33:33 +03:00
|
|
|
queryTags <- ask
|
|
|
|
let parameterizedPlan = ParameterizedSubscriptionQueryPlan _uiRole $ (MultiplexedQuery' reselect queryTags)
|
2023-10-03 08:15:07 +03:00
|
|
|
modelNameInfo <- do
|
|
|
|
let vals = InsOrdHashMap.elems rootFields
|
|
|
|
pure
|
|
|
|
$ concatMap
|
|
|
|
( \val -> do
|
|
|
|
join (irToModelInfoGen sourceName ModelSourceTypeMSSQL) val
|
|
|
|
)
|
|
|
|
vals
|
|
|
|
|
|
|
|
let modelInfo = getModelInfoPartfromModelNames modelNameInfo (ModelOperationType G.OperationTypeSubscription)
|
|
|
|
|
2023-05-24 16:51:56 +03:00
|
|
|
pure
|
2023-10-03 08:15:07 +03:00
|
|
|
$ (SubscriptionQueryPlan parameterizedPlan sourceConfig dummyCohortId () cohortVariables namespace, modelInfo)
|
2021-05-21 14:37:34 +03:00
|
|
|
|
2023-01-25 10:12:53 +03:00
|
|
|
prepareStateCohortVariables ::
|
|
|
|
(MonadError QErr m, MonadIO m, MonadBaseControl IO m) =>
|
|
|
|
SourceConfig 'MSSQL ->
|
|
|
|
SessionVariables ->
|
|
|
|
PrepareState ->
|
|
|
|
m CohortVariables
|
2021-06-08 06:50:24 +03:00
|
|
|
prepareStateCohortVariables sourceConfig session prepState = do
|
|
|
|
(namedVars, posVars) <- validateVariables sourceConfig session prepState
|
|
|
|
let PrepareState {sessionVariables} = prepState
|
2023-05-24 16:51:56 +03:00
|
|
|
pure
|
|
|
|
$ mkCohortVariables
|
2021-04-20 19:57:14 +03:00
|
|
|
sessionVariables
|
2021-05-21 14:37:34 +03:00
|
|
|
session
|
2021-06-08 06:50:24 +03:00
|
|
|
namedVars
|
|
|
|
posVars
|
2022-04-22 22:53:12 +03:00
|
|
|
mempty -- streaming cursor variables are kept empty because streaming subscriptions aren't yet supported for MS-SQL
|
2021-06-08 06:50:24 +03:00
|
|
|
|
|
|
|
-- | Ensure that the set of variables (with value instantiations) that occur in
|
|
|
|
-- a (RQL) query produce a well-formed and executable (SQL) query when
|
|
|
|
-- considered in isolation.
|
|
|
|
--
|
|
|
|
-- This helps avoiding cascading failures in multiplexed queries.
|
|
|
|
--
|
|
|
|
-- c.f. https://github.com/hasura/graphql-engine-mono/issues/1210.
|
|
|
|
validateVariables ::
|
2021-10-22 17:49:15 +03:00
|
|
|
(MonadError QErr m, MonadIO m, MonadBaseControl IO m) =>
|
2021-06-08 06:50:24 +03:00
|
|
|
SourceConfig 'MSSQL ->
|
|
|
|
SessionVariables ->
|
|
|
|
PrepareState ->
|
|
|
|
m (ValidatedQueryVariables, ValidatedSyntheticVariables)
|
|
|
|
validateVariables sourceConfig sessionVariableValues prepState = do
|
|
|
|
let PrepareState {sessionVariables, namedArguments, positionalArguments} = prepState
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
-- We generate a single 'canary' query in the form:
|
|
|
|
--
|
|
|
|
-- SELECT ... [session].[x-hasura-foo] as [x-hasura-foo], ... as a, ... as b, ...
|
|
|
|
-- FROM OPENJSON('...')
|
|
|
|
-- WITH ([x-hasura-foo] NVARCHAR(MAX)) as [session]
|
|
|
|
--
|
|
|
|
-- where 'a', 'b', etc. are aliases given to positional arguments.
|
|
|
|
-- Named arguments and session variables are aliased to themselves.
|
|
|
|
--
|
|
|
|
-- The idea being that if the canary query succeeds we can be
|
|
|
|
-- reasonably confident that adding these variables to a query being
|
|
|
|
-- polled will not crash the poller.
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
occSessionVars =
|
|
|
|
filterSessionVariables
|
|
|
|
(\k _ -> Set.member k sessionVariables)
|
|
|
|
sessionVariableValues
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
expSes, expNamed, expPos :: [Aliased Expression]
|
2021-09-22 13:43:05 +03:00
|
|
|
expSes = sessionReference <$> getSessionVariables occSessionVars
|
2021-06-08 06:50:24 +03:00
|
|
|
expNamed =
|
2021-09-24 01:56:37 +03:00
|
|
|
map
|
2021-09-22 13:43:05 +03:00
|
|
|
( \(n, v) -> Aliased (ValueExpression (RQLColumn.cvValue v)) (G.unName n)
|
2021-06-08 06:50:24 +03:00
|
|
|
)
|
2023-04-26 18:42:13 +03:00
|
|
|
$ HashMap.toList
|
2021-06-08 06:50:24 +03:00
|
|
|
$ namedArguments
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
-- For positional args we need to be a bit careful not to capture names
|
|
|
|
-- from expNamed and expSes (however unlikely)
|
|
|
|
expPos =
|
|
|
|
zipWith
|
|
|
|
(\n v -> Aliased (ValueExpression (RQLColumn.cvValue v)) n)
|
|
|
|
(freshVars (expNamed <> expSes))
|
|
|
|
positionalArguments
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
projAll :: [Projection]
|
|
|
|
projAll = map ExpressionProjection (expSes <> expNamed <> expPos)
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
canaryQuery =
|
|
|
|
if null projAll
|
|
|
|
then Nothing
|
|
|
|
else
|
2023-05-24 16:51:56 +03:00
|
|
|
Just
|
|
|
|
$ renderQuery
|
2021-06-08 06:50:24 +03:00
|
|
|
emptySelect
|
|
|
|
{ selectProjections = projAll,
|
|
|
|
selectFrom = sessionOpenJson occSessionVars
|
2021-09-24 01:56:37 +03:00
|
|
|
}
|
2021-06-08 06:50:24 +03:00
|
|
|
|
2022-10-04 00:49:32 +03:00
|
|
|
for_
|
2021-06-08 06:50:24 +03:00
|
|
|
canaryQuery
|
|
|
|
( \q -> do
|
2022-02-07 17:11:49 +03:00
|
|
|
_ :: [[ODBC.Value]] <- liftEitherM $ runExceptT $ mssqlRunReadOnly (_mscExecCtx sourceConfig) (Tx.multiRowQueryE defaultMSSQLTxErrorHandler q)
|
2021-09-24 01:56:37 +03:00
|
|
|
pure ()
|
|
|
|
)
|
2021-06-08 06:50:24 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
pure
|
2021-06-08 06:50:24 +03:00
|
|
|
( ValidatedVariables $ txtEncodedColVal <$> namedArguments,
|
|
|
|
ValidatedVariables $ txtEncodedColVal <$> positionalArguments
|
2021-09-24 01:56:37 +03:00
|
|
|
)
|
|
|
|
where
|
2021-06-08 06:50:24 +03:00
|
|
|
renderQuery :: Select -> ODBC.Query
|
|
|
|
renderQuery = toQueryFlat . fromSelect
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
freshVars :: [Aliased a] -> [Text]
|
|
|
|
freshVars boundNames = filter (not . (`elem` map aliasedAlias boundNames)) chars
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
-- Infinite list of expression aliases.
|
|
|
|
chars :: [Text]
|
|
|
|
chars = [y T.<>> x | y <- [""] <|> chars, x <- ['a' .. 'z']]
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
sessionOpenJson :: SessionVariables -> Maybe From
|
|
|
|
sessionOpenJson occSessionVars =
|
|
|
|
nonEmpty (getSessionVariables occSessionVars)
|
|
|
|
<&> \fields ->
|
2023-05-24 16:51:56 +03:00
|
|
|
FromOpenJson
|
|
|
|
$ Aliased
|
2021-09-24 01:56:37 +03:00
|
|
|
( OpenJson
|
2021-06-08 06:50:24 +03:00
|
|
|
(ValueExpression $ ODBC.TextValue $ lbsToTxt $ J.encode occSessionVars)
|
2021-07-08 23:49:10 +03:00
|
|
|
(pure (sessField <$> fields))
|
2021-09-24 01:56:37 +03:00
|
|
|
)
|
2021-06-08 06:50:24 +03:00
|
|
|
"session"
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
sessField :: Text -> JsonFieldSpec
|
|
|
|
sessField var = StringField var Nothing
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2021-06-08 06:50:24 +03:00
|
|
|
sessionReference :: Text -> Aliased Expression
|
|
|
|
sessionReference var = Aliased (ColumnExpression (TSQL.FieldName var "session")) var
|
2021-09-22 13:43:05 +03:00
|
|
|
|
2022-01-03 20:16:24 +03:00
|
|
|
-- * Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
|
2021-09-22 13:43:05 +03:00
|
|
|
|
|
|
|
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
|
|
|
|
-- relationship information into a form that SQL Server can query against.
|
|
|
|
--
|
|
|
|
-- XXX: Currently unimplemented; the Postgres implementation uses
|
|
|
|
-- @jsonb_to_recordset@ to query the remote relationship, however this
|
|
|
|
-- functionality doesn't exist in SQL Server.
|
|
|
|
--
|
|
|
|
-- NOTE: The following typeclass constraints will be necessary when implementing
|
|
|
|
-- this function for real:
|
|
|
|
--
|
|
|
|
-- @
|
|
|
|
-- MonadQueryTags m
|
|
|
|
-- Backend 'MSSQL
|
|
|
|
-- @
|
|
|
|
msDBRemoteRelationshipPlan ::
|
|
|
|
forall m.
|
|
|
|
( MonadError QErr m
|
|
|
|
) =>
|
|
|
|
UserInfo ->
|
|
|
|
SourceName ->
|
|
|
|
SourceConfig 'MSSQL ->
|
|
|
|
-- | List of json objects, each of which becomes a row of the table.
|
|
|
|
NonEmpty J.Object ->
|
|
|
|
-- | The above objects have this schema
|
|
|
|
--
|
|
|
|
-- XXX: What is this for/what does this mean?
|
|
|
|
HashMap RQLTypes.FieldName (RQLTypes.Column 'MSSQL, RQLTypes.ScalarType 'MSSQL) ->
|
|
|
|
-- | This is a field name from the lhs that *has* to be selected in the
|
|
|
|
-- response along with the relationship.
|
|
|
|
RQLTypes.FieldName ->
|
2021-12-07 16:12:02 +03:00
|
|
|
(RQLTypes.FieldName, SourceRelationshipSelection 'MSSQL Void UnpreparedValue) ->
|
2023-01-25 10:12:53 +03:00
|
|
|
[HTTP.Header] ->
|
|
|
|
Maybe G.Name ->
|
2022-12-19 17:03:13 +03:00
|
|
|
Options.StringifyNumbers ->
|
2023-10-03 08:15:07 +03:00
|
|
|
m (DBStepInfo 'MSSQL, [ModelInfoPart])
|
2023-04-13 04:29:15 +03:00
|
|
|
msDBRemoteRelationshipPlan userInfo sourceName sourceConfig lhs lhsSchema argumentId relationship _headers _gName _stringifyNumbers = do
|
2023-01-09 13:50:43 +03:00
|
|
|
-- `stringifyNumbers` is not currently handled in any SQL Server operation
|
2022-04-06 10:18:59 +03:00
|
|
|
statement <- planSourceRelationship (_uiSession userInfo) lhs lhsSchema argumentId relationship
|
|
|
|
|
|
|
|
let printer = fromSelect statement
|
|
|
|
queryString = ODBC.renderQuery $ toQueryPretty printer
|
|
|
|
odbcQuery = runSelectQuery printer
|
|
|
|
|
2023-10-03 08:15:07 +03:00
|
|
|
modelNames <- getRSModelInfoGen sourceName ModelSourceTypeMSSQL $ snd relationship
|
|
|
|
let modelInfo = getModelInfoPartfromModelNames modelNames (ModelOperationType G.OperationTypeQuery)
|
|
|
|
|
|
|
|
pure $ (DBStepInfo @'MSSQL sourceName sourceConfig (Just queryString) odbcQuery (), modelInfo)
|
2022-04-06 10:18:59 +03:00
|
|
|
where
|
Allow backend execution to happen on the base app monad.
### Description
Each Backend executes queries against the database in a slightly different stack: Postgres uses its own `TXeT`, MSSQL uses a variant of it, BigQuery is simply in `ExceptT QErr IO`... To accommodate those variations, we had originally introduced an `ExecutionMonad b` type family in `BackendExecute`, allowing each backend to describe its own stack. It was then up to that backend's `BackendTransport` instance to implement running said stack, and converting the result back into our main app monad.
However, this was not without complications: `TraceT` is one of them: as it usually needs to be on the top of the stack, converting from one stack to the other implies the use `interpTraceT`, which is quite monstrous. Furthermore, as part of the Entitlement Services work, we're trying to move to a "Services" architecture in which the entire engine runs in one base monad, that delegates features and dependencies to monad constraints; and as a result we'd like to minimize the number of different monad stacks we have to maintain and translate from and to in the codebase.
To improve things, this PR changes `ExecutionMonad b` from an _absolute_ stack to a _relative_ one: i.e.: what needs to be stacked on top of our base monad for the execution. In `Transport`, we then only need to pop the top of the stack, and voila. This greatly simplifies the implementation of the backends, as there's no longer any need to do any stack transformation: MySQL's implementation becomes a `runIdentityT`! This also removes most mentions of `TraceT` from the execution code since it's no longer required: we can rely on the base monad's existing `MonadTrace` constraint.
To continue encapsulating monadic actions in `DBStepInfo` and avoid threading a bunch of `forall` all over the place, this PR introduces a small local helper: `OnBaseMonad`. One only downside of all this is that this requires adding `MonadBaseControl IO m` constraint all over the place: previously, we would run directly on `IO` and lift, and would therefore not need to bring that constraint all the way.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7789
GitOrigin-RevId: e9b2e431c5c47fa9851abf87545c0415ff6d1a12
2023-02-09 17:38:33 +03:00
|
|
|
runSelectQuery queryPrinter = OnBaseMonad do
|
2023-09-25 11:48:15 +03:00
|
|
|
let queryTx = encJFromText <$> Tx.forJsonQueryE defaultMSSQLTxErrorHandler (toQueryFlat queryPrinter)
|
2023-03-14 14:32:20 +03:00
|
|
|
mssqlRunReadOnly (_mscExecCtx sourceConfig) (fmap withNoStatistics queryTx)
|