mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
8e88e73a52
<!-- Thank you for ss in the Title above ^ --> ## Description <!-- Please fill thier. --> <!-- Describe the changes from a user's perspective --> We don't have dependency reporting mechanism for `mssql_run_sql` API i.e when a database object (table, column etc.) is dropped through the API we should raise an exception if any dependencies (relationships, permissions etc.) with the database object exists in the metadata. This PR addresses the above mentioned problem by -> Integrating transaction to the API to rollback the SQL query execution if dependencies exists and exception is thrown -> Accepting `cascade` optional field in the API payload to drop the dependencies, if any -> Accepting `check_metadata_consistency` optional field to bypass (if value set to `false`) the dependency check ### Related Issues <!-- Please make surt title --> <!-- Add the issue number below (e.g. #234) --> Close #1853 ### Solution and Design <!-- How is this iss --> <!-- It's better if we elaborate --> The design/solution follows the `run_sql` API implementation for Postgres backend. ### Steps to test and verify <!-- If this is a fehis is a bug-fix, how do we verify the fix? --> - Create author - article tables and track them - Defined object and array relationships - Try to drop the article table without cascade or cascade set to `false` - The server should raise the relationship dependency exists exception ## Changelog - ✅ `CHANGELOG.md` is updated with user-facing content relevant to this PR. If no changelog is required, then add the `no-changelog-required` label. ## Affected components <!-- Remove non-affected components from the list --> - ✅ Server - ❎ Console - ❎ CLI - ❎ Docs - ❎ Community Content - ❎ Build System - ✅ Tests - ❎ Other (list it) PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2636 GitOrigin-RevId: 0ab152295394056c4ca6f02923142a1658ad25dc
726 lines
24 KiB
Haskell
726 lines
24 KiB
Haskell
-- | Functions for updating the metadata (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 metadata is updated.
|
|
module Hasura.RQL.DDL.Schema.Rename
|
|
( renameTableInMetadata,
|
|
renameColumnInMetadata,
|
|
renameRelationshipInMetadata,
|
|
)
|
|
where
|
|
|
|
import Control.Lens.Combinators
|
|
import Control.Lens.Operators
|
|
import Data.Aeson
|
|
import Data.HashMap.Strict qualified as M
|
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
|
import Data.HashSet qualified as Set
|
|
import Data.Text.Extended
|
|
import Hasura.Base.Error
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.DDL.Permission
|
|
import Hasura.RQL.Types
|
|
import Hasura.SQL.AnyBackend qualified as AB
|
|
import Hasura.Session
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
|
|
|
data RenameItem (b :: BackendType) a = RenameItem
|
|
{ _riTable :: !(TableName b),
|
|
_riOld :: !a,
|
|
_riNew :: !a
|
|
}
|
|
|
|
type RenameCol (b :: BackendType) = RenameItem b (Column b)
|
|
|
|
data RenameField b
|
|
= RFCol !(RenameCol b)
|
|
| RFRel !(RenameItem b RelName)
|
|
|
|
type RenameTable b = (TableName b, TableName b)
|
|
|
|
data Rename b
|
|
= RTable !(RenameTable b)
|
|
| RField !(RenameField b)
|
|
|
|
otherDeps :: QErrM m => Text -> SchemaObjId -> m ()
|
|
otherDeps errMsg d =
|
|
throw500 $
|
|
"unexpected dependency "
|
|
<> reportSchemaObj d
|
|
<> "; "
|
|
<> errMsg
|
|
|
|
-- | Replace all references to a given table name by its new name across the entire metadata.
|
|
--
|
|
-- This function will make use of the metadata dependency graph (see 'getDependentObjs') to identify
|
|
-- all places that refer to the old table name, and replace it accordingly. Most operations will
|
|
-- occur within the same source, such as table references in relationships and permissions.
|
|
-- Dependencies across sources can happen in the case of cross-source relationships.
|
|
--
|
|
-- This function will fail if it encounters a nonsensical dependency; for instance, if there's a
|
|
-- dependency from that table to a source.
|
|
--
|
|
-- For more information about the dependency graph, see 'SchemaObjId'.
|
|
renameTableInMetadata ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
CacheRM m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
TableName b ->
|
|
m ()
|
|
renameTableInMetadata source newQT oldQT = do
|
|
sc <- askSchemaCache
|
|
let allDeps =
|
|
getDependentObjs sc $
|
|
SOSourceObj source $
|
|
AB.mkAnyBackend $
|
|
SOITable @b oldQT
|
|
|
|
-- update all dependant schema objects
|
|
forM_ allDeps $ \case
|
|
-- the dependend object is a source object in the same source
|
|
sobj@(SOSourceObj depSourceName exists)
|
|
| depSourceName == source,
|
|
Just sourceObjId <- AB.unpackAnyBackend @b exists ->
|
|
case sourceObjId of
|
|
SOITableObj refQT (TORel rn) ->
|
|
updateRelDefs @b source refQT rn (oldQT, newQT)
|
|
SOITableObj refQT (TOPerm rn pt) ->
|
|
updatePermFlds @b source refQT rn pt $ RTable (oldQT, newQT)
|
|
-- A trigger's definition is not dependent on the table directly
|
|
SOITableObj _ (TOTrigger _) -> pure ()
|
|
-- A remote relationship's definition is not dependent on the table directly
|
|
SOITableObj _ (TORemoteRel _) -> pure ()
|
|
_ -> otherDeps errMsg sobj
|
|
-- the dependend object is a source object in a different source
|
|
sobj@(SOSourceObj depSourceName exists) ->
|
|
AB.dispatchAnyBackend @Backend exists \(sourceObjId :: SourceObjId b') ->
|
|
case sourceObjId of
|
|
SOITableObj tableName (TORemoteRel remoteRelationshipName) -> do
|
|
updateTableInRemoteRelationshipRHS @b' @b depSourceName tableName remoteRelationshipName (oldQT, newQT)
|
|
-- only remote relationships might create dependencies across sources
|
|
_ -> otherDeps errMsg sobj
|
|
-- any other kind of dependent object (erroneous)
|
|
d -> otherDeps errMsg d
|
|
-- Update table name in metadata
|
|
tell $
|
|
MetadataModifier $
|
|
metaSources . ix source . (toSourceMetadata @b) . smTables %~ \tables ->
|
|
flip (maybe tables) (OMap.lookup oldQT tables) $
|
|
\tableMeta -> OMap.delete oldQT $ OMap.insert newQT tableMeta {_tmTable = newQT} tables
|
|
where
|
|
errMsg = "cannot rename table " <> oldQT <<> " to " <>> newQT
|
|
|
|
-- | Replace all references to a given column name by its new name across the entire metadata.
|
|
--
|
|
-- This function will make use of the metadata dependency graph (see 'getDependentObjs') to identify
|
|
-- all places that refer to the old column name, and replace it accordingly. Most operations will
|
|
-- occur within the same source, such as column references in relationships and permissions.
|
|
-- Dependencies across sources can happen in the case of cross-source relationships.
|
|
--
|
|
-- This function will fail if it encounters a nonsensical dependency; for instance, if there's a
|
|
-- dependency from that table to a source.
|
|
--
|
|
-- For more information about the dependency graph, see 'SchemaObjId'.
|
|
renameColumnInMetadata ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
CacheRM m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
Column b ->
|
|
Column b ->
|
|
SourceName ->
|
|
TableName b ->
|
|
FieldInfoMap (FieldInfo b) ->
|
|
m ()
|
|
renameColumnInMetadata oCol nCol source qt fieldInfo = do
|
|
sc <- askSchemaCache
|
|
-- Check if any relation exists with new column name
|
|
assertFldNotExists
|
|
-- Fetch dependent objects
|
|
let depObjs =
|
|
getDependentObjs sc $
|
|
SOSourceObj source $
|
|
AB.mkAnyBackend $
|
|
SOITableObj @b qt $
|
|
TOCol @b oCol
|
|
renameItem = RenameItem @b qt oCol nCol
|
|
renameFld = RFCol renameItem
|
|
-- Update dependent objects
|
|
forM_ depObjs $ \case
|
|
-- the dependend object is a source object in the same source
|
|
sobj@(SOSourceObj depSourceName exists)
|
|
| depSourceName == source,
|
|
Just sourceObjId <- AB.unpackAnyBackend @b exists ->
|
|
case sourceObjId of
|
|
SOITableObj refQT (TOPerm role pt) ->
|
|
updatePermFlds @b source refQT role pt $ RField renameFld
|
|
SOITableObj refQT (TORel rn) ->
|
|
updateColInRel @b source refQT rn renameItem
|
|
SOITableObj refQT (TOTrigger triggerName) ->
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter @b source refQT . tmEventTriggers . ix triggerName
|
|
%~ updateColumnInEventTrigger @b refQT oCol nCol qt
|
|
SOITableObj _ (TORemoteRel remoteRelName) ->
|
|
updateColInRemoteRelationshipLHS source remoteRelName renameItem
|
|
_ -> otherDeps errMsg sobj
|
|
-- the dependend object is a source object in a different source
|
|
sobj@(SOSourceObj depSourceName exists) ->
|
|
AB.dispatchAnyBackend @Backend exists \(sourceObjId :: SourceObjId b') ->
|
|
case sourceObjId of
|
|
SOITableObj tableName (TORemoteRel remoteRelationshipName) -> do
|
|
updateColInRemoteRelationshipRHS @b' @b depSourceName tableName remoteRelationshipName renameItem
|
|
-- only remote relationships might create dependencies across sources
|
|
_ -> otherDeps errMsg sobj
|
|
-- any other kind of dependent object (erroneous)
|
|
d -> otherDeps errMsg d
|
|
-- Update custom column names
|
|
possiblyUpdateCustomColumnNames @b source qt oCol nCol
|
|
where
|
|
errMsg = "cannot rename column " <> oCol <<> " to " <>> nCol
|
|
assertFldNotExists =
|
|
case M.lookup (fromCol @b oCol) fieldInfo of
|
|
Just (FIRelationship _) ->
|
|
throw400 AlreadyExists $
|
|
"cannot rename column " <> oCol
|
|
<<> " to " <> nCol
|
|
<<> " in table " <> qt
|
|
<<> " as a relationship with the name already exists"
|
|
_ -> pure ()
|
|
|
|
renameRelationshipInMetadata ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
CacheRM m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
RelName ->
|
|
RelType ->
|
|
RelName ->
|
|
m ()
|
|
renameRelationshipInMetadata source qt oldRN relType newRN = do
|
|
sc <- askSchemaCache
|
|
let depObjs =
|
|
getDependentObjs sc $
|
|
SOSourceObj source $
|
|
AB.mkAnyBackend $
|
|
SOITableObj @b qt $
|
|
TORel oldRN
|
|
renameFld = RFRel $ RenameItem @b qt oldRN newRN
|
|
|
|
forM_ depObjs $ \case
|
|
sobj@(SOSourceObj _ exists) -> case AB.unpackAnyBackend @b exists of
|
|
Just (SOITableObj refQT (TOPerm role pt)) ->
|
|
updatePermFlds @b source refQT role pt $ RField renameFld
|
|
_ -> otherDeps errMsg sobj
|
|
d -> otherDeps errMsg d
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter @b source qt %~ case relType of
|
|
ObjRel -> tmObjectRelationships %~ rewriteRelationships
|
|
ArrRel -> tmArrayRelationships %~ rewriteRelationships
|
|
where
|
|
errMsg = "cannot rename relationship " <> oldRN <<> " to " <>> newRN
|
|
rewriteRelationships ::
|
|
Relationships (RelDef a) -> Relationships (RelDef a)
|
|
rewriteRelationships relationsMap =
|
|
flip (maybe relationsMap) (OMap.lookup oldRN relationsMap) $
|
|
\rd -> OMap.insert newRN rd {_rdName = newRN} $ OMap.delete oldRN relationsMap
|
|
|
|
-- update table names in relationship definition
|
|
updateRelDefs ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
CacheRM m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
RelName ->
|
|
RenameTable b ->
|
|
m ()
|
|
updateRelDefs source qt rn renameTable = do
|
|
fim <- askFieldInfoMap @b source qt
|
|
ri <- askRelType fim rn ""
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter source qt %~ case riType ri of
|
|
ObjRel -> tmObjectRelationships . ix rn %~ updateObjRelDef renameTable
|
|
ArrRel -> tmArrayRelationships . ix rn %~ updateArrRelDef renameTable
|
|
where
|
|
updateObjRelDef :: RenameTable b -> ObjRelDef b -> ObjRelDef b
|
|
updateObjRelDef (oldQT, newQT) =
|
|
rdUsing %~ \case
|
|
RUFKeyOn fk -> RUFKeyOn fk
|
|
RUManual (RelManualConfig origQT rmCols rmIO) ->
|
|
let updQT = bool origQT newQT $ oldQT == origQT
|
|
in RUManual $ RelManualConfig updQT rmCols rmIO
|
|
|
|
updateArrRelDef :: RenameTable b -> ArrRelDef b -> ArrRelDef b
|
|
updateArrRelDef (oldQT, newQT) =
|
|
rdUsing %~ \case
|
|
RUFKeyOn (ArrRelUsingFKeyOn origQT c) ->
|
|
let updQT = getUpdQT origQT
|
|
in RUFKeyOn $ ArrRelUsingFKeyOn updQT c
|
|
RUManual (RelManualConfig origQT rmCols rmIO) ->
|
|
let updQT = getUpdQT origQT
|
|
in RUManual $ RelManualConfig updQT rmCols rmIO
|
|
where
|
|
getUpdQT origQT = bool origQT newQT $ oldQT == origQT
|
|
|
|
-- | update fields in permissions
|
|
updatePermFlds ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
CacheRM m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
RoleName ->
|
|
PermType ->
|
|
Rename b ->
|
|
m ()
|
|
updatePermFlds source refQT rn pt rename = do
|
|
tables <- askTableCache source
|
|
let withTables :: Reader (TableCache b) a -> a
|
|
withTables = flip runReader tables
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter source refQT %~ case pt of
|
|
PTInsert ->
|
|
tmInsertPermissions . ix rn . pdPermission %~ \insPerm ->
|
|
withTables $ updateInsPermFlds refQT rename insPerm
|
|
PTSelect ->
|
|
tmSelectPermissions . ix rn . pdPermission %~ \selPerm ->
|
|
withTables $ updateSelPermFlds refQT rename selPerm
|
|
PTUpdate ->
|
|
tmUpdatePermissions . ix rn . pdPermission %~ \updPerm ->
|
|
withTables $ updateUpdPermFlds refQT rename updPerm
|
|
PTDelete ->
|
|
tmDeletePermissions . ix rn . pdPermission %~ \delPerm ->
|
|
withTables $ updateDelPermFlds refQT rename delPerm
|
|
|
|
updateInsPermFlds ::
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
Rename b ->
|
|
InsPerm b ->
|
|
m (InsPerm b)
|
|
updateInsPermFlds refQT rename (InsPerm chk preset cols mBackendOnly) =
|
|
case rename of
|
|
RTable rt -> do
|
|
let updChk = updateTableInBoolExp rt chk
|
|
pure $ InsPerm updChk preset cols mBackendOnly
|
|
RField rf -> do
|
|
updChk <- updateFieldInBoolExp refQT rf chk
|
|
let updPresetM = updatePreset refQT rf <$> preset
|
|
updColsM = updateCols refQT rf <$> cols
|
|
pure $ InsPerm updChk updPresetM updColsM mBackendOnly
|
|
|
|
updateSelPermFlds ::
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
Rename b ->
|
|
SelPerm b ->
|
|
m (SelPerm b)
|
|
updateSelPermFlds refQT rename (SelPerm cols fltr limit aggAllwd computedFields) = do
|
|
case rename of
|
|
RTable rt -> do
|
|
let updFltr = updateTableInBoolExp rt fltr
|
|
pure $ SelPerm cols updFltr limit aggAllwd computedFields
|
|
RField rf -> do
|
|
updFltr <- updateFieldInBoolExp refQT rf fltr
|
|
let updCols = updateCols refQT rf cols
|
|
pure $ SelPerm updCols updFltr limit aggAllwd computedFields
|
|
|
|
updateUpdPermFlds ::
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
Rename b ->
|
|
UpdPerm b ->
|
|
m (UpdPerm b)
|
|
updateUpdPermFlds refQT rename (UpdPerm cols preset fltr check) = do
|
|
case rename of
|
|
RTable rt -> do
|
|
let updFltr = updateTableInBoolExp rt fltr
|
|
updCheck = fmap (updateTableInBoolExp rt) check
|
|
pure $ 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
|
|
pure $ UpdPerm updCols updPresetM updFltr updCheck
|
|
|
|
updateDelPermFlds ::
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
Rename b ->
|
|
DelPerm b ->
|
|
m (DelPerm b)
|
|
updateDelPermFlds refQT rename (DelPerm fltr) = do
|
|
DelPerm <$> case rename of
|
|
RTable rt -> pure $ updateTableInBoolExp rt fltr
|
|
RField rf -> updateFieldInBoolExp refQT rf fltr
|
|
|
|
updatePreset ::
|
|
(Backend b) =>
|
|
TableName b ->
|
|
RenameField b ->
|
|
ColumnValues b Value ->
|
|
ColumnValues b 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 ::
|
|
(Backend b) => TableName b -> RenameField b -> PermColSpec b -> PermColSpec b
|
|
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 :: (Backend b) => RenameTable b -> BoolExp b -> BoolExp b
|
|
updateTableInBoolExp (oldQT, newQT) =
|
|
over _Wrapped . transform $
|
|
(_BoolExists . geTable) %~ \rqfQT ->
|
|
if rqfQT == oldQT then newQT else rqfQT
|
|
|
|
updateFieldInBoolExp ::
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
RenameField b ->
|
|
BoolExp b ->
|
|
m (BoolExp b)
|
|
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 ::
|
|
forall b m.
|
|
(MonadReader (TableCache b) m, Backend b) =>
|
|
TableName b ->
|
|
RenameField b ->
|
|
ColExp ->
|
|
m ColExp
|
|
updateColExp qt rf (ColExp fld val) =
|
|
ColExp updatedFld <$> updatedVal
|
|
where
|
|
updatedFld = bool fld nFld $ opQT == qt && oFld == fld
|
|
updatedVal = do
|
|
tables <- ask
|
|
let maybeFieldInfo =
|
|
M.lookup qt tables
|
|
>>= M.lookup fld . _tciFieldInfoMap . _tiCoreInfo
|
|
case maybeFieldInfo of
|
|
Nothing -> pure val
|
|
Just fi -> case fi of
|
|
FIColumn _ -> pure val
|
|
FIComputedField _ -> pure val
|
|
FIRelationship ri -> do
|
|
let remTable = riRTable ri
|
|
case decodeValue val of
|
|
Left _ -> pure val
|
|
Right be -> toJSON <$> updateFieldInBoolExp remTable rf be
|
|
FIRemoteRelationship {} -> pure val
|
|
|
|
(oFld, nFld, opQT) = case rf of
|
|
RFCol (RenameItem tn oCol nCol) -> (fromCol @b oCol, fromCol @b nCol, tn)
|
|
RFRel (RenameItem tn oRel nRel) -> (fromRel oRel, fromRel nRel, tn)
|
|
|
|
-- rename columns in relationship definitions
|
|
updateColInRel ::
|
|
forall b m.
|
|
(CacheRM m, MonadWriter MetadataModifier m, BackendMetadata b) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
RelName ->
|
|
RenameCol b ->
|
|
m ()
|
|
updateColInRel source fromQT rn rnCol = do
|
|
tables <- askSourceTables @b source
|
|
let maybeRelInfo =
|
|
tables ^? ix fromQT . tiCoreInfo . tciFieldInfoMap . ix (fromRel rn) . _FIRelationship
|
|
forM_ maybeRelInfo $ \relInfo ->
|
|
let relTableName = riRTable relInfo
|
|
in tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter source fromQT
|
|
%~ case riType relInfo of
|
|
ObjRel ->
|
|
tmObjectRelationships . ix rn . rdUsing
|
|
%~ updateColInObjRel fromQT relTableName rnCol
|
|
ArrRel ->
|
|
tmArrayRelationships . ix rn . rdUsing
|
|
%~ updateColInArrRel fromQT relTableName rnCol
|
|
|
|
-- | Local helper: update a column's name in the left-hand side of a remote relationship.
|
|
--
|
|
-- There are two kinds or remote relationships: remote source relationships, across sources, and
|
|
-- remote schema relationships, on remote schemas. In both cases, we maintain a mapping from the
|
|
-- source table's colunns to what they should be joined against in the target; when a column is
|
|
-- renamed, those references must be renamed as well. This function handles both cases.
|
|
--
|
|
-- See 'renameColumnInMetadata'.
|
|
updateColInRemoteRelationshipLHS ::
|
|
forall b m.
|
|
( MonadError QErr m,
|
|
MonadWriter MetadataModifier m,
|
|
BackendMetadata b
|
|
) =>
|
|
SourceName ->
|
|
RemoteRelationshipName ->
|
|
RenameCol b ->
|
|
m ()
|
|
updateColInRemoteRelationshipLHS source remoteRelationshipName (RenameItem qt oldCol newCol) = do
|
|
oldColName <- parseGraphQLName $ toTxt oldCol
|
|
newColName <- parseGraphQLName $ toTxt newCol
|
|
let oldFieldName = fromCol @b oldCol
|
|
newFieldName = fromCol @b newCol
|
|
|
|
updateSet =
|
|
Set.insert newFieldName . Set.delete oldFieldName
|
|
|
|
updateMapKey =
|
|
-- mapKeys is not available in 0.2.13.0
|
|
M.fromList . map (\(key, value) -> (if key == oldFieldName then newFieldName else key, value)) . M.toList
|
|
|
|
updateFieldCalls (RemoteFields fields) =
|
|
RemoteFields $
|
|
fields <&> \(FieldCall name (RemoteArguments args)) ->
|
|
FieldCall name $ RemoteArguments $ updateVariableName <$> args
|
|
|
|
updateVariableName =
|
|
fmap \v -> if v == oldColName then newColName else v
|
|
|
|
remoteRelationshipLens =
|
|
tableMetadataSetter @b source qt . tmRemoteRelationships . ix remoteRelationshipName . rrmDefinition
|
|
|
|
remoteSchemaLHSModifier =
|
|
remoteRelationshipLens . _RemoteSchemaRelDef . _2
|
|
%~ (rrdHasuraFields %~ updateSet)
|
|
. (rrdRemoteField %~ updateFieldCalls)
|
|
|
|
remoteSourceLHSModifier =
|
|
remoteRelationshipLens . _RemoteSourceRelDef . rsrFieldMapping %~ updateMapKey
|
|
|
|
tell $ MetadataModifier $ remoteSchemaLHSModifier . remoteSourceLHSModifier
|
|
where
|
|
parseGraphQLName txt =
|
|
G.mkName txt
|
|
`onNothing` throw400 ParseFailed (txt <> " is not a valid GraphQL name")
|
|
|
|
-- | Local helper: update a column's name in the right-hand side of a remote relationship.
|
|
--
|
|
-- In the case of remote _source_ relationships, the mapping from column to column needs to be
|
|
-- updated if one of the rhs columns has been renamed. A dependency is tracked from the rhs source's
|
|
-- column to the lhs source's relationship: when a rhs source's column has been renamed, this
|
|
-- function performs the corresponding update in the lhs source's relationship definition.
|
|
--
|
|
-- See 'renameColumnInMetadata'.
|
|
updateColInRemoteRelationshipRHS ::
|
|
forall source target m.
|
|
( MonadWriter MetadataModifier m,
|
|
Backend source,
|
|
Backend target
|
|
) =>
|
|
SourceName ->
|
|
TableName source ->
|
|
RemoteRelationshipName ->
|
|
RenameCol target ->
|
|
m ()
|
|
updateColInRemoteRelationshipRHS source tableName remoteRelationshipName (RenameItem _ oldCol newCol) =
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter @source source tableName
|
|
. tmRemoteRelationships
|
|
. ix remoteRelationshipName
|
|
. rrmDefinition
|
|
. _RemoteSourceRelDef
|
|
. rsrFieldMapping
|
|
%~ updateMapValue
|
|
where
|
|
oldFieldName = fromCol @target oldCol
|
|
newFieldName = fromCol @target newCol
|
|
updateMapValue =
|
|
fmap \value -> if value == oldFieldName then newFieldName else value
|
|
|
|
-- | Local helper: update a table's name in the right-hand side of a remote relationship.
|
|
--
|
|
-- In the case of remote _source_ relationships, the relationship definition targets a specific
|
|
-- table in the rhs source, and that reference needs to be updated if the targeted table has been
|
|
-- renamed. A dependency is tracked from the rhs source's table to the lhs source's relationship:
|
|
-- when a rhs table has been renamed, this function performs the corresponding update in the lhs
|
|
-- source's relationship definition.
|
|
--
|
|
-- See 'renameTableInMetadata'.
|
|
updateTableInRemoteRelationshipRHS ::
|
|
forall source target m.
|
|
( MonadWriter MetadataModifier m,
|
|
Backend source,
|
|
Backend target
|
|
) =>
|
|
SourceName ->
|
|
TableName source ->
|
|
RemoteRelationshipName ->
|
|
RenameTable target ->
|
|
m ()
|
|
updateTableInRemoteRelationshipRHS source tableName remoteRelationshipName (_, newTableName) =
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter @source source tableName
|
|
. tmRemoteRelationships
|
|
. ix remoteRelationshipName
|
|
. rrmDefinition
|
|
. _RemoteSourceRelDef
|
|
. rsrTable
|
|
.~ toJSON newTableName
|
|
|
|
updateColInObjRel ::
|
|
(Backend b) =>
|
|
TableName b ->
|
|
TableName b ->
|
|
RenameCol b ->
|
|
ObjRelUsing b ->
|
|
ObjRelUsing b
|
|
updateColInObjRel fromQT toQT rnCol = \case
|
|
RUFKeyOn c ->
|
|
RUFKeyOn $ updateRelChoice fromQT toQT rnCol c
|
|
RUManual manConfig ->
|
|
RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig
|
|
|
|
updateRelChoice ::
|
|
Backend b =>
|
|
TableName b ->
|
|
TableName b ->
|
|
RenameCol b ->
|
|
ObjRelUsingChoice b ->
|
|
ObjRelUsingChoice b
|
|
updateRelChoice fromQT toQT rnCol =
|
|
\case
|
|
SameTable col -> SameTable $ getNewCol rnCol fromQT col
|
|
RemoteTable t c -> RemoteTable t (getNewCol rnCol toQT c)
|
|
|
|
updateColInArrRel ::
|
|
(Backend b) =>
|
|
TableName b ->
|
|
TableName b ->
|
|
RenameCol b ->
|
|
ArrRelUsing b ->
|
|
ArrRelUsing b
|
|
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 b = HashMap (Column b) (Column b)
|
|
|
|
getNewCol ::
|
|
forall b f.
|
|
Backend b =>
|
|
Functor f =>
|
|
RenameCol b ->
|
|
TableName b ->
|
|
f (Column b) ->
|
|
f (Column b)
|
|
getNewCol rnCol qt cols =
|
|
if qt == opQT
|
|
then go <$> cols
|
|
else cols
|
|
where
|
|
RenameItem opQT oCol nCol = rnCol
|
|
go :: Column b -> Column b
|
|
go col
|
|
| col == oCol = nCol
|
|
| otherwise = col
|
|
|
|
updateRelManualConfig ::
|
|
forall b.
|
|
(Backend b) =>
|
|
TableName b ->
|
|
TableName b ->
|
|
RenameCol b ->
|
|
RelManualConfig b ->
|
|
RelManualConfig b
|
|
updateRelManualConfig fromQT toQT rnCol manConfig =
|
|
RelManualConfig tn (updateColMap fromQT toQT rnCol colMap) io
|
|
where
|
|
RelManualConfig tn colMap io = manConfig
|
|
|
|
updateColMap ::
|
|
forall b.
|
|
(Backend b) =>
|
|
TableName b ->
|
|
TableName b ->
|
|
RenameCol b ->
|
|
ColMap b ->
|
|
ColMap b
|
|
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 ::
|
|
forall b m.
|
|
(MonadWriter MetadataModifier m, BackendMetadata b) =>
|
|
SourceName ->
|
|
TableName b ->
|
|
Column b ->
|
|
Column b ->
|
|
m ()
|
|
possiblyUpdateCustomColumnNames source qt oCol nCol = do
|
|
let updateCustomColumns customColumns =
|
|
M.fromList $
|
|
flip map (M.toList customColumns) $
|
|
\(dbCol, val) -> (,val) $ if dbCol == oCol then nCol else dbCol
|
|
tell $
|
|
MetadataModifier $
|
|
tableMetadataSetter @b source qt . tmConfiguration . tcCustomColumnNames %~ updateCustomColumns
|