on master help sign in
WIP: incremental reading and writing of the graph
fde4091AgentM
ProjectM36/Base.hs+4−1
@@ -191,7 +191,7 @@ data TransactionInfo = TransactionInfo UUID (S.Set UUID) | -- 1 parent + n child
191 MergeTransactionInfo UUID UUID (S.Set UUID) -- 2 parents, n children
191 MergeTransactionInfo UUID UUID (S.Set UUID) -- 2 parents, n children
192 deriving(Show, Generic)
192 deriving(Show, Generic)
193
193
194−--instance Binary TransactionInfo
194+instance Binary TransactionInfo
195
195
196data Transaction = Transaction UUID TransactionInfo DatabaseContext -- self uuid
196data Transaction = Transaction UUID TransactionInfo DatabaseContext -- self uuid
197 deriving (Show, Generic)
197 deriving (Show, Generic)
@@ -208,6 +208,9 @@ transactionUUID (Transaction uuid _ _) = uuid
208
208
209transactionContext :: Transaction -> DatabaseContext
209transactionContext :: Transaction -> DatabaseContext
210transactionContext (Transaction _ _ context) = context
210transactionContext (Transaction _ _ context) = context
211+
212+transactionInfo :: Transaction -> TransactionInfo
213+transactionInfo (Transaction _ info _) = info
211
214
212instance Eq Transaction where
215instance Eq Transaction where
213 (Transaction uuidA _ _) == (Transaction uuidB _ _) = uuidA == uuidB
216 (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)
34 | AtomFunctionTypeError AtomFunctionName Int AtomType AtomType --arg number
34 | AtomFunctionTypeError AtomFunctionName Int AtomType AtomType --arg number
35 deriving (Show,Eq,Generic)
35 deriving (Show,Eq,Generic)
36
36
37+instance NFData RelationalError where rnf = genericRnf
38+
39+data PersistenceError = InvalidDirectoryError FilePath |
40+ MissingTransactionError U.UUID
41+ deriving (Show, Eq)
37
42
38−instance NFData RelationalError where rnf = genericRnf
43+
ProjectM36/Transaction.hs+2−129
@@ -2,140 +2,13 @@
2module ProjectM36.Transaction where
2module ProjectM36.Transaction where
3import qualified Data.UUID as U
3import qualified Data.UUID as U
4import ProjectM36.Base
4import ProjectM36.Base
5−import ProjectM36.Error
6import qualified Data.Set as S
5import qualified Data.Set as S
7−import qualified Data.Map as M
8−import Control.Applicative ((<$>))
9−import Control.Monad
10
6
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−
60--create a disconnected transaction with a parent
7--create a disconnected transaction with a parent
61newDisconnectedTransaction :: U.UUID -> DatabaseContext -> DisconnectedTransaction
8newDisconnectedTransaction :: U.UUID -> DatabaseContext -> DisconnectedTransaction
62newDisconnectedTransaction parentUUID context = DisconnectedTransaction parentUUID context
9newDisconnectedTransaction parentUUID context = DisconnectedTransaction parentUUID context
63
10
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−
139transactionParentUUIDs :: Transaction -> S.Set U.UUID
11transactionParentUUIDs :: Transaction -> S.Set U.UUID
140transactionParentUUIDs (Transaction _ (TransactionInfo pUUID _) _) = S.singleton pUUID
12transactionParentUUIDs (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 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2module ProjectM36.TransactionGraph.Persist where
2module ProjectM36.TransactionGraph.Persist where
3+import ProjectM36.Error
4+import ProjectM36.TransactionGraph
5+import ProjectM36.Transaction
6+import ProjectM36.Transaction.Persist
3import ProjectM36.Base
7import ProjectM36.Base
4−import qualified Data.Binary as B
5import System.Directory
8import System.Directory
9+import System.Posix.Files
6import System.FilePath
10import System.FilePath
11+import System.IO.Temp
7import Data.List
12import Data.List
8import qualified Data.UUID as U
13import qualified Data.UUID as U
9import qualified Data.Set as S
14import qualified Data.Set as S
10import qualified Data.Map as M
15import 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
15import qualified Data.Text as T
16import qualified Data.Text as T
17+import Control.Monad (foldM)
18+import Data.Either (isRight)
19+import Data.Maybe (catMaybes)
16
20
17{-
21{-
18The "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)
22The "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
20
24
21-}
25-}
22
26
23−--as a first pass, just write to the directory as if it's empty
27+transactionLogPath :: FilePath -> FilePath
24−--next pass, make the writing incremental by only writing new transactions at the heads
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+-}
25transactionGraphPersist :: FilePath -> TransactionGraph -> IO ()
65transactionGraphPersist :: FilePath -> TransactionGraph -> IO ()
26transactionGraphPersist destDirectory graph = do
66transactionGraphPersist destDirectory graph = do
27− mapM_ (writeTransaction destDirectory) $ S.toList (transactionsForGraph graph)
67+ mapM_ (writeTransaction destDirectory) $ M.elems (transactionHeadsForGraph graph)
28 writeGraphUUIDFile destDirectory graph
68 writeGraphUUIDFile destDirectory graph
69+ transactionGraphHeadsPersist destDirectory graph
29 return ()
70 return ()
30
71
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+
31writeGraphUUIDFile :: FilePath -> TransactionGraph -> IO ()
124writeGraphUUIDFile :: FilePath -> TransactionGraph -> IO ()
32writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graphFile uuidInfo
125writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graphFile uuidInfo
33 where
126 where
@@ -36,40 +129,12 @@ writeGraphUUIDFile destDirectory (TransactionGraph _ transSet) = writeFile graph
36 graphLines = S.toList $ S.map graphLine transSet
129 graphLines = S.toList $ S.map graphLine transSet
37 graphLine trans = U.toString (transactionUUID trans) ++ " " ++ intercalate " " (S.toList (S.map U.toString $ transactionParentUUIDs trans))
130 graphLine trans = U.toString (transactionUUID trans) ++ " " ++ intercalate " " (S.toList (S.map U.toString $ transactionParentUUIDs trans))
38
131
39−readGraphUUIDFile :: FilePath -> IO ()
132+readGraphUUIDFile :: FilePath -> IO (Either PersistenceError [(U.UUID, [U.UUID])])
40−readGraphUUIDFile destDirectory = undefined
133+readGraphUUIDFile dbdir = do
41−
134+ --read in all transactions' uuids
42−readTransaction :: FilePath -> U.UUID -> IO ()
135+ let grapher line = let uuids = catMaybes (map U.fromString (words line)) in
43−readTransaction destDirectory transUUID = undefined
136+ (head uuids, tail uuids)
137+ --warning: uses lazy IO
138+ graphUUIDData <- readFile (transactionLogPath dbdir)
139+ return $ Right (map grapher $ lines graphUUIDData)
44
140
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 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2−module ProjectM36.Transaction.Show where
2+module ProjectM36.TransactionGraph.Show where
3import ProjectM36.Base
3import ProjectM36.Base
4import ProjectM36.Relation
4import ProjectM36.Relation
5import ProjectM36.Tuple
5import ProjectM36.Tuple
6import ProjectM36.TupleSet
6import ProjectM36.TupleSet
7import ProjectM36.Error
7import ProjectM36.Error
8import ProjectM36.Transaction
8import ProjectM36.Transaction
9+import ProjectM36.TransactionGraph
9import qualified ProjectM36.Attribute as A
10import qualified ProjectM36.Attribute as A
10import qualified Data.HashSet as HS
11import qualified Data.HashSet as HS
11import qualified Data.Set as S
12import qualified Data.Set as S
TutorialD/Interpreter.hs+6−4
@@ -5,7 +5,7 @@ import TutorialD.Interpreter.RODatabaseContextOperator
5import TutorialD.Interpreter.DatabaseExpr
5import TutorialD.Interpreter.DatabaseExpr
6import TutorialD.Interpreter.TransactionGraphOperator
6import TutorialD.Interpreter.TransactionGraphOperator
7import ProjectM36.Base
7import ProjectM36.Base
8−import ProjectM36.Transaction
8+import ProjectM36.TransactionGraph
9import ProjectM36.TransactionGraph.Persist
9import ProjectM36.TransactionGraph.Persist
10import Text.Parsec
10import Text.Parsec
11import Text.Parsec.String
11import Text.Parsec.String
@@ -57,8 +57,6 @@ reprLoop :: FilePath -> DisconnectedTransaction -> TransactionGraph -> IO ()
57reprLoop transGraphDir currentTransaction graph = do
57reprLoop transGraphDir currentTransaction graph = do
58 homeDirectory <- getHomeDirectory
58 homeDirectory <- getHomeDirectory
59 let settings = defaultSettings {historyFile = Just (homeDirectory ++ "/.tutd_history")}
59 let settings = defaultSettings {historyFile = Just (homeDirectory ++ "/.tutd_history")}
60− --save the current transaction graph
61− transactionGraphPersist transGraphDir graph
62
60
63 maybeLine <- runInputT settings $ getInputLine (T.unpack (promptText currentTransaction graph))
61 maybeLine <- runInputT settings $ getInputLine (T.unpack (promptText currentTransaction graph))
64
62
@@ -75,9 +73,13 @@ reprLoop transGraphDir currentTransaction graph = do
75 reprLoop transGraphDir updatedTrans updatedGraph
73 reprLoop transGraphDir updatedTrans updatedGraph
76 (DisplayResult out, updatedTrans, updatedGraph) -> do
74 (DisplayResult out, updatedTrans, updatedGraph) -> do
77 TIO.putStrLn out
75 TIO.putStrLn out
76+ --save the current transaction graph
77+ transactionGraphPersist transGraphDir updatedGraph
78 reprLoop transGraphDir updatedTrans updatedGraph
78 reprLoop transGraphDir updatedTrans updatedGraph
79 (QuietSuccessResult, updatedTrans, updatedGraph) -> do
79 (QuietSuccessResult, updatedTrans, updatedGraph) -> do
80+ --save the current transaction graph
81+ transactionGraphPersist transGraphDir updatedGraph
80− TIO.putStrLn "Done."
82+ TIO.putStrLn "Done."
81 reprLoop transGraphDir updatedTrans updatedGraph
83 reprLoop transGraphDir updatedTrans updatedGraph
82
84
83--used by :dumpGraph
85--used by :dumpGraph
TutorialD/Interpreter/TransactionGraphOperator.hs+2−1
@@ -8,7 +8,8 @@ import qualified Data.UUID as U
8import qualified Data.Text as T
8import qualified Data.Text as T
9import ProjectM36.Error
9import ProjectM36.Error
10import ProjectM36.Transaction
10import ProjectM36.Transaction
11−import ProjectM36.Transaction.Show
11+import ProjectM36.TransactionGraph
12+import ProjectM36.TransactionGraph.Show
12import ProjectM36.Relation.Show.Term
13import ProjectM36.Relation.Show.Term
13
14
14--operators which manipulate a transaction graph
15--operators which manipulate a transaction graph
TutorialD/tutd.hs+16−4
@@ -1,17 +1,29 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2import TutorialD.Interpreter
2import TutorialD.Interpreter
3import ProjectM36.RelationalExpression
3import ProjectM36.RelationalExpression
4+import ProjectM36.TransactionGraph
5+import ProjectM36.TransactionGraph.Persist
4import ProjectM36.Transaction
6import ProjectM36.Transaction
5import ProjectM36.Base
7import ProjectM36.Base
8+import System.IO
6import Data.UUID.V4 (nextRandom)
9import Data.UUID.V4 (nextRandom)
7
10
8main :: IO ()
11main :: IO ()
9main = do
12main = do
10 freshUUID <- nextRandom
13 freshUUID <- nextRandom
11 let currentHeadName = "master"
14 let currentHeadName = "master"
15+ --if the database is empty, bootstrap it with the bootstrap graph
12− newGraph = bootstrapTransactionGraph freshUUID dateExamples
16+ bootstrapGraph = bootstrapTransactionGraph freshUUID dateExamples
13− headTransaction = case transactionForHead currentHeadName newGraph of { Just x -> x; _ -> undefined}
14 dbdir = "/tmp/testdb"
17 dbdir = "/tmp/testdb"
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}
15− currentTransaction = newDisconnectedTransaction (transactionUUID headTransaction) (transactionContext headTransaction)
27+ currentTransaction = newDisconnectedTransaction (transactionUUID headTransaction) (transactionContext headTransaction)
16− reprLoop dbdir currentTransaction newGraph
28+ reprLoop dbdir currentTransaction freshGraph'
17
29
project-m36.cabal+6−4
@@ -7,10 +7,12 @@ Category: Relational Algebra
7Cabal-Version: >= 1.2
7Cabal-Version: >= 1.2
8
8
9Library
9Library
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
11 Exposed-Modules: ProjectM36.Error,
11 Exposed-Modules: ProjectM36.Error,
12 ProjectM36.Transaction,
12 ProjectM36.Transaction,
13− ProjectM36.Transaction.Show,
13+ ProjectM36.TransactionGraph,
14+ ProjectM36.TransactionGraph.Show,
15+ ProjectM36.TransactionGraph.Persist,
14 ProjectM36.Base,
16 ProjectM36.Base,
15 ProjectM36.Attribute,
17 ProjectM36.Attribute,
16 ProjectM36.AttributeNames,
18 ProjectM36.AttributeNames,
@@ -19,13 +21,13 @@ Library
19 ProjectM36.Atom,
21 ProjectM36.Atom,
20 ProjectM36.AtomFunction,
22 ProjectM36.AtomFunction,
21 ProjectM36.Key,
23 ProjectM36.Key,
24+ ProjectM36.DatabaseContext,
22 ProjectM36.RelationalExpression,
25 ProjectM36.RelationalExpression,
23 ProjectM36.Relation.Show.HTML,
26 ProjectM36.Relation.Show.HTML,
24 ProjectM36.StaticOptimizer,
27 ProjectM36.StaticOptimizer,
25 ProjectM36.Relation.Show.Term,
28 ProjectM36.Relation.Show.Term,
26 ProjectM36.Relation.Show.CSV,
29 ProjectM36.Relation.Show.CSV,
27− ProjectM36.Relation.Show.Gnuplot,
30+ ProjectM36.Relation.Show.Gnuplot
28− ProjectM36.TransactionGraph.Persist
29 GHC-Options: -Wall -rtsopts
31 GHC-Options: -Wall -rtsopts
30
32
31Executable tutd
33Executable tutd
test/TutorialD/Interpreter.hs+1
@@ -9,6 +9,7 @@ import ProjectM36.TupleSet
9import ProjectM36.Error
9import ProjectM36.Error
10import ProjectM36.Base
10import ProjectM36.Base
11import ProjectM36.Transaction
11import ProjectM36.Transaction
12+import ProjectM36.TransactionGraph
12import qualified ProjectM36.Attribute as A
13import qualified ProjectM36.Attribute as A
13import qualified Data.HashSet as HS
14import qualified Data.HashSet as HS
14import qualified Data.Map as M
15import qualified Data.Map as M