on master help sign in
finish functional dependency implementation including documentation and test
commit167d16379ccf6fb644be8f53be2d218c009f4cf0 authorAgentM <agentm@themactionfaction.com> dateWed 30 Nov 2016, 22:00 (10 years ago) parent7868148
12 files changed+64−29
docs/tutd_tutorial.markdown+14 src/bin/TutorialD/Interpreter/DatabaseContextExpr.hs+1 src/lib/ProjectM36/Attribute.hs+7−3 src/lib/ProjectM36/AttributeNames.hs+3−1 src/lib/ProjectM36/Base.hs+3−2 src/lib/ProjectM36/FunctionalDependency.hs+4−3 src/lib/ProjectM36/IsomorphicSchema.hs+1−1 src/lib/ProjectM36/Relation.hs−1 src/lib/ProjectM36/RelationalExpression.hs+2−15 src/lib/ProjectM36/TransactionGraph.hs+1 test/TutorialD/Interpreter.hs+11−2 test/TutorialD/Interpreter/TestBase.hs+17−1
docs/tutd_tutorial.markdown+14
@@ -468,6 +468,18 @@ TutorialD (master): foreign key s#_in_sp sp{s#} in s{s#}
468468
469469This expression ensure that any sp{s#} must also appear as a value in s{s#}.
470470
471+#### Functional dependencies
472+
473+Unlike SQL databases, Project:M36 includes first-class support for functional dependencies. A functional dependency for an attribute set ```A``` to attribute set ```B``` ensures that for every unique appearance of a value for ```A``` there is only one possible value for ```B```.
474+
475+To create a functional dependency:
476+
477+```
478+TutorialD (master): funcdep sname_status (sname) -> (status) s
479+```
480+
481+In this example, the functional dependency is named "sname_status" and creates a constraint which ensures that the "sname" value is functionally determines "status" value in the relation variable "s", though the final argument can be any relational expression. Multiple attribute names are separated by a comma.
482+
471483#### Other Constraints
472484
473485All other constraints can be represented by a set of inclusion dependencies.
@@ -478,6 +490,8 @@ TutorialD (master): constraint s_status_less_than_50 (s where ^lt(50,@status)){}
478490
479491This expression ensures that the relation variable's relation can never include a tuple with status greater than 50.
480492
493+Internally, *all* constraints are converted and stored as inclusion dependencies.
494+
481495## Aggregate Queries
482496
483497Aggregate queries in TutorialD are able to provide much more information than SQL queries due to the support for relation-valued attributes. For example, a single query can return aggregate results alongside its constituent tuples.
src/bin/TutorialD/Interpreter/DatabaseContextExpr.hs+1
@@ -128,6 +128,7 @@ funcDepP = do
128128 reserved "funcdep"
129129 keyName <- identifier
130130 source <- parens attributeListP
131+ reserved "->"
131132 dependents <- parens attributeListP
132133 expr <- relExprP
133134 let newIncDeps = inclusionDependenciesForFunctionalDependency funcDep
src/lib/ProjectM36/Attribute.hs+7−3
@@ -36,7 +36,7 @@ addAttribute attr attrs = attrs `V.snoc` attr
3636--if some attribute names overlap but the types do not, then spit back an error
3737joinAttributes :: Attributes -> Attributes -> Either RelationalError Attributes
3838joinAttributes attrs1 attrs2 = if V.length uniqueOverlappingAttributes /= V.length overlappingAttributes then
39− Left $ TupleAttributeTypeMismatchError overlappingAttributes
39+ Left (TupleAttributeTypeMismatchError overlappingAttributes)
4040 else if V.length overlappingAttrsDifferentTypes > 0 then
4141 Left (TupleAttributeTypeMismatchError overlappingAttrsDifferentTypes)
4242 else
@@ -118,7 +118,7 @@ vectorUniqueify vecIn = V.fromList $ HS.toList $ HS.fromList $ V.toList vecIn
118118--check that each attribute only appears once
119119verifyAttributes :: Attributes -> Either RelationalError Attributes
120120verifyAttributes attrs = if collapsedAttrs /= attrs then
121− Left $ TupleAttributeTypeMismatchError (attributesDifference collapsedAttrs attrs)
121+ Left (TupleAttributeTypeMismatchError (attributesDifference collapsedAttrs attrs))
122122 else
123123 Right attrs
124124 where
@@ -135,4 +135,8 @@ union :: Attributes -> Attributes -> Attributes
135135union attrsA attrsB = V.fromList (M.elems unioned)
136136 where
137137 unioned = M.union (attributesAsMap attrsA) (attributesAsMap attrsB)
138−
138+
139+intersection :: Attributes -> Attributes -> Attributes
140+intersection attrsA attrsB = V.fromList (M.elems intersected)
141+ where
142+ intersected = M.intersection (attributesAsMap attrsA) (attributesAsMap attrsB)
src/lib/ProjectM36/AttributeNames.hs+3−1
@@ -29,8 +29,10 @@ projectionAttributesForAttributeNames attrs (UnionAttributeNames namesA namesB)
2929 attrsA <- projectionAttributesForAttributeNames attrs namesA
3030 attrsB <- projectionAttributesForAttributeNames attrs namesB
3131 pure (A.union attrsA attrsB)
32+projectionAttributesForAttributeNames attrs (IntersectAttributeNames namesA namesB) = A.intersection <$> projectionAttributesForAttributeNames attrs namesA <*> projectionAttributesForAttributeNames attrs namesB
3233
3334invertAttributeNames :: AttributeNames -> AttributeNames
3435invertAttributeNames (AttributeNames names) = InvertedAttributeNames names
3536invertAttributeNames (InvertedAttributeNames names) = AttributeNames names
36−invertAttributeNames (UnionAttributeNames namesA namesB) = UnionAttributeNames (invertAttributeNames namesA) (invertAttributeNames namesB)
37+invertAttributeNames (UnionAttributeNames namesA namesB) = IntersectAttributeNames (invertAttributeNames namesA) (invertAttributeNames namesB)
38+invertAttributeNames (IntersectAttributeNames namesA namesB) = UnionAttributeNames (invertAttributeNames namesA) (invertAttributeNames namesB)
src/lib/ProjectM36/Base.hs+3−2
@@ -275,7 +275,7 @@ data SchemaIsomorph = IsoRestrict RelVarName RestrictionPredicateExpr (RelVarNam
275275 IsoRename RelVarName RelVarName |
276276 IsoUnion (RelVarName, RelVarName) RestrictionPredicateExpr RelVarName --maps two relvars to one relvar
277277 -- IsoTypeConstructor in morphAttrExpr
278− deriving (Generic, Binary)
278+ deriving (Generic, Binary, Show)
279279
280280type SchemaIsomorphs = [SchemaIsomorph]
281281
@@ -454,7 +454,8 @@ instance Show AtomFunction where
454454-- | The 'AttributeNames' structure represents a set of attribute names or the same set of names but inverted in the context of a relational expression. For example, if a relational expression has attributes named "a", "b", and "c", the 'InvertedAttributeNames' of ("a","c") is ("b").
455455data AttributeNames = AttributeNames (S.Set AttributeName) |
456456 InvertedAttributeNames (S.Set AttributeName) |
457− UnionAttributeNames AttributeNames AttributeNames
457+ UnionAttributeNames AttributeNames AttributeNames |
458+ IntersectAttributeNames AttributeNames AttributeNames
458459 deriving (Eq, Show, Generic)
459460
460461instance Binary AttributeNames
src/lib/ProjectM36/FunctionalDependency.hs+4−3
@@ -7,11 +7,12 @@ data FunctionalDependency = FunctionalDependency AttributeNames AttributeNames R
77--(s{city} group ({city} as x) : {z:=count(@x)}) {z}
88-- as defined in Relational Algebra and All That Jazz page 21
99inclusionDependenciesForFunctionalDependency :: FunctionalDependency -> (InclusionDependency, InclusionDependency)
10−inclusionDependenciesForFunctionalDependency (FunctionalDependency attrNamesSource attrNamesDependent relExpr) = (InclusionDependency countSource countDep,
11− InclusionDependency countDep countSource)
10+inclusionDependenciesForFunctionalDependency (FunctionalDependency attrNamesSource attrNamesDependent relExpr) = (
11+ InclusionDependency countSource countDep,
12+ InclusionDependency countDep countSource)
1213 where
13− countSource = relExprCount relExpr attrNamesSource
1414 countDep = relExprCount relExpr (UnionAttributeNames attrNamesSource attrNamesDependent)
15+ countSource = relExprCount relExpr attrNamesSource
1516 projectZName = Project (AttributeNames (S.singleton "z"))
1617 zCount = FunctionAtomExpr "count" [AttributeAtomExpr "x"] ()
1718 extendZName = Extend (AttributeExtendTupleExpr "z" zCount)
src/lib/ProjectM36/IsomorphicSchema.hs+1−1
@@ -28,7 +28,7 @@ import Data.Monoid
2828
2929data SchemaExpr = AddSubschema SchemaName SchemaIsomorphs |
3030 RemoveSubschema SchemaName
31− deriving (Generic, Binary)
31+ deriving (Generic, Binary, Show)
3232
3333
3434isomorphs :: Schema -> SchemaIsomorphs
src/lib/ProjectM36/Relation.hs−1
@@ -19,7 +19,6 @@ import qualified Data.Text as T
1919import Data.Either (isRight)
2020import System.Random.Shuffle
2121import Control.Monad.Random
22−--import Debug.Trace
2322
2423attributes :: Relation -> Attributes
2524attributes (Relation attrs _ ) = attrs
src/lib/ProjectM36/RelationalExpression.hs+2−15
@@ -24,7 +24,6 @@ import Control.Monad.Trans.Except
2424
2525import GHC
2626import GHC.Paths
27−--import Debug.Trace
2827
2928-- we need to pass around a higher level RelationTuple and Attributes in order to solve #52
3029data RelationalExprStateElems = RelationalExprStateTupleElems DatabaseContext RelationTuple | -- used when fully evaluating a relexpr
@@ -432,28 +431,16 @@ checkConstraints context = case failures of
432431 (Left err, _) -> Just err
433432 (Right relSub, _) -> case evaldSuper of
434433 (Left err, _) -> Just err
435− (Right relSuper, _) -> case union relSub relSuper of
434+ (Right relSuper, _) -> case union relSub relSuper of -- maybe this should be rewritten as a RelationalExpr in order to take advantage of optimizations (union, ==)
436435 Left err -> Just err
437436 Right resultRel -> if resultRel == relSuper then
438437 Nothing
439− else
438+ else do
440439 Just $ InclusionDependencyCheckError depName
441440 where
442441 evaldSub = eval subsetExpr
443442 evaldSuper = eval supersetExpr
444443
445−{-
446−checkConstraint :: InclusionDependency -> DatabaseState (Maybe RelationalError)
447−checkConstraint (InclusionDependency name subDep superDep) = do
448− evalSub <- evalRelationalExpr subDep
449− evalSuper <- evalRelationalExpr superDep
450− case evalSub of
451− Left err -> Just err
452− Right relSub
453− result <- liftM2 union evalSub evalSuper
454− return $ Nothing
455−-}
456−
457444-- the type of a relational expression is equal to the relation attribute set returned from executing the relational expression; therefore, the type can be cheaply derived by evaluating a relational expression and ignoring and tuple processing
458445-- furthermore, the type of a relational expression is the resultant header of the evaluated empty-tupled relation
459446
src/lib/ProjectM36/TransactionGraph.hs+1
@@ -39,6 +39,7 @@ data TransactionGraphOperator = JumpToHead HeadName |
3939 deriving (Eq, Show, Binary, Generic)
4040
4141data ROTransactionGraphOperator = ShowGraph
42+ deriving Show
4243
4344
4445bootstrapTransactionGraph :: TransactionId -> DatabaseContext -> TransactionGraph
test/TutorialD/Interpreter.hs+11−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]
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]
3737 simpleRelTests = [("x:=true", Right relationTrue),
3838 ("x:=false", Right relationFalse),
3939 ("x:=true union false", Right relationTrue),
@@ -384,4 +384,13 @@ testRelationalExprStateTupleElems = TestCase $ do
384384 [TextAtom "Athens", IntAtom 0]]
385385 assertEqual "validate parts count" expectedRel eRv
386386
387−
387+-- | Add a functional dependency on sname -> status and insert one tuple which is valid and another tuple which is invalid.
388+testFunctionalDependencies :: Test
389+testFunctionalDependencies = TestCase $ do
390+ (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback
391+ executeTutorialD sessionId dbconn "funcdep sname_status (sname) -> (status) s"
392+ --insert a new, valid tuple
393+ executeTutorialD sessionId dbconn "insert s relation{tuple{city \"Boston\", s# \"S6\", sname \"Stevens\", status 30}}"
394+ --insert an constraint-violating tuple
395+ let expectedError = InclusionDependencyCheckError "sname_status_A"
396+ expectTutorialDErr sessionId dbconn expectedError "insert s relation{tuple{city \"Boston\", s# \"S7\", sname \"Jones\", status 20}}"
test/TutorialD/Interpreter/TestBase.hs+17−1
@@ -1,5 +1,6 @@
11module TutorialD.Interpreter.TestBase where
22import ProjectM36.Client
3+import ProjectM36.Error
34import TutorialD.Interpreter
45import TutorialD.Interpreter.Base
56import qualified ProjectM36.Base as Base
@@ -38,7 +39,22 @@ executeTutorialD sessionId conn tutd = case parseTutorialD tutd of
3839 DisplayParseErrorResult _ _ -> assertFailure "displayparseerrorresult?"
3940 DisplayErrorResult err -> assertFailure (show err)
4041 QuietSuccessResult -> pure ()
41−
42+
43+expectTutorialDErr :: SessionId -> Connection -> RelationalError -> Text -> IO ()
44+expectTutorialDErr sessionId conn expectedErr tutd = case parseTutorialD tutd of
45+ Left err -> assertFailure (show tutd ++ ": " ++ show err)
46+ Right parsed -> do
47+ result <- evalTutorialD sessionId conn UnsafeEvaluation parsed
48+ case result of
49+ QuitResult -> assertFailure "quit?"
50+ DisplayResult _ -> assertFailure "display?"
51+ DisplayIOResult _ -> assertFailure "displayIO?"
52+ DisplayRelationResult _ -> assertFailure "displayrelation?"
53+ DisplayParseErrorResult _ _ -> assertFailure "displayparseerrorresult?"
54+ DisplayErrorResult err -> assertEqual "match error" (pack (show expectedErr)) err
55+ QuietSuccessResult -> pure ()
56+
57+
4258maybeFail :: (Show a) => Maybe a -> IO ()
4359maybeFail (Just err) = assertFailure (show err)
4460maybeFail Nothing = return ()