2022-03-16 03:39:21 +03:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
2021-05-27 18:06:13 +03:00
|
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
|
2022-01-11 01:54:51 +03:00
|
|
|
-- | MSSQL DDL RunSQL
|
|
|
|
--
|
|
|
|
-- Provides primitives for running raw text SQL on MSSQL backends.
|
2021-02-23 20:37:27 +03:00
|
|
|
module Hasura.Backends.MSSQL.DDL.RunSQL
|
2021-02-25 21:15:55 +03:00
|
|
|
( runSQL,
|
2021-05-27 18:06:13 +03:00
|
|
|
MSSQLRunSQL (..),
|
2021-10-22 17:49:15 +03:00
|
|
|
isSchemaCacheBuildRequiredRunSQL,
|
2021-02-25 21:15:55 +03:00
|
|
|
)
|
2021-02-23 20:37:27 +03:00
|
|
|
where
|
|
|
|
|
2021-10-22 17:49:15 +03:00
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
2022-01-14 17:08:17 +03:00
|
|
|
import Data.Aeson
|
2021-02-25 21:15:55 +03:00
|
|
|
import Data.Aeson qualified as J
|
2023-04-26 18:42:13 +03:00
|
|
|
import Data.HashMap.Strict qualified as HashMap
|
2021-05-27 18:06:13 +03:00
|
|
|
import Data.HashSet qualified as HS
|
2021-02-25 21:15:55 +03:00
|
|
|
import Data.String (fromString)
|
|
|
|
import Data.Text qualified as T
|
2021-10-22 17:49:15 +03:00
|
|
|
import Database.MSSQL.Transaction qualified as Tx
|
2021-02-25 21:15:55 +03:00
|
|
|
import Database.ODBC.Internal qualified as ODBC
|
2022-01-14 17:08:17 +03:00
|
|
|
import Database.ODBC.SQLServer qualified as ODBC hiding (query)
|
2021-02-25 21:15:55 +03:00
|
|
|
import Hasura.Backends.MSSQL.Connection
|
2021-05-27 18:06:13 +03:00
|
|
|
import Hasura.Backends.MSSQL.Meta
|
2022-02-07 17:11:49 +03:00
|
|
|
import Hasura.Backends.MSSQL.SQL.Error
|
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.Prelude
|
2021-05-27 18:06:13 +03:00
|
|
|
import Hasura.RQL.DDL.Schema
|
|
|
|
import Hasura.RQL.DDL.Schema.Diff
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
2023-04-24 21:35:48 +03:00
|
|
|
import Hasura.RQL.Types.BackendType
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Common
|
|
|
|
import Hasura.RQL.Types.Metadata hiding (tmTable)
|
|
|
|
import Hasura.RQL.Types.Metadata.Backend
|
|
|
|
import Hasura.RQL.Types.SchemaCache
|
|
|
|
import Hasura.RQL.Types.SchemaCache.Build
|
|
|
|
import Hasura.RQL.Types.SchemaCacheTypes
|
|
|
|
import Hasura.RQL.Types.Source
|
|
|
|
import Hasura.RQL.Types.Table
|
2021-10-22 17:49:15 +03:00
|
|
|
import Hasura.SQL.AnyBackend qualified as AB
|
2021-05-27 18:06:13 +03:00
|
|
|
import Hasura.Server.Utils (quoteRegex)
|
|
|
|
import Text.Regex.TDFA qualified as TDFA
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
data MSSQLRunSQL = MSSQLRunSQL
|
2021-02-25 21:15:55 +03:00
|
|
|
{ _mrsSql :: Text,
|
2022-07-29 17:05:03 +03:00
|
|
|
_mrsSource :: SourceName,
|
|
|
|
_mrsCascade :: Bool,
|
|
|
|
_mrsCheckMetadataConsistency :: Maybe Bool
|
2021-09-24 01:56:37 +03:00
|
|
|
}
|
2021-02-25 21:15:55 +03:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
2021-10-22 17:49:15 +03:00
|
|
|
instance J.FromJSON MSSQLRunSQL where
|
|
|
|
parseJSON = J.withObject "MSSQLRunSQL" $ \o -> do
|
|
|
|
_mrsSql <- o J..: "sql"
|
|
|
|
_mrsSource <- o J..:? "source" J..!= defaultSource
|
|
|
|
_mrsCascade <- o J..:? "cascade" J..!= False
|
|
|
|
_mrsCheckMetadataConsistency <- o J..:? "check_metadata_consistency"
|
|
|
|
pure MSSQLRunSQL {..}
|
|
|
|
|
|
|
|
instance J.ToJSON MSSQLRunSQL where
|
|
|
|
toJSON MSSQLRunSQL {..} =
|
|
|
|
J.object
|
|
|
|
[ "sql" J..= _mrsSql,
|
|
|
|
"source" J..= _mrsSource,
|
|
|
|
"cascade" J..= _mrsCascade,
|
|
|
|
"check_metadata_consistency" J..= _mrsCheckMetadataConsistency
|
|
|
|
]
|
2021-02-25 21:15:55 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
runSQL ::
|
2022-01-14 17:08:17 +03:00
|
|
|
forall m.
|
2021-10-22 17:49:15 +03:00
|
|
|
(MonadIO m, MonadBaseControl IO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
|
2021-05-27 18:06:13 +03:00
|
|
|
MSSQLRunSQL ->
|
|
|
|
m EncJSON
|
2021-10-22 17:49:15 +03:00
|
|
|
runSQL mssqlRunSQL@MSSQLRunSQL {..} = do
|
2023-04-18 08:36:02 +03:00
|
|
|
SourceInfo {..} <- askSourceInfo @'MSSQL _mrsSource
|
2022-01-14 17:08:17 +03:00
|
|
|
results <-
|
2021-10-22 17:49:15 +03:00
|
|
|
-- If the SQL modifies the schema of the database then check for any metadata changes
|
|
|
|
if isSchemaCacheBuildRequiredRunSQL mssqlRunSQL
|
|
|
|
then do
|
2023-04-18 08:36:02 +03:00
|
|
|
(results, metadataUpdater) <- runTx _siConfiguration $ withMetadataCheck _siTables
|
2021-10-22 17:49:15 +03:00
|
|
|
-- Build schema cache with updated metadata
|
|
|
|
withNewInconsistentObjsCheck $
|
|
|
|
buildSchemaCacheWithInvalidations mempty {ciSources = HS.singleton _mrsSource} metadataUpdater
|
|
|
|
pure results
|
2023-04-18 08:36:02 +03:00
|
|
|
else runTx _siConfiguration sqlQueryTx
|
2021-02-25 21:15:55 +03:00
|
|
|
pure $ encJFromJValue $ toResult results
|
2021-05-27 18:06:13 +03:00
|
|
|
where
|
2022-01-14 17:08:17 +03:00
|
|
|
runTx :: SourceConfig 'MSSQL -> Tx.TxET QErr m a -> m a
|
|
|
|
runTx sourceConfig =
|
2022-04-21 10:19:37 +03:00
|
|
|
liftEitherM . runMSSQLSourceWriteTx sourceConfig
|
2021-05-27 18:06:13 +03:00
|
|
|
|
2022-01-14 17:08:17 +03:00
|
|
|
sqlQueryTx :: Tx.TxET QErr m [[(ODBC.Column, ODBC.Value)]]
|
2021-10-22 17:49:15 +03:00
|
|
|
sqlQueryTx =
|
2022-02-07 17:11:49 +03:00
|
|
|
Tx.buildGenericQueryTxE runSqlMSSQLTxErrorHandler _mrsSql textToODBCQuery ODBC.query
|
2021-10-22 17:49:15 +03:00
|
|
|
where
|
2022-01-14 17:08:17 +03:00
|
|
|
textToODBCQuery :: Text -> ODBC.Query
|
|
|
|
textToODBCQuery = fromString . T.unpack
|
|
|
|
|
2022-02-07 17:11:49 +03:00
|
|
|
runSqlMSSQLTxErrorHandler :: Tx.MSSQLTxError -> QErr
|
|
|
|
runSqlMSSQLTxErrorHandler =
|
|
|
|
-- The SQL query is user provided. Capture all error classes as expected exceptions.
|
|
|
|
mkMSSQLTxErrorHandler (const True)
|
2021-10-22 17:49:15 +03:00
|
|
|
|
|
|
|
withMetadataCheck ::
|
|
|
|
TableCache 'MSSQL ->
|
2022-01-14 17:08:17 +03:00
|
|
|
Tx.TxET QErr m ([[(ODBC.Column, ODBC.Value)]], MetadataModifier)
|
|
|
|
withMetadataCheck tableCache = do
|
|
|
|
preActionTablesMeta <- toTableMeta <$> loadDBMetadata
|
|
|
|
results <- sqlQueryTx
|
|
|
|
postActionTablesMeta <- toTableMeta <$> loadDBMetadata
|
2023-04-26 18:42:13 +03:00
|
|
|
let trackedTablesMeta = filter (flip HashMap.member tableCache . tmTable) preActionTablesMeta
|
2022-01-14 17:08:17 +03:00
|
|
|
tablesDiff = getTablesDiff trackedTablesMeta postActionTablesMeta
|
2021-10-22 17:49:15 +03:00
|
|
|
|
2022-01-14 17:08:17 +03:00
|
|
|
-- Get indirect dependencies
|
2022-04-11 14:24:11 +03:00
|
|
|
indirectDeps <- getIndirectDependenciesFromTableDiff _mrsSource tablesDiff
|
2022-01-14 17:08:17 +03:00
|
|
|
-- Report indirect dependencies, if any, when cascade is not set
|
2022-07-01 13:49:31 +03:00
|
|
|
unless (null indirectDeps || _mrsCascade) $ reportDependentObjectsExist indirectDeps
|
2021-10-22 17:49:15 +03:00
|
|
|
|
2022-01-14 17:08:17 +03:00
|
|
|
metadataUpdater <- execWriterT $ do
|
|
|
|
-- Purge all the indirect dependents from state
|
|
|
|
for_ indirectDeps \case
|
|
|
|
SOSourceObj sourceName objectID -> do
|
|
|
|
AB.dispatchAnyBackend @BackendMetadata objectID $ purgeDependentObject sourceName >=> tell
|
|
|
|
_ ->
|
|
|
|
pure ()
|
|
|
|
processTablesDiff _mrsSource tableCache tablesDiff
|
2021-10-22 17:49:15 +03:00
|
|
|
|
2022-01-14 17:08:17 +03:00
|
|
|
pure (results, metadataUpdater)
|
2021-10-22 17:49:15 +03:00
|
|
|
where
|
|
|
|
toTableMeta :: DBTablesMetadata 'MSSQL -> [TableMeta 'MSSQL]
|
|
|
|
toTableMeta dbTablesMeta =
|
2023-04-26 18:42:13 +03:00
|
|
|
HashMap.toList dbTablesMeta <&> \(table, dbTableMeta) ->
|
2021-10-22 17:49:15 +03:00
|
|
|
TableMeta table dbTableMeta [] -- No computed fields
|
|
|
|
|
|
|
|
isSchemaCacheBuildRequiredRunSQL :: MSSQLRunSQL -> Bool
|
|
|
|
isSchemaCacheBuildRequiredRunSQL MSSQLRunSQL {..} =
|
|
|
|
fromMaybe (sqlContainsDDLKeyword _mrsSql) _mrsCheckMetadataConsistency
|
|
|
|
where
|
|
|
|
sqlContainsDDLKeyword :: Text -> Bool
|
|
|
|
sqlContainsDDLKeyword =
|
|
|
|
TDFA.match
|
|
|
|
$$( quoteRegex
|
|
|
|
TDFA.defaultCompOpt
|
|
|
|
{ TDFA.caseSensitive = False,
|
|
|
|
TDFA.multiline = True,
|
|
|
|
TDFA.lastStarGreedy = True
|
|
|
|
}
|
|
|
|
TDFA.defaultExecOpt
|
|
|
|
{ TDFA.captureGroups = False
|
|
|
|
}
|
|
|
|
"\\balter\\b|\\bdrop\\b|\\bsp_rename\\b"
|
|
|
|
)
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
toResult :: [[(ODBC.Column, ODBC.Value)]] -> RunSQLRes
|
|
|
|
toResult result = case result of
|
|
|
|
[] -> RunSQLRes "CommandOk" J.Null
|
|
|
|
(firstRow : _) -> RunSQLRes "TuplesOk" $ J.toJSON $ toHeader firstRow : toRows result
|
|
|
|
where
|
|
|
|
toRows = map $ map $ odbcValueToJValue . snd
|
|
|
|
toHeader = map $ J.String . ODBC.columnName . fst
|