2021-05-27 18:06:13 +03:00
|
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
|
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(..)
|
|
|
|
, sqlContainsDDLKeyword
|
2021-02-25 21:15:55 +03:00
|
|
|
)
|
2021-02-23 20:37:27 +03:00
|
|
|
where
|
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
|
2021-02-25 21:15:55 +03:00
|
|
|
import qualified Data.Aeson as J
|
2021-05-27 18:06:13 +03:00
|
|
|
import qualified Data.HashMap.Strict as M
|
|
|
|
import qualified Data.HashSet as HS
|
2021-02-25 21:15:55 +03:00
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Database.ODBC.Internal as ODBC
|
2021-05-27 18:06:13 +03:00
|
|
|
import qualified Text.Regex.TDFA as TDFA
|
2021-02-23 20:37:27 +03:00
|
|
|
|
2021-02-25 21:15:55 +03:00
|
|
|
import Data.Aeson.TH
|
|
|
|
import Data.String (fromString)
|
2021-02-23 20:37:27 +03:00
|
|
|
|
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
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2021-02-23 20:37:27 +03:00
|
|
|
import Hasura.EncJSON
|
2021-05-27 18:06:13 +03:00
|
|
|
import Hasura.RQL.DDL.Schema
|
|
|
|
import Hasura.RQL.DDL.Schema.Diff
|
|
|
|
import Hasura.RQL.Types hiding (TableName, tmTable)
|
|
|
|
import Hasura.Server.Utils (quoteRegex)
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
|
2021-02-25 21:15:55 +03:00
|
|
|
odbcValueToJValue :: ODBC.Value -> J.Value
|
|
|
|
odbcValueToJValue = \case
|
|
|
|
ODBC.TextValue t -> J.String t
|
|
|
|
ODBC.ByteStringValue b -> J.String $ bsToTxt b
|
|
|
|
ODBC.BinaryValue b -> J.String $ bsToTxt $ ODBC.unBinary b
|
|
|
|
ODBC.BoolValue b -> J.Bool b
|
|
|
|
ODBC.DoubleValue d -> J.toJSON d
|
|
|
|
ODBC.FloatValue f -> J.toJSON f
|
|
|
|
ODBC.IntValue i -> J.toJSON i
|
|
|
|
ODBC.ByteValue b -> J.toJSON b
|
|
|
|
ODBC.DayValue d -> J.toJSON d
|
|
|
|
ODBC.TimeOfDayValue td -> J.toJSON td
|
|
|
|
ODBC.LocalTimeValue l -> J.toJSON l
|
|
|
|
ODBC.NullValue -> J.Null
|
|
|
|
|
|
|
|
data MSSQLRunSQL
|
|
|
|
= MSSQLRunSQL
|
|
|
|
{ _mrsSql :: Text
|
|
|
|
, _mrsSource :: !SourceName
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
$(deriveJSON hasuraJSON ''MSSQLRunSQL)
|
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
runSQL
|
2021-03-02 07:26:31 +03:00
|
|
|
:: (MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m)
|
2021-05-27 18:06:13 +03:00
|
|
|
=> MSSQLRunSQL
|
|
|
|
-> m EncJSON
|
2021-02-23 20:37:27 +03:00
|
|
|
runSQL (MSSQLRunSQL sqlText source) = do
|
2021-09-23 15:37:56 +03:00
|
|
|
SourceInfo _ tableCache _ sourceConfig _ <- askSourceInfo @'MSSQL source
|
2021-05-27 18:06:13 +03:00
|
|
|
let pool = _mscConnectionPool sourceConfig
|
|
|
|
results <- if sqlContainsDDLKeyword sqlText then withMetadataCheck tableCache pool else runSQLQuery pool
|
2021-02-25 21:15:55 +03:00
|
|
|
pure $ encJFromJValue $ toResult results
|
2021-05-27 18:06:13 +03:00
|
|
|
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")
|
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
|