graphql-engine/server/src-lib/Hasura/RQL/DDL/Relationship.hs
Tirumarai Selvan c0d2bc6653
Remote Joins: Create relationships across database and remote schemas (#2392)
add remote joins: Create relationships across database and remote schemas (#2392)

Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>

Co-authored-by: Chris Done <chrisdone@gmail.com>
Co-authored-by: Chris Done <github@chrisdone.com>
Co-authored-by: wawhal <rishichandra.wawhal@gmail.com>
Co-authored-by: Aravind Shankar <aravind@hasura.io>
Co-authored-by: Brandon Simmons <brandon.m.simmons@gmail.com>
Co-authored-by: Rishichandra Wawhal <rishi@hasura.io>
Co-authored-by: Brandon Simmons <brandon@hasura.io>
Co-authored-by: nizar-m <19857260+nizar-m@users.noreply.github.com>
Co-authored-by: Praveen Durairaju <praveend.web@gmail.com>
Co-authored-by: rakeshkky <12475069+rakeshkky@users.noreply.github.com>
Co-authored-by: Anon Ray <rayanon004@gmail.com>
Co-authored-by: Shahidh K Muhammed <shahidh@hasura.io>
Co-authored-by: soorajshankar <soorajshankar@users.noreply.github.com>
Co-authored-by: Sooraj Sanker <sooraj@Soorajs-MacBook-Pro.local>
Co-authored-by: Karthikeyan Chinnakonda <karthikeyan@hasura.io>
Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>
2020-05-27 20:32:58 +05:30

193 lines
7.1 KiB
Haskell

module Hasura.RQL.DDL.Relationship
( runCreateRelationship
, insertRelationshipToCatalog
, objRelP2Setup
, arrRelP2Setup
, runDropRel
, delRelFromCatalog
, runSetRelComment
, module Hasura.RQL.DDL.Relationship.Types
)
where
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.SchemaCacheTypes
import Hasura.EncJSON
import Hasura.Prelude
import Hasura.RQL.DDL.Deps
import Hasura.RQL.DDL.Permission (purgePerm)
import Hasura.RQL.DDL.Relationship.Types
import Hasura.RQL.Types
import Hasura.SQL.Types
import Data.Aeson.Types
import Data.Tuple (swap)
import Instances.TH.Lift ()
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Database.PG.Query as Q
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 (or 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, 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
]
pure (RelInfo rn ObjRel colMap foreignTable 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, 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, 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