1{-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleContexts #-}
2module ProjectM36.TransactionGraph where
5import ProjectM36.Transaction
6import ProjectM36.TransactionInfo as TI
7import ProjectM36.Relation
8import ProjectM36.TupleSet
10import ProjectM36.RelationalExpression
11import ProjectM36.TransactionGraph.Merge
12import qualified ProjectM36.DisconnectedTransaction as Discon
13import qualified ProjectM36.Attribute as A
15import Control.Monad.Except hiding (join)
16import qualified Data.Vector as V
17import qualified Data.UUID as U
18import qualified Data.Set as S
19import qualified Data.Map as M
20import qualified Data.List.NonEmpty as NE
22import qualified Data.Text as T
25import Data.Either (lefts, rights, isRight)
29#if __GLASGOW_HASKELL__ <= 802
33-- | Record a lookup for a specific transaction in the graph.
34data TransactionIdLookup = TransactionIdLookup TransactionId |
35 TransactionIdHeadNameLookup HeadName [TransactionIdHeadBacktrack]
36 deriving (Show, Eq, Binary, Generic)
38-- | Used for git-style head backtracking such as topic~3^2.
39data TransactionIdHeadBacktrack = TransactionIdHeadParentBacktrack Int | -- ^ git equivalent of ~: walk back n parents, arbitrarily choosing a parent when a choice must be made
40 TransactionIdHeadBranchBacktrack Int | -- ^ git equivalent of ^: walk back one parent level to the nth arbitrarily-chosen parent
41 TransactionStampHeadBacktrack UTCTime -- ^ git equivalent of 'git-rev-list -n 1 --before X' find the first transaction which was created before the timestamp
42 deriving (Show, Eq, Binary, Generic)
45-- | Operators which manipulate a transaction graph and which transaction the current 'Session' is based upon.
46data TransactionGraphOperator = JumpToHead HeadName |
47 JumpToTransaction TransactionId |
48 WalkBackToTime UTCTime |
50 DeleteBranch HeadName |
51 MergeTransactions MergeStrategy HeadName HeadName |
54 deriving (Eq, Show, Binary, Generic)
56isCommit :: TransactionGraphOperator -> Bool
60data ROTransactionGraphOperator = ShowGraph
63bootstrapTransactionGraph :: UTCTime -> TransactionId -> DatabaseContext -> TransactionGraph
64bootstrapTransactionGraph stamp' freshId context = TransactionGraph bootstrapHeads bootstrapTransactions
66 bootstrapHeads = M.singleton "master" freshTransaction
67 newSchemas = Schemas context M.empty
68 freshTransaction = fresh freshId stamp' newSchemas
69 bootstrapTransactions = S.singleton freshTransaction
71-- | Create a transaction graph from a context.
72freshTransactionGraph :: DatabaseContext -> IO (TransactionGraph, TransactionId)
73freshTransactionGraph ctx = do
76 pure $ (bootstrapTransactionGraph now freshId ctx, freshId)
79emptyTransactionGraph :: TransactionGraph
80emptyTransactionGraph = TransactionGraph M.empty S.empty
82transactionForHead :: HeadName -> TransactionGraph -> Maybe Transaction
83transactionForHead headName graph = M.lookup headName (transactionHeadsForGraph graph)
85headList :: TransactionGraph -> [(HeadName, TransactionId)]
86headList graph = map (second transactionId) (M.assocs (transactionHeadsForGraph graph))
88headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
89headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchingTrans then
92 Just $ (head . M.keys) matchingTrans
94 matchingTrans = M.filter (transaction ==) heads
96transactionsForIds :: S.Set TransactionId -> TransactionGraph -> Either RelationalError (S.Set Transaction)
97transactionsForIds idSet graph =
98 S.fromList <$> forM (S.toList idSet) (`transactionForId` graph)
100-- | A root transaction terminates a graph and has no parents.
101isRootTransaction :: Transaction -> Bool
102isRootTransaction trans = parentIds trans == S.singleton U.nil
104rootTransactions :: TransactionGraph -> S.Set Transaction
105rootTransactions graph = S.filter isRootTransaction (transactionsForGraph graph)
107-- the first transaction has no parent - all other do have parents- merges have two parents
108parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
109parentTransactions trans graph = transactionsForIds (parentIds trans) graph
111childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
112childTransactions trans graph = transactionsForIds childIds graph
114 childIds = S.map transactionId (S.filter filt (transactionsForGraph graph))
115 filt trans' = S.member (transactionId trans) (parentIds trans')
117-- create a new commit and add it to the heads
118-- 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)
119addBranch :: UTCTime -> TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
120addBranch stamp' newId newBranchName branchPointId graph = do
121 parentTrans <- transactionForId branchPointId graph
122 let newTrans = Transaction newId (TI.singleParent branchPointId stamp') (schemas parentTrans)
123 addTransactionToGraph newBranchName newTrans graph
125--adds a disconnected transaction to a transaction graph at some head
126addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
127addDisconnectedTransaction stamp' newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName newTrans
129 newTrans = Transaction newId (TI.singleParent parentId stamp') schemas'
131addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
132addTransactionToGraph headName newTrans graph = do
133 let parentIds' = parentIds newTrans
134 newId = transactionId newTrans
135 validateIds ids = mapM (`transactionForId` graph) (S.toList ids)
136 childTs <- childTransactions newTrans graph
137 --validate that the parent transactions are in the graph
138 _ <- validateIds parentIds'
139 when (S.size parentIds' < 1) (Left $ NewTransactionMissingParentError newId)
140 --if the headName already exists, ensure that it refers to a parent
141 case transactionForHead headName graph of
142 Nothing -> pure () -- any headName is OK
143 Just trans -> when (S.notMember (transactionId trans) parentIds') (Left (HeadNameSwitchingHeadProhibitedError headName))
144 --validate that the transaction has no children
145 unless (S.null childTs) (Left $ NewTransactionMayNotHaveChildrenError newId)
146 --validate that the trasaction's id is unique
147 when (isRight (transactionForId newId graph)) (Left (TransactionIdInUseError newId))
149 let updatedTransSet = S.insert newTrans (transactionsForGraph graph)
150 updatedHeads = M.insert headName newTrans (transactionHeadsForGraph graph)
151 pure (newTrans, TransactionGraph updatedHeads updatedTransSet)
153validateGraph :: TransactionGraph -> Maybe [RelationalError]
154validateGraph graph@(TransactionGraph _ transSet) = do
155 --check that all transaction ids are unique in the graph
157 --uuids = map transactionId transSet
158 --check that all heads appear in the transSet
159 --check that all forward and backward links are in place
160 mapM_ (walkParentTransactions S.empty graph) (S.toList transSet)
161 mapM (walkChildTransactions S.empty graph) (S.toList transSet)
163--verify that all parent links exist and that all children exist
164--maybe verify that all parents end at transaction id nil and all children end at leaves
165walkParentTransactions :: S.Set TransactionId -> TransactionGraph -> Transaction -> Maybe RelationalError
166walkParentTransactions seenTransSet graph trans =
167 let transId = transactionId trans in
168 if transId == U.nil then
170 else if S.member transId seenTransSet then
171 Just $ TransactionGraphCycleError transId
173 let parentTransSetOrError = parentTransactions trans graph in
174 case parentTransSetOrError of
176 Right parentTransSet -> do
177 walk <- mapM (walkParentTransactions (S.insert transId seenTransSet) graph) (S.toList parentTransSet)
182--refactor: needless duplication in these two functions
183walkChildTransactions :: S.Set TransactionId -> TransactionGraph -> Transaction -> Maybe RelationalError
184walkChildTransactions seenTransSet graph trans =
185 let transId = transactionId trans in
186 if childTransactions trans graph == Right S.empty then
188 else if S.member transId seenTransSet then
189 Just $ TransactionGraphCycleError transId
191 let childTransSetOrError = childTransactions trans graph in
192 case childTransSetOrError of
194 Right childTransSet -> do
195 walk <- mapM (walkChildTransactions (S.insert transId seenTransSet) graph) (S.toList childTransSet)
200-- returns the new "current" transaction, updated graph, and tutorial d result
201-- the current transaction is not part of the transaction graph until it is committed
202evalGraphOp :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
204evalGraphOp _ _ _ graph (JumpToTransaction jumpId) = case transactionForId jumpId graph of
206 Right parentTrans -> Right (newTrans, graph)
208 newTrans = DisconnectedTransaction jumpId (schemas parentTrans) False
210-- switch from one head to another
211evalGraphOp _ _ _ graph (JumpToHead headName) =
212 case transactionForHead headName graph of
213 Just newHeadTransaction -> let disconnectedTrans = DisconnectedTransaction (transactionId newHeadTransaction) (schemas newHeadTransaction) False in
214 Right (disconnectedTrans, graph)
215 Nothing -> Left $ NoSuchHeadNameError headName
217evalGraphOp _ _ discon graph (WalkBackToTime backTime) = do
218 let startTransId = Discon.parentId discon
219 jumpDest <- backtrackGraph graph startTransId (TransactionStampHeadBacktrack backTime)
220 case transactionForId jumpDest graph of
223 let disconnectedTrans = Discon.freshTransaction (transactionId trans) (schemas trans)
224 Right (disconnectedTrans, graph)
226-- add new head pointing to branchPoint
227-- repoint the disconnected transaction to the new branch commit (with a potentially different disconnected context)
228-- affects transactiongraph and the disconnectedtransaction is recreated based off the branch
230evalGraphOp newId discon@(DisconnectedTransaction parentId disconContext) graph (Branch newBranchName) = case transactionForId parentId graph of
231 Nothing -> (discon, graph, DisplayErrorResult "Failed to find parent transaction.")
232 Just parentTrans -> case addBranch newBranchName parentTrans graph of
233 Nothing -> (discon, graph, DisplayErrorResult "Failed to add branch.")
234 Just newGraph -> (newDiscon, newGraph, DisplayResult "Branched.")
236 newDiscon = DisconnectedTransaction (transactionId parentTrans) disconContext
239-- create a new commit and add it to the heads
240-- 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)
241evalGraphOp stamp' newId (DisconnectedTransaction parentId schemas' _) graph (Branch newBranchName) = do
242 let newDiscon = Discon.freshTransaction newId schemas'
243 case addBranch stamp' newId newBranchName parentId graph of
245 Right (_, newGraph) -> Right (newDiscon, newGraph)
247-- add the disconnected transaction to the graph
248-- affects graph and disconnectedtransaction- the new disconnectedtransaction's parent is the freshly committed transaction
249evalGraphOp stamp' newTransId discon@(DisconnectedTransaction parentId schemas' _) graph Commit = case transactionForId parentId graph of
251 Right parentTransaction -> case headNameForTransaction parentTransaction graph of
252 Nothing -> Left $ TransactionIsNotAHeadError parentId
253 Just headName -> case maybeUpdatedGraph of
255 Right (_, updatedGraph) -> Right (newDisconnectedTrans, updatedGraph)
257 newDisconnectedTrans = Discon.freshTransaction newTransId schemas'
258 maybeUpdatedGraph = addDisconnectedTransaction stamp' newTransId headName discon graph
260-- refresh the disconnected transaction, return the same graph
261evalGraphOp _ _ (DisconnectedTransaction parentId _ _) graph Rollback = case transactionForId parentId graph of
263 Right parentTransaction -> Right (newDiscon, graph)
265 newDiscon = Discon.freshTransaction parentId (schemas parentTransaction)
267evalGraphOp stamp' newId (DisconnectedTransaction parentId _ _) graph (MergeTransactions mergeStrategy headNameA headNameB) = runGraphRefRelationalExprM env (mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB))
269 env = freshGraphRefRelationalExprEnv Nothing graph
271evalGraphOp _ _ discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) = case transactionForHead branchName graph of
272 Nothing -> Left (NoSuchHeadNameError branchName)
273 Just _ -> Right (discon, TransactionGraph (M.delete branchName graphHeads) transSet)
275--present a transaction graph as a relation showing the uuids, parentuuids, and flag for the current location of the disconnected transaction
276graphAsRelation :: DisconnectedTransaction -> TransactionGraph -> Either RelationalError Relation
277graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _ transSet) = do
278 tupleMatrix <- mapM tupleGenerator (S.toList transSet)
279 mkRelationFromList attrs tupleMatrix
281 attrs = A.attributesFromList [Attribute "id" TextAtomType,
282 Attribute "stamp" DateTimeAtomType,
283 Attribute "parents" (RelationAtomType parentAttributes),
284 Attribute "current" BoolAtomType,
285 Attribute "head" TextAtomType
287 parentAttributes = A.attributesFromList [Attribute "id" TextAtomType]
288 tupleGenerator transaction = case transactionParentsRelation transaction graph of
290 Right parentTransRel -> Right [TextAtom $ T.pack $ show (transactionId transaction),
291 DateTimeAtom (timestamp transaction),
292 RelationAtom parentTransRel,
293 BoolAtom $ parentId == transactionId transaction,
294 TextAtom $ fromMaybe "" (headNameForTransaction transaction graph)
297transactionParentsRelation :: Transaction -> TransactionGraph -> Either RelationalError Relation
298transactionParentsRelation trans graph =
299 if isRootTransaction trans then
300 mkRelation attrs emptyTupleSet
302 parentTransSet <- parentTransactions trans graph
303 let tuples = map trans2tuple (S.toList parentTransSet)
304 mkRelationFromTuples attrs tuples
306 attrs = A.attributesFromList [Attribute "id" TextAtomType]
307 trans2tuple trans2 = mkRelationTuple attrs $ V.singleton (TextAtom (T.pack (show $ transactionId trans2)))
310--display transaction graph as relation
311evalROGraphOp :: DisconnectedTransaction -> TransactionGraph -> ROTransactionGraphOperator -> Either RelationalError Relation
312evalROGraphOp discon graph ShowGraph = do
313 graphRel <- graphAsRelation discon graph
317-- | Execute the merge strategy against the transactions, returning a new transaction which can be then added to the transaction graph
318createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
319createMergeTransaction stamp' newId (SelectedBranchMergeStrategy selectedBranch) graph t2@(trans1, trans2) = do
320 let selectedTrans = validateHeadName selectedBranch graph t2
321 Transaction newId (TransactionInfo {
322 parents = NE.fromList [transactionId trans1,
323 transactionId trans2],
324 stamp = stamp' }) . schemas <$> selectedTrans
326-- merge functions, relvars, individually
327createMergeTransaction stamp' newId strat@UnionMergeStrategy graph t2 = createUnionMergeTransaction stamp' newId strat graph t2
329-- merge function, relvars, but, on error, just take the component from the preferred branch
330createMergeTransaction stamp' newId strat@(UnionPreferMergeStrategy _) graph t2 = createUnionMergeTransaction stamp' newId strat graph t2
332-- | 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.
333validateHeadName :: HeadName -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
334validateHeadName headName graph (t1, t2) =
335 case transactionForHead headName graph of
336 Nothing -> Left SelectedHeadMismatchMergeError
337 Just trans -> if trans /= t1 && trans /= t2 then
338 Left SelectedHeadMismatchMergeError
342-- Algorithm: start at one transaction and work backwards up the parents. If there is a node we have not yet visited as a child, then walk that up to its head. If that branch contains the goal transaction, then we have completed a valid subgraph traversal.
343subGraphOfFirstCommonAncestor :: TransactionGraph -> TransactionHeads -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError TransactionGraph
344subGraphOfFirstCommonAncestor origGraph resultHeads currentTrans' goalTrans traverseSet = do
345 let currentid = transactionId currentTrans'
346 goalid = transactionId goalTrans
347 if currentTrans' == goalTrans then
348 Right (TransactionGraph resultHeads traverseSet) -- add filter
349 --catch root transaction to improve error?
351 currentTransChildren <- childTransactions currentTrans' origGraph
352 let searchChildren = S.difference (S.insert currentTrans' traverseSet) currentTransChildren
353 searchChild start = pathToTransaction origGraph start goalTrans (S.insert currentTrans' traverseSet)
354 childSearches = map searchChild (S.toList searchChildren)
355 errors = lefts childSearches
356 pathsFound = rights childSearches
357 realErrors = filter (/= FailedToFindTransactionError goalid) errors
358 -- report any non-search-related errors
359 unless (null realErrors) (Left (head realErrors))
360 -- if no paths found, search the parent
361 if null pathsFound then
362 case oneParent currentTrans' of
363 Left RootTransactionTraversalError -> Left (NoCommonTransactionAncestorError currentid goalid)
365 Right currentTransParent ->
366 subGraphOfFirstCommonAncestor origGraph resultHeads currentTransParent goalTrans (S.insert currentTrans' traverseSet)
367 else -- we found a path
368 Right (TransactionGraph resultHeads (S.unions (traverseSet : pathsFound)))
370 oneParent (Transaction _ tinfo _) = transactionForId (NE.head (parents tinfo)) origGraph
372-- | Search from a past graph point to all following heads for a specific transaction. If found, return the transaction path, otherwise a RelationalError.
373pathToTransaction :: TransactionGraph -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError (S.Set Transaction)
374pathToTransaction graph currentTransaction targetTransaction accumTransSet = do
375 let targetId = transactionId targetTransaction
376 if transactionId targetTransaction == transactionId currentTransaction then
379 currentTransChildren <- childTransactions currentTransaction graph
380 if null currentTransChildren then
381 Left (FailedToFindTransactionError targetId)
383 let searches = map (\t -> pathToTransaction graph t targetTransaction (S.insert t accumTransSet)) (S.toList currentTransChildren)
384 let realErrors = filter (/= FailedToFindTransactionError targetId) (lefts searches)
385 paths = rights searches
386 if not (null realErrors) then -- found some real errors
387 Left (head realErrors)
388 else if null paths then -- failed to find transaction in all children
389 Left (FailedToFindTransactionError targetId)
390 else --we have some paths!
391 Right (S.unions paths)
393mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> GraphRefRelationalExprM (DisconnectedTransaction, TransactionGraph)
394mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB) = do
396 let transactionForHeadErr name = case transactionForHead name graph of
397 Nothing -> throwError (NoSuchHeadNameError name)
400 Left e' -> throwError e'
402 transA <- transactionForHeadErr headNameA
403 transB <- transactionForHeadErr headNameB
404 disconParent <- gfTransForId parentId
405 let subHeads = M.filterWithKey (\k _ -> k `elem` [headNameA, headNameB]) (transactionHeadsForGraph graph)
406 subGraph <- runE $ subGraphOfFirstCommonAncestor graph subHeads transA transB S.empty
407 subGraph' <- runE $ filterSubGraph subGraph subHeads
408 case createMergeTransaction stamp' newId mergeStrategy subGraph' (transA, transB) of
409 Left err -> throwError (MergeTransactionError err)
411 case headNameForTransaction disconParent graph of
412 Nothing -> throwError (TransactionIsNotAHeadError parentId)
414 (newTrans, newGraph) <- runE $ addTransactionToGraph headName mergedTrans graph
415 case checkConstraints (concreteDatabaseContext mergedTrans) newId graph of
416 Left err -> throwError err
418 let newGraph' = TransactionGraph (transactionHeadsForGraph newGraph) (transactionsForGraph newGraph)
419 newDiscon = Discon.freshTransaction newId (schemas newTrans)
420 pure (newDiscon, newGraph')
422--TEMPORARY COPY/PASTE
423showTransactionStructureX :: Transaction -> TransactionGraph -> String
424showTransactionStructureX trans graph = headInfo ++ " " ++ show (transactionId trans) ++ " " ++ parentTransactionsInfo
426 headInfo = maybe "" show (headNameForTransaction trans graph)
427 parentTransactionsInfo = if isRootTransaction trans then "root" else case parentTransactions trans graph of
429 Right parentTransSet -> concat $ S.toList $ S.map (show . transactionId) parentTransSet
431showGraphStructureX :: TransactionGraph -> String
432showGraphStructureX graph@(TransactionGraph heads transSet) = headsInfo ++ S.foldr folder "" transSet
434 folder trans acc = acc ++ showTransactionStructureX trans graph ++ "\n"
435 headsInfo = show $ M.map transactionId heads
437-- | After splicing out a subgraph, run it through this function to remove references to transactions which are not in the subgraph.
438filterSubGraph :: TransactionGraph -> TransactionHeads -> Either RelationalError TransactionGraph
439filterSubGraph graph heads = Right $ TransactionGraph newHeads newTransSet
441 validIds = S.map transactionId (transactionsForGraph graph)
442 newTransSet = S.map (filterTransaction validIds) (transactionsForGraph graph)
443 newHeads = M.map (filterTransaction validIds) heads
445--helper function for commonalities in union merge
446createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
447createUnionMergeTransaction stamp' newId strategy graph (t1,t2) = do
448 let contextA = concreteDatabaseContext t1
449 contextB = concreteDatabaseContext t2
451 preference <- case strategy of
452 UnionMergeStrategy -> pure PreferNeither
453 UnionPreferMergeStrategy preferBranch ->
454 case transactionForHead preferBranch graph of
455 Nothing -> Left (PreferredHeadMissingMergeError preferBranch)
456 Just preferredTrans -> pure $ if t1 == preferredTrans then PreferFirst else PreferSecond
457 badStrat -> Left (InvalidMergeStrategyError badStrat)
459 incDeps <- unionMergeMaps preference (inclusionDependencies contextA) (inclusionDependencies contextB)
460 relVars <- unionMergeRelVars preference (relationVariables contextA) (relationVariables contextB)
461 atomFuncs <- unionMergeAtomFunctions preference (atomFunctions contextA) (atomFunctions contextB)
462 notifs <- unionMergeMaps preference (notifications contextA) (notifications contextB)
463 types <- unionMergeTypeConstructorMapping preference (typeConstructorMapping contextA) (typeConstructorMapping contextB)
464 dbcFuncs <- unionMergeDatabaseContextFunctions preference (dbcFunctions contextA) (dbcFunctions contextB)
465 -- TODO: add merge of subschemas
466 let newContext = DatabaseContext {
467 inclusionDependencies = incDeps,
468 relationVariables = relVars,
469 atomFunctions = atomFuncs,
470 dbcFunctions = dbcFuncs,
471 notifications = notifs,
472 typeConstructorMapping = types
474 newSchemas = Schemas newContext (subschemas t1)
475 pure (Transaction newId (TransactionInfo {
476 parents = NE.fromList [transactionId t1,
478 stamp = stamp'}) newSchemas)
480lookupTransaction :: TransactionGraph -> TransactionIdLookup -> Either RelationalError Transaction
481lookupTransaction graph (TransactionIdLookup tid) = transactionForId tid graph
482lookupTransaction graph (TransactionIdHeadNameLookup headName backtracks) = case transactionForHead headName graph of
483 Nothing -> Left (NoSuchHeadNameError headName)
485 traversedId <- traverseGraph graph (transactionId headTrans) backtracks
486 transactionForId traversedId graph
488traverseGraph :: TransactionGraph -> TransactionId -> [TransactionIdHeadBacktrack] -> Either RelationalError TransactionId
489traverseGraph graph = foldM (backtrackGraph graph)
491backtrackGraph :: TransactionGraph -> TransactionId -> TransactionIdHeadBacktrack -> Either RelationalError TransactionId
492-- tilde, step back one parent link- if a choice must be made, choose the "first" link arbitrarily
493backtrackGraph graph currentTid (TransactionIdHeadParentBacktrack steps) = do
494 trans <- transactionForId currentTid graph
495 let parentIds' = S.toList (parentIds trans)
496 if length parentIds' < 1 then
497 Left RootTransactionTraversalError
499 parentTrans <- transactionForId (head parentIds') graph
501 pure (transactionId parentTrans)
503 backtrackGraph graph (transactionId parentTrans) (TransactionIdHeadParentBacktrack (steps - 1))
505backtrackGraph graph currentTid (TransactionIdHeadBranchBacktrack steps) = do
506 trans <- transactionForId currentTid graph
507 let parentIds' = parentIds trans
508 if S.size parentIds' < 1 then
509 Left RootTransactionTraversalError
510 else if S.size parentIds' < steps then
511 Left (ParentCountTraversalError (S.size parentIds') steps)
513 pure (S.elemAt (steps - 1) parentIds')
515backtrackGraph graph currentTid btrack@(TransactionStampHeadBacktrack stamp') = do
516 trans <- transactionForId currentTid graph
517 let parentIds' = parentIds trans
518 if timestamp trans <= stamp' then
520 else if S.null parentIds' then
521 Left RootTransactionTraversalError
523 let arbitraryParent = head (S.toList parentIds') in
524 backtrackGraph graph arbitraryParent btrack
526-- | Create a temporary branch for commit, merge the result to head, delete the temporary branch. This is useful to atomically commit a transaction, avoiding a TransactionIsNotHeadError but trading it for a potential MergeError.
527--this is not a GraphOp because it combines multiple graph operations
528autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
529autoMergeToHead stamp' (tempBranchTransId, tempCommitTransId, mergeTransId) discon mergeToHeadName strat graph = do
530 let tempBranchName = "mergebranch_" <> U.toText tempBranchTransId
531 --create the temp branch
532 (discon', graph') <- evalGraphOp stamp' tempBranchTransId discon graph (Branch tempBranchName)
534 --commit to the new branch- possible future optimization: don't require fsync for this- create a temp commit type
535 (discon'', graph'') <- evalGraphOp stamp' tempCommitTransId discon' graph' Commit
538 (discon''', graph''') <- evalGraphOp stamp' tempBranchTransId discon'' graph'' (JumpToHead mergeToHeadName)
541 (discon'''', graph'''') <- evalGraphOp stamp' mergeTransId discon''' graph''' (MergeTransactions strat tempBranchName mergeToHeadName)
543 --delete the temp branch
544 (discon''''', graph''''') <- evalGraphOp stamp' tempBranchTransId discon'''' graph'''' (DeleteBranch tempBranchName)
546 let rel = runReader (evalRelationalExpr (RelationVariable "s" ())) (mkRelationalExprState $ D.concreteDatabaseContext discon'''')
550 pure (discon''''', graph''''')
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