support also attribute metadata (i.e. also different attribute types than mere strings) and empty relations (no records) v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 05 Dec 2020 19:17:21 +0100
branchv_0
changeset 3 64f8f8792470
parent 2 d68192f0e960
child 4 c176766462c5
support also attribute metadata (i.e. also different attribute types than mere strings) and empty relations (no records)
src/YAMLCommand.h
--- a/src/YAMLCommand.h	Thu Dec 03 22:31:03 2020 +0100
+++ b/src/YAMLCommand.h	Sat Dec 05 19:17:21 2020 +0100
@@ -50,6 +50,26 @@
 			return event.type;
 		}
 
+		const relpipe::writer::string_t getTypeName() const {
+			return findTypeName(event.type);
+		}
+
+		/** Just for debugging and error handling */
+		static const relpipe::writer::string_t findTypeName(const yaml_event_type_t eventType) {
+			if (eventType == YAML_NO_EVENT) return L"NO";
+			else if (eventType == YAML_STREAM_START_EVENT) return L"STREAM_START";
+			else if (eventType == YAML_STREAM_END_EVENT) return L"STREAM_END";
+			else if (eventType == YAML_DOCUMENT_START_EVENT) return L"DOCUMENT_START";
+			else if (eventType == YAML_DOCUMENT_END_EVENT) return L"DOCUMENT_END";
+			else if (eventType == YAML_ALIAS_EVENT) return L"ALIAS";
+			else if (eventType == YAML_SCALAR_EVENT) return L"SCALAR";
+			else if (eventType == YAML_SEQUENCE_START_EVENT) return L"SEQUENCE_START";
+			else if (eventType == YAML_SEQUENCE_END_EVENT) return L"SEQUENCE_END";
+			else if (eventType == YAML_MAPPING_START_EVENT) return L"MAPPING_START";
+			else if (eventType == YAML_MAPPING_END_EVENT) return L"MAPPING_END";
+			else return L"UNKNOWN";
+		}
+
 		const yaml_event_t* getEvent() const {
 			return &event;
 		}
@@ -94,31 +114,113 @@
 		}
 	};
 
+	using YAMLEvent_p = std::shared_ptr<YAMLEvent>;
+	
 	YAMLParser parser;
-
-	using YAMLEvent_p = std::shared_ptr<YAMLEvent>;
+	std::shared_ptr<relpipe::writer::RelationalWriter> writer;
 
-	enum class State {
-		START,
-		RELATIONS,
-		ATTRIBUTE,
-		RECORDS
-	};
+	relpipe::writer::string_t relationName;
+	std::vector<relpipe::writer::string_t> record;
+	std::vector<relpipe::writer::AttributeMetadata> attributesMetadata;
 
-	State state;
-
-	relpipe::writer::string_t y2s(yaml_char_t* value) {
+	relpipe::writer::string_t y2s(const yaml_char_t* value) {
 		return value ? convertor.from_bytes((const char*) value) : L"";
 	}
 
-	relpipe::writer::string_t findValue(std::vector<relpipe::writer::string_t> record, relpipe::writer::string_t key) {
-		if (record.size() % 2) relpipe::writer::RelpipeWriterException(L"Invalid count of values in the record vector");
-		for (size_t i = 0; i < record.size(); i += 2) {
-			if (record[i] == key) return record[i + 1];
+	relpipe::writer::string_t fetchScalarValue(YAMLEvent_p event) {
+		if (event->getType() == YAML_SCALAR_EVENT) return y2s(event->getEvent()->data.scalar.value);
+		else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected SCALAR but got: " + event->getTypeName());
+	}
+
+	void consumeEvent(const yaml_event_type_t expectedEventType, relpipe::writer::string_t expectedScalarValue = L"") {
+		YAMLEvent_p event = YAMLEvent_p(parser.next());
+		if (!event) throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: missing event: " + YAMLEvent::findTypeName(expectedEventType));
+		if (event->getType() != expectedEventType) throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected event: " + YAMLEvent::findTypeName(expectedEventType) + L", but got: " + event->getTypeName());
+		if (expectedEventType == YAML_SCALAR_EVENT && expectedScalarValue.size() && expectedScalarValue != fetchScalarValue(event)) throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected scalar value: " + expectedScalarValue + L", but got " + fetchScalarValue(event));
+	}
+
+	relpipe::writer::string_t consumeScalarEvent() {
+		YAMLEvent_p event = YAMLEvent_p(parser.next());
+		if (event && event->getType() == YAML_SCALAR_EVENT) return fetchScalarValue(event);
+		else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected SCALAR, but got: " + event->getTypeName());
+	}
+
+	void processRelation() {
+		YAMLEvent_p event = YAMLEvent_p(parser.next());
+		if (event->getType() == YAML_MAPPING_START_EVENT) processRelationWithMetadata();
+		else if (event->getType() == YAML_SEQUENCE_START_EVENT)processRelationWithoutMetadata();
+		else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected MAPPING or SEQUENCE, but got: " + event->getTypeName());
+	}
+
+	void processRelationWithMetadata() {
+		consumeEvent(YAML_SCALAR_EVENT, L"attribute-metadata");
+		consumeEvent(YAML_SEQUENCE_START_EVENT);
+
+		for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_SEQUENCE_END_EVENT; event = YAMLEvent_p(parser.next())) {
+			if (event->getType() != YAML_MAPPING_START_EVENT) throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected MAPPING (attribute-metadata), but got: " + event->getTypeName());
+			relpipe::writer::string_t name;
+			relpipe::writer::string_t type = L"string";
+			for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_MAPPING_END_EVENT; event = YAMLEvent_p(parser.next())) {
+				auto key = fetchScalarValue(event);
+				auto value = consumeScalarEvent();
+				if (key == L"name") name = value;
+				else if (key == L"type") type = value;
+				else; // unsupported metadata, later there might be something useful
+			}
+			attributesMetadata.push_back({name, writer->toTypeId(type)});
 		}
-		return L"";
+
+		writer->startRelation(relationName, attributesMetadata, true);
+
+		YAMLEvent_p event = YAMLEvent_p(parser.next());
+		if (event->getType() == YAML_SCALAR_EVENT && fetchScalarValue(event) == L"record") {
+			consumeEvent(YAML_SEQUENCE_START_EVENT);
+			processRecords();
+			consumeEvent(YAML_MAPPING_END_EVENT);
+		} else if (event->getType() == YAML_MAPPING_END_EVENT) {
+			// empty relation, no records
+		} else {
+			relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected 'record' or MAPPING_END, but got: " + event->getTypeName());
+		}
 	}
 
+	void processRelationWithoutMetadata() {
+		// First record:
+		consumeEvent(YAML_MAPPING_START_EVENT);
+		record.clear();
+		for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_MAPPING_END_EVENT; event = YAMLEvent_p(parser.next())) {
+			auto name = fetchScalarValue(event);
+			auto value = consumeScalarEvent();
+			attributesMetadata.push_back({name, relpipe::writer::TypeId::STRING});
+			record.push_back(value);
+		}
+		writer->startRelation(relationName, attributesMetadata, true);
+		for (auto value : record) writer->writeAttribute(value);
+
+		// Following records:
+		processRecords();
+	}
+
+	void processRecords() {
+		for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_SEQUENCE_END_EVENT; event = YAMLEvent_p(parser.next())) {
+			if (event->getType() != YAML_MAPPING_START_EVENT) throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: expected MAPPING (record), but got: " + event->getTypeName());
+			record.clear();
+			record.resize(attributesMetadata.size());
+			for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_MAPPING_END_EVENT; event = YAMLEvent_p(parser.next())) {
+				auto name = fetchScalarValue(event);
+				auto value = consumeScalarEvent();
+				for (int i = 0; i < attributesMetadata.size(); i++) {
+					if (name == attributesMetadata[i].attributeName) {
+						record[i] = value;
+						break;
+					}
+				}
+			}
+			for (auto value : record) writer->writeAttribute(value);
+		}
+	}
+
+
 public:
 
 	YAMLCommand() {
@@ -129,61 +231,20 @@
 
 	void process(std::istream& input, std::shared_ptr<relpipe::writer::RelationalWriter> writer) {
 		parser.setInput(&input);
+		this->writer = writer;
 
-		relpipe::writer::string_t relationName;
-		std::vector<relpipe::writer::string_t> record;
-		std::vector<relpipe::writer::AttributeMetadata> attributesMetadata;
-
-		state = State::START;
+		consumeEvent(YAML_STREAM_START_EVENT);
+		consumeEvent(YAML_DOCUMENT_START_EVENT);
+		consumeEvent(YAML_MAPPING_START_EVENT);
 
-		for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event; event = YAMLEvent_p(parser.next())) {
-			if (event->getType() == YAML_NO_EVENT) {
-				throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: YAML_NO_EVENT");
-			} else if (event->getType() == YAML_STREAM_START_EVENT) {
-			} else if (event->getType() == YAML_STREAM_END_EVENT) {
-			} else if (event->getType() == YAML_DOCUMENT_START_EVENT) {
-			} else if (event->getType() == YAML_DOCUMENT_END_EVENT) {
-			} else if (event->getType() == YAML_ALIAS_EVENT) {
-			} else if (event->getType() == YAML_SCALAR_EVENT) {
-				relpipe::writer::string_t scalarValue = y2s(event->getEvent()->data.scalar.value);
-				if (state == State::RELATIONS) relationName = scalarValue;
-				else if (state == State::ATTRIBUTE) record.push_back(scalarValue);
-				else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unexpected YAML_SCALAR_EVENT");
-			} else if (event->getType() == YAML_SEQUENCE_START_EVENT) {
-				if (state == State::RELATIONS) state = State::RECORDS;
-				else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unexpected YAML_SEQUENCE_START_EVENT");
-			} else if (event->getType() == YAML_SEQUENCE_END_EVENT) {
-				if (state == State::RECORDS) {
-					state = State::RELATIONS;
-					relationName.clear();
-					attributesMetadata.clear();
-				} else {
-					throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unexpected YAML_SEQUENCE_END_EVENT");
-				}
-			} else if (event->getType() == YAML_MAPPING_START_EVENT) {
-				if (state == State::START) state = State::RELATIONS;
-				else if (state == State::RECORDS) state = State::ATTRIBUTE;
-				else if (state == State::RELATIONS) throw relpipe::writer::RelpipeWriterException(L"Not yet implemented"); // TODO: there might be also a map in the relation value (not only a sequence) that would contain metadata (i.e. data types + support for relation containing no records)
-				else throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unexpected YAML_MAPPING_START_EVENT");
-			} else if (event->getType() == YAML_MAPPING_END_EVENT) {
-				if (state == State::ATTRIBUTE) {
-					if (attributesMetadata.size() == 0) {
-						if (record.size() % 2) relpipe::writer::RelpipeWriterException(L"Invalid count of values in the record vector");
-						for (size_t i = 0; i < record.size(); i += 2) attributesMetadata.push_back(relpipe::writer::AttributeMetadata{record[i], relpipe::writer::TypeId::STRING});
-						writer->startRelation(relationName, attributesMetadata, true);
-					}
-					for (auto m : attributesMetadata) writer->writeAttribute(findValue(record, m.attributeName));
-					record.clear();
-					state = State::RECORDS;
-				} else if (state == State::RELATIONS) {
-					break; // map of the relations ends (root)
-				} else {
-					throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unexpected YAML_MAPPING_END_EVENT");
-				}
-			} else {
-				throw relpipe::writer::RelpipeWriterException(L"Invalid YAML structure: unknown event: " + std::to_wstring(event->getType()));
-			}
+		for (YAMLEvent_p event = YAMLEvent_p(parser.next()); event && event->getType() != YAML_MAPPING_END_EVENT; event = YAMLEvent_p(parser.next())) {
+			relationName = fetchScalarValue(event);
+			attributesMetadata.clear();
+			processRelation();
 		}
+
+		consumeEvent(YAML_DOCUMENT_END_EVENT);
+		consumeEvent(YAML_STREAM_END_EVENT);
 	}
 };