diff --git a/src/audit/log.cpp b/src/audit/log.cpp index 2102f2862..4680bee1f 100644 --- a/src/audit/log.cpp +++ b/src/audit/log.cpp @@ -5,6 +5,7 @@ #include <fmt/format.h> #include <json/json.hpp> +#include "storage/v2/temporal.hpp" #include "utils/logging.hpp" #include "utils/string.hpp" @@ -42,6 +43,14 @@ inline nlohmann::json PropertyValueToJson(const storage::PropertyValue &pv) { } break; } + case storage::PropertyValue::Type::TemporalData: { + ret = nlohmann::json::object(); + const auto temporal_data = pv.ValueTemporalData(); + // TODO (antonio2368): Maybe we want to have custom format for each type + ret.emplace("type", storage::TemporalTypeTostring(temporal_data.type)); + ret.emplace("microseconds", temporal_data.microseconds); + break; + } } return ret; } diff --git a/src/glue/communication.cpp b/src/glue/communication.cpp index 464b5237c..ef69f642f 100644 --- a/src/glue/communication.cpp +++ b/src/glue/communication.cpp @@ -216,6 +216,8 @@ Value ToBoltValue(const storage::PropertyValue &value) { } return Value(std::move(dv_map)); } + case storage::PropertyValue::Type::TemporalData: + LOG_FATAL("Unsupported type"); } } diff --git a/src/query/dump.cpp b/src/query/dump.cpp index a47c9b174..2476e67e4 100644 --- a/src/query/dump.cpp +++ b/src/query/dump.cpp @@ -12,6 +12,7 @@ #include "query/exceptions.hpp" #include "query/stream.hpp" #include "query/typed_value.hpp" +#include "storage/v2/property_value.hpp" #include "storage/v2/storage.hpp" #include "utils/algorithm.hpp" #include "utils/logging.hpp" @@ -88,6 +89,10 @@ void DumpPropertyValue(std::ostream *os, const storage::PropertyValue &value) { *os << "}"; return; } + case storage::PropertyValue::Type::TemporalData: { + // TODO(antonio2368): Define dump command for temporal data + return; + } } } diff --git a/src/query/frontend/ast/pretty_print.cpp b/src/query/frontend/ast/pretty_print.cpp index 9b2ec186d..a57351cb3 100644 --- a/src/query/frontend/ast/pretty_print.cpp +++ b/src/query/frontend/ast/pretty_print.cpp @@ -141,6 +141,9 @@ void PrintObject(std::ostream *out, const storage::PropertyValue &value) { case storage::PropertyValue::Type::Map: PrintObject(out, value.ValueMap()); break; + case storage::PropertyValue::Type::TemporalData: + // TODO (antonio2368): Print out the temporal data based on the type + LOG_FATAL("Not implemented!"); } } diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 72b2a51d2..926f42163 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -491,6 +491,7 @@ UniqueCursorPtr ScanAllByLabelPropertyRange::MakeCursor(utils::MemoryResource *m case storage::PropertyValue::Type::Int: case storage::PropertyValue::Type::Double: case storage::PropertyValue::Type::String: + case storage::PropertyValue::Type::TemporalData: // These are all fine, there's also Point, Date and Time data types // which were added to Cypher, but we don't have support for those // yet. diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index abd453e4c..0cfd151e3 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -477,6 +477,10 @@ mgp_value::mgp_value(const storage::PropertyValue &pv, utils::MemoryResource *m) map_v = allocator.new_object<mgp_map>(std::move(items)); break; } + case storage::PropertyValue::Type::TemporalData: { + // TODO (antonio2368): Add support for temporala data types + LOG_FATAL("Unsupported type"); + } } } diff --git a/src/query/serialization/property_value.cpp b/src/query/serialization/property_value.cpp index 262ff3cf0..46158d852 100644 --- a/src/query/serialization/property_value.cpp +++ b/src/query/serialization/property_value.cpp @@ -4,6 +4,10 @@ namespace query::serialization { +namespace { +enum class ObjectType : uint8_t { MAP, TEMPORAL_DATA }; +} // namespace + nlohmann::json SerializePropertyValue(const storage::PropertyValue &property_value) { using Type = storage::PropertyValue::Type; switch (property_value.type()) { @@ -21,6 +25,13 @@ nlohmann::json SerializePropertyValue(const storage::PropertyValue &property_val return SerializePropertyValueVector(property_value.ValueList()); case Type::Map: return SerializePropertyValueMap(property_value.ValueMap()); + case Type::TemporalData: + const auto temporal_data = property_value.ValueTemporalData(); + auto data = nlohmann::json::object(); + data.emplace("type", static_cast<uint64_t>(ObjectType::TEMPORAL_DATA)); + data.emplace("value", nlohmann::json::object({{"type", static_cast<uint64_t>(temporal_data.type)}, + {"microseconds", temporal_data.microseconds}})); + return data; } } @@ -34,9 +45,11 @@ nlohmann::json SerializePropertyValueVector(const std::vector<storage::PropertyV nlohmann::json SerializePropertyValueMap(const std::map<std::string, storage::PropertyValue> ¶meters) { nlohmann::json data = nlohmann::json::object(); + data.emplace("type", static_cast<uint64_t>(ObjectType::MAP)); + data.emplace("value", nlohmann::json::object()); for (const auto &[key, value] : parameters) { - data[key] = SerializePropertyValue(value); + data["value"][key] = SerializePropertyValue(value); } return data; @@ -68,7 +81,14 @@ storage::PropertyValue DeserializePropertyValue(const nlohmann::json &data) { } MG_ASSERT(data.is_object(), "Unknown type found in the trigger storage"); - return storage::PropertyValue(DeserializePropertyValueMap(data)); + + switch (data["type"].get<ObjectType>()) { + case ObjectType::MAP: + return storage::PropertyValue(DeserializePropertyValueMap(data)); + case ObjectType::TEMPORAL_DATA: + return storage::PropertyValue(storage::TemporalData{data["value"]["type"].get<storage::TemporalType>(), + data["value"]["microseconds"].get<int64_t>()}); + } } std::vector<storage::PropertyValue> DeserializePropertyValueList(const nlohmann::json::array_t &data) { @@ -82,9 +102,11 @@ std::vector<storage::PropertyValue> DeserializePropertyValueList(const nlohmann: } std::map<std::string, storage::PropertyValue> DeserializePropertyValueMap(const nlohmann::json::object_t &data) { + MG_ASSERT(data.at("type").get<ObjectType>() == ObjectType::MAP, "Invalid map serialization"); std::map<std::string, storage::PropertyValue> property_values; - for (const auto &[key, value] : data) { + const nlohmann::json::object_t &values = data.at("value"); + for (const auto &[key, value] : values) { property_values.emplace(key, DeserializePropertyValue(value)); } diff --git a/src/query/typed_value.cpp b/src/query/typed_value.cpp index cdf51952c..573467e5b 100644 --- a/src/query/typed_value.cpp +++ b/src/query/typed_value.cpp @@ -52,6 +52,9 @@ TypedValue::TypedValue(const storage::PropertyValue &value, utils::MemoryResourc for (const auto &kv : map) map_v.emplace(kv.first, kv.second); return; } + case storage::PropertyValue::Type::TemporalData: + // TODO (antonio2368): Add support for Temporal types in TypedValues + break; } LOG_FATAL("Unsupported type"); } @@ -96,6 +99,9 @@ TypedValue::TypedValue(storage::PropertyValue &&other, utils::MemoryResource *me for (auto &kv : map) map_v.emplace(kv.first, std::move(kv.second)); break; } + case storage::PropertyValue::Type::TemporalData: + // TODO (antonio2368): Add support for Temporal types in TypedValues + LOG_FATAL("Unsupported type"); } other = storage::PropertyValue(); diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index a997fb496..5de28e244 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -1,6 +1,7 @@ set(storage_v2_src_files commit_log.cpp constraints.cpp + temporal.cpp durability/durability.cpp durability/serialization.cpp durability/snapshot.cpp diff --git a/src/storage/v2/durability/marker.hpp b/src/storage/v2/durability/marker.hpp index 856ee99bf..c9929e6eb 100644 --- a/src/storage/v2/durability/marker.hpp +++ b/src/storage/v2/durability/marker.hpp @@ -16,6 +16,7 @@ enum class Marker : uint8_t { TYPE_LIST = 0x15, TYPE_MAP = 0x16, TYPE_PROPERTY_VALUE = 0x17, + TYPE_TEMPORAL_DATA = 0x18, SECTION_VERTEX = 0x20, SECTION_EDGE = 0x21, @@ -59,6 +60,7 @@ static const Marker kMarkersAll[] = { Marker::TYPE_STRING, Marker::TYPE_LIST, Marker::TYPE_MAP, + Marker::TYPE_TEMPORAL_DATA, Marker::TYPE_PROPERTY_VALUE, Marker::SECTION_VERTEX, Marker::SECTION_EDGE, diff --git a/src/storage/v2/durability/serialization.cpp b/src/storage/v2/durability/serialization.cpp index f9ce966ff..b9d7ffe95 100644 --- a/src/storage/v2/durability/serialization.cpp +++ b/src/storage/v2/durability/serialization.cpp @@ -1,5 +1,6 @@ #include "storage/v2/durability/serialization.hpp" +#include "storage/v2/temporal.hpp" #include "utils/endian.hpp" namespace storage::durability { @@ -109,6 +110,13 @@ void Encoder::WritePropertyValue(const PropertyValue &value) { } break; } + case PropertyValue::Type::TemporalData: { + const auto temporal_data = value.ValueTemporalData(); + WriteMarker(Marker::TYPE_TEMPORAL_DATA); + WriteUint(static_cast<uint64_t>(temporal_data.type)); + WriteUint(utils::MemcpyCast<uint64_t>(temporal_data.microseconds)); + break; + } } } @@ -222,6 +230,21 @@ std::optional<std::string> Decoder::ReadString() { return value; } +namespace { +std::optional<TemporalData> ReadTemporalData(Decoder &decoder) { + const auto inner_marker = decoder.ReadMarker(); + if (!inner_marker || *inner_marker != Marker::TYPE_TEMPORAL_DATA) return std::nullopt; + + const auto type = decoder.ReadUint(); + if (!type) return std::nullopt; + + const auto microseconds = decoder.ReadUint(); + if (!microseconds) return std::nullopt; + + return TemporalData{static_cast<TemporalType>(*type), utils::MemcpyCast<int64_t>(*microseconds)}; +} +} // namespace + std::optional<PropertyValue> Decoder::ReadPropertyValue() { auto pv_marker = ReadMarker(); if (!pv_marker || *pv_marker != Marker::TYPE_PROPERTY_VALUE) return std::nullopt; @@ -283,6 +306,11 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() { } return PropertyValue(std::move(value)); } + case Marker::TYPE_TEMPORAL_DATA: { + const auto maybe_temporal_data = ReadTemporalData(*this); + if (!maybe_temporal_data) return std::nullopt; + return PropertyValue(*maybe_temporal_data); + } case Marker::TYPE_PROPERTY_VALUE: case Marker::SECTION_VERTEX: @@ -379,6 +407,9 @@ bool Decoder::SkipPropertyValue() { } return true; } + case Marker::TYPE_TEMPORAL_DATA: { + return !!ReadTemporalData(*this); + } case Marker::TYPE_PROPERTY_VALUE: case Marker::SECTION_VERTEX: diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp index 9f4bc46e7..04ce72c0b 100644 --- a/src/storage/v2/durability/wal.cpp +++ b/src/storage/v2/durability/wal.cpp @@ -158,6 +158,7 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { case Marker::TYPE_STRING: case Marker::TYPE_LIST: case Marker::TYPE_MAP: + case Marker::TYPE_TEMPORAL_DATA: case Marker::TYPE_PROPERTY_VALUE: case Marker::SECTION_VERTEX: case Marker::SECTION_EDGE: diff --git a/src/storage/v2/indices.cpp b/src/storage/v2/indices.cpp index f7bd634fd..4408e5cb0 100644 --- a/src/storage/v2/indices.cpp +++ b/src/storage/v2/indices.cpp @@ -1,6 +1,8 @@ #include "indices.hpp" #include "storage/v2/mvcc.hpp" +#include "storage/v2/property_value.hpp" +#include "utils/bound.hpp" #include "utils/logging.hpp" #include "utils/memory_tracker.hpp" @@ -519,6 +521,7 @@ const PropertyValue kSmallestNumber = PropertyValue(-std::numeric_limits<double> const PropertyValue kSmallestString = PropertyValue(""); const PropertyValue kSmallestList = PropertyValue(std::vector<PropertyValue>()); const PropertyValue kSmallestMap = PropertyValue(std::map<std::string, PropertyValue>()); +const PropertyValue kSmallestTemporalData = PropertyValue(TemporalData{static_cast<TemporalType>(0), 0}); LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property, @@ -590,6 +593,9 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac upper_bound_ = utils::MakeBoundExclusive(kSmallestMap); break; case PropertyValue::Type::Map: + upper_bound_ = utils::MakeBoundExclusive(kSmallestTemporalData); + break; + case PropertyValue::Type::TemporalData: // This is the last type in the order so we leave the upper bound empty. break; } @@ -619,7 +625,9 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac break; case PropertyValue::Type::Map: lower_bound_ = utils::MakeBoundInclusive(kSmallestMap); - // This is the last type in the order so we leave the upper bound empty. + break; + case PropertyValue::Type::TemporalData: + lower_bound_ = utils::MakeBoundInclusive(kSmallestTemporalData); break; } } diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp index b7ea25a4f..99b21eaab 100644 --- a/src/storage/v2/property_store.cpp +++ b/src/storage/v2/property_store.cpp @@ -7,6 +7,7 @@ #include <type_traits> #include <utility> +#include "storage/v2/temporal.hpp" #include "utils/cast.hpp" #include "utils/logging.hpp" @@ -90,6 +91,7 @@ enum class Type : uint8_t { STRING = 0x50, LIST = 0x60, MAP = 0x70, + TEMPORAL_DATA = 0x80 }; const uint8_t kMaskType = 0xf0; @@ -141,6 +143,16 @@ const uint8_t kShiftIdSize = 2; // + encoded key data // + encoded value size // + encoded value data +// * TEMPORAL_DATE +// - type; payload size isn't used +// - encoded property ID +// - value saved as Metadata +// + type; id size is used to indicate whether the temporal data type is encoded +// as `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t`; payload size used to +// indicate whether the microseconds are encoded as `uint8_t`, `uint16_t, `uint32_t +// or `uint64_t` +// + encoded temporal data type value +// + encoded microseconds value struct Metadata { Type type{Type::EMPTY}; @@ -428,9 +440,40 @@ std::optional<std::pair<Type, Size>> EncodePropertyValue(Writer *writer, const P } return {{Type::MAP, *size}}; } + case PropertyValue::Type::TemporalData: { + auto metadata = writer->WriteMetadata(); + if (!metadata) return std::nullopt; + + const auto temporal_data = value.ValueTemporalData(); + auto type_size = writer->WriteUint(utils::UnderlyingCast(temporal_data.type)); + if (!type_size) return std::nullopt; + + auto microseconds_size = writer->WriteInt(temporal_data.microseconds); + if (!microseconds_size) return std::nullopt; + metadata->Set({Type::TEMPORAL_DATA, *type_size, *microseconds_size}); + + // We don't need payload size so we set it to a random value + return {{Type::TEMPORAL_DATA, Size::INT8}}; + } } } +namespace { +std::optional<TemporalData> DecodeTemporalData(Reader &reader) { + auto metadata = reader.ReadMetadata(); + if (!metadata || metadata->type != Type::TEMPORAL_DATA) return std::nullopt; + + auto type_value = reader.ReadUint(metadata->id_size); + if (!type_value) return std::nullopt; + + auto microseconds_value = reader.ReadInt(metadata->payload_size); + if (!microseconds_value) return std::nullopt; + + return TemporalData{static_cast<TemporalType>(*type_value), *microseconds_value}; +} + +} // namespace + // Function used to decode a PropertyValue from a byte stream. It can either // decode or skip the encoded PropertyValue, depending on the supplied value // pointer. @@ -537,6 +580,18 @@ std::optional<std::pair<Type, Size>> EncodePropertyValue(Writer *writer, const P } return true; } + + case Type::TEMPORAL_DATA: { + const auto maybe_temporal_data = DecodeTemporalData(*reader); + + if (!maybe_temporal_data) return false; + + if (value) { + *value = PropertyValue(*maybe_temporal_data); + } + + return true; + } } } @@ -627,6 +682,16 @@ std::optional<std::pair<Type, Size>> EncodePropertyValue(Writer *writer, const P } return true; } + case Type::TEMPORAL_DATA: { + if (!value.IsTemporalData()) return false; + + const auto maybe_temporal_data = DecodeTemporalData(*reader); + if (!maybe_temporal_data) { + return false; + } + + return *maybe_temporal_data == value.ValueTemporalData(); + } } } diff --git a/src/storage/v2/property_value.hpp b/src/storage/v2/property_value.hpp index 18de7052f..91a1024eb 100644 --- a/src/storage/v2/property_value.hpp +++ b/src/storage/v2/property_value.hpp @@ -5,6 +5,7 @@ #include <string> #include <vector> +#include "storage/v2/temporal.hpp" #include "utils/algorithm.hpp" #include "utils/exceptions.hpp" @@ -33,6 +34,7 @@ class PropertyValue { String = 4, List = 5, Map = 6, + TemporalData = 7 }; static bool AreComparableTypes(Type a, Type b) { @@ -43,10 +45,11 @@ class PropertyValue { PropertyValue() : type_(Type::Null) {} // constructors for primitive types - explicit PropertyValue(bool value) : type_(Type::Bool) { bool_v = value; } - explicit PropertyValue(int value) : type_(Type::Int) { int_v = value; } - explicit PropertyValue(int64_t value) : type_(Type::Int) { int_v = value; } - explicit PropertyValue(double value) : type_(Type::Double) { double_v = value; } + explicit PropertyValue(const bool value) : type_(Type::Bool) { bool_v = value; } + explicit PropertyValue(const int value) : type_(Type::Int) { int_v = value; } + explicit PropertyValue(const int64_t value) : type_(Type::Int) { int_v = value; } + explicit PropertyValue(const double value) : type_(Type::Double) { double_v = value; } + explicit PropertyValue(const TemporalData value) : type_{Type::TemporalData} { temporal_data_v = value; } // copy constructors for non-primitive types /// @throw std::bad_alloc @@ -103,6 +106,7 @@ class PropertyValue { bool IsString() const { return type_ == Type::String; } bool IsList() const { return type_ == Type::List; } bool IsMap() const { return type_ == Type::Map; } + bool IsTemporalData() const { return type_ == Type::TemporalData; } // value getters for primitive types /// @throw PropertyValueException if value isn't of correct type. @@ -127,6 +131,15 @@ class PropertyValue { return double_v; } + /// @throw PropertyValueException if value isn't of correct type. + TemporalData ValueTemporalData() const { + if (type_ != Type::TemporalData) { + throw PropertyValueException("The value isn't a temporal data!"); + } + + return temporal_data_v; + } + // const value getters for non-primitive types /// @throw PropertyValueException if value isn't of correct type. const std::string &ValueString() const { @@ -187,6 +200,7 @@ class PropertyValue { std::string string_v; std::vector<PropertyValue> list_v; std::map<std::string, PropertyValue> map_v; + TemporalData temporal_data_v; }; Type type_; @@ -210,6 +224,8 @@ inline std::ostream &operator<<(std::ostream &os, const PropertyValue::Type type return os << "list"; case PropertyValue::Type::Map: return os << "map"; + case PropertyValue::Type::TemporalData: + return os << "temporal data"; } } /// @throw anything std::ostream::operator<< may throw. @@ -234,6 +250,9 @@ inline std::ostream &operator<<(std::ostream &os, const PropertyValue &value) { utils::PrintIterable(os, value.ValueMap(), ", ", [](auto &stream, const auto &pair) { stream << pair.first << ": " << pair.second; }); return os << "}"; + case PropertyValue::Type::TemporalData: + return os << fmt::format("type: {}, microseconds: {}", TemporalTypeTostring(value.ValueTemporalData().type), + value.ValueTemporalData().microseconds); } } @@ -265,6 +284,8 @@ inline bool operator==(const PropertyValue &first, const PropertyValue &second) return first.ValueList() == second.ValueList(); case PropertyValue::Type::Map: return first.ValueMap() == second.ValueMap(); + case PropertyValue::Type::TemporalData: + return first.ValueTemporalData() == second.ValueTemporalData(); } } @@ -293,6 +314,8 @@ inline bool operator<(const PropertyValue &first, const PropertyValue &second) n return first.ValueList() < second.ValueList(); case PropertyValue::Type::Map: return first.ValueMap() < second.ValueMap(); + case PropertyValue::Type::TemporalData: + return first.ValueTemporalData() < second.ValueTemporalData(); } } @@ -318,6 +341,9 @@ inline PropertyValue::PropertyValue(const PropertyValue &other) : type_(other.ty case Type::Map: new (&map_v) std::map<std::string, PropertyValue>(other.map_v); return; + case Type::TemporalData: + this->temporal_data_v = other.temporal_data_v; + return; } } @@ -343,6 +369,9 @@ inline PropertyValue::PropertyValue(PropertyValue &&other) noexcept : type_(othe case Type::Map: new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v)); break; + case Type::TemporalData: + this->temporal_data_v = other.temporal_data_v; + break; } // reset the type of other @@ -377,6 +406,9 @@ inline PropertyValue &PropertyValue::operator=(const PropertyValue &other) { case Type::Map: new (&map_v) std::map<std::string, PropertyValue>(other.map_v); break; + case Type::TemporalData: + this->temporal_data_v = other.temporal_data_v; + break; } return *this; @@ -409,6 +441,9 @@ inline PropertyValue &PropertyValue::operator=(PropertyValue &&other) noexcept { case Type::Map: new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v)); break; + case Type::TemporalData: + this->temporal_data_v = other.temporal_data_v; + break; } // reset the type of other @@ -425,20 +460,18 @@ inline void PropertyValue::DestroyValue() noexcept { case Type::Bool: case Type::Int: case Type::Double: + case Type::TemporalData: return; // destructor for non primitive types since we used placement new case Type::String: - // Clang fails to compile ~std::string. It seems it is a bug in some - // versions of clang. Using namespace std statement solves the issue. - using namespace std; - string_v.~string(); + std::destroy_at(&string_v); return; case Type::List: - list_v.~vector(); + std::destroy_at(&list_v); return; case Type::Map: - map_v.~map(); + std::destroy_at(&map_v); return; } } diff --git a/src/storage/v2/replication/slk.cpp b/src/storage/v2/replication/slk.cpp index e8bf1dd3a..65cafaa8a 100644 --- a/src/storage/v2/replication/slk.cpp +++ b/src/storage/v2/replication/slk.cpp @@ -2,6 +2,8 @@ #include <type_traits> +#include "storage/v2/property_value.hpp" +#include "storage/v2/temporal.hpp" #include "utils/cast.hpp" namespace slk { @@ -14,10 +16,6 @@ void Load(storage::Gid *gid, slk::Reader *reader) { *gid = storage::Gid::FromUint(value); } -void Save(const storage::PropertyValue::Type &type, slk::Builder *builder) { - slk::Save(utils::UnderlyingCast(type), builder); -} - void Load(storage::PropertyValue::Type *type, slk::Reader *reader) { using PVTypeUnderlyingType = std::underlying_type_t<storage::PropertyValue::Type>; PVTypeUnderlyingType value; @@ -31,6 +29,7 @@ void Load(storage::PropertyValue::Type *type, slk::Reader *reader) { case utils::UnderlyingCast(storage::PropertyValue::Type::String): case utils::UnderlyingCast(storage::PropertyValue::Type::List): case utils::UnderlyingCast(storage::PropertyValue::Type::Map): + case utils::UnderlyingCast(storage::PropertyValue::Type::TemporalData): valid = true; break; default: @@ -82,6 +81,13 @@ void Save(const storage::PropertyValue &value, slk::Builder *builder) { } return; } + case storage::PropertyValue::Type::TemporalData: { + slk::Save(storage::PropertyValue::Type::TemporalData, builder); + const auto temporal_data = value.ValueTemporalData(); + slk::Save(temporal_data.type, builder); + slk::Save(temporal_data.microseconds, builder); + return; + } } } @@ -138,18 +144,15 @@ void Load(storage::PropertyValue *value, slk::Reader *reader) { *value = storage::PropertyValue(std::move(map)); return; } + case storage::PropertyValue::Type::TemporalData: { + storage::TemporalType temporal_type; + slk::Load(&temporal_type, reader); + int64_t microseconds{0}; + slk::Load(µseconds, reader); + *value = storage::PropertyValue(storage::TemporalData{temporal_type, microseconds}); + return; + } } } -void Save(const storage::durability::Marker &marker, slk::Builder *builder) { - slk::Save(utils::UnderlyingCast(marker), builder); -} - -void Load(storage::durability::Marker *marker, slk::Reader *reader) { - using PVTypeUnderlyingType = std::underlying_type_t<storage::PropertyValue::Type>; - PVTypeUnderlyingType value; - slk::Load(&value, reader); - *marker = static_cast<storage::durability::Marker>(value); -} - } // namespace slk diff --git a/src/storage/v2/replication/slk.hpp b/src/storage/v2/replication/slk.hpp index 0652ae211..964a0d5cc 100644 --- a/src/storage/v2/replication/slk.hpp +++ b/src/storage/v2/replication/slk.hpp @@ -4,6 +4,7 @@ #include "storage/v2/durability/marker.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/property_value.hpp" +#include "utils/concepts.hpp" namespace slk { @@ -13,7 +14,17 @@ void Load(storage::Gid *gid, slk::Reader *reader); void Save(const storage::PropertyValue &value, slk::Builder *builder); void Load(storage::PropertyValue *value, slk::Reader *reader); -void Save(const storage::durability::Marker &marker, slk::Builder *builder); -void Load(storage::durability::Marker *marker, slk::Reader *reader); +template <utils::Enum T> +void Save(const T &enum_value, slk::Builder *builder) { + slk::Save(utils::UnderlyingCast(enum_value), builder); +} + +template <utils::Enum T> +void Load(T *enum_value, slk::Reader *reader) { + using UnderlyingType = std::underlying_type_t<T>; + UnderlyingType value; + slk::Load(&value, reader); + *enum_value = static_cast<T>(value); +} } // namespace slk diff --git a/src/storage/v2/temporal.cpp b/src/storage/v2/temporal.cpp new file mode 100644 index 000000000..7f83e7cae --- /dev/null +++ b/src/storage/v2/temporal.cpp @@ -0,0 +1,6 @@ +#include "storage/v2/temporal.hpp" + +namespace storage { +TemporalData::TemporalData(TemporalType type, int64_t microseconds) : type{type}, microseconds{microseconds} {} + +} // namespace storage diff --git a/src/storage/v2/temporal.hpp b/src/storage/v2/temporal.hpp new file mode 100644 index 000000000..6fe37b8ea --- /dev/null +++ b/src/storage/v2/temporal.hpp @@ -0,0 +1,31 @@ +#pragma once +#include <cstdint> +#include <string_view> + +namespace storage { + +enum class TemporalType : uint8_t { Date = 0, LocalTime, LocalDateTime, Duration }; + +constexpr std::string_view TemporalTypeTostring(const TemporalType type) { + switch (type) { + case TemporalType::Date: + return "Date"; + case TemporalType::LocalTime: + return "LocalTime"; + case TemporalType::LocalDateTime: + return "LocalDateTime"; + case TemporalType::Duration: + return "Duration"; + } +} + +struct TemporalData { + explicit TemporalData(TemporalType type, int64_t microseconds); + + auto operator<=>(const TemporalData &) const = default; + + TemporalType type; + int64_t microseconds; +}; + +} // namespace storage diff --git a/src/utils/concepts.hpp b/src/utils/concepts.hpp index 37365fe98..0469bc07d 100644 --- a/src/utils/concepts.hpp +++ b/src/utils/concepts.hpp @@ -4,4 +4,7 @@ namespace utils { template <typename T, typename... Args> concept SameAsAnyOf = (std::same_as<T, Args> || ...); + +template <typename T> +concept Enum = std::is_enum_v<T>; } // namespace utils diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 94bebd8d3..d0cff86ce 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -282,7 +282,7 @@ add_unit_test(commit_log_v2.cpp) target_link_libraries(${test_prefix}commit_log_v2 gflags mg-utils mg-storage-v2) add_unit_test(property_value_v2.cpp) -target_link_libraries(${test_prefix}property_value_v2 mg-utils) +target_link_libraries(${test_prefix}property_value_v2 mg-storage-v2 mg-utils) add_unit_test(storage_v2.cpp) target_link_libraries(${test_prefix}storage_v2 mg-storage-v2 storage_test_utils) diff --git a/tests/unit/property_value_v2.cpp b/tests/unit/property_value_v2.cpp index 069d7da45..f4ffbc913 100644 --- a/tests/unit/property_value_v2.cpp +++ b/tests/unit/property_value_v2.cpp @@ -3,6 +3,7 @@ #include <sstream> #include "storage/v2/property_value.hpp" +#include "storage/v2/temporal.hpp" // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(PropertyValue, Null) { @@ -496,10 +497,16 @@ TEST(PropertyValue, MapMove) { TEST(PropertyValue, CopyConstructor) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123)}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; - std::vector<storage::PropertyValue> data{storage::PropertyValue(), storage::PropertyValue(true), - storage::PropertyValue(123), storage::PropertyValue(123.5), - storage::PropertyValue("nandare"), storage::PropertyValue(vec), - storage::PropertyValue(map)}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), + storage::PropertyValue(true), + storage::PropertyValue(123), + storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), + storage::PropertyValue(vec), + storage::PropertyValue(map), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; + for (const auto &item : data) { storage::PropertyValue pv(item); ASSERT_EQ(pv.type(), item.type()); @@ -525,6 +532,8 @@ TEST(PropertyValue, CopyConstructor) { case storage::PropertyValue::Type::Map: ASSERT_EQ(pv.ValueMap(), item.ValueMap()); break; + case storage::PropertyValue::Type::TemporalData: + ASSERT_EQ(pv.ValueTemporalData(), item.ValueTemporalData()); } } } @@ -533,10 +542,16 @@ TEST(PropertyValue, CopyConstructor) { TEST(PropertyValue, MoveConstructor) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123)}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; - std::vector<storage::PropertyValue> data{storage::PropertyValue(), storage::PropertyValue(true), - storage::PropertyValue(123), storage::PropertyValue(123.5), - storage::PropertyValue("nandare"), storage::PropertyValue(vec), - storage::PropertyValue(map)}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), + storage::PropertyValue(true), + storage::PropertyValue(123), + storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), + storage::PropertyValue(vec), + storage::PropertyValue(map), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; + for (auto &item : data) { storage::PropertyValue copy(item); storage::PropertyValue pv(std::move(item)); @@ -564,6 +579,9 @@ TEST(PropertyValue, MoveConstructor) { case storage::PropertyValue::Type::Map: ASSERT_EQ(pv.ValueMap(), copy.ValueMap()); break; + case storage::PropertyValue::Type::TemporalData: + ASSERT_EQ(pv.ValueTemporalData(), copy.ValueTemporalData()); + break; } } } @@ -572,10 +590,16 @@ TEST(PropertyValue, MoveConstructor) { TEST(PropertyValue, CopyAssignment) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123)}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; - std::vector<storage::PropertyValue> data{storage::PropertyValue(), storage::PropertyValue(true), - storage::PropertyValue(123), storage::PropertyValue(123.5), - storage::PropertyValue("nandare"), storage::PropertyValue(vec), - storage::PropertyValue(map)}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), + storage::PropertyValue(true), + storage::PropertyValue(123), + storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), + storage::PropertyValue(vec), + storage::PropertyValue(map), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; + for (const auto &item : data) { storage::PropertyValue pv(123); pv = item; @@ -602,6 +626,9 @@ TEST(PropertyValue, CopyAssignment) { case storage::PropertyValue::Type::Map: ASSERT_EQ(pv.ValueMap(), item.ValueMap()); break; + case storage::PropertyValue::Type::TemporalData: + ASSERT_EQ(pv.ValueTemporalData(), item.ValueTemporalData()); + break; } } } @@ -610,10 +637,16 @@ TEST(PropertyValue, CopyAssignment) { TEST(PropertyValue, MoveAssignment) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123)}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; - std::vector<storage::PropertyValue> data{storage::PropertyValue(), storage::PropertyValue(true), - storage::PropertyValue(123), storage::PropertyValue(123.5), - storage::PropertyValue("nandare"), storage::PropertyValue(vec), - storage::PropertyValue(map)}; + std::vector<storage::PropertyValue> data{ + storage::PropertyValue(), + storage::PropertyValue(true), + storage::PropertyValue(123), + storage::PropertyValue(123.5), + storage::PropertyValue("nandare"), + storage::PropertyValue(vec), + storage::PropertyValue(map), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; + for (auto &item : data) { storage::PropertyValue copy(item); storage::PropertyValue pv(123); @@ -642,6 +675,9 @@ TEST(PropertyValue, MoveAssignment) { case storage::PropertyValue::Type::Map: ASSERT_EQ(pv.ValueMap(), copy.ValueMap()); break; + case storage::PropertyValue::Type::TemporalData: + ASSERT_EQ(pv.ValueTemporalData(), copy.ValueTemporalData()); + break; } } } diff --git a/tests/unit/query_serialization_property_value.cpp b/tests/unit/query_serialization_property_value.cpp index 569d13ec2..78b2a1b9a 100644 --- a/tests/unit/query_serialization_property_value.cpp +++ b/tests/unit/query_serialization_property_value.cpp @@ -1,6 +1,7 @@ #include <gtest/gtest.h> #include "query/serialization/property_value.hpp" +#include "storage/v2/temporal.hpp" #include "utils/logging.hpp" namespace { @@ -39,6 +40,16 @@ TEST(PropertyValueSerializationTest, String) { CheckJsonConversion(storage::PropertyValue{""}); } +TEST(PropertyValueSerializationTest, TemporalData) { + const auto test_temporal_data_conversion = [](const auto type, const auto microseconds) { + CheckJsonConversion(storage::PropertyValue{storage::TemporalData{type, microseconds}}); + }; + + test_temporal_data_conversion(storage::TemporalType::Date, 20); + test_temporal_data_conversion(storage::TemporalType::LocalDateTime, -20); + test_temporal_data_conversion(storage::TemporalType::Duration, 10000); +} + namespace { std::vector<storage::PropertyValue> GetPropertyValueListWithBasicTypes() { @@ -59,27 +70,39 @@ std::map<std::string, storage::PropertyValue> GetPropertyValueMapWithBasicTypes( TEST(PropertyValueSerializationTest, List) { storage::PropertyValue list = storage::PropertyValue{GetPropertyValueListWithBasicTypes()}; - SPDLOG_DEBUG("Basic list"); - CheckJsonConversion(list); + { + SCOPED_TRACE("Basic list"); + CheckJsonConversion(list); + } - SPDLOG_DEBUG("Nested list"); - CheckJsonConversion(storage::PropertyValue{std::vector<storage::PropertyValue>{list, list}}); + { + SCOPED_TRACE("Nested list"); + CheckJsonConversion(storage::PropertyValue{std::vector<storage::PropertyValue>{list, list}}); + } - SPDLOG_DEBUG("List with map"); - list.ValueList().emplace_back(GetPropertyValueMapWithBasicTypes()); - CheckJsonConversion(list); + { + SCOPED_TRACE("List with map"); + list.ValueList().emplace_back(GetPropertyValueMapWithBasicTypes()); + CheckJsonConversion(list); + } } TEST(PropertyValueSerializationTest, Map) { auto map = GetPropertyValueMapWithBasicTypes(); - SPDLOG_DEBUG("Basic map"); - CheckJsonConversion(storage::PropertyValue{map}); + { + SCOPED_TRACE("Basic map"); + CheckJsonConversion(storage::PropertyValue{map}); + } - SPDLOG_DEBUG("Nested map"); - map.emplace("map", storage::PropertyValue{map}); - CheckJsonConversion(storage::PropertyValue{map}); + { + SCOPED_TRACE("Nested map"); + map.emplace("map", storage::PropertyValue{map}); + CheckJsonConversion(storage::PropertyValue{map}); + } - SPDLOG_DEBUG("Map with list"); - map.emplace("list", storage::PropertyValue{GetPropertyValueListWithBasicTypes()}); - CheckJsonConversion(storage::PropertyValue{map}); + { + SCOPED_TRACE("Map with list"); + map.emplace("list", storage::PropertyValue{GetPropertyValueListWithBasicTypes()}); + CheckJsonConversion(storage::PropertyValue{map}); + } } diff --git a/tests/unit/slk_advanced.cpp b/tests/unit/slk_advanced.cpp index 8afabf30b..343214160 100644 --- a/tests/unit/slk_advanced.cpp +++ b/tests/unit/slk_advanced.cpp @@ -1,18 +1,25 @@ #include <gtest/gtest.h> +#include "storage/v2/property_value.hpp" #include "storage/v2/replication/slk.hpp" #include "slk_common.hpp" +#include "storage/v2/temporal.hpp" TEST(SlkAdvanced, PropertyValueList) { - std::vector<storage::PropertyValue> original{storage::PropertyValue("hello world!"), storage::PropertyValue(5), - storage::PropertyValue(1.123423), storage::PropertyValue(true), - storage::PropertyValue()}; + std::vector<storage::PropertyValue> original{ + storage::PropertyValue("hello world!"), + storage::PropertyValue(5), + storage::PropertyValue(1.123423), + storage::PropertyValue(true), + storage::PropertyValue(), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; ASSERT_EQ(original[0].type(), storage::PropertyValue::Type::String); ASSERT_EQ(original[1].type(), storage::PropertyValue::Type::Int); ASSERT_EQ(original[2].type(), storage::PropertyValue::Type::Double); ASSERT_EQ(original[3].type(), storage::PropertyValue::Type::Bool); ASSERT_EQ(original[4].type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(original[5].type(), storage::PropertyValue::Type::TemporalData); slk::Loopback loopback; auto builder = loopback.GetBuilder(); @@ -26,16 +33,19 @@ TEST(SlkAdvanced, PropertyValueList) { } TEST(SlkAdvanced, PropertyValueMap) { - std::map<std::string, storage::PropertyValue> original{{"hello", storage::PropertyValue("world")}, - {"number", storage::PropertyValue(5)}, - {"real", storage::PropertyValue(1.123423)}, - {"truth", storage::PropertyValue(true)}, - {"nothing", storage::PropertyValue()}}; + std::map<std::string, storage::PropertyValue> original{ + {"hello", storage::PropertyValue("world")}, + {"number", storage::PropertyValue(5)}, + {"real", storage::PropertyValue(1.123423)}, + {"truth", storage::PropertyValue(true)}, + {"nothing", storage::PropertyValue()}, + {"date", storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}}; ASSERT_EQ(original["hello"].type(), storage::PropertyValue::Type::String); ASSERT_EQ(original["number"].type(), storage::PropertyValue::Type::Int); ASSERT_EQ(original["real"].type(), storage::PropertyValue::Type::Double); ASSERT_EQ(original["truth"].type(), storage::PropertyValue::Type::Bool); ASSERT_EQ(original["nothing"].type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(original["date"].type(), storage::PropertyValue::Type::TemporalData); slk::Loopback loopback; auto builder = loopback.GetBuilder(); @@ -49,25 +59,33 @@ TEST(SlkAdvanced, PropertyValueMap) { } TEST(SlkAdvanced, PropertyValueComplex) { - std::vector<storage::PropertyValue> vec_v{storage::PropertyValue("hello world!"), storage::PropertyValue(5), - storage::PropertyValue(1.123423), storage::PropertyValue(true), - storage::PropertyValue()}; + std::vector<storage::PropertyValue> vec_v{ + storage::PropertyValue("hello world!"), + storage::PropertyValue(5), + storage::PropertyValue(1.123423), + storage::PropertyValue(true), + storage::PropertyValue(), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}; ASSERT_EQ(vec_v[0].type(), storage::PropertyValue::Type::String); ASSERT_EQ(vec_v[1].type(), storage::PropertyValue::Type::Int); ASSERT_EQ(vec_v[2].type(), storage::PropertyValue::Type::Double); ASSERT_EQ(vec_v[3].type(), storage::PropertyValue::Type::Bool); ASSERT_EQ(vec_v[4].type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(vec_v[5].type(), storage::PropertyValue::Type::TemporalData); - std::map<std::string, storage::PropertyValue> map_v{{"hello", storage::PropertyValue("world")}, - {"number", storage::PropertyValue(5)}, - {"real", storage::PropertyValue(1.123423)}, - {"truth", storage::PropertyValue(true)}, - {"nothing", storage::PropertyValue()}}; + std::map<std::string, storage::PropertyValue> map_v{ + {"hello", storage::PropertyValue("world")}, + {"number", storage::PropertyValue(5)}, + {"real", storage::PropertyValue(1.123423)}, + {"truth", storage::PropertyValue(true)}, + {"nothing", storage::PropertyValue()}, + {"date", storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))}}; ASSERT_EQ(map_v["hello"].type(), storage::PropertyValue::Type::String); ASSERT_EQ(map_v["number"].type(), storage::PropertyValue::Type::Int); ASSERT_EQ(map_v["real"].type(), storage::PropertyValue::Type::Double); ASSERT_EQ(map_v["truth"].type(), storage::PropertyValue::Type::Bool); ASSERT_EQ(map_v["nothing"].type(), storage::PropertyValue::Type::Null); + ASSERT_EQ(map_v["date"].type(), storage::PropertyValue::Type::TemporalData); storage::PropertyValue original( std::vector<storage::PropertyValue>{storage::PropertyValue(vec_v), storage::PropertyValue(map_v)}); diff --git a/tests/unit/storage_v2_decoder_encoder.cpp b/tests/unit/storage_v2_decoder_encoder.cpp index b217a4030..caeffddd3 100644 --- a/tests/unit/storage_v2_decoder_encoder.cpp +++ b/tests/unit/storage_v2_decoder_encoder.cpp @@ -4,6 +4,8 @@ #include <limits> #include "storage/v2/durability/serialization.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/temporal.hpp" static const std::string kTestMagic{"MGtest"}; static const uint64_t kTestVersion{1}; @@ -118,7 +120,8 @@ GENERATE_READ_TEST(PropertyValue, storage::PropertyValue, storage::PropertyValue storage::PropertyValue(std::vector<storage::PropertyValue>{storage::PropertyValue("nandare"), storage::PropertyValue(123L)}), storage::PropertyValue(std::map<std::string, storage::PropertyValue>{ - {"nandare", storage::PropertyValue(123)}})); + {"nandare", storage::PropertyValue(123)}}), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))); // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GENERATE_SKIP_TEST(name, type, ...) \ @@ -162,7 +165,8 @@ GENERATE_SKIP_TEST(PropertyValue, storage::PropertyValue, storage::PropertyValue storage::PropertyValue(std::vector<storage::PropertyValue>{storage::PropertyValue("nandare"), storage::PropertyValue(123L)}), storage::PropertyValue(std::map<std::string, storage::PropertyValue>{ - {"nandare", storage::PropertyValue(123)}})); + {"nandare", storage::PropertyValue(123)}}), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))); // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GENERATE_PARTIAL_READ_TEST(name, value) \ @@ -225,8 +229,9 @@ GENERATE_PARTIAL_READ_TEST(PropertyValue, storage::PropertyValue(std::vector<storage::PropertyValue>{ storage::PropertyValue(), storage::PropertyValue(true), storage::PropertyValue(123L), storage::PropertyValue(123.5), storage::PropertyValue("nandare"), - storage::PropertyValue{std::map<std::string, storage::PropertyValue>{ - {"haihai", storage::PropertyValue()}}}})); + storage::PropertyValue{ + std::map<std::string, storage::PropertyValue>{{"haihai", storage::PropertyValue()}}}, + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))})); // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GENERATE_PARTIAL_SKIP_TEST(name, value) \ @@ -275,8 +280,9 @@ GENERATE_PARTIAL_SKIP_TEST(PropertyValue, storage::PropertyValue(std::vector<storage::PropertyValue>{ storage::PropertyValue(), storage::PropertyValue(true), storage::PropertyValue(123L), storage::PropertyValue(123.5), storage::PropertyValue("nandare"), - storage::PropertyValue{std::map<std::string, storage::PropertyValue>{ - {"haihai", storage::PropertyValue()}}}})); + storage::PropertyValue{ + std::map<std::string, storage::PropertyValue>{{"haihai", storage::PropertyValue()}}}, + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23))})); // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { @@ -299,6 +305,7 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { case storage::durability::Marker::TYPE_STRING: case storage::durability::Marker::TYPE_LIST: case storage::durability::Marker::TYPE_MAP: + case storage::durability::Marker::TYPE_TEMPORAL_DATA: case storage::durability::Marker::TYPE_PROPERTY_VALUE: valid_marker = true; break; diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp index 8b0a3fc36..3bf874b78 100644 --- a/tests/unit/storage_v2_indices.cpp +++ b/tests/unit/storage_v2_indices.cpp @@ -1,7 +1,9 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "storage/v2/property_value.hpp" #include "storage/v2/storage.hpp" +#include "storage/v2/temporal.hpp" // NOLINTNEXTLINE(google-build-using-namespace) using namespace storage; @@ -614,6 +616,9 @@ TEST_F(IndexTest, LabelPropertyIndexCountEstimate) { TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { storage.CreateIndex(label1, prop_val); + const std::array temporals{TemporalData{TemporalType::Date, 23}, TemporalData{TemporalType::Date, 28}, + TemporalData{TemporalType::LocalDateTime, 20}}; + std::vector<PropertyValue> values = { PropertyValue(false), PropertyValue(true), @@ -638,6 +643,9 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { PropertyValue(std::map<std::string, PropertyValue>()), PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(5)}}), PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(10)}}), + PropertyValue(temporals[0]), + PropertyValue(temporals[1]), + PropertyValue(temporals[2]), }; // Create vertices, each with one of the values above. @@ -735,6 +743,17 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { {PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(5)}}), PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(10)}})}); + verify(utils::MakeBoundExclusive(PropertyValue(temporals[0])), + utils::MakeBoundInclusive(PropertyValue(TemporalData{TemporalType::Date, 200})), + // LocalDateTime has a "higher" type number so it is not part of the range + {PropertyValue(temporals[1])}); + verify(utils::MakeBoundExclusive(PropertyValue(temporals[0])), utils::MakeBoundInclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + verify(utils::MakeBoundInclusive(PropertyValue(temporals[0])), utils::MakeBoundExclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[0]), PropertyValue(temporals[1])}); + verify(utils::MakeBoundInclusive(PropertyValue(temporals[0])), utils::MakeBoundInclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + // Range iteration with one unspecified bound should only yield items that // have the same type as the specified bound. verify(utils::MakeBoundInclusive(PropertyValue(false)), std::nullopt, {PropertyValue(false), PropertyValue(true)}); @@ -760,6 +779,10 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { utils::MakeBoundExclusive(PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(7.5)}})), {PropertyValue(std::map<std::string, PropertyValue>()), PropertyValue(std::map<std::string, PropertyValue>{{"id", PropertyValue(5)}})}); + verify(utils::MakeBoundInclusive(PropertyValue(TemporalData(TemporalType::Date, 10))), std::nullopt, + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + verify(std::nullopt, utils::MakeBoundExclusive(PropertyValue(TemporalData(TemporalType::Duration, 0))), + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); // Range iteration with two specified bounds that don't have the same type // should yield no items. diff --git a/tests/unit/storage_v2_property_store.cpp b/tests/unit/storage_v2_property_store.cpp index 2f0f82299..b4180dc81 100644 --- a/tests/unit/storage_v2_property_store.cpp +++ b/tests/unit/storage_v2_property_store.cpp @@ -4,6 +4,8 @@ #include <limits> #include "storage/v2/property_store.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/temporal.hpp" using testing::UnorderedElementsAre; @@ -37,6 +39,7 @@ const storage::PropertyValue kSampleValues[] = { std::map<std::string, storage::PropertyValue>{{"test", storage::PropertyValue(33)}, {"map", storage::PropertyValue(std::string("sample"))}, {"item", storage::PropertyValue(-33.33)}}), + storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23)), }; void TestIsPropertyEqual(const storage::PropertyStore &store, storage::PropertyId property, @@ -71,12 +74,22 @@ TEST(PropertyStore, Simple) { TEST(PropertyStore, SimpleLarge) { storage::PropertyStore props; auto prop = storage::PropertyId::FromInt(42); - auto value = storage::PropertyValue(std::string(10000, 'a')); - ASSERT_TRUE(props.SetProperty(prop, value)); - ASSERT_EQ(props.GetProperty(prop), value); - ASSERT_TRUE(props.HasProperty(prop)); - TestIsPropertyEqual(props, prop, value); - ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + { + auto value = storage::PropertyValue(std::string(10000, 'a')); + ASSERT_TRUE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + TestIsPropertyEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + { + auto value = storage::PropertyValue(storage::TemporalData(storage::TemporalType::Date, 23)); + ASSERT_FALSE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + TestIsPropertyEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } ASSERT_FALSE(props.SetProperty(prop, storage::PropertyValue())); ASSERT_TRUE(props.GetProperty(prop).IsNull()); @@ -227,9 +240,11 @@ TEST(PropertyStore, EmptySet) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123), storage::PropertyValue()}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; - std::vector<storage::PropertyValue> data{storage::PropertyValue(true), storage::PropertyValue(123), - storage::PropertyValue(123.5), storage::PropertyValue("nandare"), - storage::PropertyValue(vec), storage::PropertyValue(map)}; + const storage::TemporalData temporal{storage::TemporalType::LocalDateTime, 23}; + std::vector<storage::PropertyValue> data{storage::PropertyValue(true), storage::PropertyValue(123), + storage::PropertyValue(123.5), storage::PropertyValue("nandare"), + storage::PropertyValue(vec), storage::PropertyValue(map), + storage::PropertyValue(temporal)}; auto prop = storage::PropertyId::FromInt(42); for (const auto &value : data) { @@ -262,13 +277,15 @@ TEST(PropertyStore, FullSet) { std::vector<storage::PropertyValue> vec{storage::PropertyValue(true), storage::PropertyValue(123), storage::PropertyValue()}; std::map<std::string, storage::PropertyValue> map{{"nandare", storage::PropertyValue(false)}}; + const storage::TemporalData temporal{storage::TemporalType::LocalDateTime, 23}; std::map<storage::PropertyId, storage::PropertyValue> data{ {storage::PropertyId::FromInt(1), storage::PropertyValue(true)}, {storage::PropertyId::FromInt(2), storage::PropertyValue(123)}, {storage::PropertyId::FromInt(3), storage::PropertyValue(123.5)}, {storage::PropertyId::FromInt(4), storage::PropertyValue("nandare")}, {storage::PropertyId::FromInt(5), storage::PropertyValue(vec)}, - {storage::PropertyId::FromInt(6), storage::PropertyValue(map)}}; + {storage::PropertyId::FromInt(6), storage::PropertyValue(map)}, + {storage::PropertyId::FromInt(7), storage::PropertyValue(temporal)}}; std::vector<storage::PropertyValue> alt{storage::PropertyValue(), storage::PropertyValue(std::string()), @@ -585,3 +602,19 @@ TEST(PropertyStore, IsPropertyEqualMap) { {"sdf", storage::PropertyValue(true)}, {"zyx", storage::PropertyValue("test")}}))); } + +TEST(PropertyStore, IsPropertyEqualTemporalData) { + storage::PropertyStore props; + auto prop = storage::PropertyId::FromInt(42); + const storage::TemporalData temporal{storage::TemporalType::Date, 23}; + ASSERT_TRUE(props.SetProperty(prop, storage::PropertyValue(temporal))); + ASSERT_TRUE(props.IsPropertyEqual(prop, storage::PropertyValue(temporal))); + + // Different type. + ASSERT_FALSE( + props.IsPropertyEqual(prop, storage::PropertyValue(storage::TemporalData{storage::TemporalType::Duration, 23}))); + + // Same type, different value. + ASSERT_FALSE( + props.IsPropertyEqual(prop, storage::PropertyValue(storage::TemporalData{storage::TemporalType::Date, 30}))); +}