diff --git a/src/codec/RowReaderV2.cpp b/src/codec/RowReaderV2.cpp index 7b904d47092..4c82b04fdc5 100644 --- a/src/codec/RowReaderV2.cpp +++ b/src/codec/RowReaderV2.cpp @@ -176,6 +176,17 @@ Value RowReaderV2::getValueByIndex(const int64_t index) const noexcept { dt.microsec = microsec; return dt; } + case PropertyType::DURATION: { + Duration d; + memcpy(reinterpret_cast(&d.seconds), &data_[offset], sizeof(int64_t)); + memcpy(reinterpret_cast(&d.microseconds), + &data_[offset + sizeof(int64_t)], + sizeof(int32_t)); + memcpy(reinterpret_cast(&d.months), + &data_[offset + sizeof(int64_t) + sizeof(int32_t)], + sizeof(int32_t)); + return d; + } case PropertyType::GEOGRAPHY: { int32_t strOffset; int32_t strLen; diff --git a/src/codec/RowWriterV2.cpp b/src/codec/RowWriterV2.cpp index 19378235fc5..d1c4cbf0613 100644 --- a/src/codec/RowWriterV2.cpp +++ b/src/codec/RowWriterV2.cpp @@ -130,6 +130,9 @@ RowWriterV2::RowWriterV2(RowReader& reader) : RowWriterV2(reader.getSchema()) { case Value::Type::GEOGRAPHY: set(i, v.moveGeography()); break; + case Value::Type::DURATION: + set(i, v.moveDuration()); + break; default: LOG(FATAL) << "Invalid data: " << v << ", type: " << v.typeName(); } @@ -209,6 +212,8 @@ WriteResult RowWriterV2::setValue(ssize_t index, const Value& val) noexcept { return write(index, val.getDateTime()); case Value::Type::GEOGRAPHY: return write(index, val.getGeography()); + case Value::Type::DURATION: + return write(index, val.getDuration()); default: return WriteResult::TYPE_MISMATCH; } @@ -762,6 +767,29 @@ WriteResult RowWriterV2::write(ssize_t index, const DateTime& v) noexcept { return WriteResult::SUCCEEDED; } +WriteResult RowWriterV2::write(ssize_t index, const Duration& v) noexcept { + auto field = schema_->field(index); + auto offset = headerLen_ + numNullBytes_ + field->offset(); + switch (field->type()) { + case PropertyType::DURATION: + memcpy(&buf_[offset], reinterpret_cast(&v.seconds), sizeof(int64_t)); + memcpy(&buf_[offset + sizeof(int64_t)], + reinterpret_cast(&v.microseconds), + sizeof(int32_t)); + memcpy(&buf_[offset + sizeof(int64_t) + sizeof(int32_t)], + reinterpret_cast(&v.months), + sizeof(int32_t)); + break; + default: + return WriteResult::TYPE_MISMATCH; + } + if (field->nullable()) { + clearNullBit(field->nullFlagPos()); + } + isSet_[index] = true; + return WriteResult::SUCCEEDED; +} + WriteResult RowWriterV2::write(ssize_t index, const Geography& v) noexcept { auto field = schema_->field(index); auto geoShape = field->geoShape(); @@ -815,6 +843,9 @@ WriteResult RowWriterV2::checkUnsetFields() noexcept { case Value::Type::GEOGRAPHY: r = write(i, defVal.getGeography()); break; + case Value::Type::DURATION: + r = write(i, defVal.getDuration()); + break; default: LOG(FATAL) << "Unsupported default value type: " << defVal.typeName() << ", default value: " << defVal diff --git a/src/codec/RowWriterV2.h b/src/codec/RowWriterV2.h index 151c0f9dc6e..30138e89c65 100644 --- a/src/codec/RowWriterV2.h +++ b/src/codec/RowWriterV2.h @@ -188,6 +188,7 @@ class RowWriterV2 { WriteResult write(ssize_t index, const Date& v) noexcept; WriteResult write(ssize_t index, const Time& v) noexcept; WriteResult write(ssize_t index, const DateTime& v) noexcept; + WriteResult write(ssize_t index, const Duration& v) noexcept; WriteResult write(ssize_t index, const Geography& v) noexcept; }; diff --git a/src/codec/test/RowWriterV2Test.cpp b/src/codec/test/RowWriterV2Test.cpp index c1b6be0eafd..5d15ae4e514 100644 --- a/src/codec/test/RowWriterV2Test.cpp +++ b/src/codec/test/RowWriterV2Test.cpp @@ -43,6 +43,7 @@ const Geography geogPolygon = Polygon( Coordinate(-102.9, 37.6), Coordinate(-96.8, 37.5), Coordinate(-100.1, 41.4)}}); +const Duration du = Duration(1, 2, 3); TEST(RowWriterV2, NoDefaultValue) { SchemaWriter schema(12 /*Schema version*/); @@ -68,6 +69,7 @@ TEST(RowWriterV2, NoDefaultValue) { schema.appendCol( "Col18", PropertyType::GEOGRAPHY, 0, false, nullptr, meta::cpp2::GeoShape::POLYGON); schema.appendCol("Col19", PropertyType::GEOGRAPHY, 0, true, nullptr, meta::cpp2::GeoShape::ANY); + schema.appendCol("Col20", PropertyType::DURATION); ASSERT_EQ(Value::Type::STRING, sVal.type()); ASSERT_EQ(Value::Type::INT, iVal.type()); @@ -92,6 +94,7 @@ TEST(RowWriterV2, NoDefaultValue) { EXPECT_EQ(WriteResult::SUCCEEDED, writer1.set(16, geogLineString)); EXPECT_EQ(WriteResult::SUCCEEDED, writer1.set(17, geogPolygon)); // Purposely skip the col19 + EXPECT_EQ(WriteResult::SUCCEEDED, writer1.set(19, du)); ASSERT_EQ(WriteResult::SUCCEEDED, writer1.finish()); RowWriterV2 writer2(&schema); @@ -114,6 +117,7 @@ TEST(RowWriterV2, NoDefaultValue) { EXPECT_EQ(WriteResult::SUCCEEDED, writer2.set("Col17", geogLineString)); EXPECT_EQ(WriteResult::SUCCEEDED, writer2.set("Col18", geogPolygon)); // Purposely skip the col19 + EXPECT_EQ(WriteResult::SUCCEEDED, writer2.set("Col20", du)); ASSERT_EQ(WriteResult::SUCCEEDED, writer2.finish()); std::string encoded1 = std::move(writer1).moveEncodedStr(); @@ -253,6 +257,13 @@ TEST(RowWriterV2, NoDefaultValue) { v2 = reader2->getValueByIndex(18); EXPECT_EQ(Value::Type::NULLVALUE, v1.type()); EXPECT_EQ(v1, v2); + + // Col20 + v1 = reader1->getValueByName("Col20"); + v2 = reader2->getValueByIndex(19); + EXPECT_EQ(Value::Type::DURATION, v1.type()); + EXPECT_EQ(du, v1.getDuration()); + EXPECT_EQ(v1, v2); } TEST(RowWriterV2, WithDefaultValue) { diff --git a/src/codec/test/SchemaWriter.cpp b/src/codec/test/SchemaWriter.cpp index a2649518a75..9731319c8fd 100644 --- a/src/codec/test/SchemaWriter.cpp +++ b/src/codec/test/SchemaWriter.cpp @@ -78,6 +78,9 @@ SchemaWriter& SchemaWriter::appendCol(folly::StringPiece name, case PropertyType::GEOGRAPHY: size = 2 * sizeof(int32_t); // as same as STRING break; + case PropertyType::DURATION: + size = sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t); + break; default: LOG(FATAL) << "Unknown column type"; } diff --git a/src/common/datatypes/CMakeLists.txt b/src/common/datatypes/CMakeLists.txt index b9b00e7ae58..61827f75791 100644 --- a/src/common/datatypes/CMakeLists.txt +++ b/src/common/datatypes/CMakeLists.txt @@ -14,6 +14,7 @@ nebula_add_library( List.cpp Set.cpp Geography.cpp + Duration.cpp ) nebula_add_subdirectory(test) diff --git a/src/common/datatypes/CommonCpp2Ops.h b/src/common/datatypes/CommonCpp2Ops.h index aee7d22abc4..bc773472d20 100644 --- a/src/common/datatypes/CommonCpp2Ops.h +++ b/src/common/datatypes/CommonCpp2Ops.h @@ -28,6 +28,7 @@ struct Point; struct LineString; struct Polygon; struct Geography; +struct Duration; } // namespace nebula namespace apache::thrift { @@ -52,6 +53,7 @@ SPECIALIZE_CPP2OPS(nebula::Point); SPECIALIZE_CPP2OPS(nebula::LineString); SPECIALIZE_CPP2OPS(nebula::Polygon); SPECIALIZE_CPP2OPS(nebula::Geography); +SPECIALIZE_CPP2OPS(nebula::Duration); } // namespace apache::thrift diff --git a/src/common/datatypes/Date.cpp b/src/common/datatypes/Date.cpp index 7c2fd9861a6..bc573962b35 100644 --- a/src/common/datatypes/Date.cpp +++ b/src/common/datatypes/Date.cpp @@ -20,6 +20,11 @@ static inline std::string decimal(const std::string& number) { const int64_t kDaysSoFar[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; const int64_t kLeapDaysSoFar[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; +int8_t dayOfMonth(int16_t year, int8_t month) { + return isLeapYear(year) ? kLeapDaysSoFar[month] - kLeapDaysSoFar[month - 1] + : kDaysSoFar[month] - kDaysSoFar[month - 1]; +} + Date::Date(uint64_t days) { fromInt(days); } int64_t Date::toInt() const { @@ -97,11 +102,108 @@ Date Date::operator-(int64_t days) const { return Date(daysSince - days); } +void Date::addDuration(const Duration& duration) { + int64_t tmp{0}, carry{0}; + tmp = month + duration.months; + if (std::abs(tmp) > 12) { + carry = tmp / 12; + month = tmp % 12; + } else { + month = tmp; + } + if (month <= 0) { + carry--; + month += 12; + } + year += carry; + + tmp = day + duration.days(); + if (tmp > 0) { + int8_t dom = dayOfMonth(year, month); + while (tmp > dom) { + tmp -= dom; + month += 1; + if (month > 12) { + year += 1; + month = 1; + } + dom = dayOfMonth(year, month); + } + } else { + int8_t dom = (month == 1 ? dayOfMonth(year - 1, 12) : dayOfMonth(year, month - 1)); + while (tmp <= 0) { + tmp += dom; + month -= 1; + if (month <= 0) { + year--; + month += 12; + } + dom = (month == 1 ? dayOfMonth(year - 1, 12) : dayOfMonth(year, month - 1)); + } + } + day = tmp; +} + +void Date::subDuration(const Duration& duration) { return addDuration(-duration); } + std::string Date::toString() const { // It's in current timezone already return folly::stringPrintf("%d-%02d-%02d", year, month, day); } +void Time::addDuration(const Duration& duration) { + int64_t tmp{0}, carry{0}; + tmp = microsec + duration.microsecondsInSecond(); + if (std::abs(tmp) >= 1000000) { + carry = tmp / 1000000; + microsec = tmp % 1000000; + } else { + microsec = tmp; + } + if (microsec < 0) { + carry--; + microsec += 1000000; + } + tmp = sec + duration.seconds + carry; + carry = 0; + if (std::abs(tmp) >= 60) { + carry = tmp / 60; + sec = tmp % 60; + } else { + sec = tmp; + } + if (sec < 0) { + carry--; + sec += 60; + } + tmp = minute + carry; + carry = 0; + if (std::abs(tmp) >= 60) { + carry = tmp / 60; + minute = tmp % 60; + } else { + minute = tmp; + } + if (minute < 0) { + carry--; + minute += 60; + } + tmp = hour + carry; + carry = 0; + if (std::abs(tmp) >= 24) { + carry = tmp / 24; + hour = tmp % 24; + } else { + hour = tmp; + } + if (hour < 0) { + carry--; + hour += 24; + } +} + +void Time::subDuration(const Duration& duration) { addDuration(-duration); } + std::string Time::toString() const { auto microsecStr = folly::stringPrintf("%.9f", static_cast(microsec) / 1000000.0); auto decimalPart = decimal(microsecStr); @@ -109,6 +211,117 @@ std::string Time::toString() const { return folly::stringPrintf("%02d:%02d:%02d%s", hour, minute, sec, decimalPart.c_str()); } +void DateTime::addDuration(const Duration& duration) { + // The origin fields of DateTime is unsigned, but there will be some negative intermediate results + // so I define some variable(field, tYear, tMonth) for this. + int64_t tmp{0}, carry{0}, field{0}; + tmp = month + duration.months; + if (std::abs(tmp) > 12) { + carry = tmp / 12; + /*month*/ field = tmp % 12; + } else { + /*month*/ field = tmp; + } + if (/*month*/ field <= 0) { + carry--; + /*month*/ field += 12; + } + year += carry; + carry = 0; + month = field; + field = 0; + + tmp = microsec + duration.microsecondsInSecond(); + if (std::abs(tmp) >= 1000000) { + carry = tmp / 1000000; + /*microsec*/ field = tmp % 1000000; + } else { + /*microsec*/ field = tmp; + } + if (/*microsec*/ field < 0) { + carry--; + /*microsec*/ field += 1000000; + } + microsec = field; + field = 0; + tmp = sec + duration.seconds + carry; + carry = 0; + if (std::abs(tmp) >= 60) { + carry = tmp / 60; + /*sec*/ field = tmp % 60; + } else { + /*sec*/ field = tmp; + } + if (/*sec*/ field < 0) { + carry--; + /*sec*/ field += 60; + } + sec = field; + field = 0; + tmp = minute + carry; + carry = 0; + if (std::abs(tmp) >= 60) { + carry = tmp / 60; + /*minute*/ field = tmp % 60; + } else { + /*minute*/ field = tmp; + } + if (/*minute*/ field < 0) { + carry--; + /*minute*/ field += 60; + } + minute = field; + field = 0; + tmp = hour + carry; + carry = 0; + if (std::abs(tmp) >= 24) { + carry = tmp / 24; + /*hour*/ field = tmp % 24; + } else { + /*hour*/ field = tmp; + } + if (/*hour*/ field < 0) { + carry--; + /*hour*/ field += 24; + } + hour = field; + field = 0; + + tmp = day + carry; + carry = 0; + if (tmp > 0) { + int8_t dom = dayOfMonth(year, month); + while (tmp > dom) { + tmp -= dom; + month += 1; + if (month > 12) { + year += 1; + month = 1; + } + dom = dayOfMonth(year, month); + } + } else { + int8_t dom = (month == 1 ? dayOfMonth(year - 1, 12) : dayOfMonth(year, month - 1)); + int64_t tMonth = month; + int64_t tYear = year; + while (tmp <= 0) { + tmp += dom; + /*month*/ tMonth -= 1; + if (/*month*/ tMonth <= 0) { + /*year*/ tYear--; + /*month*/ tMonth += 12; + } + dom = (/*month*/ tMonth == 1 ? dayOfMonth(/*year*/ tYear - 1, 12) + : dayOfMonth(/*year*/ tYear, /*month*/ tMonth - 1)); + } + month = tMonth; + year = tYear; + } + day = tmp; +} + +void DateTime::subDuration(const Duration& duration) { return addDuration(-duration); } + std::string DateTime::toString() const { auto microsecStr = folly::stringPrintf("%.9f", static_cast(microsec) / 1000000.0); auto decimalPart = decimal(microsecStr); diff --git a/src/common/datatypes/Date.h b/src/common/datatypes/Date.h index a7bd42e0c48..0e86018777b 100644 --- a/src/common/datatypes/Date.h +++ b/src/common/datatypes/Date.h @@ -10,6 +10,8 @@ #include +#include "common/datatypes/Duration.h" + namespace nebula { // In nebula only store UTC time, and the interpretation of time value based on @@ -18,6 +20,21 @@ namespace nebula { extern const int64_t kDaysSoFar[]; extern const int64_t kLeapDaysSoFar[]; +// https://en.wikipedia.org/wiki/Leap_year#Leap_day +static inline bool isLeapYear(int16_t year) { + if (year % 4 != 0) { + return false; + } else if (year % 100 != 0) { + return true; + } else if (year % 400 != 0) { + return false; + } else { + return true; + } +} + +int8_t dayOfMonth(int16_t year, int8_t month); + struct Date { int16_t year; // Any integer int8_t month; // 1 - 12 @@ -62,6 +79,9 @@ struct Date { Date operator+(int64_t days) const; Date operator-(int64_t days) const; + void addDuration(const Duration& duration); + void subDuration(const Duration& duration); + std::string toString() const; folly::dynamic toJson() const { return toString(); } @@ -71,6 +91,18 @@ struct Date { void fromInt(int64_t days); }; +inline Date operator+(const Date& l, const Duration& r) { + Date d = l; + d.addDuration(r); + return d; +} + +inline Date operator-(const Date& l, const Duration& r) { + Date d = l; + d.subDuration(r); + return d; +} + inline std::ostream& operator<<(std::ostream& os, const Date& d) { os << d.toString(); return os; @@ -114,6 +146,9 @@ struct Time { return false; } + void addDuration(const Duration& duration); + void subDuration(const Duration& duration); + std::string toString() const; // 'Z' representing UTC timezone folly::dynamic toJson() const { return toString() + "Z"; } @@ -124,6 +159,18 @@ inline std::ostream& operator<<(std::ostream& os, const Time& d) { return os; } +inline Time operator+(const Time& l, const Duration& r) { + Time t = l; + t.addDuration(r); + return t; +} + +inline Time operator-(const Time& l, const Duration& r) { + Time t = l; + t.subDuration(r); + return t; +} + struct DateTime { #if defined(__GNUC__) #pragma GCC diagnostic push @@ -219,6 +266,9 @@ struct DateTime { return false; } + void addDuration(const Duration& duration); + void subDuration(const Duration& duration); + std::string toString() const; // 'Z' representing UTC timezone folly::dynamic toJson() const { return toString() + "Z"; } @@ -229,6 +279,18 @@ inline std::ostream& operator<<(std::ostream& os, const DateTime& d) { return os; } +inline DateTime operator+(const DateTime& l, const Duration& r) { + DateTime dt = l; + dt.addDuration(r); + return dt; +} + +inline DateTime operator-(const DateTime& l, const Duration& r) { + DateTime dt = l; + dt.subDuration(r); + return dt; +} + } // namespace nebula namespace std { diff --git a/src/common/datatypes/Duration.cpp b/src/common/datatypes/Duration.cpp new file mode 100644 index 00000000000..1369ce8274b --- /dev/null +++ b/src/common/datatypes/Duration.cpp @@ -0,0 +1,15 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "common/datatypes/Duration.h" + +#include +#include + +#include + +namespace nebula {} // namespace nebula + +namespace std {} // namespace std diff --git a/src/common/datatypes/Duration.h b/src/common/datatypes/Duration.h new file mode 100644 index 00000000000..b918b84b1aa --- /dev/null +++ b/src/common/datatypes/Duration.h @@ -0,0 +1,136 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#pragma once + +#include + +#include + +#include "common/time/Constants.h" + +namespace nebula { + +// Duration equals to months + seconds + microseconds +// The base between months and days is not fixed, so we store years and months +// separately. +struct Duration { + // day + hours + minutes + seconds + microseconds + int64_t seconds; + int32_t microseconds; + // years + months + int32_t months; + + Duration() : seconds(0), microseconds(0), months(0) {} + Duration(int32_t m, int64_t s, int32_t us) : seconds(s), microseconds(us), months(m) {} + + int64_t years() const { return months / 12; } + + int64_t monthsInYear() const { return months % 12; } + + int64_t days() const { return seconds / time::kSecondsOfDay; } + + int64_t hours() const { return seconds % time::kSecondsOfDay / time::kSecondsOfHour; } + + int64_t minutes() const { return seconds % time::kSecondsOfHour / time::kSecondsOfMinute; } + + int64_t secondsInMinute() const { return seconds % time::kSecondsOfMinute; } + + int64_t microsecondsInSecond() const { return microseconds; } + + Duration operator-() const { return Duration(-months, -seconds, -microseconds); } + + Duration operator+(const Duration& rhs) const { + return Duration(months + rhs.months, seconds + rhs.seconds, microseconds + rhs.microseconds); + } + + Duration operator-(const Duration& rhs) const { + return Duration(months - rhs.months, seconds - rhs.seconds, microseconds - rhs.microseconds); + } + + Duration& addYears(int32_t y) { + months += y * 12; + return *this; + } + + Duration& addQuarters(int32_t q) { + months += q * 3; + return *this; + } + + Duration& addMonths(int32_t m) { + months += m; + return *this; + } + + Duration& addWeeks(int32_t w) { + seconds += (w * 7 * time::kSecondsOfDay); + return *this; + } + + Duration& addDays(int64_t d) { + seconds += d * time::kSecondsOfDay; + return *this; + } + + Duration& addHours(int64_t h) { + seconds += h * time::kSecondsOfHour; + return *this; + } + + Duration& addMinutes(int64_t minutes) { + seconds += minutes * time::kSecondsOfMinute; + return *this; + } + + Duration& addSeconds(int64_t s) { + seconds += s; + return *this; + } + + Duration& addMilliseconds(int64_t ms) { + seconds += ms / 1000; + microseconds += ((ms % 1000) * 1000); + return *this; + } + + Duration& addMicroseconds(int32_t us) { + microseconds += us; + return *this; + } + + // can't compare + bool operator<(const Duration& rhs) const = delete; + + bool operator==(const Duration& rhs) const { + return months == rhs.months && seconds == rhs.seconds && microseconds == rhs.microseconds; + } + + std::string toString() const { + std::stringstream ss; + ss << "P" << months << "M" << days() << "D" + << "T" << seconds << "S"; + return ss.str(); + } + + folly::dynamic toJson() const { return toString(); } +}; + +} // namespace nebula + +namespace std { + +// Inject a customized hash function +template <> +struct hash { + std::size_t operator()(const nebula::Duration& d) const noexcept { + size_t hv = folly::hash::fnv64_buf(reinterpret_cast(&d.months), sizeof(d.months)); + hv = folly::hash::fnv64_buf(reinterpret_cast(d.seconds), sizeof(d.seconds), hv); + return folly::hash::fnv64_buf( + reinterpret_cast(d.microseconds), sizeof(d.microseconds), hv); + } +}; + +} // namespace std diff --git a/src/common/datatypes/DurationOps-inl.h b/src/common/datatypes/DurationOps-inl.h new file mode 100644 index 00000000000..79a6418e575 --- /dev/null +++ b/src/common/datatypes/DurationOps-inl.h @@ -0,0 +1,201 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#pragma once + +#include +#include +#include + +#include "common/base/Base.h" +#include "common/datatypes/CommonCpp2Ops.h" +#include "common/datatypes/Duration.h" + +namespace apache { +namespace thrift { + +/************************************** + * + * Ops for class Duration + * + *************************************/ +namespace detail { + +template <> +struct TccStructTraits { + static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, + MAYBE_UNUSED int16_t& fid, + MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { + if (_fname == "seconds") { + fid = 1; + _ftype = apache::thrift::protocol::T_I64; + } else if (_fname == "microseconds") { + fid = 2; + _ftype = apache::thrift::protocol::T_I32; + } else if (_fname == "months") { + fid = 3; + _ftype = apache::thrift::protocol::T_I32; + } + } +}; + +} // namespace detail + +inline constexpr protocol::TType Cpp2Ops::thriftType() { + return apache::thrift::protocol::T_STRUCT; +} + +template +uint32_t Cpp2Ops::write(Protocol* proto, nebula::Duration const* obj) { + uint32_t xfer = 0; + xfer += proto->writeStructBegin("Duration"); + + xfer += proto->writeFieldBegin("seconds", apache::thrift::protocol::T_I64, 1); + xfer += detail::pm::protocol_methods::write(*proto, obj->seconds); + xfer += proto->writeFieldEnd(); + + xfer += proto->writeFieldBegin("microseconds", apache::thrift::protocol::T_I32, 2); + xfer += + detail::pm::protocol_methods::write(*proto, obj->microseconds); + xfer += proto->writeFieldEnd(); + + xfer += proto->writeFieldBegin("months", apache::thrift::protocol::T_I32, 3); + xfer += detail::pm::protocol_methods::write(*proto, obj->months); + xfer += proto->writeFieldEnd(); + + xfer += proto->writeFieldStop(); + xfer += proto->writeStructEnd(); + + return xfer; +} + +template +void Cpp2Ops::read(Protocol* proto, nebula::Duration* obj) { + detail::ProtocolReaderStructReadState readState; + + readState.readStructBegin(proto); + + using apache::thrift::protocol::TProtocolException; + + if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, protocol::T_I64))) { + goto _loop; + } + +_readField_seconds : { + detail::pm::protocol_methods::read(*proto, obj->seconds); +} + + if (UNLIKELY(!readState.advanceToNextField(proto, 1, 2, protocol::T_I32))) { + goto _loop; + } + +_readField_microseconds : { + detail::pm::protocol_methods::read(*proto, obj->microseconds); +} + + if (UNLIKELY(!readState.advanceToNextField(proto, 2, 3, protocol::T_I32))) { + goto _loop; + } + +_readField_months : { + detail::pm::protocol_methods::read(*proto, obj->months); +} + + if (UNLIKELY(!readState.advanceToNextField(proto, 3, 0, protocol::T_STOP))) { + goto _loop; + } + +_end: + readState.readStructEnd(proto); + + return; + +_loop: + if (readState.fieldType == apache::thrift::protocol::T_STOP) { + goto _end; + } + if (proto->kUsesFieldNames()) { + detail::TccStructTraits::translateFieldName( + readState.fieldName(), readState.fieldId, readState.fieldType); + } + + switch (readState.fieldId) { + case 1: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_I64)) { + goto _readField_seconds; + } else { + goto _skip; + } + } + case 2: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_I32)) { + goto _readField_microseconds; + } else { + goto _skip; + } + } + case 3: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_I32)) { + goto _readField_months; + } else { + goto _skip; + } + } + default: { +_skip: + proto->skip(readState.fieldType); + readState.readFieldEnd(proto); + readState.readFieldBeginNoInline(proto); + goto _loop; + } + } +} + +template +uint32_t Cpp2Ops::serializedSize(Protocol const* proto, + nebula::Duration const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Duration"); + + xfer += proto->serializedFieldSize("seconds", apache::thrift::protocol::T_I64, 1); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->seconds); + + xfer += proto->serializedFieldSize("microseconds", apache::thrift::protocol::T_I32, 2); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->microseconds); + + xfer += proto->serializedFieldSize("months", apache::thrift::protocol::T_I32, 3); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->months); + + xfer += proto->serializedSizeStop(); + return xfer; +} + +template +uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, + nebula::Duration const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Duration"); + + xfer += proto->serializedFieldSize("seconds", apache::thrift::protocol::T_I64, 1); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->seconds); + + xfer += proto->serializedFieldSize("microseconds", apache::thrift::protocol::T_I32, 2); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->microseconds); + + xfer += proto->serializedFieldSize("months", apache::thrift::protocol::T_I32, 3); + xfer += detail::pm::protocol_methods::serializedSize( + *proto, obj->months); + + xfer += proto->serializedSizeStop(); + return xfer; +} + +} // namespace thrift +} // namespace apache diff --git a/src/common/datatypes/Value.cpp b/src/common/datatypes/Value.cpp index 7eeb69a3f4e..5ca1e2efafe 100644 --- a/src/common/datatypes/Value.cpp +++ b/src/common/datatypes/Value.cpp @@ -73,6 +73,9 @@ std::size_t hash::operator()(const nebula::Value& v) const noexce case nebula::Value::Type::SET: { return hash()(v.getSet()); } + case nebula::Value::Type::DURATION: { + return hash()(v.getDuration()); + } case nebula::Value::Type::DATASET: { LOG(FATAL) << "Hash for DATASET has not been implemented"; } @@ -171,6 +174,10 @@ Value::Value(Value&& rhs) noexcept : type_(Value::Type::__EMPTY__) { setGG(std::move(rhs.value_.ggVal)); break; } + case Type::DURATION: { + setDU(std::move(rhs.value_.duVal)); + break; + } default: { assert(false); break; @@ -251,6 +258,10 @@ Value::Value(const Value& rhs) : type_(Value::Type::__EMPTY__) { setGG(rhs.value_.ggVal); break; } + case Type::DURATION: { + setDU(rhs.value_.duVal); + break; + } default: { assert(false); break; @@ -348,6 +359,10 @@ Value::Value(const Geography& v) { setGG(std::make_unique(v)); } Value::Value(Geography&& v) { setGG(std::make_unique(std::move(v))); } +Value::Value(const Duration& v) { setDU(std::make_unique(v)); } + +Value::Value(Duration&& v) { setDU(std::make_unique(std::move(v))); } + const std::string& Value::typeName() const { static const std::unordered_map typeNames = { {Type::__EMPTY__, "__EMPTY__"}, @@ -367,6 +382,7 @@ const std::string& Value::typeName() const { {Type::SET, "set"}, {Type::DATASET, "dataset"}, {Type::GEOGRAPHY, "geography"}, + {Type::DURATION, "duration"}, }; static const std::unordered_map nullTypes = { @@ -630,6 +646,21 @@ void Value::setGeography(std::unique_ptr&& v) { setGG(std::move(v)); } +void Value::setDuration(const Duration& v) { + clear(); + setDU(v); +} + +void Value::setDuration(Duration&& v) { + clear(); + setDU(std::move(v)); +} + +void Value::setDuration(std::unique_ptr&& v) { + clear(); + setDU(std::move(v)); +} + const NullType& Value::getNull() const { CHECK_EQ(type_, Type::NULLVALUE); return value_.nVal; @@ -750,6 +781,16 @@ const Geography* Value::getGeographyPtr() const { return value_.ggVal.get(); } +const Duration& Value::getDuration() const { + CHECK_EQ(type_, Type::DURATION); + return *value_.duVal; +} + +const Duration* Value::getDurationPtr() const { + CHECK_EQ(type_, Type::DURATION); + return value_.duVal.get(); +} + NullType& Value::mutableNull() { CHECK_EQ(type_, Type::NULLVALUE); return value_.nVal; @@ -830,6 +871,11 @@ Geography& Value::mutableGeography() { return *(value_.ggVal); } +Duration& Value::mutableDuration() { + CHECK_EQ(type_, Type::DURATION); + return *value_.duVal; +} + NullType Value::moveNull() { CHECK_EQ(type_, Type::NULLVALUE); NullType v = std::move(value_.nVal); @@ -942,6 +988,13 @@ Geography Value::moveGeography() { return v; } +Duration Value::moveDuration() { + CHECK_EQ(type_, Type::DURATION); + Duration v = std::move(*value_.duVal); + clear(); + return v; +} + void Value::clear() { switch (type_) { case Type::__EMPTY__: { @@ -1011,6 +1064,10 @@ void Value::clear() { destruct(value_.ggVal); break; } + case Type::DURATION: { + destruct(value_.duVal); + break; + } } type_ = Type::__EMPTY__; } @@ -1088,6 +1145,10 @@ Value& Value::operator=(Value&& rhs) noexcept { setGG(std::move(rhs.value_.ggVal)); break; } + case Type::DURATION: { + setDU(std::move(rhs.value_.duVal)); + break; + } default: { assert(false); break; @@ -1170,6 +1231,10 @@ Value& Value::operator=(const Value& rhs) { setGG(rhs.value_.ggVal); break; } + case Type::DURATION: { + setDU(rhs.value_.duVal); + break; + } default: { assert(false); break; @@ -1429,6 +1494,26 @@ void Value::setGG(Geography&& v) { new (std::addressof(value_.ggVal)) std::unique_ptr(new Geography(std::move(v))); } +void Value::setDU(const std::unique_ptr& v) { + type_ = Type::DURATION; + new (std::addressof(value_.duVal)) std::unique_ptr(new Duration(*v)); +} + +void Value::setDU(std::unique_ptr&& v) { + type_ = Type::DURATION; + new (std::addressof(value_.duVal)) std::unique_ptr(std::move(v)); +} + +void Value::setDU(const Duration& v) { + type_ = Type::DURATION; + new (std::addressof(value_.duVal)) std::unique_ptr(new Duration(v)); +} + +void Value::setDU(Duration&& v) { + type_ = Type::DURATION; + new (std::addressof(value_.duVal)) std::unique_ptr(new Duration(std::move(v))); +} + // Convert Nebula::Value to a value compatible with Json standard // DATE, TIME, DATETIME will be converted to strings in UTC // VERTEX, EDGES, PATH will be converted to objects @@ -1491,6 +1576,9 @@ folly::dynamic Value::toJson() const { } case Value::Type::GEOGRAPHY: { return getGeography().toJson(); + } + case Value::Type::DURATION: { + return getDuration().toJson(); } // no default so the compiler will warning when lack } @@ -1521,6 +1609,7 @@ folly::dynamic Value::getMetaData() const { case Value::Type::MAP: { return getMap().getMetaData(); } + case Value::Type::DURATION: case Value::Type::DATE: case Value::Type::TIME: case Value::Type::DATETIME: { @@ -1612,6 +1701,9 @@ std::string Value::toString() const { } case Value::Type::GEOGRAPHY: { return getGeography().toString(); + } + case Value::Type::DURATION: { + return getDuration().toString(); } // no default so the compiler will warning when lack } @@ -1807,6 +1899,11 @@ Value Value::lessThan(const Value& v) const { case Value::Type::GEOGRAPHY: { return getGeography() < v.getGeography(); } + case Value::Type::DURATION: { + // Duration can't compare, + // e.g. What is the result of `duration('P1M') < duration('P30D')`? + return kNullBadType; + } case Value::Type::NULLVALUE: case Value::Type::__EMPTY__: { return kNullBadType; @@ -1897,6 +1994,9 @@ Value Value::equal(const Value& v) const { case Value::Type::GEOGRAPHY: { return getGeography() == v.getGeography(); } + case Value::Type::DURATION: { + return getDuration() == v.getDuration(); + } case Value::Type::NULLVALUE: case Value::Type::__EMPTY__: { return false; @@ -1982,6 +2082,10 @@ std::ostream& operator<<(std::ostream& os, const Value::Type& type) { os << "GEOGRAPHY"; break; } + case Value::Type::DURATION: { + os << "DURATION"; + break; + } default: { os << "__UNKNOWN__"; break; @@ -2114,6 +2218,9 @@ Value operator+(const Value& lhs, const Value& rhs) { ret.values.insert(ret.values.begin(), lhs); return ret; } + case Value::Type::DURATION: { + return lhs.getDate() + rhs.getDuration(); + } default: { return Value::kNullBadType; } @@ -2129,6 +2236,9 @@ Value operator+(const Value& lhs, const Value& rhs) { ret.values.insert(ret.values.begin(), lhs); return ret; } + case Value::Type::DURATION: { + return lhs.getTime() + rhs.getDuration(); + } default: { return Value::kNullBadType; } @@ -2144,6 +2254,9 @@ Value operator+(const Value& lhs, const Value& rhs) { ret.values.insert(ret.values.begin(), lhs); return ret; } + case Value::Type::DURATION: { + return lhs.getDateTime() + rhs.getDuration(); + } default: { return Value::kNullBadType; } @@ -2164,6 +2277,7 @@ Value operator+(const Value& lhs, const Value& rhs) { case Value::Type::DATE: case Value::Type::TIME: case Value::Type::DATETIME: + case Value::Type::DURATION: case Value::Type::VERTEX: case Value::Type::EDGE: case Value::Type::PATH: @@ -2248,6 +2362,16 @@ Value operator+(const Value& lhs, const Value& rhs) { } } } + case Value::Type::DURATION: { + switch (rhs.type()) { + case Value::Type::DURATION: { + return lhs.getDuration() + rhs.getDuration(); + } + default: { + return Value::kNullBadType; + } + } + } default: { return Value::kNullBadType; } @@ -2303,6 +2427,39 @@ Value operator-(const Value& lhs, const Value& rhs) { case Value::Type::DATE: { return lhs.getDate().toInt() - rhs.getDate().toInt(); } + case Value::Type::DURATION: { + return lhs.getDate() - rhs.getDuration(); + } + default: { + return Value::kNullBadType; + } + } + } + case Value::Type::TIME: { + switch (rhs.type()) { + case Value::Type::DURATION: { + return lhs.getTime() - rhs.getDuration(); + } + default: { + return Value::kNullBadType; + } + } + } + case Value::Type::DATETIME: { + switch (rhs.type()) { + case Value::Type::DURATION: { + return lhs.getDateTime() - rhs.getDuration(); + } + default: { + return Value::kNullBadType; + } + } + } + case Value::Type::DURATION: { + switch (rhs.type()) { + case Value::Type::DURATION: { + return lhs.getDuration() - rhs.getDuration(); + } default: { return Value::kNullBadType; } @@ -2509,6 +2666,10 @@ Value operator-(const Value& rhs) { auto val = -rhs.getFloat(); return val; } + case Value::Type::DURATION: { + auto val = -rhs.getDuration(); + return val; + } default: { return Value::kNullBadType; } @@ -2601,6 +2762,10 @@ bool operator<(const Value& lhs, const Value& rhs) { case Value::Type::GEOGRAPHY: { return lhs.getGeography() < rhs.getGeography(); } + case Value::Type::DURATION: { + DLOG(FATAL) << "Duration is not comparable."; + return false; + } case Value::Type::NULLVALUE: case Value::Type::__EMPTY__: { return false; @@ -2688,6 +2853,9 @@ bool operator==(const Value& lhs, const Value& rhs) { case Value::Type::GEOGRAPHY: { return lhs.getGeography() == rhs.getGeography(); } + case Value::Type::DURATION: { + return lhs.getDuration() == rhs.getDuration(); + } case Value::Type::NULLVALUE: case Value::Type::__EMPTY__: { return false; diff --git a/src/common/datatypes/Value.h b/src/common/datatypes/Value.h index 9bfccdae9f5..a3e5429af26 100644 --- a/src/common/datatypes/Value.h +++ b/src/common/datatypes/Value.h @@ -11,6 +11,7 @@ #include #include "common/datatypes/Date.h" +#include "common/datatypes/Duration.h" #include "common/thrift/ThriftTypes.h" namespace apache { @@ -77,6 +78,7 @@ struct Value { SET = 1UL << 13, DATASET = 1UL << 14, GEOGRAPHY = 1UL << 15, + DURATION = 1UL << 16, NULLVALUE = 1UL << 63, }; @@ -130,6 +132,8 @@ struct Value { Value(DataSet&& v); // NOLINT Value(const Geography& v); // NOLINT Value(Geography&& v); // NOLINT + Value(const Duration& v); // NOLINT + Value(Duration&& v); // NOLINT ~Value() { clear(); } Type type() const noexcept { return type_; } @@ -163,6 +167,7 @@ struct Value { bool isSet() const { return type_ == Type::SET; } bool isDataSet() const { return type_ == Type::DATASET; } bool isGeography() const { return type_ == Type::GEOGRAPHY; } + bool isDuration() const { return type_ == Type::DURATION; } void clear(); @@ -218,6 +223,9 @@ struct Value { void setGeography(const Geography& v); void setGeography(Geography&& v); void setGeography(std::unique_ptr&& v); + void setDuration(const Duration& v); + void setDuration(Duration&& v); + void setDuration(std::unique_ptr&& v); const NullType& getNull() const; const bool& getBool() const; @@ -243,6 +251,8 @@ struct Value { const DataSet* getDataSetPtr() const; const Geography& getGeography() const; const Geography* getGeographyPtr() const; + const Duration& getDuration() const; + const Duration* getDurationPtr() const; NullType moveNull(); bool moveBool(); @@ -260,6 +270,7 @@ struct Value { Set moveSet(); DataSet moveDataSet(); Geography moveGeography(); + Duration moveDuration(); NullType& mutableNull(); bool& mutableBool(); @@ -277,6 +288,7 @@ struct Value { Set& mutableSet(); DataSet& mutableDataSet(); Geography& mutableGeography(); + Duration& mutableDuration(); static const Value& null() noexcept { return kNullValue; } @@ -313,6 +325,7 @@ struct Value { std::unique_ptr uVal; std::unique_ptr gVal; std::unique_ptr ggVal; + std::unique_ptr duVal; Storage() {} ~Storage() {} @@ -389,6 +402,11 @@ struct Value { void setGG(std::unique_ptr&& v); void setGG(const Geography& v); void setGG(Geography&& v); + // Duration value + void setDU(const std::unique_ptr& v); + void setDU(std::unique_ptr&& v); + void setDU(const Duration& v); + void setDU(Duration&& v); }; static_assert(sizeof(Value) == 16UL, "The size of Value should be 16UL"); diff --git a/src/common/datatypes/ValueOps-inl.h b/src/common/datatypes/ValueOps-inl.h index 0fa5729be13..53db26fa22c 100644 --- a/src/common/datatypes/ValueOps-inl.h +++ b/src/common/datatypes/ValueOps-inl.h @@ -18,6 +18,7 @@ #include "common/datatypes/CommonCpp2Ops.h" #include "common/datatypes/DataSetOps-inl.h" #include "common/datatypes/DateOps-inl.h" +#include "common/datatypes/DurationOps-inl.h" #include "common/datatypes/EdgeOps-inl.h" #include "common/datatypes/GeographyOps-inl.h" #include "common/datatypes/ListOps-inl.h" @@ -85,6 +86,9 @@ struct TccStructTraits { } else if (_fname == "ggVal") { fid = 16; _ftype = apache::thrift::protocol::T_STRUCT; + } else if (_fname == "duVal") { + fid = 17; + _ftype = apache::thrift::protocol::T_STRUCT; } } }; @@ -247,6 +251,18 @@ uint32_t Cpp2Ops::write(Protocol* proto, nebula::Value const* obj xfer += proto->writeFieldEnd(); break; } + case nebula::Value::Type::DURATION: { + xfer += proto->writeFieldBegin("duVal", protocol::T_STRUCT, 17); + if (obj->getDurationPtr()) { + xfer += Cpp2Ops::write(proto, obj->getDurationPtr()); + } else { + xfer += proto->writeStructBegin("Duration"); + xfer += proto->writeStructEnd(); + xfer += proto->writeFieldStop(); + } + xfer += proto->writeFieldEnd(); + break; + } case nebula::Value::Type::__EMPTY__: { break; } @@ -438,6 +454,17 @@ void Cpp2Ops::read(Protocol* proto, nebula::Value* obj) { } break; } + case 17: { + if (readState.fieldType == apache::thrift::protocol::T_STRUCT) { + obj->setDuration(nebula::Duration()); + auto ptr = std::make_unique(); + Cpp2Ops::read(proto, ptr.get()); + obj->setDuration(std::move(ptr)); + } else { + proto->skip(readState.fieldType); + } + break; + } default: { proto->skip(readState.fieldType); break; @@ -582,6 +609,16 @@ uint32_t Cpp2Ops::serializedSize(Protocol const* proto, nebula::V } break; } + case nebula::Value::Type::DURATION: { + xfer += proto->serializedFieldSize("duVal", protocol::T_STRUCT, 17); + if (obj->getDurationPtr()) { + xfer += Cpp2Ops::serializedSize(proto, obj->getDurationPtr()); + } else { + xfer += proto->serializedStructSize("Duration"); + xfer += proto->serializedSizeStop(); + } + break; + } case nebula::Value::Type::__EMPTY__: { break; } @@ -720,6 +757,16 @@ uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, nebula: } break; } + case nebula::Value::Type::DURATION: { + xfer += proto->serializedFieldSize("duVal", protocol::T_STRUCT, 17); + if (obj->getDurationPtr()) { + xfer += Cpp2Ops::serializedSizeZC(proto, obj->getDurationPtr()); + } else { + xfer += proto->serializedStructSize("Duration"); + xfer += proto->serializedSizeStop(); + } + break; + } case nebula::Value::Type::__EMPTY__: { break; } diff --git a/src/common/datatypes/test/ValueTest.cpp b/src/common/datatypes/test/ValueTest.cpp index b27253e935d..00f89ed8d99 100644 --- a/src/common/datatypes/test/ValueTest.cpp +++ b/src/common/datatypes/test/ValueTest.cpp @@ -135,6 +135,184 @@ TEST(Value, Arithmetics) { EXPECT_EQ(Value::Type::STRING, v.type()); EXPECT_EQ(std::string("-30.142857142857142Allen Wilson"), v.getStr()); } + { + // Duration + Value lhs = Duration(-1, -2, -3); + Value rhs = Duration(-1, -2, -3); + auto result = lhs + rhs; + EXPECT_EQ(result, Value(Duration(-2, -4, -6))); + } + { + // Duration + Value lhs = Duration(0, 0, 0); + Value rhs = Duration(-1, -2, -3); + auto result = lhs + rhs; + EXPECT_EQ(result, Value(Duration(-1, -2, -3))); + } + { + // Duration + Value lhs = Duration(1, 2, 3); + Value rhs = Duration(-1, -2, -3); + auto result = lhs + rhs; + EXPECT_EQ(result, Value(Duration(0, 0, 0))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration(); + EXPECT_EQ(lhs, lhs + rhs); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addYears(3); + EXPECT_EQ(lhs + rhs, Value(Date(2023, 1, 1))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addMonths(3); + EXPECT_EQ(lhs + rhs, Value(Date(2020, 4, 1))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addDays(31); + EXPECT_EQ(lhs + rhs, Value(Date(2020, 2, 1))); + } + { + // Date leap year + Value lhs = Date(2020, 2, 1); + Value rhs = Duration().addDays(28); + EXPECT_EQ(lhs + rhs, Value(Date(2020, 2, 29))); + } + { + // Date common year + Value lhs = Date(2021, 2, 1); + Value rhs = Duration().addDays(28); + EXPECT_EQ(lhs + rhs, Value(Date(2021, 3, 1))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addHours(24); + EXPECT_EQ(lhs + rhs, Value(Date(2020, 1, 2))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addHours(23); + EXPECT_EQ(lhs + rhs, Value(Date(2020, 1, 1))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addHours(23); + EXPECT_EQ(lhs + rhs, Value(Time(2, 30, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addHours(3); + EXPECT_EQ(lhs + rhs, Value(Time(6, 30, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addHours(3).addMinutes(30); + EXPECT_EQ(lhs + rhs, Value(Time(7, 0, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(100); + EXPECT_EQ(lhs + rhs, Value(Time(3, 30, 30, 200))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(999900); + EXPECT_EQ(lhs + rhs, Value(Time(3, 30, 31, 0))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration(); + EXPECT_EQ(lhs, lhs + rhs); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addYears(3); + EXPECT_EQ(lhs + rhs, Value(DateTime(2023, 1, 1, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMonths(3); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 4, 1, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(31); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 2, 1, 3, 30, 30, 100))); + } + { + // DateTime leap year + Value lhs = DateTime(2020, 2, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(28); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 2, 29, 3, 30, 30, 100))); + } + { + // DateTime common year + Value lhs = DateTime(2021, 2, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(28); + EXPECT_EQ(lhs + rhs, Value(DateTime(2021, 3, 1, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(24); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 2, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(20); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 1, 23, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(23); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 2, 2, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(3); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 1, 6, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(3).addMinutes(30); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 1, 7, 0, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(100); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 1, 3, 30, 30, 200))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(999900); + EXPECT_EQ(lhs + rhs, Value(DateTime(2020, 1, 1, 3, 30, 31, 0))); + } + // - { Value v = vInt1 - vInt2; @@ -162,6 +340,201 @@ TEST(Value, Arithmetics) { EXPECT_EQ(Value::Type::DATE, v.type()); EXPECT_EQ(Date(2019, 12, 30), v.getDate()); } + { + // Duration + Value lhs = Duration(1, 2, 3); + Value rhs = Duration(); + auto result = lhs - rhs; + EXPECT_EQ(result, Value(Duration(1, 2, 3))); + } + { + // Duration + Value lhs = Duration(1, 2, 3); + Value rhs = Duration(-1, -2, -3); + auto result = lhs - rhs; + EXPECT_EQ(result, Value(Duration(2, 4, 6))); + } + { + // Duration + Value lhs = Duration(-1, -2, -3); + Value rhs = Duration(-1, -2, -3); + auto result = lhs - rhs; + EXPECT_EQ(result, Value(Duration(0, 0, 0))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addYears(3); + EXPECT_EQ(lhs - rhs, Value(Date(2017, 1, 1))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addMonths(3); + EXPECT_EQ(lhs - rhs, Value(Date(2019, 10, 1))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(Date(2019, 12, 29))); + } + { + // Date leap year + Value lhs = Date(2020, 3, 1); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(Date(2020, 2, 27))); + } + { + // Date common year + Value lhs = Date(2021, 3, 1); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(Date(2021, 2, 26))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addHours(24); + EXPECT_EQ(lhs - rhs, Value(Date(2019, 12, 31))); + } + { + // Date + Value lhs = Date(2020, 1, 1); + Value rhs = Duration().addHours(23); + EXPECT_EQ(lhs - rhs, Value(Date(2020, 1, 1))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addHours(3); + EXPECT_EQ(lhs - rhs, Value(Time(0, 30, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addHours(4); + EXPECT_EQ(lhs - rhs, Value(Time(23, 30, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMinutes(30); + EXPECT_EQ(lhs - rhs, Value(Time(3, 0, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMinutes(40); + EXPECT_EQ(lhs - rhs, Value(Time(2, 50, 30, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addSeconds(30); + EXPECT_EQ(lhs - rhs, Value(Time(3, 30, 0, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addSeconds(40); + EXPECT_EQ(lhs - rhs, Value(Time(3, 29, 50, 100))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(100); + EXPECT_EQ(lhs - rhs, Value(Time(3, 30, 30, 0))); + } + { + // Time + Value lhs = Time(3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(200); + EXPECT_EQ(lhs - rhs, Value(Time(3, 30, 29, 999900))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addYears(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2017, 1, 1, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMonths(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2019, 10, 1, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2019, 12, 29, 3, 30, 30, 100))); + } + { + // DateTime leap year + Value lhs = DateTime(2020, 3, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 2, 27, 3, 30, 30, 100))); + } + { + // DateTime common year + Value lhs = DateTime(2021, 3, 1, 3, 30, 30, 100); + Value rhs = Duration().addDays(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2021, 2, 26, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(24); + EXPECT_EQ(lhs - rhs, Value(DateTime(2019, 12, 31, 3, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(3); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 0, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addHours(4); + EXPECT_EQ(lhs - rhs, Value(DateTime(2019, 12, 31, 23, 30, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMinutes(30); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 3, 0, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMinutes(40); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 2, 50, 30, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addSeconds(30); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 3, 30, 0, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addSeconds(40); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 3, 29, 50, 100))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(100); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 3, 30, 30, 0))); + } + { + // DateTime + Value lhs = DateTime(2020, 1, 1, 3, 30, 30, 100); + Value rhs = Duration().addMicroseconds(200); + EXPECT_EQ(lhs - rhs, Value(DateTime(2020, 1, 1, 3, 30, 29, 999900))); + } // * { @@ -192,6 +565,13 @@ TEST(Value, Arithmetics) { EXPECT_EQ(Value::Type::FLOAT, v.type()); EXPECT_EQ((vFloat1.getFloat() * vFloat2.getFloat()), v.getFloat()); } + { + // Duration + Value lhs = Value(Duration(1, 2, 3)); + Value rhs = Value(Duration()); + auto result = lhs * rhs; + EXPECT_TRUE(result.isNull()); + } // / { @@ -220,6 +600,13 @@ TEST(Value, Arithmetics) { v = vInt1 / vZero; EXPECT_EQ(Value::Type::NULLVALUE, v.type()); } + { + // Duration + Value lhs = Value(Duration(1, 2, 3)); + Value rhs = Value(Duration()); + auto result = lhs / rhs; + EXPECT_TRUE(result.isNull()); + } // % { @@ -244,6 +631,13 @@ TEST(Value, Arithmetics) { v = vInt1 % vZero; EXPECT_EQ(Value::Type::NULLVALUE, v.type()); } + { + // Duration + Value lhs = Duration(1, 2, 3); + Value rhs = Duration(); + auto result = lhs % rhs; + EXPECT_TRUE(result.isNull()); + } // unary -,! { @@ -267,6 +661,16 @@ TEST(Value, Arithmetics) { EXPECT_EQ(Value::Type::BOOL, v.type()); EXPECT_DOUBLE_EQ(false, v.getBool()); } + { + // Duration + Value v = Duration(1, 2, 3); + EXPECT_EQ(Duration(-1, -2, -3), -v); + } + { + // Duration + Value v = Duration(-1, -2, -3); + EXPECT_EQ(Duration(1, 2, 3), -v); + } } TEST(Value, Comparison) { @@ -572,6 +976,19 @@ TEST(Value, Comparison) { EXPECT_EQ(false, v.getBool()); // TODO(jie) Add more geo test } + // Duration + { + Value lhs{Duration(1, 2, 4)}; + Value rhs{Duration(1, 2, 3)}; + EXPECT_TRUE(lhs.lessThan(rhs).isNull()); + EXPECT_FALSE(lhs.equal(rhs).getBool()); + } + { + Value lhs{Duration(1, 2, 3)}; + Value rhs{Duration(1, 2, 3)}; + EXPECT_TRUE(lhs.lessThan(rhs).isNull()); + EXPECT_TRUE(lhs.equal(rhs).getBool()); + } } TEST(Value, Logical) { @@ -895,6 +1312,11 @@ TEST(Value, Bit) { v = vNull & vEmpty; EXPECT_TRUE(v.isNull()); } + // Duration + { + Value v = Date() & Duration(); + EXPECT_TRUE(v.isNull()); + } { Value v = vInt1 | vInt2; @@ -950,6 +1372,11 @@ TEST(Value, Bit) { v = vNull | vEmpty; EXPECT_TRUE(v.isNull()); } + // Duration + { + Value v = Date() | Duration(); + EXPECT_TRUE(v.isNull()); + } { Value v = vInt1 ^ vInt2; @@ -1005,6 +1432,11 @@ TEST(Value, Bit) { v = vNull ^ vEmpty; EXPECT_TRUE(v.isNull()); } + // Duration + { + Value v = Date() ^ Duration(); + EXPECT_TRUE(v.isNull()); + } } TEST(Value, typeName) { @@ -1023,6 +1455,7 @@ TEST(Value, typeName) { EXPECT_EQ("set", Value(Set()).typeName()); EXPECT_EQ("dataset", Value(DataSet()).typeName()); EXPECT_EQ("geography", Value(Geography()).typeName()); + EXPECT_EQ("duration", Value(Duration()).typeName()); EXPECT_EQ("__NULL__", Value::kNullValue.typeName()); EXPECT_EQ("NaN", Value::kNullNaN.typeName()); EXPECT_EQ("BAD_DATA", Value::kNullBadData.typeName()); @@ -1131,6 +1564,10 @@ TEST(Value, DecodeEncode) { Coordinate(0, 1)}, std::vector{ Coordinate(2, 4), Coordinate(5, 6), Coordinate(3, 8), Coordinate(2, 4)}}))), + // Duration + Value(Duration()), + Value(Duration(1, 2, 3)), + Value(Duration(-1, -2, -3)), }; for (const auto& val : values) { std::string buf; @@ -1181,6 +1618,11 @@ TEST(Value, Ctor) { std::vector{ Coordinate(2, 4), Coordinate(5, 6), Coordinate(3, 8), Coordinate(2, 4)}}))}; EXPECT_TRUE(vGeogPolygon.isGeography()); + + // Duration + Value vD{Duration()}; + Value vD2{Duration(1, 2, 3)}; + // Disabled // Lead to compile error // Value v(nullptr); diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index 4bf3ac2cd2d..2735b9b1d41 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -414,6 +414,9 @@ std::unordered_map> FunctionManager::typ Value::Type::LIST), }}, {"is_edge", {TypeSignature({Value::Type::EDGE}, Value::Type::BOOL)}}, + {"duration", + {TypeSignature({Value::Type::STRING}, Value::Type::DURATION), + TypeSignature({Value::Type::MAP}, Value::Type::DURATION)}}, }; // static @@ -2679,6 +2682,32 @@ FunctionManager::FunctionManager() { attr.isPure_ = true; attr.body_ = [](const auto &args) -> Value { return args[0].get().isEdge(); }; } + { + auto &attr = functions_["duration"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + const auto &arg = args[0].get(); + switch (arg.type()) { + case Value::Type::MAP: { + auto result = time::TimeUtils::durationFromMap(arg.getMap()); + if (result.ok()) { + return result.value(); + } else { + return Value::kNullBadData; + } + } + case Value::Type::STRING: { + // TODO + return Value::kNullBadType; + } + default: { + return Value::kNullBadType; + } + } + }; + } } // NOLINT // static diff --git a/src/common/meta/NebulaSchemaProvider.cpp b/src/common/meta/NebulaSchemaProvider.cpp index cb2a5a8a787..976e8498a9b 100644 --- a/src/common/meta/NebulaSchemaProvider.cpp +++ b/src/common/meta/NebulaSchemaProvider.cpp @@ -165,6 +165,8 @@ std::size_t NebulaSchemaProvider::fieldSize(PropertyType type, std::size_t fixed sizeof(int32_t); // microsec case PropertyType::GEOGRAPHY: return 8; // as same as STRING + case PropertyType::DURATION: + return sizeof(int64_t) + sizeof(int32_t) + sizeof(int32_t); case PropertyType::UNKNOWN: break; } diff --git a/src/common/time/Constants.h b/src/common/time/Constants.h new file mode 100644 index 00000000000..c6fde825fbf --- /dev/null +++ b/src/common/time/Constants.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#pragma once + +namespace nebula { +namespace time { + +static constexpr int kDayOfLeapYear = 366; +static constexpr int kDayOfCommonYear = 365; + +static constexpr int64_t kSecondsOfMinute = 60; +static constexpr int64_t kSecondsOfHour = 60 * kSecondsOfMinute; +static constexpr int64_t kSecondsOfDay = 24 * kSecondsOfHour; + +} // namespace time +} // namespace nebula diff --git a/src/common/time/TimeConversion.h b/src/common/time/TimeConversion.h index aeaaab5de01..8b3a315af4d 100644 --- a/src/common/time/TimeConversion.h +++ b/src/common/time/TimeConversion.h @@ -9,6 +9,7 @@ #include #include "common/datatypes/Date.h" +#include "common/time/Constants.h" namespace nebula { namespace time { @@ -81,28 +82,8 @@ class TimeConversion { return t; } - // https://en.wikipedia.org/wiki/Leap_year#Leap_day - static bool isLeapYear(int16_t year) { - if (year % 4 != 0) { - return false; - } else if (year % 100 != 0) { - return true; - } else if (year % 400 != 0) { - return false; - } else { - return true; - } - } - static const DateTime kEpoch; - static constexpr int kDayOfLeapYear = 366; - static constexpr int kDayOfCommonYear = 365; - - static constexpr int64_t kSecondsOfMinute = 60; - static constexpr int64_t kSecondsOfHour = 60 * kSecondsOfMinute; - static constexpr int64_t kSecondsOfDay = 24 * kSecondsOfHour; - private: // The result of a right-shift of a signed negative number is // implementation-dependent (UB. see diff --git a/src/common/time/TimeUtils.cpp b/src/common/time/TimeUtils.cpp index 5733fa72be1..66af4c82c8c 100644 --- a/src/common/time/TimeUtils.cpp +++ b/src/common/time/TimeUtils.cpp @@ -171,6 +171,39 @@ StatusOr TimeUtils::toTimestamp(const Value &val) { return timestamp; } +/*static*/ StatusOr TimeUtils::durationFromMap(const Map &m) { + Duration d; + for (const auto &kv : m.kvs) { + if (!kv.second.isInt()) { + return Status::Error("Invalid value type."); + } + if (kv.first == "years") { + d.addYears(kv.second.getInt()); + } else if (kv.first == "quarters") { + d.addQuarters(kv.second.getInt()); + } else if (kv.first == "months") { + d.addMonths(kv.second.getInt()); + } else if (kv.first == "weeks") { + d.addWeeks(kv.second.getInt()); + } else if (kv.first == "days") { + d.addDays(kv.second.getInt()); + } else if (kv.first == "hours") { + d.addHours(kv.second.getInt()); + } else if (kv.first == "minutes") { + d.addMinutes(kv.second.getInt()); + } else if (kv.first == "seconds") { + d.addSeconds(kv.second.getInt()); + } else if (kv.first == "milliseconds") { + d.addMilliseconds(kv.second.getInt()); + } else if (kv.first == "microseconds") { + d.addMicroseconds(kv.second.getInt()); + } else { + return Status::Error("Unkown field %s.", kv.first.c_str()); + } + } + return d; +} + /*static*/ StatusOr TimeUtils::parseDateTime(const std::string &str) { auto p = DatetimeReader::makeDateTimeReader(); auto result = p.readDatetime(str); diff --git a/src/common/time/TimeUtils.h b/src/common/time/TimeUtils.h index 0a9355b3ef1..c8c655ee11d 100644 --- a/src/common/time/TimeUtils.h +++ b/src/common/time/TimeUtils.h @@ -16,6 +16,7 @@ #include "common/base/Status.h" #include "common/base/StatusOr.h" #include "common/datatypes/Date.h" +#include "common/datatypes/Duration.h" #include "common/datatypes/Map.h" #include "common/fs/FileUtils.h" #include "common/time/TimeConversion.h" @@ -39,7 +40,7 @@ class TimeUtils { typename D, typename = std::enable_if_t::value || std::is_same::value>> static Status validateDate(const D &date) { - const int64_t *p = TimeConversion::isLeapYear(date.year) ? kLeapDaysSoFar : kDaysSoFar; + const int64_t *p = isLeapYear(date.year) ? kLeapDaysSoFar : kDaysSoFar; if ((p[date.month] - p[date.month - 1]) < date.day) { return Status::Error("`%s' is not a valid date.", date.toString().c_str()); } @@ -195,6 +196,8 @@ class TimeUtils { static StatusOr toTimestamp(const Value &val); + static StatusOr durationFromMap(const Map &m); + private: struct UnixTime { int64_t seconds{0}; diff --git a/src/common/utils/IndexKeyUtils.h b/src/common/utils/IndexKeyUtils.h index c8bdd4fb967..c2d8e7d1276 100644 --- a/src/common/utils/IndexKeyUtils.h +++ b/src/common/utils/IndexKeyUtils.h @@ -51,6 +51,8 @@ class IndexKeyUtils final { return Value::Type::DATETIME; case PropertyType::GEOGRAPHY: return Value::Type::GEOGRAPHY; + case PropertyType::DURATION: + return Value::Type::DURATION; case PropertyType::UNKNOWN: return Value::Type::__EMPTY__; } diff --git a/src/graph/util/SchemaUtil.cpp b/src/graph/util/SchemaUtil.cpp index 8dbf5eb593b..13e0ed7e896 100644 --- a/src/graph/util/SchemaUtil.cpp +++ b/src/graph/util/SchemaUtil.cpp @@ -302,6 +302,8 @@ Value::Type SchemaUtil::propTypeToValueType(nebula::cpp2::PropertyType propType) return Value::Type::DATETIME; case nebula::cpp2::PropertyType::GEOGRAPHY: return Value::Type::GEOGRAPHY; + case nebula::cpp2::PropertyType::DURATION: + return Value::Type::DURATION; case nebula::cpp2::PropertyType::UNKNOWN: return Value::Type::__EMPTY__; } diff --git a/src/interface/common.thrift b/src/interface/common.thrift index 8fa6ed9a49e..02349e48502 100644 --- a/src/interface/common.thrift +++ b/src/interface/common.thrift @@ -25,6 +25,7 @@ cpp_include "common/datatypes/DataSetOps-inl.h" cpp_include "common/datatypes/KeyValueOps-inl.h" cpp_include "common/datatypes/HostAddrOps-inl.h" cpp_include "common/datatypes/GeographyOps-inl.h" +cpp_include "common/datatypes/DurationOps-inl.h" /* * @@ -225,6 +226,12 @@ struct KeyValue { 2: binary value, } (cpp.type = "nebula::KeyValue") +struct Duration { + 1: i64 seconds; + 2: i32 microseconds; + 3: i32 months; +} (cpp.type = "nebula::Duration") + struct LogInfo { 1: LogID log_id; 2: TermID term_id; @@ -278,6 +285,7 @@ enum PropertyType { // Date time TIMESTAMP = 21, + DURATION = 23, DATE = 24, DATETIME = 25, TIME = 26, diff --git a/src/meta/processors/index/CreateEdgeIndexProcessor.cpp b/src/meta/processors/index/CreateEdgeIndexProcessor.cpp index cbd077717db..e3de55f3445 100644 --- a/src/meta/processors/index/CreateEdgeIndexProcessor.cpp +++ b/src/meta/processors/index/CreateEdgeIndexProcessor.cpp @@ -126,6 +126,13 @@ void CreateEdgeIndexProcessor::process(const cpp2::CreateEdgeIndexReq& req) { return; } cpp2::ColumnDef col = *iter; + if (col.type.get_type() == nebula::cpp2::PropertyType::DURATION) { + LOG(ERROR) << "Field " << field.get_name() << " in Edge " << edgeName << " is duration." + << "It can not be indexed."; + handleErrorCode(nebula::cpp2::ErrorCode::E_INVALID_PARM); + onFinished(); + return; + } if (col.type.get_type() == nebula::cpp2::PropertyType::FIXED_STRING) { if (*col.type.get_type_length() > MAX_INDEX_TYPE_LENGTH) { LOG(ERROR) << "Unsupport index type lengths greater than " << MAX_INDEX_TYPE_LENGTH << " : " diff --git a/src/meta/processors/index/CreateTagIndexProcessor.cpp b/src/meta/processors/index/CreateTagIndexProcessor.cpp index e605dfbaf3c..366a02243d6 100644 --- a/src/meta/processors/index/CreateTagIndexProcessor.cpp +++ b/src/meta/processors/index/CreateTagIndexProcessor.cpp @@ -124,6 +124,13 @@ void CreateTagIndexProcessor::process(const cpp2::CreateTagIndexReq& req) { return; } cpp2::ColumnDef col = *iter; + if (col.type.get_type() == nebula::cpp2::PropertyType::DURATION) { + LOG(ERROR) << "Field " << field.get_name() << " in Tag " << tagName << " is duration." + << "It can not be indexed."; + handleErrorCode(nebula::cpp2::ErrorCode::E_INVALID_PARM); + onFinished(); + return; + } if (col.type.get_type() == nebula::cpp2::PropertyType::FIXED_STRING) { if (*col.type.get_type_length() > MAX_INDEX_TYPE_LENGTH) { LOG(ERROR) << "Unsupport index type lengths greater than " << MAX_INDEX_TYPE_LENGTH << " : " diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 2f092a03069..0f8109775fc 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -161,7 +161,7 @@ static constexpr size_t kCommentLengthLimit = 256; /* keywords */ %token KW_BOOL KW_INT8 KW_INT16 KW_INT32 KW_INT64 KW_INT KW_FLOAT KW_DOUBLE -%token KW_STRING KW_FIXED_STRING KW_TIMESTAMP KW_DATE KW_TIME KW_DATETIME +%token KW_STRING KW_FIXED_STRING KW_TIMESTAMP KW_DATE KW_TIME KW_DATETIME KW_DURATION %token KW_GO KW_AS KW_TO KW_USE KW_SET KW_FROM KW_WHERE KW_ALTER %token KW_MATCH KW_INSERT KW_VALUE KW_VALUES KW_YIELD KW_RETURN KW_CREATE KW_VERTEX KW_VERTICES KW_IGNORE_EXISTED_INDEX %token KW_EDGE KW_EDGES KW_STEPS KW_OVER KW_UPTO KW_REVERSELY KW_SPACE KW_DELETE KW_FIND @@ -1029,22 +1029,53 @@ function_call_expression } } | KW_TIMESTAMP L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "timestamp", $3); + if (FunctionManager::find("timestamp", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "timestamp", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } | KW_DATE L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "date", $3); + if (FunctionManager::find("date", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "date", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } | KW_TIME L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "time", $3); + if (FunctionManager::find("time", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "time", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } | KW_DATETIME L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "datetime", $3); + if (FunctionManager::find("datetime", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "datetime", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } | KW_TAGS L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "tags", $3); + if (FunctionManager::find("tags", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "tags", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } | KW_SIGN L_PAREN opt_argument_list R_PAREN { - $$ = FunctionCallExpression::make(qctx->objPool(), "sign", $3); + if (FunctionManager::find("sign", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "sign", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } + } + | KW_DURATION L_PAREN opt_argument_list R_PAREN { + if (FunctionManager::find("duration", $3->numArgs()).ok()) { + $$ = FunctionCallExpression::make(qctx->objPool(), "duration", $3); + } else { + throw nebula::GraphParser::syntax_error(@1, "Unknown function "); + } } ; @@ -1182,6 +1213,10 @@ type_spec $$->set_type(nebula::cpp2::PropertyType::GEOGRAPHY); $$->set_geo_shape($3); } + | KW_DURATION { + $$ = new meta::cpp2::ColumnTypeDef(); + $$->set_type(nebula::cpp2::PropertyType::DURATION); + } ; diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 1b023c16297..fcd086be140 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -278,6 +278,7 @@ LABEL_FULL_WIDTH {CN_EN_FULL_WIDTH}{CN_EN_NUM_FULL_WIDTH}* "POINT" { return TokenType::KW_POINT; } "LINESTRING" { return TokenType::KW_LINESTRING; } "POLYGON" { return TokenType::KW_POLYGON; } +"DURATION" { return TokenType::KW_DURATION; } "MERGE" { return TokenType::KW_MERGE; } "RENAME" { return TokenType::KW_RENAME; } diff --git a/tests/tck/features/expression/TimeComparison.feature b/tests/tck/features/expression/TimeComparison.feature new file mode 100644 index 00000000000..9306252bf81 --- /dev/null +++ b/tests/tck/features/expression/TimeComparison.feature @@ -0,0 +1,23 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Time computation + + Background: + Given a graph with space named "nba" + + Scenario Outline: duration compare + When executing query: + """ + WITH duration() as x, duration() as d + RETURN x > d AS gt, x < d AS lt, x == d AS eq, x != d AS ne, + x >= d AS ge, x <= d AS le + """ + Then the result should be, in any order: + | gt | lt | eq | ne | ge | le | + | | | | | | | + + Examples: + | lhs | rhs | lessThan | greaterThan | equal | notEqual | greaterEqual | lessEqual | + | {days: 30} | {months: 1} | BAD_TYPE | BAD_TYPE | false | true | BAD_TYPE | BAD_TYPE | + | {days: 30, months: 1} | {days: 30, months: 1} | BAD_TYPE | BAD_TYPE | true | false | BAD_TYPE | BAD_TYPE | diff --git a/tests/tck/features/expression/TimeComputation.feature b/tests/tck/features/expression/TimeComputation.feature new file mode 100644 index 00000000000..31ec863e64f --- /dev/null +++ b/tests/tck/features/expression/TimeComputation.feature @@ -0,0 +1,80 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Time computation + + Background: + Given a graph with space named "nba" + + Scenario Outline: date add or subtract duration + When executing query: + """ + WITH date('1984-10-11') as x, duration() as d + RETURN x + d AS sum, x - d AS diff + """ + Then the result should be, in any order: + | sum | diff | + | | | + + Examples: + | map | sum | diff | + | {years: 12, months: 5, days: 14, hours: 16, minutes: 12, seconds: 70} | '1997-03-25' | '1972-04-27' | + + # TODO don't allow different sign + # | {months: 1, days: -14, hours: 16, minutes: -12, seconds: 70} | '1984-10-28' | '1984-09-25' | + # TODO support decimal + # | {years: 12.5, months: 5.5, days: 14.5, hours: 16.5, minutes: 12.5, seconds: 70.5} | '1997-10-11' | '1971-10-12' | + Scenario Outline: time add or subtract duration + When executing query: + """ + WITH time('12:31:14') as x, duration() as d + RETURN x + d AS sum, x - d AS diff + """ + Then the result should be, in any order: + | sum | diff | + | | | + + Examples: + | map | sum | diff | + | {years: 12, months: 5, days: 14, hours: 16, minutes: 12, seconds: 70} | '04:44:24.000000' | '20:18:04.000000' | + + # TODO don't allow different sign + # | {months: 1, days: -14, hours: 16, minutes: -12, seconds: 70} | '04:20:24.000000001+01:00' | '20:42:04.000000001+01:00' | + # TODO support decimal + # | {years: 12.5, months: 5.5, days: 14.5, hours: 16.5, minutes: 12.5, seconds: 70.5} | '22:29:27.500000004+01:00' | '02:33:00.499999998+01:00' | + Scenario Outline: datetime add or subtract duration + When executing query: + """ + WITH datetime('1984-10-11T12:31:14') as x, duration() as d + RETURN x + d AS sum, x - d AS diff + """ + Then the result should be, in any order: + | sum | diff | + | | | + + Examples: + | map | sum | diff | + | {years: 12, months: 5, days: 14, hours: 16, minutes: 12, seconds: 70} | '1997-03-26T04:44:24.000000' | '1972-04-26T20:18:04.000000' | + + # TODO don't allow different sign + # | {months: 1, days: -14, hours: 16, minutes: -12, seconds: 70} | '1984-10-29T04:20:24.000000001+01:00' | '1984-09-24T20:42:04.000000001+01:00' | + # TODO support decimal + # | {years: 12.5, months: 5.5, days: 14.5, hours: 16.5, minutes: 12.5, seconds: 70.5, nanoseconds: 3} | '1997-10-11T22:29:27.500000004+01:00' | '1971-10-12T02:33:00.499999998+01:00' | + Scenario Outline: datetime add or subtract duration + When executing query: + """ + WITH datetime('1984-10-11T12:31:14') as x, duration() as d + RETURN x + d AS sum, x - d AS diff + """ + Then the result should be, in any order: + | sum | diff | + | | | + + Examples: + | map | sum | diff | + | {years: 12, months: 5, days: 14, hours: 16, minutes: 12, seconds: 70} | '1997-03-26T04:44:24.000000' | '1972-04-26T20:18:04.000000' | + +# TODO don't allow different sign +# | {months: 1, days: -14, hours: 16, minutes: -12, seconds: 70} | '1984-10-29T04:20:24.000000001' | '1984-09-24T20:42:04.000000001' | +# TODO support decimal +# | {years: 12.5, months: 5.5, days: 14.5, hours: 16.5, minutes: 12.5, seconds: 70.5, nanoseconds: 3} | '1997-10-11T22:29:27.500000004' | '1971-10-12T02:33:00.499999998' | diff --git a/tests/tck/features/mutate/InsertDurationType.feature b/tests/tck/features/mutate/InsertDurationType.feature new file mode 100644 index 00000000000..fca74516c0d --- /dev/null +++ b/tests/tck/features/mutate/InsertDurationType.feature @@ -0,0 +1,132 @@ +Feature: Insert duration + + Background: + Given an empty graph + And create a space with following options: + | partition_num | 9 | + | replica_factor | 1 | + | vid_type | FIXED_STRING(20) | + + Scenario: insert duration failed + Given having executed: + """ + CREATE TAG IF NOT EXISTS test_failed(a int); + """ + When try to execute query: + """ + INSERT VERTEX test_failed(a) VALUES "TEST_VERTEX_FAILED":(duration({years: 3})) + """ + Then a ExecutionError should be raised at runtime:Storage Error: The data type does not meet the requirements. Use the correct type of data. + When try to execute query: + """ + INSERT VERTEX test_failed(a) VALUES "TEST_VERTEX_FAILED":(duration()) + """ + Then a SyntaxError should be raised at runtime: Unknown function near `duration' + + Scenario: duration don't support index + Given having executed: + """ + CREATE TAG IF NOT EXISTS test_tag_index_failed(a duration); + CREATE EDGE IF NOT EXISTS test_edge_index_failed(a duration); + """ + When try to execute query: + """ + CREATE TAG INDEX test_tag_duration_index ON test_tag_index_failed(a); + """ + Then a ExecutionError should be raised at runtime: Invalid param! + When try to execute query: + """ + CREATE EDGE INDEX test_edge_duration_index ON test_edge_index_failed(a); + """ + Then a ExecutionError should be raised at runtime: Invalid param! + + Scenario: Basic CRUD for duration type + Given having executed: + """ + CREATE TAG tag_duration(f_duration duration); + CREATE EDGE edge_duration(f_duration duration); + """ + When executing query: + """ + SHOW CREATE TAG tag_duration; + """ + Then the result should be, in any order: + | Tag | Create Tag | + | 'tag_duration' | 'CREATE TAG `tag_duration` (\n `f_duration` duration NULL\n) ttl_duration = 0, ttl_col = ""' | + When executing query: + """ + SHOW CREATE EDGE edge_duration; + """ + Then the result should be, in any order: + | Edge | Create Edge | + | 'edge_duration' | 'CREATE EDGE `edge_duration` (\n `f_duration` duration NULL\n) ttl_duration = 0, ttl_col = ""' | + When try to execute query: + """ + INSERT VERTEX tag_duration(f_duration) VALUES "test":(duration({years: 1, seconds: 0})); + INSERT EDGE edge_duration(f_duration) VALUES "test_src"->"test_dst":(duration({years: 1, seconds: 0})); + """ + Then the execution should be successful + When executing query: + """ + INSERT VERTEX tag_duration(f_duration) VALUES "test":(1) + """ + Then a ExecutionError should be raised at runtime: + When executing query: + """ + INSERT EDGE edge_duration(f_duration) VALUES "test_src"->"test_dst":(true) + """ + Then a ExecutionError should be raised at runtime: + # TODO TCK python-client support duration type + # When executing query: + # """ + # FETCH PROP ON tag_duration "test" YIELD tag_duration.f_duration + # """ + # Then the result should be, in any order: + # | tag_duration.f_duration | + # | 'P1YT1S' | + # When executing query: + # """ + # FETCH PROP ON edge_duration "test_src"->"test_dst" YIELD edge_duration.f_duration + # """ + # Then the result should be, in any order: + # | edge_duration.f_duration | + # | 'P1YT1S' | + # When executing query: + # """ + # UPDATE VERTEX "test" + # SET + # tag_duration.f_duration = duration({years: 3, months: 3, days: 4}) + # YIELD f_duration + # """ + # Then the result should be, in any order: + # | f_duration | + # | 'P3Y3M4D' | + # When executing query: + # """ + # UPDATE EDGE "test_src"->"test_dst" OF edge_duration + # SET + # edge_duration.f_duration = duration({years: 3, months: 3, days: 4}) + # YIELD f_duration + # """ + # Then the result should be, in any order: + # | f_duration | + # | 'P3Y3M4D' | + When executing query: + """ + DELETE VERTEX "test"; + DELETE EDGE edge_duration "test_src"->"test_dst"; + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON tag_duration "test" YIELD vertex as node; + """ + Then the result should be, in any order, with relax comparison: + | node | + When executing query: + """ + FETCH PROP ON edge_duration "test_src"->"test_dst" YIELD edge as e; + """ + Then the result should be, in any order, with relax comparison: + | e | + And drop the used space