From 771db37171ced34c88ca242b4c9f884b216d672b Mon Sep 17 00:00:00 2001 From: Corey Kosak Date: Wed, 18 Sep 2024 14:54:58 -0400 Subject: [PATCH] feat(cpp-client): Add support for LocalDate and LocalTime --- .../client/arrowutil/arrow_column_source.h | 53 ++++++- .../deephaven/client/utility/table_maker.h | 34 ++++- .../src/arrowutil/arrow_client_table.cc | 12 ++ .../src/subscription/subscribe_thread.cc | 14 ++ .../dhclient/src/utility/arrow_util.cc | 27 +++- .../public/deephaven/dhcore/chunk/chunk.h | 49 ++++--- .../deephaven/dhcore/chunk/chunk_traits.h | 10 ++ .../deephaven/dhcore/column/column_source.h | 16 ++ .../dhcore/column/column_source_helpers.h | 28 +++- .../include/public/deephaven/dhcore/types.h | 138 +++++++++++++++++- .../deephaven/dhcore/src/chunk/chunk_maker.cc | 10 ++ .../src/column/column_source_helpers.cc | 2 + .../dhcore/src/ticking/immer_table_state.cc | 8 + cpp-client/deephaven/dhcore/src/types.cc | 56 ++++++- .../dhcore/src/utility/cython_support.cc | 8 + cpp-client/deephaven/tests/src/select_test.cc | 12 +- cpp-client/deephaven/tests/src/table_test.cc | 16 +- 17 files changed, 466 insertions(+), 27 deletions(-) diff --git a/cpp-client/deephaven/dhclient/include/private/deephaven/client/arrowutil/arrow_column_source.h b/cpp-client/deephaven/dhclient/include/private/deephaven/client/arrowutil/arrow_column_source.h index a4d8d1da18c..16b5c30e987 100644 --- a/cpp-client/deephaven/dhclient/include/private/deephaven/client/arrowutil/arrow_column_source.h +++ b/cpp-client/deephaven/dhclient/include/private/deephaven/client/arrowutil/arrow_column_source.h @@ -22,7 +22,8 @@ namespace internal { // null-ness by determining whether the optional has a value. // kTimestamp is its own special case, where nullness is determined by the underlying nanos // being equal to Deephaven's NULL_LONG. -enum class ArrowProcessingStyle { kNormal, kBooleanOrString, kTimestamp }; +// kLocalDate and kLocalTime are like kTimestamp except they resolve to different data types. +enum class ArrowProcessingStyle { kNormal, kBooleanOrString, kTimestamp, kLocalDate, kLocalTime }; template class GenericArrowColumnSource final : public TColumnSourceBase { @@ -30,6 +31,8 @@ class GenericArrowColumnSource final : public TColumnSourceBase { using Chunk = deephaven::dhcore::chunk::Chunk; using ColumnSourceVisitor = deephaven::dhcore::column::ColumnSourceVisitor; using DateTime = deephaven::dhcore::DateTime; + using LocalDate = deephaven::dhcore::LocalDate; + using LocalTime = deephaven::dhcore::LocalTime; using RowSequence = deephaven::dhcore::container::RowSequence; using UInt64Chunk = deephaven::dhcore::chunk::UInt64Chunk; @@ -98,6 +101,14 @@ class GenericArrowColumnSource final : public TColumnSourceBase { auto relative_end = min_end - src_segment_begin; const auto &innerp = *outerp; + static_assert( + Style == ArrowProcessingStyle::kNormal || + Style == ArrowProcessingStyle::kBooleanOrString || + Style == ArrowProcessingStyle::kTimestamp || + Style == ArrowProcessingStyle::kLocalDate || + Style == ArrowProcessingStyle::kLocalTime, + "Unexpected ArrowProcessingStyle"); + if constexpr (Style == ArrowProcessingStyle::kNormal) { // Process these types using pointer operations and the Deephaven Null convention const auto *src_beginp = innerp->raw_values() + relative_begin; @@ -139,6 +150,34 @@ class GenericArrowColumnSource final : public TColumnSourceBase { *destp = DateTime::FromNanos(*ip); ++destp; + if (null_destp != nullptr) { + *null_destp = *ip == DeephavenTraits::kNullValue; + ++null_destp; + } + } + } else if constexpr (Style == ArrowProcessingStyle::kLocalDate) { + // Process these types using pointer operations and the Deephaven Null convention + const auto *src_beginp = innerp->raw_values() + relative_begin; + const auto *src_endp = innerp->raw_values() + relative_end; + + for (const auto *ip = src_beginp; ip != src_endp; ++ip) { + *destp = LocalDate::FromMillis(*ip); + ++destp; + + if (null_destp != nullptr) { + *null_destp = *ip == DeephavenTraits::kNullValue; + ++null_destp; + } + } + } else if constexpr (Style == ArrowProcessingStyle::kLocalTime) { + // Process these types using pointer operations and the Deephaven Null convention + const auto *src_beginp = innerp->raw_values() + relative_begin; + const auto *src_endp = innerp->raw_values() + relative_end; + + for (const auto *ip = src_beginp; ip != src_endp; ++ip) { + *destp = LocalTime::FromNanos(*ip); + ++destp; + if (null_destp != nullptr) { *null_destp = *ip == DeephavenTraits::kNullValue; ++null_destp; @@ -223,4 +262,16 @@ using DateTimeArrowColumnSource = internal::GenericArrowColumnSource< deephaven::dhcore::column::DateTimeColumnSource, arrow::TimestampArray, deephaven::dhcore::chunk::DateTimeChunk>; + +using LocalDateArrowColumnSource = internal::GenericArrowColumnSource< + internal::ArrowProcessingStyle::kLocalDate, + deephaven::dhcore::column::LocalDateColumnSource, + arrow::Date64Array, + deephaven::dhcore::chunk::LocalDateChunk>; + +using LocalTimeArrowColumnSource = internal::GenericArrowColumnSource< + internal::ArrowProcessingStyle::kLocalTime, + deephaven::dhcore::column::LocalTimeColumnSource, + arrow::Time64Array, + deephaven::dhcore::chunk::LocalTimeChunk>; } // namespace deephaven::client::arrowutil diff --git a/cpp-client/deephaven/dhclient/include/public/deephaven/client/utility/table_maker.h b/cpp-client/deephaven/dhclient/include/public/deephaven/client/utility/table_maker.h index 30dc3c365e1..02f39647c82 100644 --- a/cpp-client/deephaven/dhclient/include/public/deephaven/client/utility/table_maker.h +++ b/cpp-client/deephaven/dhclient/include/public/deephaven/client/utility/table_maker.h @@ -140,7 +140,7 @@ template struct TypeConverterTraits { // The below assert fires when this class is instantiated; i.e. when none of the specializations // match. It needs to be written this way (with "is_same") because for technical reasons it - // needso be dependent on T, even if degenerately so. + // needs to be dependent on T, even if degenerately so. static_assert(!std::is_same_v, "TableMaker doesn't know how to work with this type"); }; @@ -307,6 +307,38 @@ struct TypeConverterTraits { } }; +template<> +struct TypeConverterTraits { + static std::shared_ptr GetDataType() { + return arrow::date64(); + } + static arrow::Date64Builder GetBuilder() { + return arrow::Date64Builder(); + } + static int64_t Reinterpret(const deephaven::dhcore::LocalDate &o) { + return o.Millis(); + } + static std::string_view GetDeephavenTypeName() { + return "java.time.LocalDate"; + } +}; + +template<> +struct TypeConverterTraits { + static std::shared_ptr GetDataType() { + return arrow::time64(arrow::TimeUnit::NANO); + } + static arrow::Time64Builder GetBuilder() { + return arrow::Time64Builder(GetDataType(), arrow::default_memory_pool()); + } + static int64_t Reinterpret(const deephaven::dhcore::LocalTime &o) { + return o.Nanos(); + } + static std::string_view GetDeephavenTypeName() { + return "java.time.LocalTime"; + } +}; + template struct TypeConverterTraits> { using inner_t = TypeConverterTraits; diff --git a/cpp-client/deephaven/dhclient/src/arrowutil/arrow_client_table.cc b/cpp-client/deephaven/dhclient/src/arrowutil/arrow_client_table.cc index a1178f9ef97..1d574a3809a 100644 --- a/cpp-client/deephaven/dhclient/src/arrowutil/arrow_client_table.cc +++ b/cpp-client/deephaven/dhclient/src/arrowutil/arrow_client_table.cc @@ -168,6 +168,18 @@ struct Visitor final : public arrow::TypeVisitor { return arrow::Status::OK(); } + arrow::Status Visit(const arrow::Date64Type &/*type*/) final { + auto arrays = DowncastChunks(chunked_array_); + result_ = LocalDateArrowColumnSource::OfArrowArrayVec(std::move(arrays)); + return arrow::Status::OK(); + } + + arrow::Status Visit(const arrow::Time64Type &/*type*/) final { + auto arrays = DowncastChunks(chunked_array_); + result_ = LocalTimeArrowColumnSource::OfArrowArrayVec(std::move(arrays)); + return arrow::Status::OK(); + } + const arrow::ChunkedArray &chunked_array_; std::shared_ptr result_; }; diff --git a/cpp-client/deephaven/dhclient/src/subscription/subscribe_thread.cc b/cpp-client/deephaven/dhclient/src/subscription/subscribe_thread.cc index be7a19bf18d..0bf6a25a4bb 100644 --- a/cpp-client/deephaven/dhclient/src/subscription/subscribe_thread.cc +++ b/cpp-client/deephaven/dhclient/src/subscription/subscribe_thread.cc @@ -33,6 +33,8 @@ using deephaven::client::arrowutil::Int8ArrowColumnSource; using deephaven::client::arrowutil::Int16ArrowColumnSource; using deephaven::client::arrowutil::Int32ArrowColumnSource; using deephaven::client::arrowutil::Int64ArrowColumnSource; +using deephaven::client::arrowutil::LocalDateArrowColumnSource; +using deephaven::client::arrowutil::LocalTimeArrowColumnSource; using deephaven::client::arrowutil::StringArrowColumnSource; using deephaven::client::utility::Executor; using deephaven::client::utility::OkOrThrow; @@ -323,6 +325,18 @@ struct ArrayToColumnSourceVisitor final : public arrow::ArrayVisitor { return arrow::Status::OK(); } + arrow::Status Visit(const arrow::Date64Array &/*array*/) final { + auto typed_array = std::dynamic_pointer_cast(array_); + result_ = LocalDateArrowColumnSource::OfArrowArray(std::move(typed_array)); + return arrow::Status::OK(); + } + + arrow::Status Visit(const arrow::Time64Array &/*array*/) final { + auto typed_array = std::dynamic_pointer_cast(array_); + result_ = LocalTimeArrowColumnSource::OfArrowArray(std::move(typed_array)); + return arrow::Status::OK(); + } + const std::shared_ptr &array_; std::shared_ptr result_; }; diff --git a/cpp-client/deephaven/dhclient/src/utility/arrow_util.cc b/cpp-client/deephaven/dhclient/src/utility/arrow_util.cc index 2a0a13870ee..e71848bafef 100644 --- a/cpp-client/deephaven/dhclient/src/utility/arrow_util.cc +++ b/cpp-client/deephaven/dhclient/src/utility/arrow_util.cc @@ -81,7 +81,12 @@ struct ArrowToElementTypeId final : public arrow::TypeVisitor { return arrow::Status::OK(); } - arrow::Status Visit(const arrow::TimestampType &/*type*/) final { + arrow::Status Visit(const arrow::TimestampType &type) final { + if (type.unit() != arrow::TimeUnit::NANO) { + auto message = fmt::format("Expected TimestampType with nano units, got {}", + type.ToString()); + throw std::runtime_error(DEEPHAVEN_LOCATION_STR(message)); + } type_id_ = ElementTypeId::kTimestamp; return arrow::Status::OK(); } @@ -91,6 +96,26 @@ struct ArrowToElementTypeId final : public arrow::TypeVisitor { return arrow::Status::OK(); } + arrow::Status Visit(const arrow::Time64Type &type) final { + if (type.unit() != arrow::TimeUnit::NANO) { + auto message = fmt::format("Expected Time64Type with nano units, got {}", + type.ToString()); + throw std::runtime_error(DEEPHAVEN_LOCATION_STR(message)); + } + type_id_ = ElementTypeId::kLocalTime; + return arrow::Status::OK(); + } + + arrow::Status Visit(const arrow::Date64Type &type) final { + if (type.unit() != arrow::DateUnit::MILLI) { + auto message = fmt::format("Expected Date64Type with milli units, got {}", + type.ToString()); + throw std::runtime_error(DEEPHAVEN_LOCATION_STR(message)); + } + type_id_ = ElementTypeId::kLocalDate; + return arrow::Status::OK(); + } + ElementTypeId::Enum type_id_ = ElementTypeId::kInt8; // arbitrary initializer }; } // namespace diff --git a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk.h b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk.h index c7284f79ecb..203eadbbdc7 100644 --- a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk.h +++ b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk.h @@ -160,61 +160,70 @@ class GenericChunk final : public Chunk { }; /** - * For convenience. + * Convenience using. */ using CharChunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using Int8Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using UInt8Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using Int16Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using UInt16Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using Int32Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using UInt32Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using Int64Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using UInt64Chunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using FloatChunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using DoubleChunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using BooleanChunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using StringChunk = GenericChunk; /** - * Convenience typedef. + * Convenience using. */ using DateTimeChunk = GenericChunk; +/** + * Convenience using. + */ +using LocalDateChunk = GenericChunk; +/** + * Convenience using. + */ +using LocalTimeChunk = GenericChunk; + /** * Abstract base class that implements the visitor pattern for Chunk. @@ -272,6 +281,11 @@ class ChunkVisitor { /** * Implements the visitor pattern. */ + virtual void Visit(const LocalDateChunk &) = 0; + /** + * Implements the visitor pattern. + */ + virtual void Visit(const LocalTimeChunk &) = 0; }; template @@ -286,7 +300,8 @@ void GenericChunk::AcceptVisitor(ChunkVisitor *visitor) const { */ class AnyChunk { using variant_t = std::variant; + UInt64Chunk, FloatChunk, DoubleChunk, BooleanChunk, StringChunk, DateTimeChunk, + LocalDateChunk, LocalTimeChunk>; public: /** @@ -351,8 +366,8 @@ GenericChunk GenericChunk::Create(size_t size) { template GenericChunk GenericChunk::CreateView(T *data, size_t size) { - // GenericChunks allocated by create() point to an underlying heap-allocated buffer. On the other - // hand, GenericChunks created by createView() point to the caller's buffer. In the former case + // GenericChunks allocated by Create() point to an underlying heap-allocated buffer. On the other + // hand, GenericChunks created by CreateView() point to the caller's buffer. In the former case // we own the buffer and need to delete it when there are no more shared_ptrs pointing to it. In // the latter case the caller owns the buffer, and we should not try to deallocate it. // One might think we have to use two different data structures to handle these two different diff --git a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk_traits.h b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk_traits.h index cae2c5bd053..83d893f7d02 100644 --- a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk_traits.h +++ b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/chunk/chunk_traits.h @@ -64,4 +64,14 @@ template<> struct TypeToChunk { using type_t = deephaven::dhcore::chunk::DateTimeChunk; }; + +template<> +struct TypeToChunk { + using type_t = deephaven::dhcore::chunk::LocalDateChunk; +}; + +template<> +struct TypeToChunk { + using type_t = deephaven::dhcore::chunk::LocalTimeChunk; +}; } // namespace deephaven::client::chunk diff --git a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source.h b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source.h index 6322a4587de..eea1c94ea8e 100644 --- a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source.h +++ b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source.h @@ -174,6 +174,14 @@ using StringColumnSource = GenericColumnSource; * Convenience using. */ using DateTimeColumnSource = GenericColumnSource; +/** + * Convenience using. + */ +using LocalDateColumnSource = GenericColumnSource; +/** + * Convenience using. + */ +using LocalTimeColumnSource = GenericColumnSource; // the mutable per-type interfaces template @@ -229,5 +237,13 @@ class ColumnSourceVisitor { * Implements the visitor pattern. */ virtual void Visit(const DateTimeColumnSource &) = 0; + /** + * Implements the visitor pattern. + */ + virtual void Visit(const LocalDateColumnSource &) = 0; + /** + * Implements the visitor pattern. + */ + virtual void Visit(const LocalTimeColumnSource &) = 0; }; } // namespace deephaven::dhcore::column diff --git a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source_helpers.h b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source_helpers.h index 7beb39dbcba..7432483b81c 100644 --- a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source_helpers.h +++ b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/column/column_source_helpers.h @@ -21,6 +21,8 @@ class HumanReadableTypeNames { static const char kBoolName[]; static const char kStringName[]; static const char kDateTimeName[]; + static const char kLocalDateName[]; + static const char kLocalTimeName[]; }; struct ElementTypeVisitor : public ColumnSourceVisitor { @@ -63,7 +65,15 @@ struct ElementTypeVisitor : public ColumnSourceVisitor { void Visit(const DateTimeColumnSource & /*source*/) final { value_ = HumanReadableTypeNames::kDateTimeName; } - + + void Visit(const LocalDateColumnSource & /*source*/) final { + value_ = HumanReadableTypeNames::kLocalDateName; + } + + void Visit(const LocalTimeColumnSource & /*source*/) final { + value_ = HumanReadableTypeNames::kLocalTimeName; + } + const char *value_ = nullptr; }; } // namespace internal @@ -124,4 +134,20 @@ template<> struct HumanReadableStaticTypeName { static const char *GetName() { return internal::HumanReadableTypeNames::kStringName; } }; + +template<> +struct HumanReadableStaticTypeName { + static const char *GetName() { return internal::HumanReadableTypeNames::kDateTimeName; } +}; + +template<> +struct HumanReadableStaticTypeName { + static const char *GetName() { return internal::HumanReadableTypeNames::kLocalDateName; } +}; + +template<> +struct HumanReadableStaticTypeName { + static const char *GetName() { return internal::HumanReadableTypeNames::kLocalTimeName; } +}; + } // namespace deephaven::client::column diff --git a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/types.h b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/types.h index 5869df0bd3c..190423437a4 100644 --- a/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/types.h +++ b/cpp-client/deephaven/dhcore/include/public/deephaven/dhcore/types.h @@ -21,11 +21,14 @@ struct ElementTypeId { kInt8, kInt16, kInt32, kInt64, kFloat, kDouble, kBool, kString, kTimestamp, - kList + kList, + kLocalDate, kLocalTime }; }; class DateTime; +class LocalDate; +class LocalTime; template void VisitElementTypeId(ElementTypeId::Enum type_id, T *visitor) { @@ -70,6 +73,14 @@ void VisitElementTypeId(ElementTypeId::Enum type_id, T *visitor) { visitor->template operator()(); break; } + case ElementTypeId::kLocalDate: { + visitor->template operator()(); + break; + } + case ElementTypeId::kLocalTime: { + visitor->template operator()(); + break; + } default: { auto message = fmt::format("Unrecognized ElementTypeId {}", static_cast(type_id)); throw std::runtime_error(message); @@ -315,6 +326,16 @@ struct DeephavenTraits { static constexpr bool kIsNumeric = false; }; +template<> +struct DeephavenTraits { + static constexpr bool kIsNumeric = false; +}; + +template<> +struct DeephavenTraits { + static constexpr bool kIsNumeric = false; +}; + /** * The Deephaven DateTime type. Records nanoseconds relative to the epoch (January 1, 1970) UTC. * Times before the epoch can be represented with negative nanosecond values. @@ -401,6 +422,121 @@ class DateTime { return !(lhs == rhs); } }; + +/** + * The Deephaven LocalDate type which corresponds to java.time.LocalDate. + * For consistency with the Arrow type we use, stores its value in units of milliseconds. + * However we do not allow fractional days, so only millisecond values that are an even + * number of days are permitted. + */ +class LocalDate { +public: + /** + * Creates an instance of LocalDate from the specified year, month, and day. + */ + static LocalDate Of(int32_t year, int32_t month, int32_t day_of_month); + + /** + * Creates an instance of LocalDate from milliseconds-since-UTC-epoch. + * The Deephaven null value sentinel is turned into LocalDate(0). + * @param millis Milliseconds since the epoch (January 1, 1970 UTC). + * An exception is thrown if millis is not an even number of days. + * @return The corresponding LocalDate + */ + static LocalDate FromMillis(int64_t millis) { + if (millis == DeephavenConstants::kNullLong) { + return LocalDate(0); + } + return LocalDate(millis); + } + + /** + * Default constructor. Sets the LocalDate equal to the null value. + */ + LocalDate() = default; + + /** + * Sets the DateTime to the specified number of milliseconds relative to the epoch. + * Currently we will throw an exception if millis is not an even number of days. + * @param millis Milliseconds since the epoch (January 1, 1970 UTC). + */ + explicit LocalDate(int64_t millis); + + /** + * The LocalDate as expressed in milliseconds since the epoch. Can be negative. + */ + [[nodiscard]] + int64_t Millis() const { return millis_; } + +private: + int64_t millis_ = 0; + + friend std::ostream &operator<<(std::ostream &s, const LocalDate &o); + + friend bool operator==(const LocalDate &lhs, const LocalDate &rhs) { + return lhs.millis_ == rhs.millis_; + } + + friend bool operator!=(const LocalDate &lhs, const LocalDate &rhs) { + return !(lhs == rhs); + } +}; + +/** + * The Deephaven LocalTime type which corresponds to java.time.LocalTime. Records + * nanoseconds since midnight (of some unspecified reference day). + */ +class LocalTime { +public: + /** + * Creates an instance of LocalTime from the specified hour, minute, and second. + */ + static LocalTime Of(int32_t hour, int32_t minute, int32_t second); + + /** + * Converts nanoseconds-since-start-of-day to LocalTime. The Deephaven null value sentinel is + * turned into LocalTime(0). + * TODO(kosak): find out null convention + * @param nanos Nanoseconds since the start of the day. + * @return The corresponding LocalTime. + */ + static LocalTime FromNanos(int64_t nanos) { + if (nanos == DeephavenConstants::kNullLong) { + return LocalTime(0); + } + return LocalTime(nanos); + } + + /** + * Default constructor. Sets the DateTime equal to the epoch. + */ + LocalTime() = default; + + /** + * Sets the LocalTime to the specified number of nanoseconds relative to the start of the day. + * @param nanos Nanoseconds since the start of the day. + */ + explicit LocalTime(int64_t nanos); + + [[nodiscard]] + int64_t Nanos() const { return nanos_; } + +private: + int64_t nanos_ = 0; + + friend std::ostream &operator<<(std::ostream &s, const LocalTime &o); + + friend bool operator==(const LocalTime &lhs, const LocalTime &rhs) { + return lhs.nanos_ == rhs.nanos_; + } + + friend bool operator!=(const LocalTime &lhs, const LocalTime &rhs) { + return !(lhs == rhs); + } +}; + } // namespace deephaven::dhcore template<> struct fmt::formatter : ostream_formatter {}; +template<> struct fmt::formatter : ostream_formatter {}; +template<> struct fmt::formatter : ostream_formatter {}; diff --git a/cpp-client/deephaven/dhcore/src/chunk/chunk_maker.cc b/cpp-client/deephaven/dhcore/src/chunk/chunk_maker.cc index e62cced69d8..2f7af942070 100644 --- a/cpp-client/deephaven/dhcore/src/chunk/chunk_maker.cc +++ b/cpp-client/deephaven/dhcore/src/chunk/chunk_maker.cc @@ -15,6 +15,8 @@ using deephaven::dhcore::column::Int8ColumnSource; using deephaven::dhcore::column::Int16ColumnSource; using deephaven::dhcore::column::Int32ColumnSource; using deephaven::dhcore::column::Int64ColumnSource; +using deephaven::dhcore::column::LocalDateColumnSource; +using deephaven::dhcore::column::LocalTimeColumnSource; using deephaven::dhcore::column::StringColumnSource; namespace deephaven::dhcore::chunk { @@ -62,6 +64,14 @@ struct Visitor final : ColumnSourceVisitor { result_ = DateTimeChunk::Create(chunk_size_); } + void Visit(const LocalDateColumnSource &/*source*/) final { + result_ = LocalDateChunk::Create(chunk_size_); + } + + void Visit(const LocalTimeColumnSource &/*source*/) final { + result_ = LocalTimeChunk::Create(chunk_size_); + } + size_t chunk_size_; AnyChunk result_; }; diff --git a/cpp-client/deephaven/dhcore/src/column/column_source_helpers.cc b/cpp-client/deephaven/dhcore/src/column/column_source_helpers.cc index 44020a85874..ac961edc11e 100644 --- a/cpp-client/deephaven/dhcore/src/column/column_source_helpers.cc +++ b/cpp-client/deephaven/dhcore/src/column/column_source_helpers.cc @@ -14,4 +14,6 @@ const char HumanReadableTypeNames::kDoubleName[] = "double"; const char HumanReadableTypeNames::kBoolName[] = "bool"; const char HumanReadableTypeNames::kStringName[] = "string"; const char HumanReadableTypeNames::kDateTimeName[] = "DateTime"; +const char HumanReadableTypeNames::kLocalDateName[] = "LocalDate"; +const char HumanReadableTypeNames::kLocalTimeName[] = "LocalTime"; } // namespace deephaven::dhcore::column::internal diff --git a/cpp-client/deephaven/dhcore/src/ticking/immer_table_state.cc b/cpp-client/deephaven/dhcore/src/ticking/immer_table_state.cc index 7b588731113..f90fe607bc1 100644 --- a/cpp-client/deephaven/dhcore/src/ticking/immer_table_state.cc +++ b/cpp-client/deephaven/dhcore/src/ticking/immer_table_state.cc @@ -281,6 +281,14 @@ struct FlexVectorFromSourceMaker final : public ColumnSourceVisitor { result_ = std::make_unique>(); } + void Visit(const column::LocalDateColumnSource &/*source*/) final { + result_ = std::make_unique>(); + } + + void Visit(const column::LocalTimeColumnSource &/*source*/) final { + result_ = std::make_unique>(); + } + std::unique_ptr result_; }; diff --git a/cpp-client/deephaven/dhcore/src/types.cc b/cpp-client/deephaven/dhcore/src/types.cc index 3f92f7051bc..c3e196a847f 100644 --- a/cpp-client/deephaven/dhcore/src/types.cc +++ b/cpp-client/deephaven/dhcore/src/types.cc @@ -105,4 +105,58 @@ std::ostream &operator<<(std::ostream &s, const DateTime &o) { fmt::print(s, "{:%FT%TZ}", tp); return s; } -} // namespace deephaven::client + +LocalDate LocalDate::Of(int32_t year, int32_t month, int32_t day_of_month) { + auto ymd = date::year_month_day(date::year(year), date::month(month), date::day(day_of_month)); + auto as_sys_days = static_cast(ymd); + auto as_milliseconds = std::chrono::milliseconds(as_sys_days.time_since_epoch()); + return LocalDate(as_milliseconds.count()); +} + +LocalDate::LocalDate(int64_t millis) : millis_(millis) { + std::chrono::milliseconds chrono_millis(millis); + std::chrono::time_point tp(chrono_millis); + + auto truncated = date::floor(tp); + auto difference = tp - truncated; + if (difference.count() == 0) { + return; + } + + auto message = fmt::format("{} milliseconds is not an integral number of days", millis); + throw std::runtime_error(DEEPHAVEN_LOCATION_STR(message)); +} + +std::ostream &operator<<(std::ostream &s, const LocalDate &o) { + std::chrono::milliseconds millis(o.millis_); + std::chrono::time_point tp(millis); + fmt::print(s, "{:%F}", tp); + return s; +} + +LocalTime LocalTime::Of(int32_t hour, int32_t minute, int32_t second) { + auto ns = std::chrono::nanoseconds(0); + ns += std::chrono::hours(hour); + ns += std::chrono::minutes(minute); + ns += std::chrono::seconds(second); + return LocalTime(ns.count()); +} + +LocalTime::LocalTime(int64_t nanos) : nanos_(nanos) { + if (nanos >= 0) { + return; + } + + auto message = fmt::format("nanos argument ({}) cannot be negative", nanos); + throw std::runtime_error(DEEPHAVEN_LOCATION_STR(message)); +} + +std::ostream &operator<<(std::ostream &s, const LocalTime &o) { + std::chrono::nanoseconds ns(o.nanos_); + // Make a time point with nanosecond precision so we can print 9 digits of fractional second + // precision. + std::chrono::time_point tp(ns); + fmt::print(s, "{:%T}", tp); + return s; +} +} // namespace deephaven::dhcore diff --git a/cpp-client/deephaven/dhcore/src/utility/cython_support.cc b/cpp-client/deephaven/dhcore/src/utility/cython_support.cc index 2cde6fd972d..6609e356b56 100644 --- a/cpp-client/deephaven/dhcore/src/utility/cython_support.cc +++ b/cpp-client/deephaven/dhcore/src/utility/cython_support.cc @@ -108,6 +108,14 @@ struct ElementTypeIdVisitor final : ColumnSourceVisitor { elementTypeId_ = ElementTypeId::kTimestamp; } + void Visit(const column::LocalDateColumnSource &/*source*/) final { + elementTypeId_ = ElementTypeId::kLocalDate; + } + + void Visit(const column::LocalTimeColumnSource &/*source*/) final { + elementTypeId_ = ElementTypeId::kLocalTime; + } + ElementTypeId::Enum elementTypeId_ = ElementTypeId::kChar; }; } // namespace diff --git a/cpp-client/deephaven/tests/src/select_test.cc b/cpp-client/deephaven/tests/src/select_test.cc index d9168d48bab..04a73f3db51 100644 --- a/cpp-client/deephaven/tests/src/select_test.cc +++ b/cpp-client/deephaven/tests/src/select_test.cc @@ -12,6 +12,8 @@ using deephaven::client::Client; using deephaven::client::TableHandle; using deephaven::client::utility::TableMaker; using deephaven::dhcore::DateTime; +using deephaven::dhcore::LocalDate; +using deephaven::dhcore::LocalTime; using deephaven::dhcore::DeephavenConstants; namespace deephaven::client::tests { @@ -28,6 +30,8 @@ TEST_CASE("Support all types", "[select]") { std::vector double_data; std::vector string_data; std::vector date_time_data; + std::vector local_date_data; + std::vector local_time_data; const int start_value = -8; const int end_value = 8; @@ -42,6 +46,8 @@ TEST_CASE("Support all types", "[select]") { double_data.push_back(i * 987654.321); string_data.push_back(fmt::format("test {}", i)); date_time_data.push_back(DateTime::FromNanos(i)); + local_date_data.push_back(LocalDate::FromMillis(i * 86400 * 1000)); + local_time_data.push_back(LocalTime::FromNanos(1000 + i)); // nanos argument cannot be negative } TableMaker maker; @@ -55,6 +61,8 @@ TEST_CASE("Support all types", "[select]") { maker.AddColumn("doubleData", double_data); maker.AddColumn("stringData", string_data); maker.AddColumn("dateTimeData", date_time_data); + maker.AddColumn("localDateData", local_date_data); + maker.AddColumn("localTimeData", local_time_data); auto t = maker.MakeTable(tm.Client().GetManager()); @@ -71,7 +79,9 @@ TEST_CASE("Support all types", "[select]") { "floatData", float_data, "doubleData", double_data, "stringData", string_data, - "dateTimeData", date_time_data + "dateTimeData", date_time_data, + "localDateData", local_date_data, + "localTimeData", local_time_data ); } diff --git a/cpp-client/deephaven/tests/src/table_test.cc b/cpp-client/deephaven/tests/src/table_test.cc index 364965d2898..f2396697eef 100644 --- a/cpp-client/deephaven/tests/src/table_test.cc +++ b/cpp-client/deephaven/tests/src/table_test.cc @@ -19,6 +19,8 @@ using deephaven::dhcore::chunk::Int32Chunk; using deephaven::dhcore::chunk::Int64Chunk; using deephaven::dhcore::container::RowSequence; using deephaven::dhcore::DateTime; +using deephaven::dhcore::LocalDate; +using deephaven::dhcore::LocalTime; using deephaven::dhcore::DeephavenConstants; using deephaven::dhcore::utility::MakeReservedVector; @@ -38,7 +40,9 @@ TEST_CASE("Fetch the entire table (small)", "[client_table]") { "Doubles = ii == 5 ? null : (double)(ii)", "Bools = ii == 5 ? null : ((ii % 2) == 0)", "Strings = ii == 5 ? null : `hello ` + i", - "DateTimes = ii == 5 ? null : '2001-03-01T12:34:56Z' + ii" + "DateTimes = ii == 5 ? null : '2001-03-01T12:34:56Z' + ii", + "LocalDates = ii == 5 ? null : parseLocalDate(`2001-3-` + (ii + 1))", + "LocalTimes = ii == 5 ? null : parseLocalTime(`12:34:` + (46 + ii))" }); std::cout << th.Stream(true) << '\n'; @@ -55,6 +59,8 @@ TEST_CASE("Fetch the entire table (small)", "[client_table]") { auto bools = MakeReservedVector>(target); auto strings = MakeReservedVector>(target); auto date_times = MakeReservedVector>(target); + auto local_dates = MakeReservedVector>(target); + auto local_times = MakeReservedVector>(target); auto date_time_start = DateTime::Parse("2001-03-01T12:34:56Z"); @@ -69,6 +75,8 @@ TEST_CASE("Fetch the entire table (small)", "[client_table]") { bools.emplace_back((i % 2) == 0); strings.emplace_back(fmt::format("hello {}", i)); date_times.emplace_back(DateTime::FromNanos(date_time_start.Nanos() + i)); + local_dates.emplace_back(LocalDate::Of(2001, 3, i + 1)); + local_times.emplace_back(LocalTime::Of(12, 34, 46 + i)); } auto t2 = target / 2; @@ -84,6 +92,8 @@ TEST_CASE("Fetch the entire table (small)", "[client_table]") { bools[t2] = {}; strings[t2] = {}; date_times[t2] = {}; + local_dates[t2] = {}; + local_times[t2] = {}; CompareColumn(*ct, "Chars", chars); CompareColumn(*ct, "Bytes", int8s); @@ -95,7 +105,7 @@ TEST_CASE("Fetch the entire table (small)", "[client_table]") { CompareColumn(*ct, "Bools", bools); CompareColumn(*ct, "Strings", strings); CompareColumn(*ct, "DateTimes", date_times); - - tm.Client().Close(); + CompareColumn(*ct, "LocalDates", local_dates); + CompareColumn(*ct, "LocalTimes", local_times); } } // namespace deephaven::client::tests