graphql-engine/server/src-lib/Hasura/Backends/MSSQL/DDL/RunSQL.hs

87 lines
3.0 KiB
Haskell
Raw Normal View History

{-# LANGUAGE ViewPatterns #-}
module Hasura.Backends.MSSQL.DDL.RunSQL
( runSQL,
MSSQLRunSQL (..),
sqlContainsDDLKeyword,
)
where
import Data.Aeson qualified as J
import Data.Aeson.TH
import Data.HashMap.Strict qualified as M
import Data.HashSet qualified as HS
import Data.String (fromString)
import Data.Text qualified as T
import Database.ODBC.Internal qualified as ODBC
import Hasura.Backends.MSSQL.Connection
import Hasura.Backends.MSSQL.Meta
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Prelude
import Hasura.RQL.DDL.Schema
import Hasura.RQL.DDL.Schema.Diff
import Hasura.RQL.Types hiding (TableName, tmTable)
import Hasura.Server.Utils (quoteRegex)
import Text.Regex.TDFA qualified as TDFA
data MSSQLRunSQL = MSSQLRunSQL
{ _mrsSql :: Text,
_mrsSource :: !SourceName
}
deriving (Show, Eq)
$(deriveJSON hasuraJSON ''MSSQLRunSQL)
runSQL ::
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
MSSQLRunSQL ->
m EncJSON
runSQL (MSSQLRunSQL sqlText source) = do
SourceInfo _ tableCache _ sourceConfig _ <- askSourceInfo @'MSSQL source
let pool = _mscConnectionPool sourceConfig
results <- if sqlContainsDDLKeyword sqlText then withMetadataCheck tableCache pool else runSQLQuery pool
pure $ encJFromJValue $ toResult results
where
runSQLQuery pool = withMSSQLPool pool $ \conn ->
ODBC.query conn $ fromString $ T.unpack sqlText
toTableMeta dbTablesMeta =
M.toList dbTablesMeta <&> \(table, dbTableMeta) ->
TableMeta table dbTableMeta [] -- No computed fields
withMetadataCheck tableCache pool = do
-- If the SQL modifies the schema of the database then check for any metadata changes
preActionTablesMeta <- toTableMeta <$> loadDBMetadata pool
results <- runSQLQuery pool
postActionTablesMeta <- toTableMeta <$> loadDBMetadata pool
let trackedTablesMeta = filter (flip M.member tableCache . tmTable) preActionTablesMeta
schemaDiff = getSchemaDiff trackedTablesMeta postActionTablesMeta
metadataUpdater <- execWriterT $ processSchemaDiff source tableCache schemaDiff
-- Build schema cache with updated metadata
withNewInconsistentObjsCheck $
buildSchemaCacheWithInvalidations mempty {ciSources = HS.singleton source} metadataUpdater
pure results
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"
)
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