on master help sign in
restore interval data type

all tests passing resolves #120

commitc4193dec480baeea03d66a68fb66462cca8347b7 authorAgentM <agentm@themactionfaction.com> dateWed 9 Aug 2017, 14:41 (9 years ago) parentdeb38d8
12 files changed+164−35
project-m36.cabal+1 src/bin/ProjectM36/Server/RemoteCallTypes/Json.hs+16−1 src/lib/ProjectM36/Atom.hs+7 src/lib/ProjectM36/AtomFunctionError.hs+5 src/lib/ProjectM36/AtomFunctions/Basic.hs+3−1 src/lib/ProjectM36/AtomType.hs+4 src/lib/ProjectM36/Atomable.hs+1−1 src/lib/ProjectM36/Base.hs+5 src/lib/ProjectM36/DataTypes/Interval.hs+101−29 src/lib/ProjectM36/DataTypes/Primitive.hs+1 src/lib/ProjectM36/RelationalExpression.hs+4−1 test/TutorialD/Interpreter.hs+16−2
project-m36.cabal+1
@@ -127,6 +127,7 @@ Library
127127 ProjectM36.DataTypes.Maybe,
128128 ProjectM36.DataTypes.List,
129129 ProjectM36.DataTypes.Primitive,
130+ ProjectM36.DataTypes.Interval,
130131 ProjectM36.MiscUtils,
131132 ProjectM36.Notifications,
132133 ProjectM36.Relation,
src/bin/ProjectM36/Server/RemoteCallTypes/Json.hs+16−1
@@ -79,6 +79,15 @@ instance ToJSON Atom where
7979 "val" .= decodeUtf8 (B64.encode i) ]
8080 toJSON atom@(BoolAtom i) = object [ "type" .= atomTypeForAtom atom,
8181 "val" .= i ]
82+
83+ toJSON atom@(IntervalAtom b e i1 i2) = object [ "type" .= atomTypeForAtom atom,
84+ "val" .= object [
85+ "begin" .= toJSON b,
86+ "end" .= toJSON e,
87+ "beginopen" .= i1,
88+ "endopen" .= i2
89+ ]
90+ ]
8291 toJSON atom@(RelationAtom i) = object [ "type" .= atomTypeForAtom atom,
8392 "val" .= i ]
8493 toJSON (ConstructedAtom dConsName atomtype atomlist) = object [
@@ -91,7 +100,7 @@ instance FromJSON Atom where
91100 parseJSON = withObject "atom" $ \o -> do
92101 atype <- o .: "type"
93102 case atype of
94− TypeVariableType _ -> fail "cannot pass AnyAtomType over the wire"
103+ TypeVariableType _ -> fail "cannot pass TypeVariableType over the wire"
95104 caType@(ConstructedAtomType _ _) -> ConstructedAtom <$> o .: "dataconstructorname" <*> pure caType <*> o .: "atom"
96105 RelationAtomType _ -> do
97106 rel <- o .: "val"
@@ -109,6 +118,12 @@ instance FromJSON Atom where
109118 Left err -> fail ("Failed to parse base64-encoded ByteString: " ++ err)
110119 Right bs -> pure (ByteStringAtom bs)
111120 BoolAtomType -> BoolAtom <$> o .: "val"
121+ IntervalAtomType _ -> IntervalAtom <$>
122+ ((o .: "val") >>= (.: "begin")) <*>
123+ ((o .: "val") >>= (.: "end")) <*>
124+ ((o .: "val") >>= (.: "beginopen")) <*>
125+ ((o .: "val") >>= (.: "endopen"))
126+
112127
113128instance ToJSON Notification
114129instance FromJSON Notification
src/lib/ProjectM36/Atom.hs+7
@@ -6,6 +6,7 @@ import qualified Data.Text as T
66--import Data.Time.Clock
77--import Data.ByteString (ByteString)
88import Text.Read
9+import Data.Monoid
910
1011relationForAtom :: Atom -> Either RelationalError Relation
1112relationForAtom (RelationAtom rel) = Right rel
@@ -29,6 +30,12 @@ atomToText (DayAtom i) = (T.pack . show) i
2930atomToText (DateTimeAtom i) = (T.pack . show) i
3031atomToText (ByteStringAtom i) = (T.pack . show) i
3132atomToText (BoolAtom i) = (T.pack . show) i
33+atomToText (IntervalAtom b e bo be) = beginp <> begin <> "," <> end <> endp
34+ where beginp = if bo then "(" else "["
35+ begin = atomToText b
36+ end = atomToText e
37+ endp = if be then ")" else "]"
38+
3239atomToText (RelationAtom i) = (T.pack . show) i
3340atomToText (ConstructedAtom dConsName _ atoms) = dConsName `T.append` T.intercalate " " (map atomToText atoms)
3441
src/lib/ProjectM36/AtomFunctionError.hs+5
@@ -3,9 +3,14 @@ module ProjectM36.AtomFunctionError where
33import Data.Binary
44import GHC.Generics
55import Control.DeepSeq
6+import Data.Text
67
78data AtomFunctionError = AtomFunctionUserError String |
89 AtomFunctionTypeMismatchError |
10+ InvalidIntervalOrdering |
11+ InvalidIntervalBoundaries |
12+ AtomTypeDoesNotSupportOrdering Text |
13+ AtomTypeDoesNotSupportInterval Text |
914 AtomFunctionBytesDecodingError String
1015 deriving(Generic, Eq, Show, Binary, NFData)
1116
src/lib/ProjectM36/AtomFunctions/Basic.hs+3−1
@@ -4,6 +4,7 @@ import ProjectM36.Base
44import ProjectM36.DataTypes.Day
55import ProjectM36.DataTypes.Either
66import ProjectM36.DataTypes.Maybe
7+import ProjectM36.DataTypes.Interval
78import ProjectM36.AtomFunctions.Primitive
89import ProjectM36.AtomFunction
910import ProjectM36.DataTypes.List
@@ -16,7 +17,8 @@ basicAtomFunctions = HS.unions [primitiveAtomFunctions,
1617 dateTimeAtomFunctions,
1718 eitherAtomFunctions,
1819 maybeAtomFunctions,
19− listAtomFunctions]
20+ listAtomFunctions,
21+ intervalAtomFunctions]
2022
2123--these special atom functions aren't scripted so they can't be serialized normally. Instead, the body remains in the binary and the serialization/deserialization happens by name only.
2224precompiledAtomFunctions :: AtomFunctions
src/lib/ProjectM36/AtomType.hs+4
@@ -234,6 +234,7 @@ atomTypeVerify x@(RelationAtomType attrs1) y@(RelationAtomType attrs2) = do
234234 else
235235 atomTypeVerify (A.atomType attr1) (A.atomType attr2)) $ V.toList (V.zip attrs1 attrs2)
236236 return x
237+atomTypeVerify (IntervalAtomType typA) (IntervalAtomType typB) = atomTypeVerify typA typB
237238atomTypeVerify x y = if x == y then
238239 Right x
239240 else
@@ -250,6 +251,7 @@ prettyAtomType (ConstructedAtomType tConsName typeVarMap) = tConsName `T.append`
250251 showTypeVars (tyVarName, aType) = " (" `T.append` tyVarName `T.append` "::" `T.append` prettyAtomType aType `T.append` ")"
251252-- it would be nice to have the original ordering, but we don't have access to the type constructor here- maybe the typevarmap should be also positional (ordered map?)
252253prettyAtomType (TypeVariableType x) = "?TypeVariableType " <> x <> "?"
254+prettyAtomType (IntervalAtomType tv) = "Interval (" <> prettyAtomType tv <> ")"
253255prettyAtomType aType = T.take (T.length fullName - T.length "AtomType") fullName
254256 where fullName = (T.pack . show) aType
255257
@@ -273,6 +275,7 @@ resolveTypeVariables expectedArgTypes actualArgTypes = do
273275
274276resolveTypeVariable :: AtomType -> AtomType -> TypeVarMap
275277resolveTypeVariable (TypeVariableType tv) typ = M.singleton tv typ
278+resolveTypeVariable (IntervalAtomType ityp) typ = resolveTypeVariable ityp typ
276279resolveTypeVariable (ConstructedAtomType _ _) (ConstructedAtomType _ actualTvMap) = actualTvMap
277280resolveTypeVariable _ _ = M.empty
278281
@@ -283,6 +286,7 @@ resolveFunctionReturnValue funcName tvMap (ConstructedAtomType tCons retMap) = d
283286 pure (ConstructedAtomType tCons (M.intersection tvMap retMap))
284287 else
285288 Left (AtomFunctionTypeVariableResolutionError funcName (fst (head (M.toList diff))))
289+resolveFunctionReturnValue funcName tvMap (IntervalAtomType tv) = IntervalAtomType <$> resolveFunctionReturnValue funcName tvMap tv
286290resolveFunctionReturnValue funcName tvMap (TypeVariableType tvName) = case M.lookup tvName tvMap of
287291 Nothing -> Left (AtomFunctionTypeVariableResolutionError funcName tvName)
288292 Just typ -> pure typ
src/lib/ProjectM36/Atomable.hs+1−1
@@ -102,7 +102,7 @@ instance Atomable Bool where
102102 fromAtom _ = error "improper fromAtom"
103103 toAtomType _ = BoolAtomType
104104 toDatabaseContextExpr _ = NoOperation
105−
105+
106106instance Atomable Relation where
107107 toAtom = RelationAtom
108108 fromAtom (RelationAtom r) = r
src/lib/ProjectM36/Base.hs+5
@@ -34,10 +34,13 @@ data Atom = IntAtom Int |
3434 DateTimeAtom UTCTime |
3535 ByteStringAtom ByteString |
3636 BoolAtom Bool |
37+ IntervalAtom Atom Atom OpenInterval OpenInterval |
3738 RelationAtom Relation |
3839 ConstructedAtom DataConstructorName AtomType [Atom]
3940 deriving (Eq, Show, Binary, Typeable, NFData, Generic)
4041
42+type OpenInterval = Bool
43+
4144instance Hashable Atom where
4245 hashWithSalt salt (ConstructedAtom dConsName _ atoms) = salt `hashWithSalt` atoms
4346 `hashWithSalt` dConsName --AtomType is not hashable
@@ -48,6 +51,7 @@ instance Hashable Atom where
4851 hashWithSalt salt (DateTimeAtom dt) = salt `hashWithSalt` dt
4952 hashWithSalt salt (ByteStringAtom bs) = salt `hashWithSalt` bs
5053 hashWithSalt salt (BoolAtom b) = salt `hashWithSalt` b
54+ hashWithSalt salt (IntervalAtom a1 a2 bo be) = salt `hashWithSalt` a1 `hashWithSalt` a2 `hashWithSalt` bo `hashWithSalt` be
5155 hashWithSalt salt (RelationAtom r) = salt `hashWithSalt` r
5256
5357instance Binary UTCTime where
@@ -71,6 +75,7 @@ data AtomType = IntAtomType |
7175 DateTimeAtomType |
7276 ByteStringAtomType |
7377 BoolAtomType |
78+ IntervalAtomType AtomType |
7479 RelationAtomType Attributes |
7580 ConstructedAtomType TypeConstructorName TypeVarMap |
7681 TypeVariableType TypeVarName
src/lib/ProjectM36/DataTypes/Interval.hs+101−29
@@ -1,35 +1,107 @@
11{-# OPTIONS_GHC -fno-warn-orphans #-}
22module ProjectM36.DataTypes.Interval where
3−import Data.Interval
4−import ProjectM36.Base hiding (Finite)
5−import Data.Binary
6−import ProjectM36.Atom
7−import Data.Typeable
8−import Data.Time.Clock
3+import ProjectM36.AtomFunctionBody
4+import ProjectM36.Base
5+import ProjectM36.AtomType
6+import ProjectM36.DataTypes.Primitive
7+import ProjectM36.AtomFunctionError
8+import qualified Data.HashSet as HS
99
10−instance (Atomable a, Ord a) => Atomable (Interval a)
10+import Debug.Trace
11+
12+-- in lieu of typeclass support, we just hard-code the types which can be part of an interval
13+supportsInterval :: AtomType -> Bool
14+supportsInterval typ = case typ of
15+ IntAtomType -> True
16+ DoubleAtomType -> True
17+ TextAtomType -> False -- just because it supports ordering, doesn't mean it makes sense in an interval
18+ DayAtomType -> True
19+ DateTimeAtomType -> True
20+ ByteStringAtomType -> False
21+ BoolAtomType -> False
22+ IntervalAtomType _ -> False
23+ RelationAtomType _ -> False
24+ ConstructedAtomType _ _ -> False --once we support an interval-style typeclass, we might enable this
25+ TypeVariableType _ -> False
26+
27+supportsOrdering :: AtomType -> Bool
28+supportsOrdering typ = case typ of
29+ IntAtomType -> True
30+ DoubleAtomType -> True
31+ TextAtomType -> True
32+ DayAtomType -> True
33+ DateTimeAtomType -> True
34+ ByteStringAtomType -> False
35+ BoolAtomType -> False
36+ IntervalAtomType _ -> False
37+ RelationAtomType _ -> False
38+ ConstructedAtomType _ _ -> False --once we support an interval-style typeclass, we might enable this
39+ TypeVariableType _ -> False
40+
41+atomCompare :: Atom -> Atom -> Either AtomFunctionError Ordering
42+atomCompare a1 a2 = let aType = atomTypeForAtom a1
43+ go a b = Right (compare a b)
44+ typError = Left (AtomTypeDoesNotSupportOrdering (prettyAtomType aType)) in
45+ if atomTypeForAtom a1 /= atomTypeForAtom a2 then
46+ Left AtomFunctionTypeMismatchError
47+ else if not (supportsOrdering aType) then
48+ typError
49+ else
50+ case (a1, a2) of
51+ (IntAtom a, IntAtom b) -> go a b
52+ (DoubleAtom a, DoubleAtom b) -> go a b
53+ (TextAtom a, TextAtom b) -> go a b
54+ (DayAtom a, DayAtom b) -> go a b
55+ (DateTimeAtom a, DateTimeAtom b) -> go a b
56+ _ -> typError
57+
58+--check that interval is properly ordered and that the boundaries make sense
59+createInterval :: Atom -> Atom -> OpenInterval -> OpenInterval -> Either AtomFunctionError Atom
60+createInterval atom1 atom2 bopen eopen = do
61+ cmp <- atomCompare atom1 atom2
62+ case cmp of
63+ GT -> Left InvalidIntervalOrdering
64+ EQ -> if bopen || eopen then
65+ Left InvalidIntervalBoundaries
66+ else
67+ Right valid
68+ LT -> Right valid
69+ where valid = IntervalAtom atom1 atom2 bopen eopen
70+
71+intervalAtomFunctions :: AtomFunctions
72+intervalAtomFunctions = HS.fromList [
73+ AtomFunction { atomFuncName = "interval",
74+ atomFuncType = [TypeVariableType "a",
75+ TypeVariableType "a",
76+ BoolAtomType,
77+ BoolAtomType,
78+ IntervalAtomType (TypeVariableType "a")],
79+ atomFuncBody = compiledAtomFunctionBody $ \(atom1:atom2:BoolAtom bopen:BoolAtom eopen:_) -> do
80+ let aType = atomTypeForAtom atom1
81+ if supportsInterval aType then
82+ createInterval atom1 atom2 bopen eopen
83+ else
84+ Left (AtomTypeDoesNotSupportInterval (prettyAtomType aType))
85+ },
86+ AtomFunction {
87+ atomFuncName = "interval_overlaps",
88+ atomFuncType = [IntervalAtomType (TypeVariableType "a"),
89+ IntervalAtomType (TypeVariableType "a"),
90+ BoolAtomType],
91+ atomFuncBody = compiledAtomFunctionBody $ \(i1@IntervalAtom{}:i2@IntervalAtom{}:_) -> do
92+ res <- intervalOverlaps i1 i2
93+ pure (BoolAtom res)
94+ }]
1195
12−instance (Binary a) => Binary (Extended a) where
13− put ext = case ext of
14− NegInf -> put (1 :: Int)
15− PosInf -> put (2 :: Int)
16− Finite x -> put (3 :: Int) >> put x
17− get = do
18− flag <- get :: Get Int
19− case flag of
20− 1 -> pure NegInf
21− 2 -> pure PosInf
22− 3 -> Finite <$> get
23− _ -> error "poop"
2496
25−instance (Ord a, Binary a) => Binary (Interval a) where
26− put interv = put (lowerBound interv) >> put (upperBound interv)
27− get = interval <$> get <*> get
97+intervalOverlaps :: Atom -> Atom -> Either AtomFunctionError Bool
98+intervalOverlaps (IntervalAtom i1start i1end i1startopen i1endopen) (IntervalAtom i2start i2end i2startopen i2endopen) = do
99+ cmp1 <- atomCompare i1start i2end
100+ cmp2 <- atomCompare i2start i1end
101+ let startcmp = if i1startopen then oplt else oplte
102+ endcmp = if i2startopen then oplt else oplte
103+ oplte op = op == LT || op == EQ
104+ oplt op = op == LT
105+ pure (startcmp cmp1 && endcmp cmp2)
106+intervalOverlaps _ _ = Left AtomFunctionTypeMismatchError
28107
29−intervalFunctions :: [AtomFunction]
30−intervalFunctions = [AtomFunction { atomFuncName = "interval_dt_member",
31− atomFuncType = [dateTimeAtomType,
32− atomTypeForProxy (Proxy :: Proxy (Interval UTCTime)),
33− boolAtomType],
34− atomFunc = \(dt:dtinterval:_) -> Atom $ member (unsafeCast dt ::UTCTime) (unsafeCast dtinterval :: Interval UTCTime)}
35− ]
src/lib/ProjectM36/DataTypes/Primitive.hs+1
@@ -33,5 +33,6 @@ atomTypeForAtom (DayAtom _) = DayAtomType
3333atomTypeForAtom (DateTimeAtom _) = DateTimeAtomType
3434atomTypeForAtom (ByteStringAtom _) = ByteStringAtomType
3535atomTypeForAtom (BoolAtom _) = BoolAtomType
36+atomTypeForAtom (IntervalAtom a _ _ _) = IntervalAtomType (atomTypeForAtom a)
3637atomTypeForAtom (RelationAtom (Relation attrs _)) = RelationAtomType attrs
3738atomTypeForAtom (ConstructedAtom _ aType _) = aType
src/lib/ProjectM36/RelationalExpression.hs+4−1
@@ -741,7 +741,10 @@ evalAtomExpr tupIn (FunctionAtomExpr funcName arguments ()) = do
741741 evaldArgs <- mapM (liftE . evalAtomExpr tupIn) arguments
742742 case evalAtomFunction func evaldArgs of
743743 Left err -> throwE (AtomFunctionUserError err)
744− Right result -> pure result
744+ Right result -> do
745+ --validate that the result matches the expected type
746+ _ <- either throwE pure (atomTypeVerify (last (atomFuncType func)) (atomTypeForAtom result))
747+ pure result
745748evalAtomExpr tupIn (RelationAtomExpr relExpr) = do
746749 --merge existing state tuple context into new state tuple context to support an arbitrary number of levels, but new attributes trounce old attributes
747750 rstate <- ask
test/TutorialD/Interpreter.hs+16−2
@@ -34,7 +34,7 @@ main = do
3434 tcounts <- runTestTT (TestList tests)
3535 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
3636 where
37− 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, testEmptyCommits]
37+ 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, testEmptyCommits,testIntervalAtom]
3838 simpleRelTests = [("x:=true", Right relationTrue),
3939 ("x:=false", Right relationFalse),
4040 ("x:=true union false", Right relationTrue),
@@ -428,4 +428,18 @@ testEmptyCommits = TestCase $ do
428428 dirty''' <- disconnectedTransactionIsDirty sessionId dbconn
429429 assertEqual "empty delete empty commit" (Right False) dirty'''
430430
431−
431+testIntervalAtom :: Test
432+testIntervalAtom = TestCase $ do
433+ --test interval creation
434+ (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback
435+ executeTutorialD sessionId dbconn "x:=relation{tuple{n 1, a interval(3,4,f,f), b interval(4,5,f,f)}, tuple{n 2,a interval(3,4,t,t), b interval(4,5,t,t)}}"
436+ --test failed interval creation
437+ let err1 = "AtomFunctionUserError InvalidIntervalOrdering"
438+ err2 = "AtomFunctionUserError (AtomTypeDoesNotSupportInterval \"Text\")"
439+ expectTutorialDErr sessionId dbconn (T.isPrefixOf err1) "z:=relation{tuple{a interval(4,3,f,f)}}"
440+ expectTutorialDErr sessionId dbconn (T.isPrefixOf err2) "z:=relation{tuple{a interval(\"s\",\"t\",f,f)}}"
441+
442+ --test interval_overlaps
443+ executeTutorialD sessionId dbconn "y:=x:{c:=interval_overlaps(@a,@b)}"
444+ eRv <- executeRelationalExpr sessionId dbconn (Project (AttributeNames (S.fromList ["n","c"])) (RelationVariable "y" ()))
445+ assertEqual "interval overlap check" (mkRelationFromList (attributesFromList [Attribute "c" BoolAtomType,Attribute "n" IntAtomType]) [[BoolAtom True, IntAtom 1], [BoolAtom False, IntAtom 2]]) eRv