on master help sign in
add join elimination static optimization
commitd66583137dfe057004e5d020b12df3793c70686f authorAgentM <agentm@themactionfaction.com> dateSun 26 Nov 2017, 17:43 (9 years ago) parent5b44309
5 files changed+121−17
scripts/DateExamples.tutd+4−2 src/lib/ProjectM36/DateExamples.hs+7−4 src/lib/ProjectM36/Key.hs+20−7 src/lib/ProjectM36/StaticOptimizer.hs+55−2 test/Relation/StaticOptimizer.hs+35−2
scripts/DateExamples.tutd+4−2
@@ -20,5 +20,7 @@ sp := relation{tuple{s# "S1",p# "P1",qty 300},
2020 tuple{s# "S3",p# "P2",qty 200},
2121 tuple{s# "S4",p# "P4",qty 300},
2222 tuple{s# "S4",p# "P5",qty 400}};
23−foreign key sp2s sp{s#} in s{s#};
24−foreign key sp2p sp{p#} in p{p#};
23+key s_pkey {s#} s
24+key p_key {p#} p
25+foreign key s_sp_fk sp{s#} in s{s#};
26+foreign key p_sp_fk sp{p#} in p{p#};
src/lib/ProjectM36/DateExamples.hs+7−4
@@ -22,10 +22,13 @@ dateExamples = empty { inclusionDependencies = dateIncDeps,
2222 suppliers = suppliersRel
2323 products = productsRel
2424 supplierProducts = supplierProductsRel
25− dateIncDeps = M.fromList [("s_pkey", simplePKey ["s#"] "s"),
26− ("p_pkey", simplePKey ["p#"] "p"),
27− ("sp_pkey", simplePKey ["s#", "p#"] "sp")
28− ]
25+ dateIncDeps = M.fromList [
26+ ("s_pkey", simplePKey ["s#"] "s"),
27+ ("p_pkey", simplePKey ["p#"] "p"),
28+ ("sp_pkey", simplePKey ["s#", "p#"] "sp"),
29+ ("s_sp_fk", inclusionDependencyForForeignKey ("sp", ["s#"]) ("s", ["s#"])),
30+ ("p_sp_fk", inclusionDependencyForForeignKey ("sp", ["p#"]) ("p", ["p#"]))
31+ ]
2932 simplePKey attrNames relvarName = inclusionDependencyForKey (AttributeNames $ S.fromList attrNames) (RelationVariable relvarName ())
3033
3134suppliersRel :: Relation
src/lib/ProjectM36/Key.hs+20−7
@@ -39,16 +39,29 @@ databaseContextExprForUniqueKey rvName attrNames = AddInclusionDependency (rvNam
3939
4040-- | Create a foreign key constraint from the first relation variable and attributes to the second.
4141databaseContextExprForForeignKey :: IncDepName -> (RelVarName, [AttributeName]) -> (RelVarName, [AttributeName]) -> DatabaseContextExpr
42−databaseContextExprForForeignKey fkName (rvA, attrsA) (rvB, attrsB) =
43− AddInclusionDependency fkName (InclusionDependency
44− (renameIfNecessary attrsB attrsA (Project (attrsL attrsA)
45− (RelationVariable rvA ())))
46− (Project (attrsL attrsB)
47− (RelationVariable rvB ())))
42+databaseContextExprForForeignKey fkName infoA infoB =
43+ AddInclusionDependency fkName (inclusionDependencyForForeignKey infoA infoB)
44+
45+inclusionDependencyForForeignKey :: (RelVarName, [AttributeName]) -> (RelVarName, [AttributeName]) -> InclusionDependency
46+inclusionDependencyForForeignKey (rvA, attrsA) (rvB, attrsB) =
47+ InclusionDependency (
48+ renameIfNecessary attrsB attrsA (Project (attrsL attrsA)
49+ (RelationVariable rvA ()))) (
50+ Project (attrsL attrsB) (RelationVariable rvB ()))
4851 where
4952 attrsL = AttributeNames . S.fromList
5053 renameIfNecessary attrsExpected attrsExisting expr = foldr folder expr (zip attrsExpected attrsExisting)
5154 folder (attrExpected, attrExisting) expr = if attrExpected == attrExisting then
5255 expr
5356 else
54− Rename attrExisting attrExpected expr
57+ Rename attrExisting attrExpected expr
58+
59+-- if the constraint is a foreign key constraint, then return the relations and attributes involved - this only detects foreign keys created with `databaseContextExprForForeignKey`
60+isForeignKeyFor :: InclusionDependency -> (RelVarName, [AttributeName]) -> (RelVarName, [AttributeName]) -> Bool
61+isForeignKeyFor incDep infoA infoB = incDep == checkIncDep
62+ where
63+ checkIncDep = inclusionDependencyForForeignKey infoA infoB
64+
65+
66+
67+
src/lib/ProjectM36/StaticOptimizer.hs+55−2
@@ -3,12 +3,14 @@ import ProjectM36.Base
33import ProjectM36.RelationalExpression
44import ProjectM36.Relation
55import ProjectM36.Error
6+import qualified ProjectM36.Attribute as A
67import qualified ProjectM36.AttributeNames as AS
78import ProjectM36.TupleSet
89import Control.Monad.State hiding (join)
910import Data.Either (rights, lefts)
1011import Control.Monad.Trans.Reader
1112import qualified Data.Map as M
13+import qualified Data.Set as S
1214
1315-- the static optimizer performs optimizations which need not take any specific-relation statistics into account
1416-- apply optimizations which merely remove steps to become no-ops: example: projection of a relation across all of its attributes => original relation
@@ -22,7 +24,7 @@ applyStaticRelationalOptimization e@(MakeRelationFromExprs _ _) = pure $ Right e
2224applyStaticRelationalOptimization e@(ExistingRelation _) = pure $ Right e
2325
2426applyStaticRelationalOptimization e@(RelationVariable _ _) = pure $ Right e
25−
27+
2628--remove project of attributes which removes no attributes
2729applyStaticRelationalOptimization (Project attrNameSet expr) = do
2830 relType <- typeForRelationalExpr expr
@@ -37,7 +39,7 @@ applyStaticRelationalOptimization (Project attrNameSet expr) = do
3739 optimizedSubExpression <- applyStaticRelationalOptimization expr
3840 case optimizedSubExpression of
3941 Left err -> pure $ Left err
40− Right optSubExpr -> pure $ Right $ Project attrNameSet optSubExpr
42+ Right optSubExpr -> applyStaticJoinElimination (Project attrNameSet optSubExpr)
4143
4244applyStaticRelationalOptimization (Union exprA exprB) = do
4345 eOptExprA <- applyStaticRelationalOptimization exprA
@@ -301,3 +303,54 @@ isStaticAtomExpr ConstructedAtomExpr{} = True
301303isStaticAtomExpr AttributeAtomExpr{} = False
302304isStaticAtomExpr FunctionAtomExpr{} = False
303305isStaticAtomExpr RelationAtomExpr{} = False
306+
307+--if the projection of a join only uses the attributes from one of the expressions and there is a foreign key relationship between the expressions, we know that the join is inconsequential and can be removed
308+applyStaticJoinElimination :: RelationalExpr -> RelationalExprState (Either RelationalError RelationalExpr)
309+applyStaticJoinElimination expr@(Project attrNameSet (Join exprA exprB)) = do
310+ eProjType <- typeForRelationalExpr expr
311+ eTypeA <- typeForRelationalExpr exprA
312+ eTypeB <- typeForRelationalExpr exprB
313+ case eProjType of
314+ Left err -> pure (Left err)
315+ Right projType ->
316+ case eTypeA of
317+ Left err -> pure (Left err)
318+ Right typeA ->
319+ case eTypeB of
320+ Left err -> pure (Left err)
321+ Right typeB -> do
322+ let matchesProjectionAttributes = if attrNames projType `S.isSubsetOf` attrNames typeA then
323+ Just ((exprA, typeA), (exprB, typeB))
324+ else if attrNames projType `S.isSubsetOf` attrNames typeB then
325+ Just ((exprB, typeB), (exprA, typeA))
326+ else
327+ Nothing
328+ attrNames = A.attributeNameSet . attributes
329+ case matchesProjectionAttributes of
330+ Nothing -> -- this optimization does not apply
331+ pure (Right expr)
332+ Just ((joinedExpr, joinedType), (unjoinedExpr, _)) -> do
333+ --scan inclusion dependencies for a foreign key relationship
334+ incDeps <- inclusionDependencies . stateElemsContext <$> ask
335+ let fkConstraint = foldM isFkConstraint False incDeps
336+ --search for matching fk constraint
337+ isFkConstraint acc (InclusionDependency (Project subattrNames subrv) (Project _ superrv)) =
338+ case AS.projectionAttributesForAttributeNames (attributes projType) subattrNames of
339+ Left _ -> pure acc
340+ Right subAttrs ->
341+ pure (acc || (joinedExpr == subrv &&
342+ unjoinedExpr == superrv &&
343+ -- the fk attribute is one of the projection attributes
344+ A.attributesContained subAttrs (attributes joinedType)
345+ ))
346+ isFkConstraint acc _ = pure acc
347+ case fkConstraint of
348+ Right True -> --join elimination optimization applies
349+ applyStaticRelationalOptimization (Project attrNameSet joinedExpr)
350+ Right False -> --join elimination optimization does not apply
351+ pure (Right expr)
352+ Left err ->
353+ pure (Left err)
354+
355+applyStaticJoinElimination expr = pure (Right expr)
356+
test/Relation/StaticOptimizer.hs+35−2
@@ -9,6 +9,7 @@ import System.Exit
99import Control.Monad.State
1010import Control.Monad.Trans.Reader
1111import Test.HUnit
12+import qualified Data.Set as S
1213
1314main :: IO ()
1415main = do
@@ -16,7 +17,8 @@ main = do
1617 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
1718 where
1819 tests = relationOptTests ++ databaseOptTests ++ [testTransitiveOptimization,
19− testImpossiblePredicates]
20+ testImpossiblePredicates,
21+ testJoinElimination]
2022 optDBCTest = map (\(name, expr, unopt) -> TestCase $ assertEqual name expr (evalState (applyStaticDatabaseOptimization unopt) (freshDatabaseState dateExamples)))
2123 relvar nam = RelationVariable nam ()
2224 startState = mkRelationalExprState dateExamples
@@ -59,4 +61,35 @@ testImpossiblePredicates = TestCase $ do
5961 assertEqual "remove tautology where true" (Right rel) (optRelExpr tautTrue)
6062 assertEqual "remove tautology where true2" (Right rel) (optRelExpr tautTrue2)
6163 assertEqual "remove tautology where false" (Right emptyRel) (optRelExpr tautFalse)
62− assertEqual "remove tautology where false2" (Right emptyRel) (optRelExpr tautFalse2)
64+ assertEqual "remove tautology where false2" (Right emptyRel) (optRelExpr tautFalse2)
65+
66+testJoinElimination :: Test
67+testJoinElimination = TestCase $ do
68+ -- (s join sp){s#,qty,p#} == sp
69+ let unoptExpr = Project (AttributeNames (S.fromList ["p#", "qty", "s#"])) (Join (RelationVariable "sp" ()) (RelationVariable "s" ()))
70+ assertEqual "remove extraneous join" (Right (RelationVariable "sp" ())) (optRelExpr unoptExpr)
71+
72+-- (sp join s){s#,qty,p#} == sp --flip relvar names
73+ let unoptExpr2 = Project (AttributeNames (S.fromList ["p#", "qty", "s#"])) (Join (RelationVariable "s" ()) (RelationVariable "sp" ()))
74+ assertEqual "remove extraneous join2" (Right (RelationVariable "sp" ())) (optRelExpr unoptExpr2)
75+
76+-- (sp join s){s#,qty,sname} != sp - attribute from s included
77+ let expr3 = Project (AttributeNames (S.fromList ["p#", "qty", "sname"])) (Join (RelationVariable "sp" ()) (RelationVariable "s" ()))
78+ assertEqual "retain join" (Right expr3) (optRelExpr expr3)
79+
80+ -- (sp join s){qty} != sp --projection does not include foreign key attribute
81+ let expr4 = Project (AttributeNames (S.singleton "qty")) (Join (RelationVariable "sp" ()) (RelationVariable "s" ()))
82+ assertEqual "retain join2" (Right expr4) (optRelExpr expr4)
83+
84+ -- (sp join s){s#,qty} == sp - a subset of attributes in the projection are in the foreign key
85+ let expr5 = Project (AttributeNames (S.fromList ["s#", "qty"])) (Join (RelationVariable "sp" ()) (RelationVariable "s" ()))
86+ let expect5 = Project (AttributeNames (S.fromList ["s#", "qty"])) (RelationVariable "sp" ())
87+ assertEqual "remove extraneous join (attribute subset)" (Right expect5) (optRelExpr expr5)
88+
89+ -- (s join p){s#,p#} != sp -- no foreign key
90+ let expr6 = Project (AttributeNames (S.fromList ["s#", "p#"])) (Join (RelationVariable "s" ()) (RelationVariable "p" ()))
91+ assertEqual "retain join- no foreign key" (Right expr6) (optRelExpr expr6)
92+
93+ -- (s join p){s#,qty,p#,sname} != sp -- all sp attributes plus one s attributes
94+ let expr7 = Project (AttributeNames (S.fromList ["s#", "p#", "qty", "sname"])) (Join (RelationVariable "s" ()) (RelationVariable "sp" ()))
95+ assertEqual "retain join- attributes from both relvars" (Right expr7) (optRelExpr expr7)