diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index 8d4dcd465..7fae2c190 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -882,21 +882,36 @@ TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) } TypedValue ToString(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { - FType>("toString", args, nargs); + FType>("toString", args, nargs); const auto &arg = args[0]; if (arg.IsNull()) { return TypedValue(ctx.memory); - } else if (arg.IsString()) { + } + if (arg.IsString()) { return TypedValue(arg, ctx.memory); - } else if (arg.IsInt()) { + } + if (arg.IsInt()) { // TODO: This is making a pointless copy of std::string, we may want to // use a different conversion to string return TypedValue(std::to_string(arg.ValueInt()), ctx.memory); - } else if (arg.IsDouble()) { - return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory); - } else { - return TypedValue(arg.ValueBool() ? "true" : "false", ctx.memory); } + if (arg.IsDouble()) { + return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory); + } + if (arg.IsDate()) { + return TypedValue(arg.ValueDate().ToString(), ctx.memory); + } + if (arg.IsLocalTime()) { + return TypedValue(arg.ValueLocalTime().ToString(), ctx.memory); + } + if (arg.IsLocalDateTime()) { + return TypedValue(arg.ValueLocalDateTime().ToString(), ctx.memory); + } + if (arg.IsDuration()) { + return TypedValue(arg.ValueDuration().ToString(), ctx.memory); + } + + return TypedValue(arg.ValueBool() ? "true" : "false", ctx.memory); } TypedValue Timestamp(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { diff --git a/src/utils/temporal.cpp b/src/utils/temporal.cpp index b10507e0f..a84a26b0a 100644 --- a/src/utils/temporal.cpp +++ b/src/utils/temporal.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "utils/exceptions.hpp" @@ -175,6 +176,10 @@ int64_t Date::MicrosecondsSinceEpoch() const { int64_t Date::DaysSinceEpoch() const { return utils::DaysSinceEpoch(year, month, day).count(); } +std::string Date::ToString() const { + return fmt::format("{:0>4}-{:0>2}-{:0>2}", year, static_cast(month), static_cast(day)); +} + size_t DateHash::operator()(const Date &date) const { utils::HashCombine hasher; size_t result = hasher(0, date.year); @@ -377,6 +382,15 @@ int64_t LocalTime::NanosecondsSinceEpoch() const { return chrono::duration_cast(SumLocalTimeParts()).count(); } +std::string LocalTime::ToString() const { + using milli = std::chrono::milliseconds; + using micro = std::chrono::microseconds; + const auto subseconds = milli(millisecond) + micro(microsecond); + + return fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast(hour), static_cast(minute), + static_cast(second), subseconds.count()); +} + size_t LocalTimeHash::operator()(const LocalTime &local_time) const { utils::HashCombine hasher; size_t result = hasher(0, local_time.hour); @@ -486,6 +500,8 @@ int64_t LocalDateTime::SubSecondsAsNanoseconds() const { return (milli_as_nanos + micros_as_nanos).count(); } +std::string LocalDateTime::ToString() const { return date.ToString() + 'T' + local_time.ToString(); } + LocalDateTime::LocalDateTime(const DateParameters &date_parameters, const LocalTimeParameters &local_time_parameters) : date(date_parameters), local_time(local_time_parameters) {} @@ -699,6 +715,23 @@ int64_t Duration::SubSecondsAsNanoseconds() const { return chrono::duration_cast(micros - secs).count(); } +std::string Duration::ToString() const { + // Format [nD]T[nH]:[nM]:[nS]. + namespace chrono = std::chrono; + auto micros = chrono::microseconds(microseconds); + const auto dd = GetAndSubtractDuration(micros); + const auto h = GetAndSubtractDuration(micros); + const auto m = GetAndSubtractDuration(micros); + const auto s = GetAndSubtractDuration(micros); + + auto first_half = fmt::format("P{}DT{}H{}M", dd, h, m); + auto second_half = fmt::format("{}.{:0>6}S", s, std::abs(micros.count())); + if (s == 0 && micros.count() < 0) { + return first_half + '-' + second_half; + } + return first_half + second_half; +} + Duration Duration::operator-() const { if (microseconds == std::numeric_limits::min()) [[unlikely]] { throw temporal::InvalidArgumentException("Duration arithmetic overflows"); diff --git a/src/utils/temporal.hpp b/src/utils/temporal.hpp index 20a07bc4d..c3986e785 100644 --- a/src/utils/temporal.hpp +++ b/src/utils/temporal.hpp @@ -87,20 +87,9 @@ struct Duration { int64_t SubDaysAsNanoseconds() const; int64_t SubSecondsAsNanoseconds() const; - friend std::ostream &operator<<(std::ostream &os, const Duration &dur) { - // Format [nD]T[nH]:[nM]:[nS]. - namespace chrono = std::chrono; - auto micros = chrono::microseconds(dur.microseconds); - const auto dd = GetAndSubtractDuration(micros); - const auto h = GetAndSubtractDuration(micros); - const auto m = GetAndSubtractDuration(micros); - const auto s = GetAndSubtractDuration(micros); - os << fmt::format("P{}DT{}H{}M", dd, h, m); - if (s == 0 && micros.count() < 0) { - os << '-'; - } - return os << fmt::format("{}.{:0>6}S", s, std::abs(micros.count())); - } + std::string ToString() const; + + friend std::ostream &operator<<(std::ostream &os, const Duration &dur) { return os << dur.ToString(); } Duration operator-() const; @@ -155,13 +144,11 @@ 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.year, static_cast(date.month), - static_cast(date.day)); - } + friend std::ostream &operator<<(std::ostream &os, const Date &date) { return os << date.ToString(); } int64_t MicrosecondsSinceEpoch() const; int64_t DaysSinceEpoch() const; + std::string ToString() const; friend Date operator+(const Date &date, const Duration &dur) { namespace chrono = std::chrono; @@ -217,17 +204,11 @@ struct LocalTime { // Epoch means the start of the day, i,e, midnight int64_t MicrosecondsSinceEpoch() const; int64_t NanosecondsSinceEpoch() const; + std::string ToString() const; auto operator<=>(const LocalTime &) const = default; - friend std::ostream &operator<<(std::ostream &os, const LocalTime <) { - namespace chrono = std::chrono; - using milli = chrono::milliseconds; - using micro = chrono::microseconds; - const auto subseconds = milli(lt.millisecond) + micro(lt.microsecond); - return os << fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast(lt.hour), static_cast(lt.minute), - static_cast(lt.second), subseconds.count()); - } + friend std::ostream &operator<<(std::ostream &os, const LocalTime <) { return os << lt.ToString(); } friend LocalTime operator+(const LocalTime &local_time, const Duration &dur) { namespace chrono = std::chrono; @@ -279,13 +260,11 @@ struct LocalDateTime { int64_t MicrosecondsSinceEpoch() const; int64_t SecondsSinceEpoch() const; // seconds since epoch int64_t SubSecondsAsNanoseconds() const; + std::string ToString() 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; - } + friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) { return os << ldt.ToString(); } friend LocalDateTime operator+(const LocalDateTime &dt, const Duration &dur) { const auto local_date_time_as_duration = Duration(dt.MicrosecondsSinceEpoch()); diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index acf8828bb..13bccea6a 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -1747,6 +1747,26 @@ TEST_F(FunctionTest, ToStringBool) { EXPECT_EQ(EvaluateFunction("TOSTRING", false).ValueString(), "false"); } +TEST_F(FunctionTest, ToStringDate) { + const auto date = memgraph::utils::Date({1970, 1, 2}); + EXPECT_EQ(EvaluateFunction("TOSTRING", date).ValueString(), "1970-01-02"); +} + +TEST_F(FunctionTest, ToStringLocalTime) { + const auto lt = memgraph::utils::LocalTime({13, 2, 40, 100, 50}); + EXPECT_EQ(EvaluateFunction("TOSTRING", lt).ValueString(), "13:02:40.100050"); +} + +TEST_F(FunctionTest, ToStringLocalDateTime) { + const auto ldt = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59}); + EXPECT_EQ(EvaluateFunction("TOSTRING", ldt).ValueString(), "1970-01-02T23:02:59.000000"); +} + +TEST_F(FunctionTest, ToStringDuration) { + memgraph::utils::Duration duration{{.minute = 2, .second = 2, .microsecond = 33}}; + EXPECT_EQ(EvaluateFunction("TOSTRING", duration).ValueString(), "P0DT0H2M2.000033S"); +} + TEST_F(FunctionTest, ToStringExceptions) { EXPECT_THROW(EvaluateFunction("TOSTRING", 1, 2, 3), QueryRuntimeException); } TEST_F(FunctionTest, TimestampVoid) { diff --git a/tests/unit/utils_temporal.cpp b/tests/unit/utils_temporal.cpp index c6c9a2628..5736a2d50 100644 --- a/tests/unit/utils_temporal.cpp +++ b/tests/unit/utils_temporal.cpp @@ -609,3 +609,73 @@ TEST(TemporalTest, LocalDateTimeDelta) { ASSERT_EQ(two_years_after_unix_epoch - unix_epoch, memgraph::utils::Duration({.day = 761, .millisecond = 20, .microsecond = 34})); } + +TEST(TemporalTest, DateConvertsToString) { + const auto date1 = memgraph::utils::Date({1970, 1, 2}); + const std::string date1_expected_str = "1970-01-02"; + const auto date2 = memgraph::utils::Date({0000, 1, 1}); + const std::string date2_expected_str = "0000-01-01"; + const auto date3 = memgraph::utils::Date({2022, 7, 4}); + const std::string date3_expected_str = "2022-07-04"; + + ASSERT_EQ(date1_expected_str, date1.ToString()); + ASSERT_EQ(date2_expected_str, date2.ToString()); + ASSERT_EQ(date3_expected_str, date3.ToString()); +} + +TEST(TemporalTest, LocalTimeConvertsToString) { + const auto lt1 = memgraph::utils::LocalTime({13, 2, 40, 100, 50}); + const std::string lt1_expected_str = "13:02:40.100050"; + const auto lt2 = memgraph::utils::LocalTime({13, 2, 40}); + const std::string lt2_expected_str = "13:02:40.000000"; + const auto lt3 = memgraph::utils::LocalTime({0, 0, 0}); + const std::string lt3_expected_str = "00:00:00.000000"; + const auto lt4 = memgraph::utils::LocalTime({3, 2, 4, 6, 7}); + const std::string lt4_expected_str = "03:02:04.006007"; + + ASSERT_EQ(lt1_expected_str, lt1.ToString()); + ASSERT_EQ(lt2_expected_str, lt2.ToString()); + ASSERT_EQ(lt3_expected_str, lt3.ToString()); + ASSERT_EQ(lt4_expected_str, lt4.ToString()); +} + +TEST(TemporalTest, LocalDateTimeConvertsToString) { + const auto ldt1 = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59}); + const std::string ldt1_expected_str = "1970-01-02T23:02:59.000000"; + const auto ldt2 = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59, 456, 123}); + const std::string ldt2_expected_str = "1970-01-02T23:02:59.456123"; + const auto ldt3 = memgraph::utils::LocalDateTime({1997, 8, 24}, {16, 32, 9}); + const std::string ldt3_expected_str = "1997-08-24T16:32:09.000000"; + + ASSERT_EQ(ldt1_expected_str, ldt1.ToString()); + ASSERT_EQ(ldt2_expected_str, ldt2.ToString()); + ASSERT_EQ(ldt3_expected_str, ldt3.ToString()); +} + +TEST(TemporalTest, DurationConvertsToString) { + memgraph::utils::Duration duration1{{.minute = 2, .second = 2, .microsecond = 33}}; + const std::string duration1_expected_str = "P0DT0H2M2.000033S"; + memgraph::utils::Duration duration2{{.hour = 2.5, .minute = 2, .second = 2, .microsecond = 33}}; + const std::string duration2_expected_str = "P0DT2H32M2.000033S"; + memgraph::utils::Duration duration3{{.hour = 1.25, .minute = 2, .second = 2}}; + const std::string duration3_expected_str = "P0DT1H17M2.000000S"; + memgraph::utils::Duration duration4{{.day = 20, .hour = 1.25, .minute = 2, .second = 2}}; + const std::string duration4_expected_str = "P20DT1H17M2.000000S"; + memgraph::utils::Duration duration5{{.hour = -3, .minute = 2, .second = 2, .microsecond = -33}}; + const std::string duration5_expected_str = "P0DT-2H-57M-58.000033S"; + memgraph::utils::Duration duration6{{.day = -2, .hour = 3, .minute = 2, .second = 2, .microsecond = 33}}; + const std::string duration6_expected_str = "P-1DT-20H-57M-57.999967S"; + memgraph::utils::Duration duration7{{.day = 20, .hour = 72, .minute = 154, .second = 312}}; + const std::string duration7_expected_str = "P23DT2H39M12.000000S"; + memgraph::utils::Duration duration8{{.day = 1, .hour = 23, .minute = 59, .second = 60}}; + const std::string duration8_expected_str = "P2DT0H0M0.000000S"; + + ASSERT_EQ(duration1_expected_str, duration1.ToString()); + ASSERT_EQ(duration2_expected_str, duration2.ToString()); + ASSERT_EQ(duration3_expected_str, duration3.ToString()); + ASSERT_EQ(duration4_expected_str, duration4.ToString()); + ASSERT_EQ(duration5_expected_str, duration5.ToString()); + ASSERT_EQ(duration6_expected_str, duration6.ToString()); + ASSERT_EQ(duration7_expected_str, duration7.ToString()); + ASSERT_EQ(duration8_expected_str, duration8.ToString()); +}