1{-# LANGUAGE OverloadedStrings #-}
2module ProjectM36.TransactionGraph where
5import ProjectM36.Transaction
6import ProjectM36.Relation
7import ProjectM36.TupleSet
9import qualified Data.HashSet as HS
10import qualified Data.Vector as V
11import qualified ProjectM36.Attribute as A
12import qualified Data.UUID as U
13import qualified Data.Set as S
14import qualified Data.Map as M
15import Control.Applicative ((<$>))
17import qualified Data.Text as T
19--operators which manipulate a transaction graph
20data TransactionGraphOperator = JumpToHead HeadName |
21 JumpToTransaction U.UUID |
26data ROTransactionGraphOperator = ShowGraph
28bootstrapTransactionGraph :: U.UUID -> DatabaseContext -> TransactionGraph
29bootstrapTransactionGraph freshUUID context = TransactionGraph bootstrapHeads bootstrapTransactions
31 bootstrapHeads = M.singleton "master" freshTransaction
32 freshTransaction = Transaction freshUUID (TransactionInfo U.nil S.empty) context
33 bootstrapTransactions = S.singleton freshTransaction
35emptyTransactionGraph :: TransactionGraph
36emptyTransactionGraph = TransactionGraph M.empty S.empty
38transactionForHead :: HeadName -> TransactionGraph -> Maybe Transaction
39transactionForHead headName (TransactionGraph heads _) = M.lookup headName heads
41headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
42headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchingTrans then
45 Just $ (head . M.keys) matchingTrans
47 matchingTrans = M.filter (transaction ==) heads
49transactionForUUID :: U.UUID -> TransactionGraph -> Either RelationalError Transaction
50transactionForUUID uuid graph = if S.null matchingTrans then
51 Left $ NoSuchTransactionError uuid
53 Right $ head (S.toList matchingTrans)
55 matchingTrans = S.filter (\(Transaction uuidMatch _ _) -> uuidMatch == uuid) (transactionsForGraph graph)
57transactionsForUUIDs :: S.Set U.UUID -> TransactionGraph -> Either RelationalError (S.Set Transaction)
58transactionsForUUIDs uuidSet graph = do
59 transList <- forM (S.toList uuidSet) ((flip transactionForUUID) graph)
60 return (S.fromList transList)
62isRootTransaction :: Transaction -> TransactionGraph -> Bool
63isRootTransaction (Transaction _ (TransactionInfo pUUID _) _) _ = U.null pUUID
64isRootTransaction (Transaction _ (MergeTransactionInfo _ _ _) _) _ = False
66-- the first transaction has no parent - all other do have parents- merges have two parents
67parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
68parentTransactions (Transaction _ (TransactionInfo pUUID _) _) graph = do
69 trans <- transactionForUUID pUUID graph
70 return (S.singleton trans)
72parentTransactions (Transaction _ (MergeTransactionInfo pUUID1 pUUID2 _) _ ) graph = transactionsForUUIDs (S.fromList [pUUID1, pUUID2]) graph
75childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
76childTransactions (Transaction _ (TransactionInfo _ children) _) = transactionsForUUIDs children
77childTransactions (Transaction _ (MergeTransactionInfo _ _ children) _) = transactionsForUUIDs children
79-- create a new commit and add it to the heads
80-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
81addBranch :: U.UUID -> HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
82addBranch newUUID newBranchName branchPoint graph = addTransactionToGraph newBranchName branchPoint newUUID (transactionContext branchPoint) graph
84--adds a disconnected transaction to a transaction graph at some head
85addDisconnectedTransaction :: U.UUID -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
86addDisconnectedTransaction newUUID headName (DisconnectedTransaction parentUUID context) graph = do
87 parentTrans <- transactionForUUID parentUUID graph
88 addTransactionToGraph headName parentTrans newUUID context graph
90-- create a new transaction on "newHeadName" with the branchPointTrans
91addTransactionToGraph :: HeadName -> Transaction -> U.UUID -> DatabaseContext -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
92addTransactionToGraph newHeadName branchPointTrans newUUID newContext (TransactionGraph heads transSet) = do
93 let freshTransaction = Transaction newUUID (TransactionInfo (transactionUUID branchPointTrans) S.empty) newContext
94 -- there are two parents for MergeTransactions! not implemented
95 --update parentTransaction to add child
96 let parentTransaction = branchPointTrans
97 let updatedTransSet = (S.insert freshTransaction <$> S.insert parentTransaction <$> S.delete parentTransaction) transSet
98 let updatedGraph = TransactionGraph (M.insert newHeadName freshTransaction heads) updatedTransSet
99 return (freshTransaction, updatedGraph)
101validateGraph :: TransactionGraph -> Maybe [RelationalError]
102validateGraph graph@(TransactionGraph _ transSet) = do
103 --check that all UUIDs are unique in the graph
104 --uuids = map transactionUUID transSet
105 --check that all heads appear in the transSet
106 --check that all forward and backward links are in place
107 _ <- mapM (walkParentTransactions S.empty graph) (S.toList transSet)
108 mapM (walkChildTransactions S.empty graph) (S.toList transSet)
110--verify that all parent links exist and that all children exist
111--maybe verify that all parents end at UUID nil and all children end at leaves
112walkParentTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
113walkParentTransactions seenTransSet graph trans =
114 let transUUID = transactionUUID trans in
115 if transUUID == U.nil then
117 else if S.member transUUID seenTransSet then
118 Just $ TransactionGraphCycleError transUUID
120 let parentTransSetOrError = parentTransactions trans graph in
121 case parentTransSetOrError of
123 Right parentTransSet -> do
124 walk <- mapM (walkParentTransactions (S.insert transUUID seenTransSet) graph) (S.toList parentTransSet)
129--refactor: needless duplication in these two functions
130walkChildTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
131walkChildTransactions seenTransSet graph trans =
132 let transUUID = transactionUUID trans in
133 if childTransactions trans graph == Right S.empty then
135 else if S.member transUUID seenTransSet then
136 Just $ TransactionGraphCycleError transUUID
138 let childTransSetOrError = childTransactions trans graph in
139 case childTransSetOrError of
141 Right childTransSet -> do
142 walk <- mapM (walkChildTransactions (S.insert transUUID seenTransSet) graph) (S.toList childTransSet)
147-- returns the new "current" transaction, updated graph, and tutorial d result
148-- the current transaction is not part of the transaction graph until it is committed
149evalGraphOp :: U.UUID -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
151--affects only disconncted transaction
152evalGraphOp _ _ graph (JumpToTransaction jumpUUID) = case transactionForUUID jumpUUID graph of
154 Right parentTrans -> Right (newTrans, graph)
156 newTrans = DisconnectedTransaction jumpUUID (transactionContext parentTrans)
158-- switch from one head to another
159-- affects only disconnectedtransaction
160evalGraphOp _ _ graph (JumpToHead headName) =
161 case transactionForHead headName graph of
162 Just newHeadTransaction -> let disconnectedTrans = newDisconnectedTransaction (transactionUUID newHeadTransaction) (transactionContext newHeadTransaction) in
163 Right (disconnectedTrans, graph)
164 Nothing -> Left $ NoSuchHeadNameError headName
165-- add new head pointing to branchPoint
166-- repoint the disconnected transaction to the new branch commit (with a potentially different disconnected context)
167-- affects transactiongraph and the disconnectedtransaction is recreated based off the branch
169evalGraphOp newUUID discon@(DisconnectedTransaction parentUUID disconContext) graph (Branch newBranchName) = case transactionForUUID parentUUID graph of
170 Nothing -> (discon, graph, DisplayErrorResult "Failed to find parent transaction.")
171 Just parentTrans -> case addBranch newBranchName parentTrans graph of
172 Nothing -> (discon, graph, DisplayErrorResult "Failed to add branch.")
173 Just newGraph -> (newDiscon, newGraph, DisplayResult "Branched.")
175 newDiscon = DisconnectedTransaction (transactionUUID parentTrans) disconContext
178-- create a new commit and add it to the heads
179-- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)
180evalGraphOp newUUID (DisconnectedTransaction parentUUID disconContext) graph (Branch newBranchName) = case transactionForUUID parentUUID graph of
182 Right parentTrans -> case addBranch newUUID newBranchName parentTrans graph of
184 Right (_, newGraph) -> Right (newDiscon, newGraph)
186 newDiscon = DisconnectedTransaction newUUID disconContext
188-- add the disconnected transaction to the graph
189-- affects graph and disconnectedtransaction- the new disconnectedtransaction's parent is the freshly committed transaction
190evalGraphOp newTransUUID discon@(DisconnectedTransaction parentUUID context) graph Commit = case transactionForUUID parentUUID graph of
192 Right parentTransaction -> case headNameForTransaction parentTransaction graph of
193 Nothing -> Left $ TransactionIsNotAHeadError parentUUID
194 Just headName -> case maybeUpdatedGraph of
196 Right (_, updatedGraph) -> Right (newDisconnectedTrans, updatedGraph)
198 newDisconnectedTrans = newDisconnectedTransaction newTransUUID context
199 maybeUpdatedGraph = addDisconnectedTransaction newTransUUID headName discon graph
201-- refresh the disconnected transaction, return the same graph
202evalGraphOp _ (DisconnectedTransaction parentUUID _) graph Rollback = case transactionForUUID parentUUID graph of
204 Right parentTransaction -> Right (newDiscon, graph)
206 newDiscon = newDisconnectedTransaction parentUUID (transactionContext parentTransaction)
208--display transaction graph as relation
209evalROGraphOp :: DisconnectedTransaction -> TransactionGraph -> ROTransactionGraphOperator -> Either RelationalError Relation
210evalROGraphOp discon graph ShowGraph = do
211 graphRel <- graphAsRelation discon graph
214--present a transaction graph as a relation showing the uuids, parentuuids, and flag for the current location of the disconnected transaction
215graphAsRelation :: DisconnectedTransaction -> TransactionGraph -> Either RelationalError Relation
216graphAsRelation (DisconnectedTransaction parentUUID _) graph@(TransactionGraph _ transSet) = do
217 tupleMatrix <- mapM tupleGenerator (S.toList transSet)
218 mkRelationFromList attrs tupleMatrix
220 attrs = A.attributesFromList [Attribute "id" StringAtomType,
221 Attribute "parents" (RelationAtomType parentAttributes),
222 Attribute "current" BoolAtomType,
223 Attribute "head" StringAtomType
225 parentAttributes = A.attributesFromList [Attribute "id" StringAtomType]
226 tupleGenerator transaction = case transactionParentsRelation transaction graph of
228 Right parentTransRel -> Right [StringAtom $ T.pack $ show (transactionUUID transaction),
229 RelationAtom parentTransRel,
230 BoolAtom $ parentUUID == transactionUUID transaction,
231 StringAtom $ case headNameForTransaction transaction graph of
232 Just headName -> headName
236transactionParentsRelation :: Transaction -> TransactionGraph -> Either RelationalError Relation
237transactionParentsRelation trans graph = do
238 if isRootTransaction trans graph then do
239 mkRelation attrs emptyTupleSet
241 parentTransSet <- parentTransactions trans graph
242 let tupleSet = HS.fromList $ map trans2tuple (S.toList parentTransSet)
243 mkRelation attrs tupleSet
245 attrs = A.attributesFromList [Attribute "id" StringAtomType]
246 trans2tuple trans2 = mkRelationTuple attrs $ V.singleton (StringAtom (T.pack (show $ transactionUUID trans2)))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list