2019-03-01 12:17:22 +03:00
|
|
|
module Hasura.RQL.DDL.Relationship
|
2019-11-20 21:21:30 +03:00
|
|
|
( runCreateRelationship
|
2019-03-25 11:56:29 +03:00
|
|
|
, objRelP2Setup
|
2019-03-01 12:17:22 +03:00
|
|
|
, arrRelP2Setup
|
2019-11-20 21:21:30 +03:00
|
|
|
|
2019-03-01 12:17:22 +03:00
|
|
|
, runDropRel
|
2020-12-08 17:22:31 +03:00
|
|
|
, dropRelationshipInMetadata
|
2019-11-20 21:21:30 +03:00
|
|
|
|
2019-03-01 12:17:22 +03:00
|
|
|
, runSetRelComment
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
2018-11-23 16:02:46 +03:00
|
|
|
import Hasura.Prelude
|
2020-10-27 16:53:49 +03:00
|
|
|
|
2021-02-14 09:07:52 +03:00
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
|
|
import qualified Data.HashMap.Strict.InsOrd as OMap
|
|
|
|
import qualified Data.HashSet as HS
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-02-14 09:07:52 +03:00
|
|
|
import Control.Lens ((.~))
|
2018-06-27 16:11:32 +03:00
|
|
|
import Data.Aeson.Types
|
2020-12-08 17:22:31 +03:00
|
|
|
import Data.Text.Extended
|
2021-02-14 09:07:52 +03:00
|
|
|
import Data.Tuple (swap)
|
2020-05-27 18:02:58 +03:00
|
|
|
|
2021-03-15 16:02:58 +03:00
|
|
|
import qualified Hasura.SQL.AnyBackend as AB
|
|
|
|
|
2020-10-27 16:53:49 +03:00
|
|
|
import Hasura.EncJSON
|
|
|
|
import Hasura.RQL.DDL.Deps
|
2020-12-08 17:22:31 +03:00
|
|
|
import Hasura.RQL.DDL.Permission
|
2020-10-27 16:53:49 +03:00
|
|
|
import Hasura.RQL.Types
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-04-27 16:44:51 +03:00
|
|
|
|
2019-11-20 21:21:30 +03:00
|
|
|
runCreateRelationship
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall m b a
|
|
|
|
. (MonadError QErr m, CacheRWM m, ToJSON a, MetadataM m, Backend b, BackendMetadata b)
|
2021-02-14 09:07:52 +03:00
|
|
|
=> RelType -> WithTable b (RelDef a) -> m EncJSON
|
2020-12-28 15:56:00 +03:00
|
|
|
runCreateRelationship relType (WithTable source tableName relDef) = do
|
2020-12-08 17:22:31 +03:00
|
|
|
let relName = _rdName relDef
|
|
|
|
-- Check if any field with relationship name already exists in the table
|
2021-04-22 00:44:37 +03:00
|
|
|
tableFields <- _tciFieldInfoMap <$> askTableCoreInfo @b source tableName
|
2020-12-08 17:22:31 +03:00
|
|
|
onJust (HM.lookup (fromRel relName) tableFields) $ const $
|
|
|
|
throw400 AlreadyExists $
|
|
|
|
"field with name " <> relName <<> " already exists in table " <>> tableName
|
|
|
|
let comment = _rdComment relDef
|
2021-03-15 16:02:58 +03:00
|
|
|
metadataObj = MOSourceObjId source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SMOTableObj @b tableName
|
2021-03-15 16:02:58 +03:00
|
|
|
$ MTORel relName relType
|
2020-12-08 17:22:31 +03:00
|
|
|
addRelationshipToMetadata <- case relType of
|
|
|
|
ObjRel -> do
|
|
|
|
value <- decodeValue $ toJSON $ _rdUsing relDef
|
|
|
|
pure $ tmObjectRelationships %~ OMap.insert relName (RelDef relName value comment)
|
|
|
|
ArrRel -> do
|
|
|
|
value <- decodeValue $ toJSON $ _rdUsing relDef
|
|
|
|
pure $ tmArrayRelationships %~ OMap.insert relName (RelDef relName value comment)
|
|
|
|
|
|
|
|
buildSchemaCacheFor metadataObj
|
|
|
|
$ MetadataModifier
|
2021-04-22 00:44:37 +03:00
|
|
|
$ tableMetadataSetter @b source tableName %~ addRelationshipToMetadata
|
2019-11-20 21:21:30 +03:00
|
|
|
pure successMsg
|
2019-05-08 10:36:43 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
runDropRel
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall b m
|
|
|
|
. (MonadError QErr m, CacheRWM m, MetadataM m, BackendMetadata b)
|
2021-02-23 20:37:27 +03:00
|
|
|
=> DropRel b -> m EncJSON
|
2020-12-28 15:56:00 +03:00
|
|
|
runDropRel (DropRel source qt rn cascade) = do
|
2019-11-20 21:21:30 +03:00
|
|
|
depObjs <- collectDependencies
|
|
|
|
withNewInconsistentObjsCheck do
|
2020-12-08 17:22:31 +03:00
|
|
|
metadataModifiers <- traverse purgeRelDep depObjs
|
|
|
|
buildSchemaCache $ MetadataModifier $
|
2021-04-22 00:44:37 +03:00
|
|
|
tableMetadataSetter @b source qt %~
|
2020-12-08 17:22:31 +03:00
|
|
|
dropRelationshipInMetadata rn . foldr (.) id metadataModifiers
|
2019-11-20 21:21:30 +03:00
|
|
|
pure successMsg
|
|
|
|
where
|
|
|
|
collectDependencies = do
|
2021-04-22 00:44:37 +03:00
|
|
|
tabInfo <- askTableCoreInfo @b source qt
|
2020-12-08 17:22:31 +03:00
|
|
|
void $ askRelType (_tciFieldInfoMap tabInfo) rn ""
|
2019-11-20 21:21:30 +03:00
|
|
|
sc <- askSchemaCache
|
2021-03-15 16:02:58 +03:00
|
|
|
let depObjs = getDependentObjs
|
|
|
|
sc
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b qt
|
2021-03-15 16:02:58 +03:00
|
|
|
$ TORel rn)
|
2020-11-12 12:25:48 +03:00
|
|
|
when (depObjs /= [] && not cascade) $ reportDeps depObjs
|
2019-11-20 21:21:30 +03:00
|
|
|
pure depObjs
|
2018-12-13 10:26:15 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
dropRelationshipInMetadata
|
2021-02-14 09:07:52 +03:00
|
|
|
:: RelName -> TableMetadata b -> TableMetadata b
|
2020-12-08 17:22:31 +03:00
|
|
|
dropRelationshipInMetadata relName =
|
|
|
|
-- Since the name of a relationship is unique in a table, the relationship
|
|
|
|
-- with given name may present in either array or object relationships but
|
|
|
|
-- not in both.
|
|
|
|
(tmObjectRelationships %~ OMap.delete relName)
|
|
|
|
. (tmArrayRelationships %~ OMap.delete relName)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-20 21:21:30 +03:00
|
|
|
objRelP2Setup
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, Backend b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
2021-03-03 16:02:00 +03:00
|
|
|
-> HashMap (TableName b) (HashSet (ForeignKey b))
|
2021-02-14 09:07:52 +03:00
|
|
|
-> RelDef (ObjRelUsing b)
|
2021-03-10 11:54:53 +03:00
|
|
|
-> FieldInfoMap (ColumnInfo b)
|
2021-02-14 09:07:52 +03:00
|
|
|
-> m (RelInfo b, [SchemaDependency])
|
2021-03-10 11:54:53 +03:00
|
|
|
objRelP2Setup source qt foreignKeys (RelDef rn ru _) fieldInfoMap = case ru of
|
2020-01-14 10:09:10 +03:00
|
|
|
RUManual rm -> do
|
2019-11-20 21:21:30 +03:00
|
|
|
let refqt = rmTable rm
|
2019-12-13 00:46:33 +03:00
|
|
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
2021-03-03 16:02:00 +03:00
|
|
|
io = fromMaybe BeforeParent $ rmInsertOrder rm
|
2021-03-15 16:02:58 +03:00
|
|
|
mkDependency tableName reason col = SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b tableName
|
|
|
|
$ TOCol @b col)
|
2021-03-15 16:02:58 +03:00
|
|
|
reason
|
2019-11-20 21:21:30 +03:00
|
|
|
dependencies = map (mkDependency qt DRLeftColumn) lCols
|
|
|
|
<> map (mkDependency refqt DRRightColumn) rCols
|
2021-03-03 16:02:00 +03:00
|
|
|
pure (RelInfo rn ObjRel (rmColumns rm) refqt True True io, dependencies)
|
|
|
|
RUFKeyOn (SameTable columnName) -> do
|
2021-04-22 00:44:37 +03:00
|
|
|
foreignTableForeignKeys <- findTable @b qt foreignKeys
|
2021-03-03 16:02:00 +03:00
|
|
|
ForeignKey constraint foreignTable colMap <- getRequiredFkey columnName (HS.toList foreignTableForeignKeys)
|
2019-11-20 21:21:30 +03:00
|
|
|
let dependencies =
|
2021-03-15 16:02:58 +03:00
|
|
|
[ SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b qt
|
|
|
|
$ TOForeignKey @b (_cName constraint))
|
2021-03-15 16:02:58 +03:00
|
|
|
DRFkey
|
|
|
|
, SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b qt
|
|
|
|
$ TOCol @b columnName)
|
2021-03-15 16:02:58 +03:00
|
|
|
DRUsingColumn
|
2019-11-20 21:21:30 +03:00
|
|
|
-- 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.
|
2021-03-15 16:02:58 +03:00
|
|
|
, SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITable @b foreignTable)
|
2021-03-15 16:02:58 +03:00
|
|
|
DRRemoteTable
|
2019-11-20 21:21:30 +03:00
|
|
|
]
|
2021-04-22 00:44:37 +03:00
|
|
|
colInfo <- HM.lookup (fromCol @b columnName) fieldInfoMap
|
2021-03-10 11:54:53 +03:00
|
|
|
`onNothing` throw500 "could not find column info in schema cache"
|
|
|
|
let nullable = pgiIsNullable colInfo
|
|
|
|
pure (RelInfo rn ObjRel colMap foreignTable False nullable BeforeParent, dependencies)
|
2021-04-27 16:44:51 +03:00
|
|
|
RUFKeyOn (RemoteTable remoteTable remoteCol) ->
|
|
|
|
mkFkeyRel ObjRel AfterParent source rn qt remoteTable remoteCol foreignKeys
|
2019-11-20 21:21:30 +03:00
|
|
|
|
|
|
|
arrRelP2Setup
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, Backend b)
|
2021-02-14 09:07:52 +03:00
|
|
|
=> HashMap (TableName b) (HashSet (ForeignKey b))
|
2020-12-28 15:56:00 +03:00
|
|
|
-> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
2021-03-15 16:02:58 +03:00
|
|
|
-> ArrRelDef b
|
2021-02-14 09:07:52 +03:00
|
|
|
-> m (RelInfo b, [SchemaDependency])
|
2020-12-28 15:56:00 +03:00
|
|
|
arrRelP2Setup foreignKeys source qt (RelDef rn ru _) = case ru of
|
2020-01-14 10:09:10 +03:00
|
|
|
RUManual rm -> do
|
2019-11-20 21:21:30 +03:00
|
|
|
let refqt = rmTable rm
|
2019-12-13 00:46:33 +03:00
|
|
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
2021-03-15 16:02:58 +03:00
|
|
|
deps = map (\c -> SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b qt
|
|
|
|
$ TOCol @b c) DRLeftColumn)
|
2021-03-15 16:02:58 +03:00
|
|
|
lCols
|
|
|
|
<> map (\c -> SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SOITableObj @b refqt
|
|
|
|
$ TOCol @b c)
|
2021-03-15 16:02:58 +03:00
|
|
|
DRRightColumn)
|
|
|
|
rCols
|
2021-04-27 16:44:51 +03:00
|
|
|
pure (RelInfo rn ArrRel (rmColumns rm) refqt True True AfterParent, deps)
|
|
|
|
RUFKeyOn (ArrRelUsingFKeyOn refqt refCol) ->
|
|
|
|
mkFkeyRel ArrRel AfterParent source rn qt refqt refCol foreignKeys
|
|
|
|
|
|
|
|
mkFkeyRel
|
|
|
|
:: forall b m
|
|
|
|
. QErrM m
|
|
|
|
=> Backend b
|
|
|
|
=> RelType
|
|
|
|
-> InsertOrder
|
|
|
|
-> SourceName
|
|
|
|
-> RelName
|
|
|
|
-> TableName b
|
|
|
|
-> TableName b
|
|
|
|
-> Column b
|
|
|
|
-> HashMap (TableName b) (HashSet (ForeignKey b))
|
|
|
|
-> m (RelInfo b, [SchemaDependency])
|
|
|
|
mkFkeyRel relType io source rn sourceTable remoteTable remoteColumn foreignKeys = do
|
|
|
|
foreignTableForeignKeys <- findTable @b remoteTable foreignKeys
|
|
|
|
let keysThatReferenceUs = filter ((== sourceTable) . _fkForeignTable) (HS.toList foreignTableForeignKeys)
|
|
|
|
ForeignKey constraint _foreignTable colMap <- getRequiredFkey remoteColumn keysThatReferenceUs
|
|
|
|
let dependencies =
|
|
|
|
[ SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
|
|
|
$ SOITableObj @b remoteTable
|
|
|
|
$ TOForeignKey @b (_cName constraint))
|
|
|
|
DRRemoteFkey
|
|
|
|
, SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
|
|
|
$ SOITableObj @b remoteTable
|
|
|
|
$ TOCol @b remoteColumn)
|
|
|
|
DRUsingColumn
|
|
|
|
, SchemaDependency
|
|
|
|
(SOSourceObj source
|
|
|
|
$ AB.mkAnyBackend
|
|
|
|
$ SOITable @b remoteTable)
|
|
|
|
DRRemoteTable
|
|
|
|
]
|
|
|
|
pure (RelInfo rn relType (reverseHM colMap) remoteTable False False io, dependencies)
|
|
|
|
where
|
|
|
|
reverseHM :: Eq y => Hashable y => HashMap x y -> HashMap y x
|
|
|
|
reverseHM = HM.fromList . fmap swap . HM.toList
|
2019-11-20 21:21:30 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
purgeRelDep
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall b m
|
|
|
|
. QErrM m
|
|
|
|
=> Backend b
|
2021-02-14 09:07:52 +03:00
|
|
|
=> SchemaObjId -> m (TableMetadata b -> TableMetadata b)
|
2021-03-15 16:02:58 +03:00
|
|
|
purgeRelDep (SOSourceObj _ exists)
|
|
|
|
| Just (SOITableObj _ (TOPerm rn pt)) <- AB.unpackAnyBackend @b exists =
|
|
|
|
pure $ dropPermissionInMetadata rn pt
|
2019-11-20 21:21:30 +03:00
|
|
|
purgeRelDep d = throw500 $ "unexpected dependency of relationship : "
|
|
|
|
<> reportSchemaObj d
|
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
runSetRelComment
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall m b
|
|
|
|
. (CacheRWM m, MonadError QErr m, MetadataM m, BackendMetadata b)
|
2021-02-23 20:37:27 +03:00
|
|
|
=> SetRelComment b -> m EncJSON
|
2018-12-13 10:26:15 +03:00
|
|
|
runSetRelComment defn = do
|
2021-04-22 00:44:37 +03:00
|
|
|
tabInfo <- askTableCoreInfo @b source qt
|
2020-12-08 17:22:31 +03:00
|
|
|
relType <- riType <$> askRelType (_tciFieldInfoMap tabInfo) rn ""
|
2021-03-15 16:02:58 +03:00
|
|
|
let metadataObj = MOSourceObjId source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SMOTableObj @b qt
|
2021-03-15 16:02:58 +03:00
|
|
|
$ MTORel rn relType
|
2020-12-08 17:22:31 +03:00
|
|
|
buildSchemaCacheFor metadataObj
|
|
|
|
$ MetadataModifier
|
2021-04-22 00:44:37 +03:00
|
|
|
$ tableMetadataSetter @b source qt %~ case relType of
|
2020-12-08 17:22:31 +03:00
|
|
|
ObjRel -> tmObjectRelationships.ix rn.rdComment .~ comment
|
|
|
|
ArrRel -> tmArrayRelationships.ix rn.rdComment .~ comment
|
|
|
|
pure successMsg
|
2019-03-01 12:17:22 +03:00
|
|
|
where
|
2020-12-28 15:56:00 +03:00
|
|
|
SetRelComment source qt rn comment = defn
|
2019-05-08 10:36:43 +03:00
|
|
|
|
|
|
|
getRequiredFkey
|
2021-02-14 09:07:52 +03:00
|
|
|
:: (QErrM m, Backend b)
|
|
|
|
=> Column b
|
|
|
|
-> [ForeignKey b]
|
|
|
|
-> m (ForeignKey b)
|
2019-12-09 07:18:53 +03:00
|
|
|
getRequiredFkey col fkeys =
|
|
|
|
case filteredFkeys of
|
2019-05-08 10:36:43 +03:00
|
|
|
[] -> 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
|
2019-12-09 07:18:53 +03:00
|
|
|
filteredFkeys = filter ((== [col]) . HM.keys . _fkColumnMapping) fkeys
|