mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
Support query explain for MSSQL (fixes #1024)
Co-authored-by: Abby Sassel <3883855+sassela@users.noreply.github.com> Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com> GitOrigin-RevId: 5a57d7570884a5469a947742e4ab9290a0cff55f
This commit is contained in:
parent
62f69a428a
commit
56c094b299
@ -8,12 +8,13 @@
|
||||
|
||||
- server: fix query execution of custom function containing a composite argument type
|
||||
- server: fix a bug in query validation that would cause some queries using default variable values to be rejected (fix #6867)
|
||||
- server: REST endpoint bugfix for UUID url params
|
||||
- server: custom URI schemes are now supported in CORS config (fix #5818) (#5940)
|
||||
- server: explaining/analyzing a query now works for mssql sources
|
||||
- server: fix MSSQL multiplexed subscriptions (fix #6887)
|
||||
- console: read-only modify page for mssql
|
||||
- console: filter out partitions from track table list and display partition info
|
||||
- console: fixes an issue where no schemas are listed on an MSSQL source
|
||||
- server: REST endpoint bugfix for UUID url params
|
||||
|
||||
## v2.0.0-alpha.10
|
||||
|
||||
|
@ -76,6 +76,16 @@ msDBQueryPlan _env _manager _reqHeaders userInfo _directives sourceName sourceCo
|
||||
. AB.mkAnyBackend
|
||||
$ DBStepInfo @'MSSQL sourceName sourceConfig (Just queryString) odbcQuery
|
||||
|
||||
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
|
||||
|
||||
msDBQueryExplain
|
||||
:: MonadError QErr m
|
||||
=> G.Name
|
||||
@ -85,28 +95,33 @@ msDBQueryExplain
|
||||
-> QueryDB 'MSSQL (UnpreparedValue 'MSSQL)
|
||||
-> m (AB.AnyBackend DBStepInfo)
|
||||
msDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
|
||||
select <- withExplain . fromSelect <$> planNoPlan userInfo qrf
|
||||
let queryString = ODBC.renderQuery $ toQueryPretty select
|
||||
select <- fromSelect <$> planNoPlan userInfo qrf
|
||||
let query = toQueryPretty select
|
||||
queryString = ODBC.renderQuery $ query
|
||||
pool = _mscConnectionPool sourceConfig
|
||||
-- TODO: execute `select` in separate batch
|
||||
-- https://github.com/hasura/graphql-engine-mono/issues/1024
|
||||
odbcQuery = runJSONPathQuery pool (toQueryFlat select) <&> \explainInfo ->
|
||||
encJFromJValue $ ExplainPlan fieldName (Just queryString) (Just [explainInfo])
|
||||
odbcQuery =
|
||||
withMSSQLPool
|
||||
pool
|
||||
(\conn -> do
|
||||
showplan <- runShowplan query conn
|
||||
pure (encJFromJValue $
|
||||
ExplainPlan
|
||||
fieldName
|
||||
(Just queryString)
|
||||
(Just showplan)))
|
||||
pure
|
||||
$ AB.mkAnyBackend
|
||||
$ DBStepInfo @'MSSQL sourceName sourceConfig Nothing odbcQuery
|
||||
|
||||
msDBLiveQueryExplain
|
||||
:: MonadError QErr m
|
||||
:: (MonadIO m, MonadError QErr m)
|
||||
=> LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL) -> m LiveQueryPlanExplanation
|
||||
msDBLiveQueryExplain (LiveQueryPlan plan _sourceConfig variables) = do
|
||||
let query = _plqpQuery plan
|
||||
-- TODO: execute `select` in separate batch
|
||||
-- https://github.com/hasura/graphql-engine-mono/issues/1024
|
||||
-- select = withExplain $ QueryPrinter query
|
||||
-- pool = _mscConnectionPool sourceConfig
|
||||
-- explainInfo <- runJSONPathQuery pool (toQueryFlat select)
|
||||
pure $ LiveQueryPlanExplanation (T.toTxt query) [] variables
|
||||
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
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Producing the correct SQL-level list comprehension to multiplex a query
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
module Hasura.Backends.MSSQL.ToQuery
|
||||
( fromSelect
|
||||
, withExplain
|
||||
, fromReselect
|
||||
, toSQL
|
||||
, toQueryFlat
|
||||
@ -178,15 +177,6 @@ fromSelect Select {..} = wrapFor selectFor result
|
||||
, fromFor selectFor
|
||||
]
|
||||
|
||||
withExplain :: Printer -> Printer
|
||||
withExplain p =
|
||||
SepByPrinter
|
||||
NewlinePrinter
|
||||
[ "SET SHOWPLAN_TEXT ON"
|
||||
, p
|
||||
, "SET SHOWPLAN_TEXT OFF"
|
||||
]
|
||||
|
||||
fromJoinSource :: JoinSource -> Printer
|
||||
fromJoinSource =
|
||||
\case
|
||||
|
@ -89,6 +89,7 @@ Additional details are provided by the documentation for individual bindings.
|
||||
-}
|
||||
module Hasura.GraphQL.Execute.LiveQuery.Plan
|
||||
( CohortId
|
||||
, dummyCohortId
|
||||
, newCohortId
|
||||
, CohortIdArray(..)
|
||||
, CohortVariablesArray(..)
|
||||
@ -108,6 +109,7 @@ import qualified Data.Aeson.Extended as J
|
||||
import qualified Data.Aeson.TH as J
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.HashSet as Set
|
||||
import qualified Data.UUID as UUID
|
||||
import qualified Data.UUID.V4 as UUID
|
||||
import qualified Database.PG.Query as Q
|
||||
import qualified Database.PG.Query.PTI as PTI
|
||||
@ -130,6 +132,9 @@ newtype CohortId = CohortId { unCohortId :: UUID }
|
||||
newCohortId :: (MonadIO m) => m CohortId
|
||||
newCohortId = CohortId <$> liftIO UUID.nextRandom
|
||||
|
||||
dummyCohortId :: CohortId
|
||||
dummyCohortId = CohortId UUID.nil
|
||||
|
||||
data CohortVariables
|
||||
= CohortVariables
|
||||
{ _cvSessionVariables :: !SessionVariables
|
||||
|
32
server/tests-py/queries/explain/permissions_query_mssql.yaml
Normal file
32
server/tests-py/queries/explain/permissions_query_mssql.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
description: Explain query with permissions
|
||||
url: /v1/graphql/explain
|
||||
status: 200
|
||||
response:
|
||||
- field: user
|
||||
plan:
|
||||
- "SELECT ISNULL((SELECT [t_user1].[id] AS [id],\n [t_user1].[name] AS [name],\n\
|
||||
\ [t_user1].[age] AS [age]\nFROM [dbo].[user] AS [t_user1]\nWHERE ((((([t_user1].[id])\
|
||||
\ = ((N'1')))\n OR ((([t_user1].[id]) IS NULL)\n AND (((N'1')) IS NULL)))))\n\
|
||||
FOR JSON PATH), '[]')"
|
||||
- " |--Compute Scalar(DEFINE:([Expr1003]=isnull([Expr1001],CONVERT_IMPLICIT(nvarchar(max),'[]',0))))"
|
||||
- " |--UDX(([t_user1].[id], [t_user1].[name], [t_user1].[age]))"
|
||||
- " |--Clustered Index Seek(OBJECT:([master].[dbo].[user].[PK__user__3213E83F2F718733]
|
||||
AS [t_user1]), SEEK:([t_user1].[id]=(1)) ORDERED FORWARD)"
|
||||
sql:
|
||||
"SELECT ISNULL((SELECT [t_user1].[id] AS [id],\n [t_user1].[name] AS\
|
||||
\ [name],\n [t_user1].[age] AS [age]\nFROM [dbo].[user] AS [t_user1]\nWHERE\
|
||||
\ ((((([t_user1].[id]) = ((N'1')))\n OR ((([t_user1].[id]) IS NULL)\n \
|
||||
\ AND (((N'1')) IS NULL)))))\nFOR JSON PATH), '[]')"
|
||||
query:
|
||||
user:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: "1"
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
user{
|
||||
id
|
||||
name
|
||||
age
|
||||
}
|
||||
}
|
27
server/tests-py/queries/explain/simple_query_mssql.yaml
Normal file
27
server/tests-py/queries/explain/simple_query_mssql.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
description: Explain query
|
||||
url: /v1/graphql/explain
|
||||
status: 200
|
||||
response:
|
||||
- field: user
|
||||
plan:
|
||||
- "SELECT ISNULL((SELECT [t_user1].[id] AS [id],\n [t_user1].[name] AS [name],\n\
|
||||
\ [t_user1].[age] AS [age]\nFROM [dbo].[user] AS [t_user1]\nFOR JSON PATH),\
|
||||
\ '[]')"
|
||||
- " |--Compute Scalar(DEFINE:([Expr1003]=isnull([Expr1001],CONVERT_IMPLICIT(nvarchar(max),'[]',0))))"
|
||||
- " |--UDX(([t_user1].[id], [t_user1].[name], [t_user1].[age]))"
|
||||
- " |--Clustered Index Scan(OBJECT:([master].[dbo].[user].[PK__user__3213E83F2F718733]
|
||||
AS [t_user1]))"
|
||||
sql:
|
||||
"SELECT ISNULL((SELECT [t_user1].[id] AS [id],\n [t_user1].[name] AS\
|
||||
\ [name],\n [t_user1].[age] AS [age]\nFROM [dbo].[user] AS [t_user1]\nFOR\
|
||||
\ JSON PATH), '[]')"
|
||||
query:
|
||||
query:
|
||||
query: |
|
||||
query {
|
||||
user{
|
||||
id
|
||||
name
|
||||
age
|
||||
}
|
||||
}
|
@ -875,17 +875,18 @@ class TestUnauthorizedRolePermission:
|
||||
def test_unauth_role(self, hge_ctx, transport):
|
||||
check_query_f(hge_ctx, self.dir() + '/unauthorized_role.yaml', transport, False)
|
||||
|
||||
@usefixtures('per_class_tests_db_state')
|
||||
@pytest.mark.parametrize("backend", ['postgres', 'mssql'])
|
||||
@usefixtures('per_class_tests_db_state', 'per_backend_tests')
|
||||
class TestGraphQLExplain:
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return 'queries/explain'
|
||||
|
||||
def test_simple_query(self, hge_ctx):
|
||||
self.with_admin_secret(hge_ctx, self.dir() + '/simple_query.yaml')
|
||||
def test_simple_query(self, hge_ctx, backend):
|
||||
self.with_admin_secret(hge_ctx, self.dir() + hge_ctx.backend_suffix('/simple_query') + ".yaml")
|
||||
|
||||
def test_permissions_query(self, hge_ctx):
|
||||
self.with_admin_secret(hge_ctx, self.dir() + '/permissions_query.yaml')
|
||||
def test_permissions_query(self, hge_ctx, backend):
|
||||
self.with_admin_secret(hge_ctx, self.dir() + hge_ctx.backend_suffix('/permissions_query') + ".yaml")
|
||||
|
||||
def with_admin_secret(self, hge_ctx, f):
|
||||
conf = get_conf_f(f)
|
||||
@ -895,8 +896,7 @@ class TestGraphQLExplain:
|
||||
headers['X-Hasura-Admin-Secret'] = hge_ctx.hge_key
|
||||
status_code, resp_json, _ = hge_ctx.anyq(conf['url'], conf['query'], headers)
|
||||
assert status_code == 200, resp_json
|
||||
# Comparing only with generated 'sql' since the 'plan' is not consistent
|
||||
# across all Postgres versions
|
||||
# Comparing only with generated 'sql' since the 'plan' may differ
|
||||
resp_sql = resp_json[0]['sql']
|
||||
exp_sql = conf['response'][0]['sql']
|
||||
assert resp_sql == exp_sql, resp_json
|
||||
|
Loading…
Reference in New Issue
Block a user