-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[fix](arrow-flight) Return DATETIMEV2 as a timezone-naive Arrow timestamp #65780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,8 +49,8 @@ | |
| namespace doris { | ||
|
|
||
| Status convert_to_arrow_type(const DataTypePtr& origin_type, | ||
| std::shared_ptr<arrow::DataType>* result, | ||
| const std::string& timezone) { | ||
| std::shared_ptr<arrow::DataType>* result, const std::string& timezone, | ||
| bool datetime_naive) { | ||
| auto type = get_serialized_type(origin_type); | ||
| switch (type->get_primitive_type()) { | ||
| case TYPE_NULL: | ||
|
|
@@ -97,17 +97,25 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type, | |
| case TYPE_DATEV2: | ||
| *result = std::make_shared<arrow::Date32Type>(); | ||
| break; | ||
| // TODO: maybe need to distinguish TYPE_DATETIME and TYPE_TIMESTAMPTZ | ||
| case TYPE_TIMESTAMPTZ: | ||
| // TIMESTAMPTZ has instant semantics and always keeps the timezone. DATETIMEV2 is a | ||
| // timezone-naive wall-clock type: only the Arrow Flight result path (datetime_naive) maps | ||
| // it WITHOUT timezone, so ADBC/pyarrow clients do not treat it as an instant and render a | ||
| // spurious "+00:00" (apache/doris#65741). Other callers (Parquet export, including forced | ||
| // INT96, and Python UDF) keep the timezone to preserve their existing round-trip semantics. | ||
| case TYPE_DATETIMEV2: | ||
| case TYPE_TIMESTAMPTZ: { | ||
| const std::string empty_timezone; | ||
| const bool naive = type->get_primitive_type() == TYPE_DATETIMEV2 && datetime_naive; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Handle legacy DATETIME or narrow the option contract The public SessionVariable/Thrift descriptions promise timezone-naive Arrow timestamps for both DATETIME and DATETIMEV2, but this condition only changes |
||
| const std::string& ts_timezone = naive ? empty_timezone : timezone; | ||
| if (type->get_scale() > 3) { | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MICRO, timezone); | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MICRO, ts_timezone); | ||
| } else if (type->get_scale() > 0) { | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MILLI, timezone); | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MILLI, ts_timezone); | ||
| } else { | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::SECOND, timezone); | ||
| *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::SECOND, ts_timezone); | ||
| } | ||
| break; | ||
| } | ||
| case TYPE_DECIMALV2: | ||
| case TYPE_DECIMAL32: | ||
| case TYPE_DECIMAL64: | ||
|
|
@@ -123,16 +131,19 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type, | |
| case TYPE_ARRAY: { | ||
| const auto* type_arr = assert_cast<const DataTypeArray*>(remove_nullable(type).get()); | ||
| std::shared_ptr<arrow::DataType> item_type; | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(), &item_type, timezone)); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(), &item_type, timezone, | ||
| datetime_naive)); | ||
| *result = std::make_shared<arrow::ListType>(item_type); | ||
| break; | ||
| } | ||
| case TYPE_MAP: { | ||
| const auto* type_map = assert_cast<const DataTypeMap*>(remove_nullable(type).get()); | ||
| std::shared_ptr<arrow::DataType> key_type; | ||
| std::shared_ptr<arrow::DataType> val_type; | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(), &key_type, timezone)); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(), &val_type, timezone)); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(), &key_type, timezone, | ||
| datetime_naive)); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(), &val_type, timezone, | ||
| datetime_naive)); | ||
| *result = std::make_shared<arrow::MapType>(key_type, val_type); | ||
| break; | ||
| } | ||
|
|
@@ -141,8 +152,8 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type, | |
| std::vector<std::shared_ptr<arrow::Field>> fields; | ||
| for (size_t i = 0; i < type_struct->get_elements().size(); i++) { | ||
| std::shared_ptr<arrow::DataType> field_type; | ||
| RETURN_IF_ERROR( | ||
| convert_to_arrow_type(type_struct->get_element(i), &field_type, timezone)); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(type_struct->get_element(i), &field_type, | ||
| timezone, datetime_naive)); | ||
| fields.push_back( | ||
| std::make_shared<arrow::Field>(type_struct->get_element_name(i), field_type, | ||
| type_struct->get_element(i)->is_nullable())); | ||
|
|
@@ -206,12 +217,16 @@ Status get_arrow_schema_from_block(const Block& block, std::shared_ptr<arrow::Sc | |
|
|
||
| Status get_arrow_schema_from_expr_ctxs(const VExprContextSPtrs& output_vexpr_ctxs, | ||
| std::shared_ptr<arrow::Schema>* result, | ||
| const std::string& timezone) { | ||
| const std::string& timezone, bool datetime_naive) { | ||
| std::vector<std::shared_ptr<arrow::Field>> fields; | ||
| for (int i = 0; i < output_vexpr_ctxs.size(); i++) { | ||
| std::shared_ptr<arrow::DataType> arrow_type; | ||
| auto root_expr = output_vexpr_ctxs.at(i)->root(); | ||
| RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(), &arrow_type, timezone)); | ||
| // The Arrow Flight result path emits timezone-naive DATETIMEV2 when datetime_naive is set | ||
| // (session variable enable_arrow_flight_datetime_naive), so ADBC clients receive | ||
| // wall-clock values instead of timezone-aware instants (apache/doris#65741). | ||
| RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(), &arrow_type, timezone, | ||
| datetime_naive)); | ||
| auto field_name = root_expr->is_slot_ref() && !root_expr->expr_label().empty() | ||
| ? root_expr->expr_label() | ||
| : fmt::format("{}_{}", root_expr->data_type()->get_name(), i); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Decode aware timestamps in their advertised timezone
This branch still discards non-empty Arrow timezone metadata. A producer session in UTC writes
2026-07-02 01:36:22as raw1782956182withtz=UTC, but both Remote Doris readers pass the hard-coded+08:00, so this selectsctzand materializes09:36:22. This is distinct from the existing old-reader/naive compatibility thread: it reproduces with the default aware encoding and current code on both ends. Please resolvetype->timezone()for the aware case and add a producer/reader-timezone mismatch test.