1{-# LANGUAGE DeriveAnyClass, DeriveGeneric, CPP, 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 Control.Monad.Reader hiding (join)
17import qualified Data.Vector as V
18import qualified Data.UUID as U
19import qualified Data.Set as S
20import qualified Data.Map as M
21import qualified Data.List.NonEmpty as NE
23import qualified Data.Text as T
26import Data.Either (lefts, rights, isRight)
27#if __GLASGOW_HASKELL__ < 804
34-- | Record a lookup for a specific transaction in the graph.
35data TransactionIdLookup = TransactionIdLookup TransactionId |
36 TransactionIdHeadNameLookup HeadName [TransactionIdHeadBacktrack]
37 deriving (Show, Eq, Binary, Generic)
39-- | Used for git-style head backtracking such as topic~3^2.
40data TransactionIdHeadBacktrack = TransactionIdHeadParentBacktrack Int | -- ^ git equivalent of ~: walk back n parents, arbitrarily choosing a parent when a choice must be made
41 TransactionIdHeadBranchBacktrack Int | -- ^ git equivalent of ^: walk back one parent level to the nth arbitrarily-chosen parent
42 TransactionStampHeadBacktrack UTCTime -- ^ git equivalent of 'git-rev-list -n 1 --before X' find the first transaction which was created before the timestamp
43 deriving (Show, Eq, Binary, Generic)
46-- | Operators which manipulate a transaction graph and which transaction the current 'Session' is based upon.
47data TransactionGraphOperator = JumpToHead HeadName |
48 JumpToTransaction TransactionId |
49 WalkBackToTime UTCTime |
51 DeleteBranch HeadName |
52 MergeTransactions MergeStrategy HeadName HeadName |
55 deriving (Eq, Show, Binary, Generic)
57isCommit :: TransactionGraphOperator -> Bool
61data ROTransactionGraphOperator = ShowGraph
64bootstrapTransactionGraph :: UTCTime -> TransactionId -> DatabaseContext -> TransactionGraph
65bootstrapTransactionGraph stamp' freshId context = TransactionGraph bootstrapHeads bootstrapTransactions
67 bootstrapHeads = M.singleton "master" freshTransaction
68 newSchemas = Schemas context M.empty
69 freshTransaction = fresh freshId stamp' newSchemas
70 bootstrapTransactions = S.singleton freshTransaction
72-- | Create a transaction graph from a context.
73freshTransactionGraph :: DatabaseContext -> IO (TransactionGraph, TransactionId)
74freshTransactionGraph ctx = do
77 pure (bootstrapTransactionGraph now freshId ctx, freshId)
80emptyTransactionGraph :: TransactionGraph
81emptyTransactionGraph = TransactionGraph M.empty S.empty
83transactionForHead :: HeadName -> TransactionGraph -> Maybe Transaction
84transactionForHead headName graph = M.lookup headName (transactionHeadsForGraph graph)
86headList :: TransactionGraph -> [(HeadName, TransactionId)]
87headList graph = map (second transactionId) (M.assocs (transactionHeadsForGraph graph))
89headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
90headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchingTrans then
93 Just $ (head . M.keys) matchingTrans
95 matchingTrans = M.filter (transaction ==) heads
97transactionsForIds :: S.Set TransactionId -> TransactionGraph -> Either RelationalError (S.Set Transaction)
98transactionsForIds idSet graph =
99 S.fromList <$> forM (S.toList idSet) (`transactionForId` graph)
101-- | A root transaction terminates a graph and has no parents.
102isRootTransaction :: Transaction -> Bool
103isRootTransaction trans = parentIds trans == S.singleton U.nil
105rootTransactions :: TransactionGraph -> S.Set Transaction
106rootTransactions graph = S.filter isRootTransaction (transactionsForGraph graph)
108-- the first transaction has no parent - all other do have parents- merges have two parents
109parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
110parentTransactions trans = transactionsForIds (parentIds trans)
112childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
113childTransactions trans graph = transactionsForIds childIds graph
115 childIds = S.map transactionId (S.filter filt (transactionsForGraph graph))
116 filt trans' = S.member (transactionId trans) (parentIds trans')
118-- create a new commit and add it to the heads
119-- 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)
120addBranch :: UTCTime -> TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
121addBranch stamp' newId newBranchName branchPointId graph = do
122 parentTrans <- transactionForId branchPointId graph
123 let newTrans = Transaction newId (TI.singleParent branchPointId stamp') (schemas parentTrans)
124 addTransactionToGraph newBranchName newTrans graph
126--adds a disconnected transaction to a transaction graph at some head
127addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
128addDisconnectedTransaction stamp' newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName newTrans
130 newTrans = Transaction newId (TI.singleParent parentId stamp') schemas'
132addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
133addTransactionToGraph headName newTrans graph = do
134 let parentIds' = parentIds newTrans
135 newId = transactionId newTrans
136 validateIds ids = mapM (`transactionForId` graph) (S.toList ids)
137 childTs <- childTransactions newTrans graph
138 --validate that the parent transactions are in the graph
139 _ <- validateIds parentIds'
140 when (S.size parentIds' < 1) (Left $ NewTransactionMissingParentError newId)
141 --if the headName already exists, ensure that it refers to a parent
142 case transactionForHead headName graph of
143 Nothing -> pure () -- any headName is OK
144 Just trans -> when (S.notMember (transactionId trans) parentIds') (Left (HeadNameSwitchingHeadProhibitedError headName))
145 --validate that the transaction has no children
146 unless (S.null childTs) (Left $ NewTransactionMayNotHaveChildrenError newId)
147 --validate that the trasaction's id is unique
148 when (isRight (transactionForId newId graph)) (Left (TransactionIdInUseError newId))
149 --replace all references to UncommittedTransactionMarker to new transaction id
150 let newTrans' = newTransUncommittedReplace newTrans
151 updatedTransSet = S.insert newTrans' (transactionsForGraph graph)
152 updatedHeads = M.insert headName newTrans' (transactionHeadsForGraph graph)
153 pure (newTrans', TransactionGraph updatedHeads updatedTransSet)
155--replace all occurrences of the uncommitted context marker
156newTransUncommittedReplace :: Transaction -> Transaction
157newTransUncommittedReplace trans@(Transaction tid tinfo (Schemas ctx sschemas)) =
158 Transaction tid tinfo (Schemas fixedContext sschemas)
160 uncommittedReplace UncommittedContextMarker = TransactionMarker tid
161 uncommittedReplace marker = marker
162 relvars = relationVariables (concreteDatabaseContext trans)
163 fixedRelvars = M.map (fmap uncommittedReplace) relvars
164 fixedContext = ctx { relationVariables = fixedRelvars }
168validateGraph :: TransactionGraph -> Maybe [RelationalError]
169validateGraph graph@(TransactionGraph _ transSet) = do
170 --check that all transaction ids are unique in the graph
172 --uuids = map transactionId transSet
173 --check that all heads appear in the transSet
174 --check that all forward and backward links are in place
175 mapM_ (walkParentTransactions S.empty graph) (S.toList transSet)
176 mapM (walkChildTransactions S.empty graph) (S.toList transSet)
178--verify that all parent links exist and that all children exist
179--maybe verify that all parents end at transaction id nil and all children end at leaves
180walkParentTransactions :: S.Set TransactionId -> TransactionGraph -> Transaction -> Maybe RelationalError
181walkParentTransactions seenTransSet graph trans =
182 let transId = transactionId trans in
183 if transId == U.nil then
185 else if S.member transId seenTransSet then
186 Just $ TransactionGraphCycleError transId
188 let parentTransSetOrError = parentTransactions trans graph in
189 case parentTransSetOrError of
191 Right parentTransSet -> do
192 walk <- mapM (walkParentTransactions (S.insert transId seenTransSet) graph) (S.toList parentTransSet)
197--refactor: needless duplication in these two functions
198walkChildTransactions :: S.Set TransactionId -> TransactionGraph -> Transaction -> Maybe RelationalError
199walkChildTransactions seenTransSet graph trans =
200 let transId = transactionId trans in
201 if childTransactions trans graph == Right S.empty then
203 else if S.member transId seenTransSet then
204 Just $ TransactionGraphCycleError transId
206 let childTransSetOrError = childTransactions trans graph in
207 case childTransSetOrError of
209 Right childTransSet -> do
210 walk <- mapM (walkChildTransactions (S.insert transId seenTransSet) graph) (S.toList childTransSet)
215-- returns the new "current" transaction, updated graph, and tutorial d result
216-- the current transaction is not part of the transaction graph until it is committed
217evalGraphOp :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
219evalGraphOp _ _ _ graph (JumpToTransaction jumpId) = case transactionForId jumpId graph of
221 Right parentTrans -> Right (newTrans, graph)
223 newTrans = DisconnectedTransaction jumpId (schemas parentTrans) False
225-- switch from one head to another
226evalGraphOp _ _ _ graph (JumpToHead headName) =
227 case transactionForHead headName graph of
228 Just newHeadTransaction -> let disconnectedTrans = DisconnectedTransaction (transactionId newHeadTransaction) (schemas newHeadTransaction) False in
229 Right (disconnectedTrans, graph)
230 Nothing -> Left $ NoSuchHeadNameError headName
232evalGraphOp _ _ discon graph (WalkBackToTime backTime) = do
233 let startTransId = Discon.parentId discon
234 jumpDest <- backtrackGraph graph startTransId (TransactionStampHeadBacktrack backTime)
235 case transactionForId jumpDest graph of
238 let disconnectedTrans = Discon.freshTransaction (transactionId trans) (schemas trans)
239 Right (disconnectedTrans, graph)
241-- add new head pointing to branchPoint
242-- repoint the disconnected transaction to the new branch commit (with a potentially different disconnected context)
243-- affects transactiongraph and the disconnectedtransaction is recreated based off the branch
245evalGraphOp newId discon@(DisconnectedTransaction parentId disconContext) graph (Branch newBranchName) = case transactionForId parentId graph of
246 Nothing -> (discon, graph, DisplayErrorResult "Failed to find parent transaction.")
247 Just parentTrans -> case addBranch newBranchName parentTrans graph of
248 Nothing -> (discon, graph, DisplayErrorResult "Failed to add branch.")
249 Just newGraph -> (newDiscon, newGraph, DisplayResult "Branched.")
251 newDiscon = DisconnectedTransaction (transactionId parentTrans) disconContext
254-- create a new commit and add it to the heads
255-- 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)
256evalGraphOp stamp' newId (DisconnectedTransaction parentId schemas' _) graph (Branch newBranchName) = do
257 let newDiscon = Discon.freshTransaction newId schemas'
258 case addBranch stamp' newId newBranchName parentId graph of
260 Right (_, newGraph) -> Right (newDiscon, newGraph)
262-- add the disconnected transaction to the graph
263-- affects graph and disconnectedtransaction- the new disconnectedtransaction's parent is the freshly committed transaction
264evalGraphOp stamp' newTransId discon@(DisconnectedTransaction parentId schemas' _) graph Commit = case transactionForId parentId graph of
266 Right parentTransaction -> case headNameForTransaction parentTransaction graph of
267 Nothing -> Left $ TransactionIsNotAHeadError parentId
268 Just headName -> case maybeUpdatedGraph of
270 Right (_, updatedGraph) -> Right (newDisconnectedTrans, updatedGraph)
272 newDisconnectedTrans = Discon.freshTransaction newTransId schemas'
273 maybeUpdatedGraph = addDisconnectedTransaction stamp' newTransId headName discon graph
275-- refresh the disconnected transaction, return the same graph
276evalGraphOp _ _ (DisconnectedTransaction parentId _ _) graph Rollback = case transactionForId parentId graph of
278 Right parentTransaction -> Right (newDiscon, graph)
280 newDiscon = Discon.freshTransaction parentId (schemas parentTransaction)
282evalGraphOp stamp' newId (DisconnectedTransaction parentId _ _) graph (MergeTransactions mergeStrategy headNameA headNameB) =
283 runGraphRefRelationalExprM env $ mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB)
285 env = freshGraphRefRelationalExprEnv Nothing graph
287evalGraphOp _ _ discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) = case transactionForHead branchName graph of
288 Nothing -> Left (NoSuchHeadNameError branchName)
289 Just _ -> Right (discon, TransactionGraph (M.delete branchName graphHeads) transSet)
291--present a transaction graph as a relation showing the uuids, parentuuids, and flag for the current location of the disconnected transaction
292graphAsRelation :: DisconnectedTransaction -> TransactionGraph -> Either RelationalError Relation
293graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _ transSet) = do
294 tupleMatrix <- mapM tupleGenerator (S.toList transSet)
295 mkRelationFromList attrs tupleMatrix
297 attrs = A.attributesFromList [Attribute "id" TextAtomType,
298 Attribute "stamp" DateTimeAtomType,
299 Attribute "parents" (RelationAtomType parentAttributes),
300 Attribute "current" BoolAtomType,
301 Attribute "head" TextAtomType
303 parentAttributes = A.attributesFromList [Attribute "id" TextAtomType]
304 tupleGenerator transaction = case transactionParentsRelation transaction graph of
306 Right parentTransRel -> Right [TextAtom $ T.pack $ show (transactionId transaction),
307 DateTimeAtom (timestamp transaction),
308 RelationAtom parentTransRel,
309 BoolAtom $ parentId == transactionId transaction,
310 TextAtom $ fromMaybe "" (headNameForTransaction transaction graph)
313transactionParentsRelation :: Transaction -> TransactionGraph -> Either RelationalError Relation
314transactionParentsRelation trans graph =
315 if isRootTransaction trans then
316 mkRelation attrs emptyTupleSet
318 parentTransSet <- parentTransactions trans graph
319 let tuples = map trans2tuple (S.toList parentTransSet)
320 mkRelationFromTuples attrs tuples
322 attrs = A.attributesFromList [Attribute "id" TextAtomType]
323 trans2tuple trans2 = mkRelationTuple attrs $ V.singleton (TextAtom (T.pack (show $ transactionId trans2)))
326--display transaction graph as relation
327evalROGraphOp :: DisconnectedTransaction -> TransactionGraph -> ROTransactionGraphOperator -> Either RelationalError Relation
328evalROGraphOp discon graph ShowGraph = do
329 graphRel <- graphAsRelation discon graph
333-- | Execute the merge strategy against the transactions, returning a new transaction which can be then added to the transaction graph
334createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction
335createMergeTransaction stamp' newId (SelectedBranchMergeStrategy selectedBranch) t2@(trans1, trans2) = do
337 selectedTrans <- validateHeadName selectedBranch graph t2
338 pure $ Transaction newId (TransactionInfo {
339 parents = NE.fromList [transactionId trans1,
340 transactionId trans2],
341 stamp = stamp' }) (schemas selectedTrans)
343-- merge functions, relvars, individually
344createMergeTransaction stamp' newId strat@UnionMergeStrategy t2 =
345 createUnionMergeTransaction stamp' newId strat t2
347-- merge function, relvars, but, on error, just take the component from the preferred branch
348createMergeTransaction stamp' newId strat@(UnionPreferMergeStrategy _) t2 =
349 createUnionMergeTransaction stamp' newId strat t2
351-- | 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.
352validateHeadName :: HeadName -> TransactionGraph -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction
353validateHeadName headName graph (t1, t2) =
354 case transactionForHead headName graph of
355 Nothing -> throwError (MergeTransactionError SelectedHeadMismatchMergeError)
356 Just trans -> if trans /= t1 && trans /= t2 then
357 throwError (MergeTransactionError SelectedHeadMismatchMergeError)
361-- 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.
362subGraphOfFirstCommonAncestor :: TransactionGraph -> TransactionHeads -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError TransactionGraph
363subGraphOfFirstCommonAncestor origGraph resultHeads currentTrans' goalTrans traverseSet = do
364 let currentid = transactionId currentTrans'
365 goalid = transactionId goalTrans
366 if currentTrans' == goalTrans then
367 Right (TransactionGraph resultHeads traverseSet) -- add filter
368 --catch root transaction to improve error?
370 currentTransChildren <- childTransactions currentTrans' origGraph
371 let searchChildren = S.difference (S.insert currentTrans' traverseSet) currentTransChildren
372 searchChild start = pathToTransaction origGraph start goalTrans (S.insert currentTrans' traverseSet)
373 childSearches = map searchChild (S.toList searchChildren)
374 errors = lefts childSearches
375 pathsFound = rights childSearches
376 realErrors = filter (/= FailedToFindTransactionError goalid) errors
377 -- report any non-search-related errors
378 unless (null realErrors) (Left (head realErrors))
379 -- if no paths found, search the parent
380 if null pathsFound then
381 case oneParent currentTrans' of
382 Left RootTransactionTraversalError -> Left (NoCommonTransactionAncestorError currentid goalid)
384 Right currentTransParent ->
385 subGraphOfFirstCommonAncestor origGraph resultHeads currentTransParent goalTrans (S.insert currentTrans' traverseSet)
386 else -- we found a path
387 Right (TransactionGraph resultHeads (S.unions (traverseSet : pathsFound)))
389 oneParent (Transaction _ tinfo _) = transactionForId (NE.head (parents tinfo)) origGraph
391-- | Search from a past graph point to all following heads for a specific transaction. If found, return the transaction path, otherwise a RelationalError.
392pathToTransaction :: TransactionGraph -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError (S.Set Transaction)
393pathToTransaction graph currentTransaction targetTransaction accumTransSet = do
394 let targetId = transactionId targetTransaction
395 if transactionId targetTransaction == transactionId currentTransaction then
398 currentTransChildren <- childTransactions currentTransaction graph
399 if null currentTransChildren then
400 Left (FailedToFindTransactionError targetId)
402 let searches = map (\t -> pathToTransaction graph t targetTransaction (S.insert t accumTransSet)) (S.toList currentTransChildren)
403 let realErrors = filter (/= FailedToFindTransactionError targetId) (lefts searches)
404 paths = rights searches
405 if not (null realErrors) then -- found some real errors
406 Left (head realErrors)
407 else if null paths then -- failed to find transaction in all children
408 Left (FailedToFindTransactionError targetId)
409 else --we have some paths!
410 Right (S.unions paths)
412mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> GraphRefRelationalExprM (DisconnectedTransaction, TransactionGraph)
413mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB) = do
415 let transactionForHeadErr name = case transactionForHead name graph of
416 Nothing -> throwError (NoSuchHeadNameError name)
419 Left e' -> throwError e'
421 transA <- transactionForHeadErr headNameA
422 transB <- transactionForHeadErr headNameB
423 disconParent <- gfTransForId parentId
424 let subHeads = M.filterWithKey (\k _ -> k `elem` [headNameA, headNameB]) (transactionHeadsForGraph graph)
425 subGraph <- runE $ subGraphOfFirstCommonAncestor graph subHeads transA transB S.empty
426 subGraph' <- runE $ filterSubGraph subGraph subHeads
427 mergedTrans <- local (const (freshGraphRefRelationalExprEnv Nothing subGraph')) $ createMergeTransaction stamp' newId mergeStrategy (transA, transB)
428 case headNameForTransaction disconParent graph of
429 Nothing -> throwError (TransactionIsNotAHeadError parentId)
431 (newTrans, newGraph) <- runE $ addTransactionToGraph headName mergedTrans graph
432 case checkConstraints (concreteDatabaseContext mergedTrans) newId graph of
433 Left err -> throwError err
435 let newGraph' = TransactionGraph (transactionHeadsForGraph newGraph) (transactionsForGraph newGraph)
436 newDiscon = Discon.freshTransaction newId (schemas newTrans)
437 pure (newDiscon, newGraph')
439--TEMPORARY COPY/PASTE
440showTransactionStructureX :: Transaction -> TransactionGraph -> String
441showTransactionStructureX trans graph = headInfo ++ " " ++ show (transactionId trans) ++ " " ++ parentTransactionsInfo
443 headInfo = maybe "" show (headNameForTransaction trans graph)
444 parentTransactionsInfo = if isRootTransaction trans then "root" else case parentTransactions trans graph of
446 Right parentTransSet -> concat $ S.toList $ S.map (show . transactionId) parentTransSet
448showGraphStructureX :: TransactionGraph -> String
449showGraphStructureX graph@(TransactionGraph heads transSet) = headsInfo ++ S.foldr folder "" transSet
451 folder trans acc = acc ++ showTransactionStructureX trans graph ++ "\n"
452 headsInfo = show $ M.map transactionId heads
454-- | After splicing out a subgraph, run it through this function to remove references to transactions which are not in the subgraph.
455filterSubGraph :: TransactionGraph -> TransactionHeads -> Either RelationalError TransactionGraph
456filterSubGraph graph heads = Right $ TransactionGraph newHeads newTransSet
458 validIds = S.map transactionId (transactionsForGraph graph)
459 newTransSet = S.map (filterTransaction validIds) (transactionsForGraph graph)
460 newHeads = M.map (filterTransaction validIds) heads
462--helper function for commonalities in union merge
463createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction
464createUnionMergeTransaction stamp' newId strategy (t1,t2) = do
465 let contextA = concreteDatabaseContext t1
466 contextB = concreteDatabaseContext t2
467 liftMergeE x = case x of
468 Left e -> throwError (MergeTransactionError e)
472 preference <- case strategy of
473 UnionMergeStrategy -> pure PreferNeither
474 UnionPreferMergeStrategy preferBranch ->
475 case transactionForHead preferBranch graph of
476 Nothing -> throwError (MergeTransactionError (PreferredHeadMissingMergeError preferBranch))
477 Just preferredTrans -> pure $ if t1 == preferredTrans then PreferFirst else PreferSecond
478 badStrat -> throwError (MergeTransactionError (InvalidMergeStrategyError badStrat))
480 incDeps <- liftMergeE $ unionMergeMaps preference (inclusionDependencies contextA) (inclusionDependencies contextB)
481 relVars <- unionMergeRelVars preference (relationVariables contextA) (relationVariables contextB)
482 atomFuncs <- liftMergeE $ unionMergeAtomFunctions preference (atomFunctions contextA) (atomFunctions contextB)
483 notifs <- liftMergeE $ unionMergeMaps preference (notifications contextA) (notifications contextB)
484 types <- liftMergeE $ unionMergeTypeConstructorMapping preference (typeConstructorMapping contextA) (typeConstructorMapping contextB)
485 dbcFuncs <- liftMergeE $ unionMergeDatabaseContextFunctions preference (dbcFunctions contextA) (dbcFunctions contextB)
486 -- TODO: add merge of subschemas
487 let newContext = DatabaseContext {
488 inclusionDependencies = incDeps,
489 relationVariables = relVars,
490 atomFunctions = atomFuncs,
491 dbcFunctions = dbcFuncs,
492 notifications = notifs,
493 typeConstructorMapping = types
495 newSchemas = Schemas newContext (subschemas t1)
496 pure (Transaction newId (TransactionInfo {
497 parents = NE.fromList [transactionId t1,
499 stamp = stamp'}) newSchemas)
501lookupTransaction :: TransactionGraph -> TransactionIdLookup -> Either RelationalError Transaction
502lookupTransaction graph (TransactionIdLookup tid) = transactionForId tid graph
503lookupTransaction graph (TransactionIdHeadNameLookup headName backtracks) = case transactionForHead headName graph of
504 Nothing -> Left (NoSuchHeadNameError headName)
506 traversedId <- traverseGraph graph (transactionId headTrans) backtracks
507 transactionForId traversedId graph
509traverseGraph :: TransactionGraph -> TransactionId -> [TransactionIdHeadBacktrack] -> Either RelationalError TransactionId
510traverseGraph graph = foldM (backtrackGraph graph)
512backtrackGraph :: TransactionGraph -> TransactionId -> TransactionIdHeadBacktrack -> Either RelationalError TransactionId
513-- tilde, step back one parent link- if a choice must be made, choose the "first" link arbitrarily
514backtrackGraph graph currentTid (TransactionIdHeadParentBacktrack steps) = do
515 trans <- transactionForId currentTid graph
517 let parentIds' = S.toAscList (parentIds trans)
519 [] -> Left RootTransactionTraversalError
520 firstParentId:_ -> do
521 parentTrans <- transactionForId firstParentId graph
523 pure (transactionId parentTrans)
525 backtrackGraph graph (transactionId parentTrans) (TransactionIdHeadParentBacktrack (steps - 1))
527backtrackGraph graph currentTid (TransactionIdHeadBranchBacktrack steps) = do
528 trans <- transactionForId currentTid graph
529 let parentIds' = parentIds trans
530 if S.size parentIds' < 1 then
531 Left RootTransactionTraversalError
532 else if S.size parentIds' < steps then
533 Left (ParentCountTraversalError (S.size parentIds') steps)
535 pure (S.elemAt (steps - 1) parentIds')
537backtrackGraph graph currentTid btrack@(TransactionStampHeadBacktrack stamp') = do
538 trans <- transactionForId currentTid graph
539 let parentIds' = parentIds trans
540 if timestamp trans <= stamp' then
542 else if S.null parentIds' then
543 Left RootTransactionTraversalError
545 let arbitraryParent = head (S.toList parentIds') in
546 backtrackGraph graph arbitraryParent btrack
548-- | 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.
549--this is not a GraphOp because it combines multiple graph operations
550autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
551autoMergeToHead stamp' (tempBranchTransId, tempCommitTransId, mergeTransId) discon mergeToHeadName strat graph = do
552 let tempBranchName = "mergebranch_" <> U.toText tempBranchTransId
553 --create the temp branch
554 (discon', graph') <- evalGraphOp stamp' tempBranchTransId discon graph (Branch tempBranchName)
556 --commit to the new branch- possible future optimization: don't require fsync for this- create a temp commit type
557 (discon'', graph'') <- evalGraphOp stamp' tempCommitTransId discon' graph' Commit
560 (discon''', graph''') <- evalGraphOp stamp' tempBranchTransId discon'' graph'' (JumpToHead mergeToHeadName)
563 (discon'''', graph'''') <- evalGraphOp stamp' mergeTransId discon''' graph''' (MergeTransactions strat tempBranchName mergeToHeadName)
565 --delete the temp branch
566 (discon''''', graph''''') <- evalGraphOp stamp' tempBranchTransId discon'''' graph'''' (DeleteBranch tempBranchName)
568 let rel = runReader (evalRelationalExpr (RelationVariable "s" ())) (mkRelationalExprState $ D.concreteDatabaseContext discon'''')
572 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