@@ -13,6 +13,7 @@ import Data.Map (Map)
13import qualified Data.Map as Map
13import qualified Data.Map as Map
14import Data.Maybe (catMaybes)
14import Data.Maybe (catMaybes)
15import Data.Semigroup ((<>))
15import Data.Semigroup ((<>))
16import qualified Data.Set as Set
17import qualified Data.Set as Set
17import Data.Text (Text)
18import Data.Text (Text)
18import qualified Data.Text as T
19import qualified Data.Text as T
@@ -33,6 +34,8 @@ data Migration action = Migration
33-- A path is a list of edges
34-- A path is a list of edges
34type Path = [(Hash, Hash)]
35type Path = [(Hash, Hash)]
37+data GraphInternalError = GraphInternalError Text
37data CyclicGraphError = CyclicGraphError
40data CyclicGraphError = CyclicGraphError
38 deriving Show
41 deriving Show
@@ -49,11 +52,18 @@ data NonEquivalentPaths = NonEquivalentPaths Hash Hash
49data MissingEdgeInActionMap = MissingEdgeInActionMap (Hash, Hash)
52data MissingEdgeInActionMap = MissingEdgeInActionMap (Hash, Hash)
50 deriving Show
53 deriving Show
56+ = NonLinearGraph_MultipleAdjacents Hash
57+ | NonLinearGraph_NotConnected (Set Hash)
60+instance Exception GraphInternalError
52instance Exception CyclicGraphError
61instance Exception CyclicGraphError
53instance Exception MultipleTerminalVertices
62instance Exception MultipleTerminalVertices
54instance Exception NoTerminalVertex
63instance Exception NoTerminalVertex
55instance Exception NonEquivalentPaths
64instance Exception NonEquivalentPaths
56instance Exception MissingEdgeInActionMap
65instance Exception MissingEdgeInActionMap
66+instance Exception NonLinearGraph
58class Action action where
68class Action action where
59 parseEdgeMeta :: String -> action
69 parseEdgeMeta :: String -> action
@@ -62,6 +72,7 @@ instance Action Text where
62 parseEdgeMeta = T.pack
72 parseEdgeMeta = T.pack
64-- | Get the directed-acylic graph of migration
74-- | Get the directed-acylic graph of migration
75+-- TODO: Consider calling getDag only when reading the graph for efficiency.
65getDag :: (MonadThrow m, Ord a) => AdjacencyMap a -> m (AdjacencyMap a)
76getDag :: (MonadThrow m, Ord a) => AdjacencyMap a -> m (AdjacencyMap a)
66getDag g = case topSort g of
77getDag g = case topSort g of
67 Just _ -> pure g
78 Just _ -> pure g
@@ -89,7 +100,7 @@ readGraph root name = doesDirectoryExist root >>= \case
89 False -> do
100 False -> do
90 return Nothing
101 return Nothing
92−-- | Ensures that the migration graph is correct
103+-- | Ensure that the migration graph is correct
94-- In a correct migration graph: for all pairs of nodes, all paths between
105-- In a correct migration graph: for all pairs of nodes, all paths between
95-- those nodes have the same migration value
106-- those nodes have the same migration value
@@ -122,6 +133,25 @@ ensureGraphIntegrity m = do
122 -- further above).
133 -- further above).
123 visitFrom mempty mempty firstVertex
134 visitFrom mempty mempty firstVertex
136+-- | Ensure that the graph is a fully connected linear list
137+ensureGraphLinearity :: MonadThrow m => Migration action -> m ()
138+ensureGraphLinearity m = do
139+ graph <- fmap adjacencyMap $ getDag $ _migration_graph m
140+ firstVertex <- getFirst $ _migration_graph m
142+ visitFrom start = case Map.lookup start graph of
143+ Nothing -> throwM $ GraphInternalError $ "Vertex not found: " <> start
144+ Just adjs -> case (Set.toList adjs) of
146+ [adj] -> start : visitFrom adj
147+ _ -> throwM $ NonLinearGraph_MultipleAdjacents start
148+ visitedVertices = Set.fromList $ visitFrom firstVertex
149+ allVertices = vertexSet $ _migration_graph m
150+ unvisitedVertices = Set.difference allVertices visitedVertices
151+ if Set.null unvisitedVertices
153+ else throwM $ NonLinearGraph_NotConnected unvisitedVertices
125-- | Find the concatenated actions between two vertices
155-- | Find the concatenated actions between two vertices
127-- All paths between the vertices must have the same mappend'ed action;
157-- All paths between the vertices must have the same mappend'ed action;