graphql-engine/server/src-lib/Hasura/RQL/DDL/Schema/Rename.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

450 lines
16 KiB
Haskell

-- | Functions for mutating the catalog (with integrity checking) to incorporate schema changes
-- discovered after applying a user-supplied SQL query. None of these functions modify the schema
-- cache, so it must be reloaded after the catalog is updated.
module Hasura.RQL.DDL.Schema.Rename
( renameTableInCatalog
, renameColInCatalog
, renameRelInCatalog
)
where
import Control.Lens.Combinators
import Control.Lens.Operators
import Hasura.Prelude
import Hasura.RQL.DDL.Permission
import Hasura.RQL.DDL.Permission.Internal
import Hasura.RQL.DDL.Relationship.Types
import Hasura.RQL.DDL.Schema.Catalog
import Hasura.RQL.Types
import Hasura.Session
import Hasura.SQL.Types
import qualified Hasura.RQL.DDL.EventTrigger as DS
import qualified Data.HashMap.Strict as M
import qualified Database.PG.Query as Q
import Data.Aeson
data RenameItem a
= RenameItem
{ _riTable :: !QualifiedTable
, _riOld :: !a
, _riNew :: !a
} deriving (Show, Eq)
type RenameCol = RenameItem PGCol
data RenameField
= RFCol !RenameCol
| RFRel !(RenameItem RelName)
deriving (Show, Eq)
type RenameTable = (QualifiedTable, QualifiedTable)
data Rename
= RTable !RenameTable
| RField !RenameField
deriving (Show, Eq)
otherDeps :: QErrM m => Text -> SchemaObjId -> m ()
otherDeps errMsg d =
throw500 $ "unexpected dependancy "
<> reportSchemaObj d <> "; " <> errMsg
renameTableInCatalog
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> QualifiedTable -> m ()
renameTableInCatalog newQT oldQT = do
sc <- askSchemaCache
let allDeps = getDependentObjs sc $ SOTable oldQT
-- update all dependant schema objects
forM_ allDeps $ \case
SOTableObj refQT (TORel rn) ->
updateRelDefs refQT rn (oldQT, newQT)
SOTableObj refQT (TOPerm rn pt) ->
updatePermFlds refQT rn pt $ RTable (oldQT, newQT)
-- A trigger's definition is not dependent on the table directly
SOTableObj _ (TOTrigger _) -> return ()
d -> otherDeps errMsg d
-- -- Update table name in hdb_catalog
liftTx $ Q.catchE defaultTxErrorHandler updateTableInCatalog
where
QualifiedObject nsn ntn = newQT
QualifiedObject osn otn = oldQT
errMsg = "cannot rename table " <> oldQT <<> " to " <>> newQT
updateTableInCatalog =
Q.unitQ [Q.sql|
UPDATE "hdb_catalog"."hdb_table"
SET table_schema = $1, table_name = $2
WHERE table_schema = $3 AND table_name = $4
|] (nsn, ntn, osn, otn) False
renameColInCatalog
:: (MonadTx m, CacheRM m)
=> PGCol -> PGCol -> QualifiedTable -> FieldInfoMap FieldInfo -> m ()
renameColInCatalog oCol nCol qt fieldInfo = do
sc <- askSchemaCache
-- Check if any relation exists with new column name
assertFldNotExists
-- Fetch dependent objects
let depObjs = getDependentObjs sc $ SOTableObj qt $ TOCol oCol
renameFld = RFCol $ RenameItem qt oCol nCol
-- Update dependent objects
forM_ depObjs $ \case
SOTableObj refQT (TOPerm role pt) ->
updatePermFlds refQT role pt $ RField renameFld
SOTableObj refQT (TORel rn) ->
updateColInRel refQT rn $ RenameItem qt oCol nCol
SOTableObj _ (TOTrigger triggerName) ->
updateColInEventTriggerDef triggerName $ RenameItem qt oCol nCol
d -> otherDeps errMsg d
-- Update custom column names
possiblyUpdateCustomColumnNames qt oCol nCol
where
errMsg = "cannot rename column " <> oCol <<> " to " <>> nCol
assertFldNotExists =
case M.lookup (fromPGCol oCol) fieldInfo of
Just (FIRelationship _) ->
throw400 AlreadyExists $ "cannot rename column " <> oCol
<<> " to " <> nCol <<> " in table " <> qt <<>
" as a relationship with the name already exists"
_ -> return ()
renameRelInCatalog
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> RelName -> RelName -> m ()
renameRelInCatalog qt oldRN newRN = do
sc <- askSchemaCache
let depObjs = getDependentObjs sc $ SOTableObj qt $ TORel oldRN
renameFld = RFRel $ RenameItem qt oldRN newRN
forM_ depObjs $ \case
SOTableObj refQT (TOPerm role pt) ->
updatePermFlds refQT role pt $ RField renameFld
d -> otherDeps errMsg d
liftTx updateRelName
where
errMsg = "cannot rename relationship " <> oldRN <<> " to " <>> newRN
QualifiedObject sn tn = qt
updateRelName =
Q.unitQE defaultTxErrorHandler [Q.sql|
UPDATE hdb_catalog.hdb_relationship
SET rel_name = $1
WHERE table_schema = $2
AND table_name = $3
AND rel_name = $4
|] (newRN, sn, tn, oldRN) True
-- update table names in relationship definition
updateRelDefs
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> RelName -> RenameTable -> m ()
updateRelDefs qt rn renameTable = do
fim <- askFieldInfoMap qt
ri <- askRelType fim rn ""
case riType ri of
ObjRel -> updateObjRelDef qt rn renameTable
ArrRel -> updateArrRelDef qt rn renameTable
updateObjRelDef
:: (MonadTx m)
=> QualifiedTable -> RelName -> RenameTable -> m ()
updateObjRelDef qt rn (oldQT, newQT) = do
oldDefV <- liftTx $ getRelDef qt rn
oldDef :: ObjRelUsing <- decodeValue oldDefV
let newDef = case oldDef of
RUFKeyOn _ -> oldDef
RUManual (RelManualConfig dbQT rmCols) ->
let updQT = bool oldQT newQT $ oldQT == dbQT
in RUManual $ RelManualConfig updQT rmCols
liftTx $ updateRel qt rn $ toJSON newDef
updateArrRelDef
:: (MonadTx m)
=> QualifiedTable -> RelName -> RenameTable -> m ()
updateArrRelDef qt rn (oldQT, newQT) = do
oldDefV <- liftTx $ getRelDef qt rn
oldDef <- decodeValue oldDefV
let newDef = case oldDef of
RUFKeyOn (ArrRelUsingFKeyOn dbQT c) ->
let updQT = getUpdQT dbQT
in RUFKeyOn $ ArrRelUsingFKeyOn updQT c
RUManual (RelManualConfig dbQT rmCols) ->
let updQT = getUpdQT dbQT
in RUManual $ RelManualConfig updQT rmCols
liftTx $ updateRel qt rn $ toJSON newDef
where
getUpdQT dbQT = bool oldQT newQT $ oldQT == dbQT
-- | update fields in premissions
updatePermFlds :: (MonadTx m, CacheRM m)
=> QualifiedTable -> RoleName -> PermType -> Rename -> m ()
updatePermFlds refQT rn pt rename = do
pDef <- fmap fst $ liftTx $ fetchPermDef refQT rn pt
case pt of
PTInsert -> do
perm <- decodeValue pDef
updateInsPermFlds refQT rename rn perm
PTSelect -> do
perm <- decodeValue pDef
updateSelPermFlds refQT rename rn perm
PTUpdate -> do
perm <- decodeValue pDef
updateUpdPermFlds refQT rename rn perm
PTDelete -> do
perm <- decodeValue pDef
updateDelPermFlds refQT rename rn perm
updateInsPermFlds
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> Rename -> RoleName -> InsPerm -> m ()
updateInsPermFlds refQT rename rn (InsPerm chk preset cols mBackendOnly) = do
updatedPerm <- case rename of
RTable rt -> do
let updChk = updateTableInBoolExp rt chk
return $ InsPerm updChk preset cols mBackendOnly
RField rf -> do
updChk <- updateFieldInBoolExp refQT rf chk
let updPresetM = updatePreset refQT rf <$> preset
updColsM = updateCols refQT rf <$> cols
return $ InsPerm updChk updPresetM updColsM mBackendOnly
liftTx $ updatePermDefInCatalog PTInsert refQT rn updatedPerm
updateSelPermFlds
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> Rename -> RoleName -> SelPerm -> m ()
updateSelPermFlds refQT rename rn (SelPerm cols fltr limit aggAllwd computedFields) = do
updatedPerm <- case rename of
RTable rt -> do
let updFltr = updateTableInBoolExp rt fltr
return $ SelPerm cols updFltr limit aggAllwd computedFields
RField rf -> do
updFltr <- updateFieldInBoolExp refQT rf fltr
let updCols = updateCols refQT rf cols
return $ SelPerm updCols updFltr limit aggAllwd computedFields
liftTx $ updatePermDefInCatalog PTSelect refQT rn updatedPerm
updateUpdPermFlds
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> Rename -> RoleName -> UpdPerm -> m ()
updateUpdPermFlds refQT rename rn (UpdPerm cols preset fltr check) = do
updatedPerm <- case rename of
RTable rt -> do
let updFltr = updateTableInBoolExp rt fltr
updCheck = fmap (updateTableInBoolExp rt) check
return $ UpdPerm cols preset updFltr updCheck
RField rf -> do
updFltr <- updateFieldInBoolExp refQT rf fltr
updCheck <- traverse (updateFieldInBoolExp refQT rf) check
let updCols = updateCols refQT rf cols
updPresetM = updatePreset refQT rf <$> preset
return $ UpdPerm updCols updPresetM updFltr updCheck
liftTx $ updatePermDefInCatalog PTUpdate refQT rn updatedPerm
updateDelPermFlds
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> Rename -> RoleName -> DelPerm -> m ()
updateDelPermFlds refQT rename rn (DelPerm fltr) = do
updFltr <- case rename of
RTable rt -> return $ updateTableInBoolExp rt fltr
RField rf -> updateFieldInBoolExp refQT rf fltr
liftTx $ updatePermDefInCatalog PTDelete refQT rn $ DelPerm updFltr
updatePreset
:: QualifiedTable -> RenameField -> (ColumnValues Value) -> (ColumnValues Value)
updatePreset qt rf obj =
case rf of
RFCol (RenameItem opQT oCol nCol) ->
if qt == opQT then updatePreset' oCol nCol
else obj
_ -> obj
where
updatePreset' oCol nCol =
M.fromList updItems
where
updItems= map procObjItem $ M.toList obj
procObjItem (pgCol, v) =
let isUpdated = pgCol == oCol
updCol = bool pgCol nCol isUpdated
in (updCol, v)
updateCols
:: QualifiedTable -> RenameField -> PermColSpec -> PermColSpec
updateCols qt rf permSpec =
case rf of
RFCol (RenameItem opQT oCol nCol) ->
if qt == opQT then updateCols' oCol nCol permSpec
else permSpec
_ -> permSpec
where
updateCols' oCol nCol cols = case cols of
PCStar -> cols
PCCols c -> PCCols $ flip map c $
\col -> if col == oCol then nCol else col
updateTableInBoolExp :: RenameTable -> BoolExp -> BoolExp
updateTableInBoolExp (oldQT, newQT) =
over _Wrapped . transform $ (_BoolExists . geTable) %~ \rqfQT ->
if rqfQT == oldQT then newQT else rqfQT
updateFieldInBoolExp
:: (QErrM m, CacheRM m)
=> QualifiedTable -> RenameField -> BoolExp -> m BoolExp
updateFieldInBoolExp qt rf be = BoolExp <$>
case unBoolExp be of
BoolAnd exps -> BoolAnd <$> procExps exps
BoolOr exps -> BoolOr <$> procExps exps
BoolNot e -> BoolNot <$> updateBoolExp' e
BoolExists (GExists refqt wh) ->
(BoolExists . GExists refqt . unBoolExp)
<$> updateFieldInBoolExp refqt rf (BoolExp wh)
BoolFld fld -> BoolFld <$> updateColExp qt rf fld
where
procExps = mapM updateBoolExp'
updateBoolExp' =
fmap unBoolExp . updateFieldInBoolExp qt rf . BoolExp
updateColExp
:: (QErrM m, CacheRM m)
=> QualifiedTable -> RenameField -> ColExp-> m ColExp
updateColExp qt rf (ColExp fld val) =
ColExp updatedFld <$> updatedVal
where
updatedFld = bool fld nFld $ opQT == qt && oFld == fld
updatedVal = do
fim <- askFieldInfoMap qt
fi <- askFieldInfo fim fld
case fi of
FIColumn _ -> return val
FIComputedField _ -> return val
FIRelationship ri -> do
let remTable = riRTable ri
be <- decodeValue val
ube <- updateFieldInBoolExp remTable rf be
return $ toJSON ube
FIRemoteRelationship {} ->
throw500 "cannot update remote field" -- TODO: determine the proper behavior here.
(oFld, nFld, opQT) = case rf of
RFCol (RenameItem tn oCol nCol) -> (fromPGCol oCol, fromPGCol nCol, tn)
RFRel (RenameItem tn oRel nRel) -> (fromRel oRel, fromRel nRel, tn)
-- rename columns in relationship definitions
updateColInRel
:: (MonadTx m, CacheRM m)
=> QualifiedTable -> RelName -> RenameCol -> m ()
updateColInRel fromQT rn rnCol = do
fim <- askFieldInfoMap fromQT
ri <- askRelType fim rn ""
let toQT = riRTable ri
oldDefV <- liftTx $ getRelDef fromQT rn
newDefV <- case riType ri of
ObjRel -> fmap toJSON $
updateColInObjRel fromQT toQT rnCol <$> decodeValue oldDefV
ArrRel -> fmap toJSON $
updateColInArrRel fromQT toQT rnCol <$> decodeValue oldDefV
liftTx $ updateRel fromQT rn newDefV
-- rename columns in relationship definitions
updateColInEventTriggerDef
:: (MonadTx m)
=> TriggerName -> RenameCol -> m ()
updateColInEventTriggerDef trigName rnCol = do
(trigTab, trigDef) <- liftTx $ DS.getEventTriggerDef trigName
void $ liftTx $ DS.updateEventTriggerInCatalog $
rewriteEventTriggerConf trigTab trigDef
where
rewriteSubsCols trigTab = \case
SubCStar -> SubCStar
SubCArray cols -> SubCArray $
map (getNewCol rnCol trigTab) cols
rewriteOpSpec trigTab (SubscribeOpSpec cols payload) =
SubscribeOpSpec
(rewriteSubsCols trigTab cols)
(rewriteSubsCols trigTab <$> payload)
rewriteTrigOpsDef trigTab (TriggerOpsDef ins upd del man) =
TriggerOpsDef
(rewriteOpSpec trigTab <$> ins)
(rewriteOpSpec trigTab <$> upd)
(rewriteOpSpec trigTab <$> del)
man
rewriteEventTriggerConf trigTab etc =
etc { etcDefinition =
rewriteTrigOpsDef trigTab $ etcDefinition etc
}
updateColInObjRel
:: QualifiedTable -> QualifiedTable
-> RenameCol -> ObjRelUsing -> ObjRelUsing
updateColInObjRel fromQT toQT rnCol = \case
RUFKeyOn col -> RUFKeyOn $ getNewCol rnCol fromQT col
RUManual manConfig -> RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig
updateColInArrRel
:: QualifiedTable -> QualifiedTable
-> RenameCol -> ArrRelUsing -> ArrRelUsing
updateColInArrRel fromQT toQT rnCol = \case
RUFKeyOn (ArrRelUsingFKeyOn t c) ->
let updCol = getNewCol rnCol toQT c
in RUFKeyOn $ ArrRelUsingFKeyOn t updCol
RUManual manConfig -> RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig
type ColMap = HashMap PGCol PGCol
getNewCol
:: RenameCol -> QualifiedTable -> PGCol -> PGCol
getNewCol rnCol qt col =
if opQT == qt && col == oCol then nCol else col
where
RenameItem opQT oCol nCol = rnCol
updateRelManualConfig
:: QualifiedTable -> QualifiedTable
-> RenameCol -> RelManualConfig -> RelManualConfig
updateRelManualConfig fromQT toQT rnCol manConfig =
RelManualConfig tn $ updateColMap fromQT toQT rnCol colMap
where
RelManualConfig tn colMap = manConfig
updateColMap
:: QualifiedTable -> QualifiedTable
-> RenameCol -> ColMap -> ColMap
updateColMap fromQT toQT rnCol =
M.fromList . map (modCol fromQT *** modCol toQT) . M.toList
where
RenameItem qt oCol nCol = rnCol
modCol colQt col = if colQt == qt && col == oCol then nCol else col
possiblyUpdateCustomColumnNames
:: MonadTx m => QualifiedTable -> PGCol -> PGCol -> m ()
possiblyUpdateCustomColumnNames qt oCol nCol = do
TableConfig customRootFields customColumns <- getTableConfig qt
let updatedCustomColumns =
M.fromList $ flip map (M.toList customColumns) $
\(dbCol, val) -> (, val) $ if dbCol == oCol then nCol else dbCol
when (updatedCustomColumns /= customColumns) $
updateTableConfig qt $ TableConfig customRootFields updatedCustomColumns
-- database functions for relationships
getRelDef :: QualifiedTable -> RelName -> Q.TxE QErr Value
getRelDef (QualifiedObject sn tn) rn =
Q.getAltJ . runIdentity . Q.getRow <$> Q.withQE defaultTxErrorHandler
[Q.sql|
SELECT rel_def::json FROM hdb_catalog.hdb_relationship
WHERE table_schema = $1 AND table_name = $2
AND rel_name = $3
|] (sn, tn, rn) True
updateRel :: QualifiedTable -> RelName -> Value -> Q.TxE QErr ()
updateRel (QualifiedObject sn tn) rn relDef =
Q.unitQE defaultTxErrorHandler [Q.sql|
UPDATE hdb_catalog.hdb_relationship
SET rel_def = $1 :: jsonb
WHERE table_schema = $2
AND table_name = $3
AND rel_name = $4
|] (Q.AltJ relDef, sn , tn, rn) True