on master help sign in
empty commit detection completion

all tests passing

commit261654cb2ff460b06f3d68c0da3d3600f3996abc authorAgentM <agentm@themactionfaction.com> dateTue 20 Jun 2017, 15:51 (9 years ago) parent0fd3b52
10 files changed+105−63
project-m36.cabal+2−2 src/bin/benchmark/bigrel.hs+1−1 src/lib/ProjectM36/Client.hs+9−9 src/lib/ProjectM36/RelationalExpression.hs+48−31 test/IsomorphicSchema.hs+12−11 test/Relation/Basic.hs+2−1 test/Relation/StaticOptimizer.hs+6−3 test/TransactionGraph/Merge.hs+2−2 test/TransactionGraph/Persist.hs+1−1 test/TutorialD/Interpreter.hs+22−2
project-m36.cabal+2−2
@@ -307,7 +307,7 @@ Test-Suite test-relation
307307 Default-Extensions: OverloadedStrings
308308 type: exitcode-stdio-1.0
309309 main-is: test/Relation/Basic.hs
310− Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, stm-containers, project-m36 == 0.1
310+ Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, stm-containers, project-m36 == 0.1, transformers
311311 GHC-Options: -Wall
312312
313313Test-Suite test-static-optimizer
@@ -315,7 +315,7 @@ Test-Suite test-static-optimizer
315315 Default-Extensions: OverloadedStrings
316316 type: exitcode-stdio-1.0
317317 main-is: test/Relation/StaticOptimizer.hs
318− Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, stm-containers, project-m36 == 0.1
318+ Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, vector-binary-instances, time, hashable-time, bytestring, uuid, stm, deepseq, deepseq-generics, binary, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, stm-containers, project-m36 == 0.1, transformers
319319 GHC-Options: -Wall
320320 CPP-Options: -DPROJECTM36_VERSION="v0.1"
321321
src/bin/benchmark/bigrel.hs+1−1
@@ -59,7 +59,7 @@ matrixRun (BigrelArgs attributeCount tupleCount tutd) = do
5959 putStrLn "Done."
6060 else do
6161 let setx = Assign "x" (ExistingRelation (force rel))
62− context = execState (evalDatabaseContextExpr setx) dateExamples
62+ (context,_,_) = execState (evalDatabaseContextExpr setx) (freshDatabaseState dateExamples)
6363 interpreted = interpretDatabaseContextExpr context tutd
6464 --plan = interpretRODatabaseContextOp context $ ":showplan " ++ tutd
6565 --displayOpResult plan
src/lib/ProjectM36/Client.hs+9−9
@@ -641,8 +641,6 @@ executeGraphExpr sessionId (InProcessConnection conf) graphExpr = excMaybe $ do
641641 case eSession of
642642 Left err -> pure (Left err)
643643 Right session -> do
644− if not (isDirty session) then pure (Left EmptyCommitError)
645− else do
646644 let mScriptSession = ipScriptSession conf
647645 dbdir = case strat of
648646 MinimalPersistence x -> x
@@ -661,13 +659,15 @@ executeGraphExpr sessionId (InProcessConnection conf) graphExpr = excMaybe $ do
661659 Right newGraph -> do
662660 --handle commit
663661 if graphExpr == Commit then
664− case transactionForId (Sess.parentId session) oldGraph of
665− Left err -> pure $ Left err
666− Right previousTrans -> do
667− (evaldNots, nodes) <- executeCommitExprSTM_ (Trans.concreteDatabaseContext previousTrans) (Sess.concreteDatabaseContext session) clientNodes
668− nodesToNotify <- toList (STMSet.stream nodes)
669− pure $ Right (evaldNots, nodesToNotify, newGraph)
670− else
662+ if not (isDirty session) then pure (Left EmptyCommitError)
663+ else do
664+ case transactionForId (Sess.parentId session) oldGraph of
665+ Left err -> pure $ Left err
666+ Right previousTrans -> do
667+ (evaldNots, nodes) <- executeCommitExprSTM_ (Trans.concreteDatabaseContext previousTrans) (Sess.concreteDatabaseContext session) clientNodes
668+ nodesToNotify <- toList (STMSet.stream nodes)
669+ pure $ Right (evaldNots, nodesToNotify, newGraph)
670+ else
671671 pure $ Right (M.empty, [], newGraph)
672672 case manip of
673673 Left err -> return $ Just err
src/lib/ProjectM36/RelationalExpression.hs+48−31
@@ -91,18 +91,9 @@ getStateContext = do
9191
9292putStateContext :: DatabaseContext -> DatabaseState ()
9393putStateContext ctx = do
94− (_, accum, flag) <- get
95− put (ctx, accum, flag)
94+ (_, accum, _) <- get
95+ put (ctx, accum, True)
9696
97−-- indicates that the underlying database context has changed and warrants a new commit
98−putDirtyFlag :: DatabaseState ()
99−putDirtyFlag = do
100− (ctx, accum, flag) <- get
101− if flag then
102− pure ()
103− else
104− put (ctx, accum, True)
105−
10697type RelationalExprState a = Reader RelationalExprStateElems a
10798
10899stateElemsContext :: RelationalExprStateElems -> DatabaseContext
@@ -248,18 +239,20 @@ setRelVar relVarName rel = do
248239 Just err -> return $ Just err
249240 Nothing -> do
250241 putStateContext potentialContext
251− putDirtyFlag
252242 return Nothing
253243
254244-- it is not an error to delete a relvar which does not exist, just like it is not an error to insert a pre-existing tuple into a relation
255245deleteRelVar :: RelVarName -> DatabaseState (Maybe RelationalError)
256246deleteRelVar relVarName = do
257247 currContext <- getStateContext
258− let newRelVars = M.delete relVarName (relationVariables currContext)
259− newContext = currContext { relationVariables = newRelVars }
260− putStateContext newContext
261− putDirtyFlag
262− pure Nothing
248+ let relVars = relationVariables currContext
249+ if M.notMember relVarName relVars then
250+ pure Nothing
251+ else do
252+ let newRelVars = M.delete relVarName relVars
253+ newContext = currContext { relationVariables = newRelVars }
254+ putStateContext newContext
255+ pure Nothing
263256
264257evalDatabaseContextExpr :: DatabaseContextExpr -> DatabaseState (Maybe RelationalError)
265258evalDatabaseContextExpr NoOperation = pure Nothing
@@ -272,7 +265,7 @@ evalDatabaseContextExpr (Define relVarName attrExprs) = do
272265 err:_ -> pure (Just err)
273266 [] -> case M.member relVarName relvars of
274267 True -> return (Just (RelVarAlreadyDefinedError relVarName))
275− False -> setRelVar relVarName emptyRelation >> putDirtyFlag >> pure Nothing
268+ False -> setRelVar relVarName emptyRelation >> pure Nothing
276269 where
277270 attrs = A.attributesFromList (rights eAttrs)
278271 emptyRelation = Relation attrs emptyTupleSet
@@ -297,14 +290,34 @@ evalDatabaseContextExpr (Assign relVarName expr) = do
297290 else
298291 return $ Just (RelVarAssignmentTypeMismatchError expectedAttributes foundAttributes)
299292
300−evalDatabaseContextExpr (Insert relVarName relExpr) = evalDatabaseContextExpr $ Assign relVarName (Union relExpr (RelationVariable relVarName ()))
293+evalDatabaseContextExpr (Insert relVarName relExpr) = do
294+ context <- getStateContext
295+ let unionexp = Union relExpr rv
296+ rv = RelationVariable relVarName ()
297+ unioned = runReader (evalRelationalExpr unionexp) (RelationalExprStateElems context)
298+ origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
299+ case unioned of
300+ Left err -> pure (Just err)
301+ Right unioned' -> case origRel of
302+ Left err -> pure (Just err)
303+ Right origRel' -> if cardinality unioned' == cardinality origRel' then --no tuples actually inserted
304+ pure Nothing
305+ else
306+ evalDatabaseContextExpr $ Assign relVarName (ExistingRelation unioned')
301307
302308evalDatabaseContextExpr (Delete relVarName predicate) = do
303309 context <- getStateContext
304− let updatedRel = runReader (evalRelationalExpr (Restrict (NotPredicate predicate) (RelationVariable relVarName ()))) (RelationalExprStateElems context)
310+ let rv = RelationVariable relVarName ()
311+ let updatedRel = runReader (evalRelationalExpr (Restrict (NotPredicate predicate) rv)) (RelationalExprStateElems context)
312+ origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
305313 case updatedRel of
306− Left err -> return $ Just err
307− Right rel -> setRelVar relVarName rel
314+ Left err -> pure (Just err)
315+ Right updatedRel' -> case origRel of
316+ Left err -> pure (Just err)
317+ Right origRel' -> if cardinality origRel' == cardinality updatedRel' then
318+ pure Nothing
319+ else
320+ setRelVar relVarName updatedRel'
308321
309322--union of restricted+updated portion and the unrestricted+unupdated portion
310323evalDatabaseContextExpr (Update relVarName atomExprMap restrictionPredicateExpr) = do
@@ -316,15 +329,19 @@ evalDatabaseContextExpr (Update relVarName atomExprMap restrictionPredicateExpr)
316329 case runReader (predicateRestrictionFilter (attributes rel) restrictionPredicateExpr) (RelationalExprStateElems context) of
317330 Left err -> return $ Just err
318331 Right predicateFunc -> do
319− case makeUpdatedRel rel of
320− Left err -> return $ Just err
321− Right updatedRel -> setRelVar relVarName updatedRel
322− where
323− makeUpdatedRel relin = do
324− restrictedPortion <- restrict predicateFunc relin
325− unrestrictedPortion <- restrict (not . predicateFunc) relin
326− updatedPortion <- relMap (updateTupleWithAtomExprs atomExprMap context) restrictedPortion
327− union updatedPortion unrestrictedPortion
332+ let ret = do
333+ restrictedPortion <- restrict predicateFunc rel
334+ if cardinality restrictedPortion == Finite 0 then
335+ pure Nothing
336+ else do
337+ unrestrictedPortion <- restrict (not . predicateFunc) rel
338+ updatedPortion <- relMap (updateTupleWithAtomExprs atomExprMap context) restrictedPortion
339+ updatedRel <- union updatedPortion unrestrictedPortion
340+ pure (Just updatedRel)
341+ case ret of
342+ Left err -> pure (Just err)
343+ Right Nothing -> pure Nothing
344+ Right (Just updatedRel) -> setRelVar relVarName updatedRel
328345
329346evalDatabaseContextExpr (AddInclusionDependency newDepName newDep) = do
330347 currContext <- getStateContext
test/IsomorphicSchema.hs+12−11
@@ -10,6 +10,7 @@ import System.Exit
1010import qualified Data.Map as M
1111import qualified Data.Set as S
1212import Control.Monad.State
13+import Control.Monad.Trans.Reader
1314
1415testList :: Test
1516testList = TestList [testIsoRename, testIsoRestrict, testIsoUnion, testSchemaValidation]
@@ -57,7 +58,7 @@ testIsoRename = TestCase $ do
5758 IsoRename "department" "department"]
5859 unionExpr = Union (RelationVariable "emp" ()) (RelationVariable "department" ())
5960 relExpr <- assertEither (applyRelationalExprSchemaIsomorphs isomorphsAtoB unionExpr)
60− let relResult = evalState (evalRelationalExpr relExpr) schemaA
61+ let relResult = runReader (evalRelationalExpr relExpr) schemaA
6162 assertEqual "employee relation morph" (Right relationTrue) relResult
6263
6364testIsoRestrict :: Test
@@ -71,8 +72,8 @@ testIsoRestrict = TestCase $ do
7172 [TextAtom "Cindy", TextAtom "Steve"],
7273 [TextAtom "Sam", TextAtom "Steve"]]
7374 let predicate = AttributeEqualityPredicate "boss" (NakedAtomExpr (TextAtom ""))
74− bossRel <- assertEither $ evalState (evalRelationalExpr (Restrict predicate (ExistingRelation emprel))) (mkRelationalExprState DBC.empty)
75− nonBossRel <- assertEither $ evalState (evalRelationalExpr (Restrict (NotPredicate predicate) (ExistingRelation emprel))) (mkRelationalExprState DBC.empty)
75+ bossRel <- assertEither $ runReader (evalRelationalExpr (Restrict predicate (ExistingRelation emprel))) (mkRelationalExprState DBC.empty)
76+ nonBossRel <- assertEither $ runReader (evalRelationalExpr (Restrict (NotPredicate predicate) (ExistingRelation emprel))) (mkRelationalExprState DBC.empty)
7677
7778 let schemaA = mkRelationalExprState DBC.empty {
7879 relationVariables = M.fromList [("nonboss", nonBossRel),
@@ -85,18 +86,18 @@ testIsoRestrict = TestCase $ do
8586 employeeq = RelationVariable "employee" ()
8687 unionq = Union bossq nonbossq
8788 empExpr <- assertEither (applyRelationalExprSchemaIsomorphs isomorphsAtoB employeeq)
88− let empResult = evalState (evalRelationalExpr empExpr) schemaA
89− unionResult = evalState (evalRelationalExpr unionq) schemaA
89+ let empResult = runReader (evalRelationalExpr empExpr) schemaA
90+ unionResult = runReader (evalRelationalExpr unionq) schemaA
9091 assertEqual "boss/nonboss isorestrict" unionResult empResult
9192 --execute database context expr
9293 bobRel <- assertEither (mkRelationFromList empattrs [[TextAtom "Bob", TextAtom ""]])
9394 let schemaBInsertExpr = Insert "employee" (ExistingRelation bobRel)
9495 schemaBInsertExpr' <- assertEither (processDatabaseContextExprInSchema (Schema isomorphsAtoB) schemaBInsertExpr)
95− let postInsertContext = execState (evalDatabaseContextExpr schemaBInsertExpr') (stateContext schemaA)
96− expectedRel = evalState (evalRelationalExpr (Union (RelationVariable "boss" ()) (RelationVariable "nonboss" ()))) (mkRelationalExprState postInsertContext)
96+ let (postInsertContext,_,_) = execState (evalDatabaseContextExpr schemaBInsertExpr') (freshDatabaseState (stateElemsContext schemaA))
97+ expectedRel = runReader (evalRelationalExpr (Union (RelationVariable "boss" ()) (RelationVariable "nonboss" ()))) (mkRelationalExprState postInsertContext)
9798 --execute the expression against the schema and compare against the base context
9899 processedExpr <- assertEither (processRelationalExprInSchema (Schema isomorphsAtoB) (RelationVariable "employee" ()))
99− let processedRel = evalState (evalRelationalExpr processedExpr) (mkRelationalExprState postInsertContext)
100+ let processedRel = runReader (evalRelationalExpr processedExpr) (mkRelationalExprState postInsertContext)
100101 assertEqual "insert bob boss" expectedRel processedRel
101102
102103testIsoUnion :: Test
@@ -116,9 +117,9 @@ testIsoUnion = TestCase $ do
116117 splitIsomorphs = [IsoUnion ("lowpower", "highpower") splitPredicate "motor",
117118 IsoRename "true" "true",
118119 IsoRename "false" "false"]
119− lowmotors = evalState (evalRelationalExpr (Restrict splitPredicate (RelationVariable "motor" ()))) baseSchema
120− highmotors = evalState (evalRelationalExpr (Restrict (NotPredicate splitPredicate) (RelationVariable "motor" ()))) baseSchema
121− relResult expr = evalState (evalRelationalExpr expr) baseSchema
120+ lowmotors = runReader (evalRelationalExpr (Restrict splitPredicate (RelationVariable "motor" ()))) baseSchema
121+ highmotors = runReader (evalRelationalExpr (Restrict (NotPredicate splitPredicate) (RelationVariable "motor" ()))) baseSchema
122+ relResult expr = runReader (evalRelationalExpr expr) baseSchema
122123 lowpowerExpr <- assertEither (processRelationalExprInSchema (Schema splitIsomorphs) (RelationVariable "lowpower" ()))
123124 lowpowerRel <- assertEither (relResult lowpowerExpr)
124125 highpowerExpr <- assertEither (processRelationalExprInSchema (Schema splitIsomorphs) (RelationVariable "highpower" ()))
test/Relation/Basic.hs+2−1
@@ -8,6 +8,7 @@ import ProjectM36.RelationalExpression
88import ProjectM36.Tuple
99import qualified ProjectM36.DatabaseContext as DBC
1010import Control.Monad.State
11+import Control.Monad.Trans.Reader
1112import qualified ProjectM36.Attribute as A
1213import qualified Data.Map as M
1314import qualified Data.Vector as V
@@ -90,6 +91,6 @@ testMkRelation1 = TestCase $ assertEqual "key mismatch" expectedError (mkRelatio
9091testMkRelationFromExprsBadAttrs :: Test
9192testMkRelationFromExprsBadAttrs = TestCase $ do
9293 let context = DBC.empty
93− case evalState (evalRelationalExpr (MakeRelationFromExprs (Just [AttributeAndTypeNameExpr "badAttr1" (PrimitiveTypeConstructor "Int" IntAtomType) ()]) [TupleExpr (M.singleton "badAttr2" (NakedAtomExpr (IntAtom 1)))])) (mkRelationalExprState context) of
94+ case runReader (evalRelationalExpr (MakeRelationFromExprs (Just [AttributeAndTypeNameExpr "badAttr1" (PrimitiveTypeConstructor "Int" IntAtomType) ()]) [TupleExpr (M.singleton "badAttr2" (NakedAtomExpr (IntAtom 1)))])) (mkRelationalExprState context) of
9495 Left err -> assertEqual "tuple type mismatch" (TupleAttributeTypeMismatchError (A.attributesFromList [Attribute "badAttr2" IntAtomType])) err
9596 Right _ -> assertFailure "expected tuple type mismatch"
test/Relation/StaticOptimizer.hs+6−3
@@ -6,6 +6,7 @@ import ProjectM36.TupleSet
66import ProjectM36.StaticOptimizer
77import System.Exit
88import Control.Monad.State
9+import Control.Monad.Trans.Reader
910import Test.HUnit
1011
1112main :: IO ()
@@ -14,16 +15,18 @@ main = do
1415 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
1516 where
1617 tests = relationOptTests ++ databaseOptTests
17− optTest optfunc estate testparams = map (\(name, expr, unopt) -> TestCase $ assertEqual name expr (evalState (optfunc unopt) estate)) $ testparams
18+ optDBCTest = map (\(name, expr, unopt) -> TestCase $ assertEqual name expr (evalState (applyStaticDatabaseOptimization unopt) (freshDatabaseState dateExamples)))
1819 relvar nam = RelationVariable nam ()
19− relationOptTests = optTest applyStaticRelationalOptimization (mkRelationalExprState dateExamples) [
20+ startState = mkRelationalExprState dateExamples
21+ optRelTest = map (\(name, expr, unopt) -> TestCase $ assertEqual name expr (runReader (applyStaticRelationalOptimization unopt) startState))
22+ relationOptTests = optRelTest [
2023 ("StaticProject", Right $ relvar "s", Project (AttributeNames (attributeNames suppliersRel)) (relvar "s")),
2124 ("StaticUnion", Right $ relvar "s", Union (relvar "s") (relvar "s")),
2225 ("StaticJoin", Right $ relvar "s", Join (relvar "s") (relvar "s")),
2326 ("StaticRestrictTrue", Right $ relvar "s", Restrict TruePredicate (relvar "s")),
2427 ("StaticRestrictFalse", Right $ MakeStaticRelation (attributes suppliersRel) emptyTupleSet, Restrict (NotPredicate TruePredicate) (relvar "s"))
2528 ]
26− databaseOptTests = optTest applyStaticDatabaseOptimization dateExamples [
29+ databaseOptTests = optDBCTest [
2730 ("StaticAssign",
2831 Right $ Assign "z" (relvar "s"),
2932 Assign "z" (Restrict TruePredicate (relvar "s"))
test/TransactionGraph/Merge.hs+2−2
@@ -113,7 +113,7 @@ testSubGraphToFirstAncestorMoreTransactions = TestCase $ do
113113
114114 -- add another relvar to branchB
115115 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
116− updatedBranchBContext <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (concreteDatabaseContext branchBTrans) of
116+ (updatedBranchBContext,_,_) <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (freshDatabaseState (concreteDatabaseContext branchBTrans)) of
117117 (Just err, _) -> assertFailure (show err) >> undefined
118118 (Nothing, context) -> pure context
119119 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty) updatedBranchBContext) graph
@@ -140,7 +140,7 @@ testSelectedBranchMerge = TestCase $ do
140140
141141 -- add another relvar to branchB
142142 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
143− updatedBranchBContext <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (concreteDatabaseContext branchBTrans) of
143+ (updatedBranchBContext,_,_) <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (freshDatabaseState (concreteDatabaseContext branchBTrans)) of
144144 (Just err, _) -> assertFailure (show err) >> undefined
145145 (Nothing, context) -> pure context
146146 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty) updatedBranchBContext) graph
test/TransactionGraph/Persist.hs+1−1
@@ -50,7 +50,7 @@ testDBSimplePersistence = TestCase $ withSystemTempDirectory "m36testdb" $ \temp
5050 Left err -> assertFailure (show err)
5151 Right context' -> do
5252 freshId' <- nextRandom
53− let newdiscon = DisconnectedTransaction (transactionId headTrans) (Schemas context' M.empty)
53+ let newdiscon = DisconnectedTransaction (transactionId headTrans) (Schemas context' M.empty) True
5454 addTrans = addDisconnectedTransaction freshId' "master" newdiscon graph
5555 --add a transaction to the graph
5656 case addTrans of
test/TutorialD/Interpreter.hs+22−2
@@ -33,7 +33,7 @@ main = do
3333 tcounts <- runTestTT (TestList tests)
3434 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
3535 where
36− tests = map (\(tutd, expected) -> TestCase $ assertTutdEqual basicDatabaseContext expected tutd) simpleRelTests ++ map (\(tutd, expected) -> TestCase $ assertTutdEqual dateExamples expected tutd) dateExampleRelTests ++ [transactionGraphBasicTest, transactionGraphAddCommitTest, transactionRollbackTest, transactionJumpTest, transactionBranchTest, simpleJoinTest, testNotification, testTypeConstructors, testMergeTransactions, testComments, testTransGraphRelationalExpr, failJoinTest, testMultiAttributeRename, testSchemaExpr, testRelationalExprStateTupleElems, testFunctionalDependencies]
36+ tests = map (\(tutd, expected) -> TestCase $ assertTutdEqual basicDatabaseContext expected tutd) simpleRelTests ++ map (\(tutd, expected) -> TestCase $ assertTutdEqual dateExamples expected tutd) dateExampleRelTests ++ [transactionGraphBasicTest, transactionGraphAddCommitTest, transactionRollbackTest, transactionJumpTest, transactionBranchTest, simpleJoinTest, testNotification, testTypeConstructors, testMergeTransactions, testComments, testTransGraphRelationalExpr, failJoinTest, testMultiAttributeRename, testSchemaExpr, testRelationalExprStateTupleElems, testFunctionalDependencies, testEmptyCommits]
3737 simpleRelTests = [("x:=true", Right relationTrue),
3838 ("x:=false", Right relationFalse),
3939 ("x:=true union false", Right relationTrue),
@@ -184,7 +184,7 @@ transactionRollbackTest = TestCase $ do
184184transactionJumpTest :: Test
185185transactionJumpTest = TestCase $ do
186186 (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback
187− (DisconnectedTransaction firstUUID _) <- disconnectedTransaction_ sessionId dbconn
187+ (DisconnectedTransaction firstUUID _ _) <- disconnectedTransaction_ sessionId dbconn
188188 maybeErr <- executeDatabaseContextExpr sessionId dbconn (Assign "x" (RelationVariable "s" ()))
189189 case maybeErr of
190190 Just err -> assertFailure (show err)
@@ -304,6 +304,7 @@ testTransGraphRelationalExpr :: Test
304304testTransGraphRelationalExpr = TestCase $ do
305305 (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback
306306 mapM_ (executeTutorialD sessionId dbconn) [
307+ "x:=s", --dud relvar so that the commit isn't empty
307308 ":commit",
308309 ":branch testbranch",
309310 "insert s relation{tuple{city \"Boston\", s# \"S9\", sname \"Smithers\", status 50}}",
@@ -398,3 +399,22 @@ testFunctionalDependencies = TestCase $ do
398399 let expectedError = "InclusionDependencyCheckError \"sname_status_A\""
399400 expectTutorialDErr sessionId dbconn (T.isPrefixOf expectedError) "insert s relation{tuple{city \"Boston\", s# \"S7\", sname \"Jones\", status 20}}"
400401
402+testEmptyCommits :: Test
403+testEmptyCommits = TestCase $ do
404+ (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback
405+ err <- executeGraphExpr sessionId dbconn Commit
406+ assertEqual "no updates empty commit" (Just EmptyCommitError) err
407+ --insert no tuples
408+ Nothing <- executeDatabaseContextExpr sessionId dbconn (Insert "s" (RelationVariable "s" ()))
409+ err <- executeGraphExpr sessionId dbconn Commit
410+ assertEqual "empty insert empty commit" (Just EmptyCommitError) err
411+ --update no tuples
412+ Nothing <- executeDatabaseContextExpr sessionId dbconn (Update "s" (M.singleton "sname" (NakedAtomExpr (TextAtom "Bob"))) (AttributeEqualityPredicate "sname" (NakedAtomExpr (TextAtom "Mike"))))
413+ err <- executeGraphExpr sessionId dbconn Commit
414+ assertEqual "empty update empty commit" (Just EmptyCommitError) err
415+ --delete no tuples
416+ Nothing <- executeDatabaseContextExpr sessionId dbconn (Delete "s" (AttributeEqualityPredicate "sname" (NakedAtomExpr (TextAtom "Mike"))))
417+ err <- executeGraphExpr sessionId dbconn Commit
418+ assertEqual "empty delete empty commit" (Just EmptyCommitError) err
419+
420+