on master help sign in
add outer-level attribute support to restriction relational subexpression

references #52

commit05b5ae13ac4c1adb8808afc15d6e0bd527bf1a62 authorAgentM <agentm@themactionfaction.com> dateMon 10 Jul 2017, 08:27 (9 years ago) parent52eca17
6 files changed+91−51
src/bin/TutorialD/Interpreter/RelationalExpr.hs+3−2 src/lib/ProjectM36/Attribute.hs+1−1 src/lib/ProjectM36/Relation.hs+9−9 src/lib/ProjectM36/RelationalExpression.hs+57−36 src/lib/ProjectM36/Tuple.hs+2−2 test/TutorialD/Interpreter.hs+19−1
src/bin/TutorialD/Interpreter/RelationalExpr.hs+3−2
@@ -148,7 +148,7 @@ restrictionPredicateP = makeExprParser predicateTerm predicateOperators
148148 [InfixL (reservedOp "and" >> return AndPredicate)],
149149 [InfixL (reservedOp "or" >> return OrPredicate)]
150150 ]
151− predicateTerm = parens restrictionPredicateP
151+ predicateTerm = try (parens restrictionPredicateP)
152152 <|> try restrictionAtomExprP
153153 <|> try restrictionAttributeEqualityP
154154 <|> try relationalBooleanExprP
@@ -156,7 +156,8 @@ restrictionPredicateP = makeExprParser predicateTerm predicateOperators
156156
157157relationalBooleanExprP :: RelationalMarkerExpr a => Parser (RestrictionPredicateExprBase a)
158158relationalBooleanExprP = do
159− relexpr <- relExprP
159+ relexpr <- parens relExprP <|> relTerm
160+ --we can't actually detect if the type is relational boolean, so we just pass it to the next phase
160161 return $ RelationalExprPredicate relexpr
161162
162163restrictionAttributeEqualityP :: RelationalMarkerExpr a => Parser (RestrictionPredicateExprBase a)
src/lib/ProjectM36/Attribute.hs+1−1
@@ -71,7 +71,7 @@ atomTypeForAttributeName attrName attrs = do
7171
7272attributeForName :: AttributeName -> Attributes -> Either RelationalError Attribute
7373attributeForName attrName attrs = case V.find (\attr -> attributeName attr == attrName) attrs of
74− Nothing -> Left $ NoSuchAttributeNamesError (S.singleton attrName)
74+ Nothing -> Left (NoSuchAttributeNamesError (S.singleton attrName))
7575 Just attr -> Right attr
7676
7777attributesForNames :: S.Set AttributeName -> Attributes -> Attributes
src/lib/ProjectM36/Relation.hs+9−9
@@ -12,7 +12,7 @@ import qualified ProjectM36.Attribute as A
1212import qualified ProjectM36.AttributeNames as AS
1313import ProjectM36.TupleSet
1414import ProjectM36.Error
15−import qualified Control.Parallel.Strategies as P
15+--import qualified Control.Parallel.Strategies as P
1616import qualified ProjectM36.TypeConstructorDef as TCD
1717import qualified ProjectM36.DataConstructorDef as DCD
1818import qualified Data.Text as T
@@ -165,8 +165,8 @@ group groupAttrNames newAttrName rel = do
165165restrictEq :: RelationTuple -> Relation -> Either RelationalError Relation
166166restrictEq tuple rel = restrict rfilter rel
167167 where
168− rfilter :: RelationTuple -> Bool
169− rfilter tupleIn = tupleIntersection tuple tupleIn == tuple
168+ rfilter :: RelationTuple -> Either RelationalError Bool
169+ rfilter tupleIn = pure (tupleIntersection tuple tupleIn == tuple)
170170
171171-- unwrap relation-valued attribute
172172-- return error if relval attrs and nongroup attrs overlap
@@ -203,11 +203,11 @@ attributesForRelval relvalAttrName (Relation attrs _) = do
203203 (RelationAtomType relAttrs) -> Right relAttrs
204204 _ -> Left $ AttributeIsNotRelationValuedError relvalAttrName
205205
206−restrict :: (RelationTuple -> Bool) -> Relation -> Either RelationalError Relation
207−--restrict rfilter (Relation attrs tupset) = Right $ Relation attrs $ HS.filter rfilter tupset
208−restrict rfilter (Relation attrs tupset) = Right $ Relation attrs processedTupSet
209− where
210− processedTupSet = RelationTupleSet ((filter rfilter (asList tupset)) `P.using` (P.parListChunk 1000 P.rdeepseq))
206+type RestrictionFilter = RelationTuple -> Either RelationalError Bool
207+restrict :: RestrictionFilter -> Relation -> Either RelationalError Relation
208+restrict rfilter (Relation attrs tupset) = do
209+ tuples <- filterM rfilter (asList tupset)
210+ Right $ Relation attrs (RelationTupleSet tuples)
211211
212212--joins on columns with the same name- use rename to avoid this- base case: cartesian product
213213--after changing from string atoms, there needs to be a type-checking step!
@@ -233,7 +233,7 @@ difference relA relB =
233233 where
234234 attrsA = attributes relA
235235 attrsB = attributes relB
236− rfilter tupInA = relFold (\tupInB acc -> if acc == False then False else if tupInB == tupInA then False else True) True relB
236+ rfilter tupInA = relFold (\tupInB acc -> if acc == Right False then pure False else pure (tupInB /= tupInA)) (Right True) relB
237237
238238--a map should NOT change the structure of a relation, so attributes should be constant
239239relMap :: (RelationTuple -> Either RelationalError RelationTuple) -> Relation -> Either RelationalError Relation
src/lib/ProjectM36/RelationalExpression.hs+57−36
@@ -195,7 +195,8 @@ evalRelationalExpr (Restrict predicateExpr relExpr) = do
195195 eFilterFunc <- predicateRestrictionFilter (attributes rel) predicateExpr
196196 case eFilterFunc of
197197 Left err -> return $ Left err
198− Right filterfunc -> return $ restrict filterfunc rel
198+ Right filterfunc -> do
199+ pure (restrict filterfunc rel)
199200
200201evalRelationalExpr (Equals relExprA relExprB) = do
201202 evaldA <- evalRelationalExpr relExprA
@@ -334,7 +335,7 @@ evalDatabaseContextExpr (Update relVarName atomExprMap restrictionPredicateExpr)
334335 if cardinality restrictedPortion == Finite 0 then
335336 pure Nothing
336337 else do
337− unrestrictedPortion <- restrict (not . predicateFunc) rel
338+ unrestrictedPortion <- restrict (\tup -> predicateFunc tup >>= pure . not) rel
338339 updatedPortion <- relMap (updateTupleWithAtomExprs atomExprMap context) restrictedPortion
339340 updatedRel <- union updatedPortion unrestrictedPortion
340341 pure (Just updatedRel)
@@ -614,52 +615,73 @@ liftE v = do
614615 Right val -> pure val
615616
616617{- used for restrictions- take the restrictionpredicate and return the corresponding filter function -}
617−predicateRestrictionFilter :: Attributes -> RestrictionPredicateExpr -> RelationalExprState (Either RelationalError (RelationTuple -> Bool))
618+predicateRestrictionFilter :: Attributes -> RestrictionPredicateExpr -> RelationalExprState (Either RelationalError RestrictionFilter)
618619predicateRestrictionFilter attrs (AndPredicate expr1 expr2) = do
619620 runExceptT $ do
620621 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
621622 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
622− pure (\x -> expr1v x && expr2v x)
623+ pure (\x -> do
624+ ev1 <- expr1v x
625+ ev2 <- expr2v x
626+ pure (ev1 && ev2))
623627
624628predicateRestrictionFilter attrs (OrPredicate expr1 expr2) = do
625629 runExceptT $ do
626630 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
627631 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
628− pure (\x -> expr1v x || expr2v x)
632+ pure (\x -> do
633+ ev1 <- expr1v x
634+ ev2 <- expr2v x
635+ pure (ev1 || ev2))
629636
630−predicateRestrictionFilter _ TruePredicate = pure (Right (\_ -> True))
637+predicateRestrictionFilter _ TruePredicate = pure (Right (\_ -> pure True))
631638
632639predicateRestrictionFilter attrs (NotPredicate expr) = do
633640 runExceptT $ do
634641 exprv <- liftE (predicateRestrictionFilter attrs expr)
635− pure (\x -> not (exprv x))
636−
637−predicateRestrictionFilter _ (RelationalExprPredicate relExpr) = runExceptT $ do
638− --merge attrs into to state attributes
639− rel <- liftE (evalRelationalExpr relExpr)
640− if rel == relationTrue then
641− pure (\_ -> True)
642− else if rel == relationFalse then
643− pure (\_ -> False)
644− else
645− throwE (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")
642+ pure (\x -> do
643+ ev <- exprv x
644+ pure (not ev))
645+
646+--optimization opportunity: if the subexpression does not reference attributes in the top-level expression, then it need only be evaluated once, statically, outside the tuple filter- see historical implementation here
647+predicateRestrictionFilter _ (RelationalExprPredicate relExpr) = do
648+ rstate <- ask
649+ pure (Right (\tup -> case runReader (evalRelationalExpr relExpr) (mergeTuplesIntoRelationalExprState tup rstate) of
650+ Left err -> Left err
651+ Right rel -> if arity rel /= 0 then
652+ Left (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")
653+ else
654+ pure (rel == relationTrue)))
646655
647656predicateRestrictionFilter attrs (AttributeEqualityPredicate attrName atomExpr) = do
648− --merge attrs into the state attributes
649657 rstate <- ask
658+ let (attrs', ctxtup') = case rstate of
659+ RelationalExprStateElems _ -> (attrs, emptyTuple)
660+ RelationalExprStateAttrsElems _ _ -> (attrs, emptyTuple)
661+ RelationalExprStateTupleElems _ ctxtup -> (A.union attrs (tupleAttributes ctxtup), ctxtup)
650662 runExceptT $ do
651− atomExprType <- liftE (typeFromAtomExpr attrs atomExpr)
652− attr <- either throwE pure (A.attributeForName attrName attrs)
663+ atomExprType <- liftE (typeFromAtomExpr attrs' atomExpr)
664+ attr <- either throwE pure $ case A.attributeForName attrName attrs of
665+ Right attr -> Right attr
666+ Left (NoSuchAttributeNamesError _) -> case A.attributeForName attrName (tupleAttributes ctxtup') of
667+ Right ctxattr -> Right ctxattr
668+ Left err2@(NoSuchAttributeNamesError _) -> Left err2
669+ Left err -> Left err
670+ Left err -> Left err
653671 if atomExprType /= A.atomType attr then
654672 throwE (TupleAttributeTypeMismatchError (A.attributesFromList [attr]))
655673 else
656− pure $ \tupleIn -> case atomForAttributeName attrName tupleIn of
657− Left _ -> False
658− Right atomIn ->
659− let atomEvald = runReader (evalAtomExpr tupleIn atomExpr) rstate in
660− case atomEvald of
661− Right atomCmp -> atomCmp == atomIn
662− Left _ -> False
674+ pure $ \tupleIn -> let evalAndCmp atomIn = case atomEvald of
675+ Right atomCmp -> atomCmp == atomIn
676+ Left _ -> False
677+ atomEvald = runReader (evalAtomExpr tupleIn atomExpr) rstate
678+ in
679+ pure $ case atomForAttributeName attrName tupleIn of
680+ Left (NoSuchAttributeNamesError _) -> case atomForAttributeName attrName ctxtup' of
681+ Left _ -> False
682+ Right ctxatom -> evalAndCmp ctxatom
683+ Left _ -> False
684+ Right atomIn -> evalAndCmp atomIn
663685-- in the future, it would be useful to do typechecking on the attribute and atom expr filters in advance
664686predicateRestrictionFilter attrs (AtomExprPredicate atomExpr) = do
665687 --merge attrs into the state attributes
@@ -670,13 +692,13 @@ predicateRestrictionFilter attrs (AtomExprPredicate atomExpr) = do
670692 throwE (AtomTypeMismatchError aType BoolAtomType)
671693 else
672694 pure (\tupleIn -> do
673− case runReader (evalAtomExpr tupleIn atomExpr) rstate of
695+ pure $ case runReader (evalAtomExpr tupleIn atomExpr) rstate of
674696 Left _ -> False
675697 Right boolAtomValue -> boolAtomValue == BoolAtom True)
676698
677699tupleExprCheckNewAttrName :: AttributeName -> Relation -> Either RelationalError Relation
678−tupleExprCheckNewAttrName attrName rel = if isRight $ attributeForName attrName rel then
679− Left $ AttributeNameInUseError attrName
700+tupleExprCheckNewAttrName attrName rel = if isRight (attributeForName attrName rel) then
701+ Left (error "SPAMMIT" $ AttributeNameInUseError attrName)
680702 else
681703 Right rel
682704
@@ -742,19 +764,18 @@ evalAtomExpr tupIn cons@(ConstructedAtomExpr dConsName dConsArgs ()) = runExcept
742764
743765typeFromAtomExpr :: Attributes -> AtomExpr -> RelationalExprState (Either RelationalError AtomType)
744766typeFromAtomExpr attrs (AttributeAtomExpr attrName) = do
745− --first, check if the attribute is in the immediate attributes
746767 rstate <- ask
747768 case A.atomTypeForAttributeName attrName attrs of
748769 Right aType -> pure (Right aType)
749770 Left err@(NoSuchAttributeNamesError _) -> case rstate of
750771 RelationalExprStateAttrsElems _ attrs' -> case A.attributeForName attrName attrs' of
751− Left err' -> pure (Left err')
772+ Left err' -> pure (error "SPAMMO2" $ Left err')
752773 Right attr -> pure (Right (A.atomType attr))
753− RelationalExprStateElems _ -> pure (Left err)
774+ RelationalExprStateElems _ -> pure (error (show attrs) $ Left err)
754775 RelationalExprStateTupleElems _ tup -> case atomForAttributeName attrName tup of
755− Left err' -> pure (Left err')
776+ Left err' -> pure (error "STAMP" $ Left err')
756777 Right atom -> pure (Right (atomTypeForAtom atom))
757− Left err -> pure (Left err)
778+ Left err -> pure (error "GONK" $ Left err)
758779typeFromAtomExpr _ (NakedAtomExpr atom) = pure (Right (atomTypeForAtom atom))
759780typeFromAtomExpr attrs (FunctionAtomExpr funcName atomArgs _) = do
760781 context <- liftM stateElemsContext ask
@@ -795,7 +816,7 @@ verifyAtomExprTypes relIn (AttributeAtomExpr attrName) expectedType = runExceptT
795816 RelationalExprStateTupleElems _ _ -> throwE err
796817 RelationalExprStateElems _ -> throwE err
797818 RelationalExprStateAttrsElems _ attrs -> case A.attributeForName attrName attrs of
798− Left err' -> throwE err'
819+ Left err' -> throwE (error "GONK" err')
799820 Right attrType -> either throwE pure (atomTypeVerify expectedType (A.atomType attrType))
800821 Left err -> throwE err
801822verifyAtomExprTypes _ (NakedAtomExpr atom) expectedType = pure (atomTypeVerify expectedType (atomTypeForAtom atom))
src/lib/ProjectM36/Tuple.hs+2−2
@@ -33,9 +33,9 @@ tupleAtoms (RelationTuple _ tupVec) = tupVec
3333
3434atomForAttributeName :: AttributeName -> RelationTuple -> Either RelationalError Atom
3535atomForAttributeName attrName (RelationTuple tupAttrs tupVec) = case V.findIndex (\attr -> attributeName attr == attrName) tupAttrs of
36− Nothing -> Left $ NoSuchAttributeNamesError (S.singleton attrName)
36+ Nothing -> Left (NoSuchAttributeNamesError (S.singleton attrName))
3737 Just index -> case tupVec V.!? index of
38− Nothing -> Left $ NoSuchAttributeNamesError (S.singleton attrName)
38+ Nothing -> Left (NoSuchAttributeNamesError (S.singleton attrName))
3939 Just atom -> Right atom
4040
4141{- -- resolve naming clash with Attribute and Relation later
test/TutorialD/Interpreter.hs+19−1
@@ -15,6 +15,7 @@ import ProjectM36.Base hiding (Finite)
1515import ProjectM36.TransactionGraph
1616import ProjectM36.Client
1717import qualified ProjectM36.DisconnectedTransaction as Discon
18+import qualified ProjectM36.AttributeNames as AN
1819import qualified ProjectM36.Session as Sess
1920import qualified ProjectM36.Attribute as A
2021import qualified Data.Map as M
@@ -89,7 +90,7 @@ main = do
8990 Right $ updateTupleWithAtoms (M.singleton "status" (IntAtom ((castInt statusAtom) + 10))) tuple
9091 else Right tuple) suppliersRel
9192 dateExampleRelTests = [("x:=s where true", Right suppliersRel),
92− ("x:=s where city = \"London\"", restrict (\tuple -> atomForAttributeName "city" tuple == (Right $ TextAtom "London")) suppliersRel),
93+ ("x:=s where city = \"London\"", restrict (\tuple -> pure $ atomForAttributeName "city" tuple == (Right $ TextAtom "London")) suppliersRel),
9394 ("x:=s where false", Right $ Relation (attributes suppliersRel) emptyTupleSet),
9495 ("x:=p where color=\"Blue\" and city=\"Paris\"", mkRelationFromList (attributes productsRel) [[TextAtom "P5", TextAtom "Cam", TextAtom "Blue", IntAtom 12, TextAtom "Paris"]]),
9596 ("a:=s; update a (status:=50); x:=a{status}", mkRelation (A.attributesFromList [Attribute "status" IntAtomType]) (RelationTupleSet [mkRelationTuple (A.attributesFromList [Attribute "status" IntAtomType]) (V.fromList [IntAtom 50])])),
@@ -388,6 +389,23 @@ testRelationalExprStateTupleElems = TestCase $ do
388389 [TextAtom "Athens", IntAtom 0]]
389390 assertEqual "validate parts count" expectedRel eRv
390391
392+ executeTutorialD sessionId dbconn "rv1:=relation{tuple{test 1}}"
393+ executeTutorialD sessionId dbconn "rv2:=relation{tuple{val 1},tuple{val 2}}"
394+ --check subexpression evaluation in restriction predicate
395+ -- "rv1 where ((rv2 where val=@test) {})"
396+ let correctSubexpr = Restrict (AttributeEqualityPredicate "val" (AttributeAtomExpr "test")) (RelationVariable "rv2" ())
397+ mainExpr subexpr = (Restrict
398+ (RelationalExprPredicate
399+ (Project AN.empty subexpr)) (RelationVariable "rv1" ()))
400+ eRv2 <- executeRelationalExpr sessionId dbconn (mainExpr correctSubexpr)
401+ let expectedRel2 = mkRelationFromList (attributesFromList [Attribute "test" IntAtomType]) [[IntAtom 1]]
402+ assertEqual "validate sub-expression attribute" expectedRel2 eRv2
403+
404+ --check error in subexpression
405+ let wrongSubexpr = Restrict (AttributeEqualityPredicate "nosuchattr" (AttributeAtomExpr "test")) (RelationVariable "rv2" ())
406+ eRv3 <- executeRelationalExpr sessionId dbconn (mainExpr wrongSubexpr)
407+ assertEqual "validate missing attribute in subexpression" (Left (NoSuchAttributeNamesError (S.singleton "nosuchattr"))) eRv3
408+
391409-- | Add a functional dependency on sname -> status and insert one tuple which is valid and another tuple which is invalid.
392410testFunctionalDependencies :: Test
393411testFunctionalDependencies = TestCase $ do