on master help sign in
refactor CSV atom parser to use attoparsec on potential atom values

the previous parser relied solely on Text.Read which made it impossible to represent ADT type tested ADT round trips, quoted text atoms resolves #148

commite7bc48fb7452a349c621e57486b53b225f0e6a5d authorAgentM <agentm@themactionfaction.com> dateFri 22 Sep 2017, 00:01 (9 years ago) parentf901574
18 files changed+267−73
project-m36.cabal+1 src/bin/TutorialD/Interpreter.hs+7−3 src/bin/TutorialD/Interpreter/Import/Base.hs+3−3 src/bin/TutorialD/Interpreter/Import/CSV.hs+4−4 src/lib/ProjectM36/Atom.hs+9−12 src/lib/ProjectM36/AtomType.hs+20−8 src/lib/ProjectM36/Client.hs+13−1 src/lib/ProjectM36/DataConstructorDef.hs+2−1 src/lib/ProjectM36/DataTypes/List.hs+6−1 src/lib/ProjectM36/Relation/Parse/CSV.hs+120−18 src/lib/ProjectM36/Relation/Show/CSV.hs−2 src/lib/ProjectM36/RelationalExpression.hs+3−3 src/lib/ProjectM36/Server.hs+1 src/lib/ProjectM36/Server/EntryPoints.hs+5−2 src/lib/ProjectM36/Server/RemoteCallTypes.hs+3−1 src/lib/ProjectM36/TransGraphRelationalExpression.hs+1−1 test/Relation/Export/CSV.hs+66−12 test/Relation/Import/CSV.hs+3−1
project-m36.cabal+1
@@ -55,6 +55,7 @@ Library
5555 ,time
5656 ,hashable-time
5757 ,old-locale
58+ --used for CSV parsing
5859 ,attoparsec
5960 ,either
6061 ,base64-bytestring
src/bin/TutorialD/Interpreter.hs+7−3
@@ -128,10 +128,14 @@ evalTutorialD sessionId conn safe expr = case expr of
128128 case eImportType of
129129 Left err -> barf err
130130 Right importType -> do
131− exprErr <- evalRelVarDataImportOperator execOp (attributes importType)
132− case exprErr of
131+ eTConsMap <- C.typeConstructorMapping sessionId conn
132+ case eTConsMap of
133133 Left err -> barf err
134− Right dbexpr -> evalTutorialD sessionId conn safe (DatabaseContextExprOp dbexpr)
134+ Right tConsMap -> do
135+ exprErr <- evalRelVarDataImportOperator execOp tConsMap (attributes importType)
136+ case exprErr of
137+ Left err -> barf err
138+ Right dbexpr -> evalTutorialD sessionId conn safe (DatabaseContextExprOp dbexpr)
135139
136140 (ImportDBContextOp execOp) ->
137141 if needsSafe then
src/bin/TutorialD/Interpreter/Import/Base.hs+3−3
@@ -4,7 +4,7 @@ import ProjectM36.Error
44import Data.Monoid
55
66-- | import data into a relation variable
7−data RelVarDataImportOperator = RelVarDataImportOperator RelVarName FilePath (RelVarName -> Attributes -> FilePath -> IO (Either RelationalError DatabaseContextExpr))
7+data RelVarDataImportOperator = RelVarDataImportOperator RelVarName FilePath (RelVarName -> TypeConstructorMapping -> Attributes -> FilePath -> IO (Either RelationalError DatabaseContextExpr))
88
99instance Show RelVarDataImportOperator where
1010 show (RelVarDataImportOperator rv path _) = "RelVarDataImportOperator " <> show rv <> " " <> path
@@ -17,8 +17,8 @@ instance Show DatabaseContextDataImportOperator where
1717
1818-- perhaps create a structure to import a whole transaction graph section in the future
1919
20−evalRelVarDataImportOperator :: RelVarDataImportOperator -> Attributes -> IO (Either RelationalError DatabaseContextExpr)
21−evalRelVarDataImportOperator (RelVarDataImportOperator relVarName path importFunc) attrs = importFunc relVarName attrs path
20+evalRelVarDataImportOperator :: RelVarDataImportOperator -> TypeConstructorMapping -> Attributes -> IO (Either RelationalError DatabaseContextExpr)
21+evalRelVarDataImportOperator (RelVarDataImportOperator relVarName path importFunc) tConsMap attrs = importFunc relVarName tConsMap attrs path
2222
2323evalDatabaseContextDataImportOperator :: DatabaseContextDataImportOperator -> IO (Either RelationalError DatabaseContextExpr)
2424evalDatabaseContextDataImportOperator (DatabaseContextDataImportOperator path importFunc) = importFunc path
src/bin/TutorialD/Interpreter/Import/CSV.hs+4−4
@@ -2,20 +2,20 @@ module TutorialD.Interpreter.Import.CSV where
22import TutorialD.Interpreter.Import.Base
33import ProjectM36.Base
44import ProjectM36.Error
5−import ProjectM36.Relation.Parse.CSV
5+import ProjectM36.Relation.Parse.CSV hiding (quotedString)
66import qualified Data.ByteString.Lazy as BS
77import qualified Data.Text as T
88import Text.Megaparsec.Text
99import TutorialD.Interpreter.Base
1010import Control.Exception
1111
12−importCSVRelation :: RelVarName -> Attributes -> FilePath -> IO (Either RelationalError DatabaseContextExpr)
13−importCSVRelation relVarName attrs pathIn = do
12+importCSVRelation :: RelVarName -> TypeConstructorMapping -> Attributes -> FilePath -> IO (Either RelationalError DatabaseContextExpr)
13+importCSVRelation relVarName tConsMap attrs pathIn = do
1414 --TODO: handle filesystem errors
1515 csvData <- try (BS.readFile pathIn) :: IO (Either IOError BS.ByteString)
1616 case csvData of
1717 Left err -> return $ Left (ImportError $ T.pack (show err))
18− Right csvData' -> case csvAsRelation csvData' attrs of
18+ Right csvData' -> case csvAsRelation attrs tConsMap csvData' of
1919 Left err -> return $ Left (ParseError $ T.pack (show err))
2020 Right csvRel -> return $ Right (Insert relVarName (ExistingRelation csvRel))
2121
src/lib/ProjectM36/Atom.hs+9−12
@@ -5,23 +5,12 @@ import qualified Data.Text as T
55--import Data.Time.Calendar
66--import Data.Time.Clock
77--import Data.ByteString (ByteString)
8−import Text.Read
98import Data.Monoid
109
1110relationForAtom :: Atom -> Either RelationalError Relation
1211relationForAtom (RelationAtom rel) = Right rel
1312relationForAtom _ = Left $ AttributeIsNotRelationValuedError ""
1413
15−makeAtomFromText :: AttributeName -> AtomType -> T.Text -> Either RelationalError Atom
16−makeAtomFromText _ IntAtomType textIn = maybe ((Left . ParseError) textIn) (Right . IntAtom) (readMaybe (T.unpack textIn))
17−makeAtomFromText _ DoubleAtomType textIn = maybe ((Left . ParseError) textIn) (Right . DoubleAtom) (readMaybe (T.unpack textIn))
18−makeAtomFromText _ TextAtomType textIn = maybe ((Left . ParseError) textIn) (Right . TextAtom) (readMaybe (T.unpack textIn))
19−makeAtomFromText _ DayAtomType textIn = maybe ((Left . ParseError) textIn) (Right . DayAtom) (readMaybe (T.unpack textIn))
20−makeAtomFromText _ DateTimeAtomType textIn = maybe ((Left . ParseError) textIn) (Right . DateTimeAtom) (readMaybe (T.unpack textIn))
21−makeAtomFromText _ ByteStringAtomType textIn = maybe ((Left . ParseError) textIn) (Right . ByteStringAtom) (readMaybe (T.unpack textIn))
22−makeAtomFromText _ BoolAtomType textIn = maybe ((Left . ParseError) textIn) (Right . BoolAtom) (readMaybe (T.unpack textIn))
23−makeAtomFromText attrName _ _ = Left $ AtomTypeNotSupported attrName
24−
2514atomToText :: Atom -> T.Text
2615atomToText (IntegerAtom i) = (T.pack . show) i
2716atomToText (IntAtom i) = (T.pack . show) i
@@ -38,7 +27,15 @@ atomToText (IntervalAtom b e bo be) = beginp <> begin <> "," <> end <> endp
3827 endp = if be then ")" else "]"
3928
4029atomToText (RelationAtom i) = (T.pack . show) i
41−atomToText (ConstructedAtom dConsName _ atoms) = dConsName `T.append` T.intercalate " " (map atomToText atoms)
30+atomToText (ConstructedAtom dConsName _ atoms) = dConsName <> dConsArgs
31+ where
32+ parensAtomToText a@(ConstructedAtom _ _ []) = atomToText a
33+ parensAtomToText a@(ConstructedAtom _ _ _) = "(" <> atomToText a <> ")"
34+ parensAtomToText a = atomToText a
35+
36+ dConsArgs = case atoms of
37+ [] -> ""
38+ args -> " " <> T.intercalate " " (map parensAtomToText args)
4239
4340
4441
src/lib/ProjectM36/AtomType.hs+20−8
@@ -41,7 +41,7 @@ atomTypeForDataConstructorName dConsName atomTypesIn tConsList =
4141 typeVars <- resolveDataConstructorTypeVars dCons atomTypesIn tConsList
4242 pure (ConstructedAtomType (TCD.name tCons) typeVars)
4343
44−atomTypeForDataConstructorDefArg :: DataConstructorDefArg -> AtomType -> TypeConstructorMapping -> Either RelationalError AtomType
44+atomTypeForDataConstructorDefArg :: DataConstructorDefArg -> AtomType -> TypeConstructorMapping -> Either RelationalError AtomType
4545atomTypeForDataConstructorDefArg (DataConstructorDefTypeConstructorArg tCons) aType tConss =
4646 case isValidAtomTypeForTypeConstructor aType tCons tConss of
4747 Just err -> Left err
@@ -134,13 +134,15 @@ validateTypeConstructorDef tConsDef dConsList = execWriter $ do
134134
135135-- | Create an atom type iff all type variables are provided.
136136-- Either Int Text -> ConstructedAtomType "Either" {Int , Text}
137−atomTypeForTypeConstructor :: TypeConstructor -> TypeConstructorMapping -> Either RelationalError AtomType
138−atomTypeForTypeConstructor (PrimitiveTypeConstructor _ aType) _ = Right aType
139−atomTypeForTypeConstructor (TypeVariable tvname) _ = Right (TypeVariableType tvname)
140−atomTypeForTypeConstructor tCons tConss = case findTypeConstructor (TC.name tCons) tConss of
137+atomTypeForTypeConstructor :: TypeConstructor -> TypeConstructorMapping -> TypeVarMap -> Either RelationalError AtomType
138+atomTypeForTypeConstructor (PrimitiveTypeConstructor _ aType) _ _ = Right aType
139+atomTypeForTypeConstructor (TypeVariable tvname) _ tvMap = case M.lookup tvname tvMap of
140+ Nothing -> Right (TypeVariableType tvname)
141+ Just typ -> Right typ
142+atomTypeForTypeConstructor tCons tConss tvMap = case findTypeConstructor (TC.name tCons) tConss of
141143 Nothing -> Left (NoSuchTypeConstructorError (TC.name tCons))
142144 Just (tConsDef, _) -> do
143− tConsArgTypes <- mapM (`atomTypeForTypeConstructor` tConss) (TC.arguments tCons)
145+ tConsArgTypes <- mapM (\tConsArg -> atomTypeForTypeConstructor tConsArg tConss tvMap) (TC.arguments tCons)
144146 let pVarNames = TCD.typeVars tConsDef
145147 tConsArgs = M.fromList (zip pVarNames tConsArgTypes)
146148 Right (ConstructedAtomType (TC.name tCons) tConsArgs)
@@ -245,7 +247,7 @@ atomTypeVerify x y = if x == y then
245247 else
246248 Left $ AtomTypeMismatchError x y
247249
248−-- | Determine if two typeVar
250+-- | Determine if two typeVars are logically compatible.
249251typeVarMapsVerify :: TypeVarMap -> TypeVarMap -> Bool
250252typeVarMapsVerify a b = M.keysSet a == M.keysSet b && (length . rights) (map (\((_,v1),(_,v2)) -> atomTypeVerify v1 v2) (zip (M.toAscList a) (M.toAscList b))) == M.size a
251253
@@ -295,4 +297,14 @@ resolveFunctionReturnValue funcName tvMap (IntervalAtomType tv) = IntervalAtomTy
295297resolveFunctionReturnValue funcName tvMap (TypeVariableType tvName) = case M.lookup tvName tvMap of
296298 Nothing -> Left (AtomFunctionTypeVariableResolutionError funcName tvName)
297299 Just typ -> pure typ
298−resolveFunctionReturnValue _ _ typ = pure typ
300+resolveFunctionReturnValue _ _ typ = pure typ
301+
302+-- convert a typevarmap and data constructor definition into a list of atomtypes which represent the arguments-- no type variables are allowed to remain
303+resolvedAtomTypesForDataConstructorDefArgs :: TypeConstructorMapping -> TypeVarMap -> DataConstructorDef -> Either RelationalError [AtomType]
304+resolvedAtomTypesForDataConstructorDefArgs tConsMap tvMap (DataConstructorDef _ args) = mapM (resolvedAtomTypeForDataConstructorDefArg tConsMap tvMap) args
305+
306+resolvedAtomTypeForDataConstructorDefArg :: TypeConstructorMapping -> TypeVarMap -> DataConstructorDefArg -> Either RelationalError AtomType
307+resolvedAtomTypeForDataConstructorDefArg tConsMap tvMap (DataConstructorDefTypeConstructorArg typCons) = atomTypeForTypeConstructor typCons tConsMap tvMap
308+resolvedAtomTypeForDataConstructorDefArg _ tvMap (DataConstructorDefTypeVarNameArg tvName) = case M.lookup tvName tvMap of
309+ Nothing -> Left (DataConstructorUsesUndeclaredTypeVariable tvName)
310+ Just typ -> Right typ
src/lib/ProjectM36/Client.hs+13−1
@@ -24,6 +24,7 @@ module ProjectM36.Client
2424 rollback,
2525 typeForRelationalExpr,
2626 inclusionDependencies,
27+ ProjectM36.Client.typeConstructorMapping,
2728 planForDatabaseContextExpr,
2829 currentSchemaName,
2930 SchemaName,
@@ -828,6 +829,17 @@ inclusionDependencies sessionId (InProcessConnection conf) = do
828829
829830inclusionDependencies sessionId conn@(RemoteProcessConnection _) = remoteCall conn (RetrieveInclusionDependencies sessionId)
830831
832+typeConstructorMapping :: SessionId -> Connection -> IO (Either RelationalError TypeConstructorMapping)
833+typeConstructorMapping sessionId (InProcessConnection conf) = do
834+ let sessions = ipSessions conf
835+ atomically $ do
836+ eSession <- sessionAndSchema sessionId sessions
837+ case eSession of
838+ Left err -> pure $ Left err
839+ Right (session, _) -> --warning, no schema support for typeconstructors
840+ pure (Right (B.typeConstructorMapping (Sess.concreteDatabaseContext session)))
841+typeConstructorMapping sessionId conn@(RemoteProcessConnection _) = remoteCall conn (RetrieveTypeConstructorMapping sessionId)
842+
831843-- | Return an optimized database expression which is logically equivalent to the input database expression. This function can be used to determine which expression will actually be evaluated.
832844planForDatabaseContextExpr :: SessionId -> Connection -> DatabaseContextExpr -> IO (Either RelationalError DatabaseContextExpr)
833845planForDatabaseContextExpr sessionId (InProcessConnection conf) dbExpr = do
@@ -921,7 +933,7 @@ atomTypesAsRelation sessionId (InProcessConnection conf) = do
921933 case eSession of
922934 Left err -> pure (Left err)
923935 Right session ->
924− case typesAsRelation (typeConstructorMapping (Sess.concreteDatabaseContext session)) of
936+ case typesAsRelation (B.typeConstructorMapping (Sess.concreteDatabaseContext session)) of
925937 Left err -> pure (Left err)
926938 Right rel -> pure (Right rel)
927939atomTypesAsRelation sessionId conn@(RemoteProcessConnection _) = remoteCall conn (RetrieveAtomTypesAsRelation sessionId)
src/lib/ProjectM36/DataConstructorDef.hs+2−1
@@ -17,4 +17,5 @@ typeVars (DataConstructorDef _ tConsArgs) = S.unions $ map typeVarsInDefArg tCon
1717
1818typeVarsInDefArg :: DataConstructorDefArg -> S.Set TypeVarName
1919typeVarsInDefArg (DataConstructorDefTypeConstructorArg tCons) = TC.typeVars tCons
20−typeVarsInDefArg (DataConstructorDefTypeVarNameArg pVarName) = S.singleton pVarName
20+typeVarsInDefArg (DataConstructorDefTypeVarNameArg pVarName) = S.singleton pVarName
21+
src/lib/ProjectM36/DataTypes/List.hs+6−1
@@ -46,4 +46,9 @@ listAtomFunctions = HS.fromList [
4646 atomFuncType = [listAtomType (TypeVariableType "a"), maybeAtomType (TypeVariableType "a")],
4747 atomFuncBody = AtomFunctionBody Nothing (\(listAtom:_) -> listMaybeHead listAtom)
4848 }
49− ]
49+ ]
50+
51+--just a private utility function
52+listCons :: AtomType -> [Atom] -> Atom
53+listCons typ [] = ConstructedAtom "Empty" (listAtomType typ) []
54+listCons typ (a:as) = ConstructedAtom "Cons" (listAtomType typ) [a, listCons typ as]
src/lib/ProjectM36/Relation/Parse/CSV.hs+120−18
@@ -1,21 +1,27 @@
11module ProjectM36.Relation.Parse.CSV where
22--parse Relations from CSV
3+import ProjectM36.Base
4+import ProjectM36.Error
5+import ProjectM36.Relation
6+import ProjectM36.AtomType
7+import qualified ProjectM36.Attribute as A
8+
39import Data.Csv.Parser
410import qualified Data.Vector as V
511import Data.Char (ord)
612import qualified Data.ByteString.Lazy as BS
7−import ProjectM36.Base
8−import ProjectM36.Relation
9−import ProjectM36.Error
1013import Data.Text.Encoding (decodeUtf8)
11−import qualified ProjectM36.Attribute as A
1214import qualified Data.Set as S
1315import Data.HashMap.Lazy as HM
1416import qualified Data.List as L
1517import qualified Data.Text as T
16−import Data.Attoparsec.ByteString.Lazy
17−import ProjectM36.Atom
18+import qualified Data.Attoparsec.ByteString.Lazy as APBL
19+import qualified Data.Attoparsec.Text as APT
1820import Control.Arrow
21+import Text.Read hiding (parens)
22+import Control.Applicative
23+import Data.Either
24+import Data.Char
1925
2026data CsvImportError = CsvParseError String |
2127 AttributeMappingError RelationalError |
@@ -25,25 +31,23 @@ data CsvImportError = CsvParseError String |
2531csvDecodeOptions :: DecodeOptions
2632csvDecodeOptions = DecodeOptions {decDelimiter = fromIntegral (ord ',')}
2733
28−--special case from Text- outer quotes are *not* required in CSV, so we have to add them to make it parseable
29−makeAtomFromCSVText :: AttributeName -> AtomType -> T.Text -> Either RelationalError Atom
30−makeAtomFromCSVText attrName aType textIn = makeAtomFromText attrName aType $ if aType == TextAtomType then
31− "\"" `T.append` textIn `T.append` "\""
32− else
33− textIn
34−
35−csvAsRelation :: BS.ByteString -> Attributes -> Either CsvImportError Relation
36−csvAsRelation inString attrs = case parse (csvWithHeader csvDecodeOptions) inString of
37− Fail _ _ err -> Left (CsvParseError err)
38− Done _ (headerRaw,vecMapsRaw) -> do
34+csvAsRelation :: Attributes -> TypeConstructorMapping -> BS.ByteString -> Either CsvImportError Relation
35+csvAsRelation attrs tConsMap inString = case APBL.parse (csvWithHeader csvDecodeOptions) inString of
36+ APBL.Fail _ _ err -> Left (CsvParseError err)
37+ APBL.Done _ (headerRaw,vecMapsRaw) -> do
3938 let strHeader = V.map decodeUtf8 headerRaw
4039 strMapRecords = V.map convertMap vecMapsRaw
4140 convertMap hmap = HM.fromList $ L.map (decodeUtf8 *** (T.unpack . decodeUtf8)) (HM.toList hmap)
4241 attrNames = V.map A.attributeName attrs
4342 attrNameSet = S.fromList (V.toList attrNames)
4443 headerSet = S.fromList (V.toList strHeader)
44+ parseAtom attrName aType textIn = case APT.parseOnly (parseCSVAtomP attrName tConsMap aType <* APT.endOfInput) textIn of
45+ Left err -> Left (ParseError (T.pack err))
46+ Right eAtom -> eAtom
4547 makeTupleList :: HM.HashMap AttributeName String -> [Either CsvImportError Atom]
46− makeTupleList tupMap = V.toList $ V.map (\attr -> either (Left . AttributeMappingError) Right $ makeAtomFromCSVText (A.attributeName attr) (A.atomType attr) (T.pack $ tupMap HM.! A.attributeName attr)) attrs
48+ makeTupleList tupMap = V.toList $ V.map (\attr ->
49+ either (Left . AttributeMappingError) Right $
50+ parseAtom (A.attributeName attr) (A.atomType attr) (T.pack $ tupMap HM.! A.attributeName attr)) attrs
4751 if attrNameSet == headerSet then do
4852 tupleList <- mapM sequence $ V.toList (V.map makeTupleList strMapRecords)
4953 case mkRelationFromList attrs tupleList of
@@ -52,3 +56,101 @@ csvAsRelation inString attrs = case parse (csvWithHeader csvDecodeOptions) inStr
5256 else
5357 Left $ HeaderAttributeMismatchError (S.difference attrNameSet headerSet)
5458
59+
60+parseCSVAtomP :: AttributeName -> TypeConstructorMapping -> AtomType -> APT.Parser (Either RelationalError Atom)
61+parseCSVAtomP _ _ IntegerAtomType = (Right . IntegerAtom) <$> APT.decimal
62+parseCSVAtomP _ _ IntAtomType = Right . IntAtom <$> APT.decimal
63+parseCSVAtomP _ _ DoubleAtomType = Right . DoubleAtom <$> APT.double
64+parseCSVAtomP _ _ TextAtomType = Right . TextAtom <$>
65+ (quotedString <|>
66+ takeToEndOfData)
67+parseCSVAtomP _ _ DayAtomType = do
68+ dString <- T.unpack <$> takeToEndOfData
69+ case readMaybe dString of
70+ Nothing -> fail ("invalid Day string: " ++ dString)
71+ Just date -> pure (Right (DayAtom date))
72+parseCSVAtomP _ _ DateTimeAtomType = do
73+ dString <- T.unpack <$> takeToEndOfData
74+ case readMaybe dString of
75+ Nothing -> fail ("invalid Date string: " ++ dString)
76+ Just date -> pure (Right (DateTimeAtom date))
77+parseCSVAtomP _ _ ByteStringAtomType = do
78+ bsString <- T.unpack <$> takeToEndOfData
79+ case readMaybe bsString of
80+ Nothing -> fail ("invalid ByteString string: " ++ bsString)
81+ Just bs -> pure (Right (ByteStringAtom bs))
82+parseCSVAtomP _ _ BoolAtomType = do
83+ bString <- T.unpack <$> takeToEndOfData
84+ case readMaybe bString of
85+ Nothing -> fail ("invalid BoolAtom string: " ++ bString)
86+ Just b -> pure (Right (BoolAtom b))
87+parseCSVAtomP attrName tConsMap (IntervalAtomType iType) = do
88+ begin <- (APT.char '[' >> pure False) <|> (APT.char '(' >> pure True)
89+ eBeginv <- parseCSVAtomP attrName tConsMap iType
90+ case eBeginv of
91+ Left err -> pure (Left err)
92+ Right beginv -> do
93+ _ <- APT.char ','
94+ eEndv <- parseCSVAtomP attrName tConsMap iType
95+ case eEndv of
96+ Left err -> pure (Left err)
97+ Right endv -> do
98+ end <- (APT.char ']' >> pure False) <|> (APT.char ')' >> pure True)
99+ pure (Right (IntervalAtom beginv endv begin end))
100+parseCSVAtomP attrName tConsMap typ@(ConstructedAtomType _ tvmap) = do
101+ dConsName <- capitalizedIdentifier
102+ APT.skipSpace
103+ --we need to look up the name right away in order to determine the types of the following arguments
104+ -- grab the data constructor
105+ case findDataConstructor dConsName tConsMap of
106+ Nothing -> pure (Left (NoSuchDataConstructorError dConsName))
107+ Just (_, dConsDef) -> do
108+ -- identify the data constructor's expected atom type args
109+ case resolvedAtomTypesForDataConstructorDefArgs tConsMap tvmap dConsDef of
110+ Left err -> pure (Left err)
111+ Right argAtomTypes -> do
112+ atomArgs <- mapM (\argTyp -> let parseNextAtom = parseCSVAtomP attrName tConsMap argTyp <* APT.skipSpace in
113+ case argTyp of
114+ ConstructedAtomType _ _ ->
115+ (parens parseNextAtom) <|>
116+ parseNextAtom
117+ _ -> parseNextAtom
118+ ) argAtomTypes
119+ case lefts atomArgs of
120+ [] -> pure (Right (ConstructedAtom dConsName typ (rights atomArgs)))
121+ errs -> pure (Left (someErrors errs))
122+parseCSVAtomP attrName _ (RelationAtomType _) = pure (Left (RelationValuedAttributesNotSupportedError [attrName]))
123+parseCSVAtomP _ _ (TypeVariableType x) = pure (Left (TypeConstructorTypeVarMissing x))
124+
125+capitalizedIdentifier :: APT.Parser T.Text
126+capitalizedIdentifier = do
127+ fletter <- APT.satisfy isUpper APT.<?> "capitalized data constructor letter"
128+ rest <- APT.takeWhile (\c -> not (isSpace c || c == ')')) APT.<?> "data constructor name"
129+ pure (fletter `T.cons` rest)
130+
131+--read data for Text.Read parser but be wary of end of interval blocks
132+takeToEndOfData :: APT.Parser T.Text
133+takeToEndOfData = APT.takeWhile (APT.notInClass ",)]")
134+
135+parens :: APT.Parser a -> APT.Parser a
136+parens p = do
137+ APT.skip (== '(')
138+ APT.skipSpace
139+ v <- p
140+ APT.skipSpace
141+ APT.skip (== ')')
142+ pure v
143+
144+quotedString :: APT.Parser T.Text
145+quotedString = do
146+ APT.skip (== '"')
147+ v <- APT.scan False (\prevEscape nextChar -> if nextChar == '"' && not prevEscape then
148+ Nothing
149+ else if nextChar == '\\' then
150+ Just True
151+ else
152+ Just False
153+ )
154+ APT.skip (== '"')
155+ pure v
156+
src/lib/ProjectM36/Relation/Show/CSV.hs−2
@@ -8,7 +8,6 @@ import qualified Data.ByteString.Lazy as BS
88import qualified Data.Vector as V
99import ProjectM36.Error
1010import qualified Data.Text.Encoding as TE
11−import qualified Data.Text as T
1211import ProjectM36.Atom
1312
1413--spit out error for relations without attributes (since relTrue and relFalse cannot be distinguished then as CSV) and for relations with relation-valued attributes
@@ -42,7 +41,6 @@ instance DefaultOrdered RecordRelationTuple where
4241newtype RecordAtom = RecordAtom {unAtom :: Atom}
4342
4443instance ToField RecordAtom where
45− toField (RecordAtom (ConstructedAtom dConsName _ atomList)) = TE.encodeUtf8 $ dConsName `T.append` T.intercalate " " (map atomToText atomList)
4644 toField (RecordAtom (TextAtom atomVal)) = TE.encodeUtf8 atomVal --squelch extraneous quotes for text type- the CSV library will add them if necessary
4745 toField (RecordAtom atomVal) = (TE.encodeUtf8 . atomToText) atomVal
4846
src/lib/ProjectM36/RelationalExpression.hs+3−3
@@ -497,7 +497,7 @@ evalDatabaseContextIOExpr mScriptSession currentContext (AddAtomFunction funcNam
497497 pure $ case eCompiledFunc of
498498 Left err -> Left (ScriptError err)
499499 Right compiledFunc -> do
500− funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext)) adjustedAtomTypeCons
500+ funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext) M.empty) adjustedAtomTypeCons
501501 let updatedFuncs = HS.insert newAtomFunc atomFuncs
502502 newContext = currentContext { atomFunctions = updatedFuncs }
503503 newAtomFunc = AtomFunction { atomFuncName = funcName,
@@ -534,7 +534,7 @@ evalDatabaseContextIOExpr mScriptSession currentContext (AddDatabaseContextFunct
534534 Left err -> Left (ScriptError err)
535535 Right compiledFunc -> do
536536 --if we are here, we have validated that the written function type is X -> DatabaseContext -> DatabaseContext, so we need to munge the first elements into an array
537− funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext)) atomArgs
537+ funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext) M.empty) atomArgs
538538 let updatedDBCFuncs = HS.insert newDBCFunc (dbcFunctions currentContext)
539539 newContext = currentContext { dbcFunctions = updatedDBCFuncs }
540540 dbcFuncs = dbcFunctions currentContext
@@ -844,7 +844,7 @@ verifyAtomExprTypes rel cons@ConstructedAtomExpr{} expectedType = runExceptT $ d
844844-- | Look up the type's name and create a new attribute.
845845evalAttrExpr :: TypeConstructorMapping -> AttributeExpr -> Either RelationalError Attribute
846846evalAttrExpr aTypes (AttributeAndTypeNameExpr attrName tCons ()) = do
847− aType <- atomTypeForTypeConstructor tCons aTypes
847+ aType <- atomTypeForTypeConstructor tCons aTypes M.empty
848848 Right (Attribute attrName aType)
849849
850850evalAttrExpr _ (NakedAttributeExpr attr) = Right attr
src/lib/ProjectM36/Server.hs+1
@@ -42,6 +42,7 @@ serverDefinition testBool ti = defaultProcess {
4242 handleCall (\conn (ExecuteSchemaExpr sessionId schemaExpr) -> handleExecuteSchemaExpr ti sessionId conn schemaExpr),
4343 handleCall (\conn (RetrieveSessionIsDirty sessionId) -> handleRetrieveSessionIsDirty ti sessionId conn),
4444 handleCall (\conn (ExecuteAutoMergeToHead sessionId strat headName') -> handleExecuteAutoMergeToHead ti sessionId conn strat headName'),
45+ handleCall (\conn (RetrieveTypeConstructorMapping sessionId) -> handleRetrieveTypeConstructorMapping ti sessionId conn),
4546 handleCall (\conn Logout -> handleLogout ti conn)
4647 ] ++ testModeHandlers,
4748 unhandledMessagePolicy = Terminate
src/lib/ProjectM36/Server/EntryPoints.hs+5−2
@@ -1,7 +1,7 @@
11module ProjectM36.Server.EntryPoints where
22import ProjectM36.Base hiding (inclusionDependencies)
33import ProjectM36.IsomorphicSchema
4−import ProjectM36.Client
4+import ProjectM36.Client as C
55import ProjectM36.Error
66import Control.Distributed.Process (Process, ProcessId)
77import Control.Distributed.Process.ManagedProcess (ProcessReply)
@@ -150,5 +150,8 @@ handleExecuteAutoMergeToHead ti sessionId conn strat headName' = do
150150 ret <- timeoutOrDie ti (autoMergeToHead sessionId conn strat headName')
151151 reply ret conn
152152
153−
153+handleRetrieveTypeConstructorMapping :: Timeout -> SessionId -> Connection -> Reply (Either RelationalError TypeConstructorMapping)
154+handleRetrieveTypeConstructorMapping ti sessionId conn = do
155+ ret <- timeoutOrDie ti (C.typeConstructorMapping sessionId conn)
156+ reply ret conn
154157
src/lib/ProjectM36/Server/RemoteCallTypes.hs+3−1
@@ -59,4 +59,6 @@ data TestTimeout = TestTimeout SessionId
5959data RetrieveSessionIsDirty = RetrieveSessionIsDirty SessionId
6060 deriving (Binary, Generic)
6161data ExecuteAutoMergeToHead = ExecuteAutoMergeToHead SessionId MergeStrategy HeadName
62− deriving (Binary, Generic)
62+ deriving (Binary, Generic)
63+data RetrieveTypeConstructorMapping = RetrieveTypeConstructorMapping SessionId
64+ deriving (Binary, Generic)
src/lib/ProjectM36/TransGraphRelationalExpression.hs+1−1
@@ -149,6 +149,6 @@ evalTransGraphExtendTupleExpr (AttributeExtendTupleExpr attrName expr) graph = d
149149evalTransGraphAttributeExpr :: TransactionGraph -> TransGraphAttributeExpr -> Either RelationalError AttributeExpr
150150evalTransGraphAttributeExpr graph (AttributeAndTypeNameExpr attrName tCons tLookup) = do
151151 trans <- lookupTransaction graph tLookup
152− aType <- atomTypeForTypeConstructor tCons (typeConstructorMapping (concreteDatabaseContext trans))
152+ aType <- atomTypeForTypeConstructor tCons (typeConstructorMapping (concreteDatabaseContext trans)) M.empty
153153 pure (NakedAttributeExpr (Attribute attrName aType))
154154evalTransGraphAttributeExpr _ (NakedAttributeExpr attr) = pure (NakedAttributeExpr attr)
test/Relation/Export/CSV.hs+66−12
@@ -5,28 +5,82 @@ import ProjectM36.Relation.Show.CSV
55import ProjectM36.Relation.Parse.CSV
66import qualified ProjectM36.Attribute as A
77import ProjectM36.Relation
8+import ProjectM36.DataTypes.Basic
9+import ProjectM36.DataTypes.List
10+import ProjectM36.DataTypes.Interval
11+import Data.Time.Calendar
12+import Data.Time.Clock
13+import Data.Monoid
14+
15+--import qualified Data.Text.Lazy as TL
16+--import qualified Data.Text.Lazy.Encoding as TE
17+--import ProjectM36.Relation.Show.Term
818
919main :: IO ()
1020main = do
11− tcounts <- runTestTT $ TestList [testCSVExport]
21+ tcounts <- runTestTT $ TestList [testCSVExport,
22+ testADTExport]
1223 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
1324
14−--round-trip a basic relation through CSV
25+assertEither :: (Show a) => IO (Either a b) -> IO b
26+assertEither x = do
27+ res <- x
28+ case res of
29+ Left err -> assertFailure (show err) >> undefined
30+ Right val -> pure val
31+
32+testADTExport :: Test
33+testADTExport = TestCase $ do
34+ let adtCSV = "a\nCons 4 (Cons 5 Empty)\n"
35+ attrs = A.attributesFromList [Attribute "a" (listAtomType IntegerAtomType)]
36+ expectedRel = mkRelationFromList attrs [[listCons IntegerAtomType [IntegerAtom 4, IntegerAtom 5]]]
37+ case csvAsRelation attrs basicTypeConstructorMapping adtCSV of
38+ Left err -> assertFailure ("import failure: " <> show err)
39+ Right rel -> assertEqual "import cons list" expectedRel (Right rel)
40+
41+--round-trip various atom types through CSV export/import
1542testCSVExport :: Test
1643testCSVExport = TestCase $ do
17− let attrs = A.attributesFromList [Attribute "S#" TextAtomType,
18− Attribute "SNAME" TextAtomType,
19− Attribute "STATUS" IntAtomType,
20− Attribute "CITY" TextAtomType]
44+ now <- getCurrentTime
45+ testInterval <- assertEither $ pure (createInterval
46+ (DateTimeAtom now)
47+ (DateTimeAtom (addUTCTime 86400 now))
48+ True
49+ False)
50+ let attrs = A.attributesFromList [Attribute "textattr" TextAtomType,
51+ Attribute "integerattr" IntegerAtomType,
52+ Attribute "dayattr" DayAtomType,
53+ Attribute "datetimeattr" DateTimeAtomType,
54+ Attribute "listintegerattr" (listAtomType IntegerAtomType),
55+ Attribute "textintegerattr" (listAtomType TextAtomType),
56+ Attribute "intervalattr" (IntervalAtomType DateTimeAtomType)
57+ ]
2158 relOrErr = mkRelationFromList attrs [
22− [TextAtom "S9", TextAtom "Perry", IntAtom 170, TextAtom "Londonderry"],
23− [TextAtom "S8", TextAtom "Mike", IntAtom 150, TextAtom "Boston"]]
59+ [TextAtom "text atom with \"quote\"",
60+ IntegerAtom 123,
61+ DayAtom (fromGregorian 2017 4 10),
62+ DateTimeAtom now,
63+ listCons IntegerAtomType [IntegerAtom 5, IntegerAtom 6, IntegerAtom 7],
64+ listCons TextAtomType [TextAtom "text1", TextAtom "text2"],
65+ testInterval
66+ ],
67+ [TextAtom "second text atom",
68+ IntegerAtom 314,
69+ DayAtom (fromGregorian 1001 6 28),
70+ DateTimeAtom (addUTCTime 360 now),
71+ listCons IntegerAtomType [IntegerAtom 10, IntegerAtom 11, IntegerAtom 12],
72+ listCons TextAtomType [TextAtom "text5", TextAtom "text6"],
73+ testInterval
74+ ]]
75+
2476 case relOrErr of
2577 Left err -> assertFailure $ "export relation creation failure: " ++ show err
26− Right rel ->
78+ Right rel -> do
2779 case relationAsCSV rel of
2880 Left err -> assertFailure $ "export failed: " ++ show err
29− Right csvData -> case csvAsRelation csvData attrs of -- import csv data back to relation
30− Left err -> assertFailure $ "re-import failed: " ++ show err
31− Right rel' -> assertEqual "relation CSV comparison" rel rel'
81+ Right csvData -> do
82+ --putStrLn (TL.unpack (TE.decodeUtf8 csvData))
83+ case csvAsRelation attrs basicTypeConstructorMapping csvData of -- import csv data back to relation
84+ Left err -> assertFailure $ "re-import failed: " ++ show err
85+ Right rel' -> assertEqual "relation CSV comparison" rel rel'
3286
test/Relation/Import/CSV.hs+3−1
@@ -3,6 +3,8 @@ import ProjectM36.Base
33import ProjectM36.Relation.Parse.CSV
44import ProjectM36.Relation
55import qualified ProjectM36.Attribute as A
6+import ProjectM36.DataTypes.Basic
7+
68import System.Exit
79import qualified Data.Text.Lazy as T
810import Data.Text.Lazy.Encoding
@@ -23,7 +25,7 @@ testCSVSuccess = TestCase $ do
2325 expectedRel = mkRelationFromList expectedAttrs [
2426 [TextAtom "S9", TextAtom "Perry", IntAtom 170, TextAtom "Londonderry"],
2527 [TextAtom "S8", TextAtom "Mike", IntAtom 150, TextAtom "Boston"]]
26− case csvAsRelation sampleCSV expectedAttrs of
28+ case csvAsRelation expectedAttrs basicTypeConstructorMapping sampleCSV of
2729 Left err -> assertFailure $ show err
2830 Right csvRel -> assertEqual "csv->relation" expectedRel (Right csvRel)
2931