mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 04:51:35 +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
208 lines
6.7 KiB
Haskell
208 lines
6.7 KiB
Haskell
module Hasura.Backends.MSSQL.Meta
|
|
( loadDBMetadata,
|
|
)
|
|
where
|
|
|
|
import Data.Aeson as Aeson
|
|
import Data.ByteString.UTF8 qualified as BSUTF8
|
|
import Data.FileEmbed (embedFile, makeRelativeToProject)
|
|
import Data.HashMap.Strict qualified as HM
|
|
import Data.HashSet qualified as HS
|
|
import Data.String
|
|
import Data.Text qualified as T
|
|
import Data.Text.Encoding qualified as T
|
|
import Database.MSSQL.Transaction qualified as Tx
|
|
import Database.ODBC.SQLServer qualified as ODBC
|
|
import Hasura.Backends.MSSQL.Connection
|
|
import Hasura.Backends.MSSQL.Instances.Types ()
|
|
import Hasura.Backends.MSSQL.Types
|
|
import Hasura.Base.Error
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.Types.Column
|
|
import Hasura.RQL.Types.Common (OID (..))
|
|
import Hasura.RQL.Types.Table
|
|
import Hasura.SQL.Backend
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Loader
|
|
|
|
loadDBMetadata :: (MonadIO m) => Tx.TxET QErr m (DBTablesMetadata 'MSSQL)
|
|
loadDBMetadata = do
|
|
let queryBytes = $(makeRelativeToProject "src-rsr/mssql_table_metadata.sql" >>= embedFile)
|
|
odbcQuery :: ODBC.Query = fromString . BSUTF8.toString $ queryBytes
|
|
sysTablesText <- runIdentity <$> Tx.singleRowQueryE fromMSSQLTxError odbcQuery
|
|
case Aeson.eitherDecodeStrict (T.encodeUtf8 sysTablesText) of
|
|
Left e -> throw500 $ T.pack $ "error loading sql server database schema: " <> e
|
|
Right sysTables -> pure $ HM.fromList $ map transformTable sysTables
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Local types
|
|
|
|
data SysTable = SysTable
|
|
{ staName :: Text,
|
|
staObjectId :: Int,
|
|
staJoinedSysColumn :: [SysColumn],
|
|
staJoinedSysSchema :: SysSchema,
|
|
staJoinedSysPrimaryKey :: Maybe SysPrimaryKey
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysTable where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
newtype SysPrimaryKeyColumn = SysPrimaryKeyColumn
|
|
{spkcName :: Text}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysPrimaryKeyColumn where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
data SysPrimaryKey = SysPrimaryKey
|
|
{ spkName :: Text,
|
|
spkIndexId :: Int,
|
|
spkColumns :: NESeq SysPrimaryKeyColumn
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysPrimaryKey where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
data SysSchema = SysSchema
|
|
{ ssName :: Text,
|
|
ssSchemaId :: Int
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysSchema where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
data SysColumn = SysColumn
|
|
{ scName :: Text,
|
|
scColumnId :: Int,
|
|
scUserTypeId :: Int,
|
|
scIsNullable :: Bool,
|
|
scIsIdentity :: Bool,
|
|
scJoinedSysType :: SysType,
|
|
scJoinedForeignKeyColumns :: [SysForeignKeyColumn]
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysColumn where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
data SysType = SysType
|
|
{ styName :: Text,
|
|
stySchemaId :: Int,
|
|
styUserTypeId :: Int
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysType where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
data SysForeignKeyColumn = SysForeignKeyColumn
|
|
{ sfkcConstraintObjectId :: Int,
|
|
sfkcConstraintColumnId :: Int,
|
|
sfkcParentObjectId :: Int,
|
|
sfkcParentColumnId :: Int,
|
|
sfkcReferencedObjectId :: Int,
|
|
sfkcReferencedColumnId :: Int,
|
|
sfkcJoinedReferencedTableName :: Text,
|
|
sfkcJoinedReferencedColumnName :: Text,
|
|
sfkcJoinedReferencedSysSchema :: SysSchema
|
|
}
|
|
deriving (Show, Generic)
|
|
|
|
instance FromJSON SysForeignKeyColumn where
|
|
parseJSON = genericParseJSON hasuraJSON
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Transform
|
|
|
|
transformTable :: SysTable -> (TableName, DBTableMetadata 'MSSQL)
|
|
transformTable tableInfo =
|
|
let schemaName = ssName $ staJoinedSysSchema tableInfo
|
|
tableName = TableName (staName tableInfo) schemaName
|
|
tableOID = OID $ staObjectId tableInfo
|
|
(columns, foreignKeys) = unzip $ transformColumn <$> staJoinedSysColumn tableInfo
|
|
foreignKeysMetadata = HS.fromList $ map ForeignKeyMetadata $ coalesceKeys $ concat foreignKeys
|
|
primaryKey = transformPrimaryKey <$> staJoinedSysPrimaryKey tableInfo
|
|
identityColumns =
|
|
map (ColumnName . scName) $
|
|
filter scIsIdentity $ staJoinedSysColumn tableInfo
|
|
in ( tableName,
|
|
DBTableMetadata
|
|
tableOID
|
|
columns
|
|
primaryKey
|
|
HS.empty -- no unique constraints?
|
|
foreignKeysMetadata
|
|
Nothing -- no views, only tables
|
|
Nothing -- no description
|
|
identityColumns
|
|
)
|
|
|
|
transformColumn ::
|
|
SysColumn ->
|
|
(RawColumnInfo 'MSSQL, [ForeignKey 'MSSQL])
|
|
transformColumn columnInfo =
|
|
let prciName = ColumnName $ scName columnInfo
|
|
prciPosition = scColumnId columnInfo
|
|
|
|
prciIsNullable = scIsNullable columnInfo
|
|
prciDescription = Nothing
|
|
prciType = parseScalarType $ styName $ scJoinedSysType columnInfo
|
|
foreignKeys =
|
|
scJoinedForeignKeyColumns columnInfo <&> \foreignKeyColumn ->
|
|
let _fkConstraint = Constraint "fk_mssql" $ OID $ sfkcConstraintObjectId foreignKeyColumn
|
|
|
|
schemaName = ssName $ sfkcJoinedReferencedSysSchema foreignKeyColumn
|
|
_fkForeignTable = TableName (sfkcJoinedReferencedTableName foreignKeyColumn) schemaName
|
|
_fkColumnMapping = HM.singleton prciName $ ColumnName $ sfkcJoinedReferencedColumnName foreignKeyColumn
|
|
in ForeignKey {..}
|
|
in (RawColumnInfo {..}, foreignKeys)
|
|
|
|
transformPrimaryKey :: SysPrimaryKey -> PrimaryKey 'MSSQL (Column 'MSSQL)
|
|
transformPrimaryKey (SysPrimaryKey {..}) =
|
|
let constraint = Constraint spkName $ OID spkIndexId
|
|
columns = (ColumnName . spkcName) <$> spkColumns
|
|
in PrimaryKey constraint columns
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Helpers
|
|
|
|
coalesceKeys :: [ForeignKey 'MSSQL] -> [ForeignKey 'MSSQL]
|
|
coalesceKeys = HM.elems . foldl' coalesce HM.empty
|
|
where
|
|
coalesce mapping fk@(ForeignKey constraint tableName _) = HM.insertWith combine (constraint, tableName) fk mapping
|
|
combine oldFK newFK = oldFK {_fkColumnMapping = (HM.union `on` _fkColumnMapping) oldFK newFK}
|
|
|
|
parseScalarType :: Text -> ScalarType
|
|
parseScalarType = \case
|
|
"char" -> CharType
|
|
"numeric" -> NumericType
|
|
"decimal" -> DecimalType
|
|
"money" -> DecimalType
|
|
"smallmoney" -> DecimalType
|
|
"int" -> IntegerType
|
|
"smallint" -> SmallintType
|
|
"float" -> FloatType
|
|
"real" -> RealType
|
|
"date" -> DateType
|
|
"time" -> Ss_time2Type
|
|
"varchar" -> VarcharType
|
|
"nchar" -> WcharType
|
|
"nvarchar" -> WvarcharType
|
|
"ntext" -> WtextType
|
|
"timestamp" -> TimestampType
|
|
"text" -> TextType
|
|
"binary" -> BinaryType
|
|
"bigint" -> BigintType
|
|
"tinyint" -> TinyintType
|
|
"varbinary" -> VarbinaryType
|
|
"bit" -> BitType
|
|
"uniqueidentifier" -> GuidType
|
|
"geography" -> GeographyType
|
|
"geometry" -> GeometryType
|
|
t -> UnknownType t
|