@@ -7,16 +7,19 @@ import ProjectM36.Relation
77import ProjectM36.TupleSet
88import ProjectM36.Tuple
99import ProjectM36.RelationalExpression
10+import ProjectM36.TransactionGraph.Merge
11+import qualified ProjectM36.DisconnectedTransaction as Discon
12+
1013import qualified Data.Vector as V
1114import qualified ProjectM36.Attribute as A
1215import qualified Data.UUID as U
1316import qualified Data.Set as S
1417import qualified Data.Map as M
1518import Control.Monad
19+import Data.Time.Clock
1620import qualified Data.Text as T
1721import GHC.Generics
1822import Data.Binary
19−import ProjectM36.TransactionGraph.Merge
2023import Data.Either (lefts, rights, isRight)
2124import Data.Monoid
2225import Control.Arrow
@@ -35,13 +38,15 @@ data TransactionIdLookup = TransactionIdLookup TransactionId |
3538
3639-- | Used for git-style head backtracking such as topic~3^2.
3740data TransactionIdHeadBacktrack = TransactionIdHeadParentBacktrack Int | -- ^ git equivalent of ~: walk back n parents, arbitrarily choosing a parent when a choice must be made
38− TransactionIdHeadBranchBacktrack Int -- ^ git equivalent of ^: walk back one parent level to the nth arbitrarily-chosen parent
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
3943 deriving (Show, Eq, Binary, Generic)
4044
4145
4246-- | Operators which manipulate a transaction graph and which transaction the current 'Session' is based upon.
4347data TransactionGraphOperator = JumpToHead HeadName |
4448 JumpToTransaction TransactionId |
49+ WalkBackToTime UTCTime |
4550 Branch HeadName |
4651 DeleteBranch HeadName |
4752 MergeTransactions MergeStrategy HeadName HeadName |
@@ -56,12 +61,12 @@ isCommit _ = False
5661data ROTransactionGraphOperator = ShowGraph
5762 deriving Show
5863
59−bootstrapTransactionGraph :: TransactionId -> DatabaseContext -> TransactionGraph
60−bootstrapTransactionGraph freshId context = TransactionGraph bootstrapHeads bootstrapTransactions
64+bootstrapTransactionGraph :: UTCTime -> TransactionId -> DatabaseContext -> TransactionGraph
65+bootstrapTransactionGraph stamp freshId context = TransactionGraph bootstrapHeads bootstrapTransactions
6166 where
6267 bootstrapHeads = M.singleton "master" freshTransaction
6368 newSchemas = Schemas context M.empty
64− freshTransaction = Transaction freshId (TransactionInfo U.nil S.empty) newSchemas
69+ freshTransaction = Transaction freshId (TransactionInfo U.nil S.empty stamp) newSchemas
6570 bootstrapTransactions = S.singleton freshTransaction
6671
6772emptyTransactionGraph :: TransactionGraph
@@ -98,33 +103,33 @@ transactionsForIds idSet graph = do
98103 return (S.fromList transList)
99104
100105isRootTransaction :: Transaction -> TransactionGraph -> Bool
101−isRootTransaction (Transaction _ (TransactionInfo pId _) _) _ = U.null pId
106+isRootTransaction (Transaction _ (TransactionInfo pId _ _) _) _ = U.null pId
102107isRootTransaction (Transaction _ MergeTransactionInfo{} _) _ = False
103108
104109-- the first transaction has no parent - all other do have parents- merges have two parents
105110parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
106−parentTransactions (Transaction _ (TransactionInfo pId _) _) graph = do
111+parentTransactions (Transaction _ (TransactionInfo pId _ _) _) graph = do
107112 trans <- transactionForId pId graph
108113 return (S.singleton trans)
109114
110−parentTransactions (Transaction _ (MergeTransactionInfo pId1 pId2 _) _ ) graph = transactionsForIds (S.fromList [pId1, pId2]) graph
115+parentTransactions (Transaction _ (MergeTransactionInfo pId1 pId2 _ _) _ ) graph = transactionsForIds (S.fromList [pId1, pId2]) graph
111116
112117
113118childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
114−childTransactions (Transaction _ (TransactionInfo _ children) _) = transactionsForIds children
115−childTransactions (Transaction _ (MergeTransactionInfo _ _ children) _) = transactionsForIds children
119+childTransactions (Transaction _ (TransactionInfo _ children _) _) = transactionsForIds children
120+childTransactions (Transaction _ (MergeTransactionInfo _ _ children _) _) = transactionsForIds children
116121
117122-- create a new commit and add it to the heads
118123-- 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)
119−addBranch :: TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
120−addBranch newId newBranchName branchPointId graph = do
124+addBranch :: UTCTime -> TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
125+addBranch stamp newId newBranchName branchPointId graph = do
121126 parentTrans <- transactionForId branchPointId graph
122− let newTrans = Transaction newId (TransactionInfo branchPointId S.empty) (schemas parentTrans)
127+ let newTrans = Transaction newId (TransactionInfo branchPointId S.empty stamp) (schemas parentTrans)
123128 addTransactionToGraph newBranchName newTrans graph
124129
125130--adds a disconnected transaction to a transaction graph at some head
126−addDisconnectedTransaction :: TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
127−addDisconnectedTransaction newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName (Transaction newId (TransactionInfo parentId S.empty) schemas')
131+addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
132+addDisconnectedTransaction stamp newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName (Transaction newId (TransactionInfo parentId S.empty stamp) schemas')
128133
129134
130135addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
@@ -201,20 +206,30 @@ walkChildTransactions seenTransSet graph trans =
201206
202207-- returns the new "current" transaction, updated graph, and tutorial d result
203208-- the current transaction is not part of the transaction graph until it is committed
204−evalGraphOp :: TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
209+evalGraphOp :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
205210
206−evalGraphOp _ _ graph (JumpToTransaction jumpId) = case transactionForId jumpId graph of
211+evalGraphOp _ _ _ graph (JumpToTransaction jumpId) = case transactionForId jumpId graph of
207212 Left err -> Left err
208213 Right parentTrans -> Right (newTrans, graph)
209214 where
210215 newTrans = DisconnectedTransaction jumpId (schemas parentTrans) False
211216
212217-- switch from one head to another
213−evalGraphOp _ _ graph (JumpToHead headName) =
218+evalGraphOp _ _ _ graph (JumpToHead headName) =
214219 case transactionForHead headName graph of
215220 Just newHeadTransaction -> let disconnectedTrans = DisconnectedTransaction (transactionId newHeadTransaction) (schemas newHeadTransaction) False in
216221 Right (disconnectedTrans, graph)
217222 Nothing -> Left $ NoSuchHeadNameError headName
223+
224+evalGraphOp _ _ discon graph (WalkBackToTime backTime) = do
225+ let startTransId = Discon.parentId discon
226+ jumpDest <- backtrackGraph graph startTransId (TransactionStampHeadBacktrack backTime)
227+ case transactionForId jumpDest graph of
228+ Left err -> Left err
229+ Right trans -> do
230+ let disconnectedTrans = DisconnectedTransaction (transactionId trans) (schemas trans) False
231+ Right (disconnectedTrans, graph)
232+
218233-- add new head pointing to branchPoint
219234-- repoint the disconnected transaction to the new branch commit (with a potentially different disconnected context)
220235-- affects transactiongraph and the disconnectedtransaction is recreated based off the branch
@@ -230,15 +245,15 @@ evalGraphOp newId discon@(DisconnectedTransaction parentId disconContext) graph
230245
231246-- create a new commit and add it to the heads
232247-- 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)
233−evalGraphOp newId (DisconnectedTransaction parentId schemas' _) graph (Branch newBranchName) = do
248+evalGraphOp stamp newId (DisconnectedTransaction parentId schemas' _) graph (Branch newBranchName) = do
234249 let newDiscon = DisconnectedTransaction newId schemas' False
235− case addBranch newId newBranchName parentId graph of
250+ case addBranch stamp newId newBranchName parentId graph of
236251 Left err -> Left err
237252 Right (_, newGraph) -> Right (newDiscon, newGraph)
238253
239254-- add the disconnected transaction to the graph
240255-- affects graph and disconnectedtransaction- the new disconnectedtransaction's parent is the freshly committed transaction
241−evalGraphOp newTransId discon@(DisconnectedTransaction parentId schemas' _) graph Commit = case transactionForId parentId graph of
256+evalGraphOp stamp newTransId discon@(DisconnectedTransaction parentId schemas' _) graph Commit = case transactionForId parentId graph of
242257 Left err -> Left err
243258 Right parentTransaction -> case headNameForTransaction parentTransaction graph of
244259 Nothing -> Left $ TransactionIsNotAHeadError parentId
@@ -247,18 +262,18 @@ evalGraphOp newTransId discon@(DisconnectedTransaction parentId schemas' _) grap
247262 Right (_, updatedGraph) -> Right (newDisconnectedTrans, updatedGraph)
248263 where
249264 newDisconnectedTrans = DisconnectedTransaction newTransId schemas' False
250− maybeUpdatedGraph = addDisconnectedTransaction newTransId headName discon graph
265+ maybeUpdatedGraph = addDisconnectedTransaction stamp newTransId headName discon graph
251266
252267-- refresh the disconnected transaction, return the same graph
253−evalGraphOp _ (DisconnectedTransaction parentId _ _) graph Rollback = case transactionForId parentId graph of
268+evalGraphOp _ _ (DisconnectedTransaction parentId _ _) graph Rollback = case transactionForId parentId graph of
254269 Left err -> Left err
255270 Right parentTransaction -> Right (newDiscon, graph)
256271 where
257272 newDiscon = DisconnectedTransaction parentId (schemas parentTransaction) False
258273
259−evalGraphOp newId (DisconnectedTransaction parentId _ _) graph (MergeTransactions mergeStrategy headNameA headNameB) = mergeTransactions newId parentId mergeStrategy (headNameA, headNameB) graph
274+evalGraphOp stamp newId (DisconnectedTransaction parentId _ _) graph (MergeTransactions mergeStrategy headNameA headNameB) = mergeTransactions stamp newId parentId mergeStrategy (headNameA, headNameB) graph
260275
261−evalGraphOp _ discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) = case transactionForHead branchName graph of
276+evalGraphOp _ _ discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) = case transactionForHead branchName graph of
262277 Nothing -> Left (NoSuchHeadNameError branchName)
263278 Just _ -> Right (discon, TransactionGraph (M.delete branchName graphHeads) transSet)
264279
@@ -269,6 +284,7 @@ graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _
269284 mkRelationFromList attrs tupleMatrix
270285 where
271286 attrs = A.attributesFromList [Attribute "id" TextAtomType,
287+ Attribute "stamp" DateTimeAtomType,
272288 Attribute "parents" (RelationAtomType parentAttributes),
273289 Attribute "current" BoolAtomType,
274290 Attribute "head" TextAtomType
@@ -277,6 +293,7 @@ graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _
277293 tupleGenerator transaction = case transactionParentsRelation transaction graph of
278294 Left err -> Left err
279295 Right parentTransRel -> Right [TextAtom $ T.pack $ show (transactionId transaction),
296+ DateTimeAtom (transactionTimestamp transaction),
280297 RelationAtom parentTransRel,
281298 BoolAtom $ parentId == transactionId transaction,
282299 TextAtom $ fromMaybe "" (headNameForTransaction transaction graph)
@@ -303,16 +320,16 @@ evalROGraphOp discon graph ShowGraph = do
303320-}
304321
305322-- | Execute the merge strategy against the transactions, returning a new transaction which can be then added to the transaction graph
306−createMergeTransaction :: TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
307−createMergeTransaction newId (SelectedBranchMergeStrategy selectedBranch) graph t2@(trans1, trans2) = do
323+createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
324+createMergeTransaction stamp newId (SelectedBranchMergeStrategy selectedBranch) graph t2@(trans1, trans2) = do
308325 selectedTrans <- validateHeadName selectedBranch graph t2
309− pure $ Transaction newId (MergeTransactionInfo (transactionId trans1) (transactionId trans2) S.empty) (schemas selectedTrans)
326+ pure $ Transaction newId (MergeTransactionInfo (transactionId trans1) (transactionId trans2) S.empty stamp) (schemas selectedTrans)
310327
311328-- merge functions, relvars, individually
312−createMergeTransaction newId strat@UnionMergeStrategy graph t2 = createUnionMergeTransaction newId strat graph t2
329+createMergeTransaction stamp newId strat@UnionMergeStrategy graph t2 = createUnionMergeTransaction stamp newId strat graph t2
313330
314331-- merge function, relvars, but, on error, just take the component from the preferred branch
315−createMergeTransaction newId strat@(UnionPreferMergeStrategy _) graph t2 = createUnionMergeTransaction newId strat graph t2
332+createMergeTransaction stamp newId strat@(UnionPreferMergeStrategy _) graph t2 = createUnionMergeTransaction stamp newId strat graph t2
316333
317334-- | 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.
318335validateHeadName :: HeadName -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
@@ -352,8 +369,8 @@ subGraphOfFirstCommonAncestor origGraph resultHeads currentTrans goalTrans trave
352369 else -- we found a path
353370 Right (TransactionGraph resultHeads (S.unions (traverseSet : pathsFound)))
354371 where
355− oneParent (Transaction _ (TransactionInfo parentId _) _) = transactionForId parentId origGraph
356− oneParent (Transaction _ (MergeTransactionInfo parentId _ _) _) = transactionForId parentId origGraph
372+ oneParent (Transaction _ (TransactionInfo parentId _ _) _) = transactionForId parentId origGraph
373+ oneParent (Transaction _ (MergeTransactionInfo parentId _ _ _) _) = transactionForId parentId origGraph
357374
358375-- | Search from a past graph point to all following heads for a specific transaction. If found, return the transaction path, otherwise a RelationalError.
359376pathToTransaction :: TransactionGraph -> Transaction -> Transaction -> S.Set Transaction -> Either RelationalError (S.Set Transaction)
@@ -376,8 +393,8 @@ pathToTransaction graph currentTransaction targetTransaction accumTransSet = do
376393 else --we have some paths!
377394 Right (S.unions paths)
378395
379−mergeTransactions :: TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
380−mergeTransactions newId parentId mergeStrategy (headNameA, headNameB) graph = do
396+mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
397+mergeTransactions stamp newId parentId mergeStrategy (headNameA, headNameB) graph = do
381398 let transactionForHeadErr name = case transactionForHead name graph of
382399 Nothing -> Left (NoSuchHeadNameError name)
383400 Just t -> Right t
@@ -387,7 +404,7 @@ mergeTransactions newId parentId mergeStrategy (headNameA, headNameB) graph = do
387404 let subHeads = M.filterWithKey (\k _ -> elem k [headNameA, headNameB]) (transactionHeadsForGraph graph)
388405 subGraph <- subGraphOfFirstCommonAncestor graph subHeads transA transB S.empty
389406 subGraph' <- filterSubGraph subGraph subHeads
390− case createMergeTransaction newId mergeStrategy subGraph' (transA, transB) of
407+ case createMergeTransaction stamp newId mergeStrategy subGraph' (transA, transB) of
391408 Left err -> Left (MergeTransactionError err)
392409 Right mergedTrans -> case checkConstraints (concreteDatabaseContext mergedTrans) of
393410 Just err -> Left err
@@ -423,8 +440,8 @@ filterSubGraph graph heads = Right $ TransactionGraph newHeads newTransSet
423440 newHeads = M.map (filterTransaction validIds) heads
424441
425442--helper function for commonalities in union merge
426−createUnionMergeTransaction :: TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
427−createUnionMergeTransaction newId strategy graph (t1,t2) = do
443+createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> TransactionGraph -> (Transaction, Transaction) -> Either MergeError Transaction
444+createUnionMergeTransaction stamp newId strategy graph (t1,t2) = do
428445 let contextA = concreteDatabaseContext t1
429446 contextB = concreteDatabaseContext t2
430447
@@ -452,7 +469,7 @@ createUnionMergeTransaction newId strategy graph (t1,t2) = do
452469 typeConstructorMapping = types
453470 }
454471 newSchemas = Schemas newContext (subschemas t1)
455− pure (Transaction newId (MergeTransactionInfo (transactionId t1) (transactionId t2) S.empty) newSchemas)
472+ pure (Transaction newId (MergeTransactionInfo (transactionId t1) (transactionId t2) S.empty stamp) newSchemas)
456473
457474lookupTransaction :: TransactionGraph -> TransactionIdLookup -> Either RelationalError Transaction
458475lookupTransaction graph (TransactionIdLookup tid) = transactionForId tid graph
@@ -488,23 +505,34 @@ backtrackGraph graph currentTid (TransactionIdHeadBranchBacktrack steps) = do
488505 Left (ParentCountTraversalError (S.size parents) steps)
489506 else
490507 pure (S.elemAt (steps - 1) parents)
508+
509+backtrackGraph graph currentTid btrack@(TransactionStampHeadBacktrack stamp) = do
510+ trans <- transactionForId currentTid graph
511+ let parents = transactionParentIds trans
512+ if transactionTimestamp trans < stamp then
513+ pure currentTid
514+ else if S.null parents then
515+ Left RootTransactionTraversalError
516+ else
517+ let arbitraryParent = head (S.toList parents) in
518+ backtrackGraph graph arbitraryParent btrack
491519
492520-- | 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.
493521--this is not a GraphOp because it combines multiple graph operations
494−autoMergeToHead :: (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
495−autoMergeToHead (tempBranchTransId, tempCommitTransId, mergeTransId) discon mergeToHeadName strat graph = do
522+autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
523+autoMergeToHead stamp (tempBranchTransId, tempCommitTransId, mergeTransId) discon mergeToHeadName strat graph = do
496524 let tempBranchName = "mergebranch_" <> U.toText tempBranchTransId
497525 --create the temp branch
498− (discon', graph') <- evalGraphOp tempBranchTransId discon graph (Branch tempBranchName)
526+ (discon', graph') <- evalGraphOp stamp tempBranchTransId discon graph (Branch tempBranchName)
499527
500528 --commit to the new branch- possible future optimization: don't require fsync for this- create a temp commit type
501− (discon'', graph'') <- evalGraphOp tempCommitTransId discon' graph' Commit
529+ (discon'', graph'') <- evalGraphOp stamp tempCommitTransId discon' graph' Commit
502530
503531 --create the merge
504− (discon''', graph''') <- evalGraphOp mergeTransId discon'' graph'' (MergeTransactions strat tempBranchName mergeToHeadName)
532+ (discon''', graph''') <- evalGraphOp stamp mergeTransId discon'' graph'' (MergeTransactions strat tempBranchName mergeToHeadName)
505533
506534 --delete the temp branch
507− (discon'''', graph'''') <- evalGraphOp tempBranchTransId discon''' graph''' (DeleteBranch tempBranchName)
535+ (discon'''', graph'''') <- evalGraphOp stamp tempBranchTransId discon''' graph''' (DeleteBranch tempBranchName)
508536 {-
509537 let rel = runReader (evalRelationalExpr (RelationVariable "s" ())) (mkRelationalExprState $ D.concreteDatabaseContext discon'''')
510538 traceShowM rel