mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-22 15:01:39 +03:00
193 lines
7.2 KiB
Haskell
193 lines
7.2 KiB
Haskell
module Hasura.RQL.DDL.Relationship
|
|
( runCreateRelationship
|
|
, insertRelationshipToCatalog
|
|
, objRelP2Setup
|
|
, arrRelP2Setup
|
|
|
|
, runDropRel
|
|
, delRelFromCatalog
|
|
|
|
, runSetRelComment
|
|
)
|
|
where
|
|
|
|
import Hasura.Prelude
|
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
import qualified Data.HashSet as HS
|
|
import qualified Database.PG.Query as Q
|
|
|
|
import Data.Aeson.Types
|
|
import Data.Tuple (swap)
|
|
import Instances.TH.Lift ()
|
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
|
import Hasura.EncJSON
|
|
import Hasura.RQL.DDL.Deps
|
|
import Hasura.RQL.DDL.Permission (purgePerm)
|
|
import Hasura.RQL.Types
|
|
|
|
runCreateRelationship
|
|
:: (MonadTx m, CacheRWM m, HasSystemDefined m, ToJSON a)
|
|
=> RelType -> WithTable (RelDef a) -> m EncJSON
|
|
runCreateRelationship relType (WithTable tableName relDef) = do
|
|
insertRelationshipToCatalog tableName relType relDef
|
|
buildSchemaCacheFor $ MOTableObj tableName (MTORel (_rdName relDef) relType)
|
|
pure successMsg
|
|
|
|
insertRelationshipToCatalog
|
|
:: (MonadTx m, HasSystemDefined m, ToJSON a)
|
|
=> QualifiedTable
|
|
-> RelType
|
|
-> RelDef a
|
|
-> m ()
|
|
insertRelationshipToCatalog (QualifiedObject schema table) relType (RelDef name using comment) = do
|
|
systemDefined <- askSystemDefined
|
|
let args = (schema, table, name, relTypeToTxt relType, Q.AltJ using, comment, systemDefined)
|
|
liftTx $ Q.unitQE defaultTxErrorHandler query args True
|
|
where
|
|
query = [Q.sql|
|
|
INSERT INTO
|
|
hdb_catalog.hdb_relationship
|
|
(table_schema, table_name, rel_name, rel_type, rel_def, comment, is_system_defined)
|
|
VALUES ($1, $2, $3, $4, $5 :: jsonb, $6, $7) |]
|
|
|
|
runDropRel :: (MonadTx m, CacheRWM m) => DropRel -> m EncJSON
|
|
runDropRel (DropRel qt rn cascade) = do
|
|
depObjs <- collectDependencies
|
|
withNewInconsistentObjsCheck do
|
|
traverse_ purgeRelDep depObjs
|
|
liftTx $ delRelFromCatalog qt rn
|
|
buildSchemaCache
|
|
pure successMsg
|
|
where
|
|
collectDependencies = do
|
|
tabInfo <- askTableCoreInfo qt
|
|
_ <- askRelType (_tciFieldInfoMap tabInfo) rn ""
|
|
sc <- askSchemaCache
|
|
let depObjs = getDependentObjs sc (SOTableObj qt $ TORel rn)
|
|
when (depObjs /= [] && not cascade) $ reportDeps depObjs
|
|
pure depObjs
|
|
|
|
delRelFromCatalog
|
|
:: QualifiedTable
|
|
-> RelName
|
|
-> Q.TxE QErr ()
|
|
delRelFromCatalog (QualifiedObject sn tn) rn =
|
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
|
DELETE FROM
|
|
hdb_catalog.hdb_relationship
|
|
WHERE table_schema = $1
|
|
AND table_name = $2
|
|
AND rel_name = $3
|
|
|] (sn, tn, rn) True
|
|
|
|
objRelP2Setup
|
|
:: (QErrM m)
|
|
=> QualifiedTable
|
|
-> HashSet ForeignKey
|
|
-> RelDef ObjRelUsing
|
|
-> m (RelInfo, [SchemaDependency])
|
|
objRelP2Setup qt foreignKeys (RelDef rn ru _) = case ru of
|
|
RUManual rm -> do
|
|
let refqt = rmTable rm
|
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
|
mkDependency tableName reason col = SchemaDependency (SOTableObj tableName $ TOCol col) reason
|
|
dependencies = map (mkDependency qt DRLeftColumn) lCols
|
|
<> map (mkDependency refqt DRRightColumn) rCols
|
|
pure (RelInfo rn ObjRel (rmColumns rm) refqt True True, dependencies)
|
|
RUFKeyOn columnName -> do
|
|
ForeignKey constraint foreignTable colMap <- getRequiredFkey columnName (HS.toList foreignKeys)
|
|
let dependencies =
|
|
[ SchemaDependency (SOTableObj qt $ TOForeignKey (_cName constraint)) DRFkey
|
|
, SchemaDependency (SOTableObj qt $ TOCol columnName) DRUsingColumn
|
|
-- this needs to be added explicitly to handle the remote table being untracked. In this case,
|
|
-- neither the using_col nor the constraint name will help.
|
|
, SchemaDependency (SOTable foreignTable) DRRemoteTable
|
|
]
|
|
-- TODO(PDV?): this is too optimistic. Some object relationships are nullable, but
|
|
-- we are marking some as non-nullable here. This should really be done by
|
|
-- checking nullability in the SQL schema.
|
|
pure (RelInfo rn ObjRel colMap foreignTable False False, dependencies)
|
|
|
|
arrRelP2Setup
|
|
:: (QErrM m)
|
|
=> HashMap QualifiedTable (HashSet ForeignKey)
|
|
-> QualifiedTable
|
|
-> ArrRelDef
|
|
-> m (RelInfo, [SchemaDependency])
|
|
arrRelP2Setup foreignKeys qt (RelDef rn ru _) = case ru of
|
|
RUManual rm -> do
|
|
let refqt = rmTable rm
|
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
|
deps = map (\c -> SchemaDependency (SOTableObj qt $ TOCol c) DRLeftColumn) lCols
|
|
<> map (\c -> SchemaDependency (SOTableObj refqt $ TOCol c) DRRightColumn) rCols
|
|
pure (RelInfo rn ArrRel (rmColumns rm) refqt True True, deps)
|
|
RUFKeyOn (ArrRelUsingFKeyOn refqt refCol) -> do
|
|
foreignTableForeignKeys <- getTableInfo refqt foreignKeys
|
|
let keysThatReferenceUs = filter ((== qt) . _fkForeignTable) (HS.toList foreignTableForeignKeys)
|
|
ForeignKey constraint _ colMap <- getRequiredFkey refCol keysThatReferenceUs
|
|
let deps = [ SchemaDependency (SOTableObj refqt $ TOForeignKey (_cName constraint)) DRRemoteFkey
|
|
, SchemaDependency (SOTableObj refqt $ TOCol refCol) DRUsingColumn
|
|
-- we don't need to necessarily track the remote table like we did in
|
|
-- case of obj relationships as the remote table is indirectly
|
|
-- tracked by tracking the constraint name and 'using_col'
|
|
, SchemaDependency (SOTable refqt) DRRemoteTable
|
|
]
|
|
mapping = HM.fromList $ map swap $ HM.toList colMap
|
|
pure (RelInfo rn ArrRel mapping refqt False False, deps)
|
|
|
|
purgeRelDep :: (MonadTx m) => SchemaObjId -> m ()
|
|
purgeRelDep (SOTableObj tn (TOPerm rn pt)) = purgePerm tn rn pt
|
|
purgeRelDep d = throw500 $ "unexpected dependency of relationship : "
|
|
<> reportSchemaObj d
|
|
|
|
validateRelP1
|
|
:: (UserInfoM m, QErrM m, TableCoreInfoRM m)
|
|
=> QualifiedTable -> RelName -> m RelInfo
|
|
validateRelP1 qt rn = do
|
|
tabInfo <- askTableCoreInfo qt
|
|
askRelType (_tciFieldInfoMap tabInfo) rn ""
|
|
|
|
setRelCommentP2
|
|
:: (QErrM m, MonadTx m)
|
|
=> SetRelComment -> m EncJSON
|
|
setRelCommentP2 arc = do
|
|
liftTx $ setRelComment arc
|
|
return successMsg
|
|
|
|
runSetRelComment
|
|
:: (QErrM m, CacheRM m, MonadTx m, UserInfoM m)
|
|
=> SetRelComment -> m EncJSON
|
|
runSetRelComment defn = do
|
|
void $ validateRelP1 qt rn
|
|
setRelCommentP2 defn
|
|
where
|
|
SetRelComment qt rn _ = defn
|
|
|
|
setRelComment :: SetRelComment
|
|
-> Q.TxE QErr ()
|
|
setRelComment (SetRelComment (QualifiedObject sn tn) rn comment) =
|
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
|
UPDATE hdb_catalog.hdb_relationship
|
|
SET comment = $1
|
|
WHERE table_schema = $2
|
|
AND table_name = $3
|
|
AND rel_name = $4
|
|
|] (comment, sn, tn, rn) True
|
|
|
|
getRequiredFkey
|
|
:: (QErrM m)
|
|
=> PGCol
|
|
-> [ForeignKey]
|
|
-> m ForeignKey
|
|
getRequiredFkey col fkeys =
|
|
case filteredFkeys of
|
|
[] -> throw400 ConstraintError
|
|
"no foreign constraint exists on the given column"
|
|
[k] -> return k
|
|
_ -> throw400 ConstraintError
|
|
"more than one foreign key constraint exists on the given column"
|
|
where
|
|
filteredFkeys = filter ((== [col]) . HM.keys . _fkColumnMapping) fkeys
|