Add support for temporal data types in the communication Bolt layer (#198)

This commit is contained in:
Kostas Kyrimis 2021-08-11 19:04:01 +03:00 committed by Antonio Andelic
parent 4e604de9d7
commit 6913feacc7
11 changed files with 1058 additions and 74 deletions

View File

@ -30,6 +30,12 @@ enum class Signature : uint8_t {
Relationship = 0x52,
Path = 0x50,
UnboundRelationship = 0x72,
/// Temporal data types
Date = 0x44,
Duration = 0x45,
LocalDateTime = 0x64,
LocalTime = 0x74,
};
enum class Marker : uint8_t {

View File

@ -1,5 +1,7 @@
#pragma once
#include <array>
#include <chrono>
#include <string>
#include "communication/bolt/v1/codes.hpp"
@ -7,6 +9,7 @@
#include "utils/cast.hpp"
#include "utils/endian.hpp"
#include "utils/logging.hpp"
#include "utils/temporal.hpp"
namespace communication::bolt {
@ -69,12 +72,37 @@ class Decoder {
case Marker::Map16:
case Marker::Map32:
return ReadMap(marker, data);
case Marker::TinyStruct1: {
uint8_t signature = 0;
if (!buffer_.Read(&signature, 1)) {
return false;
}
switch (static_cast<Signature>(signature)) {
case Signature::Date:
return ReadDate(data);
case Signature::LocalTime:
return ReadLocalTime(data);
default:
return false;
}
}
case Marker::TinyStruct2: {
uint8_t signature = 0;
if (!buffer_.Read(&signature, 1)) {
return false;
}
switch (static_cast<Signature>(signature)) {
case Signature::LocalDateTime:
return ReadLocalDateTime(data);
default:
return false;
}
}
case Marker::TinyStruct3: {
// For tiny struct 3 we will also read the Signature to switch between
// vertex, unbounded_edge and path. Note that in those functions we
// won't perform an additional signature read.
uint8_t signature;
uint8_t signature = 0;
if (!buffer_.Read(&signature, 1)) {
return false;
}
@ -89,10 +117,30 @@ class Decoder {
return false;
}
}
case Marker::TinyStruct5:
return ReadEdge(marker, data);
case Marker::TinyStruct4: {
uint8_t signature = 0;
if (!buffer_.Read(&signature, 1)) {
return false;
}
switch (static_cast<Signature>(signature)) {
case Signature::Duration:
return ReadDuration(data);
default:
return false;
}
}
case Marker::TinyStruct5: {
uint8_t signature = 0;
if (!buffer_.Read(&signature, 1)) {
return false;
}
switch (static_cast<Signature>(signature)) {
case Signature::Relationship:
return ReadEdge(data);
default:
return false;
}
}
default:
if ((value & 0xF0) == utils::UnderlyingCast(Marker::TinyString)) {
return ReadString(marker, data);
@ -351,24 +399,11 @@ class Decoder {
return true;
}
bool ReadEdge(const Marker &marker, Value *data) {
uint8_t value;
bool ReadEdge(Value *data) {
Value dv;
*data = Value(Edge());
auto &edge = data->ValueEdge();
if (!buffer_.Read(&value, 1)) {
return false;
}
// check header
if (marker != Marker::TinyStruct5) {
return false;
}
if (value != utils::UnderlyingCast(Signature::Relationship)) {
return false;
}
// read ID
if (!ReadValue(&dv, Value::Type::Int)) {
return false;
@ -468,5 +503,81 @@ class Decoder {
return true;
}
bool ReadDate(Value *data) {
Value dv;
if (!ReadValue(&dv, Value::Type::Int)) {
return false;
}
const auto chrono_days = std::chrono::days(dv.ValueInt());
const auto sys_days = std::chrono::sys_days(chrono_days);
const auto date = std::chrono::year_month_day(sys_days);
*data = Value(utils::Date(
{static_cast<int>(date.year()), static_cast<unsigned>(date.month()), static_cast<unsigned>(date.day())}));
return true;
}
bool ReadLocalTime(Value *data) {
Value dv;
if (!ReadValue(&dv, Value::Type::Int)) {
return false;
}
namespace chrono = std::chrono;
const auto nanos = chrono::nanoseconds(dv.ValueInt());
const auto microseconds = chrono::duration_cast<chrono::microseconds>(nanos);
*data = Value(utils::LocalTime(microseconds.count()));
return true;
}
bool ReadLocalDateTime(Value *data) {
Value secs;
if (!ReadValue(&secs, Value::Type::Int)) {
return false;
}
Value nanos;
if (!ReadValue(&nanos, Value::Type::Int)) {
return false;
}
namespace chrono = std::chrono;
const auto chrono_seconds = chrono::seconds(secs.ValueInt());
const auto sys_seconds = chrono::sys_seconds(chrono_seconds);
const auto sys_days = chrono::time_point_cast<chrono::days>(sys_seconds);
const auto date = chrono::year_month_day(sys_days);
const auto ldt = utils::Date(
{static_cast<int>(date.year()), static_cast<unsigned>(date.month()), static_cast<unsigned>(date.day())});
auto secs_leftover = chrono::seconds(sys_seconds - sys_days);
const auto h = utils::GetAndSubtractDuration<chrono::hours>(secs_leftover);
const auto m = utils::GetAndSubtractDuration<chrono::minutes>(secs_leftover);
const auto s = secs_leftover.count();
auto nanos_leftover = chrono::nanoseconds(nanos.ValueInt());
const auto ml = utils::GetAndSubtractDuration<chrono::milliseconds>(nanos_leftover);
const auto mi = chrono::duration_cast<chrono::microseconds>(nanos_leftover).count();
const auto params = utils::LocalTimeParameters{h, m, s, ml, mi};
const auto tm = utils::LocalTime(params);
*data = utils::LocalDateTime(ldt, tm);
return true;
}
bool ReadDuration(Value *data) {
Value dv;
std::array<int64_t, 4> values{0};
for (auto &val : values) {
if (!ReadValue(&dv, Value::Type::Int)) {
return false;
}
val = dv.ValueInt();
}
namespace chrono = std::chrono;
const auto months = chrono::months(values[0]);
const auto days = chrono::days(values[1]);
const auto secs = chrono::seconds(values[2]);
const auto nanos = chrono::nanoseconds(values[3]);
const auto micros = months + days + secs + chrono::duration_cast<chrono::microseconds>(nanos);
*data = Value(utils::Duration(micros.count()));
return true;
}
};
} // namespace communication::bolt

View File

@ -15,7 +15,8 @@ namespace communication::bolt {
/**
* Bolt BaseEncoder. Has public interfaces for writing Bolt encoded data.
* Supported types are: Null, Bool, Int, Double, String, List, Map, Vertex, Edge
* Supported types are: Null, Bool, Int, Double, String, List, Map, Vertex,
* Edge, Date, LocalDate, LocalDateTime, Duration.
*
* The purpose of this class is to stream bolt data into the given Buffer.
*
@ -89,6 +90,7 @@ class BaseEncoder {
WriteTypeSize(value.size(), MarkerString);
WriteRAW(value.c_str(), value.size());
}
void WriteList(const std::vector<Value> &value) {
WriteTypeSize(value.size(), MarkerList);
for (auto &x : value) WriteValue(x);
@ -168,6 +170,34 @@ class BaseEncoder {
for (auto &i : path.indices) WriteInt(i);
}
void WriteDate(const utils::Date &date) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::Date));
WriteInt(date.DaysSinceEpoch());
}
void WriteLocalTime(const utils::LocalTime &local_time) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::LocalTime));
WriteInt(local_time.NanosecondsSinceEpoch());
}
void WriteLocalDateTime(const utils::LocalDateTime &local_date_time) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
WriteRAW(utils::UnderlyingCast(Signature::LocalDateTime));
WriteInt(local_date_time.SecondsSinceEpoch());
WriteInt(local_date_time.SubSecondsAsNanoseconds());
}
void WriteDuration(const utils::Duration &duration) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct4));
WriteRAW(utils::UnderlyingCast(Signature::Duration));
WriteInt(duration.Months());
WriteInt(duration.SubMonthsAsDays());
WriteInt(duration.SubDaysAsSeconds());
WriteInt(duration.SubSecondsAsNanoseconds());
}
void WriteValue(const Value &value) {
switch (value.type()) {
case Value::Type::Null:
@ -203,6 +233,18 @@ class BaseEncoder {
case Value::Type::Path:
WritePath(value.ValuePath());
break;
case Value::Type::Date:
WriteDate(value.ValueDate());
break;
case Value::Type::LocalTime:
WriteLocalTime(value.ValueLocalTime());
break;
case Value::Type::LocalDateTime:
WriteLocalDateTime(value.ValueLocalDateTime());
break;
case Value::Type::Duration:
WriteDuration(value.ValueDuration());
break;
}
}

View File

@ -39,6 +39,10 @@ DEF_GETTER_BY_REF(Vertex, Vertex, vertex_v)
DEF_GETTER_BY_REF(Edge, Edge, edge_v)
DEF_GETTER_BY_REF(UnboundedEdge, UnboundedEdge, unbounded_edge_v)
DEF_GETTER_BY_REF(Path, Path, path_v)
DEF_GETTER_BY_REF(Date, utils::Date, date_v)
DEF_GETTER_BY_REF(LocalTime, utils::LocalTime, local_time_v)
DEF_GETTER_BY_REF(LocalDateTime, utils::LocalDateTime, local_date_time_v)
DEF_GETTER_BY_REF(Duration, utils::Duration, duration_v)
#undef DEF_GETTER_BY_REF
@ -76,6 +80,18 @@ Value::Value(const Value &other) : type_(other.type_) {
case Type::Path:
new (&path_v) Path(other.path_v);
return;
case Type::Date:
new (&date_v) utils::Date(other.date_v);
return;
case Type::LocalTime:
new (&local_time_v) utils::LocalTime(other.local_time_v);
return;
case Type::LocalDateTime:
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
return;
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
return;
}
}
@ -118,6 +134,18 @@ Value &Value::operator=(const Value &other) {
case Type::Path:
new (&path_v) Path(other.path_v);
return *this;
case Type::Date:
new (&date_v) utils::Date(other.date_v);
return *this;
case Type::LocalTime:
new (&local_time_v) utils::LocalTime(other.local_time_v);
return *this;
case Type::LocalDateTime:
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
return *this;
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
return *this;
}
}
return *this;
@ -157,6 +185,18 @@ Value::Value(Value &&other) noexcept : type_(other.type_) {
case Type::Path:
new (&path_v) Path(std::move(other.path_v));
break;
case Type::Date:
new (&date_v) utils::Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) utils::LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
}
// reset the type of other
@ -203,6 +243,18 @@ Value &Value::operator=(Value &&other) noexcept {
case Type::Path:
new (&path_v) Path(std::move(other.path_v));
break;
case Type::Date:
new (&date_v) utils::Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) utils::LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
}
// reset the type of other
@ -249,6 +301,18 @@ Value::~Value() {
case Type::Path:
path_v.~Path();
return;
case Type::Date:
date_v.~Date();
return;
case Type::LocalTime:
local_time_v.~LocalTime();
return;
case Type::LocalDateTime:
local_date_time_v.~LocalDateTime();
return;
case Type::Duration:
duration_v.~Duration();
return;
}
}
@ -341,6 +405,14 @@ std::ostream &operator<<(std::ostream &os, const Value &value) {
return os << value.ValueUnboundedEdge();
case Value::Type::Path:
return os << value.ValuePath();
case Value::Type::Date:
return os << value.ValueDate();
case Value::Type::LocalTime:
return os << value.ValueLocalTime();
case Value::Type::LocalDateTime:
return os << value.ValueLocalDateTime();
case Value::Type::Duration:
return os << value.ValueDuration();
}
}
@ -368,6 +440,14 @@ std::ostream &operator<<(std::ostream &os, const Value::Type type) {
return os << "unbounded_edge";
case Value::Type::Path:
return os << "path";
case Value::Type::Date:
return os << "date";
case Value::Type::LocalTime:
return os << "local_time";
case Value::Type::LocalDateTime:
return os << "local_date_time";
case Value::Type::Duration:
return os << "duration";
}
}
} // namespace communication::bolt

View File

@ -7,6 +7,7 @@
#include "utils/cast.hpp"
#include "utils/exceptions.hpp"
#include "utils/temporal.hpp"
namespace communication::bolt {
@ -120,7 +121,23 @@ class Value {
Value() : type_(Type::Null) {}
/** Types that can be stored in a Value. */
enum class Type : unsigned { Null, Bool, Int, Double, String, List, Map, Vertex, Edge, UnboundedEdge, Path };
enum class Type : unsigned {
Null,
Bool,
Int,
Double,
String,
List,
Map,
Vertex,
Edge,
UnboundedEdge,
Path,
Date,
LocalTime,
LocalDateTime,
Duration
};
// constructors for primitive types
Value(bool value) : type_(Type::Bool) { bool_v = value; }
@ -139,7 +156,12 @@ class Value {
Value(const Edge &value) : type_(Type::Edge) { new (&edge_v) Edge(value); }
Value(const UnboundedEdge &value) : type_(Type::UnboundedEdge) { new (&unbounded_edge_v) UnboundedEdge(value); }
Value(const Path &value) : type_(Type::Path) { new (&path_v) Path(value); }
Value(const utils::Date &date) : type_(Type::Date) { new (&date_v) utils::Date(date); }
Value(const utils::LocalTime &time) : type_(Type::LocalTime) { new (&local_time_v) utils::LocalTime(time); }
Value(const utils::LocalDateTime &date_time) : type_(Type::LocalDateTime) {
new (&local_date_time_v) utils::LocalDateTime(date_time);
}
Value(const utils::Duration &dur) : type_(Type::Duration) { new (&duration_v) utils::Duration(dur); }
// move constructors for non-primitive values
Value(std::string &&value) noexcept : type_(Type::String) { new (&string_v) std::string(std::move(value)); }
Value(std::vector<Value> &&value) noexcept : type_(Type::List) { new (&list_v) std::vector<Value>(std::move(value)); }
@ -183,7 +205,10 @@ class Value {
DECL_GETTER_BY_REFERENCE(Edge, Edge)
DECL_GETTER_BY_REFERENCE(UnboundedEdge, UnboundedEdge)
DECL_GETTER_BY_REFERENCE(Path, Path)
DECL_GETTER_BY_REFERENCE(Date, utils::Date)
DECL_GETTER_BY_REFERENCE(LocalTime, utils::LocalTime)
DECL_GETTER_BY_REFERENCE(LocalDateTime, utils::LocalDateTime)
DECL_GETTER_BY_REFERENCE(Duration, utils::Duration)
#undef DECL_GETTER_BY_REFERNCE
#define TYPE_CHECKER(type) \
@ -199,7 +224,10 @@ class Value {
TYPE_CHECKER(Edge)
TYPE_CHECKER(UnboundedEdge)
TYPE_CHECKER(Path)
TYPE_CHECKER(Date)
TYPE_CHECKER(LocalTime)
TYPE_CHECKER(LocalDateTime)
TYPE_CHECKER(Duration)
#undef TYPE_CHECKER
friend std::ostream &operator<<(std::ostream &os, const Value &value);
@ -219,9 +247,12 @@ class Value {
Edge edge_v;
UnboundedEdge unbounded_edge_v;
Path path_v;
utils::Date date_v;
utils::LocalTime local_time_v;
utils::LocalDateTime local_date_time_v;
utils::Duration duration_v;
};
};
/**
* An exception raised by the Value system.
*/

View File

@ -7,6 +7,7 @@
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/storage.hpp"
#include "storage/v2/vertex_accessor.hpp"
#include "utils/temporal.hpp"
using communication::bolt::Value;
@ -40,6 +41,14 @@ query::TypedValue ToTypedValue(const Value &value) {
case Value::Type::UnboundedEdge:
case Value::Type::Path:
throw communication::bolt::ValueException("Unsupported conversion from Value to TypedValue");
case Value::Type::Date:
return query::TypedValue(value.ValueDate());
case Value::Type::LocalTime:
return query::TypedValue(value.ValueLocalTime());
case Value::Type::LocalDateTime:
return query::TypedValue(value.ValueLocalDateTime());
case Value::Type::Duration:
return query::TypedValue(value.ValueDuration());
}
}
@ -100,12 +109,13 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
return Value(std::move(*maybe_path));
}
case query::TypedValue::Type::Date:
return Value(value.ValueDate());
case query::TypedValue::Type::LocalTime:
return Value(value.ValueLocalTime());
case query::TypedValue::Type::LocalDateTime:
return Value(value.ValueLocalDateTime());
case query::TypedValue::Type::Duration:
// TODO(antonio2368): Change this when Bolt value for temporal types
// are implemented
LOG_FATAL("Temporal types not yet supported");
return Value(value.ValueDuration());
}
}
@ -190,6 +200,18 @@ storage::PropertyValue ToPropertyValue(const Value &value) {
case Value::Type::UnboundedEdge:
case Value::Type::Path:
throw communication::bolt::ValueException("Unsupported conversion from Value to PropertyValue");
case Value::Type::Date:
return storage::PropertyValue(
storage::TemporalData(storage::TemporalType::Date, value.ValueDate().MicrosecondsSinceEpoch()));
case Value::Type::LocalTime:
return storage::PropertyValue(
storage::TemporalData(storage::TemporalType::LocalTime, value.ValueLocalTime().MicrosecondsSinceEpoch()));
case Value::Type::LocalDateTime:
return storage::PropertyValue(storage::TemporalData(storage::TemporalType::LocalDateTime,
value.ValueLocalDateTime().MicrosecondsSinceEpoch()));
case Value::Type::Duration:
return storage::PropertyValue(
storage::TemporalData(storage::TemporalType::Duration, value.ValueDuration().microseconds));
}
}
@ -224,7 +246,17 @@ Value ToBoltValue(const storage::PropertyValue &value) {
return Value(std::move(dv_map));
}
case storage::PropertyValue::Type::TemporalData:
LOG_FATAL("Unsupported type");
const auto &type = value.ValueTemporalData();
switch (type.type) {
case storage::TemporalType::Date:
return Value(utils::Date(type.microseconds));
case storage::TemporalType::LocalTime:
return Value(utils::LocalTime(type.microseconds));
case storage::TemporalType::LocalDateTime:
return Value(utils::LocalDateTime(type.microseconds));
case storage::TemporalType::Duration:
return Value(utils::Duration(type.microseconds));
}
}
}

View File

@ -9,20 +9,6 @@
namespace utils {
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; }
@ -48,12 +34,13 @@ std::optional<T> ParseNumber(const std::string_view string, const size_t size) {
} // 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);
namespace chrono = std::chrono;
const auto chrono_micros = chrono::microseconds(microseconds);
const auto s_days = chrono::sys_days(chrono::duration_cast<chrono::days>(chrono_micros));
const auto date = chrono::year_month_day(s_days);
years = static_cast<int>(date.year());
months = static_cast<unsigned>(date.month());
days = static_cast<unsigned>(date.day());
}
Date::Date(const DateParameters &date_parameters) {
@ -159,13 +146,12 @@ std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string
}
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();
namespace chrono = std::chrono;
return chrono::duration_cast<chrono::microseconds>(utils::DaysSinceEpoch(years, months, days)).count();
}
int64_t Date::DaysSinceEpoch() const { return utils::DaysSinceEpoch(years, months, days).count(); }
size_t DateHash::operator()(const Date &date) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, date.years);
@ -307,10 +293,14 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
LocalTime::LocalTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
MG_ASSERT(chrono_microseconds.count() >= 0, "Negative LocalTime specified in microseconds");
if (chrono_microseconds.count() < 0) {
throw utils::BasicException("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");
if (parsed_hours > 23) {
throw utils::BasicException("Invalid LocalTime specified in microseconds");
}
hours = parsed_hours;
minutes = GetAndSubtractDuration<std::chrono::minutes>(chrono_microseconds);
@ -334,11 +324,11 @@ LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
}
if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
throw utils::BasicException("Creating a LocalTime with invalid milliseconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.microseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
throw utils::BasicException("Creating a LocalTime with invalid microseconds parameter.");
}
hours = local_time_parameters.hours;
@ -348,11 +338,17 @@ LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
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();
std::chrono::microseconds LocalTime::SumLocalTimeParts() const {
namespace chrono = std::chrono;
return chrono::hours{hours} + chrono::minutes{minutes} + chrono::seconds{seconds} +
chrono::milliseconds{milliseconds} + chrono::microseconds{microseconds};
}
int64_t LocalTime::MicrosecondsSinceEpoch() const { return SumLocalTimeParts().count(); }
int64_t LocalTime::NanosecondsSinceEpoch() const {
namespace chrono = std::chrono;
return chrono::duration_cast<chrono::nanoseconds>(SumLocalTimeParts()).count();
}
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
@ -443,6 +439,23 @@ int64_t LocalDateTime::MicrosecondsSinceEpoch() const {
return date.MicrosecondsSinceEpoch() + local_time.MicrosecondsSinceEpoch();
}
int64_t LocalDateTime::SecondsSinceEpoch() const {
namespace chrono = std::chrono;
const auto to_sec = chrono::duration_cast<chrono::seconds>(DaysSinceEpoch(date.years, date.months, date.days));
const auto local_time_seconds =
chrono::hours(local_time.hours) + chrono::minutes(local_time.minutes) + chrono::seconds(local_time.seconds);
return (to_sec + local_time_seconds).count();
}
int64_t LocalDateTime::SubSecondsAsNanoseconds() const {
namespace chrono = std::chrono;
const auto milli_as_nanos = chrono::duration_cast<chrono::nanoseconds>(chrono::milliseconds(local_time.milliseconds));
const auto micros_as_nanos =
chrono::duration_cast<chrono::nanoseconds>(chrono::microseconds(local_time.microseconds));
return (milli_as_nanos + micros_as_nanos).count();
}
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
: date(date_parameters), local_time(local_time_parameters) {}
@ -643,15 +656,14 @@ DurationParameters ParseDurationParameters(std::string_view string) {
}
}
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
} // namespace
Duration::Duration(int64_t microseconds) { this->microseconds = microseconds; }
Duration::Duration(const DurationParameters &parameters) {
microseconds = (CastChronoDouble<std::chrono::years, std::chrono::microseconds>(parameters.years) +
@ -665,6 +677,35 @@ Duration::Duration(const DurationParameters &parameters) {
.count();
}
int64_t Duration::Months() const {
std::chrono::microseconds ms(microseconds);
return std::chrono::duration_cast<std::chrono::months>(ms).count();
}
int64_t Duration::SubMonthsAsDays() const {
namespace chrono = std::chrono;
const auto months = chrono::months(Months());
const auto micros = chrono::microseconds(microseconds);
return chrono::duration_cast<chrono::days>(micros - months).count();
}
int64_t Duration::SubDaysAsSeconds() const {
namespace chrono = std::chrono;
const auto months = chrono::months(Months());
const auto days = chrono::days(SubMonthsAsDays());
const auto micros = chrono::microseconds(microseconds);
return chrono::duration_cast<chrono::seconds>(micros - months - days).count();
}
int64_t Duration::SubSecondsAsNanoseconds() const {
namespace chrono = std::chrono;
const auto months = chrono::months(Months());
const auto days = chrono::days(SubMonthsAsDays());
const auto secs = chrono::seconds(SubDaysAsSeconds());
const auto micros = chrono::microseconds(microseconds);
return chrono::duration_cast<chrono::nanoseconds>(micros - months - days - secs).count();
}
Duration Duration::operator-() const {
Duration result{-microseconds};
return result;

View File

@ -1,12 +1,29 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <chrono>
#include <iostream>
#include "fmt/format.h"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
namespace utils {
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();
}
struct DateParameters {
int64_t years{0};
int64_t months{1};
@ -24,7 +41,13 @@ struct Date {
explicit Date(int64_t microseconds);
explicit Date(const DateParameters &date_parameters);
friend std::ostream &operator<<(std::ostream &os, const Date &date) {
return os << fmt::format("{:0>2}-{:0>2}-{:0>2}", date.years, static_cast<int>(date.months),
static_cast<int>(date.days));
}
int64_t MicrosecondsSinceEpoch() const;
int64_t DaysSinceEpoch() const;
auto operator<=>(const Date &) const = default;
@ -55,10 +78,23 @@ struct LocalTime {
explicit LocalTime(int64_t microseconds);
explicit LocalTime(const LocalTimeParameters &local_time_parameters);
std::chrono::microseconds SumLocalTimeParts() const;
// Epoch means the start of the day, i,e, midnight
int64_t MicrosecondsSinceEpoch() const;
int64_t NanosecondsSinceEpoch() const;
auto operator<=>(const LocalTime &) const = default;
friend std::ostream &operator<<(std::ostream &os, const LocalTime &lt) {
namespace chrono = std::chrono;
using milli = chrono::milliseconds;
using micro = chrono::microseconds;
const auto subseconds = milli(lt.milliseconds) + micro(lt.microseconds);
return os << fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast<int>(lt.hours), static_cast<int>(lt.minutes),
static_cast<int>(lt.seconds), subseconds.count());
}
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
@ -74,12 +110,21 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
struct LocalDateTime {
explicit LocalDateTime(int64_t microseconds);
explicit LocalDateTime(DateParameters date, const LocalTimeParameters &local_time);
explicit LocalDateTime(DateParameters date_parameters, const LocalTimeParameters &local_time_parameters);
LocalDateTime(const Date &dt, const LocalTime &lt) : date(dt), local_time(lt) {}
int64_t MicrosecondsSinceEpoch() const;
int64_t SecondsSinceEpoch() const; // seconds since epoch
int64_t SubSecondsAsNanoseconds() const;
auto operator<=>(const LocalDateTime &) const = default;
friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) {
os << ldt.date << 'T' << ldt.local_time;
return os;
}
Date date;
LocalTime local_time;
};
@ -107,6 +152,24 @@ struct Duration {
auto operator<=>(const Duration &) const = default;
int64_t Months() const;
int64_t SubMonthsAsDays() const;
int64_t SubDaysAsSeconds() const;
int64_t SubSecondsAsNanoseconds() const;
friend std::ostream &operator<<(std::ostream &os, const Duration &dur) {
// ISO 8601 extended format: P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].
namespace chrono = std::chrono;
auto micros = chrono::microseconds(dur.microseconds);
const auto y = GetAndSubtractDuration<chrono::years>(micros);
const auto mo = GetAndSubtractDuration<chrono::months>(micros);
const auto dd = GetAndSubtractDuration<chrono::days>(micros);
const auto h = GetAndSubtractDuration<chrono::hours>(micros);
const auto m = GetAndSubtractDuration<chrono::minutes>(micros);
const auto s = GetAndSubtractDuration<chrono::seconds>(micros);
return os << fmt::format("P{:0>4}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}.{:0>6}", y, mo, dd, h, m, s, micros.count());
}
Duration operator-() const;
int64_t microseconds;
@ -116,4 +179,10 @@ struct DurationHash {
size_t operator()(const Duration &duration) const;
};
constexpr std::chrono::days DaysSinceEpoch(uint16_t years, uint8_t months, uint8_t days) {
namespace chrono = std::chrono;
const auto ymd = chrono::year_month_day(chrono::year(years), chrono::month(months), chrono::day(days));
return chrono::sys_days{ymd}.time_since_epoch();
}
} // namespace utils

View File

@ -1,6 +1,7 @@
#include <bit>
#include "bolt_common.hpp"
#include "bolt_testdata.hpp"
#include "communication/bolt/v1/decoder/decoder.hpp"
using communication::bolt::Value;
@ -434,3 +435,258 @@ TEST_F(BoltDecoder, Edge) {
ASSERT_EQ(edge.type, std::string("a"));
ASSERT_EQ(edge.properties[std::string("a")].ValueInt(), 1);
}
// Temporal types testing starts here
template <typename T>
constexpr uint8_t Cast(T marker) {
return static_cast<uint8_t>(marker);
}
void AssertThatDatesAreEqual(const utils::Date &d1, const utils::Date &d2) {
ASSERT_EQ(d1.days, d2.days);
ASSERT_EQ(d1.months, d2.months);
ASSERT_EQ(d1.years, d2.years);
}
TEST_F(BoltDecoder, DateOld) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
const auto date = utils::Date({1970, 1, 1});
const auto days = date.DaysSinceEpoch();
ASSERT_EQ(days, 0);
// clang-format off
std::array<uint8_t, 3> data = {
Cast(Marker::TinyStruct1),
Cast(Sig::Date),
0x0 };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Date), true);
AssertThatDatesAreEqual(dv.ValueDate(), date);
}
TEST_F(BoltDecoder, DateRecent) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
const auto date = utils::Date({2021, 7, 20});
const auto days = date.DaysSinceEpoch();
ASSERT_EQ(days, 18828);
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
// clang-format off
std::array<uint8_t, 7> data = {
Cast(Marker::TinyStruct1),
Cast(Sig::Date),
Cast(Marker::Int32),
d_bytes[3],
d_bytes[2],
d_bytes[1],
d_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Date), true);
AssertThatDatesAreEqual(dv.ValueDate(), date);
}
TEST_F(BoltDecoder, DurationOneSec) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto value = Value(utils::Duration(1));
const auto &dur = value.ValueDuration();
const auto nanos = dur.SubSecondsAsNanoseconds();
ASSERT_EQ(dur.Months(), 0);
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
ASSERT_EQ(nanos, 1000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 8> data = {
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0x0,
0x0,
0x0,
Cast(Marker::Int16),
n_bytes[1], n_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
}
TEST_F(BoltDecoder, DurationMinusOneSec) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto value = Value(utils::Duration(-1));
const auto &dur = value.ValueDuration();
const auto nanos = dur.SubSecondsAsNanoseconds();
ASSERT_EQ(dur.Months(), 0);
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
ASSERT_EQ(nanos, -1000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 8> data = {
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0x0,
0x0,
0x0,
Cast(Marker::Int16),
n_bytes[1], n_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
}
TEST_F(BoltDecoder, ArbitraryDuration) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto value = Value(utils::Duration({1, 1, 1, 1, 1, 1, 1, 0}));
const auto &dur = value.ValueDuration();
ASSERT_EQ(dur.Months(), 13);
ASSERT_EQ(dur.SubMonthsAsDays(), 1);
const auto secs = dur.SubDaysAsSeconds();
ASSERT_EQ(secs, 3661);
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
const auto nanos = dur.SubSecondsAsNanoseconds();
ASSERT_EQ(nanos, 1000000);
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 12> data = {
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0xD,
0x1,
Cast(Marker::Int16),
sec_bytes[1],
sec_bytes[0],
Cast(Marker::Int32),
nano_bytes[3],
nano_bytes[2],
nano_bytes[1],
nano_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
}
void AssertThatLocalTimeIsEqual(utils::LocalTime t1, utils::LocalTime t2) {
ASSERT_EQ(t1.hours, t2.hours);
ASSERT_EQ(t1.minutes, t2.minutes);
ASSERT_EQ(t1.seconds, t2.seconds);
ASSERT_EQ(t1.microseconds, t2.microseconds);
ASSERT_EQ(t1.milliseconds, t2.milliseconds);
}
TEST_F(BoltDecoder, LocalTimeOneMicro) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto value = Value(utils::LocalTime(1));
const auto &local_time = value.ValueLocalTime();
const auto nanos = local_time.NanosecondsSinceEpoch();
ASSERT_EQ(nanos, 1000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 5> data = {
Cast(Marker::TinyStruct1),
Cast(Sig::LocalTime),
Cast(Marker::Int16),
n_bytes[1],
n_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalTime), true);
AssertThatLocalTimeIsEqual(dv.ValueLocalTime(), local_time);
}
TEST_F(BoltDecoder, LocalTimeOneThousandMicro) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto value = Value(utils::LocalTime(1000));
const auto &local_time = value.ValueLocalTime();
const auto nanos = local_time.NanosecondsSinceEpoch();
ASSERT_EQ(nanos, 1000000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 7> data = {
Cast(Marker::TinyStruct1),
Cast(Sig::LocalTime),
Cast(Marker::Int32),
n_bytes[3], n_bytes[2],
n_bytes[1], n_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalTime), true);
AssertThatLocalTimeIsEqual(dv.ValueLocalTime(), local_time);
}
TEST_F(BoltDecoder, LocalDateTime) {
TestDecoderBuffer buffer;
DecoderT decoder(buffer);
Value dv;
const auto local_time = utils::LocalTime(utils::LocalTimeParameters({0, 0, 30, 1, 0}));
const auto date = utils::Date(1);
const auto value = Value(utils::LocalDateTime(date, local_time));
const auto local_date_time = value.ValueLocalDateTime();
const auto secs = local_date_time.SecondsSinceEpoch();
ASSERT_EQ(secs, 30);
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
const auto nanos = local_date_time.SubSecondsAsNanoseconds();
ASSERT_EQ(nanos, 1000000);
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
std::array<uint8_t, 8> data = {
Cast(Marker::TinyStruct2),
Cast(Sig::LocalDateTime),
// Seconds
sec_bytes[0],
// Nanoseconds
Cast(Marker::Int32),
nano_bytes[3], nano_bytes[2],
nano_bytes[1], nano_bytes[0] };
// clang-format on
buffer.Clear();
buffer.Write(data.data(), data.size());
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalDateTime), true);
AssertThatDatesAreEqual(dv.ValueLocalDateTime().date, local_date_time.date);
AssertThatLocalTimeIsEqual(dv.ValueLocalDateTime().local_time, local_date_time.local_time);
}

View File

@ -1,10 +1,13 @@
#include <array>
#include <bit>
#include "bolt_common.hpp"
#include "bolt_testdata.hpp"
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/encoder/encoder.hpp"
#include "glue/communication.hpp"
#include "storage/v2/storage.hpp"
#include "utils/temporal.hpp"
using communication::bolt::Value;
/**
@ -240,10 +243,269 @@ TEST_F(BoltEncoder, BoltV1ExampleMessages) {
fvals.insert(std::make_pair(fk2, ftv2));
bolt_encoder.MessageFailure(fvals);
CheckOutput(output,
(const uint8_t *) "\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E",
(const uint8_t *)
"\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E",
71);
// ignored message
bolt_encoder.MessageIgnored();
CheckOutput(output, (const uint8_t *)"\xB0\x7E", 2);
}
// Temporal types testing starts here
template <typename T>
constexpr uint8_t Cast(T marker) {
return static_cast<uint8_t>(marker);
}
TEST_F(BoltEncoder, DateOld) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::Date({1970, 1, 1}));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &date = value.ValueDate();
const auto days = date.DaysSinceEpoch();
ASSERT_EQ(days, 0);
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize() in base_encoder.hpp).
// We reverse the order of d_bytes because after the encoding
// it has BigEndian orderring.
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 6> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct1),
Cast(Sig::Date),
d_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, DateRecent) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::Date({2021, 7, 20}));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &date = value.ValueDate();
const auto days = date.DaysSinceEpoch();
ASSERT_EQ(days, 18828);
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize() in base_encoder.hpp).
// We reverse the order of d_bytes because after the encoding
// it has BigEndian orderring.
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 8> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct1),
Cast(Sig::Date),
Cast(Marker::Int16),
d_bytes[1],
d_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, DurationOneSec) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::Duration(1));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &dur = value.ValueDuration();
ASSERT_EQ(dur.Months(), 0);
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
const auto nanos = dur.SubSecondsAsNanoseconds();
ASSERT_EQ(nanos, 1000);
const auto *d_bytes = std::bit_cast<const uint8_t *>(&nanos);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize in base_encoder.hpp).
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 11> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0x0,
0x0,
0x0,
Cast(Marker::Int16),
d_bytes[1],
d_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, DurationMinusOneSec) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::Duration(-1));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &dur = value.ValueDuration();
ASSERT_EQ(dur.Months(), 0);
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
const auto nanos = dur.SubSecondsAsNanoseconds();
const auto *d_bytes = std::bit_cast<const uint8_t *>(&nanos);
ASSERT_EQ(nanos, -1000);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize in base_encoder.hpp).
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 11> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0x0,
0x0,
0x0,
Cast(Marker::Int16),
d_bytes[1],
d_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, ArbitraryDuration) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::Duration({1, 1, 1, 1, 1, 1, 1, 0}));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &dur = value.ValueDuration();
ASSERT_EQ(dur.Months(), 13);
ASSERT_EQ(dur.SubMonthsAsDays(), 1);
const auto secs = dur.SubDaysAsSeconds();
ASSERT_EQ(secs, 3661);
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
const auto nanos = dur.SubSecondsAsNanoseconds();
ASSERT_EQ(nanos, 1000000);
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize in base_encoder.hpp).
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 15> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct4),
Cast(Sig::Duration),
0xD,
0x1,
Cast(Marker::Int16),
sec_bytes[1],
sec_bytes[0],
Cast(Marker::Int32),
nano_bytes[3],
nano_bytes[2],
nano_bytes[1],
nano_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, LocalTimeOneMicro) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::LocalTime(1));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &local_time = value.ValueLocalTime();
const auto nanos = local_time.NanosecondsSinceEpoch();
ASSERT_EQ(nanos, 1000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 8> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct1),
Cast(Sig::LocalTime),
Cast(Marker::Int16),
n_bytes[1], n_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, LocalTimeOneThousandMicro) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::LocalTime(1000));
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto &local_time = value.ValueLocalTime();
const auto nanos = local_time.NanosecondsSinceEpoch();
ASSERT_EQ(nanos, 1000000);
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 10> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct1),
Cast(Sig::LocalTime),
Cast(Marker::Int32),
n_bytes[3], n_bytes[2],
n_bytes[1], n_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}
TEST_F(BoltEncoder, LocalDateTime) {
output.clear();
std::vector<Value> vals;
const auto value = Value(utils::LocalDateTime(utils::Date(1), utils::LocalTime({0, 0, 30, 1, 0})));
const auto &local_date_time = value.ValueLocalDateTime();
vals.push_back(value);
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
const auto secs = local_date_time.SecondsSinceEpoch();
ASSERT_EQ(secs, 30);
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
const auto nanos = local_date_time.SubSecondsAsNanoseconds();
ASSERT_EQ(nanos, 1000000);
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
// WriteTypeSize in base_encoder.hpp).
// The rest of the expected results follow logically from LocalTime and Date test cases
using Marker = communication::bolt::Marker;
using Sig = communication::bolt::Signature;
// clang-format off
const auto expected = std::array<uint8_t, 11> {
Cast(Marker::TinyStruct1),
Cast(Sig::Record),
0x91,
Cast(Marker::TinyStruct2),
Cast(Sig::LocalDateTime),
// SuperSeconds
sec_bytes[0],
// SubSeconds
Cast(Marker::Int32),
nano_bytes[3], nano_bytes[2],
nano_bytes[1], nano_bytes[0] };
// clang-format on
CheckOutput(output, expected.data(), expected.size());
}

View File

@ -1,5 +1,7 @@
#include <chrono>
#include <iostream>
#include <optional>
#include <sstream>
#include <gtest/gtest.h>
@ -65,6 +67,7 @@ TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) {
check_microseconds(utils::DateParameters{2020, 11, 22});
check_microseconds(utils::DateParameters{1900, 2, 22});
check_microseconds(utils::DateParameters{0, 1, 1});
check_microseconds(utils::DateParameters{1994, 12, 7});
ASSERT_THROW(check_microseconds(utils::DateParameters{-10, 1, 1}), utils::BasicException);
@ -289,3 +292,54 @@ TEST(TemporalTest, DurationParsing) {
CheckDurationParameters(utils::ParseDurationParameters("P2020-11-22T19:20:32"),
utils::DurationParameters{2020, 11, 22, 19, 20, 32});
}
TEST(TemporalTest, PrintDate) {
const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1});
std::ostringstream stream;
stream << unix_epoch;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "1970-01-01");
}
TEST(TemporalTest, PrintLocalTime) {
const auto lt = utils::LocalTime({13, 2, 40, 100, 50});
std::ostringstream stream;
stream << lt;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "13:02:40.100050");
}
TEST(TemporalTest, PrintDuration) {
const auto dur = utils::Duration({1, 0, 0, 0, 0, 0, 0, 0});
std::ostringstream stream;
stream << dur;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "P0001-00-00T00:00:00.000000");
stream.str("");
stream.clear();
const auto complex_dur = utils::Duration({1, 5, 10, 3, 30, 33, 100, 50});
stream << complex_dur;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.100050");
/// stream.str("");
/// stream.clear();
/// TODO (kostasrim)
/// We do not support pasring negative Durations yet. We are the only ones we have access
/// to the constructor below.
/*
const auto negative_dur = utils::Duration({-1, 5, -10, -3, -30, -33});
stream << negative_dur;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.000000");
*/
}
TEST(TemporalTest, PrintLocalDateTime) {
const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1});
const auto lt = utils::LocalTime({13, 2, 40, 100, 50});
utils::LocalDateTime ldt(unix_epoch, lt);
std::ostringstream stream;
stream << ldt;
ASSERT_TRUE(stream);
ASSERT_EQ(stream.view(), "1970-01-01T13:02:40.100050");
}