2018-06-27 16:11:32 +03:00
|
|
|
module Hasura.RQL.DDL.Schema.Diff
|
|
|
|
( TableMeta(..)
|
|
|
|
, PGColMeta(..)
|
|
|
|
, ConstraintMeta(..)
|
|
|
|
, fetchTableMeta
|
|
|
|
|
2019-04-17 19:29:39 +03:00
|
|
|
, getDifference
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
, TableDiff(..)
|
|
|
|
, getTableDiff
|
|
|
|
, getTableChangeDeps
|
|
|
|
|
|
|
|
, SchemaDiff(..)
|
|
|
|
, getSchemaDiff
|
|
|
|
, getSchemaChangeDeps
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
, FunctionMeta(..)
|
2019-02-05 08:57:03 +03:00
|
|
|
, funcFromMeta
|
2019-01-25 06:31:54 +03:00
|
|
|
, fetchFunctionMeta
|
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
|
|
|
import Hasura.RQL.Types
|
2019-02-14 07:05:18 +03:00
|
|
|
import Hasura.Server.Utils (duplicates)
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
2018-08-10 15:44:44 +03:00
|
|
|
import qualified Database.PG.Query as Q
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
import Data.Aeson.Casing
|
|
|
|
import Data.Aeson.TH
|
|
|
|
|
2018-08-10 15:44:44 +03:00
|
|
|
import qualified Data.HashMap.Strict as M
|
|
|
|
import qualified Data.HashSet as HS
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
data PGColMeta
|
|
|
|
= PGColMeta
|
|
|
|
{ pcmColumnName :: !PGCol
|
|
|
|
, pcmOrdinalPosition :: !Int
|
2019-08-06 18:27:35 +03:00
|
|
|
, pcmDataType :: !PGScalarType
|
2018-08-10 15:44:44 +03:00
|
|
|
, pcmIsNullable :: !Bool
|
2019-07-22 15:47:13 +03:00
|
|
|
, pcmReferences :: ![QualifiedTable]
|
2018-06-27 16:11:32 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''PGColMeta)
|
|
|
|
|
|
|
|
data ConstraintMeta
|
|
|
|
= ConstraintMeta
|
2019-01-03 06:58:12 +03:00
|
|
|
{ cmName :: !ConstraintName
|
|
|
|
, cmOid :: !Int
|
|
|
|
, cmType :: !ConstraintType
|
2018-06-27 16:11:32 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''ConstraintMeta)
|
|
|
|
|
|
|
|
data TableMeta
|
|
|
|
= TableMeta
|
|
|
|
{ tmOid :: !Int
|
|
|
|
, tmTable :: !QualifiedTable
|
|
|
|
, tmColumns :: ![PGColMeta]
|
|
|
|
, tmConstraints :: ![ConstraintMeta]
|
2019-06-03 13:21:55 +03:00
|
|
|
, tmForeignKeys :: ![ForeignKey]
|
2018-06-27 16:11:32 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
fetchTableMeta :: Q.Tx [TableMeta]
|
|
|
|
fetchTableMeta = do
|
2019-06-03 13:21:55 +03:00
|
|
|
res <- Q.listQ $(Q.sqlFromFile "src-rsr/table_meta.sql") () False
|
|
|
|
forM res $ \(ts, tn, toid, cols, constrnts, fkeys) ->
|
|
|
|
return $ TableMeta toid (QualifiedObject ts tn) (Q.getAltJ cols)
|
|
|
|
(Q.getAltJ constrnts) (Q.getAltJ fkeys)
|
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))
|
|
|
|
|
|
|
|
data TableDiff
|
|
|
|
= TableDiff
|
2019-01-03 06:58:12 +03:00
|
|
|
{ _tdNewName :: !(Maybe QualifiedTable)
|
|
|
|
, _tdDroppedCols :: ![PGCol]
|
2019-08-11 18:34:38 +03:00
|
|
|
, _tdAddedCols :: ![PGRawColumnInfo]
|
|
|
|
, _tdAlteredCols :: ![(PGRawColumnInfo, PGRawColumnInfo)]
|
2019-01-03 06:58:12 +03:00
|
|
|
, _tdDroppedFKeyCons :: ![ConstraintName]
|
|
|
|
-- 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]
|
2018-06-27 16:11:32 +03:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
getTableDiff :: TableMeta -> TableMeta -> TableDiff
|
|
|
|
getTableDiff oldtm newtm =
|
2019-01-03 06:58:12 +03:00
|
|
|
TableDiff mNewName droppedCols addedCols alteredCols
|
|
|
|
droppedFKeyConstraints uniqueOrPrimaryCons
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
mNewName = bool (Just $ tmTable newtm) Nothing $ tmTable oldtm == tmTable newtm
|
|
|
|
oldCols = tmColumns oldtm
|
|
|
|
newCols = tmColumns newtm
|
|
|
|
|
2019-01-03 06:58:12 +03:00
|
|
|
uniqueOrPrimaryCons =
|
2019-03-22 10:08:42 +03:00
|
|
|
[cmName cm | cm <- tmConstraints newtm, isUniqueOrPrimary (cmType cm)]
|
2019-01-03 06:58:12 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
droppedCols =
|
|
|
|
map pcmColumnName $ getDifference pcmOrdinalPosition oldCols newCols
|
|
|
|
|
|
|
|
addedCols =
|
|
|
|
map pcmToPci $ getDifference pcmOrdinalPosition newCols oldCols
|
|
|
|
|
|
|
|
existingCols = getOverlap pcmOrdinalPosition oldCols newCols
|
|
|
|
|
2019-07-22 15:47:13 +03:00
|
|
|
pcmToPci (PGColMeta colName _ colType isNullable references)
|
2019-08-11 18:34:38 +03:00
|
|
|
= PGRawColumnInfo colName colType isNullable references
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
alteredCols =
|
2019-03-01 12:17:22 +03:00
|
|
|
flip map (filter (uncurry (/=)) existingCols) $ pcmToPci *** pcmToPci
|
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
|
|
|
|
droppedFKeyConstraints = map _fkConstraint $ HS.toList $
|
|
|
|
droppedFKeysWithOid `HS.intersection` droppedFKeysWithUniq
|
|
|
|
|
|
|
|
droppedFKeysWithOid = HS.fromList $
|
|
|
|
getDifference _fkOid (tmForeignKeys oldtm) (tmForeignKeys newtm)
|
|
|
|
|
|
|
|
droppedFKeysWithUniq = HS.fromList $
|
|
|
|
getDifference mkFKeyUniqId (tmForeignKeys oldtm) (tmForeignKeys newtm)
|
|
|
|
|
|
|
|
mkFKeyUniqId (ForeignKey _ reftn _ _ colMap) = (reftn, colMap)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
getTableChangeDeps
|
|
|
|
:: (QErrM m, CacheRWM m)
|
2019-08-11 18:34:38 +03:00
|
|
|
=> TableInfo PGColumnInfo -> TableDiff -> m [SchemaObjId]
|
2018-06-27 16:11:32 +03:00
|
|
|
getTableChangeDeps ti tableDiff = do
|
|
|
|
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
|
2018-06-27 16:11:32 +03:00
|
|
|
let objId = SOTableObj tn $ TOCons droppedCons
|
|
|
|
return $ getDependentObjs sc objId
|
|
|
|
return $ droppedConsDeps <> droppedColDeps
|
|
|
|
where
|
2019-07-22 15:47:13 +03:00
|
|
|
tn = _tiName ti
|
2019-01-03 06:58:12 +03:00
|
|
|
TableDiff _ droppedCols _ _ droppedFKeyConstraints _ = tableDiff
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
data SchemaDiff
|
|
|
|
= SchemaDiff
|
|
|
|
{ _sdDroppedTables :: ![QualifiedTable]
|
|
|
|
, _sdAlteredTables :: ![(QualifiedTable, TableDiff)]
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
getSchemaDiff :: [TableMeta] -> [TableMeta] -> SchemaDiff
|
|
|
|
getSchemaDiff oldMeta newMeta =
|
|
|
|
SchemaDiff droppedTables survivingTables
|
|
|
|
where
|
|
|
|
droppedTables = map tmTable $ getDifference tmOid oldMeta newMeta
|
|
|
|
survivingTables =
|
|
|
|
flip map (getOverlap tmOid oldMeta newMeta) $ \(oldtm, newtm) ->
|
|
|
|
(tmTable oldtm, getTableDiff oldtm newtm)
|
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
getSchemaChangeDeps
|
|
|
|
:: (QErrM m, CacheRWM m)
|
|
|
|
=> SchemaDiff -> 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
|
|
|
|
tableModDeps <- fmap concat $ forM alteredTables $ \(oldQtn, tableDiff) -> do
|
|
|
|
ti <- case M.lookup oldQtn $ scTables sc of
|
|
|
|
Just ti -> return ti
|
|
|
|
Nothing -> throw500 $ "old table metadata not found in cache : " <>> oldQtn
|
|
|
|
getTableChangeDeps ti tableDiff
|
|
|
|
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
|
|
|
|
|
|
|
data FunctionMeta
|
|
|
|
= FunctionMeta
|
2019-02-05 08:57:03 +03:00
|
|
|
{ fmOid :: !Int
|
|
|
|
, fmSchema :: !SchemaName
|
|
|
|
, fmName :: !FunctionName
|
|
|
|
, fmType :: !FunctionType
|
2019-01-25 06:31:54 +03:00
|
|
|
} deriving (Show, Eq)
|
2019-02-05 08:57:03 +03:00
|
|
|
$(deriveJSON (aesonDrop 2 snakeCase) ''FunctionMeta)
|
|
|
|
|
|
|
|
funcFromMeta :: FunctionMeta -> QualifiedFunction
|
|
|
|
funcFromMeta fm = QualifiedObject (fmSchema fm) (fmName fm)
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
fetchFunctionMeta :: Q.Tx [FunctionMeta]
|
2019-03-07 13:24:07 +03:00
|
|
|
fetchFunctionMeta =
|
2019-02-05 08:57:03 +03:00
|
|
|
map (Q.getAltJ . runIdentity) <$> Q.listQ [Q.sql|
|
2019-01-25 06:31:54 +03:00
|
|
|
SELECT
|
2019-02-05 08:57:03 +03:00
|
|
|
json_build_object(
|
|
|
|
'oid', p.oid :: integer,
|
|
|
|
'schema', f.function_schema,
|
|
|
|
'name', f.function_name,
|
|
|
|
'type', f.function_type
|
|
|
|
) AS function_meta
|
|
|
|
FROM
|
|
|
|
hdb_catalog.hdb_function_agg f
|
|
|
|
JOIN pg_catalog.pg_proc p ON (p.proname = f.function_name)
|
|
|
|
JOIN pg_catalog.pg_namespace pn ON (
|
|
|
|
pn.oid = p.pronamespace
|
|
|
|
AND pn.nspname = f.function_schema
|
|
|
|
)
|
2019-01-25 06:31:54 +03:00
|
|
|
WHERE
|
2019-02-05 08:57:03 +03:00
|
|
|
f.function_schema <> 'hdb_catalog'
|
2019-02-14 07:05:18 +03:00
|
|
|
GROUP BY p.oid, f.function_schema, f.function_name, f.function_type
|
2019-02-05 08:57:03 +03:00
|
|
|
|] () False
|
|
|
|
|
|
|
|
data FunctionDiff
|
|
|
|
= FunctionDiff
|
|
|
|
{ fdDropped :: ![QualifiedFunction]
|
|
|
|
, fdAltered :: ![(QualifiedFunction, FunctionType)]
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
getFuncDiff :: [FunctionMeta] -> [FunctionMeta] -> FunctionDiff
|
|
|
|
getFuncDiff oldMeta newMeta =
|
|
|
|
FunctionDiff droppedFuncs alteredFuncs
|
|
|
|
where
|
|
|
|
droppedFuncs = map funcFromMeta $ getDifference fmOid oldMeta newMeta
|
|
|
|
alteredFuncs = mapMaybe mkAltered $ getOverlap fmOid oldMeta newMeta
|
|
|
|
mkAltered (oldfm, newfm) =
|
|
|
|
let isTypeAltered = fmType oldfm /= fmType newfm
|
|
|
|
alteredFunc = (funcFromMeta oldfm, fmType newfm)
|
|
|
|
in bool Nothing (Just alteredFunc) isTypeAltered
|
2019-02-14 07:05:18 +03:00
|
|
|
|
|
|
|
getOverloadedFuncs
|
|
|
|
:: [QualifiedFunction] -> [FunctionMeta] -> [QualifiedFunction]
|
|
|
|
getOverloadedFuncs trackedFuncs newFuncMeta =
|
|
|
|
duplicates $ map funcFromMeta trackedMeta
|
|
|
|
where
|
|
|
|
trackedMeta = flip filter newFuncMeta $ \fm ->
|
|
|
|
funcFromMeta fm `elem` trackedFuncs
|