From cc875f00e501f4f8d4bb6a0c97013579818f01d2 Mon Sep 17 00:00:00 2001 From: Nathaniel Rupprecht Date: Sun, 24 Mar 2024 14:35:50 -0400 Subject: [PATCH] Refactor of documents. - Documents can have arrays and sub-documents. - Got rid of document reader, document values can now recursively initialize themselves from a span. - Not following exact BSON anymore. --- .clang-tidy | 6 +- applications/database-string-pk.example.cpp | 63 ++- include/NeverSQL/data/Document.h | 266 +++++++---- include/NeverSQL/database/DataManager.h | 6 +- include/NeverSQL/database/Query.h | 10 +- include/NeverSQL/utility/DataTypes.h | 50 +- source/NeverSQL/data/Document.cpp | 503 +++++++++++++------- source/NeverSQL/data/PageCache.cpp | 1 + source/NeverSQL/database/DataManager.cpp | 23 +- test/UT_Document.cpp | 156 ++++-- 10 files changed, 691 insertions(+), 393 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 42ae8c5..b4154ce 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -108,6 +108,7 @@ CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.StructCase, value: CamelCase } - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } + - { key: readability-identifier-naming.TemplateParameterSuffix, value: _t } - { key: readability-identifier-naming.FunctionCase, value: aNy_CasE } - { key: readability-identifier-naming.VariableCase, value: lower_case } - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } @@ -115,15 +116,10 @@ CheckOptions: - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } - - { key: readability-identifier-naming.EnumConstantPrefix, value: k } - { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase } - - { key: readability-identifier-naming.ConstexprVariablePrefix, value: k } - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } - - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } - { key: readability-identifier-naming.MemberConstantCase, value: CamelCase } - - { key: readability-identifier-naming.MemberConstantPrefix, value: k } - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } - - { key: readability-identifier-naming.StaticConstantPrefix, value: k } - { key: readability-implicit-bool-conversion.AllowIntegerConditions, value: 1 } - { key: readability-implicit-bool-conversion.AllowPointerConditions, value: 1 } - { key: readability-function-cognitive-complexity.IgnoreMacros, value: 1 } \ No newline at end of file diff --git a/applications/database-string-pk.example.cpp b/applications/database-string-pk.example.cpp index c0c7c65..a4117e5 100644 --- a/applications/database-string-pk.example.cpp +++ b/applications/database-string-pk.example.cpp @@ -6,10 +6,10 @@ #include #include "NeverSQL/data/btree/BTree.h" +#include "NeverSQL/data/internals/Utility.h" #include "NeverSQL/database/DataManager.h" #include "NeverSQL/utility/HexDump.h" #include "NeverSQL/utility/PageDump.h" -#include "NeverSQL/data/internals/Utility.h" using neversql::primary_key_t; using namespace lightning; @@ -17,7 +17,7 @@ using namespace lightning; void SetupLogger(Severity min_severity = Severity::Info); int main() { - SetupLogger(Severity::Trace); + SetupLogger(Severity::Info); // ---> Your database path here. std::filesystem::path database_path = "your-path-here"; @@ -33,30 +33,61 @@ int main() { // Create a document. { - neversql::DocumentBuilder builder; - builder.AddEntry("name", "George"); - builder.AddEntry("age", 24); - builder.AddEntry("favorite_color", "blue"); + neversql::Document builder; + builder.AddElement("name", neversql::StringValue{"George"}); + builder.AddElement("age", neversql::IntegralValue{24}); + builder.AddElement("favorite_color", neversql::StringValue{"blue"}); // Add the document. std::string key = "George"; manager.AddValue("elements", neversql::internal::SpanValue(key), builder); } { - neversql::DocumentBuilder builder; - builder.AddEntry("name", "Helen"); - builder.AddEntry("age", 25); + neversql::Document builder; + builder.AddElement("name", neversql::StringValue{"Helen"}); + builder.AddElement("age", neversql::IntegralValue{25}); + + // Sub-document. + { + auto sub_builder = std::make_unique(); + sub_builder->AddElement("favorite_color", neversql::StringValue{"green"}); + + auto array = std::make_unique(neversql::DataTypeEnum::Int32); + array->AddElement(neversql::IntegralValue{33}); + array->AddElement(neversql::IntegralValue{42}); + array->AddElement(neversql::IntegralValue{109}); + sub_builder->AddElement("favorite_numbers", std::move(array)); + builder.AddElement("favorites", std::move(sub_builder)); + } + // Add the document. std::string key = "Helen"; manager.AddValue("elements", neversql::internal::SpanValue(key), builder); } { - neversql::DocumentBuilder builder; - builder.AddEntry("name", "Carson"); - builder.AddEntry("age", 44); + neversql::Document builder; + builder.AddElement("name", neversql::StringValue{"Carson"}); + builder.AddElement("age", neversql::IntegralValue{44}); // Add the document. std::string key = "Carson"; manager.AddValue("elements", neversql::internal::SpanValue(key), builder); } + { + neversql::Document builder; + builder.AddElement("name", neversql::StringValue{"Julia"}); + builder.AddElement("age", neversql::IntegralValue{18}); + // Add the document. + std::string key = "Julia"; + manager.AddValue("elements", neversql::internal::SpanValue(key), builder); + } + + + // Execute a query. + auto iterator = neversql::query::BTreeQueryIterator(manager.Begin("elements"), + neversql::query::LessEqual("age", 40)); + for (; !iterator.IsEnd(); ++iterator) { + auto document = neversql::ReadDocumentFromBuffer(*iterator); + LOG_SEV(Info) << "Found: " << neversql::PrettyPrint(*document); + } auto total_pages = manager.GetDataAccessLayer().GetNumPages(); LOG_SEV(Major) << lightning::formatting::Format("Database has {:L} pages.", total_pages); @@ -69,22 +100,20 @@ int main() { // Search for some elements. - std::vector names_to_check{"Helen"}; + std::vector names_to_check {"Helen"}; for (auto& name : names_to_check) { auto result = manager.Retrieve("elements", neversql::internal::SpanValue(name)); if (result.IsFound()) { - auto& view = result.value_view; - // Interpret the data as a document. - neversql::DocumentReader reader(view); + auto document = neversql::ReadDocumentFromBuffer(result.value_view); LOG_SEV(Info) << formatting::Format( "Found key {:?} on page {:L}, search depth {}, value: \n{@BYELLOW}{}{@RESET}", name, result.search_result.node->GetPageNumber(), result.search_result.GetSearchDepth(), - neversql::PrettyPrint(reader)); + neversql::PrettyPrint(*document)); } else { LOG_SEV(Info) << formatting::Format("{@BRED}Key {:?} was not found.{@RESET}", name); diff --git a/include/NeverSQL/data/Document.h b/include/NeverSQL/data/Document.h index 6dfeae1..c5ef786 100644 --- a/include/NeverSQL/data/Document.h +++ b/include/NeverSQL/data/Document.h @@ -4,141 +4,219 @@ #pragma once +#include +#include +#include #include #include +#include + +#include #include "NeverSQL/utility/DataTypes.h" +#include "internals/Utility.h" namespace neversql { -//! \brief A representation of a document, which is a collection of keys and typed data fields. -class DocumentBuilder { -private: - struct Field { - std::string name; - DataTypeEnum type; - std::variant data; - }; +//! \brief Base class for values that can be stored in documents (include documents themselves). +class DocumentValue { +public: + explicit DocumentValue(DataTypeEnum type); + + virtual ~DocumentValue() = default; + + void WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, bool write_enum = true) const; + + void InitializeFromBuffer(std::span& buffer); + + std::size_t CalculateRequiredSize(bool with_enum = true) const; + + void PrintToStream(std::ostream& out, std::size_t indent = 0) const; - std::vector fields_; + DataTypeEnum GetDataType() const noexcept; - friend void WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, - const DocumentBuilder& document); + std::any GetData() const; + + template + std::optional TryGetAs() const { + if (type_ != GetDataTypeEnum()) { + return std::nullopt; + } + auto data = GetData(); + return std::any_cast(data); + } + +protected: + virtual std::any getData() const = 0; + + //! \brief Write only the data (not the data type enum) to the buffer. + virtual void writeData(lightning::memory::BasicMemoryBuffer& buffer) const = 0; + //! \brief Calculate the size required by the writeData function. + virtual std::size_t calculateRequiredDataSize() const = 0; + //! \brief Initialize the document value from a data representation in a buffer. + virtual void initializeFromBuffer(std::span& buffer) = 0; + + virtual void printToStream(std::ostream& out, std::size_t indent) const = 0; + + DataTypeEnum type_; +}; +class DoubleValue final : public DocumentValue { public: - DocumentBuilder() = default; + DoubleValue(); + explicit DoubleValue(double value); - //! \brief Get the number of fields in the document. - NO_DISCARD std::size_t GetNumFields() const { return fields_.size(); } + double GetValue() const noexcept { return value_; } - //! \brief Calculate the amount of memory required to store the document. - std::size_t CalculateRequiredSize() const; +private: + std::any getData() const override { return value_; } + void writeData(lightning::memory::BasicMemoryBuffer& buffer) const override; + std::size_t calculateRequiredDataSize() const override; + void initializeFromBuffer(std::span& buffer) override; + void printToStream(std::ostream& out, std::size_t indent) const override; + + double value_ {}; +}; + +template +class IntegralValue final : public DocumentValue { +public: + IntegralValue() + : DocumentValue(GetDataTypeEnum()) {} + + explicit IntegralValue(Integral_t value) + : DocumentValue(GetDataTypeEnum()) + , value_(value) {} - template - void AddEntry(const std::string& name, T&& data) { - using type = std::decay_t>; - addEntry(name, std::forward(data)); + Integral_t GetValue() const noexcept { return value_; } + +private: + std::any getData() const override { return value_; } + + void writeData(lightning::memory::BasicMemoryBuffer& buffer) const override { + // Write the data to the buffer. + buffer.Append(internal::SpanValue(value_)); } - template - NO_DISCARD const T& GetEntryAs(const std::string& name) const { - auto it = std::ranges::find_if(fields_, [&name](const auto& field) { return field.name == name; }); - NOSQL_ASSERT(it != fields_.end(), "field '" << name << "' not found"); - NOSQL_ASSERT(it->type == GetDataTypeEnum(), "type mismatch"); - return std::get(it->data); + std::size_t calculateRequiredDataSize() const override { return sizeof(Integral_t); } + + void initializeFromBuffer(std::span& buffer) override { + std::memcpy(&value_, buffer.data(), sizeof(Integral_t)); + buffer = buffer.subspan(sizeof(Integral_t)); } -private: - template - void addEntry(const std::string& name, T&& data) { - if constexpr (std::is_same_v) { - fields_.emplace_back( - Field {.name = name, .type = GetDataTypeEnum(), .data = std::string(data)}); - } - else { - fields_.emplace_back(Field {.name = name, .type = GetDataTypeEnum(), .data = std::forward(data)}); - } + void printToStream(std::ostream& out, [[maybe_unused]] std::size_t indent) const override { + out << value_; } + + Integral_t value_ {}; }; -class DocumentReader { +//! \brief Document value representing a string. +class StringValue final : public DocumentValue { public: - explicit DocumentReader(std::span buffer); + StringValue(); + explicit StringValue(std::string value); - std::size_t GetNumFields() const; + const std::string& GetValue() const noexcept; - std::string_view GetFieldName(std::size_t index) const; +private: + std::any getData() const override { return value_; } - DataTypeEnum GetFieldType(std::size_t index) const; + void writeData(lightning::memory::BasicMemoryBuffer& buffer) const override; + std::size_t calculateRequiredDataSize() const override; + void initializeFromBuffer(std::span& buffer) override; + void printToStream(std::ostream& out, std::size_t indent) const override; - template - NO_DISCARD T GetEntryAs(std::size_t index) const { - NOSQL_REQUIRE(index < fields_.size(), "index " << index << " out of range"); - NOSQL_REQUIRE(fields_[index].type == GetDataTypeEnum(), "type mismatch"); - if constexpr (!std::is_same_v) { - T out; - std::memcpy(&out, fields_[index].data.data(), sizeof(T)); - return out; - } - else { - return std::string(reinterpret_cast(fields_[index].data.data()), - fields_[index].data.size()); - } + std::string value_; +}; + +class ArrayValue final : public DocumentValue { +public: + ArrayValue(); + explicit ArrayValue(DataTypeEnum element_type); + + void AddElement(std::unique_ptr&& value); + + template + requires std::is_base_of_v + void AddElement(DocValue_t&& value) { + values_.emplace_back(std::make_unique(std::forward(value))); } - template - NO_DISCARD T GetEntryAs(const std::string& field_name) const { - auto it = std::ranges::find_if( - fields_, [&field_name](const auto& field) { return field.field_name == field_name; }); - NOSQL_REQUIRE(it != fields_.end(), "field '" << field_name << "' not found"); - return GetEntryAs(std::distance(fields_.begin(), it)); + const DocumentValue& GetElement(std::size_t index) const; + +private: + std::any getData() const override { NOSQL_FAIL("ArrayValue has no GetData"); } + + void writeData(lightning::memory::BasicMemoryBuffer& buffer) const override; + std::size_t calculateRequiredDataSize() const override; + void initializeFromBuffer(std::span& buffer) override; + void printToStream(std::ostream& out, std::size_t indent) const override; + + DataTypeEnum element_type_; + + std::vector> values_; +}; + +//! \brief Document value representing a document. +class Document final : public DocumentValue { +public: + Document(); + + void AddElement(const std::string& name, std::unique_ptr value); + + template + requires std::is_base_of_v + void AddElement(const std::string& name, DocValue_t&& value) { + elements_.emplace_back(name, std::make_unique(std::forward(value))); } - template - NO_DISCARD std::optional TryGetAs(std::size_t index) const { - if (fields_.size() <= index || fields_[index].type != GetDataTypeEnum()) { - return {}; - } - if constexpr (!std::is_same_v) { - T out; - std::memcpy(&out, fields_[index].data.data(), sizeof(T)); - return out; - } - else { - return std::string(reinterpret_cast(fields_[index].data.data()), - fields_[index].data.size()); + std::optional> GetElement(std::string_view name) const; + + std::size_t GetNumFields() const noexcept; + + template + std::optional TryGetAs(std::string_view field_name) const { + if (auto element = GetElement(field_name)) { + return element->get().TryGetAs(); } + return std::nullopt; } - template - NO_DISCARD std::optional TryGetAs(const std::string& field_name) const { - auto it = std::ranges::find_if( - fields_, [&field_name](const auto& field) { return field.field_name == field_name; }); - return TryGetAs(std::distance(fields_.begin(), it)); + template + std::optional TryGetAs(std::size_t index) const { + if (elements_.size() <= index) { + return {}; + } + return elements_[index].second->TryGetAs(); } -private: - //! \brief Scan the data and initialize the field descriptors. - void initialize(); + std::string_view GetFieldName(std::size_t index) const; - struct FieldDescriptor { - std::string_view field_name; - DataTypeEnum type; - std::span data; - }; + DataTypeEnum GetFieldType(std::size_t index) const; - const std::span buffer_; +protected: + std::any getData() const override { NOSQL_FAIL("ArrayValue has no GetData"); } + void writeData(lightning::memory::BasicMemoryBuffer& buffer) const override; + std::size_t calculateRequiredDataSize() const override; + void initializeFromBuffer(std::span& buffer) override; + void printToStream(std::ostream& out, std::size_t indent) const override; - std::vector fields_; + std::vector>> elements_; }; -//! \brief Serialize a document to a memory buffer. -void WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, const DocumentBuilder& document); +inline void WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, const Document& document) { + document.WriteToBuffer(buffer); +} + +//! \brief Read a document value from a buffer. +std::unique_ptr ReadFromBuffer(std::span buffer); -//! \brief Pretty print the contents of a document to a stream. -void PrettyPrint(const DocumentReader& reader, std::ostream& out); +//! \brief Read a document from a buffer. +std::unique_ptr ReadDocumentFromBuffer(std::span buffer, bool expect_enum = true); -//! \brief Pretty print the contents of a document to a string. -std::string PrettyPrint(const DocumentReader& reader); +void PrettyPrint(const Document& document, std::ostream& out); +std::string PrettyPrint(const Document& document); } // namespace neversql \ No newline at end of file diff --git a/include/NeverSQL/database/DataManager.h b/include/NeverSQL/database/DataManager.h index bafd853..25d32b8 100644 --- a/include/NeverSQL/database/DataManager.h +++ b/include/NeverSQL/database/DataManager.h @@ -16,7 +16,7 @@ namespace neversql { struct RetrievalResult { SearchResult search_result; page_size_t cell_offset {}; - std::span value_view {}; + std::span value_view; bool IsFound() const noexcept { return search_result.node.has_value(); } }; @@ -36,7 +36,7 @@ class DataManager { void AddValue(const std::string& collection_name, GeneralKey key, std::span value); - void AddValue(const std::string& collection_name, GeneralKey key, const DocumentBuilder& document); + void AddValue(const std::string& collection_name, GeneralKey key, const Document& document); //! \brief Get a search result for a given key. SearchResult Search(const std::string& collection_name, GeneralKey key) const; @@ -55,7 +55,7 @@ class DataManager { void AddValue(const std::string& collection_name, std::span value); //! \brief Add a document to the database. - void AddValue(const std::string& collection_name, const DocumentBuilder& document); + void AddValue(const std::string& collection_name, const Document& document); //! \brief Get a search result for a given key. SearchResult Search(const std::string& collection_name, primary_key_t key) const; diff --git a/include/NeverSQL/database/Query.h b/include/NeverSQL/database/Query.h index aa7e0f5..a8c442e 100644 --- a/include/NeverSQL/database/Query.h +++ b/include/NeverSQL/database/Query.h @@ -17,7 +17,7 @@ class Condition : public lightning::ImplBase { protected: class Impl : public lightning::ImplBase::Impl { public: - virtual bool Test(const DocumentReader& reader) const = 0; + virtual bool Test(const Document& reader) const = 0; virtual std::shared_ptr Copy() const = 0; }; @@ -25,7 +25,7 @@ class Condition : public lightning::ImplBase { : lightning::ImplBase(impl) {} public: - bool operator()(const DocumentReader& reader) const { return impl()->Test(reader); } + bool operator()(const Document& reader) const { return impl()->Test(reader); } Condition Copy() const { return Condition(impl()->Copy()); } }; @@ -40,7 +40,7 @@ class Comparison : public Condition { : field_name_(std::move(field_name)) , value_(value) {} - bool Test(const DocumentReader& reader) const override { + bool Test(const Document& reader) const override { if (auto field_value = reader.TryGetAs(field_name_)) { return Predicate_t {}(*field_value, value_); } @@ -111,8 +111,8 @@ class BTreeQueryIterator { void advance() { // Find the next valid iterator. for (; !iterator_.IsEnd(); ++iterator_) { - DocumentReader reader(*iterator_); - if (condition_(reader)) { + auto document = ReadDocumentFromBuffer(*iterator_); + if (condition_(*document)) { return; } } diff --git a/include/NeverSQL/utility/DataTypes.h b/include/NeverSQL/utility/DataTypes.h index 7d1efc8..d1ab9c4 100644 --- a/include/NeverSQL/utility/DataTypes.h +++ b/include/NeverSQL/utility/DataTypes.h @@ -9,27 +9,17 @@ namespace neversql { enum class DataTypeEnum : int8_t { + Null = 0, Double = 1, String = 2, Document = 3, Array = 4, - Binary = 5, - // ObjectId = 7, - Boolean = 8, - DateTime = 9, - // Null = 10, - // RegularExpression = 11, - - Int32 = 16, - // Timestamp = 17, - Int64 = 18, - // Decimal128 = 19, - - // MinKey = -1, - // MaxKey = 127, - - // My own additions - UInt64 = 20 + BinaryData = 5, + Boolean = 6, + DateTime = 7, + Int32 = 8, + Int64 = 9, + UInt64 = 10 }; class Document; @@ -40,28 +30,34 @@ template DataTypeEnum GetDataTypeEnum(); template<> -inline DataTypeEnum GetDataTypeEnum() { - return DataTypeEnum::Int32; +inline DataTypeEnum GetDataTypeEnum() { + return DataTypeEnum::Double; } template<> -inline DataTypeEnum GetDataTypeEnum() { - return DataTypeEnum::Double; +inline DataTypeEnum GetDataTypeEnum() { + return DataTypeEnum::String; } +// Document + +// Array + +// Binary data + template<> inline DataTypeEnum GetDataTypeEnum() { return DataTypeEnum::Boolean; } template<> -inline DataTypeEnum GetDataTypeEnum() { - return DataTypeEnum::String; +inline DataTypeEnum GetDataTypeEnum() { + return DataTypeEnum::DateTime; } template<> -inline DataTypeEnum GetDataTypeEnum() { - return DataTypeEnum::DateTime; +inline DataTypeEnum GetDataTypeEnum() { + return DataTypeEnum::Int32; } template<> @@ -76,9 +72,9 @@ inline DataTypeEnum GetDataTypeEnum() { } // namespace detail -template +template DataTypeEnum GetDataTypeEnum() { - return detail::GetDataTypeEnum>(); + return detail::GetDataTypeEnum>(); } } // namespace neversql diff --git a/source/NeverSQL/data/Document.cpp b/source/NeverSQL/data/Document.cpp index 9a00c15..b485abb 100644 --- a/source/NeverSQL/data/Document.cpp +++ b/source/NeverSQL/data/Document.cpp @@ -6,213 +6,358 @@ namespace neversql { -std::size_t DocumentBuilder::CalculateRequiredSize() const { - std::size_t size = 0; - for (const auto& field : fields_) { - // Length of the field name. - size += 2; - // Field name. - size += field.name.size(); - // Type of the field. - size += 1; - // Data. How we do this is type dependent. - std::visit( - [&](auto&& arg) { - using T = std::decay_t; - // Trivially copyable types. - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - size += sizeof(arg); - } - // String has to store its size also. - else if constexpr (std::is_same_v) { - size += sizeof(uint32_t); - size += arg.size(); - } - }, - field.data); +namespace { + +std::unique_ptr makeDocumentValue(DataTypeEnum data_type) { + switch (data_type) { + case DataTypeEnum::Int32: + return std::make_unique>(); + case DataTypeEnum::Int64: + return std::make_unique>(); + case DataTypeEnum::UInt64: + return std::make_unique>(); + // case DataTypeEnum::Double: + // return std::make_unique>(); + case DataTypeEnum::Boolean: + return std::make_unique>(); + // case DataTypeEnum::DateTime: + // return std::make_unique>(); + case DataTypeEnum::String: + return std::make_unique(); + case DataTypeEnum::Document: + return std::make_unique(); + case DataTypeEnum::Array: + return std::make_unique(); + // case DataTypeEnum::BinaryData: + // return std::make_unique(); + default: + NOSQL_FAIL("unknown data type"); } - return size; } -DocumentReader::DocumentReader(std::span buffer) - : buffer_(buffer) { - initialize(); +} // namespace + +// =========================================================================================================== +// DocumentValue +// =========================================================================================================== + +DocumentValue::DocumentValue(DataTypeEnum type) + : type_(type) {} + +void DocumentValue::WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, + bool write_enum) const { + if (write_enum) { + // Write the data type enum to the buffer. + buffer.PushBack(std::bit_cast(type_)); + } + // Write the data. + writeData(buffer); +} + +void DocumentValue::InitializeFromBuffer(std::span& buffer) { + initializeFromBuffer(buffer); +} + +std::size_t DocumentValue::CalculateRequiredSize(bool with_enum) const { + return calculateRequiredDataSize() + (with_enum ? 1 : 0); } -std::size_t DocumentReader::GetNumFields() const { - return fields_.size(); +void DocumentValue::PrintToStream(std::ostream& out, std::size_t indent) const { + printToStream(out, indent); } -std::string_view DocumentReader::GetFieldName(std::size_t index) const { - NOSQL_REQUIRE(index < fields_.size(), "index " << index << " out of range"); - return fields_[index].field_name; +DataTypeEnum DocumentValue::GetDataType() const noexcept { + return type_; } -DataTypeEnum DocumentReader::GetFieldType(std::size_t index) const { - NOSQL_REQUIRE(index < fields_.size(), "index " << index << " out of range"); - return fields_[index].type; +std::any DocumentValue::GetData() const { + return getData(); } -void DocumentReader::initialize() { - // Read the fields. - auto buffer = buffer_; - while (!buffer.empty()) { - FieldDescriptor field_descriptor; +// =========================================================================================================== +// DoubleValue +// =========================================================================================================== - // Read the length of the field name. Two bytes. - uint16_t name_size; - std::memcpy(&name_size, buffer.data(), 2); - NOSQL_ASSERT(name_size < buffer.size() - 2, "field name is impossibly long (" << name_size << " bytes)"); +DoubleValue::DoubleValue() + : DocumentValue(DataTypeEnum::Double) {} + +DoubleValue::DoubleValue(double value) + : DocumentValue(DataTypeEnum::Double) + , value_(value) {} + +void DoubleValue::writeData(lightning::memory::BasicMemoryBuffer& buffer) const { + // Write the data to the buffer. + buffer.Append(internal::SpanValue(value_)); +} + +std::size_t DoubleValue::calculateRequiredDataSize() const { + return sizeof(double); +} + +void DoubleValue::initializeFromBuffer(std::span& buffer) { + std::memcpy(&value_, buffer.data(), sizeof(double)); + buffer = buffer.subspan(sizeof(double)); +} + +void DoubleValue::printToStream(std::ostream& out, [[maybe_unused]] std::size_t indent) const { + out << "\"" << value_ << "\""; +} + +// =========================================================================================================== +// StringValue +// =========================================================================================================== + +StringValue::StringValue() + : DocumentValue(DataTypeEnum::String) {} + +StringValue::StringValue(std::string value) + : DocumentValue(DataTypeEnum::String) + , value_(std::move(value)) {} + +const std::string& StringValue::GetValue() const noexcept { + return value_; +} + +void StringValue::writeData(lightning::memory::BasicMemoryBuffer& buffer) const { + // Write the string length to the buffer. + const auto str_length = static_cast(value_.size()); + buffer.Append(internal::SpanValue(str_length)); + + // Write the string data to the buffer. + buffer.Append(internal::SpanValue(value_)); +} + +std::size_t StringValue::calculateRequiredDataSize() const { + return sizeof(uint32_t) + value_.size(); +} + +void StringValue::initializeFromBuffer(std::span& buffer) { + // Read the string length. + uint32_t str_length {}; + std::memcpy(&str_length, buffer.data(), sizeof(str_length)); + buffer = buffer.subspan(sizeof(str_length)); // Shrink. + + // Read the string data. + value_ = std::string(reinterpret_cast(buffer.data()), str_length); + buffer = buffer.subspan(str_length); // Shrink. +} + +void StringValue::printToStream(std::ostream& out, [[maybe_unused]] std::size_t indent) const { + out << "\"" << value_ << "\""; +} + +// =========================================================================================================== +// ArrayValue +// =========================================================================================================== + +ArrayValue::ArrayValue() + : DocumentValue(DataTypeEnum::Array) + , element_type_(DataTypeEnum::Null) {} + +ArrayValue::ArrayValue(DataTypeEnum element_type) + : DocumentValue(DataTypeEnum::Array) + , element_type_(element_type) {} + +void ArrayValue::AddElement(std::unique_ptr&& value) { + values_.emplace_back(std::move(value)); +} + +const DocumentValue& ArrayValue::GetElement(std::size_t index) const { + NOSQL_REQUIRE(index < values_.size(), "index " << index << " out of range"); + return *values_[index]; +} + +void ArrayValue::writeData(lightning::memory::BasicMemoryBuffer& buffer) const { + // Write the element type to the buffer + buffer.PushBack(std::bit_cast(element_type_)); + + // Write the array size to the buffer. + const auto num_elements = static_cast(values_.size()); + buffer.Append(internal::SpanValue(num_elements)); + + // Write the data to the buffer. + for (auto const& value : values_) { + value->WriteToBuffer(buffer, false); + } +} +std::size_t ArrayValue::calculateRequiredDataSize() const { + return sizeof(DataTypeEnum) + sizeof(uint32_t) + + static_cast( + std::accumulate(values_.begin(), values_.end(), 0, [](std::size_t acc, const auto& value) { + return acc + value->CalculateRequiredSize(false); + })); +} + +void ArrayValue::initializeFromBuffer(std::span& buffer) { + // Read the element type. + std::memcpy(&element_type_, buffer.data(), 1); + buffer = buffer.subspan(1); // Shrink. + + // Get the number of elements in the array. + uint32_t num_elements {}; + std::memcpy(&num_elements, buffer.data(), 4); + buffer = buffer.subspan(4); // Shrink. + + for (std::size_t i = 0; i < num_elements; ++i) { + auto value = makeDocumentValue(element_type_); + value->InitializeFromBuffer(buffer); + values_.emplace_back(std::move(value)); + } +} + +void ArrayValue::printToStream(std::ostream& out, std::size_t indent) const { + out << "[\n"; + for (const auto& value : values_) { + out << std::string(indent + 2, ' '); + value->PrintToStream(out); + out << ",\n"; + } + out << std::string(indent, ' '); + out << "]"; +} + +// =========================================================================================================== +// Document +// =========================================================================================================== + +Document::Document() + : DocumentValue(DataTypeEnum::Document) {} + +void Document::AddElement(const std::string& name, std::unique_ptr value) { + elements_.emplace_back(name, std::move(value)); +} + +std::optional> Document::GetElement(std::string_view name) const { + auto pred = [name](const auto& element) { return element.first == name; }; + auto it = std::ranges::find_if(elements_, pred); + if (it == elements_.end()) { + return {}; + } + return std::optional {std::cref(*it->second)}; +} + +std::size_t Document::GetNumFields() const noexcept { + return elements_.size(); +} + +std::string_view Document::GetFieldName(std::size_t index) const { + NOSQL_ASSERT(index < elements_.size(), "index " << index << " out of range"); + return elements_[index].first; +} + +DataTypeEnum Document::GetFieldType(std::size_t index) const { + NOSQL_ASSERT(index < elements_.size(), "index " << index << " out of range"); + return elements_[index].second->GetDataType(); +} + +void Document::writeData(lightning::memory::BasicMemoryBuffer& buffer) const { + // Write the number of fields in the document to the buffer. + const auto num_elements = elements_.size(); + buffer.Append(internal::SpanValue(num_elements)); + + // Write the data to the buffer. + for (const auto& value : elements_) { + // Write the field name to the buffer. + auto name_length = static_cast(value.first.size()); + // String: string length, then string data. + buffer.Append(internal::SpanValue(name_length)); + buffer.Append(internal::SpanValue(value.first)); + + // Write the field value to the buffer. + value.second->WriteToBuffer(buffer); + } +} + +std::size_t Document::calculateRequiredDataSize() const { + auto size = sizeof(uint64_t); // Number of elements. + for (const auto& [name, value] : elements_) { + size += sizeof(uint16_t) + name.size() + value->CalculateRequiredSize(); + } + return size; +} + +void Document::initializeFromBuffer(std::span& buffer) { + // Read the number of elements in the document. + uint64_t num_elements {}; + std::memcpy(&num_elements, buffer.data(), 8); + buffer = buffer.subspan(8); // Shrink. + + for (std::size_t i = 0; i < num_elements; ++i) { + // Read the length of the field name. + uint16_t name_size {}; + std::memcpy(&name_size, buffer.data(), 2); buffer = buffer.subspan(2); // Shrink. + // Read the field name. - field_descriptor.field_name = std::string_view(reinterpret_cast(buffer.data()), name_size); + std::string field_name(reinterpret_cast(buffer.data()), name_size); buffer = buffer.subspan(name_size); // Shrink. - // Read the type of the field. One byte. - uint8_t type; + + // Read the type of the field. + DataTypeEnum type; std::memcpy(&type, buffer.data(), 1); buffer = buffer.subspan(1); // Shrink. - field_descriptor.type = static_cast(type); - // Read the data. How we do this is type dependent. - switch (field_descriptor.type) { - case DataTypeEnum::Int32: { - field_descriptor.data = buffer.subspan(0, sizeof(int)); - buffer = buffer.subspan(sizeof(int)); // Shrink. - break; - } - - case DataTypeEnum::Double: { - field_descriptor.data = buffer.subspan(0, sizeof(double)); - buffer = buffer.subspan(sizeof(double)); // Shrink. - break; - } - case DataTypeEnum::Boolean: { - field_descriptor.data = buffer.subspan(0, sizeof(bool)); - buffer = buffer.subspan(sizeof(bool)); // Shrink - break; - } - case DataTypeEnum::DateTime: { - field_descriptor.data = buffer.subspan(0, sizeof(DateTime)); - buffer = buffer.subspan(sizeof(DateTime)); // Shrink - break; - } - case DataTypeEnum::String: { - uint32_t str_size; - std::memcpy(&str_size, buffer.data(), sizeof(str_size)); - buffer = buffer.subspan(sizeof(str_size)); // Shrink. - field_descriptor.data = buffer.subspan(0, str_size); - buffer = buffer.subspan(str_size); // Shrink. - break; - } - case DataTypeEnum::Document: - break; - case DataTypeEnum::Array: - break; - case DataTypeEnum::Binary: - break; - case DataTypeEnum::Int64: - field_descriptor.data = buffer.subspan(0, sizeof(int64_t)); - buffer = buffer.subspan(sizeof(int64_t)); // Shrink. - break; - case DataTypeEnum::UInt64: - field_descriptor.data = buffer.subspan(0, sizeof(uint64_t)); - buffer = buffer.subspan(sizeof(uint64_t)); // Shrink. - break; - default: - NOSQL_FAIL(lightning::formatting::Format("unknown data type, uint8_t value was {}", type)); - - } - - fields_.emplace_back(std::move(field_descriptor)); + + // Read the data. + auto value = makeDocumentValue(type); + value->InitializeFromBuffer(buffer); + + // Add the field to the document. + elements_.emplace_back(field_name, std::move(value)); } } -void WriteToBuffer(lightning::memory::BasicMemoryBuffer& buffer, const DocumentBuilder& document) { - for (const auto& field : document.fields_) { - // Write the length of the field name. Two bytes. - auto name_size = static_cast(field.name.size()); - const auto* begin = reinterpret_cast(&name_size); - buffer.Append(begin, begin + 2); - // Write the field name. - buffer.Append(reinterpret_cast(field.name.data()), - reinterpret_cast(field.name.data() + field.name.size())); - // Write the type of the field. One byte. - auto type = static_cast(field.type); - buffer.Append(reinterpret_cast(&type), reinterpret_cast(&type) + 1); - // Write the data. How we do this is type dependent. - std::visit( - [&](auto&& arg) { - using T = std::decay_t; - // Trivially copyable types. - if constexpr (std::is_integral_v || std::is_floating_point_v) { - const auto* ptr = reinterpret_cast(&arg); - buffer.Append(ptr, ptr + sizeof(arg)); - } - // String has to store its size also. - else if constexpr (std::is_same_v) { - // First, write string size. - auto str_size = static_cast(arg.size()); - const auto* ptr = reinterpret_cast(&str_size); - buffer.Append(ptr, ptr + sizeof(str_size)); - // Then write the string itself. - const auto* start_ptr = reinterpret_cast(arg.data()); - buffer.Append(start_ptr, start_ptr + str_size); - } - else { - static_assert(lightning::typetraits::always_false_v, "unhandled type"); - } - }, - field.data); +void Document::printToStream(std::ostream& out, std::size_t indent) const { + out << "{\n"; + for (const auto& [name, value] : elements_) { + out << std::string(indent + 2, ' '); + out << name << ": "; + value->PrintToStream(out, indent + 2); + out << ",\n"; } + out << std::string(indent, ' '); + out << "}"; } -void PrettyPrint(const DocumentReader& reader, std::ostream& out) { - for (std::size_t i = 0; i < reader.GetNumFields(); ++i) { - out << reader.GetFieldName(i) << ": "; - switch (reader.GetFieldType(i)) { - case DataTypeEnum::Int32: { - out << reader.GetEntryAs(i); - break; - } - case DataTypeEnum::Double: { - out << reader.GetEntryAs(i); - break; - } - case DataTypeEnum::Boolean: { - out << (reader.GetEntryAs(i) ? "true" : "false"); - break; - } - case DataTypeEnum::String: { - out << lightning::formatting::Format("{:?}", reader.GetEntryAs(i)); - break; - } - case DataTypeEnum::DateTime: { - out << reader.GetEntryAs(i); - break; - } - default: - NOSQL_FAIL("unknown data type"); - case DataTypeEnum::Document: - out << ""; - break; - case DataTypeEnum::Array: - out << ""; - break; - case DataTypeEnum::Binary: - out << ""; - break; - case DataTypeEnum::Int64: - out << reader.GetEntryAs(i); - break; - } - out << std::endl; +// =========================================================================================================== +// Free functions. +// =========================================================================================================== + +std::unique_ptr ReadFromBuffer(std::span buffer) { + // Read the enum. + DataTypeEnum enum_value; + std::memcpy(&enum_value, buffer.data(), 1); + buffer = buffer.subspan(1); // Shrink. + + auto document_value = makeDocumentValue(enum_value); + document_value->InitializeFromBuffer(buffer); + return document_value; +} + +std::unique_ptr ReadDocumentFromBuffer(std::span buffer, bool expect_enum) { + if (buffer.empty()) { + return {}; + } + if (expect_enum) { + // Read the enum. + DataTypeEnum enum_value; + std::memcpy(&enum_value, buffer.data(), 1); + buffer = buffer.subspan(1); // Shrink. + NOSQL_ASSERT(enum_value == DataTypeEnum::Document, "expected document type"); } + auto document = std::make_unique(); + document->InitializeFromBuffer(buffer); + return document; +} + +void PrettyPrint(const Document& document, std::ostream& out) { + document.PrintToStream(out, 0); } -std::string PrettyPrint(const DocumentReader& reader) { +std::string PrettyPrint(const Document& document) { std::ostringstream out; - PrettyPrint(reader, out); + PrettyPrint(document, out); return out.str(); } -} // namespace neversql \ No newline at end of file +} // namespace neversql diff --git a/source/NeverSQL/data/PageCache.cpp b/source/NeverSQL/data/PageCache.cpp index fc0b099..67f057e 100644 --- a/source/NeverSQL/data/PageCache.cpp +++ b/source/NeverSQL/data/PageCache.cpp @@ -3,6 +3,7 @@ // #include "NeverSQL/data/PageCache.h" +// Other files. namespace neversql { diff --git a/source/NeverSQL/database/DataManager.cpp b/source/NeverSQL/database/DataManager.cpp index e82a909..8bd9a39 100644 --- a/source/NeverSQL/database/DataManager.cpp +++ b/source/NeverSQL/database/DataManager.cpp @@ -30,13 +30,13 @@ DataManager::DataManager(const std::filesystem::path& database_path) LOG_SEV(Trace) << "Loaded collection index from page " << meta.GetIndexPage() << "."; collection_index_ = std::make_unique(meta.GetIndexPage(), page_cache_); - std::size_t num_collections{}; + std::size_t num_collections {}; for (auto it : *collection_index_) { // Interpret the data as a document. - DocumentReader reader(it); + auto document = ReadDocumentFromBuffer(it); - auto collection_name = reader.GetEntryAs("collection_name"); - auto page_number = reader.GetEntryAs("index_page_number"); + auto collection_name = document->TryGetAs("collection_name").value(); + auto page_number = document->TryGetAs("index_page_number").value(); LOG_SEV(Debug) << "Loaded collection named '" << collection_name << "' with index page " << page_number << "."; @@ -53,9 +53,9 @@ void DataManager::AddCollection(const std::string& collection_name, DataTypeEnum auto page_number = btree->GetRootPageNumber(); - neversql::DocumentBuilder document; - document.AddEntry("collection_name", collection_name); - document.AddEntry("index_page_number", page_number); + neversql::Document document; + document.AddElement("collection_name", StringValue {collection_name}); + document.AddElement("index_page_number", IntegralValue {page_number}); // NOTE: This is not the best way to do this, I just want to get something that works. [[maybe_unused]] auto size = document.CalculateRequiredSize(); @@ -79,14 +79,13 @@ void DataManager::AddValue(const std::string& collection_name, it->second->AddValue(key, value); } -void DataManager::AddValue(const std::string& collection_name, - GeneralKey key, - const DocumentBuilder& document) { +void DataManager::AddValue(const std::string& collection_name, GeneralKey key, const Document& document) { // Serialize the document and add it to the database. [[maybe_unused]] auto size = document.CalculateRequiredSize(); lightning::memory::MemoryBuffer buffer; - WriteToBuffer(buffer, document); + document.WriteToBuffer(buffer); + // WriteToBuffer(buffer, document); std::span value(buffer.Data(), buffer.Size()); AddValue(collection_name, key, value); } @@ -135,7 +134,7 @@ void DataManager::AddValue(const std::string& collection_name, std::spansecond->AddValue(value); } -void DataManager::AddValue(const std::string& collection_name, const DocumentBuilder& document) { +void DataManager::AddValue(const std::string& collection_name, const Document& document) { // Serialize the document and add it to the database. // TODO: Deal with documents that are too long. // NOTE: This is not the best way to do this, I just want to get something that works. diff --git a/test/UT_Document.cpp b/test/UT_Document.cpp index 45e35bd..3f6820c 100644 --- a/test/UT_Document.cpp +++ b/test/UT_Document.cpp @@ -9,13 +9,71 @@ using namespace std::string_literals; using namespace std::string_view_literals; +using namespace neversql; + namespace testing { +TEST(Document, Single_Integer) { + Document document; + document.AddElement("Age", IntegralValue {42}); + ASSERT_EQ(document.GetNumFields(), 1); + + // [DataTypeEnum: 1 byte] + // [Num elements: 8 bytes] + // [Field Name Length: 2 bytes] + // [Field Name: 3 bytes] + // -> (element) + // [DataTypeEnum: 1 byte] [Data: 4 bytes] + // Total: 19 bytes + + lightning::memory::MemoryBuffer buffer; + + WriteToBuffer(buffer, document); + std::span written_data(buffer.Data(), buffer.Size()); + EXPECT_EQ(written_data.size(), 19); + EXPECT_EQ(document.CalculateRequiredSize(), 19); + + auto read_document = ReadDocumentFromBuffer(written_data); + + ASSERT_EQ(read_document->GetNumFields(), 1); + EXPECT_EQ(read_document->GetFieldName(0), "Age"sv); + EXPECT_EQ(read_document->GetFieldType(0), DataTypeEnum::Int32); + EXPECT_EQ(read_document->TryGetAs("Age").value(), 42); +} + +TEST(Document, Array) { + Document document; + + ArrayValue array(DataTypeEnum::Int32); + array.AddElement(IntegralValue {1}); + array.AddElement(IntegralValue {3}); + array.AddElement(IntegralValue {5}); + array.AddElement(IntegralValue {7}); + array.AddElement(IntegralValue {9}); + + document.AddElement("elements", std::move(array)); + + // Checks + + lightning::memory::MemoryBuffer buffer; + WriteToBuffer(buffer, document); + std::span written_data(buffer.Data(), buffer.Size()); + EXPECT_EQ(written_data.size(), 45); + EXPECT_EQ(document.CalculateRequiredSize(), 45); + + auto read_document = ReadDocumentFromBuffer(written_data); + ASSERT_EQ(read_document->GetNumFields(), 1); + EXPECT_EQ(read_document->GetFieldName(0), "elements"sv); + EXPECT_EQ(read_document->GetFieldType(0), DataTypeEnum::Array); +} + +TEST(Document, NestedDocument) {} + TEST(Document, Basic) { - neversql::DocumentBuilder document; - document.AddEntry("Age", 42); - document.AddEntry("Birthday", "My business"s); - document.AddEntry("IsAlive", true); + Document document; + document.AddElement("Age", IntegralValue {42}); + document.AddElement("Birthday", StringValue {"My business"}); + document.AddElement("IsAlive", IntegralValue {true}); ASSERT_EQ(document.GetNumFields(), 3); @@ -23,61 +81,57 @@ TEST(Document, Basic) { WriteToBuffer(buffer, document); std::span written_data(buffer.Data(), buffer.Size()); - EXPECT_EQ(written_data.size(), 47); - EXPECT_EQ(document.CalculateRequiredSize(), 47); - - neversql::DocumentReader reader(written_data); - ASSERT_EQ(reader.GetNumFields(), 3); - EXPECT_EQ(reader.GetFieldName(0), "Age"sv); - EXPECT_EQ(reader.GetFieldName(1), "Birthday"sv); - EXPECT_EQ(reader.GetFieldName(2), "IsAlive"sv); - EXPECT_ANY_THROW(reader.GetFieldName(3)); - - EXPECT_EQ(reader.GetFieldType(0), neversql::DataTypeEnum::Int32); - EXPECT_EQ(reader.GetFieldType(1), neversql::DataTypeEnum::String); - EXPECT_EQ(reader.GetFieldType(2), neversql::DataTypeEnum::Boolean); - EXPECT_ANY_THROW(reader.GetFieldType(3)); - - EXPECT_EQ(reader.GetEntryAs(0), 42); - EXPECT_EQ(reader.GetEntryAs(1), "My business"); - EXPECT_EQ(reader.GetEntryAs(2), true); - EXPECT_ANY_THROW([[maybe_unused]] auto x = reader.GetEntryAs(1)); - EXPECT_ANY_THROW([[maybe_unused]] auto x = reader.GetEntryAs(0)); - EXPECT_ANY_THROW([[maybe_unused]] auto x = reader.GetEntryAs(0)); - - std::ostringstream stream; - neversql::PrettyPrint(reader, stream); - EXPECT_EQ(stream.str(), "Age: 42\nBirthday: \"My business\"\nIsAlive: true\n"); + EXPECT_EQ(written_data.size(), 56); + EXPECT_EQ(document.CalculateRequiredSize(), 56); + + auto read_document = ReadDocumentFromBuffer(written_data); + ASSERT_EQ(read_document->GetNumFields(), 3); + EXPECT_EQ(read_document->GetFieldName(0), "Age"sv); + EXPECT_EQ(read_document->GetFieldName(1), "Birthday"sv); + EXPECT_EQ(read_document->GetFieldName(2), "IsAlive"sv); + EXPECT_ANY_THROW(read_document->GetFieldName(3)); + + EXPECT_EQ(read_document->GetFieldType(0), DataTypeEnum::Int32); + EXPECT_EQ(read_document->GetFieldType(1), DataTypeEnum::String); + EXPECT_EQ(read_document->GetFieldType(2), DataTypeEnum::Boolean); + EXPECT_ANY_THROW(read_document->GetFieldType(3)); + + EXPECT_EQ(read_document->TryGetAs(0).value(), 42); + EXPECT_EQ(read_document->TryGetAs(1).value(), "My business"); + EXPECT_EQ(read_document->TryGetAs(2).value(), true); + EXPECT_FALSE(read_document->TryGetAs(1)); + EXPECT_FALSE(read_document->TryGetAs(0)); + EXPECT_FALSE(read_document->TryGetAs(0)); } TEST(Document, Strings) { - neversql::DocumentBuilder document; - document.AddEntry("A-string", "Hello"s); - document.AddEntry("B-string", "There"s); - document.AddEntry("C-string", "World"s); + Document document; + document.AddElement("A-string", StringValue {"Hello"}); + document.AddElement("B-string", StringValue {"There"}); + document.AddElement("C-string", StringValue {"World"}); lightning::memory::MemoryBuffer buffer; WriteToBuffer(buffer, document); std::span written_data(buffer.Data(), buffer.Size()); - EXPECT_EQ(written_data.size(), 60); - EXPECT_EQ(document.CalculateRequiredSize(), 60); - - neversql::DocumentReader reader(written_data); - ASSERT_EQ(reader.GetNumFields(), 3); - EXPECT_EQ(reader.GetFieldName(0), "A-string"sv); - EXPECT_EQ(reader.GetFieldName(1), "B-string"sv); - EXPECT_EQ(reader.GetFieldName(2), "C-string"sv); - EXPECT_ANY_THROW(reader.GetFieldName(3)); - - EXPECT_EQ(reader.GetFieldType(0), neversql::DataTypeEnum::String); - EXPECT_EQ(reader.GetFieldType(1), neversql::DataTypeEnum::String); - EXPECT_EQ(reader.GetFieldType(2), neversql::DataTypeEnum::String); - EXPECT_ANY_THROW(reader.GetFieldType(3)); - - EXPECT_EQ(reader.GetEntryAs(0), "Hello"); - EXPECT_EQ(reader.GetEntryAs(1), "There"); - EXPECT_EQ(reader.GetEntryAs(2), "World"); + EXPECT_EQ(written_data.size(), 69); + EXPECT_EQ(document.CalculateRequiredSize(), 69); + + auto read_document = ReadDocumentFromBuffer(written_data); + ASSERT_EQ(read_document->GetNumFields(), 3); + EXPECT_EQ(read_document->GetFieldName(0), "A-string"sv); + EXPECT_EQ(read_document->GetFieldName(1), "B-string"sv); + EXPECT_EQ(read_document->GetFieldName(2), "C-string"sv); + EXPECT_ANY_THROW(read_document->GetFieldName(3)); + + EXPECT_EQ(read_document->GetFieldType(0), DataTypeEnum::String); + EXPECT_EQ(read_document->GetFieldType(1), DataTypeEnum::String); + EXPECT_EQ(read_document->GetFieldType(2), DataTypeEnum::String); + EXPECT_ANY_THROW(read_document->GetFieldType(3)); + + EXPECT_EQ(read_document->TryGetAs(0).value(), "Hello"); + EXPECT_EQ(read_document->TryGetAs(1).value(), "There"); + EXPECT_EQ(read_document->TryGetAs(2).value(), "World"); } } // namespace testing \ No newline at end of file