2018-06-27 16:11:32 +03:00
|
|
|
module Hasura.RQL.DDL.Schema.Diff
|
|
|
|
( TableMeta(..)
|
2019-10-18 11:29:47 +03:00
|
|
|
, ComputedFieldMeta(..)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
, fetchMeta
|
|
|
|
|
2019-04-17 19:29:39 +03:00
|
|
|
, getDifference
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
, TableDiff(..)
|
|
|
|
, getTableDiff
|
|
|
|
, getTableChangeDeps
|
2019-10-18 11:29:47 +03:00
|
|
|
, ComputedFieldDiff(..)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
, SchemaDiff(..)
|
|
|
|
, getSchemaDiff
|
|
|
|
, getSchemaChangeDeps
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
, FunctionMeta(..)
|
2019-02-05 08:57:03 +03:00
|
|
|
, FunctionDiff(..)
|
|
|
|
, getFuncDiff
|
2019-02-14 07:05:18 +03:00
|
|
|
, getOverloadedFuncs
|
2018-06-27 16:11:32 +03:00
|
|
|
) where
|
|
|
|
|
2018-08-10 15:44:44 +03:00
|
|
|
import Hasura.Prelude
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-10-27 16:53:49 +03:00
|
|
|
import qualified Data.HashMap.Strict as M
|
|
|
|
import qualified Data.HashSet as HS
|
|
|
|
import qualified Data.List.NonEmpty as NE
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
import Data.Aeson.Casing
|
|
|
|
import Data.Aeson.TH
|
2020-10-27 16:53:49 +03:00
|
|
|
import Data.List.Extended (duplicates)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-10-27 16:53:49 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
2020-12-08 17:22:31 +03:00
|
|
|
import Hasura.RQL.DDL.Schema.Common
|
2020-11-12 12:25:48 +03:00
|
|
|
import Hasura.RQL.Types hiding (ConstraintName, fmFunction,
|
|
|
|
tmComputedFields, tmTable)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-10-18 11:29:47 +03:00
|
|
|
data FunctionMeta
|
|
|
|
= FunctionMeta
|
2019-12-09 07:18:53 +03:00
|
|
|
{ fmOid :: !OID
|
2019-11-27 01:49:42 +03:00
|
|
|
, fmFunction :: !QualifiedFunction
|
2020-11-18 21:04:57 +03:00
|
|
|
, fmType :: !FunctionVolatility
|
2019-10-18 11:29:47 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
$(deriveJSON (aesonDrop 2 snakeCase) ''FunctionMeta)
|
|
|
|
|
|
|
|
data ComputedFieldMeta
|
|
|
|
= ComputedFieldMeta
|
|
|
|
{ ccmName :: !ComputedFieldName
|
|
|
|
, ccmFunctionMeta :: !FunctionMeta
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''ComputedFieldMeta)
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
data TableMeta
|
|
|
|
= TableMeta
|
2019-12-09 07:18:53 +03:00
|
|
|
{ tmTable :: !QualifiedTable
|
2020-12-08 17:22:31 +03:00
|
|
|
, tmInfo :: !PGTableMetadata
|
2019-10-18 11:29:47 +03:00
|
|
|
, tmComputedFields :: ![ComputedFieldMeta]
|
2020-12-08 17:22:31 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
fetchMeta
|
|
|
|
:: (MonadTx m)
|
|
|
|
=> TableCache 'Postgres
|
|
|
|
-> FunctionCache
|
|
|
|
-> m ([TableMeta], [FunctionMeta])
|
|
|
|
fetchMeta tables functions = do
|
|
|
|
tableMetaInfos <- fetchTableMetadata
|
|
|
|
functionMetaInfos <- fetchFunctionMetadata
|
|
|
|
|
|
|
|
let getFunctionMetas function =
|
|
|
|
let mkFunctionMeta rawInfo =
|
|
|
|
FunctionMeta (rfiOid rawInfo) function (rfiFunctionType rawInfo)
|
|
|
|
in maybe [] (map mkFunctionMeta) $ M.lookup function functionMetaInfos
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
mkComputedFieldMeta computedField =
|
|
|
|
let function = _cffName $ _cfiFunction computedField
|
|
|
|
in map (ComputedFieldMeta (_cfiName computedField)) $ getFunctionMetas function
|
|
|
|
|
|
|
|
tableMetas = flip map (M.toList tableMetaInfos) $ \(table, tableMetaInfo) ->
|
|
|
|
TableMeta table tableMetaInfo $ fromMaybe [] $
|
|
|
|
M.lookup table tables <&> \tableInfo ->
|
|
|
|
let tableCoreInfo = _tiCoreInfo tableInfo
|
|
|
|
computedFields = getComputedFieldInfos $ _tciFieldInfoMap tableCoreInfo
|
|
|
|
in concatMap mkComputedFieldMeta computedFields
|
|
|
|
|
|
|
|
functionMetas = concatMap getFunctionMetas $ M.keys functions
|
|
|
|
|
|
|
|
pure (tableMetas, functionMetas)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
getOverlap :: (Eq k, Hashable k) => (v -> k) -> [v] -> [v] -> [(v, v)]
|
|
|
|
getOverlap getKey left right =
|
|
|
|
M.elems $ M.intersectionWith (,) (mkMap left) (mkMap right)
|
|
|
|
where
|
|
|
|
mkMap = M.fromList . map (\v -> (getKey v, v))
|
|
|
|
|
|
|
|
getDifference :: (Eq k, Hashable k) => (v -> k) -> [v] -> [v] -> [v]
|
|
|
|
getDifference getKey left right =
|
|
|
|
M.elems $ M.difference (mkMap left) (mkMap right)
|
|
|
|
where
|
|
|
|
mkMap = M.fromList . map (\v -> (getKey v, v))
|
|
|
|
|
2019-10-18 11:29:47 +03:00
|
|
|
data ComputedFieldDiff
|
|
|
|
= ComputedFieldDiff
|
|
|
|
{ _cfdDropped :: [ComputedFieldName]
|
|
|
|
, _cfdAltered :: [(ComputedFieldMeta, ComputedFieldMeta)]
|
|
|
|
, _cfdOverloaded :: [(ComputedFieldName, QualifiedFunction)]
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
2020-11-12 12:25:48 +03:00
|
|
|
data TableDiff (b :: BackendType)
|
2018-06-27 16:11:32 +03:00
|
|
|
= TableDiff
|
2019-01-03 06:58:12 +03:00
|
|
|
{ _tdNewName :: !(Maybe QualifiedTable)
|
2020-10-22 23:42:27 +03:00
|
|
|
, _tdDroppedCols :: ![Column b]
|
|
|
|
, _tdAddedCols :: ![RawColumnInfo b]
|
|
|
|
, _tdAlteredCols :: ![(RawColumnInfo b, RawColumnInfo b)]
|
2019-01-03 06:58:12 +03:00
|
|
|
, _tdDroppedFKeyCons :: ![ConstraintName]
|
2019-10-18 11:29:47 +03:00
|
|
|
, _tdComputedFields :: !ComputedFieldDiff
|
2019-01-03 06:58:12 +03:00
|
|
|
-- The final list of uniq/primary constraint names
|
|
|
|
-- used for generating types on_conflict clauses
|
|
|
|
-- TODO: this ideally should't be part of TableDiff
|
2019-03-22 10:08:42 +03:00
|
|
|
, _tdUniqOrPriCons :: ![ConstraintName]
|
2019-09-17 04:51:11 +03:00
|
|
|
, _tdNewDescription :: !(Maybe PGDescription)
|
2020-10-22 23:42:27 +03:00
|
|
|
}
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-10-22 23:42:27 +03:00
|
|
|
getTableDiff :: TableMeta -> TableMeta -> TableDiff 'Postgres
|
2018-06-27 16:11:32 +03:00
|
|
|
getTableDiff oldtm newtm =
|
2019-01-03 06:58:12 +03:00
|
|
|
TableDiff mNewName droppedCols addedCols alteredCols
|
2019-11-07 17:39:48 +03:00
|
|
|
droppedFKeyConstraints computedFieldDiff uniqueOrPrimaryCons mNewDesc
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
mNewName = bool (Just $ tmTable newtm) Nothing $ tmTable oldtm == tmTable newtm
|
2020-12-08 17:22:31 +03:00
|
|
|
oldCols = _ptmiColumns $ tmInfo oldtm
|
|
|
|
newCols = _ptmiColumns $ tmInfo newtm
|
2019-09-17 04:51:11 +03:00
|
|
|
|
2019-12-09 07:18:53 +03:00
|
|
|
uniqueOrPrimaryCons = map _cName $
|
2020-12-08 17:22:31 +03:00
|
|
|
maybeToList (_pkConstraint <$> _ptmiPrimaryKey (tmInfo newtm))
|
|
|
|
<> toList (_ptmiUniqueConstraints $ tmInfo newtm)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
mNewDesc = _ptmiDescription $ tmInfo newtm
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-12-09 07:18:53 +03:00
|
|
|
droppedCols = map prciName $ getDifference prciPosition oldCols newCols
|
|
|
|
addedCols = getDifference prciPosition newCols oldCols
|
|
|
|
existingCols = getOverlap prciPosition oldCols newCols
|
|
|
|
alteredCols = filter (uncurry (/=)) existingCols
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-06-03 13:21:55 +03:00
|
|
|
-- foreign keys are considered dropped only if their oid
|
|
|
|
-- and (ref-table, column mapping) are changed
|
2019-12-09 07:18:53 +03:00
|
|
|
droppedFKeyConstraints = map (_cName . _fkConstraint) $ HS.toList $
|
2019-06-03 13:21:55 +03:00
|
|
|
droppedFKeysWithOid `HS.intersection` droppedFKeysWithUniq
|
2020-12-08 17:22:31 +03:00
|
|
|
tmForeignKeys = fmap unPGForeignKeyMetadata . toList . _ptmiForeignKeys . tmInfo
|
2019-06-03 13:21:55 +03:00
|
|
|
droppedFKeysWithOid = HS.fromList $
|
2019-12-09 07:18:53 +03:00
|
|
|
(getDifference (_cOid . _fkConstraint) `on` tmForeignKeys) oldtm newtm
|
2019-06-03 13:21:55 +03:00
|
|
|
droppedFKeysWithUniq = HS.fromList $
|
2019-12-09 07:18:53 +03:00
|
|
|
(getDifference mkFKeyUniqId `on` tmForeignKeys) oldtm newtm
|
|
|
|
mkFKeyUniqId (ForeignKey _ reftn colMap) = (reftn, colMap)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-10-18 11:29:47 +03:00
|
|
|
-- calculate computed field diff
|
|
|
|
oldComputedFieldMeta = tmComputedFields oldtm
|
|
|
|
newComputedFieldMeta = tmComputedFields newtm
|
|
|
|
|
|
|
|
droppedComputedFields = map ccmName $
|
|
|
|
getDifference (fmOid . ccmFunctionMeta) oldComputedFieldMeta newComputedFieldMeta
|
|
|
|
|
|
|
|
alteredComputedFields =
|
|
|
|
getOverlap (fmOid . ccmFunctionMeta) oldComputedFieldMeta newComputedFieldMeta
|
|
|
|
|
|
|
|
overloadedComputedFieldFunctions =
|
|
|
|
let getFunction = fmFunction . ccmFunctionMeta
|
|
|
|
getSecondElement (_ NE.:| list) = listToMaybe list
|
|
|
|
in mapMaybe (fmap ((&&&) ccmName getFunction) . getSecondElement) $
|
|
|
|
flip NE.groupBy newComputedFieldMeta $ \l r ->
|
|
|
|
ccmName l == ccmName r && getFunction l == getFunction r
|
|
|
|
|
2019-11-07 17:39:48 +03:00
|
|
|
computedFieldDiff = ComputedFieldDiff droppedComputedFields alteredComputedFields
|
2019-10-18 11:29:47 +03:00
|
|
|
overloadedComputedFieldFunctions
|
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
getTableChangeDeps
|
2019-11-20 21:21:30 +03:00
|
|
|
:: (QErrM m, CacheRM m)
|
2020-10-22 23:42:27 +03:00
|
|
|
=> QualifiedTable -> TableDiff 'Postgres -> m [SchemaObjId]
|
2019-11-20 21:21:30 +03:00
|
|
|
getTableChangeDeps tn tableDiff = do
|
2018-06-27 16:11:32 +03:00
|
|
|
sc <- askSchemaCache
|
|
|
|
-- for all the dropped columns
|
|
|
|
droppedColDeps <- fmap concat $ forM droppedCols $ \droppedCol -> do
|
|
|
|
let objId = SOTableObj tn $ TOCol droppedCol
|
|
|
|
return $ getDependentObjs sc objId
|
|
|
|
-- for all dropped constraints
|
2019-01-03 06:58:12 +03:00
|
|
|
droppedConsDeps <- fmap concat $ forM droppedFKeyConstraints $ \droppedCons -> do
|
2019-12-09 07:18:53 +03:00
|
|
|
let objId = SOTableObj tn $ TOForeignKey droppedCons
|
2018-06-27 16:11:32 +03:00
|
|
|
return $ getDependentObjs sc objId
|
2019-10-18 11:29:47 +03:00
|
|
|
return $ droppedConsDeps <> droppedColDeps <> droppedComputedFieldDeps
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
2019-11-07 17:39:48 +03:00
|
|
|
TableDiff _ droppedCols _ _ droppedFKeyConstraints computedFieldDiff _ _ = tableDiff
|
|
|
|
droppedComputedFieldDeps = map (SOTableObj tn . TOComputedField) $ _cfdDropped computedFieldDiff
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-11-12 12:25:48 +03:00
|
|
|
data SchemaDiff (b :: BackendType)
|
2018-06-27 16:11:32 +03:00
|
|
|
= SchemaDiff
|
|
|
|
{ _sdDroppedTables :: ![QualifiedTable]
|
2020-10-22 23:42:27 +03:00
|
|
|
, _sdAlteredTables :: ![(QualifiedTable, TableDiff b)]
|
|
|
|
}
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-10-22 23:42:27 +03:00
|
|
|
getSchemaDiff :: [TableMeta] -> [TableMeta] -> SchemaDiff 'Postgres
|
2018-06-27 16:11:32 +03:00
|
|
|
getSchemaDiff oldMeta newMeta =
|
|
|
|
SchemaDiff droppedTables survivingTables
|
|
|
|
where
|
2020-12-08 17:22:31 +03:00
|
|
|
droppedTables = map tmTable $ getDifference (_ptmiOid . tmInfo) oldMeta newMeta
|
2018-06-27 16:11:32 +03:00
|
|
|
survivingTables =
|
2020-12-08 17:22:31 +03:00
|
|
|
flip map (getOverlap (_ptmiOid . tmInfo) oldMeta newMeta) $ \(oldtm, newtm) ->
|
2018-06-27 16:11:32 +03:00
|
|
|
(tmTable oldtm, getTableDiff oldtm newtm)
|
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
getSchemaChangeDeps
|
2019-11-20 21:21:30 +03:00
|
|
|
:: (QErrM m, CacheRM m)
|
2020-10-22 23:42:27 +03:00
|
|
|
=> SchemaDiff 'Postgres -> m [SchemaObjId]
|
2018-06-27 16:11:32 +03:00
|
|
|
getSchemaChangeDeps schemaDiff = do
|
|
|
|
-- Get schema cache
|
|
|
|
sc <- askSchemaCache
|
|
|
|
let tableIds = map SOTable droppedTables
|
|
|
|
-- Get the dependent of the dropped tables
|
|
|
|
let tableDropDeps = concatMap (getDependentObjs sc) tableIds
|
2019-11-20 21:21:30 +03:00
|
|
|
tableModDeps <- concat <$> traverse (uncurry getTableChangeDeps) alteredTables
|
2018-06-27 16:11:32 +03:00
|
|
|
return $ filter (not . isDirectDep) $
|
|
|
|
HS.toList $ HS.fromList $ tableDropDeps <> tableModDeps
|
|
|
|
where
|
|
|
|
SchemaDiff droppedTables alteredTables = schemaDiff
|
|
|
|
|
2019-03-01 12:17:22 +03:00
|
|
|
isDirectDep (SOTableObj tn _) = tn `HS.member` HS.fromList droppedTables
|
2018-06-27 16:11:32 +03:00
|
|
|
isDirectDep _ = False
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2019-02-05 08:57:03 +03:00
|
|
|
data FunctionDiff
|
|
|
|
= FunctionDiff
|
|
|
|
{ fdDropped :: ![QualifiedFunction]
|
2020-11-18 21:04:57 +03:00
|
|
|
, fdAltered :: ![(QualifiedFunction, FunctionVolatility)]
|
2019-02-05 08:57:03 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
getFuncDiff :: [FunctionMeta] -> [FunctionMeta] -> FunctionDiff
|
|
|
|
getFuncDiff oldMeta newMeta =
|
|
|
|
FunctionDiff droppedFuncs alteredFuncs
|
|
|
|
where
|
2019-10-18 11:29:47 +03:00
|
|
|
droppedFuncs = map fmFunction $ getDifference fmOid oldMeta newMeta
|
2019-02-05 08:57:03 +03:00
|
|
|
alteredFuncs = mapMaybe mkAltered $ getOverlap fmOid oldMeta newMeta
|
|
|
|
mkAltered (oldfm, newfm) =
|
|
|
|
let isTypeAltered = fmType oldfm /= fmType newfm
|
2019-11-20 21:21:30 +03:00
|
|
|
alteredFunc = (fmFunction oldfm, fmType newfm)
|
|
|
|
in bool Nothing (Just alteredFunc) $ isTypeAltered
|
2019-02-14 07:05:18 +03:00
|
|
|
|
|
|
|
getOverloadedFuncs
|
|
|
|
:: [QualifiedFunction] -> [FunctionMeta] -> [QualifiedFunction]
|
|
|
|
getOverloadedFuncs trackedFuncs newFuncMeta =
|
2020-05-27 18:02:58 +03:00
|
|
|
toList $ duplicates $ map fmFunction trackedMeta
|
2019-02-14 07:05:18 +03:00
|
|
|
where
|
|
|
|
trackedMeta = flip filter newFuncMeta $ \fm ->
|
2019-10-18 11:29:47 +03:00
|
|
|
fmFunction fm `elem` trackedFuncs
|