@@ -21,12 +21,14 @@ import Data.Binary
2121data TransactionGraphOperator = JumpToHead HeadName |
2222 JumpToTransaction U.UUID |
2323 Branch HeadName |
24+ MergeTransactions MergeStrategy HeadName HeadName |
2425 Commit |
2526 Rollback
2627 deriving (Eq, Show, Binary, Generic)
2728
2829data ROTransactionGraphOperator = ShowGraph
2930
31+
3032bootstrapTransactionGraph :: U.UUID -> DatabaseContext -> TransactionGraph
3133bootstrapTransactionGraph freshUUID context = TransactionGraph bootstrapHeads bootstrapTransactions
3234 where
@@ -206,6 +208,36 @@ evalGraphOp _ (DisconnectedTransaction parentUUID _) graph Rollback = case trans
206208 Right parentTransaction -> Right (newDiscon, graph)
207209 where
208210 newDiscon = newDisconnectedTransaction parentUUID (transactionContext parentTransaction)
211+
212+-- a successful merge should remove a head
213+evalGraphOp newUUID (DisconnectedTransaction parentUUID _) graph (MergeTransactions mergeStrategy headNameA headNameB) = do
214+ let transactionForHeadErr name = case transactionForHead name graph of
215+ Nothing -> Left (NoSuchHeadNameError name)
216+ Just t -> Right t
217+ transA <- transactionForHeadErr headNameA
218+ transB <- transactionForHeadErr headNameB
219+ let transAid = transactionUUID transA
220+ transBid = transactionUUID transB
221+ headNameToDelete <- if parentUUID == transAid then
222+ pure headNameB
223+ else if parentUUID == transBid then
224+ pure headNameA
225+ else
226+ Left (MergeTransactionError (DisconnectedTransactionNotAMergeHeadError parentUUID))
227+
228+ disconParent <- transactionForUUID parentUUID graph
229+ subGraph <- subGraphToFirstCommonAncestor graph emptyTransactionGraph transA transB
230+ case mergeTransactions newUUID mergeStrategy subGraph (transA, transB) of
231+ Left err -> Left (MergeTransactionError err)
232+ Right mergedTrans -> case headNameForTransaction disconParent graph of
233+ Nothing -> Left (TransactionIsNotAHeadError parentUUID)
234+ Just headName -> do
235+ let newDiscon = newDisconnectedTransaction mergedTransUUID (transactionContext mergedTrans)
236+ mergedTransUUID = transactionUUID mergedTrans
237+ (_, newGraph) <- addDisconnectedTransaction mergedTransUUID headName newDiscon graph
238+ let newGraph' = TransactionGraph newHeads (transactionsForGraph newGraph)
239+ newHeads = M.delete headNameToDelete (transactionHeadsForGraph newGraph)
240+ pure (newDiscon, newGraph')
209241
210242--present a transaction graph as a relation showing the uuids, parentuuids, and flag for the current location of the disconnected transaction
211243graphAsRelation :: DisconnectedTransaction -> TransactionGraph -> Either RelationalError Relation
@@ -249,3 +281,43 @@ evalROGraphOp discon graph ShowGraph = do
249281 return graphRel
250282-}
251283
284+--execute the merge strategy against the transactions, returning a new transaction to be added to the graph
285+-- a successful merge should also remove a head
286+mergeTransactions :: U.UUID -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
287+mergeTransactions newUUID (SelectedBranchMergeStrategy selectedBranch) graph t2@(trans1, trans2) = do
288+ selectedTrans <- validateHeadName selectedBranch graph t2
289+ pure $ Transaction newUUID (MergeTransactionInfo (transactionUUID trans1) (transactionUUID trans2) S.empty) (transactionContext selectedTrans)
290+
291+mergeTransactions newUUID UnionMergeStrategy graph t2 = undefined
292+
293+
294+-- | Returns the correct Transaction for the branch name in the graph and ensures that it is one of the two transaction arguments in the tuple.
295+validateHeadName :: HeadName -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
296+validateHeadName headName graph (t1, t2) = case transactionForHead headName graph of
297+ Nothing -> Left SelectedHeadMismatchMergeError
298+ Just trans -> if trans /= t1 && trans /= t2 then
299+ Left SelectedHeadMismatchMergeError
300+ else
301+ Right trans
302+
303+-- | Return the common ancestor of two transactions. When looking for the first common ancestor, we don't need to search both parents of a merge transaction because the merge must have diverged from a common ancestor anyway, so we arbitrarily just search one of the parents of a merge transaction. Technically, due to merge transactions, this may not actually return the first common ancestor, but it should be pretty close. The incoming subgraph's heads remain unchanged in the return value.
304+subGraphToFirstCommonAncestor :: TransactionGraph -> TransactionGraph -> Transaction -> Transaction -> Either RelationalError TransactionGraph
305+subGraphToFirstCommonAncestor origGraph subGraph trans1 trans2 = do
306+ let t1id = transactionUUID trans1
307+ t2id = transactionUUID trans2
308+ if t1id == t2id then
309+ Right subGraph
310+ else if U.null t1id || U.null t2id then --no common ancestor
311+ Left (NoCommonTransactionAncestorError t1id t2id)
312+ else do
313+ parentTrans1 <- oneParent trans1
314+ parentTrans2 <- oneParent trans2
315+ --add transactions to graph
316+ let subGraph' = TransactionGraph headNames newTrans
317+ headNames = transactionHeadsForGraph subGraph
318+ newTrans = S.union (transactionsForGraph subGraph) (S.fromList [parentTrans1, parentTrans2])
319+ subGraphToFirstCommonAncestor origGraph subGraph' parentTrans1 parentTrans2
320+ where
321+ oneParent (Transaction _ (TransactionInfo parentId _) _) = transactionForUUID parentId origGraph
322+ oneParent (Transaction _ (MergeTransactionInfo parentId _ _) _) = transactionForUUID parentId origGraph
323+