2022-03-16 03:39:21 +03:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
|
|
|
|
|
|
module Hasura.Backends.MySQL.Meta
|
|
|
|
( getMetadata,
|
|
|
|
)
|
|
|
|
where
|
2021-07-15 15:44:26 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
import Control.Exception (throw)
|
|
|
|
import Data.ByteString.Char8 qualified as B8
|
|
|
|
import Data.FileEmbed (embedFile, makeRelativeToProject)
|
|
|
|
import Data.HashMap.Strict qualified as HM
|
2022-05-10 18:43:24 +03:00
|
|
|
import Data.HashMap.Strict.NonEmpty qualified as NEHashMap
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.HashSet qualified as HS
|
|
|
|
import Data.Sequence.NonEmpty qualified as SNE
|
|
|
|
import Data.String (fromString)
|
|
|
|
import Database.MySQL.Base (Connection)
|
|
|
|
import Database.MySQL.Base.Types (Field (..))
|
|
|
|
import Database.MySQL.Simple (Only (Only), query)
|
|
|
|
import Database.MySQL.Simple.QueryResults (QueryResults (..), convertError)
|
|
|
|
import Database.MySQL.Simple.Result (Result, ResultError (..), convert)
|
|
|
|
import Hasura.Backends.MySQL.Instances.Types ()
|
|
|
|
import Hasura.Backends.MySQL.Types
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.Types.Column
|
|
|
|
import Hasura.RQL.Types.Common
|
|
|
|
import Hasura.RQL.Types.Table
|
|
|
|
import Hasura.SQL.Backend
|
|
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
2021-07-15 15:44:26 +03:00
|
|
|
|
|
|
|
getMetadata :: ConnSourceConfig -> Connection -> IO (DBTablesMetadata 'MySQL)
|
2021-09-24 01:56:37 +03:00
|
|
|
getMetadata ConnSourceConfig {_cscDatabase} scConnection = do
|
2021-07-15 15:44:26 +03:00
|
|
|
let sql = $(makeRelativeToProject "src-rsr/mysql_table_metadata.sql" >>= embedFile)
|
|
|
|
results :: [InformationSchema] <- query scConnection (fromString . B8.unpack $ sql) (Only _cscDatabase)
|
|
|
|
pure (mkMetadata results)
|
|
|
|
|
|
|
|
mkMetadata :: [InformationSchema] -> DBTablesMetadata 'MySQL
|
|
|
|
mkMetadata = foldr mergeMetadata HM.empty
|
|
|
|
|
|
|
|
mergeMetadata :: InformationSchema -> DBTablesMetadata 'MySQL -> DBTablesMetadata 'MySQL
|
2021-09-24 01:56:37 +03:00
|
|
|
mergeMetadata InformationSchema {..} =
|
2021-07-15 15:44:26 +03:00
|
|
|
HM.insertWith
|
|
|
|
mergeDBTableMetadata
|
2021-10-23 14:42:20 +03:00
|
|
|
(TableName {name = isTableName, schema = pure isTableSchema})
|
2021-09-24 01:56:37 +03:00
|
|
|
$ DBTableMetadata
|
|
|
|
{ _ptmiOid = OID 0,
|
|
|
|
_ptmiColumns =
|
2021-07-15 15:44:26 +03:00
|
|
|
[ RawColumnInfo
|
2022-01-19 11:37:50 +03:00
|
|
|
{ rciName = Column isColumnName,
|
|
|
|
rciPosition = fromIntegral isOrdinalPosition,
|
|
|
|
rciType = parseMySQLScalarType isColumnType, -- TODO: This needs to become more precise by considering Field length and character-set
|
|
|
|
rciIsNullable = isIsNullable == "YES", -- ref: https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html
|
|
|
|
rciDescription = Just $ G.Description isColumnComment,
|
|
|
|
rciMutability = ColumnMutability {_cmIsInsertable = True, _cmIsUpdatable = True}
|
2021-07-15 15:44:26 +03:00
|
|
|
}
|
2021-09-24 01:56:37 +03:00
|
|
|
],
|
|
|
|
_ptmiPrimaryKey =
|
|
|
|
if isColumnKey == PRI
|
|
|
|
then
|
|
|
|
Just $
|
|
|
|
PrimaryKey
|
|
|
|
( Constraint
|
|
|
|
(ConstraintName $ fromMaybe "" isConstraintName)
|
|
|
|
(OID $ fromIntegral $ fromMaybe 0 isConstraintOrdinalPosition)
|
|
|
|
)
|
|
|
|
(SNE.singleton (Column isColumnName))
|
|
|
|
else Nothing,
|
|
|
|
_ptmiUniqueConstraints =
|
|
|
|
if isColumnKey == UNI
|
|
|
|
then
|
|
|
|
HS.singleton
|
|
|
|
( Constraint
|
|
|
|
(ConstraintName $ fromMaybe "" isConstraintName)
|
|
|
|
(OID $ fromIntegral $ fromMaybe 0 isConstraintOrdinalPosition)
|
|
|
|
)
|
|
|
|
else HS.empty,
|
|
|
|
_ptmiForeignKeys =
|
|
|
|
if isColumnKey == MUL
|
|
|
|
then
|
|
|
|
HS.singleton
|
|
|
|
( ForeignKeyMetadata
|
|
|
|
( ForeignKey
|
|
|
|
( Constraint
|
|
|
|
(ConstraintName $ fromMaybe "" isConstraintName)
|
|
|
|
(OID $ fromIntegral $ fromMaybe 0 isConstraintOrdinalPosition)
|
|
|
|
)
|
|
|
|
( TableName
|
2021-10-23 14:42:20 +03:00
|
|
|
{ name = (fromMaybe "" isReferencedTableName),
|
|
|
|
schema = isReferencedTableSchema
|
|
|
|
}
|
2021-09-24 01:56:37 +03:00
|
|
|
)
|
2022-05-10 18:43:24 +03:00
|
|
|
( NEHashMap.singleton
|
2021-09-24 01:56:37 +03:00
|
|
|
(Column isColumnName)
|
|
|
|
(Column $ fromMaybe "" isReferencedColumnName)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
else HS.empty,
|
|
|
|
_ptmiViewInfo = Nothing,
|
|
|
|
_ptmiDescription = Nothing,
|
|
|
|
_ptmiExtraTableMetadata = ()
|
2021-07-15 15:44:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mergeDBTableMetadata :: DBTableMetadata 'MySQL -> DBTableMetadata 'MySQL -> DBTableMetadata 'MySQL
|
|
|
|
mergeDBTableMetadata new existing =
|
|
|
|
DBTableMetadata
|
2021-09-24 01:56:37 +03:00
|
|
|
{ _ptmiOid = OID 0,
|
|
|
|
_ptmiColumns = _ptmiColumns existing <> _ptmiColumns new,
|
|
|
|
_ptmiPrimaryKey = _ptmiPrimaryKey existing <|> _ptmiPrimaryKey new, -- Only one column can be a PRIMARY KEY, so this is just a courtesy choice.
|
|
|
|
_ptmiUniqueConstraints = _ptmiUniqueConstraints existing <> _ptmiUniqueConstraints new, -- union
|
|
|
|
_ptmiForeignKeys = _ptmiForeignKeys existing <> _ptmiForeignKeys new, -- union
|
|
|
|
_ptmiViewInfo = _ptmiViewInfo existing <|> _ptmiViewInfo new,
|
|
|
|
_ptmiDescription = _ptmiDescription existing <|> _ptmiDescription new,
|
|
|
|
_ptmiExtraTableMetadata = ()
|
2021-07-15 15:44:26 +03:00
|
|
|
}
|
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
data InformationSchema = InformationSchema
|
|
|
|
{ isTableSchema :: !Text,
|
|
|
|
isTableName :: !Text,
|
|
|
|
isColumnName :: !Text,
|
|
|
|
isOrdinalPosition :: !Word,
|
|
|
|
isColumnDefault :: !(Maybe Text),
|
|
|
|
isIsNullable :: !Text,
|
|
|
|
isDataType :: !(Maybe Text),
|
|
|
|
isColumnType :: !Text,
|
|
|
|
isColumnKey :: !InformationSchemaColumnKey,
|
|
|
|
isColumnComment :: !Text,
|
|
|
|
isConstraintName :: !(Maybe Text),
|
|
|
|
isConstraintOrdinalPosition :: !(Maybe Word),
|
|
|
|
isPositionInUniqueConstraint :: !(Maybe Word),
|
|
|
|
isReferencedTableSchema :: !(Maybe Text),
|
|
|
|
isReferencedTableName :: !(Maybe Text),
|
|
|
|
isReferencedColumnName :: !(Maybe Text)
|
|
|
|
}
|
|
|
|
deriving (Show, Eq, Generic)
|
2021-07-15 15:44:26 +03:00
|
|
|
|
|
|
|
instance QueryResults InformationSchema where
|
|
|
|
convertResults
|
2021-09-24 01:56:37 +03:00
|
|
|
[ fisTableSchema,
|
|
|
|
fisTableName,
|
|
|
|
fisColumnName,
|
|
|
|
fisOrdinalPosition,
|
|
|
|
fisColumnDefault,
|
|
|
|
fisIsNullable,
|
|
|
|
fisDataType,
|
|
|
|
fisColumnType,
|
|
|
|
fisColumnKey,
|
|
|
|
fisColumnComment,
|
|
|
|
fisConstraintName,
|
|
|
|
fisConstraintOrdinalPosition,
|
|
|
|
fisPositionInUniqueConstraint,
|
|
|
|
fisReferencedTableSchema,
|
|
|
|
fisReferencedTableName,
|
|
|
|
fisReferencedColumnName
|
|
|
|
]
|
|
|
|
[ visTableSchema,
|
|
|
|
visTableName,
|
|
|
|
visColumnName,
|
|
|
|
visOrdinalPosition,
|
|
|
|
visColumnDefault,
|
|
|
|
visIsNullable,
|
|
|
|
visDataType,
|
|
|
|
visColumnType,
|
|
|
|
visColumnKey,
|
|
|
|
visColumnComment,
|
|
|
|
visConstraintName,
|
|
|
|
visConstraintOrdinalPosition,
|
|
|
|
visPositionInUniqueConstraint,
|
|
|
|
visReferencedTableSchema,
|
|
|
|
visReferencedTableName,
|
|
|
|
visReferencedColumnName
|
|
|
|
] =
|
|
|
|
InformationSchema
|
|
|
|
(convert fisTableSchema visTableSchema)
|
|
|
|
(convert fisTableName visTableName)
|
|
|
|
(convert fisColumnName visColumnName)
|
|
|
|
(convert fisOrdinalPosition visOrdinalPosition)
|
|
|
|
(convert fisColumnDefault visColumnDefault)
|
|
|
|
(convert fisIsNullable visIsNullable)
|
|
|
|
(convert fisDataType visDataType)
|
|
|
|
(convert fisColumnType visColumnType)
|
|
|
|
(convert fisColumnKey visColumnKey)
|
|
|
|
(convert fisColumnComment visColumnComment)
|
|
|
|
(convert fisConstraintName visConstraintName)
|
|
|
|
(convert fisConstraintOrdinalPosition visConstraintOrdinalPosition)
|
|
|
|
(convert fisPositionInUniqueConstraint visPositionInUniqueConstraint)
|
|
|
|
(convert fisReferencedTableSchema visReferencedTableSchema)
|
|
|
|
(convert fisReferencedTableName visReferencedTableName)
|
|
|
|
(convert fisReferencedColumnName visReferencedColumnName)
|
2021-07-15 15:44:26 +03:00
|
|
|
convertResults fs vs = convertError fs vs 16
|
|
|
|
|
|
|
|
data InformationSchemaColumnKey
|
|
|
|
= PRI
|
|
|
|
| UNI
|
|
|
|
| MUL
|
2021-09-24 01:56:37 +03:00
|
|
|
| -- | This field isn't NULLable and uses empty strings, by the looks of it.
|
|
|
|
BLANK
|
|
|
|
deriving (Show, Read, Eq, Generic)
|
|
|
|
|
2021-07-15 15:44:26 +03:00
|
|
|
instance Result InformationSchemaColumnKey where
|
|
|
|
convert f mbs =
|
|
|
|
case mbs of
|
|
|
|
Nothing ->
|
|
|
|
throw $
|
|
|
|
UnexpectedNull
|
|
|
|
(show $ fieldType f)
|
|
|
|
"InformationSchemaColumnKey"
|
|
|
|
(B8.unpack $ fieldName f)
|
|
|
|
"COLUMN_KEY in INFORMATION_SCHEMA cannot be NULL"
|
|
|
|
Just bs -> case bs of
|
|
|
|
-- Could have used 'readMaybe' here, but we need the specific errors.
|
2021-08-17 01:19:24 +03:00
|
|
|
"PRI" -> PRI -- primary key
|
|
|
|
"UNI" -> UNI -- unique key
|
|
|
|
"MUL" -> MUL -- foreign key (`MUL`tiple allowed, non-unique key)
|
2021-07-15 15:44:26 +03:00
|
|
|
"" -> BLANK
|
|
|
|
x ->
|
|
|
|
throw $
|
|
|
|
ConversionFailed
|
|
|
|
(show $ fieldType f)
|
|
|
|
"InformationSchemaColumnKey"
|
|
|
|
(B8.unpack $ fieldName f)
|
|
|
|
("COLUMN_KEY in INFORMATION_SCHEMA has value extraneous to the expected ENUM: " <> B8.unpack x)
|