on master help sign in
working Tutorial D parser
commit49ed7221886b04954a4792a4efbbaa68cc73ccb5 authorAgentM <agentm@themactionfaction.com> dateSun 26 Oct 2014, 20:45 (12 years ago) parentecc4793
12 files changed+866
.gitignore+3 Relation.hs+343 RelationAtom.hs+4 RelationAttribute.hs+35 RelationExpr.hs+137 RelationHTML.hs+39 RelationTest.hs+43 RelationTuple.hs+71 RelationTupleSet.hs+21 RelationType.hs+30 TutorialDInterpreter.hs+115 TutorialDInterpreterTest.hs+25
.gitignorenew file+3
@@ -0,0 +1,3 @@
1+*.o
2+*.hi
3+*~
Relation.hsnew file+343
@@ -0,0 +1,343 @@
1+-- this is an implementation of the Relation typeclass which creates a GADT DSL
2+{-# LANGUAGE GADTs,ExistentialQuantification #-}
3+module Relation where
4+import qualified Data.Map as M
5+import qualified Data.Serialize as SZ
6+import qualified Data.Set as S
7+import qualified Data.List as L
8+import qualified Data.HashSet as HS
9+import qualified Data.Hashable as Hash
10+import qualified Control.Monad.Error as Err
11+
12+import Debug.Trace
13+import RelationType
14+import RelationAtom
15+import RelationTuple
16+import RelationAttribute
17+import RelationTupleSet
18+
19+testTuple1 = mkRelationTuple (S.fromList ["hair"]) (M.fromList [("hair", StringAtom "brown")])
20+
21+attributes :: Relation -> Attributes
22+attributes (Relation attrs _ ) = attrs
23+
24+attributeNames :: Relation -> S.Set AttributeName
25+attributeNames (Relation attrs _) = M.keysSet attrs
26+
27+attributeForName :: AttributeName -> Relation -> Maybe Attribute
28+attributeForName attrName (Relation attrs _) = M.lookup attrName attrs
29+
30+attributesForNames :: S.Set AttributeName -> Relation -> Attributes
31+attributesForNames attrNameSet (Relation attrs _) = M.intersection attrs hollowMap
32+ where
33+ hollowMap = M.fromList $ zip (S.toList attrNameSet) (repeat (Attribute "" StringAtomType))
34+
35+attributeTypeForName :: AttributeName -> Relation -> Maybe AtomType
36+attributeTypeForName attrName rel = case attr of
37+ (Just (Attribute _ atomType)) -> Just atomType
38+ _ -> Nothing
39+ where
40+ attr = attributeForName attrName rel
41+
42+atomValueType :: Atom -> AtomType
43+atomValueType (StringAtom _) = StringAtomType
44+atomValueType (IntAtom _) = IntAtomType
45+atomValueType (RelationAtom rel) = RelationAtomType (attributes rel)
46+
47+
48+instance Err.Error RelationalError where
49+
50+mkRelation :: Attributes -> RelationTupleSet -> Either RelationalError Relation
51+mkRelation attrs tupleSet =
52+ --check that all tuples have the same keys
53+ --check that all tuples have keys (1-N) where N is the attribute count
54+ if differentTupleCounts
55+ then Left $ RelationalError 1 "Tuple attribute count mismatch"
56+ else if attrCountMismatch
57+ then Left $ RelationalError 1 "Attribute count mismatch"
58+ else
59+ Right $ Relation attrs tupleSet
60+ where
61+ attrCount = M.size attrs
62+ tupleCounts = HS.map tupleSize tupleSet
63+ attrCountMismatch = if HS.size tupleSet > 0
64+ then not $ HS.member attrCount tupleCounts
65+ else False --empty tuple set is always OK
66+ differentTupleCounts = HS.size tupleCounts > 1
67+
68+r1 = case mkRelation attributes tupleSet1 of
69+ Right x -> x
70+ Left x -> relationFalse
71+ where
72+ tupleSet1 = HS.fromList [tuple1,tuple2]
73+ tuple1 = mkRelationTuple (S.fromList ["hair"]) $ M.fromList [("hair", StringAtom "brown")]
74+ tuple2 = mkRelationTuple (S.fromList ["hair"]) $ M.fromList [("hair", StringAtom "blonde")]
75+ attributes = M.singleton "hair" (Attribute "hair" StringAtomType)
76+
77+testr1 = Relation attributes tupleSet
78+ where
79+ attributes = M.fromList [("hair", Attribute "hair" StringAtomType), ("name", Attribute "name" StringAtomType)]
80+ tupleSet = HS.fromList $ mkRelationTuples attributes [M.fromList [("name", StringAtom "spammo"),("hair", StringAtom "brown")]]
81+
82+testr2 = Relation attributes tupleSet
83+ where
84+ attributes = M.singleton "hair" (Attribute "hair" StringAtomType)
85+ tupleSet = HS.fromList $ mkRelationTuples attributes [M.fromList [("name", StringAtom "spammo")],M.fromList [("name", StringAtom "gonk")]]
86+
87+testcitiesr = Relation attributes tupleSet
88+ where
89+ attributes = M.singleton "name" (Attribute "name" StringAtomType)
90+ tupleSet = HS.fromList $ mkRelationTuples attributes [M.fromList [("name", StringAtom "Boston"), ("name", StringAtom "Cambridge")]]
91+
92+testrinr = Relation attributes tupleSet
93+ where
94+ attributes = M.fromList [("state", Attribute "state" StringAtomType), ("cities", Attribute "cities" StringAtomType)]
95+ tupleSet = HS.fromList $ mkRelationTuples attributes [M.fromList [("state", StringAtom "MA"), ("cities", RelationAtom testcitiesr)]]
96+
97+relationTrue = Relation M.empty $ HS.singleton (RelationTuple M.empty)
98+relationFalse = Relation M.empty emptyTupleSet
99+
100+madeTrue = mkRelation M.empty (HS.fromList [])
101+madeFalse = mkRelation M.empty emptyTupleSet
102+
103+union :: Relation -> Relation -> Either RelationalError Relation
104+union (Relation attrs1 tupSet1) (Relation attrs2 tupSet2) =
105+ if not (attrs1 == attrs2)
106+ then Left $ RelationalError 1 ("Attribute mismatch " ++ show attrs1 ++ show attrs2)
107+ else
108+ Right $ Relation attrs1 newtuples
109+ where
110+ newtuples = HS.union tupSet1 tupSet2
111+
112+project :: S.Set AttributeName -> Relation -> Either RelationalError Relation
113+project projectionAttrNames rel@(Relation oldattrs rtuples) =
114+ if not $ attributeNamesContained projectionAttrNames (attributeNames rel)
115+ then Left $ RelationalError 1 "Attributes mismatch"
116+ else
117+ relFold folder (Right $ Relation newAttrs HS.empty) rel
118+ where
119+ newAttrs = attributesForNames projectionAttrNames rel
120+ folder :: RelationTuple -> Either RelationalError Relation -> Either RelationalError Relation
121+ folder tupleToProject acc = case acc of
122+ Left err -> Left err
123+ Right acc -> union acc (Relation newAttrs (HS.singleton (tupleProject projectionAttrNames tupleToProject)))
124+
125+tupleProject :: S.Set AttributeName -> RelationTuple -> RelationTuple
126+tupleProject projectAttrs tupleIn@(RelationTuple tupleInMap) = RelationTuple $ M.filterWithKey attrFilter tupleInMap
127+ where
128+ attrFilter key _ = S.member key projectAttrs
129+
130+--the expensive rename is why I wanted to use IntMap instead of attribute names in the tupleset
131+rename :: AttributeName -> AttributeName -> Relation -> Either RelationalError Relation
132+rename oldAttrName newAttrName rel@(Relation oldAttrs oldTupSet) =
133+ if not attributeValid
134+ then Left $ RelationalError 1 "No such attribute"
135+ else
136+ mkRelation newAttrs newTupSet
137+ where
138+ attributeValid = attributeNamesContained (S.singleton oldAttrName) (attributeNames rel)
139+ newAttrs = M.insert newAttrName (renameAttribute newAttrName (oldAttrs M.! oldAttrName)) oldAttrs
140+ newTupSet = HS.map tupsetmapper oldTupSet
141+ tupsetmapper tuple = tupleRenameAttribute oldAttrName newAttrName tuple
142+
143+--the algebra should return a relation of one attribute and one row with the arity
144+arity :: Relation -> Int
145+arity (Relation attrs _) = M.size attrs
146+
147+cardinality :: Relation -> RelationCardinality
148+cardinality (Relation _ tupSet) = Countable (HS.size tupSet)
149+
150+--find tuples where the atoms in the relation which are NOT in the AttributeNameSet are equal
151+-- create a relation for each tuple where the attributes NOT in the AttributeNameSet are equal
152+--the attrname set attrs end up in the nested relation
153+
154+--algorithm:
155+-- map projection of non-grouped attributes to restriction of matching grouped attribute tuples and then project on grouped attributes to construct the sub-relation
156+{-
157+group :: S.Set AttributeName -> AttributeName -> Relation -> Either RelationalError Relation
158+group groupAttrNames newAttrName rel@(Relation oldAttrs tupleSet) = do
159+ nonGroupProjection <- project nonGroupAttrNames rel
160+ relFold folder (Right (Relation newAttrs emptyTupleSet)) nonGroupProjection
161+ where
162+ newAttrs = M.union (attributesForNames nonGroupAttrNames rel) groupAttr
163+ groupAttr = Attribute newAttrName RelationAtomType (attributesForNames nonGroupAttrNames rel)
164+ newAttrNameSet = S.insert newAttrName nonGroupAttrNames
165+ nonGroupAttrNames = S.difference groupAttrNames (attributeNames rel)
166+ --map the projection to add the additional new attribute
167+ --create the new attribute (a new relation) by filtering and projecting the tupleSet
168+ folder tupleFromProjection acc = case acc of
169+ Left err -> Left err
170+ Right acc -> union acc (Relation newAttrs (HS.singleton (tupleExtend tupleFromProjection (matchingRelTuple tupleFromProjection))))
171+-}
172+
173+--algorithm: self-join with image relation
174+group2 :: S.Set AttributeName -> AttributeName -> Relation -> Either RelationalError Relation
175+group2 groupAttrNames newAttrName rel@(Relation attrs tupleSet) = do
176+ nonGroupProjection <- project nonGroupAttrNames rel
177+ relFold folder (Right $ Relation newAttrs emptyTupleSet) nonGroupProjection
178+ where
179+ newAttrs = M.insert newAttrName groupAttr (attributesForNames nonGroupAttrNames rel)
180+ groupAttr = Attribute newAttrName (RelationAtomType (attributesForNames nonGroupAttrNames rel))
181+ nonGroupAttrNames = nonMatchingAttributeNameSet groupAttrNames (attributeNames rel)
182+ newAttrNames = S.insert newAttrName nonGroupAttrNames
183+ folder tupleIn acc = case acc of
184+ Left err -> Left err
185+ Right acc -> union acc (Relation newAttrs (HS.singleton (tupleExtend tupleIn (matchingRelTuple tupleIn))))
186+ matchingRelTuple groupTuple = case imageRelationFor groupTuple rel of
187+ Right rel -> RelationTuple $ M.singleton newAttrName (RelationAtom rel)
188+ Left _ -> undefined
189+
190+--help restriction function
191+--returns a subrelation of
192+restrictEq :: RelationTuple -> Relation -> Either RelationalError Relation
193+restrictEq (RelationTuple tupMap) rel = restrict filter rel
194+ where
195+ filter :: RelationTuple -> Bool
196+ filter (RelationTuple tupMap2) = M.isSubmapOf tupMap tupMap2
197+
198+-- unwrap relation-valued attribute
199+-- return error if relval attrs and nongroup attrs overlap
200+ungroup :: AttributeName -> Relation -> Either RelationalError Relation
201+ungroup relvalAttrName rel = relFold relFolder (Right $ Relation newAttrs emptyTupleSet) rel
202+ where
203+ newAttrNames = M.keysSet newAttrs
204+ newAttrs = M.union (attributesForRelval relvalAttrName rel) nonGroupAttrs
205+ nonGroupAttrs = M.delete relvalAttrName (attributes rel)
206+ relFolder :: RelationTuple -> Either RelationalError Relation -> Either RelationalError Relation
207+ relFolder tupleIn acc = case (tupleUngroup relvalAttrName newAttrs tupleIn) of
208+ Right ungroupRelation -> case acc of
209+ Left err -> Left err
210+ Right accRel -> do
211+ ungrouped <- tupleUngroup relvalAttrName newAttrs tupleIn
212+ union accRel ungrouped
213+ Left err -> Left err
214+
215+--take an relval attribute name and a tuple and ungroup the relval
216+tupleUngroup :: AttributeName -> Attributes -> RelationTuple -> Either RelationalError Relation
217+tupleUngroup relvalAttrName newAttrs tuple@(RelationTuple tupMap) = case (tupMap M.! relvalAttrName) of
218+ (RelationAtom relvalRelation) -> relFold folder (Right $ Relation newAttrs emptyTupleSet) relvalRelation
219+ where
220+ newAttrNameSet = M.keysSet newAttrs
221+ nonGroupTupleProjection = tupleProject nonGroupAttrNames tuple
222+ nonGroupAttrNames = M.keysSet newAttrs
223+ folder tupleIn acc = case acc of
224+ Left err -> Left err
225+ Right accRel -> union accRel $ Relation newAttrs (HS.singleton (tupleExtend nonGroupTupleProjection tupleIn))
226+ relvalRelation = unsafeRelValFromAtom $ tupleAtomForAttribute tuple relvalAttrName
227+ where
228+ unsafeRelValFromAtom :: Atom -> Relation
229+ unsafeRelValFromAtom (RelationAtom rel) = rel
230+ _ -> Left $ RelationalError 1 "Attribute is not relation-valued."
231+
232+--this is a hack needed because the relation attributes are untyped so we must dig into the relval to get the attribute names
233+attributesForRelval :: AttributeName -> Relation -> Attributes
234+attributesForRelval relvalAttrName relIn@(Relation attrs tupleSet) = attributes relvalRelation
235+ where
236+ relvalRelationAttrs = attributes relvalRelation
237+ --this is only temporarily needed until the Relation stores the types of the columns as data- hack alert
238+ --find a tuple with attributes to steal to ungroup
239+ oneTuple :: RelationTupleSet -> RelationTuple
240+ oneTuple tupleSet = head $ HS.toList $ HS.filter (\tup@(RelationTuple tupMap) -> M.size tupMap > 0) tupleSet
241+ relvalRelation = unsafeRelValFromAtom $ tupleAtomForAttribute (oneTuple tupleSet) relvalAttrName
242+ where
243+ unsafeRelValFromAtom :: Atom -> Relation
244+ unsafeRelValFromAtom (RelationAtom rel) = rel
245+
246+restrict :: (RelationTuple -> Bool) -> Relation -> Either RelationalError Relation
247+restrict filter (Relation attrs tupset) = Right $ Relation attrs $ HS.filter filter tupset
248+
249+--joins on columns with the same name- use rename to avoid this- base case: cartesian product
250+--after changing from string atoms, there needs to be a type-checking step!
251+--this is a "nested loop" scan as described by the postgresql documentation
252+join :: Relation -> Relation -> Either RelationalError Relation
253+join (Relation attrs1 tupSet1) (Relation attrs2 tupSet2) = Right $ Relation newAttrs newTupSet
254+ where
255+ newAttrs = M.union attrs1 attrs2
256+ newTupSet = HS.foldr tupleSetJoiner emptyTupleSet tupSet1
257+ tupleSetJoiner tuple1 accumulator = HS.union (singleTupleSetJoin tuple1 tupSet2) accumulator
258+
259+
260+--a map should NOT change the structure of a relation, so attributes should be constant relMap :: (RelationTuple -> RelationTuple) -> Attributes -> Relation -> Either RelationalError Relation
261+relMap mapper newAttributes (Relation attrs tupleSet) = Right $ Relation newAttributes (HS.map mapper tupleSet)
262+
263+relFold :: (RelationTuple -> a -> a) -> a -> Relation -> a
264+relFold folder acc (Relation attrs tupleSet) = HS.foldr folder acc tupleSet
265+
266+{- a specialized form of fold which always returns a Relation
267+relTuplesFold :: (RelationTuple -> RelationTupleSet -> RelationTupleSet) -> RelationTupleSet -> AttributeNameSet -> Relation -> Either RelationalError Relation
268+-}
269+
270+--image relation as defined by CJ Date
271+--given tupleA and relationB, return restricted relation where tuple attributes are not the attribues in tupleA but are attributes in relationB and match the tuple's value
272+
273+--check that matching attribute names have the same types
274+imageRelationFor :: RelationTuple -> Relation -> Either RelationalError Relation
275+imageRelationFor matchTuple rel@(Relation attrs _) = do
276+ restricted <- restrictEq matchTuple rel --restrict across matching tuples
277+ project (nonMatchingAttributeNameSet (attributeNames rel) (tupleAttributeNameSet matchTuple)) restricted --project across attributes not in rel
278+
279+imageRelationTest = imageRelationFor tup s
280+ where
281+ tup = mkRelationTuple (S.fromList ["SNAME", "CITY", "S#"]) (M.fromList [("SNAME", StringAtom "Smith"), ("S#", StringAtom "S1"), ("CITY", StringAtom "London")])
282+
283+--returns a relation-valued attribute image relation for each tuple in rel1
284+--algorithm:
285+ {-
286+imageRelationJoin :: Relation -> Relation -> Either RelationalError Relation
287+imageRelationJoin rel1@(Relation attrNameSet1 tupSet1) rel2@(Relation attrNameSet2 tupSet2) = do
288+ Right $ Relation undefined
289+ where
290+ matchingAttrs = matchingAttributeNameSet attrNameSet1 attrNameSet2
291+ newAttrs = nonMatchingAttributeNameSet matchingAttrs $ S.union attrNameSet1 attrNameSet2
292+
293+ tupleSetJoiner tup1 acc = undefined
294+-}
295+
296+{- C.J. Date's canonical example relvars -}
297+
298+relvarS = mkRelation attributes tupleSet
299+ where
300+ attributes = M.fromList [("S#", Attribute "S#" StringAtomType),
301+ ("SNAME", Attribute "SNAME" StringAtomType),
302+ ("STATUS", Attribute "STATUS" StringAtomType),
303+ ("CITY", Attribute "CITY" StringAtomType)]
304+ tupleSet = HS.fromList $ mkRelationTuples attributes [
305+ M.fromList [("S#", StringAtom "S1") , ("SNAME", StringAtom "Smith"), ("STATUS", IntAtom 20) , ("CITY", StringAtom "London")],
306+ M.fromList [("S#", StringAtom "S2"), ("SNAME", StringAtom "Jones"), ("STATUS", IntAtom 10), ("CITY", StringAtom "Paris")],
307+ M.fromList [("S#", StringAtom "S3"), ("SNAME", StringAtom "Blake"), ("STATUS", IntAtom 30), ("CITY", StringAtom "Paris")],
308+ M.fromList [("S#", StringAtom "S4"), ("SNAME", StringAtom "Clark"), ("STATUS", IntAtom 20), ("CITY", StringAtom "London")],
309+ M.fromList [("S#", StringAtom "S5"), ("SNAME", StringAtom "Adams"), ("STATUS", IntAtom 30), ("CITY", StringAtom "Athens")]]
310+
311+s = case relvarS of { Right r -> r }
312+
313+relvarP = mkRelation attributes tupleSet
314+ where
315+ attributes = M.fromList [("P#", Attribute "P#" StringAtomType),
316+ ("PNAME", Attribute "PNAME" StringAtomType),
317+ ("COLOR", Attribute "COLOR" StringAtomType),
318+ ("WEIGHT", Attribute "WEIGHT" StringAtomType),
319+ ("CITY", Attribute "CITY" StringAtomType)]
320+ tupleSet = HS.fromList $ mkRelationTuples attributes [
321+ M.fromList [("P#", StringAtom "P1"), ("PNAME", StringAtom "Nut"), ("COLOR", StringAtom "Red"), ("WEIGHT", IntAtom 12), ("CITY", StringAtom "London")]
322+ ]
323+p = case relvarP of { Right r -> r }
324+
325+relvarSP = mkRelation attributes tupleSet
326+ where
327+ attributes = M.fromList [("S#", Attribute "S#" StringAtomType),
328+ ("P#", Attribute "P#" StringAtomType),
329+ ("QTY", Attribute "QTY" StringAtomType)]
330+ tupleSet = HS.fromList $ mkRelationTuples attributes [
331+ M.fromList [("S#", StringAtom "S1"), ("P#", StringAtom "P1"), ("QTY", IntAtom 300)]
332+ ]
333+
334+sp = case relvarSP of { Right r -> r }
335+
336+grouptest = case x of
337+ Right x -> x
338+ where
339+ x = group2 (S.fromList ["CITY", "SNAME", "S#"]) "X" s
340+
341+ungrouptest = case x of
342+ Right x -> x
343+ where x = ungroup "X" grouptest
RelationAtom.hsnew file+4
@@ -0,0 +1,4 @@
1+module RelationAtom where
2+import RelationType
3+import RelationAttribute
4+
RelationAttribute.hsnew file+35
@@ -0,0 +1,35 @@
1+module RelationAttribute where
2+import RelationType
3+import qualified Data.Map as M
4+import qualified Data.Set as S
5+import qualified Data.List as L
6+
7+renameAttribute :: AttributeName -> Attribute -> Attribute
8+renameAttribute newAttrName (Attribute oldAttrName typeo) = Attribute newAttrName typeo
9+
10+remapWithAttributes :: S.Set AttributeName -> M.Map AttributeName Atom -> M.Map AttributeName Atom
11+remapWithAttributes attrs m = M.intersection m hollowMap
12+ where
13+ hollowMap = M.fromList $ zip (S.toList attrs) (repeat "")
14+
15+attributeNameSet :: Attributes -> S.Set AttributeName
16+attributeNameSet = M.keysSet
17+
18+--checks if set s1 is wholly contained in the set s2
19+attributesContained :: Attributes -> Attributes -> Bool
20+attributesContained attrs1 attrs2 = attributeNamesContained (attributeNameSet attrs1) (attributeNameSet attrs2)
21+
22+attributeNamesContained :: S.Set AttributeName -> S.Set AttributeName -> Bool
23+attributeNamesContained attrs1 attrs2 = S.isSubsetOf attrs1 attrs2
24+
25+--returns the disjunction of the AttributeNameSets
26+nonMatchingAttributeNameSet :: S.Set AttributeName -> S.Set AttributeName -> S.Set AttributeName
27+nonMatchingAttributeNameSet a1 a2 = S.difference (S.union a1 a2) (S.intersection a1 a2)
28+
29+matchingAttributeNameSet :: S.Set AttributeName -> S.Set AttributeName -> S.Set AttributeName
30+matchingAttributeNameSet = S.intersection
31+
32+
33+-- this is sorted so the tuples know in which order to output- the ordering is arbitrary
34+sortedAttributeNameList attrNameSet= L.sort $ S.toList attrNameSet
35+
RelationExpr.hsnew file+137
@@ -0,0 +1,137 @@
1+{-# LANGUAGE GADTs #-}
2+module RelationExpr where
3+import Relation
4+import RelationTuple
5+import RelationType
6+import RelationHTML
7+import qualified Data.Set as S
8+import qualified Data.Map as M
9+import qualified Data.HashSet as HS
10+import Control.Monad.State hiding (join)
11+import Debug.Trace
12+
13+type RelVarContext = M.Map String Relation
14+
15+type AtomName = String
16+
17+data RelationalExpr where
18+ MakeStaticRelation :: Attributes -> RelationTupleSet -> RelationalExpr
19+ --MakeFunctionalRelation (creates a relation from a tuple-generating function, potentially infinite)
20+ --in Tutorial D, relational variables pick up the type of the first relation assigned to them
21+ --relational variables should also be able to be explicitly-typed like in Haskell
22+ RelationVariable :: String -> RelationalExpr
23+ Project :: S.Set AttributeName -> RelationalExpr -> RelationalExpr
24+ Union :: RelationalExpr -> RelationalExpr -> RelationalExpr
25+ Join :: RelationalExpr -> RelationalExpr -> RelationalExpr
26+ Assign :: String -> RelationalExpr -> RelationalExpr
27+ MultipleExpr :: [RelationalExpr] -> RelationalExpr
28+ --Restrict :: RExpr.RestrictionExpr -> RelationalExpr -> RelationalExpr
29+{- maybe break this into multiple steps:
30+1. check relational types for match (attribute counts) (typechecking step
31+2. create an execution plan (another system of nodes, another GADT)
32+3. execute the plan
33+-}
34+ deriving (Show)
35+
36+type RelVarState a = State RelVarContext a
37+
38+eval :: RelationalExpr -> RelVarState (Either RelationalError Relation)
39+eval (Assign relVarName expr) = do
40+ relvarTable <- get
41+ value <- eval expr
42+ case value of
43+ Right rel -> (put $ M.insert relVarName rel relvarTable) >>
44+ (return $ Right rel)
45+ Left err -> return $ Left err
46+
47+eval (RelationVariable name) = do
48+ relvarTable <- get
49+ return $ case M.lookup name relvarTable of
50+ Just res -> Right res
51+ Nothing -> Left (RelationalError 1 (name ++ " has not been defined."))
52+
53+eval (Project attrNameSet expr) = do
54+ rel <- eval expr
55+ case rel of
56+ Right rel -> return $ project attrNameSet rel
57+ Left err -> return $ Left err
58+
59+eval (Union exprA exprB) = do
60+ relA <- eval exprA
61+ relB <- eval exprB
62+ case relA of
63+ Left err -> return $ Left err
64+ Right relA -> case relB of
65+ Left err -> return $ Left err
66+ Right relB -> return $ union relA relB
67+
68+eval (Join exprA exprB) = do
69+ relA <- eval exprA
70+ relB <- eval exprB
71+ case relA of
72+ Left err -> return $ Left err
73+ Right relA -> case relB of
74+ Left err -> return $ Left err
75+ Right relB -> return $ join relA relB
76+
77+eval (MultipleExpr exprs) = do
78+ evald <- mapM eval exprs
79+ return $ last evald
80+
81+eval (MakeStaticRelation attributes tupleSet) = do
82+ case mkRelation attributes tupleSet of
83+ Right rel -> return $ Right rel
84+ Left err -> return $ Left err
85+
86+emptyRelVarContext :: RelVarContext
87+emptyRelVarContext = M.empty
88+
89+basicRelVarContext :: RelVarContext
90+basicRelVarContext = M.fromList [("true", relationTrue),
91+ ("false", relationFalse)]
92+
93+dateExamples :: RelVarContext
94+dateExamples = M.union basicRelVarContext dateRelVars
95+ where
96+ dateRelVars = M.fromList [("S", suppliers),
97+ ("P", products),
98+ ("SP", supplierProducts)]
99+ suppliers = suppliersRel
100+ products = productsRel
101+ supplierProducts = supplierProductsRel
102+
103+suppliersRel = case mkRelation attributes tupleSet of
104+ Right rel -> rel
105+ where
106+ attributes = M.fromList [("S#", Attribute "S#" StringAtomType),
107+ ("SNAME", Attribute "SNAME" StringAtomType),
108+ ("STATUS", Attribute "STATUS" StringAtomType),
109+ ("CITY", Attribute "CITY" StringAtomType)]
110+ tupleSet = HS.fromList $ mkRelationTuples attributes [
111+ M.fromList [("S#", StringAtom "S1") , ("SNAME", StringAtom "Smith"), ("STATUS", IntAtom 20) , ("CITY", StringAtom "London")],
112+ M.fromList [("S#", StringAtom "S2"), ("SNAME", StringAtom "Jones"), ("STATUS", IntAtom 10), ("CITY", StringAtom "Paris")],
113+ M.fromList [("S#", StringAtom "S3"), ("SNAME", StringAtom "Blake"), ("STATUS", IntAtom 30), ("CITY", StringAtom "Paris")],
114+ M.fromList [("S#", StringAtom "S4"), ("SNAME", StringAtom "Clark"), ("STATUS", IntAtom 20), ("CITY", StringAtom "London")],
115+ M.fromList [("S#", StringAtom "S5"), ("SNAME", StringAtom "Adams"), ("STATUS", IntAtom 30), ("CITY", StringAtom "Athens")]]
116+
117+productsRel = case mkRelation attributes tupleSet of
118+ Right rel -> rel
119+ where
120+ attributes = M.fromList [("P#", Attribute "P#" StringAtomType),
121+ ("PNAME", Attribute "PNAME" StringAtomType),
122+ ("COLOR", Attribute "COLOR" StringAtomType),
123+ ("WEIGHT", Attribute "WEIGHT" StringAtomType),
124+ ("CITY", Attribute "CITY" StringAtomType)]
125+ tupleSet = HS.fromList $ mkRelationTuples attributes [
126+ M.fromList [("P#", StringAtom "P1"), ("PNAME", StringAtom "Nut"), ("COLOR", StringAtom "Red"), ("WEIGHT", IntAtom 12), ("CITY", StringAtom "London")]
127+ ]
128+
129+supplierProductsRel = case mkRelation attributes tupleSet of
130+ Right rel -> rel
131+ where
132+ attributes = M.fromList [("S#", Attribute "S#" StringAtomType),
133+ ("P#", Attribute "P#" StringAtomType),
134+ ("QTY", Attribute "QTY" StringAtomType)]
135+ tupleSet = HS.fromList $ mkRelationTuples attributes [
136+ M.fromList [("S#", StringAtom "S1"), ("P#", StringAtom "P1"), ("QTY", IntAtom 300)]
137+ ]
RelationHTML.hsnew file+39
@@ -0,0 +1,39 @@
1+module RelationHTML where
2+import RelationType
3+import Relation
4+import RelationTuple
5+import qualified Data.HashSet as HS
6+import qualified Data.List as L
7+import RelationAttribute
8+
9+attributesAsHTML :: Attributes -> String
10+attributesAsHTML attrs = "<tr>" ++ (foldr folder "" attrNameList) ++ "</tr>"
11+ where
12+ folder attrName acc = acc ++ "<th>" ++ attrName ++ "</th>"
13+ attrNameList = sortedAttributeNameList (attributeNameSet attrs)
14+
15+relationAsHTML :: Relation -> String
16+relationAsHTML rel@(Relation attrNameSet tupleSet) = "<table border=\"1\">" ++ (attributesAsHTML attrNameSet) ++ (tupleSetAsHTML tupleSet) ++ "<tfoot>" ++ tablefooter ++ "</tfoot></table>"
17+ where
18+ tablefooter = "<tr><td colspan=\"100%\">" ++ show (cardinality rel) ++ " tuples</td></tr>"
19+
20+writeHTML :: String -> IO ()
21+writeHTML = writeFile "/home/agentm/rel.html"
22+
23+writeRel :: Relation -> IO ()
24+writeRel = writeHTML . relationAsHTML
25+
26+atomAsHTML :: Atom -> String
27+atomAsHTML (StringAtom s) = s
28+atomAsHTML (IntAtom i) = show i
29+atomAsHTML (RelationAtom rel) = relationAsHTML rel
30+
31+tupleAsHTML :: RelationTuple -> String
32+tupleAsHTML tuple = "<tr>" ++ L.foldr folder "" (tupleSortedAssocs tuple) ++ "</tr>"
33+ where
34+ folder tup acc = acc ++ "<td>" ++ (atomAsHTML (snd tup)) ++ "</td>"
35+
36+tupleSetAsHTML :: RelationTupleSet -> String
37+tupleSetAsHTML tupSet = HS.foldr folder "" tupSet
38+ where
39+ folder tuple acc = acc ++ tupleAsHTML tuple
RelationTest.hsnew file+43
@@ -0,0 +1,43 @@
1+import Test.HUnit
2+import RelationType
3+import Relation
4+import qualified Data.Set as S
5+import qualified Data.Map as M
6+import qualified Data.HashSet as HS
7+
8+testList = TestList [testRelation relationTrue, testRelation relationFalse]
9+main = runTestTT testList
10+
11+testRelation :: Relation -> Test
12+testRelation rel = TestCase $ assertEqual "nope" relationValidation (Right rel)
13+ where
14+ relationValidation = validateRelation rel
15+
16+-- run common relation checks
17+validateRelation :: Relation -> Either RelationalError Relation
18+validateRelation rel = do
19+ validateAttrNamesMatchTupleAttrNames rel
20+ validateAttrTypesMatchTupleAttrTypes rel
21+
22+validateAttrNamesMatchTupleAttrNames :: Relation -> Either RelationalError Relation
23+validateAttrNamesMatchTupleAttrNames rel@(Relation _ tupMapSet)
24+ | HS.null invalidSet = Right rel
25+ | otherwise = Left (RelationalError 1 "Tuple attributes do not match relation attributes")
26+ where
27+ nameCheck (RelationTuple tupMap) = attributeNames rel == M.keysSet tupMap
28+ relAttrNames = attributeNames rel
29+ invalidSet = HS.filter (not . nameCheck) tupMapSet
30+
31+
32+validateAttrTypesMatchTupleAttrTypes :: Relation -> Either RelationalError Relation
33+validateAttrTypesMatchTupleAttrTypes rel@(Relation _ tupMapSet)
34+ | HS.null invalidSet = Right rel
35+ | otherwise = Left (RelationalError 1 "Tuple types do not match relation attribute types")
36+ where
37+ tupleTypeCheck (RelationTuple tupMap) = M.null $ M.filterWithKey attrTypeCheck tupMap
38+ attrTypeCheck attrName atomValue = case attributeTypeForName attrName rel of
39+ Just t -> atomValueType atomValue == t
40+ _ -> False
41+ invalidSet = HS.filter (not . tupleTypeCheck) tupMapSet
42+
43+
RelationTuple.hsnew file+71
@@ -0,0 +1,71 @@
1+module RelationTuple where
2+import RelationType
3+import RelationAttribute
4+import qualified Data.Map as M
5+import qualified Data.Serialize as SZ
6+import qualified Data.Set as S
7+import qualified Data.List as L
8+import qualified Data.HashSet as HS
9+
10+tupleSize :: RelationTuple -> Int
11+tupleSize (RelationTuple m) = M.size m
12+
13+tupleAttributeNameSet :: RelationTuple -> S.Set AttributeName
14+tupleAttributeNameSet (RelationTuple tupMap) = M.keysSet tupMap
15+
16+tupleAtomForAttribute :: RelationTuple -> AttributeName -> Atom
17+tupleAtomForAttribute (RelationTuple tupMap) attrName = tupMap M.! attrName
18+
19+{-instance Show RelationTuple where
20+ show (RelationTuple t) = show $ M.toAscList t-}
21+
22+--in case the oldattr does not exist in the tuple, then return the old tuple
23+tupleRenameAttribute :: AttributeName -> AttributeName -> RelationTuple -> RelationTuple
24+tupleRenameAttribute oldattr newattr old@(RelationTuple tmap) =
25+ if M.notMember oldattr tmap
26+ then old
27+ else
28+ RelationTuple newmap
29+ where
30+ newmap = M.insert newattr oldvalue deletedKeyMap
31+ oldvalue = tmap M.! oldattr
32+ deletedKeyMap = M.delete oldattr tmap
33+
34+--in case the map does not contain the attributes, filter them out
35+mkRelationTuple :: S.Set AttributeName-> M.Map AttributeName Atom -> RelationTuple
36+mkRelationTuple attrs map = RelationTuple newMap
37+ where
38+ --make sure only attributes in the name set are in the tuple
39+ --check that Atoms are all of the same type- change to Either return value
40+ newMap = remapWithAttributes attrs map
41+
42+mkRelationTuples :: Attributes -> [M.Map AttributeName Atom] -> [RelationTuple]
43+mkRelationTuples attrs maplist = map mapper maplist
44+ where
45+ mapper m = RelationTuple $ remapWithAttributes (attributeNameSet attrs) m
46+
47+singleTupleSetJoin :: RelationTuple -> RelationTupleSet -> RelationTupleSet
48+singleTupleSetJoin tup tupSet = HS.foldr tupleJoiner HS.empty tupSet
49+ where
50+ tupleJoiner tuple2 accumulator = case joinedTuple of
51+ Nothing -> accumulator
52+ Just relTuple -> HS.insert relTuple accumulator
53+ where joinedTuple = singleTupleJoin tup tuple2
54+
55+singleTupleJoin :: RelationTuple -> RelationTuple -> Maybe RelationTuple
56+singleTupleJoin (RelationTuple tup1) (RelationTuple tup2) = newTuple
57+ where
58+ newTuple = if M.isSubmapOfBy (==) matchingMap tup2 then
59+ Just $ RelationTuple (M.union tup1 tup2)
60+ else
61+ Nothing
62+ matchingMap = M.intersection tup1 tup2
63+
64+
65+tupleExtend :: RelationTuple -> RelationTuple -> RelationTuple
66+tupleExtend (RelationTuple map1) (RelationTuple map2) = RelationTuple $ M.union map1 map2
67+
68+{- sort the associative list to match the header sorting -}
69+tupleSortedAssocs :: RelationTuple -> [(AttributeName, Atom)]
70+tupleSortedAssocs (RelationTuple tupMap) = L.sortBy (\x y -> compare (fst x) (fst y)) (M.assocs tupMap)
71+
RelationTupleSet.hsnew file+21
@@ -0,0 +1,21 @@
1+module RelationTupleSet where
2+import RelationType
3+import qualified Data.Map as M
4+import qualified Data.Serialize as SZ
5+import qualified Data.Set as S
6+import qualified Data.List as L
7+import qualified Data.HashSet as HS
8+
9+emptyTupleSet = HS.empty
10+emptyAttributeSet = M.empty
11+
12+--ensure that all maps have the same keys and key count
13+verifyRelationTuple :: RelationTupleSet -> (Bool,String)
14+verifyRelationTuple tupleSet = if HS.size failedTupleSet > 0
15+ then (False,"Tuple count or key mismatch: " ++ show failedTupleSet)
16+ else (True, "")
17+ where
18+ allKeys = HS.foldr allKeysFolder S.empty tupleSet
19+ allKeysFolder (RelationTuple m) = (S.union . M.keysSet) m
20+ failedTupleSet = HS.filter (\(RelationTuple m) -> (M.keysSet m) == allKeys) tupleSet
21+
RelationType.hsnew file+30
@@ -0,0 +1,30 @@
1+module RelationType where
2+import qualified Data.Map as M
3+import qualified Data.HashSet as HS
4+import qualified Data.Hashable as Hash
5+
6+data Atom = StringAtom String |
7+ IntAtom Int |
8+ RelationAtom Relation deriving (Show, Eq)
9+
10+data AtomType = StringAtomType |
11+ IntAtomType |
12+ RelationAtomType Attributes deriving (Eq, Show)
13+
14+type AttributeName = String
15+data Attribute = Attribute AttributeName AtomType deriving (Eq, Show)
16+
17+type Attributes = M.Map AttributeName Attribute --attributes keys by attribute name for ease of access
18+
19+type RelationTupleSet = HS.HashSet RelationTuple
20+
21+instance Hash.Hashable RelationTuple where
22+ hash tup = Hash.hash $ show tup
23+
24+data RelationTuple = RelationTuple (M.Map AttributeName Atom) deriving (Eq, Show)
25+
26+data Relation = Relation Attributes RelationTupleSet deriving (Show, Eq)
27+data RelationCardinality = Uncountable | Countable Int deriving (Eq, Show)
28+data RelationSizeInfinite = RelationSizeInfinite
29+
30+data RelationalError = RelationalError Int String deriving (Show,Eq)
TutorialDInterpreter.hsnew file+115
@@ -0,0 +1,115 @@
1+module TutorialDInterpreter where
2+import RelationType
3+import Relation
4+import RelationExpr
5+import RelationTuple
6+import Text.Parsec
7+import Text.Parsec.String
8+import Text.Parsec.Expr
9+import Text.Parsec.Language
10+import qualified Text.Parsec.Token as Token
11+import qualified Data.Set as S
12+import qualified Data.HashSet as HS
13+import qualified Data.Map as M
14+import Control.Applicative ((<$), (<*), (*>), liftA, liftA2)
15+import Control.Monad.State
16+
17+lexer :: Token.TokenParser ()
18+lexer = Token.makeTokenParser tutD
19+ where tutD = emptyDef {
20+ Token.reservedOpNames = ["join", "where", "union"],
21+ Token.reservedNames = [],
22+ Token.identStart = letter,
23+ Token.identLetter = alphaNum}
24+
25+parens = Token.parens lexer
26+reservedOp = Token.reservedOp lexer
27+reserved = Token.reserved lexer
28+braces = Token.braces lexer
29+identifier = Token.identifier lexer
30+comma = Token.comma lexer
31+semi = Token.semi lexer
32+
33+--used in projection
34+attributeList :: Parser [AttributeName]
35+attributeList = braces (sepBy identifier comma)
36+
37+makeRelation :: Parser RelationalExpr
38+makeRelation = do
39+ reservedOp "relation"
40+ attrs <- makeAttributes
41+ return $ MakeStaticRelation attrs HS.empty
42+
43+--used in relation creation
44+makeAttributes :: Parser Attributes
45+makeAttributes = do
46+ attrList <- braces (sepBy attributeAndType comma)
47+ return $ M.fromList $ map toAttributeAssocList attrList
48+ where
49+ toAttributeAssocList attr@(Attribute attrName _) = (attrName, attr)
50+
51+attributeAndType :: Parser Attribute
52+attributeAndType = do
53+ attrName <- identifier
54+ attrTypeName <- identifier
55+ --convert type name into type
56+ case tutDTypeToAtomType attrTypeName of
57+ Just t -> return $ Attribute attrName t
58+ Nothing -> fail (attrTypeName ++ " is not a valid type name.")
59+
60+--convert Tutorial D type to AtomType
61+tutDTypeToAtomType :: String -> Maybe AtomType
62+tutDTypeToAtomType tutDType = case tutDType of
63+ "char" -> Just StringAtomType
64+ "int" -> Just IntAtomType
65+ _ -> Nothing
66+
67+relVarP :: Parser RelationalExpr
68+relVarP = liftA RelationVariable identifier
69+
70+relTerm = parens relExpr
71+ <|> makeRelation
72+ <|> relVarP
73+
74+projectOp = do
75+ attrs <- attributeList
76+ return $ Project (S.fromList attrs)
77+
78+assignP = do
79+ relVarName <- identifier
80+ reservedOp ":="
81+ return $ Assign relVarName
82+
83+relOperators = [
84+ [Postfix projectOp],
85+ [Infix (reservedOp "join" >> return Join) AssocLeft],
86+ [Infix (reservedOp "union" >> return Union) AssocLeft],
87+ [Prefix (try assignP)]
88+ ]
89+
90+relExpr :: Parser RelationalExpr
91+relExpr = buildExpressionParser relOperators relTerm
92+
93+multipleRelExpr :: Parser RelationalExpr
94+multipleRelExpr = do
95+ exprs <- sepBy1 relExpr semi
96+ return $ MultipleExpr exprs
97+
98+parseString :: String -> RelationalExpr
99+parseString str = case parse multipleRelExpr "" str of
100+ Left err -> error $ show err
101+ Right r -> r
102+
103+example1 = "relA {a,b, c}"
104+example2 = "relA join relB"
105+example3 = "relA join relB {x,y,z}"
106+example4 = "(relA) {x,y,z}"
107+example5 = "relA union relB"
108+example6 = "rv1 := true"
109+example7 = "rv1 := relA union relB"
110+example8 = "relA := true; relB := false"
111+example9 = "relA := relation { SNO CHAR }"
112+
113+interpret :: RelVarContext -> String -> (Either RelationalError Relation, RelVarContext)
114+interpret context tutdstring = runState (eval (parseString tutdstring)) context
115+
TutorialDInterpreterTest.hsnew file+25
@@ -0,0 +1,25 @@
1+import TutorialDInterpreter
2+import Test.HUnit
3+import RelationExpr
4+import Relation
5+import RelationType
6+import qualified Data.HashSet as HS
7+import qualified Data.Map as M
8+
9+main = runTestTT (TestList tests)
10+ where
11+ tests = map (\(tutd, expected)->TestCase $ assertTutdEqual tutd expected) simpleRelTests
12+ simpleRelTests = [("true", Right relationTrue),
13+ ("false", Right relationFalse),
14+ ("true union false", Right relationTrue),
15+ ("true; false", Right relationFalse),
16+ ("a:=relation{a int}", mkRelation simpleAAttributes HS.empty)
17+ ]
18+ simpleAAttributes = M.fromList [("a", Attribute "a" IntAtomType)]
19+
20+
21+testSimple1 = TestCase $ assertTutdEqual "true" (Right relationTrue)
22+
23+assertTutdEqual tutd expected = assertEqual tutd interpreted expected
24+ where
25+ interpreted = fst (interpret basicRelVarContext tutd)