on master help sign in
functioning atom function and database context function serialization

dbc test is broken

commit6ea4ad3873665d2109790060ffd5800e46597d3b authorAgentM <agentm@themactionfaction.com> dateMon 10 Apr 2017, 21:36 (10 years ago) parent6219d5d
10 files changed+182−42
src/lib/ProjectM36/AtomFunction.hs+10−1 src/lib/ProjectM36/AtomFunctionError.hs+11 src/lib/ProjectM36/AtomFunctions/Basic.hs+4 src/lib/ProjectM36/Base.hs+1−1 src/lib/ProjectM36/DatabaseContextFunction.hs+14−1 src/lib/ProjectM36/Error.hs+4 src/lib/ProjectM36/RelationalExpression.hs+18−15 src/lib/ProjectM36/Transaction/Persist.hs+88−18 src/lib/ProjectM36/TransactionGraph/Persist.hs+1−2 test/TransactionGraph/Persist.hs+31−4
src/lib/ProjectM36/AtomFunction.hs+10−1
@@ -47,4 +47,13 @@ extractAtomFunctionType typeIn = do
4747 (ADTypeConstructor "AtomFunctionError" []):
4848 (TypeConstructorArg atomRetArg):[])):[] -> do
4949 pure (atomArgs ++ [atomRetArg])
50− otherType -> Left (ScriptError (TypeCheckCompilationError "function returning \"Either AtomFunctionError a\"" (show otherType)))
50+ otherType -> Left (ScriptError (TypeCheckCompilationError "function returning \"Either AtomFunctionError a\"" (show otherType)))
51+
52+isScriptedAtomFunction :: AtomFunction -> Bool
53+isScriptedAtomFunction func = case atomFuncBody func of
54+ AtomFunctionBody (Just _) _ -> True
55+ AtomFunctionBody Nothing _ -> False
56+
57+atomFunctionScript :: AtomFunction -> Maybe AtomFunctionBodyScript
58+atomFunctionScript func = case atomFuncBody func of
59+ AtomFunctionBody script _ -> script
src/lib/ProjectM36/AtomFunctionError.hsnew file+11
@@ -0,0 +1,11 @@
1+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
2+module ProjectM36.AtomFunctionError where
3+import Data.Binary
4+import GHC.Generics
5+import Control.DeepSeq
6+
7+data AtomFunctionError = AtomFunctionUserError String |
8+ AtomFunctionTypeMismatchError |
9+ AtomFunctionBytesDecodingError String
10+ deriving(Generic, Eq, Show, Binary, NFData)
11+
src/lib/ProjectM36/AtomFunctions/Basic.hs+4
@@ -5,6 +5,7 @@ import ProjectM36.DataTypes.Day
55import ProjectM36.DataTypes.Either
66import ProjectM36.DataTypes.Maybe
77import ProjectM36.AtomFunctions.Primitive
8+import ProjectM36.AtomFunction
89import qualified Data.HashSet as HS
910
1011basicAtomFunctions :: AtomFunctions
@@ -13,3 +14,6 @@ basicAtomFunctions = HS.unions [primitiveAtomFunctions,
1314 eitherAtomFunctions,
1415 maybeAtomFunctions]
1516
17+--these special atom functions aren't scripted so they can't be serialized normally. Instead, the body remains in the binary and the serialization/deserialization happens by name only.
18+precompiledAtomFunctions :: AtomFunctions
19+precompiledAtomFunctions = HS.filter (not . isScriptedAtomFunction) basicAtomFunctions
src/lib/ProjectM36/Base.hs+1−1
@@ -434,7 +434,7 @@ data AtomFunction = AtomFunction {
434434 atomFuncType :: [AtomType],
435435 atomFuncBody :: AtomFunctionBody
436436 } deriving (Generic, NFData)
437−
437+
438438instance Hashable AtomFunction where
439439 hashWithSalt salt func = salt `hashWithSalt` (atomFuncName func)
440440
src/lib/ProjectM36/DatabaseContextFunction.hs+14−1
@@ -32,4 +32,17 @@ basicDatabaseContextFunctions = HS.fromList [
3232 dbcFuncType = [],
3333 dbcFuncBody = DatabaseContextFunctionBody Nothing (\_ ctx -> pure $ ctx { relationVariables = M.empty })
3434 }
35− ]
35+ ]
36+
37+--the precompiled functions are special because they cannot be serialized. Their names are therefore used in perpetuity so that the functions can be "serialized" (by name).
38+precompiledDatabaseContextFunctions :: DatabaseContextFunctions
39+precompiledDatabaseContextFunctions = HS.filter (not . isScriptedDatabaseContextFunction) basicDatabaseContextFunctions
40+
41+isScriptedDatabaseContextFunction :: DatabaseContextFunction -> Bool
42+isScriptedDatabaseContextFunction func = case dbcFuncBody func of
43+ DatabaseContextFunctionBody (Just _) _ -> True
44+ DatabaseContextFunctionBody Nothing _ -> False
45+
46+databaseContextFunctionScript :: DatabaseContextFunction -> Maybe DatabaseContextFunctionBodyScript
47+databaseContextFunctionScript func = case dbcFuncBody func of
48+ DatabaseContextFunctionBody script _ -> script
src/lib/ProjectM36/Error.hs+4
@@ -10,6 +10,7 @@ import GHC.Generics (Generic)
1010import qualified Data.Text as T
1111import Data.Binary
1212import Data.Typeable
13+import Control.Exception
1314
1415data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)
1516 | TupleAttributeCountMismatchError Int --attribute name
@@ -68,6 +69,7 @@ data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)
6869 | AtomTypeCountError [AtomType] [AtomType]
6970 | AtomFunctionTypeError AtomFunctionName Int AtomType AtomType --arg number
7071 | AtomFunctionUserError AtomFunctionError
72+ | PrecompiledFunctionRemoveError AtomFunctionName -- pre-compiled atom functions cannot be serialized, so they cannot change over time- they are referred to in perpetuity
7173 | RelationValuedAttributesNotSupportedError [AttributeName]
7274 | NotificationNameInUseError NotificationName
7375 | NotificationNameNotInUseError NotificationName
@@ -117,6 +119,8 @@ data ScriptCompilationError = TypeCheckCompilationError String String | --expect
117119 ScriptCompilationDisabledError |
118120 OtherScriptCompilationError String
119121 deriving (Show,Eq, Generic, Binary, Typeable, NFData)
122+
123+instance Exception ScriptCompilationError
120124
121125data SchemaError = RelVarReferencesMissing (S.Set RelVarName) |
122126 RelVarInReferencedMoreThanOnce RelVarName |
src/lib/ProjectM36/RelationalExpression.hs+18−15
@@ -362,24 +362,27 @@ evalDatabaseContextExpr (MultipleExpr exprs) = do
362362evalDatabaseContextExpr (RemoveAtomFunction funcName) = do
363363 currentContext <- get
364364 let atomFuncs = atomFunctions currentContext
365− dudFunc = emptyAtomFunction funcName -- just for lookup in the hashset
366− if HS.member dudFunc atomFuncs then do
367− let updatedFuncs = HS.delete dudFunc atomFuncs
368− put (currentContext {atomFunctions = updatedFuncs })
369− pure Nothing
370− else
371− pure (Just (FunctionNameNotInUseError funcName))
365+ case atomFunctionForName funcName atomFuncs of
366+ Left err -> pure (Just err)
367+ Right realFunc -> if isScriptedAtomFunction realFunc then do
368+ let updatedFuncs = HS.delete realFunc atomFuncs
369+ put (currentContext {atomFunctions = updatedFuncs })
370+ pure Nothing
371+ else
372+ pure (Just (PrecompiledFunctionRemoveError funcName))
373+
372374
373375evalDatabaseContextExpr (RemoveDatabaseContextFunction funcName) = do
374376 context <- get
375− let dudFunc = emptyDatabaseContextFunction funcName
376− dbcFuncs = dbcFunctions context
377− if HS.member dudFunc dbcFuncs then do
378− let updatedFuncs = HS.delete dudFunc dbcFuncs
379− put (context { dbcFunctions = updatedFuncs })
380− pure Nothing
381− else
382− pure (Just (FunctionNameNotInUseError funcName))
377+ let dbcFuncs = dbcFunctions context
378+ case databaseContextFunctionForName funcName dbcFuncs of
379+ Left err -> pure (Just err)
380+ Right realFunc -> if isScriptedDatabaseContextFunction realFunc then do
381+ let updatedFuncs = HS.delete realFunc dbcFuncs
382+ put (context { dbcFunctions = updatedFuncs })
383+ pure Nothing
384+ else
385+ pure (Just (PrecompiledFunctionRemoveError funcName))
383386
384387evalDatabaseContextExpr (ExecuteDatabaseContextFunction funcName atomArgExprs) = do
385388 context <- get
src/lib/ProjectM36/Transaction/Persist.hs+88−18
@@ -2,6 +2,8 @@ module ProjectM36.Transaction.Persist where
22import ProjectM36.Base
33import ProjectM36.Error
44import ProjectM36.Transaction
5+import ProjectM36.DatabaseContextFunction
6+import ProjectM36.AtomFunction
57import ProjectM36.Persist (writeBSFileSync, DiskSync, renameSync)
68import qualified Data.Map as M
79import qualified Data.HashSet as HS
@@ -11,7 +13,13 @@ import System.FilePath
1113import System.Directory
1214import qualified Data.Text as T
1315import Control.Monad
14−import ProjectM36.AtomFunctions.Basic (basicAtomFunctions)
16+import ProjectM36.ScriptSession
17+import ProjectM36.AtomFunctions.Basic (precompiledAtomFunctions)
18+import Control.Exception
19+import GHC
20+import GHC.Paths
21+
22+import Debug.Trace
1523
1624getDirectoryNames :: FilePath -> IO [FilePath]
1725getDirectoryNames path = do
@@ -36,14 +44,17 @@ incDepsDir transdir = transdir </> "incdeps"
3644atomFuncsDir :: FilePath -> FilePath
3745atomFuncsDir transdir = transdir </> "atomfuncs"
3846
47+dbcFuncsDir :: FilePath -> FilePath
48+dbcFuncsDir transdir = transdir </> "dbcfuncs"
49+
3950typeConsPath :: FilePath -> FilePath
4051typeConsPath transdir = transdir </> "typecons"
4152
4253subschemasPath :: FilePath -> FilePath
4354subschemasPath transdir = transdir </> "schemas"
4455
45−readTransaction :: FilePath -> TransactionId -> IO (Either PersistenceError Transaction)
46−readTransaction dbdir transId = do
56+readTransaction :: FilePath -> TransactionId -> Maybe ScriptSession -> IO (Either PersistenceError Transaction)
57+readTransaction dbdir transId mScriptSession = do
4758 let transDir = transactionDir dbdir transId
4859 transDirExists <- doesDirectoryExist transDir
4960 if not transDirExists then
@@ -54,15 +65,14 @@ readTransaction dbdir transId = do
5465 incDeps <- readIncDeps transDir
5566 typeCons <- readTypeConstructorMapping transDir
5667 sschemas <- readSubschemas transDir
57− --dbcFuncs <-
58− --atomFuncs <- readAtomFuncs transDir -- not yet supported since there is no bytecode to serialize yet
59− let atomFuncs = basicAtomFunctions
68+ dbcFuncs <- readDBCFuncs transDir mScriptSession
69+ atomFuncs <- readAtomFuncs transDir mScriptSession
6070 let newContext = DatabaseContext { inclusionDependencies = incDeps,
6171 relationVariables = relvars,
6272 typeConstructorMapping = typeCons,
6373 notifications = M.empty,
6474 atomFunctions = atomFuncs,
65− dbcFunctions = HS.empty }
75+ dbcFunctions = dbcFuncs }
6676 newSchemas = Schemas newContext sschemas
6777 return $ Right $ Transaction transId transInfo newSchemas
6878
@@ -71,13 +81,14 @@ writeTransaction sync dbdir trans = do
7181 let tempTransDir = tempTransactionDir dbdir (transactionId trans)
7282 finalTransDir = transactionDir dbdir (transactionId trans)
7383 context = concreteDatabaseContext trans
74− transDirExists <- doesDirectoryExist finalTransDir
84+ transDirExists <- doesDirectoryExist finalTransDir
7585 if not transDirExists then do
7686 --create sub directories
77− mapM_ createDirectory [tempTransDir, relvarsDir tempTransDir, incDepsDir tempTransDir, atomFuncsDir tempTransDir]
87+ mapM_ createDirectory [tempTransDir, relvarsDir tempTransDir, incDepsDir tempTransDir, atomFuncsDir tempTransDir, dbcFuncsDir tempTransDir]
7888 writeRelVars sync tempTransDir (relationVariables context)
7989 writeIncDeps sync tempTransDir (inclusionDependencies context)
8090 writeAtomFuncs sync tempTransDir (atomFunctions context)
91+ writeDBCFuncs sync tempTransDir (dbcFunctions context)
8192 writeTypeConstructorMapping sync tempTransDir (typeConstructorMapping context)
8293 writeSubschemas sync tempTransDir (subschemas trans)
8394 BS.writeFile (transactionInfoPath tempTransDir) (B.encode $ transactionInfo trans)
@@ -107,24 +118,83 @@ writeAtomFuncs :: DiskSync -> FilePath -> AtomFunctions -> IO ()
107118writeAtomFuncs sync transDir funcs = mapM_ (writeAtomFunc sync transDir) $ HS.toList funcs
108119
109120--all the atom functions are in one file (???)
110−readAtomFuncs :: FilePath -> IO (AtomFunctions)
111−readAtomFuncs transDir = do
121+readAtomFuncs :: FilePath -> Maybe ScriptSession -> IO AtomFunctions
122+readAtomFuncs transDir mScriptSession = do
112123 funcNames <- getDirectoryNames (atomFuncsDir transDir)
113− funcs <- mapM (readAtomFunc transDir) (map T.pack funcNames)
114− return $ HS.fromList funcs
124+ --only Haskell script functions can be serialized
125+ --we always return the pre-compiled functions
126+ funcs <- mapM (\name -> readAtomFunc transDir name mScriptSession precompiledAtomFunctions) (map T.pack funcNames)
127+ pure (HS.union precompiledAtomFunctions (HS.fromList funcs))
115128
116129--to write the atom functions, we really some bytecode to write (GHCi bytecode?)
117130writeAtomFunc :: DiskSync -> FilePath -> AtomFunction -> IO ()
118131writeAtomFunc sync transDir func = do
119132 let atomFuncPath = atomFuncsDir transDir </> T.unpack (atomFuncName func)
120− writeBSFileSync sync atomFuncPath BS.empty
133+ writeBSFileSync sync atomFuncPath (B.encode (atomFuncType func, atomFunctionScript func))
121134
122−readAtomFunc :: FilePath -> AtomFunctionName -> IO (AtomFunction)
123−readAtomFunc transDir funcName = do
135+--if the script session is enabled, compile the script, otherwise, hard error!
136+readAtomFunc :: FilePath -> AtomFunctionName -> Maybe ScriptSession -> AtomFunctions -> IO (AtomFunction)
137+readAtomFunc transDir funcName mScriptSession precompiledFuncs = do
124138 let atomFuncPath = atomFuncsDir transDir </> T.unpack funcName
125− _ <- BS.readFile atomFuncPath
126− return undefined
139+ (funcType, mFuncScript) <- liftM B.decode (BS.readFile atomFuncPath)
140+ case mFuncScript of
141+ --handle pre-compiled case- pull it from the precompiled list
142+ Nothing -> case atomFunctionForName funcName precompiledFuncs of
143+ --WARNING: possible landmine here if we remove a precompiled atom function in the future, then the transaction cannot be restored
144+ Left _ -> error ("expected precompiled atom function: " ++ T.unpack funcName)
145+ Right realFunc -> pure realFunc
146+ --handle a real Haskell scripted function- compile and load
147+ Just funcScript -> do
148+ case mScriptSession of
149+ Nothing -> error "attempted to read serialized AtomFunction without scripting enabled"
150+ Just scriptSession -> do
151+ --risk of GHC exception during compilation here
152+ eCompiledScript <- runGhc (Just libdir) $ do
153+ setSession (hscEnv scriptSession)
154+ compileScript (atomFunctionBodyType scriptSession) funcScript
155+ case eCompiledScript of
156+ Left err -> throwIO err
157+ Right compiledScript -> pure (AtomFunction { atomFuncName = funcName,
158+ atomFuncType = funcType,
159+ atomFuncBody = AtomFunctionBody (Just funcScript) compiledScript })
160+
161+writeDBCFuncs :: DiskSync -> FilePath -> DatabaseContextFunctions -> IO ()
162+writeDBCFuncs sync transDir funcs = mapM_ (writeDBCFunc sync transDir) (HS.toList funcs)
127163
164+writeDBCFunc :: DiskSync -> FilePath -> DatabaseContextFunction -> IO ()
165+writeDBCFunc sync transDir func = do
166+ let dbcFuncPath = dbcFuncsDir transDir </> T.unpack (dbcFuncName func)
167+ writeBSFileSync sync (traceShowId dbcFuncPath) (B.encode (dbcFuncType func, databaseContextFunctionScript func))
168+
169+readDBCFuncs :: FilePath -> Maybe ScriptSession -> IO DatabaseContextFunctions
170+readDBCFuncs transDir mScriptSession = do
171+ funcNames <- getDirectoryNames (dbcFuncsDir transDir)
172+ --only Haskell script functions can be serialized
173+ --we always return the pre-compiled functions
174+ funcs <- mapM (\name -> readDBCFunc transDir name mScriptSession precompiledDatabaseContextFunctions) (map T.pack funcNames)
175+ return $ HS.union basicDatabaseContextFunctions (HS.fromList funcs)
176+
177+readDBCFunc :: FilePath -> DatabaseContextFunctionName -> Maybe ScriptSession -> DatabaseContextFunctions -> IO DatabaseContextFunction
178+readDBCFunc transDir funcName mScriptSession precompiledFuncs = do
179+ let dbcFuncPath = dbcFuncsDir transDir </> T.unpack funcName
180+ (funcType, mFuncScript) <- liftM B.decode (BS.readFile dbcFuncPath)
181+ case mFuncScript of
182+ Nothing -> case databaseContextFunctionForName funcName precompiledFuncs of
183+ Left _ -> error ("expected precompiled dbc function: " ++ T.unpack funcName)
184+ Right realFunc -> pure realFunc --return precompiled function
185+ Just funcScript -> do
186+ case mScriptSession of
187+ Nothing -> error "attempted to read serialized AtomFunction without scripting enabled"
188+ Just scriptSession -> do
189+ eCompiledScript <- runGhc (Just libdir) $ do
190+ setSession (hscEnv scriptSession)
191+ compileScript (dbcFunctionBodyType scriptSession) funcScript
192+ case eCompiledScript of
193+ Left err -> throwIO err
194+ Right compiledScript -> pure (DatabaseContextFunction { dbcFuncName = funcName,
195+ dbcFuncType = funcType,
196+ dbcFuncBody = DatabaseContextFunctionBody (Just funcScript) compiledScript})
197+
128198writeIncDep :: DiskSync -> FilePath -> (IncDepName, InclusionDependency) -> IO ()
129199writeIncDep sync transDir (incDepName, incDep) = do
130200 writeBSFileSync sync (incDepsDir transDir </> T.unpack incDepName) $ B.encode incDep
src/lib/ProjectM36/TransactionGraph/Persist.hs+1−2
@@ -55,7 +55,6 @@ bootstrapDatabaseDir :: DiskSync -> FilePath -> TransactionGraph -> IO ()
5555bootstrapDatabaseDir sync dbdir bootstrapGraph = do
5656 createDirectory dbdir
5757 transactionGraphPersist sync dbdir bootstrapGraph
58− putStrLn "Bootstrapped DB."
5958
6059{-
6160incrementally updates an existing database directory
@@ -127,7 +126,7 @@ readTransactionIfNecessary dbdir transId mScriptSession graphIn = do
127126 --the transaction is already known and loaded- done
128127 return $ Right graphIn
129128 else do
130− trans <- readTransaction dbdir transId
129+ trans <- readTransaction dbdir transId mScriptSession
131130 case trans of
132131 Left err -> return $ Left err
133132 Right trans' -> return $ Right $ TransactionGraph (transactionHeadsForGraph graphIn) (S.insert trans' (transactionsForGraph graphIn))
test/TransactionGraph/Persist.hs+31−4
@@ -6,6 +6,8 @@ import ProjectM36.TransactionGraph.Persist
66import ProjectM36.TransactionGraph
77import ProjectM36.Transaction
88import ProjectM36.DateExamples
9+import ProjectM36.Client
10+import ProjectM36.Relation
911import System.IO.Temp
1012import System.Exit
1113import Data.Either
@@ -21,7 +23,9 @@ main = do
2123 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
2224
2325testList :: Test
24−testList = TestList [testBootstrapDB, testDBSimplePersistence]
26+testList = TestList [testBootstrapDB,
27+ testDBSimplePersistence,
28+ testFunctionPersistence]
2529
2630{- bootstrap a database, ensure that it can be read -}
2731testBootstrapDB :: Test
@@ -29,7 +33,7 @@ testBootstrapDB = TestCase $ withSystemTempDirectory "m36testdb" $ \tempdir -> d
2933 let dbdir = tempdir </> "dbdir"
3034 freshId <- nextRandom
3135 _ <- bootstrapDatabaseDir NoDiskSync dbdir (bootstrapTransactionGraph freshId dateExamples)
32− loadedGraph <- transactionGraphLoad dbdir emptyTransactionGraph
36+ loadedGraph <- transactionGraphLoad dbdir emptyTransactionGraph Nothing
3337 assertBool "transactionGraphLoad" $ isRight loadedGraph
3438
3539{- create a database with several transactions, ensure that all transactions can be read -}
@@ -55,11 +59,34 @@ testDBSimplePersistence = TestCase $ withSystemTempDirectory "m36testdb" $ \temp
5559 --persist the new graph
5660 transactionGraphPersist NoDiskSync dbdir graph'
5761 --reload the graph from the filesystem and confirm that the transaction is present
58− graphErr <- transactionGraphLoad dbdir emptyTransactionGraph
62+ graphErr <- transactionGraphLoad dbdir emptyTransactionGraph Nothing
5963 let mapEq graphArg = S.map transactionId (transactionsForGraph graphArg)
6064 case graphErr of
6165 Left err -> assertFailure (show err)
6266 Right graph'' -> assertBool "graph equality" (mapEq graph'' == mapEq graph')
6367
6468
65−
69+--only Haskell-scripted dbc and atom functions can be serialized
70+testFunctionPersistence :: Test
71+testFunctionPersistence = TestCase $ withSystemTempDirectory "m36testdb" $ \tempdir -> do
72+ let dbdir = tempdir </> "dbdir"
73+ connInfo = InProcessConnectionInfo (MinimalPersistence dbdir) emptyNotificationCallback []
74+ Right conn <- connectProjectM36 connInfo
75+ Right sess <- createSessionAtHead "master" conn
76+ let intTCons = PrimitiveTypeConstructor "Int" IntAtomType
77+ addfunc = AddAtomFunction "testdisk" [
78+ intTCons,
79+ ADTypeConstructor "Either" [TypeConstructorArg (ADTypeConstructor "AtomFunctionError" []),
80+ TypeConstructorArg intTCons]] "(\\(x:_) -> pure x) :: [Atom] -> Either AtomFunctionError Atom"
81+ Nothing <- executeDatabaseContextIOExpr sess conn addfunc
82+ Nothing <- commit sess conn
83+ close conn
84+ --re-open the connection to reload the graph
85+ Right conn2 <- connectProjectM36 connInfo
86+ Right sess2 <- createSessionAtHead "master" conn2
87+
88+ res <- executeRelationalExpr sess2 conn2 (MakeRelationFromExprs Nothing [TupleExpr (M.singleton "a" (FunctionAtomExpr "testdisk" [NakedAtomExpr (IntAtom 3)] ()))])
89+ let expectedRel = mkRelationFromList (attributesFromList [Attribute "a" IntAtomType]) [[IntAtom 3]]
90+ assertEqual "testdisk dbc function run" expectedRel res
91+
92+