on master help sign in
WIP implement inclusion dependency

-need to fix circular module dependency between RelationalExpression and StaticOptimmizer

commit8ea9b8c3f8d8b9f2fcd32b1280a7c736a77e7973 authorAgentM <agentm@themactionfaction.com> dateTue 28 Feb 2017, 22:56 (10 years ago) parent02b60f1
5 files changed+166−9
src/lib/ProjectM36/Base.hs+1−1 src/lib/ProjectM36/InclusionDependency.hs+1−1 src/lib/ProjectM36/RelationalExpression.hs+63−4 src/lib/ProjectM36/StaticOptimizer.hs+84−2 test/Relation/StaticOptimizer.hs+17−1
src/lib/ProjectM36/Base.hs+1−1
@@ -1,4 +1,4 @@
1−{-# LANGUAGE ExistentialQuantification,BangPatterns,DeriveGeneric,DeriveAnyClass, TypeSynonymInstances, FlexibleInstances #-}
1+{-# LANGUAGE ExistentialQuantification,BangPatterns,DeriveGeneric,DeriveAnyClass, TypeSynonymInstances, FlexibleInstances, DeriveFoldable #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
33module ProjectM36.Base where
44import qualified Data.Map as M
src/lib/ProjectM36/InclusionDependency.hs+1−1
@@ -5,6 +5,7 @@ import ProjectM36.Error
55import ProjectM36.Relation
66import qualified Data.Map as M
77import qualified Data.Text as T
8+import qualified Data.Set as S
89
910inclusionDependenciesAsRelation :: InclusionDependencies -> Either RelationalError Relation
1011inclusionDependenciesAsRelation incDeps = do
@@ -17,4 +18,3 @@ inclusionDependenciesAsRelation incDeps = do
1718 incDepAsAtoms (name, (InclusionDependency exprA exprB)) = [TextAtom name,
1819 TextAtom (T.pack (show exprA)),
1920 TextAtom (T.pack (show exprB))]
20−
src/lib/ProjectM36/RelationalExpression.hs+63−4
@@ -9,9 +9,11 @@ import ProjectM36.AtomType
99import ProjectM36.AtomFunctionBody
1010import ProjectM36.DataTypes.Primitive
1111import ProjectM36.AtomFunction
12+import ProjectM36.StaticOptimizer
1213import qualified ProjectM36.Attribute as A
1314import qualified Data.Map as M
1415import qualified Data.HashSet as HS
16+import qualified Data.Set as S
1517import Control.Monad.State hiding (join)
1618import Control.Exception
1719import Data.Maybe
@@ -25,6 +27,8 @@ import Control.Monad.Trans.Except
2527import GHC
2628import GHC.Paths
2729
30+import Debug.Trace
31+
2832-- we need to pass around a higher level RelationTuple and Attributes in order to solve #52
2933data RelationalExprStateElems = RelationalExprStateTupleElems DatabaseContext RelationTuple | -- used when fully evaluating a relexpr
3034 RelationalExprStateAttrsElems DatabaseContext Attributes | --used when evaluating the type of a relexpr
@@ -420,11 +424,14 @@ updateTupleWithAtomExprs exprMap context tupIn = do
420424
421425--run verification on all constraints
422426checkConstraints :: DatabaseContext -> Maybe RelationalError
423−checkConstraints context = case failures of
424− [] -> Nothing
425− l:_ -> Just l
427+checkConstraints context = case optimizedIncDepMap of
428+ Left err -> Just err
429+ Right filteredIncDepMap -> case failures filteredIncDepMap of
430+ [] -> Nothing
431+ l -> Just (MultipleErrors l)
426432 where
427− failures = M.elems $ M.mapMaybeWithKey checkIncDep deps
433+ optimizedIncDepMap = filterInclusionDependenciesForValidation deps
434+ failures incDeps = M.elems $ M.mapMaybeWithKey checkIncDep incDeps
428435 deps = inclusionDependencies context
429436 eval expr = evalState (evalRelationalExpr expr) (RelationalExprStateElems context)
430437 checkIncDep depName (InclusionDependency subsetExpr supersetExpr) = do
@@ -688,3 +695,55 @@ evalTupleExpr attrs (TupleExpr tupMap) = runExceptT $ do
688695 _ <- either throwE pure (validateTuple tup' tConss)
689696 pure tup'
690697
698+ -- | Return all relation variable names mentioned in the relational expression. This is used by the constraint static optimizer to determine which relvars are relevant.
699+relationVariableNames :: Show a => RelationalExprBase a -> S.Set RelVarName
700+relationVariableNames relExpr = foldRelationalExprs (\names re -> case re of
701+ RelationVariable name _ -> S.insert name names
702+ _ -> names) S.empty relExpr
703+
704+-- | Return all the unit RelationalExprs. This is useful, for example, for finding all mentions of relation
705+foldRelationalExprs :: (b -> RelationalExprBase a -> b) -> b -> RelationalExprBase a -> b
706+foldRelationalExprs func accum expr = case expr of
707+ e@(MakeRelationFromExprs _ tupleExprs) -> foldl func accum (e : concatMap tupleExprRelationalExprs tupleExprs)
708+ e@(MakeStaticRelation _ _) -> folder e []
709+ e@(ExistingRelation _) -> folder e []
710+ e@(RelationVariable _ _) -> folder e []
711+ e@(Project _ expr2) -> folder e [expr2]
712+ e@(Union exprA exprB) -> folder e [exprA, exprB]
713+ e@(Join exprA exprB) -> folder e [exprA, exprB]
714+ e@(Rename _ _ expr2) -> folder e [expr2]
715+ e@(Difference exprA exprB) -> folder e [exprA, exprB]
716+ e@(Group _ _ expr2) -> folder e [expr2]
717+ e@(Ungroup _ expr2) -> folder e [expr2]
718+ e@(Restrict predi expr2) -> folder e (expr2 : restrictionPredicateRelationalExprs predi)
719+ e@(Equals exprA exprB) -> folder e [exprA, exprB]
720+ e@(NotEquals exprA exprB) -> folder e [exprA, exprB]
721+ e@(Extend extendExpr expr2) -> folder e (expr2 : extendTupleRelationalExprs extendExpr)
722+ where
723+ folder e exprs = func (foldl (foldRelationalExprs func) accum exprs) e
724+
725+-- | Scan tupleExpr to return RelationExprs.
726+tupleExprRelationalExprs :: TupleExprBase a -> [RelationalExprBase a]
727+tupleExprRelationalExprs (TupleExpr amap) = concat (map atomExprRelationalExprs (M.elems amap))
728+
729+atomExprRelationalExprs :: AtomExprBase a -> [RelationalExprBase a]
730+atomExprRelationalExprs (FunctionAtomExpr _ exprs _) = concatMap atomExprRelationalExprs exprs
731+atomExprRelationalExprs (RelationAtomExpr expr) = [expr]
732+atomExprRelationalExprs (ConstructedAtomExpr _ exprs _) = concatMap atomExprRelationalExprs exprs
733+atomExprRelationalExprs _ = []
734+
735+restrictionPredicateRelationalExprs :: RestrictionPredicateExprBase a -> [RelationalExprBase a]
736+restrictionPredicateRelationalExprs predi = case predi of
737+ TruePredicate -> []
738+ AndPredicate exprA exprB -> restrictionPredicateRelationalExprs exprA ++ restrictionPredicateRelationalExprs exprB
739+ OrPredicate exprA exprB -> restrictionPredicateRelationalExprs exprA ++ restrictionPredicateRelationalExprs exprB
740+ NotPredicate expr -> restrictionPredicateRelationalExprs expr
741+ RelationalExprPredicate relExpr -> foldRelationalExprs (\acc expr -> expr : acc) [] relExpr
742+ AtomExprPredicate atomExpr -> atomExprRelationalExprs atomExpr
743+ AttributeEqualityPredicate _ atomExpr -> atomExprRelationalExprs atomExpr
744+
745+extendTupleRelationalExprs :: ExtendTupleExprBase a -> [RelationalExprBase a]
746+extendTupleRelationalExprs (AttributeExtendTupleExpr _ atomExpr) = atomExprRelationalExprs atomExpr
747+
748+relvarReferences :: InclusionDependency -> S.Set RelVarName
749+relvarReferences (InclusionDependency super sub) = S.union (relationVariableNames super) (relationVariableNames sub)
src/lib/ProjectM36/StaticOptimizer.hs+84−2
@@ -1,12 +1,15 @@
11module ProjectM36.StaticOptimizer where
22import ProjectM36.Base
3−import ProjectM36.RelationalExpression
3+--import ProjectM36.RelationalExpression
44import ProjectM36.Relation
55import ProjectM36.Error
6+import ProjectM36.InclusionDependency
67import qualified ProjectM36.AttributeNames as AS
78import ProjectM36.TupleSet
89import Control.Monad.State hiding (join)
910import Data.Either (rights, lefts)
11+import qualified Data.Set as S
12+import qualified Data.Map as M
1013
1114-- the static optimizer performs optimizations which need not take any specific-relation statistics into account
1215-- apply optimizations which merely remove steps to become no-ops: example: projection of a relation across all of its attributes => original relation
@@ -183,4 +186,83 @@ applyStaticDatabaseOptimization (MultipleExpr exprs) = do
183186 --restore original context
184187
185188applyStaticPredicateOptimization :: RestrictionPredicateExpr -> RelationalExprState (Either RelationalError RestrictionPredicateExpr)
186−applyStaticPredicateOptimization predicate = return $ Right predicate
189+applyStaticPredicateOptimization predicate = return $ Right predicate
190+
191+-- | Potentially optimize away constraints which need not be checked- they are tautologically valid because the update cannot possibly violate the constraint.
192+data Validation = NoValidationNeeded |
193+ ValidationNeeded |
194+ Violated --sometimes, we statically know that the inclusion dependency will be violated
195+ deriving (Eq, Show, Ord)
196+
197+filterInclusionDependenciesForValidation :: DatabaseContextExpr -> InclusionDependencies -> Either RelationalError InclusionDependencies
198+filterInclusionDependenciesForValidation context incDeps = if M.size errors > 0 then
199+ Left (MultipleErrors errors)
200+ else
201+ Right needValidation
202+ where
203+ violated = M.filter ((==) Violated) validationMap
204+ errors = map InclusionDependencyCheckError (M.elems violated)
205+ validationMap = M.map (inclusionDependencyValidation context) incDeps
206+ needValidation = M.filter ((==) ValidationNeeded) validationMap
207+
208+
209+inclusionDependencyValidation :: DatabaseContextExpr -> InclusionDependency -> Validation
210+
211+inclusionDependencyValidation NoOperation _ = NoValidationNeeded
212+
213+-- a new relvar cannot possibly part of any constraint
214+inclusionDependencyValidation (Define _ _) _ = NoValidationNeeded
215+
216+-- if there are any foreign keys pointing to the relvar, then
217+inclusionDependencyValidation (Undefine name) incDep = if
218+ _nameInIncDep name incDep then
219+ Violated
220+ else
221+ NoValidationNeeded
222+
223+inclusionDependencyValidation (Assign name _) incDep = _nameInIncDepValidation name incDep
224+
225+inclusionDependencyValidation (Insert name _) incDep = _nameInIncDepValidation name incDep
226+
227+inclusionDependencyValidation (Delete name _) incDep = _nameInIncDepValidation name incDep
228+
229+inclusionDependencyValidation (Update name _ _) incDep = _nameInIncDepValidation name incDep
230+
231+--inc deps can't violate another inc dep- there is no change in relvars
232+inclusionDependencyValidation (AddInclusionDependency _ _) _ = NoValidationNeeded
233+
234+inclusionDependencyValidation (RemoveInclusionDependency _) _ = NoValidationNeeded
235+
236+inclusionDependencyValidation (AddNotification _ _ _) _ = NoValidationNeeded
237+
238+inclusionDependencyValidation (RemoveNotification _) _ = NoValidationNeeded
239+
240+inclusionDependencyValidation (AddTypeConstructor _ _) _ = NoValidationNeeded
241+
242+inclusionDependencyValidation (RemoveTypeConstructor _) _ = NoValidationNeeded
243+
244+inclusionDependencyValidation (RemoveAtomFunction _) _ = NoValidationNeeded
245+
246+inclusionDependencyValidation (MultipleExpr exprs) incDep = checkValidation validationSet
247+ where
248+ validationSet = S.fromList (map (flip inclusionDependencyValidation incDep) exprs)
249+
250+_nameInIncDep :: RelVarName -> InclusionDependency -> Bool
251+_nameInIncDep name incDep = S.member name (relvarReferences incDep)
252+
253+_nameInIncDepValidation :: RelVarName -> InclusionDependency -> Validation
254+_nameInIncDepValidation name incDep = if _nameInIncDep name incDep then
255+ ValidationNeeded
256+ else
257+ NoValidationNeeded
258+
259+-- | Return what validation is needed after looking at optimizations over multiple expressions.
260+checkValidation :: S.Set Validation -> Validation
261+checkValidation vSet = if S.member Violated vSet then
262+ Violated
263+ else if S.member ValidationNeeded vSet || S.null vSet then
264+ ValidationNeeded
265+ else
266+ NoValidationNeeded
267+
268+
test/Relation/StaticOptimizer.hs+17−1
@@ -4,16 +4,19 @@ import ProjectM36.RelationalExpression
44import ProjectM36.DateExamples
55import ProjectM36.TupleSet
66import ProjectM36.StaticOptimizer
7+import ProjectM36.Key
8+import ProjectM36.InclusionDependency
79import System.Exit
810import Control.Monad.State
911import Test.HUnit
12+import qualified Data.Set as S
1013
1114main :: IO ()
1215main = do
1316 tcounts <- runTestTT (TestList tests)
1417 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
1518 where
16− tests = relationOptTests ++ databaseOptTests
19+ tests = relationOptTests ++ databaseOptTests ++ [testIncDepOptimizer]
1720 optTest optfunc estate testparams = map (\(name, expr, unopt) -> TestCase $ assertEqual name expr (evalState (optfunc unopt) estate)) $ testparams
1821 relvar nam = RelationVariable nam ()
1922 relationOptTests = optTest applyStaticRelationalOptimization (mkRelationalExprState dateExamples) [
@@ -34,3 +37,16 @@ main = do
3437 MultipleExpr [Assign "z" (Restrict TruePredicate (relvar "s")),
3538 Assign "z" (Restrict TruePredicate (relvar "s"))])
3639 ]
40+
41+-- test that the static optimizer doesn't eliminate inclusion dependencies improperly
42+testIncDepOptimizer :: Test
43+testIncDepOptimizer = TestCase $ do
44+ --simple case: an insert not mentioning the incdep need not be validated
45+ let incDep1 = inclusionDependencyForKey (AttributeNames (S.singleton "attrX")) (RelationVariable "rv" ())
46+ (InclusionDependency i1 _) = incDep1
47+ putStrLn (show i1)
48+ putStrLn (show (relationVariableNames i1))
49+ assertEqual "relevant relvar names" (S.singleton "rv") (relvarReferences incDep1)
50+ assertEqual "irrelevant insert" NoValidationNeeded (inclusionDependencyValidation (Insert "rv2" (RelationVariable "x" ())) incDep1)
51+ assertEqual "relevant insert" ValidationNeeded (inclusionDependencyValidation (Insert "rv" (RelationVariable "x" ())) incDep1)
52+