mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
8e88e73a52
<!-- Thank you for ss in the Title above ^ --> ## Description <!-- Please fill thier. --> <!-- Describe the changes from a user's perspective --> We don't have dependency reporting mechanism for `mssql_run_sql` API i.e when a database object (table, column etc.) is dropped through the API we should raise an exception if any dependencies (relationships, permissions etc.) with the database object exists in the metadata. This PR addresses the above mentioned problem by -> Integrating transaction to the API to rollback the SQL query execution if dependencies exists and exception is thrown -> Accepting `cascade` optional field in the API payload to drop the dependencies, if any -> Accepting `check_metadata_consistency` optional field to bypass (if value set to `false`) the dependency check ### Related Issues <!-- Please make surt title --> <!-- Add the issue number below (e.g. #234) --> Close #1853 ### Solution and Design <!-- How is this iss --> <!-- It's better if we elaborate --> The design/solution follows the `run_sql` API implementation for Postgres backend. ### Steps to test and verify <!-- If this is a fehis is a bug-fix, how do we verify the fix? --> - Create author - article tables and track them - Defined object and array relationships - Try to drop the article table without cascade or cascade set to `false` - The server should raise the relationship dependency exists exception ## Changelog - ✅ `CHANGELOG.md` is updated with user-facing content relevant to this PR. If no changelog is required, then add the `no-changelog-required` label. ## Affected components <!-- Remove non-affected components from the list --> - ✅ Server - ❎ Console - ❎ CLI - ❎ Docs - ❎ Community Content - ❎ Build System - ✅ Tests - ❎ Other (list it) PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2636 GitOrigin-RevId: 0ab152295394056c4ca6f02923142a1658ad25dc
218 lines
6.2 KiB
Haskell
218 lines
6.2 KiB
Haskell
module Hasura.Backends.MSSQL.Connection where
|
|
|
|
import Control.Exception.Lifted qualified as EL
|
|
import Control.Monad.Trans.Control
|
|
import Data.Aeson
|
|
import Data.Aeson qualified as J
|
|
import Data.Aeson.Casing
|
|
import Data.Aeson.TH
|
|
import Data.Environment qualified as Env
|
|
import Data.Pool qualified as Pool
|
|
import Data.Text (pack, unpack)
|
|
import Database.MSSQL.Transaction
|
|
import Database.ODBC.SQLServer qualified as ODBC
|
|
import Hasura.Base.Error
|
|
import Hasura.Incremental (Cacheable (..))
|
|
import Hasura.Prelude
|
|
|
|
-- | ODBC connection string for MSSQL server
|
|
newtype MSSQLConnectionString = MSSQLConnectionString {unMSSQLConnectionString :: Text}
|
|
deriving (Show, Eq, ToJSON, FromJSON, Cacheable, Hashable, NFData)
|
|
|
|
data InputConnectionString
|
|
= RawString !MSSQLConnectionString
|
|
| FromEnvironment !Text
|
|
deriving stock (Show, Eq, Generic)
|
|
|
|
instance Cacheable InputConnectionString
|
|
|
|
instance Hashable InputConnectionString
|
|
|
|
instance NFData InputConnectionString
|
|
|
|
instance ToJSON InputConnectionString where
|
|
toJSON =
|
|
\case
|
|
(RawString m) -> toJSON m
|
|
(FromEnvironment wEnv) -> object ["from_env" .= wEnv]
|
|
|
|
instance FromJSON InputConnectionString where
|
|
parseJSON =
|
|
\case
|
|
(Object o) -> FromEnvironment <$> o .: "from_env"
|
|
s@(String _) -> RawString <$> parseJSON s
|
|
_ -> fail "one of string or object must be provided"
|
|
|
|
data MSSQLPoolSettings = MSSQLPoolSettings
|
|
{ _mpsMaxConnections :: !Int,
|
|
_mpsIdleTimeout :: !Int
|
|
}
|
|
deriving (Show, Eq, Generic)
|
|
|
|
instance Cacheable MSSQLPoolSettings
|
|
|
|
instance Hashable MSSQLPoolSettings
|
|
|
|
instance NFData MSSQLPoolSettings
|
|
|
|
$(deriveToJSON hasuraJSON ''MSSQLPoolSettings)
|
|
|
|
instance FromJSON MSSQLPoolSettings where
|
|
parseJSON = withObject "MSSQL pool settings" $ \o ->
|
|
MSSQLPoolSettings
|
|
<$> o .:? "max_connections" .!= _mpsMaxConnections defaultMSSQLPoolSettings
|
|
<*> o .:? "idle_timeout" .!= _mpsIdleTimeout defaultMSSQLPoolSettings
|
|
|
|
defaultMSSQLPoolSettings :: MSSQLPoolSettings
|
|
defaultMSSQLPoolSettings =
|
|
MSSQLPoolSettings
|
|
{ _mpsMaxConnections = 50,
|
|
_mpsIdleTimeout = 5
|
|
}
|
|
|
|
data MSSQLConnectionInfo = MSSQLConnectionInfo
|
|
{ _mciConnectionString :: !InputConnectionString,
|
|
_mciPoolSettings :: !MSSQLPoolSettings
|
|
}
|
|
deriving (Show, Eq, Generic)
|
|
|
|
instance Cacheable MSSQLConnectionInfo
|
|
|
|
instance Hashable MSSQLConnectionInfo
|
|
|
|
instance NFData MSSQLConnectionInfo
|
|
|
|
$(deriveToJSON hasuraJSON ''MSSQLConnectionInfo)
|
|
|
|
instance FromJSON MSSQLConnectionInfo where
|
|
parseJSON = withObject "Object" $ \o ->
|
|
MSSQLConnectionInfo
|
|
<$> ((o .: "database_url") <|> (o .: "connection_string"))
|
|
<*> o .:? "pool_settings" .!= defaultMSSQLPoolSettings
|
|
|
|
data MSSQLConnConfiguration = MSSQLConnConfiguration
|
|
{ _mccConnectionInfo :: !MSSQLConnectionInfo
|
|
}
|
|
deriving (Show, Eq, Generic)
|
|
|
|
instance Cacheable MSSQLConnConfiguration
|
|
|
|
instance Hashable MSSQLConnConfiguration
|
|
|
|
instance NFData MSSQLConnConfiguration
|
|
|
|
$(deriveJSON hasuraJSON ''MSSQLConnConfiguration)
|
|
|
|
newtype MSSQLPool = MSSQLPool {unMSSQLPool :: Pool.Pool ODBC.Connection}
|
|
|
|
createMSSQLPool ::
|
|
MonadIO m =>
|
|
QErrM m =>
|
|
MSSQLConnectionInfo ->
|
|
Env.Environment ->
|
|
m (MSSQLConnectionString, MSSQLPool)
|
|
createMSSQLPool (MSSQLConnectionInfo iConnString MSSQLPoolSettings {..}) env = do
|
|
connString <- resolveInputConnectionString env iConnString
|
|
pool <-
|
|
liftIO $
|
|
MSSQLPool
|
|
<$> Pool.createPool
|
|
(ODBC.connect $ unMSSQLConnectionString connString)
|
|
ODBC.close
|
|
1
|
|
(fromIntegral _mpsIdleTimeout)
|
|
_mpsMaxConnections
|
|
pure (connString, pool)
|
|
|
|
resolveInputConnectionString ::
|
|
QErrM m =>
|
|
Env.Environment ->
|
|
InputConnectionString ->
|
|
m MSSQLConnectionString
|
|
resolveInputConnectionString env =
|
|
\case
|
|
(RawString cs) -> pure cs
|
|
(FromEnvironment envVar) -> MSSQLConnectionString <$> getEnv env envVar
|
|
|
|
getEnv :: QErrM m => Env.Environment -> Text -> m Text
|
|
getEnv env k = do
|
|
let mEnv = Env.lookupEnv env (unpack k)
|
|
case mEnv of
|
|
Nothing -> throw400 NotFound $ "environment variable '" <> k <> "' not set"
|
|
Just envVal -> return (pack envVal)
|
|
|
|
drainMSSQLPool :: MSSQLPool -> IO ()
|
|
drainMSSQLPool (MSSQLPool pool) =
|
|
Pool.destroyAllResources pool
|
|
|
|
odbcExceptionToJSONValue :: ODBC.ODBCException -> Value
|
|
odbcExceptionToJSONValue =
|
|
$(mkToJSON defaultOptions {constructorTagModifier = snakeCase} ''ODBC.ODBCException)
|
|
|
|
runJSONPathQuery ::
|
|
(MonadError QErr m, MonadIO m, MonadBaseControl IO m) =>
|
|
MSSQLPool ->
|
|
ODBC.Query ->
|
|
m Text
|
|
runJSONPathQuery pool query =
|
|
mconcat <$> withMSSQLPool pool (`ODBC.query` query)
|
|
|
|
withMSSQLPool ::
|
|
(MonadIO m, MonadBaseControl IO m, MonadError QErr m) =>
|
|
MSSQLPool ->
|
|
(ODBC.Connection -> m a) ->
|
|
m a
|
|
withMSSQLPool (MSSQLPool pool) f = do
|
|
res <- EL.try $ Pool.withResource pool f
|
|
onLeft res $ \e ->
|
|
throw500WithDetail "sql server exception" $ odbcExceptionToJSONValue e
|
|
|
|
data MSSQLSourceConfig = MSSQLSourceConfig
|
|
{ _mscConnectionString :: !MSSQLConnectionString,
|
|
_mscConnectionPool :: !MSSQLPool
|
|
}
|
|
deriving (Generic)
|
|
|
|
instance Show MSSQLSourceConfig where
|
|
show = show . _mscConnectionString
|
|
|
|
instance Eq MSSQLSourceConfig where
|
|
MSSQLSourceConfig connStr1 _ == MSSQLSourceConfig connStr2 _ =
|
|
connStr1 == connStr2
|
|
|
|
instance Cacheable MSSQLSourceConfig where
|
|
unchanged _ = (==)
|
|
|
|
instance ToJSON MSSQLSourceConfig where
|
|
toJSON = toJSON . _mscConnectionString
|
|
|
|
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
|
|
|
|
newtype MSSQLConnErr = MSSQLConnErr {getConnErr :: Text}
|
|
deriving (Show, Eq, ToJSON)
|
|
|
|
fromMSSQLTxError :: MSSQLTxError -> QErr
|
|
fromMSSQLTxError (MSSQLTxError query exception) =
|
|
(internalError "database query error")
|
|
{ qeInternal =
|
|
Just $
|
|
ExtraInternal $
|
|
object
|
|
[ "query" .= ODBC.renderQuery query,
|
|
"exception" .= odbcExceptionToJSONValue exception
|
|
]
|
|
}
|