on master help sign in
WIP: incremental reading and writing of the graph
commitfde4091317c4a2c0caa9919161921a79784cce65 authorAgentM <agentm@themactionfaction.com> dateTue 7 Jul 2015, 23:50 (11 years ago) parentddb2516
13 files changed+416−189
ProjectM36/Base.hs+4−1 ProjectM36/DatabaseContext.hs+9 ProjectM36/Error.hs+6−1 ProjectM36/Transaction.hs+2−129 ProjectM36/Transaction/Persist.hs+124 ProjectM36/TransactionGraph.hs+129 ProjectM36/TransactionGraph/Persist.hs+109−44 ProjectM36/Transaction/Show.hs → ProjectM36/TransactionGraph/Show.hs+2−1 TutorialD/Interpreter.hs+6−4 TutorialD/Interpreter/TransactionGraphOperator.hs+2−1 TutorialD/tutd.hs+16−4 project-m36.cabal+6−4 test/TutorialD/Interpreter.hs+1
ProjectM36/Base.hs+4−1
@@ -191,7 +191,7 @@ data TransactionInfo = TransactionInfo UUID (S.Set UUID) | -- 1 parent + n child
191191 MergeTransactionInfo UUID UUID (S.Set UUID) -- 2 parents, n children
192192 deriving(Show, Generic)
193193
194−--instance Binary TransactionInfo
194+instance Binary TransactionInfo
195195
196196data Transaction = Transaction UUID TransactionInfo DatabaseContext -- self uuid
197197 deriving (Show, Generic)
@@ -208,6 +208,9 @@ transactionUUID (Transaction uuid _ _) = uuid
208208
209209transactionContext :: Transaction -> DatabaseContext
210210transactionContext (Transaction _ _ context) = context
211+
212+transactionInfo :: Transaction -> TransactionInfo
213+transactionInfo (Transaction _ info _) = info
211214
212215instance Eq Transaction where
213216 (Transaction uuidA _ _) == (Transaction uuidB _ _) = uuidA == uuidB
ProjectM36/DatabaseContext.hsnew file+9
@@ -0,0 +1,9 @@
1+module ProjectM36.DatabaseContext where
2+import ProjectM36.Base
3+import qualified Data.Map as M
4+import qualified Data.HashSet as HS
5+
6+empty :: DatabaseContext
7+empty = DatabaseContext { inclusionDependencies = M.empty,
8+ relationVariables = M.empty,
9+ atomFunctions = HS.empty }
ProjectM36/Error.hs+6−1
@@ -34,5 +34,10 @@ data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)
3434 | AtomFunctionTypeError AtomFunctionName Int AtomType AtomType --arg number
3535 deriving (Show,Eq,Generic)
3636
37+instance NFData RelationalError where rnf = genericRnf
38+
39+data PersistenceError = InvalidDirectoryError FilePath |
40+ MissingTransactionError U.UUID
41+ deriving (Show, Eq)
3742
38−instance NFData RelationalError where rnf = genericRnf
43+
ProjectM36/Transaction.hs+2−129
@@ -2,140 +2,13 @@
22module ProjectM36.Transaction where
33import qualified Data.UUID as U
44import ProjectM36.Base
5−import ProjectM36.Error
65import qualified Data.Set as S
7−import qualified Data.Map as M
8−import Control.Applicative ((<$>))
9−import Control.Monad
106
11−bootstrapTransactionGraph :: U.UUID -> DatabaseContext -> TransactionGraph
12−bootstrapTransactionGraph freshUUID context = TransactionGraph bootstrapHeads bootstrapTransactions
13− where
14− bootstrapHeads = M.singleton "master" freshTransaction
15− freshTransaction = Transaction freshUUID (TransactionInfo U.nil S.empty) context
16− bootstrapTransactions = S.singleton freshTransaction
17−
18−transactionForHead :: HeadName -> TransactionGraph -> Maybe Transaction
19−transactionForHead headName (TransactionGraph heads _) = M.lookup headName heads
20−
21−headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
22−headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchingTrans then
23− Nothing
24− else
25− Just $ (head . M.keys) matchingTrans
26− where
27− matchingTrans = M.filter (transaction ==) heads
28−
29−transactionForUUID :: U.UUID -> TransactionGraph -> Either RelationalError Transaction
30−transactionForUUID uuid graph = if S.null matchingTrans then
31− Left $ NoSuchTransactionError uuid
32− else
33− Right $ head (S.toList matchingTrans)
34− where
35− matchingTrans = S.filter (\(Transaction uuidMatch _ _) -> uuidMatch == uuid) (transactionsForGraph graph)
36−
37−transactionsForUUIDs :: S.Set U.UUID -> TransactionGraph -> Either RelationalError (S.Set Transaction)
38−transactionsForUUIDs uuidSet graph = do
39− transList <- forM (S.toList uuidSet) ((flip transactionForUUID) graph)
40− return (S.fromList transList)
41−
42−isRootTransaction :: Transaction -> TransactionGraph -> Bool
43−isRootTransaction (Transaction _ (TransactionInfo pUUID _) _) _ = U.null pUUID
44−isRootTransaction (Transaction _ (MergeTransactionInfo _ _ _) _) _ = False
45−
46−-- the first transaction has no parent - all other do have parents- merges have two parents
47−parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
48−parentTransactions (Transaction _ (TransactionInfo pUUID _) _) graph = do
49− trans <- transactionForUUID pUUID graph
50− return (S.singleton trans)
51−
52−parentTransactions (Transaction _ (MergeTransactionInfo pUUID1 pUUID2 _) _ ) graph = transactionsForUUIDs (S.fromList [pUUID1, pUUID2]) graph
53−
54−
55−childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
56−childTransactions (Transaction _ (TransactionInfo _ children) _) = transactionsForUUIDs children
57−childTransactions (Transaction _ (MergeTransactionInfo _ _ children) _) = transactionsForUUIDs children
58−
59−
607--create a disconnected transaction with a parent
618newDisconnectedTransaction :: U.UUID -> DatabaseContext -> DisconnectedTransaction
629newDisconnectedTransaction parentUUID context = DisconnectedTransaction parentUUID context
6310
64−{-
65−addBranch :: HeadName -> Transaction -> TransactionGraph -> Maybe TransactionGraph
66−addBranch newBranchName branchPoint graph@(TransactionGraph heads transSet) = if not (S.member branchPoint transSet) then Nothing else Just $ TransactionGraph newHeads transSet
67− where
68− newHeads = M.insert newBranchName branchPoint heads
69−-}
70−
71−-- create a new commit and add it to the heads
72−-- 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)
73−addBranch :: U.UUID -> HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
74−addBranch newUUID newBranchName branchPoint graph = addTransactionToGraph newBranchName branchPoint newUUID (transactionContext branchPoint) graph
75−
76−--adds a disconnected transaction to a transaction graph at some head
77−addDisconnectedTransaction :: U.UUID -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
78−addDisconnectedTransaction newUUID headName (DisconnectedTransaction parentUUID context) graph = do
79− parentTrans <- transactionForUUID parentUUID graph
80− addTransactionToGraph headName parentTrans newUUID context graph
81−
82−-- create a new transaction on "newHeadName" with the branchPointTrans
83−addTransactionToGraph :: HeadName -> Transaction -> U.UUID -> DatabaseContext -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
84−addTransactionToGraph newHeadName branchPointTrans newUUID newContext (TransactionGraph heads transSet) = do
85− let freshTransaction = Transaction newUUID (TransactionInfo (transactionUUID branchPointTrans) S.empty) newContext
86− -- there are two parents for MergeTransactions! not implemented
87− --update parentTransaction to add child
88− let parentTransaction = branchPointTrans
89− let updatedTransSet = (S.insert freshTransaction <$> S.insert parentTransaction <$> S.delete parentTransaction) transSet
90− let updatedGraph = TransactionGraph (M.insert newHeadName freshTransaction heads) updatedTransSet
91− return (freshTransaction, updatedGraph)
92−
93−validateGraph :: TransactionGraph -> Maybe [RelationalError]
94−validateGraph graph@(TransactionGraph _ transSet) = do
95− --check that all UUIDs are unique in the graph
96− --uuids = map transactionUUID transSet
97− --check that all heads appear in the transSet
98− --check that all forward and backward links are in place
99− _ <- mapM (walkParentTransactions S.empty graph) (S.toList transSet)
100− mapM (walkChildTransactions S.empty graph) (S.toList transSet)
101−
102−--verify that all parent links exist and that all children exist
103−--maybe verify that all parents end at UUID nil and all children end at leaves
104−walkParentTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
105−walkParentTransactions seenTransSet graph trans =
106− let transUUID = transactionUUID trans in
107− if transUUID == U.nil then
108− Nothing
109− else if S.member transUUID seenTransSet then
110− Just $ TransactionGraphCycleError transUUID
111− else
112− let parentTransSetOrError = parentTransactions trans graph in
113− case parentTransSetOrError of
114− Left err -> Just err
115− Right parentTransSet -> do
116− walk <- mapM (walkParentTransactions (S.insert transUUID seenTransSet) graph) (S.toList parentTransSet)
117− case walk of
118− err:_ -> Just err
119− _ -> Nothing
120−
121−--refactor: needless duplication in these two functions
122−walkChildTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
123−walkChildTransactions seenTransSet graph trans =
124− let transUUID = transactionUUID trans in
125− if childTransactions trans graph == Right S.empty then
126− Nothing
127− else if S.member transUUID seenTransSet then
128− Just $ TransactionGraphCycleError transUUID
129− else
130− let childTransSetOrError = childTransactions trans graph in
131− case childTransSetOrError of
132− Left err -> Just err
133− Right childTransSet -> do
134− walk <- mapM (walkChildTransactions (S.insert transUUID seenTransSet) graph) (S.toList childTransSet)
135− case walk of
136− err:_ -> Just err
137− _ -> Nothing
138−
13911transactionParentUUIDs :: Transaction -> S.Set U.UUID
14012transactionParentUUIDs (Transaction _ (TransactionInfo pUUID _) _) = S.singleton pUUID
141−transactionParentUUIDs (Transaction _ (MergeTransactionInfo pUUID1 pUUID2 _) _) = S.fromList [pUUID1, pUUID2]
13+transactionParentUUIDs (Transaction _ (MergeTransactionInfo pUUID1 pUUID2 _) _) = S.fromList [pUUID1, pUUID2]
14+
ProjectM36/Transaction/Persist.hsnew file+124
@@ -0,0 +1,124 @@
1+module ProjectM36.Transaction.Persist where
2+import ProjectM36.Base
3+import ProjectM36.Error
4+import qualified Data.Map as M
5+import qualified Data.HashSet as HS
6+import qualified Data.UUID as U
7+import qualified Data.Binary as B
8+import qualified Data.ByteString.Lazy as BS
9+import System.FilePath
10+import System.Directory
11+import qualified Data.Text as T
12+import Control.Monad
13+
14+tempTransactionDir :: FilePath -> U.UUID -> FilePath
15+tempTransactionDir dbdir transUUID = dbdir </> "." ++ U.toString transUUID
16+
17+transactionDir :: FilePath -> U.UUID -> FilePath
18+transactionDir dbdir transUUID = dbdir </> U.toString transUUID
19+
20+transactionInfoPath :: FilePath -> FilePath
21+transactionInfoPath = (</>) "info"
22+
23+relvarsDir :: FilePath -> FilePath
24+relvarsDir = (</>) "relvars"
25+
26+incDepsDir :: FilePath -> FilePath
27+incDepsDir = (</>) "incdeps"
28+
29+atomFuncsDir :: FilePath -> FilePath
30+atomFuncsDir = (</>) "atomfuncs"
31+
32+readTransaction :: FilePath -> U.UUID -> IO (Either PersistenceError Transaction)
33+readTransaction dbdir transUUID = do
34+ let transDir = transactionDir dbdir transUUID
35+ transDirExists <- doesDirectoryExist transDir
36+ if not transDirExists then
37+ return $ Left $ MissingTransactionError transUUID
38+ else do
39+ relvars <- readRelVars transDir
40+ transInfo <- liftM B.decode $ BS.readFile (transactionInfoPath transDir)
41+ incDeps <- readIncDeps transDir
42+ atomFuncs <- readAtomFuncs transDir
43+ let newContext = DatabaseContext { inclusionDependencies = incDeps,
44+ relationVariables = relvars,
45+ atomFunctions = atomFuncs }
46+
47+ return $ Right $ Transaction transUUID transInfo newContext
48+
49+writeTransaction :: FilePath -> Transaction -> IO ()
50+writeTransaction dbdir trans = do
51+ let tempTransDir = tempTransactionDir dbdir (transactionUUID trans)
52+ finalTransDir = transactionDir dbdir (transactionUUID trans)
53+ context = transactionContext trans
54+ transDirExists <- doesDirectoryExist finalTransDir
55+ if not transDirExists then do
56+ --create sub directories
57+ mapM_ createDirectory [tempTransDir, relvarsDir tempTransDir, incDepsDir tempTransDir, atomFuncsDir tempTransDir]
58+ writeRelVars tempTransDir (relationVariables context)
59+ writeIncDeps tempTransDir (inclusionDependencies context)
60+ writeAtomFuncs tempTransDir (atomFunctions context)
61+ BS.writeFile (transactionInfoPath tempTransDir) (B.encode $ transactionInfo trans)
62+ --move the temp directory to final location
63+ renameDirectory tempTransDir finalTransDir
64+ else
65+ return ()
66+
67+writeRelVar :: FilePath -> (RelVarName, Relation) -> IO ()
68+writeRelVar transDir (relvarName, rel) = do
69+ let relvarPath = relvarsDir transDir </> T.unpack relvarName
70+ BS.writeFile relvarPath (B.encode rel)
71+
72+writeRelVars :: FilePath -> (M.Map RelVarName Relation) -> IO ()
73+writeRelVars transDir relvars = mapM_ (writeRelVar transDir) $ M.toList relvars
74+
75+readRelVars :: FilePath -> IO (M.Map RelVarName Relation)
76+readRelVars transDir = do
77+ let relvarsPath = relvarsDir transDir
78+ relvarNames <- getDirectoryContents relvarsPath
79+ relvars <- mapM (\name -> do
80+ rel <- liftM B.decode $ BS.readFile (relvarsPath </> name)
81+ return (T.pack name, rel)) relvarNames
82+ return $ M.fromList relvars
83+
84+writeAtomFuncs :: FilePath -> AtomFunctions -> IO ()
85+writeAtomFuncs transDir funcs = mapM_ (writeAtomFunc (atomFuncsDir transDir)) $ HS.toList funcs
86+
87+--all the atom functions are in one file (???)
88+readAtomFuncs :: FilePath -> IO (AtomFunctions)
89+readAtomFuncs transDir = do
90+ funcNames <- getDirectoryContents (incDepsDir transDir)
91+ funcs <- mapM (readAtomFunc transDir) (map T.pack funcNames)
92+ return $ HS.fromList funcs
93+
94+--to write the atom functions, we really some bytecode to write (GHCi bytecode?)
95+writeAtomFunc :: FilePath -> AtomFunction -> IO ()
96+writeAtomFunc transDir func = do
97+ let atomFuncPath = atomFuncsDir transDir </> T.unpack (atomFuncName func)
98+ BS.writeFile atomFuncPath BS.empty
99+
100+readAtomFunc :: FilePath -> AtomFunctionName -> IO (AtomFunction)
101+readAtomFunc transDir funcName = do
102+ let atomFuncPath = atomFuncsDir transDir </> T.unpack funcName
103+ _ <- BS.readFile atomFuncPath
104+ return undefined
105+
106+writeIncDep :: FilePath -> (IncDepName, InclusionDependency) -> IO ()
107+writeIncDep transDir (incDepName, incDep) = do
108+ BS.writeFile (incDepsDir transDir </> T.unpack incDepName) $ B.encode incDep
109+
110+writeIncDeps :: FilePath -> M.Map IncDepName InclusionDependency -> IO ()
111+writeIncDeps transDir incdeps = mapM_ (writeIncDep transDir) $ M.toList incdeps
112+
113+readIncDep :: FilePath -> IncDepName -> IO (IncDepName, InclusionDependency)
114+readIncDep transDir incdepName = do
115+ let incDepPath = incDepsDir transDir </> T.unpack incdepName
116+ incDepData <- BS.readFile incDepPath
117+ return $ (incdepName, B.decode incDepData)
118+
119+readIncDeps :: FilePath -> IO (M.Map IncDepName InclusionDependency)
120+readIncDeps transDir = do
121+ let incDepsPath = incDepsDir transDir
122+ incDepNames <- getDirectoryContents incDepsPath
123+ incDeps <- mapM (readIncDep transDir) (map T.pack incDepNames)
124+ return $ M.fromList incDeps
ProjectM36/TransactionGraph.hsnew file+129
@@ -0,0 +1,129 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module ProjectM36.TransactionGraph where
3+import ProjectM36.Base
4+import ProjectM36.Error
5+import qualified Data.UUID as U
6+import qualified Data.Set as S
7+import qualified Data.Map as M
8+import Control.Applicative ((<$>))
9+import Control.Monad
10+
11+
12+bootstrapTransactionGraph :: U.UUID -> DatabaseContext -> TransactionGraph
13+bootstrapTransactionGraph freshUUID context = TransactionGraph bootstrapHeads bootstrapTransactions
14+ where
15+ bootstrapHeads = M.singleton "master" freshTransaction
16+ freshTransaction = Transaction freshUUID (TransactionInfo U.nil S.empty) context
17+ bootstrapTransactions = S.singleton freshTransaction
18+
19+emptyTransactionGraph :: TransactionGraph
20+emptyTransactionGraph = TransactionGraph M.empty S.empty
21+
22+transactionForHead :: HeadName -> TransactionGraph -> Maybe Transaction
23+transactionForHead headName (TransactionGraph heads _) = M.lookup headName heads
24+
25+headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
26+headNameForTransaction transaction (TransactionGraph heads _) = if M.null matchingTrans then
27+ Nothing
28+ else
29+ Just $ (head . M.keys) matchingTrans
30+ where
31+ matchingTrans = M.filter (transaction ==) heads
32+
33+transactionForUUID :: U.UUID -> TransactionGraph -> Either RelationalError Transaction
34+transactionForUUID uuid graph = if S.null matchingTrans then
35+ Left $ NoSuchTransactionError uuid
36+ else
37+ Right $ head (S.toList matchingTrans)
38+ where
39+ matchingTrans = S.filter (\(Transaction uuidMatch _ _) -> uuidMatch == uuid) (transactionsForGraph graph)
40+
41+transactionsForUUIDs :: S.Set U.UUID -> TransactionGraph -> Either RelationalError (S.Set Transaction)
42+transactionsForUUIDs uuidSet graph = do
43+ transList <- forM (S.toList uuidSet) ((flip transactionForUUID) graph)
44+ return (S.fromList transList)
45+
46+isRootTransaction :: Transaction -> TransactionGraph -> Bool
47+isRootTransaction (Transaction _ (TransactionInfo pUUID _) _) _ = U.null pUUID
48+isRootTransaction (Transaction _ (MergeTransactionInfo _ _ _) _) _ = False
49+
50+-- the first transaction has no parent - all other do have parents- merges have two parents
51+parentTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
52+parentTransactions (Transaction _ (TransactionInfo pUUID _) _) graph = do
53+ trans <- transactionForUUID pUUID graph
54+ return (S.singleton trans)
55+
56+parentTransactions (Transaction _ (MergeTransactionInfo pUUID1 pUUID2 _) _ ) graph = transactionsForUUIDs (S.fromList [pUUID1, pUUID2]) graph
57+
58+
59+childTransactions :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction)
60+childTransactions (Transaction _ (TransactionInfo _ children) _) = transactionsForUUIDs children
61+childTransactions (Transaction _ (MergeTransactionInfo _ _ children) _) = transactionsForUUIDs children
62+
63+-- create a new commit and add it to the heads
64+-- 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)
65+addBranch :: U.UUID -> HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
66+addBranch newUUID newBranchName branchPoint graph = addTransactionToGraph newBranchName branchPoint newUUID (transactionContext branchPoint) graph
67+
68+--adds a disconnected transaction to a transaction graph at some head
69+addDisconnectedTransaction :: U.UUID -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
70+addDisconnectedTransaction newUUID headName (DisconnectedTransaction parentUUID context) graph = do
71+ parentTrans <- transactionForUUID parentUUID graph
72+ addTransactionToGraph headName parentTrans newUUID context graph
73+
74+-- create a new transaction on "newHeadName" with the branchPointTrans
75+addTransactionToGraph :: HeadName -> Transaction -> U.UUID -> DatabaseContext -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
76+addTransactionToGraph newHeadName branchPointTrans newUUID newContext (TransactionGraph heads transSet) = do
77+ let freshTransaction = Transaction newUUID (TransactionInfo (transactionUUID branchPointTrans) S.empty) newContext
78+ -- there are two parents for MergeTransactions! not implemented
79+ --update parentTransaction to add child
80+ let parentTransaction = branchPointTrans
81+ let updatedTransSet = (S.insert freshTransaction <$> S.insert parentTransaction <$> S.delete parentTransaction) transSet
82+ let updatedGraph = TransactionGraph (M.insert newHeadName freshTransaction heads) updatedTransSet
83+ return (freshTransaction, updatedGraph)
84+
85+validateGraph :: TransactionGraph -> Maybe [RelationalError]
86+validateGraph graph@(TransactionGraph _ transSet) = do
87+ --check that all UUIDs are unique in the graph
88+ --uuids = map transactionUUID transSet
89+ --check that all heads appear in the transSet
90+ --check that all forward and backward links are in place
91+ _ <- mapM (walkParentTransactions S.empty graph) (S.toList transSet)
92+ mapM (walkChildTransactions S.empty graph) (S.toList transSet)
93+
94+--verify that all parent links exist and that all children exist
95+--maybe verify that all parents end at UUID nil and all children end at leaves
96+walkParentTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
97+walkParentTransactions seenTransSet graph trans =
98+ let transUUID = transactionUUID trans in
99+ if transUUID == U.nil then
100+ Nothing
101+ else if S.member transUUID seenTransSet then
102+ Just $ TransactionGraphCycleError transUUID
103+ else
104+ let parentTransSetOrError = parentTransactions trans graph in
105+ case parentTransSetOrError of
106+ Left err -> Just err
107+ Right parentTransSet -> do
108+ walk <- mapM (walkParentTransactions (S.insert transUUID seenTransSet) graph) (S.toList parentTransSet)
109+ case walk of
110+ err:_ -> Just err
111+ _ -> Nothing
112+
113+--refactor: needless duplication in these two functions
114+walkChildTransactions :: S.Set U.UUID -> TransactionGraph -> Transaction -> Maybe RelationalError
115+walkChildTransactions seenTransSet graph trans =
116+ let transUUID = transactionUUID trans in
117+ if childTransactions trans graph == Right S.empty then
118+ Nothing
119+ else if S.member transUUID seenTransSet then
120+ Just $ TransactionGraphCycleError transUUID
121+ else
122+ let childTransSetOrError = childTransactions trans graph in
123+ case childTransSetOrError of
124+ Left err -> Just err
125+ Right childTransSet -> do
126+ walk <- mapM (walkChildTransactions (S.insert transUUID seenTransSet) graph) (S.toList childTransSet)
127+ case walk of
128+ err:_ -> Just err
129+ _ -> Nothing
ProjectM36/TransactionGraph/Persist.hs+109−44
@@ -1,18 +1,22 @@
11{-# LANGUAGE OverloadedStrings #-}
22module ProjectM36.TransactionGraph.Persist where
3+import ProjectM36.Error
4+import ProjectM36.TransactionGraph
5+import ProjectM36.Transaction
6+import ProjectM36.Transaction.Persist
37import ProjectM36.Base
4−import qualified Data.Binary as B
58import System.Directory
9+import System.Posix.Files
610import System.FilePath
11+import System.IO.Temp
712import Data.List
813import qualified Data.UUID as U
914import qualified Data.Set as S
1015import qualified Data.Map as M
11−import qualified Data.HashSet as HS
12−import ProjectM36.Transaction
13−import qualified Data.ByteString.Lazy as BS
14−import qualified Data.Text.Encoding as TE
1516import qualified Data.Text as T
17+import Control.Monad (foldM)
18+import Data.Either (isRight)
19+import Data.Maybe (catMaybes)
1620
1721{-
1822The "m36v1" file at the top-level of the destination directory contains the the transaction graph as a set of UUIDs referencing their parents (1 or more)
@@ -20,14 +24,103 @@ Each Transaction is written to it own directory named by its UUID. Partially wri
2024
2125-}
2226
23−--as a first pass, just write to the directory as if it's empty
24−--next pass, make the writing incremental by only writing new transactions at the heads
27+transactionLogPath :: FilePath -> FilePath
28+transactionLogPath = (</>) "m36v1"
29+
30+headsDir :: FilePath -> FilePath
31+headsDir = (</>) "heads"
32+
33+{-
34+verify that the database directory is valid or bootstrap it
35+-note: checking for the existence of every transaction may be prohibitively expensive
36+-}
37+
38+setupDatabaseDir :: FilePath -> TransactionGraph -> IO (Maybe PersistenceError)
39+setupDatabaseDir dbdir bootstrapGraph = do
40+ dbdirExists <- doesDirectoryExist dbdir
41+ m36exists <- doesFileExist (transactionLogPath dbdir)
42+ if dbdirExists && m36exists then
43+ return Nothing
44+ else if not m36exists then do
45+ bootstrapDatabaseDir dbdir bootstrapGraph
46+ return Nothing
47+ else
48+ return $ Just (InvalidDirectoryError dbdir)
49+
50+{-
51+initialize a database directory with the graph from which to bootstrap
52+-}
53+bootstrapDatabaseDir :: FilePath -> TransactionGraph -> IO ()
54+bootstrapDatabaseDir dbdir bootstrapGraph = do
55+ createDirectory dbdir
56+ transactionGraphPersist dbdir bootstrapGraph
57+
58+{-
59+incrementally updates an existing database directory
60+--algorithm:
61+-check that all heads have been written
62+-assume that all non-head transactions have already been written because this is an incremental (and concurrent!) write method
63+--store the head names with a symlink to the transaction under "heads"
64+-}
2565transactionGraphPersist :: FilePath -> TransactionGraph -> IO ()
2666transactionGraphPersist destDirectory graph = do
27− mapM_ (writeTransaction destDirectory) $ S.toList (transactionsForGraph graph)
67+ mapM_ (writeTransaction destDirectory) $ M.elems (transactionHeadsForGraph graph)
2868 writeGraphUUIDFile destDirectory graph
69+ transactionGraphHeadsPersist destDirectory graph
2970 return ()
3071
72+{-
73+write a head datum to the file system
74+1. create a symlink in a temporary directory
75+2. atomically rename it
76+-}
77+--should updating all the heads be atomic (create temp directory and new symlinks for every persistence?
78+transactionGraphHeadsPersist :: FilePath -> TransactionGraph -> IO ()
79+transactionGraphHeadsPersist dbdir graph = do
80+ let writeHead headsPath (headName, trans) = do
81+ let symlinkPath = ".." </> U.toString (transactionUUID trans)
82+ createSymbolicLink symlinkPath (headsPath </> T.unpack headName)
83+ finalHeadsDir = headsDir dbdir
84+ withTempDirectory dbdir ".headsdir.XXXXX" $ \newHeadsDir -> do
85+ let tempHeadsDir = (newHeadsDir </> "heads")
86+ createDirectory tempHeadsDir
87+ mapM_ (writeHead tempHeadsDir) $ M.toList (transactionHeadsForGraph graph)
88+ --move the new heads dir into place atomically
89+ renameDirectory tempHeadsDir finalHeadsDir
90+
91+
92+{-
93+load any transactions which are not already part of the incoming transaction graph
94+-}
95+
96+--ALERT I need to figure out how to manage the transaction heads (branch names)
97+transactionGraphLoad :: FilePath -> TransactionGraph -> IO (Either PersistenceError TransactionGraph)
98+transactionGraphLoad dbdir graphIn = do
99+ --optimization: perform tail-bisection search to find last-recorded transaction in the existing stream- replay the rest
100+ --read in all missing transactions from transaction directories and add to graph
101+ uuidInfo <- readGraphUUIDFile dbdir
102+ case uuidInfo of
103+ Left err -> return $ Left err
104+ Right info -> do
105+ let folder = \eitherGraph transUUID -> case eitherGraph of
106+ Left err -> return $ Left err
107+ Right graph -> readTransactionIfNecessary dbdir transUUID graph
108+ foldM folder (Right graphIn) (map fst info)
109+
110+{-
111+if the transaction with the UUID argument is not yet part of the graph, then read the transaction and add it - this does not update the heads
112+-}
113+readTransactionIfNecessary :: FilePath -> U.UUID -> TransactionGraph -> IO (Either PersistenceError TransactionGraph)
114+readTransactionIfNecessary dbdir transUUID graphIn = do
115+ if isRight $ transactionForUUID transUUID graphIn then
116+ --the transaction is already known and loaded- done
117+ return $ Right graphIn
118+ else do
119+ trans <- readTransaction dbdir transUUID
120+ case trans of
121+ Left err -> return $ Left err
122+ Right trans' -> return $ Right $ TransactionGraph (transactionHeadsForGraph graphIn) (S.insert trans' (transactionsForGraph graphIn))
123+
31124writeGraphUUIDFile :: FilePath -> TransactionGraph -> IO ()
32125writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graphFile uuidInfo
33126 where
@@ -36,40 +129,12 @@ writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graph
36129 graphLines = S.toList $ S.map graphLine transSet
37130 graphLine trans = U.toString (transactionUUID trans) ++ " " ++ intercalate " " (S.toList (S.map U.toString $ transactionParentUUIDs trans))
38131
39−readGraphUUIDFile :: FilePath -> IO ()
40−readGraphUUIDFile destDirectory = undefined
41−
42−readTransaction :: FilePath -> U.UUID -> IO ()
43−readTransaction destDirectory transUUID = undefined
132+readGraphUUIDFile :: FilePath -> IO (Either PersistenceError [(U.UUID, [U.UUID])])
133+readGraphUUIDFile dbdir = do
134+ --read in all transactions' uuids
135+ let grapher line = let uuids = catMaybes (map U.fromString (words line)) in
136+ (head uuids, tail uuids)
137+ --warning: uses lazy IO
138+ graphUUIDData <- readFile (transactionLogPath dbdir)
139+ return $ Right (map grapher $ lines graphUUIDData)
44140
45−writeTransaction :: FilePath -> Transaction -> IO ()
46−writeTransaction destDirectory trans = do
47− let tempDestDir = takeDirectory destDirectory </> "." ++ takeFileName destDirectory
48− relvarsDir = tempDestDir </> "relvars"
49− incDepsDir = tempDestDir </> "incdeps"
50− atomFuncsDir = tempDestDir </> "atomfuncs"
51− context = transactionContext trans
52− --create sub directories
53− mapM_ createDirectory [tempDestDir, relvarsDir, incDepsDir, atomFuncsDir]
54− --write relvars
55− mapM_ (writeRelVar relvarsDir) $ M.toList (relationVariables context)
56− --write inclusion dependencies
57− mapM_ (writeIncDep incDepsDir) $ M.toList (inclusionDependencies context)
58− --write atom functions
59− mapM_ (writeAtomFunc atomFuncsDir) $ HS.toList (atomFunctions context)
60− --move the temp directory to final location
61− renameDirectory tempDestDir destDirectory
62−
63−writeRelVar :: FilePath -> (RelVarName, Relation) -> IO ()
64−writeRelVar relvarDestDir (relvarName, rel) = do
65− let relvarPath = relvarDestDir </> T.unpack relvarName
66− BS.writeFile relvarPath (B.encode rel)
67−
68−--to write the atom functions, we really some bytecode to write (GHCi bytecode?)
69−writeAtomFunc :: FilePath -> AtomFunction -> IO ()
70−writeAtomFunc atomFuncDestDir func = do
71− BS.appendFile (atomFuncDestDir </> "atomFuncs") $ BS.fromStrict (TE.encodeUtf8 (atomFuncName func `T.append` "\n"))
72−
73−writeIncDep :: FilePath -> (IncDepName, InclusionDependency) -> IO ()
74−writeIncDep incDepDir (incDepName, incDep) = do
75− BS.writeFile (incDepDir </> T.unpack incDepName) $ B.encode incDep
ProjectM36/Transaction/Show.hs → ProjectM36/TransactionGraph/Show.hsrenamed+2−1
@@ -1,11 +1,12 @@
11{-# LANGUAGE OverloadedStrings #-}
2−module ProjectM36.Transaction.Show where
2+module ProjectM36.TransactionGraph.Show where
33import ProjectM36.Base
44import ProjectM36.Relation
55import ProjectM36.Tuple
66import ProjectM36.TupleSet
77import ProjectM36.Error
88import ProjectM36.Transaction
9+import ProjectM36.TransactionGraph
910import qualified ProjectM36.Attribute as A
1011import qualified Data.HashSet as HS
1112import qualified Data.Set as S
TutorialD/Interpreter.hs+6−4
@@ -5,7 +5,7 @@ import TutorialD.Interpreter.RODatabaseContextOperator
55import TutorialD.Interpreter.DatabaseExpr
66import TutorialD.Interpreter.TransactionGraphOperator
77import ProjectM36.Base
8−import ProjectM36.Transaction
8+import ProjectM36.TransactionGraph
99import ProjectM36.TransactionGraph.Persist
1010import Text.Parsec
1111import Text.Parsec.String
@@ -57,8 +57,6 @@ reprLoop :: FilePath -> DisconnectedTransaction -> TransactionGraph -> IO ()
5757reprLoop transGraphDir currentTransaction graph = do
5858 homeDirectory <- getHomeDirectory
5959 let settings = defaultSettings {historyFile = Just (homeDirectory ++ "/.tutd_history")}
60− --save the current transaction graph
61− transactionGraphPersist transGraphDir graph
6260
6361 maybeLine <- runInputT settings $ getInputLine (T.unpack (promptText currentTransaction graph))
6462
@@ -75,9 +73,13 @@ reprLoop transGraphDir currentTransaction graph = do
7573 reprLoop transGraphDir updatedTrans updatedGraph
7674 (DisplayResult out, updatedTrans, updatedGraph) -> do
7775 TIO.putStrLn out
76+ --save the current transaction graph
77+ transactionGraphPersist transGraphDir updatedGraph
7878 reprLoop transGraphDir updatedTrans updatedGraph
7979 (QuietSuccessResult, updatedTrans, updatedGraph) -> do
80− TIO.putStrLn "Done."
80+ --save the current transaction graph
81+ transactionGraphPersist transGraphDir updatedGraph
82+ TIO.putStrLn "Done."
8183 reprLoop transGraphDir updatedTrans updatedGraph
8284
8385--used by :dumpGraph
TutorialD/Interpreter/TransactionGraphOperator.hs+2−1
@@ -8,7 +8,8 @@ import qualified Data.UUID as U
88import qualified Data.Text as T
99import ProjectM36.Error
1010import ProjectM36.Transaction
11−import ProjectM36.Transaction.Show
11+import ProjectM36.TransactionGraph
12+import ProjectM36.TransactionGraph.Show
1213import ProjectM36.Relation.Show.Term
1314
1415--operators which manipulate a transaction graph
TutorialD/tutd.hs+16−4
@@ -1,17 +1,29 @@
11{-# LANGUAGE OverloadedStrings #-}
22import TutorialD.Interpreter
33import ProjectM36.RelationalExpression
4+import ProjectM36.TransactionGraph
5+import ProjectM36.TransactionGraph.Persist
46import ProjectM36.Transaction
57import ProjectM36.Base
8+import System.IO
69import Data.UUID.V4 (nextRandom)
710
811main :: IO ()
912main = do
1013 freshUUID <- nextRandom
1114 let currentHeadName = "master"
12− newGraph = bootstrapTransactionGraph freshUUID dateExamples
13− headTransaction = case transactionForHead currentHeadName newGraph of { Just x -> x; _ -> undefined}
15+ --if the database is empty, bootstrap it with the bootstrap graph
16+ bootstrapGraph = bootstrapTransactionGraph freshUUID dateExamples
1417 dbdir = "/tmp/testdb"
15− currentTransaction = newDisconnectedTransaction (transactionUUID headTransaction) (transactionContext headTransaction)
16− reprLoop dbdir currentTransaction newGraph
18+ err <- setupDatabaseDir dbdir bootstrapGraph
19+ case err of
20+ Just err -> hPutStrLn stderr $ "Database setup failed: " ++ show err
21+ Nothing -> do
22+ freshGraph <- transactionGraphLoad dbdir emptyTransactionGraph
23+ case freshGraph of
24+ Left err -> hPutStrLn stderr $ "Database load failed: " ++ show err
25+ Right freshGraph' -> do
26+ let headTransaction = case transactionForHead currentHeadName freshGraph' of { Just x -> x; _ -> undefined}
27+ currentTransaction = newDisconnectedTransaction (transactionUUID headTransaction) (transactionContext headTransaction)
28+ reprLoop dbdir currentTransaction freshGraph'
1729
project-m36.cabal+6−4
@@ -7,10 +7,12 @@ Category: Relational Algebra
77Cabal-Version: >= 1.2
88
99Library
10− Build-Depends: base,mtl,containers,unordered-containers,parsec,hashable,haskeline,directory,uuid,cassava,text,bytestring,deepseq,deepseq-generics,vector,parallel,monad-parallel,transformers,easyplot,binary,filepath,directory,vector-binary-instances
10+ Build-Depends: base,mtl,containers,unordered-containers,parsec,hashable,haskeline,directory,uuid,cassava,text,bytestring,deepseq,deepseq-generics,vector,parallel,monad-parallel,transformers,easyplot,binary,filepath,directory,vector-binary-instances,unix,temporary
1111 Exposed-Modules: ProjectM36.Error,
1212 ProjectM36.Transaction,
13− ProjectM36.Transaction.Show,
13+ ProjectM36.TransactionGraph,
14+ ProjectM36.TransactionGraph.Show,
15+ ProjectM36.TransactionGraph.Persist,
1416 ProjectM36.Base,
1517 ProjectM36.Attribute,
1618 ProjectM36.AttributeNames,
@@ -19,13 +21,13 @@ Library
1921 ProjectM36.Atom,
2022 ProjectM36.AtomFunction,
2123 ProjectM36.Key,
24+ ProjectM36.DatabaseContext,
2225 ProjectM36.RelationalExpression,
2326 ProjectM36.Relation.Show.HTML,
2427 ProjectM36.StaticOptimizer,
2528 ProjectM36.Relation.Show.Term,
2629 ProjectM36.Relation.Show.CSV,
27− ProjectM36.Relation.Show.Gnuplot,
28− ProjectM36.TransactionGraph.Persist
30+ ProjectM36.Relation.Show.Gnuplot
2931 GHC-Options: -Wall -rtsopts
3032
3133Executable tutd
test/TutorialD/Interpreter.hs+1
@@ -9,6 +9,7 @@ import ProjectM36.TupleSet
99import ProjectM36.Error
1010import ProjectM36.Base
1111import ProjectM36.Transaction
12+import ProjectM36.TransactionGraph
1213import qualified ProjectM36.Attribute as A
1314import qualified Data.HashSet as HS
1415import qualified Data.Map as M