on master help sign in
Merge branch '148_csv_import_parser_refactoring'
commitd7f370e0a22f53bd05c60d8a71e928455dac03a3 authorAgentM <agentm@themactionfaction.com> dateFri 22 Sep 2017, 19:36 (9 years ago) parent6c600cd011e28f
18 files changed+273−74
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+123−20 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+69−11 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+123−20
@@ -1,21 +1,26 @@
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
5−import Data.Char (ord)
11+import Data.Char (ord, isUpper, isSpace)
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
13−import Data.HashMap.Lazy as HM
15+import qualified 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
1924
2025data CsvImportError = CsvParseError String |
2126 AttributeMappingError RelationalError |
@@ -25,25 +30,23 @@ data CsvImportError = CsvParseError String |
2530csvDecodeOptions :: DecodeOptions
2631csvDecodeOptions = DecodeOptions {decDelimiter = fromIntegral (ord ',')}
2732
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
33+csvAsRelation :: Attributes -> TypeConstructorMapping -> BS.ByteString -> Either CsvImportError Relation
34+csvAsRelation attrs tConsMap inString = case APBL.parse (csvWithHeader csvDecodeOptions) inString of
35+ APBL.Fail _ _ err -> Left (CsvParseError err)
36+ APBL.Done _ (headerRaw,vecMapsRaw) -> do
3937 let strHeader = V.map decodeUtf8 headerRaw
4038 strMapRecords = V.map convertMap vecMapsRaw
4139 convertMap hmap = HM.fromList $ L.map (decodeUtf8 *** (T.unpack . decodeUtf8)) (HM.toList hmap)
4240 attrNames = V.map A.attributeName attrs
4341 attrNameSet = S.fromList (V.toList attrNames)
4442 headerSet = S.fromList (V.toList strHeader)
43+ parseAtom attrName aType textIn = case APT.parseOnly (parseCSVAtomP attrName tConsMap aType <* APT.endOfInput) textIn of
44+ Left err -> Left (ParseError (T.pack err))
45+ Right eAtom -> eAtom
4546 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
47+ makeTupleList tupMap = V.toList $ V.map (\attr ->
48+ either (Left . AttributeMappingError) Right $
49+ parseAtom (A.attributeName attr) (A.atomType attr) (T.pack $ tupMap HM.! A.attributeName attr)) attrs
4750 if attrNameSet == headerSet then do
4851 tupleList <- mapM sequence $ V.toList (V.map makeTupleList strMapRecords)
4952 case mkRelationFromList attrs tupleList of
@@ -52,3 +55,103 @@ csvAsRelation inString attrs = case parse (csvWithHeader csvDecodeOptions) inStr
5255 else
5356 Left $ HeaderAttributeMismatchError (S.difference attrNameSet headerSet)
5457
58+
59+parseCSVAtomP :: AttributeName -> TypeConstructorMapping -> AtomType -> APT.Parser (Either RelationalError Atom)
60+parseCSVAtomP _ _ IntegerAtomType = (Right . IntegerAtom) <$> APT.decimal
61+parseCSVAtomP _ _ IntAtomType = Right . IntAtom <$> APT.decimal
62+parseCSVAtomP _ _ DoubleAtomType = Right . DoubleAtom <$> APT.double
63+parseCSVAtomP _ _ TextAtomType = do
64+ s <- quotedString <|> takeToEndOfData
65+ pure (Right (TextAtom s))
66+parseCSVAtomP _ _ DayAtomType = do
67+ dString <- T.unpack <$> takeToEndOfData
68+ case readMaybe dString of
69+ Nothing -> fail ("invalid Day string: " ++ dString)
70+ Just date -> pure (Right (DayAtom date))
71+parseCSVAtomP _ _ DateTimeAtomType = do
72+ dString <- T.unpack <$> takeToEndOfData
73+ case readMaybe dString of
74+ Nothing -> fail ("invalid Date string: " ++ dString)
75+ Just date -> pure (Right (DateTimeAtom date))
76+parseCSVAtomP _ _ ByteStringAtomType = do
77+ bsString <- T.unpack <$> takeToEndOfData
78+ case readMaybe bsString of
79+ Nothing -> fail ("invalid ByteString string: " ++ bsString)
80+ Just bs -> pure (Right (ByteStringAtom bs))
81+parseCSVAtomP _ _ BoolAtomType = do
82+ bString <- T.unpack <$> takeToEndOfData
83+ case readMaybe bString of
84+ Nothing -> fail ("invalid BoolAtom string: " ++ bString)
85+ Just b -> pure (Right (BoolAtom b))
86+parseCSVAtomP attrName tConsMap (IntervalAtomType iType) = do
87+ begin <- (APT.char '[' >> pure False) <|> (APT.char '(' >> pure True)
88+ eBeginv <- parseCSVAtomP attrName tConsMap iType
89+ case eBeginv of
90+ Left err -> pure (Left err)
91+ Right beginv -> do
92+ _ <- APT.char ','
93+ eEndv <- parseCSVAtomP attrName tConsMap iType
94+ case eEndv of
95+ Left err -> pure (Left err)
96+ Right endv -> do
97+ end <- (APT.char ']' >> pure False) <|> (APT.char ')' >> pure True)
98+ pure (Right (IntervalAtom beginv endv begin end))
99+parseCSVAtomP attrName tConsMap typ@(ConstructedAtomType _ tvmap) = do
100+ dConsName <- capitalizedIdentifier
101+ APT.skipSpace
102+ --we need to look up the name right away in order to determine the types of the following arguments
103+ -- grab the data constructor
104+ case findDataConstructor dConsName tConsMap of
105+ Nothing -> pure (Left (NoSuchDataConstructorError dConsName))
106+ Just (_, dConsDef) ->
107+ -- identify the data constructor's expected atom type args
108+ case resolvedAtomTypesForDataConstructorDefArgs tConsMap tvmap dConsDef of
109+ Left err -> pure (Left err)
110+ Right argAtomTypes -> do
111+ atomArgs <- mapM (\argTyp -> let parseNextAtom = parseCSVAtomP attrName tConsMap argTyp <* APT.skipSpace in
112+ case argTyp of
113+ ConstructedAtomType _ _ ->
114+ parens parseNextAtom <|>
115+ parseNextAtom
116+ _ -> parseNextAtom
117+ ) argAtomTypes
118+ case lefts atomArgs of
119+ [] -> pure (Right (ConstructedAtom dConsName typ (rights atomArgs)))
120+ errs -> pure (Left (someErrors errs))
121+parseCSVAtomP attrName _ (RelationAtomType _) = pure (Left (RelationValuedAttributesNotSupportedError [attrName]))
122+parseCSVAtomP _ _ (TypeVariableType x) = pure (Left (TypeConstructorTypeVarMissing x))
123+
124+capitalizedIdentifier :: APT.Parser T.Text
125+capitalizedIdentifier = do
126+ fletter <- APT.satisfy isUpper APT.<?> "capitalized data constructor letter"
127+ rest <- APT.takeWhile (\c -> not (isSpace c || c == ')')) APT.<?> "data constructor name"
128+ pure (fletter `T.cons` rest)
129+
130+--read data for Text.Read parser but be wary of end of interval blocks
131+takeToEndOfData :: APT.Parser T.Text
132+takeToEndOfData = APT.takeWhile (APT.notInClass ",)]")
133+
134+parens :: APT.Parser a -> APT.Parser a
135+parens p = do
136+ APT.skip (== '(')
137+ APT.skipSpace
138+ v <- p
139+ APT.skipSpace
140+ APT.skip (== ')')
141+ pure v
142+
143+quotedString :: APT.Parser T.Text
144+quotedString = do
145+ let escapeMap = [('"','"'), ('n', '\n'), ('r', '\r')]
146+ APT.skip (== '"')
147+ (_, s) <- APT.runScanner [] (\prevl nextChar -> case prevl of
148+ [] -> Just [nextChar]
149+ chars | last chars == '\\' ->
150+ case lookup nextChar escapeMap of
151+ Nothing -> Just (chars ++ [nextChar]) --there is no escape sequence, so leave backslash + char
152+ Just escapeVal -> Just (init chars ++ [escapeVal]) -- nuke the backslash and add the escapeVal
153+ | nextChar == '"' -> Nothing
154+ | otherwise -> Just (chars ++ [nextChar]))
155+ APT.skip (== '"')
156+ pure (T.pack s)
157+
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+69−11
@@ -5,28 +5,86 @@ 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+{-
16+import qualified Data.Text.Lazy as TL
17+import qualified Data.Text.Lazy.Encoding as TE
18+import ProjectM36.Relation.Show.Term
19+import qualified Data.ByteString.Lazy as BS
20+-}
821
922main :: IO ()
1023main = do
11− tcounts <- runTestTT $ TestList [testCSVExport]
24+ tcounts <- runTestTT $ TestList [testCSVExport,
25+ testADTExport]
1226 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
1327
14−--round-trip a basic relation through CSV
28+assertEither :: (Show a) => IO (Either a b) -> IO b
29+assertEither x = do
30+ res <- x
31+ case res of
32+ Left err -> assertFailure (show err) >> undefined
33+ Right val -> pure val
34+
35+testADTExport :: Test
36+testADTExport = TestCase $ do
37+ let adtCSV = "a\nCons 4 (Cons 5 Empty)\n"
38+ attrs = A.attributesFromList [Attribute "a" (listAtomType IntegerAtomType)]
39+ expectedRel = mkRelationFromList attrs [[listCons IntegerAtomType [IntegerAtom 4, IntegerAtom 5]]]
40+ case csvAsRelation attrs basicTypeConstructorMapping adtCSV of
41+ Left err -> assertFailure ("import failure: " <> show err)
42+ Right rel -> assertEqual "import cons list" expectedRel (Right rel)
43+
44+--round-trip various atom types through CSV export/import
1545testCSVExport :: Test
1646testCSVExport = TestCase $ do
17− let attrs = A.attributesFromList [Attribute "S#" TextAtomType,
18− Attribute "SNAME" TextAtomType,
19− Attribute "STATUS" IntAtomType,
20− Attribute "CITY" TextAtomType]
47+ now <- getCurrentTime
48+ testInterval <- assertEither $ pure (createInterval
49+ (DateTimeAtom now)
50+ (DateTimeAtom (addUTCTime 86400 now))
51+ True
52+ False)
53+ let attrs = A.attributesFromList [Attribute "textattr" TextAtomType,
54+ Attribute "integerattr" IntegerAtomType,
55+ Attribute "dayattr" DayAtomType,
56+ Attribute "datetimeattr" DateTimeAtomType,
57+ Attribute "listintegerattr" (listAtomType IntegerAtomType),
58+ Attribute "listtextattr" (listAtomType TextAtomType),
59+ Attribute "intervalattr" (IntervalAtomType DateTimeAtomType)
60+ ]
2161 relOrErr = mkRelationFromList attrs [
22− [TextAtom "S9", TextAtom "Perry", IntAtom 170, TextAtom "Londonderry"],
23− [TextAtom "S8", TextAtom "Mike", IntAtom 150, TextAtom "Boston"]]
62+ [TextAtom "text atom with \"quote\"",
63+ IntegerAtom 123,
64+ DayAtom (fromGregorian 2017 4 10),
65+ DateTimeAtom now,
66+ listCons IntegerAtomType [IntegerAtom 5, IntegerAtom 6, IntegerAtom 7],
67+ listCons TextAtomType [TextAtom "text1", TextAtom "text2"],
68+ testInterval
69+ ],
70+ [TextAtom "second text atom",
71+ IntegerAtom 314,
72+ DayAtom (fromGregorian 1001 6 28),
73+ DateTimeAtom (addUTCTime 360 now),
74+ listCons IntegerAtomType [IntegerAtom 10, IntegerAtom 11, IntegerAtom 12],
75+ listCons TextAtomType [TextAtom "text5\"", TextAtom "text6\r\n"],
76+ testInterval
77+ ]]
78+
2479 case relOrErr of
2580 Left err -> assertFailure $ "export relation creation failure: " ++ show err
2681 Right rel ->
2782 case relationAsCSV rel of
2883 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'
84+ Right csvData ->
85+ --BS.writeFile "/tmp/csv" csvData
86+ --putStrLn (TL.unpack (TE.decodeUtf8 csvData))
87+ case csvAsRelation attrs basicTypeConstructorMapping csvData of -- import csv data back to relation
88+ Left err -> assertFailure $ "re-import failed: " ++ show err
89+ Right rel' -> assertEqual "relation CSV comparison" rel rel'
3290
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