graphql-engine/server/src-lib/Hasura/Backends/MSSQL/Meta.hs
Rakesh Emmadi 09ae6af337 server/mssql: insert SQL generation and execution
>

### Description
>
Insert mutations for MSSQL backend. This PR implements execution logic.

### Changelog

- [x] `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

- [x] Server
- [x] Tests

### Related Issues
->
Close https://github.com/hasura/graphql-engine-mono/issues/2114

### Steps to test and verify
>
Track a MSSQL table and perform the generated insert mutation to test.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2248
Co-authored-by: Abby Sassel <3883855+sassela@users.noreply.github.com>
Co-authored-by: Philip Lykke Carlsen <358550+plcplc@users.noreply.github.com>
GitOrigin-RevId: 936f138c80d7a928180e6e7b0c4da64ecc1f7ebc
2021-10-01 12:53:15 +00:00

210 lines
6.6 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.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 ::
(MonadError QErr m, MonadIO m) =>
MSSQLPool ->
m (DBTablesMetadata 'MSSQL)
loadDBMetadata pool = do
let queryBytes = $(makeRelativeToProject "src-rsr/mssql_table_metadata.sql" >>= embedFile)
odbcQuery :: ODBC.Query = fromString . BSUTF8.toString $ queryBytes
sysTablesText <- runJSONPathQuery pool 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