Add temporal types to TypedValue (#176)

This commit is contained in:
antonio2368 2021-07-01 16:04:25 +02:00 committed by Antonio Andelic
parent 5da32c1bff
commit e628b5ba6e
13 changed files with 762 additions and 10 deletions

View File

@ -46,7 +46,7 @@ inline nlohmann::json PropertyValueToJson(const storage::PropertyValue &pv) {
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
// 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;

View File

@ -99,6 +99,13 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
if (maybe_path.HasError()) return maybe_path.GetError();
return Value(std::move(*maybe_path));
}
case query::TypedValue::Type::Date:
case query::TypedValue::Type::LocalTime:
case query::TypedValue::Type::LocalDateTime:
case query::TypedValue::Type::Duration:
// TODO(antonio2368): Change this when Bolt value for temporal types
// are implemented
LOG_FATAL("Temporal types not yet supported");
}
}

View File

@ -33,6 +33,7 @@ set(mg_query_sources
procedure/py_module.cpp
serialization/property_value.cpp
streams.cpp
temporal.cpp
trigger.cpp
trigger_context.cpp
typed_value.cpp)

View File

@ -142,7 +142,7 @@ void PrintObject(std::ostream *out, const storage::PropertyValue &value) {
PrintObject(out, value.ValueMap());
break;
case storage::PropertyValue::Type::TemporalData:
// TODO (antonio2368): Print out the temporal data based on the type
// TODO(antonio2368): Print out the temporal data based on the type
LOG_FATAL("Not implemented!");
}
}

View File

@ -542,6 +542,14 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
return TypedValue("RELATIONSHIP", ctx.memory);
case TypedValue::Type::Path:
return TypedValue("PATH", ctx.memory);
case TypedValue::Type::Date:
return TypedValue("DATE", ctx.memory);
case TypedValue::Type::LocalTime:
return TypedValue("LOCAL_TIME", ctx.memory);
case TypedValue::Type::LocalDateTime:
return TypedValue("LOCAL_DATE_TIME", ctx.memory);
case TypedValue::Type::Duration:
return TypedValue("DURATION", ctx.memory);
}
}

View File

@ -262,6 +262,9 @@ mgp_value_type FromTypedValueType(query::TypedValue::Type type) {
return MGP_VALUE_TYPE_EDGE;
case query::TypedValue::Type::Path:
return MGP_VALUE_TYPE_PATH;
default:
// TODO(antonio2368): Implement this when we add mgp temporal types
LOG_FATAL("Unsupported value in the procedures");
}
}
@ -478,7 +481,7 @@ mgp_value::mgp_value(const storage::PropertyValue &pv, utils::MemoryResource *m)
break;
}
case storage::PropertyValue::Type::TemporalData: {
// TODO (antonio2368): Add support for temporala data types
// TODO(antonio2368): Add support for temporala data types
LOG_FATAL("Unsupported type");
}
}
@ -1942,6 +1945,12 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) {
case TypedValue::Type::Edge:
case TypedValue::Type::Path:
LOG_FATAL("value must not be a graph element");
case TypedValue::Type::Date:
case TypedValue::Type::LocalTime:
case TypedValue::Type::LocalDateTime:
case TypedValue::Type::Duration:
// TODO(antonio2368): Check how to print out nicely temporal types
LOG_FATAL("Temporal types not imlemented yet");
}
}

198
src/query/temporal.cpp Normal file
View File

@ -0,0 +1,198 @@
#include "query/temporal.hpp"
#include <chrono>
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
namespace query {
namespace {
template <typename T>
concept Chrono = requires(T) {
typename T::rep;
typename T::period;
};
template <Chrono TFirst, Chrono TSecond>
constexpr auto GetAndSubtractDuration(TSecond &base_duration) {
const auto duration = std::chrono::duration_cast<TFirst>(base_duration);
base_duration -= duration;
return duration.count();
}
constexpr std::chrono::microseconds epoch{std::chrono::years{1970} + std::chrono::months{1} + std::chrono::days{1}};
constexpr bool IsInBounds(const auto low, const auto high, const auto value) { return low <= value && value <= high; }
constexpr bool IsValidDay(const auto day, const auto month, const auto year) {
constexpr std::array<uint8_t, 12> days{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
MG_ASSERT(IsInBounds(1, 12, month), "Invalid month!");
if (day <= 0) {
return false;
}
const auto is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
uint8_t leap_day = month == 2 && is_leap_year;
return day <= (days[month - 1] + leap_day);
}
} // namespace
Date::Date(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
chrono_microseconds += epoch;
MG_ASSERT(chrono_microseconds.count() >= 0, "Invalid Date specified in microseconds");
years = GetAndSubtractDuration<std::chrono::years>(chrono_microseconds);
months = GetAndSubtractDuration<std::chrono::months>(chrono_microseconds);
days = GetAndSubtractDuration<std::chrono::days>(chrono_microseconds);
}
Date::Date(const DateParameters &date_parameters) {
if (!IsInBounds(0, 9999, date_parameters.years)) {
throw utils::BasicException("Creating a Date with invalid year parameter.");
}
// TODO(antonio2368): Replace with year_month_day when it's implemented
// https://en.cppreference.com/w/cpp/chrono/year_month_day/ok
if (!IsInBounds(1, 12, date_parameters.months)) {
throw utils::BasicException("Creating a Date with invalid month parameter.");
}
if (!IsValidDay(date_parameters.days, date_parameters.months, date_parameters.years)) {
throw utils::BasicException("Creating a Date with invalid day parameter.");
}
years = date_parameters.years;
months = date_parameters.months;
days = date_parameters.days;
}
int64_t Date::MicrosecondsSinceEpoch() const {
auto result = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::years{years} + std::chrono::months{months} + std::chrono::days{days});
result -= epoch;
return result.count();
}
size_t DateHash::operator()(const Date &date) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, date.years);
result = hasher(result, date.months);
result = hasher(result, date.days);
return result;
}
LocalTime::LocalTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
MG_ASSERT(chrono_microseconds.count() >= 0, "Negative LocalTime specified in microseconds");
const auto parsed_hours = GetAndSubtractDuration<std::chrono::hours>(chrono_microseconds);
MG_ASSERT(parsed_hours <= 23, "Invalid LocalTime specified in microseconds");
hours = parsed_hours;
minutes = GetAndSubtractDuration<std::chrono::minutes>(chrono_microseconds);
seconds = GetAndSubtractDuration<std::chrono::seconds>(chrono_microseconds);
milliseconds = GetAndSubtractDuration<std::chrono::milliseconds>(chrono_microseconds);
this->microseconds = chrono_microseconds.count();
}
LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
if (!IsInBounds(0, 23, local_time_parameters.hours)) {
throw utils::BasicException("Creating a LocalTime with invalid hour parameter.");
}
if (!IsInBounds(0, 59, local_time_parameters.minutes)) {
throw utils::BasicException("Creating a LocalTime with invalid minutes parameter.");
}
if (!IsInBounds(0, 59, local_time_parameters.seconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.microseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
hours = local_time_parameters.hours;
minutes = local_time_parameters.minutes;
seconds = local_time_parameters.seconds;
milliseconds = local_time_parameters.milliseconds;
microseconds = local_time_parameters.microseconds;
}
int64_t LocalTime::MicrosecondsSinceEpoch() const {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::hours{hours} + std::chrono::minutes{minutes} + std::chrono::seconds{seconds} +
std::chrono::milliseconds{milliseconds} + std::chrono::microseconds{microseconds})
.count();
}
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, local_time.hours);
result = hasher(result, local_time.minutes);
result = hasher(result, local_time.seconds);
result = hasher(result, local_time.milliseconds);
result = hasher(result, local_time.microseconds);
return result;
}
LocalDateTime::LocalDateTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
date = Date(chrono_microseconds.count());
chrono_microseconds -= std::chrono::microseconds{date.MicrosecondsSinceEpoch()};
local_time = LocalTime(chrono_microseconds.count());
}
// return microseconds normilized with regard to epoch time point
int64_t LocalDateTime::MicrosecondsSinceEpoch() const {
return date.MicrosecondsSinceEpoch() + local_time.MicrosecondsSinceEpoch();
}
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
: date(date_parameters), local_time(local_time_parameters) {}
size_t LocalDateTimeHash::operator()(const LocalDateTime &local_date_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, LocalTimeHash{}(local_date_time.local_time));
result = hasher(result, DateHash{}(local_date_time.date));
return result;
}
Duration::Duration(int64_t microseconds) { this->microseconds = microseconds; }
namespace {
template <Chrono From, Chrono To>
constexpr To CastChronoDouble(const double value) {
return std::chrono::duration_cast<To>(std::chrono::duration<double, typename From::period>(value));
};
} // namespace
Duration::Duration(const DurationParameters &parameters) {
microseconds = (CastChronoDouble<std::chrono::years, std::chrono::microseconds>(parameters.years) +
CastChronoDouble<std::chrono::months, std::chrono::microseconds>(parameters.months) +
CastChronoDouble<std::chrono::days, std::chrono::microseconds>(parameters.days) +
CastChronoDouble<std::chrono::hours, std::chrono::microseconds>(parameters.hours) +
CastChronoDouble<std::chrono::minutes, std::chrono::microseconds>(parameters.minutes) +
CastChronoDouble<std::chrono::seconds, std::chrono::microseconds>(parameters.seconds))
.count();
}
Duration Duration::operator-() const {
Duration result{-microseconds};
return result;
}
size_t DurationHash::operator()(const Duration &duration) const { return std::hash<int64_t>{}(duration.microseconds); }
} // namespace query

105
src/query/temporal.hpp Normal file
View File

@ -0,0 +1,105 @@
#pragma once
#include <chrono>
#include <cstdint>
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
namespace query {
struct DateParameters {
int64_t years{0};
int64_t months{1};
int64_t days{1};
};
struct Date {
explicit Date() : Date{DateParameters{}} {}
// we assume we accepted date in microseconds which was normilized using the epoch time point
explicit Date(int64_t microseconds);
explicit Date(const DateParameters &date_parameters);
int64_t MicrosecondsSinceEpoch() const;
auto operator<=>(const Date &) const = default;
uint16_t years;
uint8_t months;
uint8_t days;
};
struct DateHash {
size_t operator()(const Date &date) const;
};
struct LocalTimeParameters {
int64_t hours{0};
int64_t minutes{0};
int64_t seconds{0};
int64_t milliseconds{0};
int64_t microseconds{0};
};
struct LocalTime {
explicit LocalTime() : LocalTime{LocalTimeParameters{}} {}
explicit LocalTime(int64_t microseconds);
explicit LocalTime(const LocalTimeParameters &local_time_parameters);
int64_t MicrosecondsSinceEpoch() const;
auto operator<=>(const LocalTime &) const = default;
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
uint16_t milliseconds;
uint16_t microseconds;
};
struct LocalTimeHash {
size_t operator()(const LocalTime &local_time) const;
};
struct LocalDateTime {
explicit LocalDateTime(int64_t microseconds);
explicit LocalDateTime(DateParameters date, const LocalTimeParameters &local_time);
int64_t MicrosecondsSinceEpoch() const;
auto operator<=>(const LocalDateTime &) const = default;
Date date;
LocalTime local_time;
};
struct LocalDateTimeHash {
size_t operator()(const LocalDateTime &local_date_time) const;
};
struct DurationParameters {
double years{0};
double months{0};
double days{0};
double hours{0};
double minutes{0};
double seconds{0};
// TODO(antonio2368): Check how to include milliseconds/microseconds
// ISO 8601 does not specify string format for them
};
struct Duration {
explicit Duration(int64_t microseconds);
explicit Duration(const DurationParameters &parameters);
auto operator<=>(const Duration &) const = default;
Duration operator-() const;
int64_t microseconds;
};
struct DurationHash {
size_t operator()(const Duration &duration) const;
};
} // namespace query

View File

@ -1,12 +1,15 @@
#include "query/typed_value.hpp"
#include <fmt/format.h>
#include <chrono>
#include <cmath>
#include <iostream>
#include <memory>
#include <string_view>
#include <utility>
#include "query/temporal.hpp"
#include "storage/v2/temporal.hpp"
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
@ -52,9 +55,32 @@ 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;
case storage::PropertyValue::Type::TemporalData: {
const auto &temporal_data = value.ValueTemporalData();
switch (temporal_data.type) {
case storage::TemporalType::Date: {
type_ = Type::Date;
new (&date_v) Date(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalTime: {
type_ = Type::LocalTime;
new (&local_time_v) LocalTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalDateTime: {
type_ = Type::LocalDateTime;
new (&local_date_time_v) LocalDateTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::Duration: {
type_ = Type::Duration;
new (&duration_v) Duration(temporal_data.microseconds);
break;
}
}
return;
}
}
LOG_FATAL("Unsupported type");
}
@ -99,9 +125,32 @@ 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");
case storage::PropertyValue::Type::TemporalData: {
const auto &temporal_data = other.ValueTemporalData();
switch (temporal_data.type) {
case storage::TemporalType::Date: {
type_ = Type::Date;
new (&date_v) Date(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalTime: {
type_ = Type::LocalTime;
new (&local_time_v) LocalTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalDateTime: {
type_ = Type::LocalDateTime;
new (&local_date_time_v) LocalDateTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::Duration: {
type_ = Type::Duration;
new (&duration_v) Duration(temporal_data.microseconds);
break;
}
}
break;
}
}
other = storage::PropertyValue();
@ -143,6 +192,18 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) :
case Type::Path:
new (&path_v) Path(other.path_v, memory_);
return;
case Type::Date:
new (&date_v) Date(other.date_v);
return;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
return;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
return;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
return;
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@ -180,6 +241,18 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo
case Type::Path:
new (&path_v) Path(std::move(other.path_v), memory_);
break;
case Type::Date:
new (&date_v) Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
break;
}
other.DestroyValue();
}
@ -203,6 +276,17 @@ TypedValue::operator storage::PropertyValue() const {
for (const auto &kv : map_v) map.emplace(kv.first, kv.second);
return storage::PropertyValue(std::move(map));
}
case Type::Date:
return storage::PropertyValue(
storage::TemporalData{storage::TemporalType::Date, date_v.MicrosecondsSinceEpoch()});
case Type::LocalTime:
return storage::PropertyValue(
storage::TemporalData{storage::TemporalType::LocalTime, local_time_v.MicrosecondsSinceEpoch()});
case Type::LocalDateTime:
return storage::PropertyValue(
storage::TemporalData{storage::TemporalType::LocalDateTime, local_date_time_v.MicrosecondsSinceEpoch()});
case Type::Duration:
return storage::PropertyValue(storage::TemporalData{storage::TemporalType::Duration, duration_v.microseconds});
default:
break;
}
@ -233,6 +317,10 @@ DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::TMap, Map, map_v)
DEFINE_VALUE_AND_TYPE_GETTERS(VertexAccessor, Vertex, vertex_v)
DEFINE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge, edge_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Path, Path, path_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Date, Date, date_v)
DEFINE_VALUE_AND_TYPE_GETTERS(LocalTime, LocalTime, local_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(LocalDateTime, LocalDateTime, local_date_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Duration, Duration, duration_v)
#undef DEFINE_VALUE_AND_TYPE_GETTERS
@ -249,6 +337,10 @@ bool TypedValue::IsPropertyValue() const {
case Type::String:
case Type::List:
case Type::Map:
case Type::Date:
case Type::LocalTime:
case Type::LocalDateTime:
case Type::Duration:
return true;
default:
return false;
@ -277,6 +369,14 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
return os << "edge";
case TypedValue::Type::Path:
return os << "path";
case TypedValue::Type::Date:
return os << "date";
case TypedValue::Type::LocalTime:
return os << "local_time";
case TypedValue::Type::LocalDateTime:
return os << "local_date_time";
case TypedValue::Type::Duration:
return os << "duration";
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@ -325,6 +425,10 @@ TypedValue &TypedValue::operator=(const std::map<std::string, TypedValue> &other
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const VertexAccessor &, Vertex, vertex_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const EdgeAccessor &, Edge, edge_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Path &, Path, path_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Date &, Date, date_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const LocalTime &, LocalTime, local_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const LocalDateTime &, LocalDateTime, local_date_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Duration &, Duration, duration_v)
#undef DEFINE_TYPED_VALUE_COPY_ASSIGNMENT
@ -408,6 +512,18 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
case TypedValue::Type::Path:
new (&path_v) Path(other.path_v, memory_);
return *this;
case Type::Date:
new (&date_v) Date(other.date_v);
return *this;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
return *this;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
return *this;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
return *this;
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@ -455,6 +571,18 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
case TypedValue::Type::Path:
new (&path_v) Path(std::move(other.path_v), memory_);
break;
case Type::Date:
new (&date_v) Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
break;
}
other.DestroyValue();
}
@ -490,6 +618,11 @@ void TypedValue::DestroyValue() {
case Type::Path:
path_v.~Path();
break;
case Type::Date:
case Type::LocalTime:
case Type::LocalDateTime:
case Type::Duration:
break;
}
type_ = TypedValue::Type::Null;
@ -515,6 +648,15 @@ double ToDouble(const TypedValue &value) {
}
}
namespace {
bool IsTemporalType(const TypedValue::Type type) {
constexpr std::array temporal_types{TypedValue::Type::Date, TypedValue::Type::LocalTime,
TypedValue::Type::LocalDateTime, TypedValue::Type::Duration};
return std::any_of(temporal_types.begin(), temporal_types.end(),
[type](const auto temporal_type) { return temporal_type == type; });
};
} // namespace
TypedValue operator<(const TypedValue &a, const TypedValue &b) {
auto is_legal = [](TypedValue::Type type) {
switch (type) {
@ -522,6 +664,10 @@ TypedValue operator<(const TypedValue &a, const TypedValue &b) {
case TypedValue::Type::Int:
case TypedValue::Type::Double:
case TypedValue::Type::String:
case TypedValue::Type::Date:
case TypedValue::Type::LocalTime:
case TypedValue::Type::LocalDateTime:
case TypedValue::Type::Duration:
return true;
default:
return false;
@ -540,6 +686,29 @@ TypedValue operator<(const TypedValue &a, const TypedValue &b) {
}
}
if (IsTemporalType(a.type()) || IsTemporalType(b.type())) {
if (a.type() != b.type()) {
throw TypedValueException("Invalid 'less' operand types({} + {})", a.type(), b.type());
}
switch (a.type()) {
case TypedValue::Type::Date:
// NOLINTNEXTLINE(modernize-use-nullptr)
return TypedValue(a.ValueDate() < b.ValueDate(), a.GetMemoryResource());
case TypedValue::Type::LocalTime:
// NOLINTNEXTLINE(modernize-use-nullptr)
return TypedValue(a.ValueLocalTime() < b.ValueLocalTime(), a.GetMemoryResource());
case TypedValue::Type::LocalDateTime:
// NOLINTNEXTLINE(modernize-use-nullptr)
return TypedValue(a.ValueLocalDateTime() < b.ValueLocalDateTime(), a.GetMemoryResource());
case TypedValue::Type::Duration:
// NOLINTNEXTLINE(modernize-use-nullptr)
return TypedValue(a.ValueDuration() < b.ValueDuration(), a.GetMemoryResource());
default:
LOG_FATAL("Invalid temporal type");
}
}
// at this point we only have int and double
if (a.IsDouble() || b.IsDouble()) {
return TypedValue(ToDouble(a) < ToDouble(b), a.GetMemoryResource());
@ -605,6 +774,14 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) {
}
case TypedValue::Type::Path:
return TypedValue(a.ValuePath() == b.ValuePath(), a.GetMemoryResource());
case TypedValue::Type::Date:
return TypedValue(a.ValueDate() == b.ValueDate(), a.GetMemoryResource());
case TypedValue::Type::LocalTime:
return TypedValue(a.ValueLocalTime() == b.ValueLocalTime(), a.GetMemoryResource());
case TypedValue::Type::LocalDateTime:
return TypedValue(a.ValueLocalDateTime() == b.ValueLocalDateTime(), a.GetMemoryResource());
case TypedValue::Type::Duration:
return TypedValue(a.ValueDuration() == b.ValueDuration(), a.GetMemoryResource());
default:
LOG_FATAL("Unhandled comparison for types");
}
@ -635,6 +812,7 @@ TypedValue operator-(const TypedValue &a) {
if (a.IsNull()) return TypedValue(a.GetMemoryResource());
if (a.IsInt()) return TypedValue(-a.ValueInt(), a.GetMemoryResource());
if (a.IsDouble()) return TypedValue(-a.ValueDouble(), a.GetMemoryResource());
if (a.IsDuration()) return TypedValue(-a.ValueDuration(), a.GetMemoryResource());
throw TypedValueException("Invalid unary minus operand type (-{})", a.type());
}
@ -666,6 +844,7 @@ inline void EnsureArithmeticallyOk(const TypedValue &a, const TypedValue &b, boo
// checked here because they are handled before this check is performed in
// arithmetic op implementations.
// TODO(antonio2368): Introduce typed value arithmetic
if (!is_legal(a) || !is_legal(b))
throw TypedValueException("Invalid {} operand types {}, {}", op_name, a.type(), b.type());
}
@ -699,6 +878,7 @@ TypedValue operator+(const TypedValue &a, const TypedValue &b) {
} else {
return TypedValue(a.ValueInt() + b.ValueInt(), a.GetMemoryResource());
}
// TODO(antonio2368): Introduce typed value arithmetic
}
TypedValue operator-(const TypedValue &a, const TypedValue &b) {
@ -711,6 +891,7 @@ TypedValue operator-(const TypedValue &a, const TypedValue &b) {
} else {
return TypedValue(a.ValueInt() - b.ValueInt(), a.GetMemoryResource());
}
// TODO(antonio2368): Introduce typed value arithmetic
}
TypedValue operator/(const TypedValue &a, const TypedValue &b) {
@ -838,6 +1019,15 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
return utils::FnvCollection<decltype(vertices), VertexAccessor>{}(vertices) ^
utils::FnvCollection<decltype(edges), EdgeAccessor>{}(edges);
}
case TypedValue::Type::Date:
return DateHash{}(value.ValueDate());
case TypedValue::Type::LocalTime:
return LocalTimeHash{}(value.ValueLocalTime());
case TypedValue::Type::LocalDateTime:
return LocalDateTimeHash{}(value.ValueLocalDateTime());
case TypedValue::Type::Duration:
return DurationHash{}(value.ValueDuration());
break;
}
LOG_FATAL("Unhandled TypedValue.type() in hash function");
}

View File

@ -11,6 +11,7 @@
#include "query/db_accessor.hpp"
#include "query/path.hpp"
#include "query/temporal.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
#include "utils/pmr/map.hpp"
@ -56,7 +57,22 @@ class TypedValue {
};
/** A value type. Each type corresponds to exactly one C++ type */
enum class Type : unsigned { Null, Bool, Int, Double, String, List, Map, Vertex, Edge, Path };
enum class Type : unsigned {
Null,
Bool,
Int,
Double,
String,
List,
Map,
Vertex,
Edge,
Path,
Date,
LocalTime,
LocalDateTime,
Duration
};
// TypedValue at this exact moment of compilation is an incomplete type, and
// the standard says that instantiating a container with an incomplete type
@ -124,6 +140,26 @@ class TypedValue {
double_v = value;
}
explicit TypedValue(const Date &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Date) {
date_v = value;
}
explicit TypedValue(const LocalTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::LocalTime) {
local_time_v = value;
}
explicit TypedValue(const LocalDateTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::LocalDateTime) {
local_date_time_v = value;
}
explicit TypedValue(const Duration &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Duration) {
duration_v = value;
}
// conversion function to storage::PropertyValue
explicit operator storage::PropertyValue() const;
@ -381,6 +417,10 @@ class TypedValue {
TypedValue &operator=(const VertexAccessor &);
TypedValue &operator=(const EdgeAccessor &);
TypedValue &operator=(const Path &);
TypedValue &operator=(const Date &);
TypedValue &operator=(const LocalTime &);
TypedValue &operator=(const LocalDateTime &);
TypedValue &operator=(const Duration &);
/** Copy assign other, utils::MemoryResource of `this` is used */
TypedValue &operator=(const TypedValue &other);
@ -431,6 +471,11 @@ class TypedValue {
DECLARE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge)
DECLARE_VALUE_AND_TYPE_GETTERS(Path, Path)
DECLARE_VALUE_AND_TYPE_GETTERS(Date, Date)
DECLARE_VALUE_AND_TYPE_GETTERS(LocalTime, LocalTime)
DECLARE_VALUE_AND_TYPE_GETTERS(LocalDateTime, LocalDateTime)
DECLARE_VALUE_AND_TYPE_GETTERS(Duration, Duration)
#undef DECLARE_VALUE_AND_TYPE_GETTERS
/** Checks if value is a TypedValue::Null. */
@ -468,6 +513,10 @@ class TypedValue {
VertexAccessor vertex_v;
EdgeAccessor edge_v;
Path path_v;
Date date_v;
LocalTime local_time_v;
LocalDateTime local_date_time_v;
Duration duration_v;
};
/**

View File

@ -125,6 +125,8 @@ target_link_libraries(${test_prefix}query_serialization_property_value mg-query)
add_unit_test(query_streams.cpp)
target_link_libraries(${test_prefix}query_streams mg-query kafka-mock)
add_unit_test(query_temporal query_temporal.cpp)
target_link_libraries(${test_prefix}query_temporal mg-query)
# Test query/procedure
add_unit_test(query_procedure_mgp_type.cpp)

View File

@ -3,6 +3,7 @@
#include <sstream>
#include <string>
#include "query/temporal.hpp"
#include "query/typed_value.hpp"
#include "utils/algorithm.hpp"
@ -61,6 +62,15 @@ inline std::string ToString(const query::Path &path, const TAccessor &acc) {
return os.str();
}
// TODO(antonio2368): Define printing of dates
inline std::string ToString(const query::Date) { return ""; }
inline std::string ToString(const query::LocalTime) { return ""; }
inline std::string ToString(const query::LocalDateTime) { return ""; }
inline std::string ToString(const query::Duration) { return ""; }
template <class TAccessor>
inline std::string ToString(const query::TypedValue &value, const TAccessor &acc) {
std::ostringstream os;
@ -102,6 +112,18 @@ inline std::string ToString(const query::TypedValue &value, const TAccessor &acc
case query::TypedValue::Type::Path:
os << ToString(value.ValuePath(), acc);
break;
case query::TypedValue::Type::Date:
os << ToString(value.ValueDate());
break;
case query::TypedValue::Type::LocalTime:
os << ToString(value.ValueLocalTime());
break;
case query::TypedValue::Type::LocalDateTime:
os << ToString(value.ValueLocalDateTime());
break;
case query::TypedValue::Type::Duration:
os << ToString(value.ValueDuration());
break;
}
return os.str();
}

View File

@ -0,0 +1,161 @@
#include <optional>
#include <gtest/gtest.h>
#include "query/temporal.hpp"
#include "utils/exceptions.hpp"
namespace {
struct TestDateParameters {
query::DateParameters date_parameters;
bool should_throw;
};
constexpr std::array test_dates{TestDateParameters{{-1996, 11, 22}, true}, TestDateParameters{{1996, -11, 22}, true},
TestDateParameters{{1996, 11, -22}, true}, TestDateParameters{{1, 13, 3}, true},
TestDateParameters{{1, 12, 32}, true}, TestDateParameters{{1, 2, 29}, true},
TestDateParameters{{2020, 2, 29}, false}, TestDateParameters{{1700, 2, 29}, true},
TestDateParameters{{1200, 2, 29}, false}, TestDateParameters{{10000, 12, 3}, true}};
struct TestLocalTimeParameters {
query::LocalTimeParameters local_time_parameters;
bool should_throw;
};
constexpr std::array test_local_times{
TestLocalTimeParameters{{.hours = 24}, true}, TestLocalTimeParameters{{.hours = -1}, true},
TestLocalTimeParameters{{.minutes = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.seconds = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.milliseconds = -1}, true}, TestLocalTimeParameters{{.milliseconds = 1000}, true},
TestLocalTimeParameters{{.microseconds = -1}, true}, TestLocalTimeParameters{{.microseconds = 1000}, true},
TestLocalTimeParameters{{23, 59, 59, 999, 999}, false}, TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}};
} // namespace
TEST(TemporalTest, DateConstruction) {
std::optional<query::Date> test_date;
for (const auto [date_parameters, should_throw] : test_dates) {
if (should_throw) {
ASSERT_THROW(test_date.emplace(date_parameters), utils::BasicException);
} else {
ASSERT_NO_THROW(test_date.emplace(date_parameters));
}
}
}
TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const auto date_parameters) {
query::Date initial_date{date_parameters};
const auto microseconds = initial_date.MicrosecondsSinceEpoch();
query::Date new_date{microseconds};
ASSERT_EQ(initial_date, new_date);
};
check_microseconds(query::DateParameters{2020, 11, 22});
check_microseconds(query::DateParameters{1900, 2, 22});
check_microseconds(query::DateParameters{0, 1, 1});
ASSERT_THROW(check_microseconds(query::DateParameters{-10, 1, 1}), utils::BasicException);
{
query::Date date{query::DateParameters{1970, 1, 1}};
ASSERT_EQ(date.MicrosecondsSinceEpoch(), 0);
}
{
query::Date date{query::DateParameters{1910, 1, 1}};
ASSERT_LT(date.MicrosecondsSinceEpoch(), 0);
}
{
query::Date date{query::DateParameters{2021, 1, 1}};
ASSERT_GT(date.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, LocalTimeConstruction) {
std::optional<query::LocalTime> test_local_time;
for (const auto [local_time_parameters, should_throw] : test_local_times) {
if (should_throw) {
ASSERT_THROW(test_local_time.emplace(local_time_parameters), utils::BasicException);
} else {
ASSERT_NO_THROW(test_local_time.emplace(local_time_parameters));
}
}
}
TEST(TemporalTest, LocalTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const query::LocalTimeParameters &parameters) {
query::LocalTime initial_local_time{parameters};
const auto microseconds = initial_local_time.MicrosecondsSinceEpoch();
query::LocalTime new_local_time{microseconds};
ASSERT_EQ(initial_local_time, new_local_time);
};
check_microseconds(query::LocalTimeParameters{23, 59, 59, 999, 999});
check_microseconds(query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::LocalTimeParameters{14, 8, 55, 321, 452});
}
TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const query::DateParameters date_parameters,
const query::LocalTimeParameters &local_time_parameters) {
query::LocalDateTime initial_local_date_time{date_parameters, local_time_parameters};
const auto microseconds = initial_local_date_time.MicrosecondsSinceEpoch();
query::LocalDateTime new_local_date_time{microseconds};
ASSERT_EQ(initial_local_date_time, new_local_date_time);
};
check_microseconds(query::DateParameters{2020, 11, 22}, query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::DateParameters{1900, 2, 22}, query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::DateParameters{0, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
{
query::LocalDateTime local_date_time(query::DateParameters{1970, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalDateTime local_date_time(query::DateParameters{1970, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 1});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalTimeParameters local_time_parameters{12, 10, 40, 42, 42};
query::LocalDateTime local_date_time{query::DateParameters{1970, 1, 1}, local_time_parameters};
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(),
query::LocalTime{local_time_parameters}.MicrosecondsSinceEpoch());
}
{
query::LocalDateTime local_date_time(query::DateParameters{1910, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_LT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalDateTime local_date_time(query::DateParameters{2021, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, DurationConversion) {
{
query::Duration duration{{.years = 2.5}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 6 + 1);
};
{
query::Duration duration{{.months = 26}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 2 + 1);
};
{
query::Duration duration{{.minutes = 123.25}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 1970);
ASSERT_EQ(local_date_time.date.months, 1);
ASSERT_EQ(local_date_time.date.days, 1);
ASSERT_EQ(local_date_time.local_time.hours, 2);
ASSERT_EQ(local_date_time.local_time.minutes, 3);
ASSERT_EQ(local_date_time.local_time.seconds, 15);
};
}