This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 193c0ca652f [be](serde) support more arrow::Type in
read_column_from_arrow (#66489)
193c0ca652f is described below
commit 193c0ca652f25f3dfe276a22a3aedbbdef01cd8e
Author: zhangstar333 <[email protected]>
AuthorDate: Fri Aug 7 10:57:02 2026 +0800
[be](serde) support more arrow::Type in read_column_from_arrow (#66489)
### What problem does this PR solve?
Problem Summary:
- Support Arrow `LargeList` and `FixedSizeList` for Doris ARRAY.
- Support `Binary`, `LargeBinary`, and `FixedSizeBinary` for VARBINARY.
- Support additional Arrow numeric and temporal types, including
Float16, unsigned integers, Date64, TIME, and TIMESTAMPTZ.
- Improve Arrow offset, range, type, and temporal-unit validation.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/src/core/data_type_serde/arrow_validation.h | 44 +++-
.../core/data_type_serde/data_type_array_serde.cpp | 84 +++++--
.../data_type_serde/data_type_datetimev2_serde.cpp | 5 +-
.../data_type_serde/data_type_datev2_serde.cpp | 62 +++++-
.../data_type_serde/data_type_number_serde.cpp | 70 +++++-
.../core/data_type_serde/data_type_time_serde.cpp | 94 ++++++++
be/src/core/data_type_serde/data_type_time_serde.h | 3 +
.../data_type_timestamptz_serde.cpp | 5 +-
.../data_type_serde/data_type_varbinary_serde.cpp | 83 +++++++
.../data_type_serde/data_type_varbinary_serde.h | 5 +-
.../data_type_serde/data_type_serde_array_test.cpp | 118 ++++++++++
.../data_type_serde_arrow_validation_test.cpp | 244 ++++++++++++++++++++-
.../data_type_serde_datetime_v2_test.cpp | 137 ++++++++++++
.../data_type_serde_number_test.cpp | 124 +++++++++++
.../data_type_serde_timestamptz_test.cpp | 36 +++
.../data_type_serde_varbinary_test.cpp | 52 ++++-
16 files changed, 1112 insertions(+), 54 deletions(-)
diff --git a/be/src/core/data_type_serde/arrow_validation.h
b/be/src/core/data_type_serde/arrow_validation.h
index 9d8b09b0cf1..f7381250277 100644
--- a/be/src/core/data_type_serde/arrow_validation.h
+++ b/be/src/core/data_type_serde/arrow_validation.h
@@ -93,6 +93,15 @@ inline std::shared_ptr<arrow::Int32Array>
get_int32_offsets_array(const arrow::A
return offsets;
}
+inline std::shared_ptr<arrow::Int64Array> get_int64_offsets_array(const
arrow::Array& array) {
+ auto offsets_array = static_cast<const
arrow::LargeListArray&>(array).offsets();
+ auto offsets = std::dynamic_pointer_cast<arrow::Int64Array>(offsets_array);
+ if (UNLIKELY(!offsets)) {
+ throw_invalid_arrow(array, "offsets array is not Int64Array");
+ }
+ return offsets;
+}
+
} // namespace arrow_validation_detail
inline void check_arrow_no_offset(const arrow::Array& array) {
@@ -210,31 +219,33 @@ inline void check_arrow_value_range(const arrow::Array&
array, int64_t offset, i
namespace arrow_validation_detail {
// Offsets buffers may come from external Arrow producers through Buffer::Wrap
or FFI and are not
-// guaranteed to be aligned to int32_t. Do not use Int32Array::Value() here
because it performs a
+// guaranteed to be naturally aligned. Do not use NumericArray::Value() here
because it performs a
// typed raw_values()[i] load and can trigger UBSan on misaligned buffers.
Keep this validation path
// consistent with the array/map readers below, which load offsets through
unaligned_load().
-inline int32_t read_int32_offset(const arrow::Int32Array& offsets, int64_t
index) {
+template <typename OffsetType, typename ArrowOffsetArray>
+inline OffsetType read_offset(const ArrowOffsetArray& offsets, int64_t index) {
const auto* data = reinterpret_cast<const uint8_t*>(offsets.raw_values());
- return unaligned_load<int32_t>(data + index * sizeof(int32_t));
+ return unaligned_load<OffsetType>(data + index * sizeof(OffsetType));
}
-inline int64_t check_arrow_offsets_range(const arrow::Int32Array& offsets,
int64_t start,
+template <typename OffsetType, typename ArrowOffsetArray>
+inline int64_t check_arrow_offsets_range(const ArrowOffsetArray& offsets,
int64_t start,
int64_t end) {
check_arrow_array_range(offsets, 0, offsets.length());
- check_arrow_fixed_width_buffer(offsets, sizeof(int32_t));
+ check_arrow_fixed_width_buffer(offsets, sizeof(OffsetType));
if (UNLIKELY(start < 0 || end < start || end >= offsets.length())) {
arrow_validation_detail::throw_invalid_arrow(
offsets, "offsets read range is invalid: start={}, end={},
offsets_length={}",
start, end, offsets.length());
}
- int64_t previous_offset = read_int32_offset(offsets, start);
+ int64_t previous_offset = read_offset<OffsetType>(offsets, start);
if (UNLIKELY(previous_offset < 0)) {
arrow_validation_detail::throw_invalid_arrow(
offsets, "offsets contain negative value: offset[{}]={}",
start, previous_offset);
}
for (int64_t i = start + 1; i <= end; ++i) {
- const int64_t current_offset = read_int32_offset(offsets, i);
+ const int64_t current_offset = read_offset<OffsetType>(offsets, i);
if (UNLIKELY(current_offset < previous_offset)) {
arrow_validation_detail::throw_invalid_arrow(
offsets,
@@ -253,7 +264,22 @@ inline void check_arrow_list_offsets(const
arrow::ListArray& array, int64_t star
check_arrow_array_range(array, start, end);
const auto offsets =
arrow_validation_detail::get_int32_offsets_array(array);
const int64_t last_offset =
- arrow_validation_detail::check_arrow_offsets_range(*offsets,
start, end);
+
arrow_validation_detail::check_arrow_offsets_range<int32_t>(*offsets, start,
end);
+ const int64_t values_length = array.values() ? array.values()->length() :
0;
+ if (UNLIKELY(last_offset > values_length)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "offsets exceed values length: last_offset={},
values_length={}",
+ last_offset, values_length);
+ }
+}
+
+// Validate LargeList offsets before reading offsets or recursing into values.
+inline void check_arrow_large_list_offsets(const arrow::LargeListArray& array,
int64_t start,
+ int64_t end) {
+ check_arrow_array_range(array, start, end);
+ const auto offsets =
arrow_validation_detail::get_int64_offsets_array(array);
+ const int64_t last_offset =
+
arrow_validation_detail::check_arrow_offsets_range<int64_t>(*offsets, start,
end);
const int64_t values_length = array.values() ? array.values()->length() :
0;
if (UNLIKELY(last_offset > values_length)) {
arrow_validation_detail::throw_invalid_arrow(
@@ -267,7 +293,7 @@ inline void check_arrow_map_offsets(const arrow::MapArray&
array, int64_t start,
check_arrow_array_range(array, start, end);
const auto offsets =
arrow_validation_detail::get_int32_offsets_array(array);
const int64_t last_offset =
- arrow_validation_detail::check_arrow_offsets_range(*offsets,
start, end);
+
arrow_validation_detail::check_arrow_offsets_range<int32_t>(*offsets, start,
end);
const int64_t keys_length = array.keys() ? array.keys()->length() : 0;
if (UNLIKELY(last_offset > keys_length)) {
arrow_validation_detail::throw_invalid_arrow(
diff --git a/be/src/core/data_type_serde/data_type_array_serde.cpp
b/be/src/core/data_type_serde/data_type_array_serde.cpp
index bfab0f1eddb..6c5a0927e6a 100644
--- a/be/src/core/data_type_serde/data_type_array_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_array_serde.cpp
@@ -325,29 +325,67 @@ Status
DataTypeArraySerDe::read_column_from_arrow(IColumn& column, const arrow::
const cctz::time_zone& ctz)
const {
auto& column_array = static_cast<ColumnArray&>(column);
auto& offsets_data = column_array.get_offsets();
- const auto* concrete_array = dynamic_cast<const
arrow::ListArray*>(arrow_array);
- auto arrow_offsets_array = concrete_array->offsets();
- auto* arrow_offsets =
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
- if (config::enable_arrow_input_validation) {
- check_arrow_list_offsets(*concrete_array, start, end);
- }
- auto prev_size = offsets_data.back();
- const auto* base_offsets_ptr = reinterpret_cast<const
uint8_t*>(arrow_offsets->raw_values());
- const size_t offset_element_size = sizeof(int32_t);
- const uint8_t* start_offset_ptr = base_offsets_ptr + start *
offset_element_size;
- const uint8_t* end_offset_ptr = base_offsets_ptr + end *
offset_element_size;
- auto arrow_nested_start_offset = unaligned_load<int32_t>(start_offset_ptr);
- auto arrow_nested_end_offset = unaligned_load<int32_t>(end_offset_ptr);
-
- for (auto i = start + 1; i < end + 1; ++i) {
- const uint8_t* current_offset_ptr = base_offsets_ptr + i *
offset_element_size;
- auto current_offset = unaligned_load<int32_t>(current_offset_ptr);
- // convert to doris offset, start from offsets.back()
- offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
- }
- return nested_serde->read_column_from_arrow(
- column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
- arrow_nested_end_offset, ctz);
+ const auto read_list = [&](const auto* concrete_array, const auto&
read_offset) -> Status {
+ const int64_t arrow_nested_start_offset = read_offset(start);
+ const int64_t arrow_nested_end_offset = read_offset(end);
+ const auto prev_size = offsets_data.back();
+ for (int64_t i = start + 1; i <= end; ++i) {
+ // Convert Arrow offsets to Doris offsets, starting at
offsets.back().
+ offsets_data.emplace_back(prev_size + read_offset(i) -
arrow_nested_start_offset);
+ }
+ return nested_serde->read_column_from_arrow(
+ column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
+ arrow_nested_end_offset, ctz);
+ };
+
+ switch (arrow_array->type_id()) {
+ case arrow::Type::LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::ListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow ListArray, got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_list_offsets(*concrete_array, start, end);
+ }
+ const auto* offsets = concrete_array->value_offsets()->data();
+ const auto array_offset = concrete_array->offset();
+ return read_list(concrete_array, [offsets, array_offset](int64_t
index) {
+ return unaligned_load<int32_t>(offsets + (array_offset + index) *
sizeof(int32_t));
+ });
+ }
+ case arrow::Type::LARGE_LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::LargeListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow LargeListArray, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_large_list_offsets(*concrete_array, start, end);
+ }
+ const auto* offsets = concrete_array->value_offsets()->data();
+ const auto array_offset = concrete_array->offset();
+ return read_list(concrete_array, [offsets, array_offset](int64_t
index) {
+ return unaligned_load<int64_t>(offsets + (array_offset + index) *
sizeof(int64_t));
+ });
+ }
+ case arrow::Type::FIXED_SIZE_LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::FixedSizeListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow FixedSizeListArray,
got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ }
+ return read_list(concrete_array, [concrete_array](int64_t index) {
+ return concrete_array->value_offset(index);
+ });
+ }
+ default:
+ return Status::InvalidArgument("Unsupported Arrow array type for Doris
ARRAY: {}",
+ arrow_array->type()->name());
+ }
}
Status DataTypeArraySerDe::write_column_to_mysql_binary(const IColumn& column,
diff --git a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
index 78370a858c9..a4b11788986 100644
--- a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
@@ -665,6 +665,9 @@ Status
DataTypeDateTimeV2SerDe::read_column_from_arrow(IColumn& column,
type->unit());
}
}
+ // A timezone-naive Arrow timestamp is a wall-clock value. Decode its
epoch-based
+ // representation in UTC so the session timezone does not shift its
date/time fields.
+ const cctz::time_zone& real_ctz = type->timezone().empty() ?
cctz::utc_time_zone() : ctz;
const auto* base_ptr = reinterpret_cast<const
uint8_t*>(concrete_array->raw_values());
const size_t element_size = sizeof(int64_t);
for (auto value_i = start; value_i < end; ++value_i) {
@@ -681,7 +684,7 @@ Status
DataTypeDateTimeV2SerDe::read_column_from_arrow(IColumn& column,
--seconds;
remainder += divisor;
}
- v.from_unixtime(seconds, ctz);
+ v.from_unixtime(seconds, real_ctz);
// Get the fractional part.
// add 0 on the right to make it 6 digits. DateTimeV2Value
microsecond is 6 digits,
// the scale decides to keep the first few digits, so the valid
digits should be kept at the front.
diff --git a/be/src/core/data_type_serde/data_type_datev2_serde.cpp
b/be/src/core/data_type_serde/data_type_datev2_serde.cpp
index be9c841d233..02059045469 100644
--- a/be/src/core/data_type_serde/data_type_datev2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_datev2_serde.cpp
@@ -235,16 +235,58 @@ Status
DataTypeDateV2SerDe::read_column_from_arrow(IColumn& column, const arrow:
check_arrow_no_offset(*arrow_array);
}
auto& col_data = static_cast<ColumnDateV2&>(column).get_data();
- const auto* concrete_array = dynamic_cast<const
arrow::Date32Array*>(arrow_array);
- const auto* base_ptr = reinterpret_cast<const
uint8_t*>(concrete_array->raw_values());
- const size_t element_size = sizeof(int32_t);
- for (auto value_i = start; value_i < end; ++value_i) {
- const uint8_t* raw_byte_ptr = base_ptr + value_i * element_size;
- auto date_value = unaligned_load<int32_t>(raw_byte_ptr);
-
- DateV2Value<DateV2ValueType> v;
- v.get_date_from_daynr(date_value + date_threshold);
- col_data.emplace_back(v);
+ if (arrow_array->type_id() == arrow::Type::DATE64) {
+ static constexpr int64_t MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
+ const auto* concrete_array = dynamic_cast<const
arrow::Date64Array*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow Date64Array, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ check_arrow_fixed_width_buffer(*concrete_array,
sizeof(arrow::Date64Array::value_type));
+ }
+ for (int64_t value_i = start; value_i < end; ++value_i) {
+ if (concrete_array->IsNull(value_i)) {
+ col_data.emplace_back(DateV2Value<DateV2ValueType>());
+ continue;
+ }
+ const int64_t milliseconds = concrete_array->Value(value_i);
+ if (milliseconds % MILLISECONDS_PER_DAY != 0) {
+ return Status::InvalidArgument(
+ "Arrow Date64 value must contain whole days: row={},
milliseconds={}",
+ value_i, milliseconds);
+ }
+ const int64_t daynr = milliseconds / MILLISECONDS_PER_DAY +
date_threshold;
+ DateV2Value<DateV2ValueType> value;
+ if (daynr <= 0 || daynr > DATE_MAX_DAYNR ||
+ !value.get_date_from_daynr(static_cast<uint64_t>(daynr))) {
+ return Status::InvalidArgument(
+ "Arrow Date64 value is outside the Doris DATE range: "
+ "row={}, milliseconds={}",
+ value_i, milliseconds);
+ }
+ col_data.emplace_back(value);
+ }
+ } else if (arrow_array->type_id() == arrow::Type::DATE32) {
+ const auto* concrete_array = dynamic_cast<const
arrow::Date32Array*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow Date32Array, got
{}",
+ arrow_array->type()->name());
+ }
+ const auto* base_ptr = reinterpret_cast<const
uint8_t*>(concrete_array->raw_values());
+ const size_t element_size = sizeof(int32_t);
+ for (auto value_i = start; value_i < end; ++value_i) {
+ const uint8_t* raw_byte_ptr = base_ptr + value_i * element_size;
+ auto date_value = unaligned_load<int32_t>(raw_byte_ptr);
+
+ DateV2Value<DateV2ValueType> v;
+ v.get_date_from_daynr(date_value + date_threshold);
+ col_data.emplace_back(v);
+ }
+ } else {
+ return Status::InvalidArgument("Expected Arrow Date32Array or
Date64Array, got {}",
+ arrow_array->type()->name());
}
return Status::OK();
}
diff --git a/be/src/core/data_type_serde/data_type_number_serde.cpp
b/be/src/core/data_type_serde/data_type_number_serde.cpp
index ca30ec96d25..a1a4a3df65d 100644
--- a/be/src/core/data_type_serde/data_type_number_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_number_serde.cpp
@@ -18,6 +18,7 @@
#include "core/data_type_serde/data_type_number_serde.h"
#include <arrow/builder.h>
+#include <arrow/util/float16.h>
#include <bit>
#include <cmath>
@@ -45,8 +46,6 @@
#include "exprs/function/cast/cast_to_basic_number_common.h"
#include "exprs/function/cast/cast_to_boolean.h"
#include "exprs/function/cast/cast_to_string.h"
-#include "storage/olap_common.h"
-#include "storage/types.h"
#include "util/jsonb_document.h"
#include "util/jsonb_document_cast.h"
#include "util/jsonb_writer.h"
@@ -342,6 +341,28 @@ Status read_integer_decoded_values(IColumn& column, const
DecodedColumnView& vie
}
}
+template <PrimitiveType DorisType, typename ArrowArrayType>
+Status read_widened_arrow_integer_values(IColumn& column, const arrow::Array*
arrow_array,
+ int64_t start, int64_t end) {
+ const auto* source = dynamic_cast<const ArrowArrayType*>(arrow_array);
+ if (source == nullptr) {
+ return Status::InvalidArgument("Expected a compatible Arrow numeric
array for {}, got {}",
+ column.get_name(),
arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*source, sizeof(typename
ArrowArrayType::value_type));
+ }
+
+ auto& data =
+ assert_cast<typename
PrimitiveTypeTraits<DorisType>::ColumnType&>(column).get_data();
+ for (int64_t row = start; row < end; ++row) {
+ using DorisCppType = typename PrimitiveTypeTraits<DorisType>::CppType;
+ data.push_back(source->IsNull(row) ? DorisCppType()
+ :
static_cast<DorisCppType>(source->Value(row)));
+ }
+ return Status::OK();
+}
+
template <typename DorisCppType, typename SourceType>
Status append_parquet_number(PaddedPODArray<DorisCppType>& data, const
uint8_t* values,
size_t num_values, const ParquetDecodeContext&
context,
@@ -933,6 +954,51 @@ Status
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
return Status::OK();
}
+ if constexpr (T == TYPE_FLOAT) {
+ if (arrow_array->type_id() == arrow::Type::HALF_FLOAT) {
+ const auto* concrete_array = dynamic_cast<const
arrow::HalfFloatArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow HalfFloatArray,
got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*concrete_array,
+
sizeof(arrow::HalfFloatArray::value_type));
+ }
+ for (int64_t i = start; i < end; ++i) {
+ const auto value =
+ concrete_array->IsNull(i)
+ ? 0.0F
+ :
arrow::util::Float16::FromBits(concrete_array->Value(i))
+ .ToFloat();
+ col_data.emplace_back(value);
+ }
+ return Status::OK();
+ }
+ }
+
+ if constexpr (T == TYPE_SMALLINT) {
+ if (arrow_array->type_id() == arrow::Type::UINT8) {
+ return read_widened_arrow_integer_values<TYPE_SMALLINT,
arrow::UInt8Array>(
+ column, arrow_array, start, end);
+ }
+ } else if constexpr (T == TYPE_INT) {
+ if (arrow_array->type_id() == arrow::Type::UINT16) {
+ return read_widened_arrow_integer_values<TYPE_INT,
arrow::UInt16Array>(
+ column, arrow_array, start, end);
+ }
+ } else if constexpr (T == TYPE_BIGINT) {
+ if (arrow_array->type_id() == arrow::Type::UINT32) {
+ return read_widened_arrow_integer_values<TYPE_BIGINT,
arrow::UInt32Array>(
+ column, arrow_array, start, end);
+ }
+ } else if constexpr (T == TYPE_LARGEINT) {
+ if (arrow_array->type_id() == arrow::Type::UINT64) {
+ return read_widened_arrow_integer_values<TYPE_LARGEINT,
arrow::UInt64Array>(
+ column, arrow_array, start, end);
+ }
+ }
+
// only for largeint(int128) type
if (arrow_array->type_id() == arrow::Type::STRING) {
const auto* concrete_array = dynamic_cast<const
arrow::StringArray*>(arrow_array);
diff --git a/be/src/core/data_type_serde/data_type_time_serde.cpp
b/be/src/core/data_type_serde/data_type_time_serde.cpp
index 9beb9792244..566bd3982b4 100644
--- a/be/src/core/data_type_serde/data_type_time_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_time_serde.cpp
@@ -17,11 +17,16 @@
#include "core/data_type_serde/data_type_time_serde.h"
+#include <arrow/array.h>
+#include <arrow/type.h>
+
#include <limits>
+#include "common/config.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/decoded_column_view.h"
#include "core/data_type_serde/parquet_decode_source.h"
#include "core/value/time_value.h"
@@ -281,6 +286,95 @@ Status
DataTypeTimeV2SerDe::from_string_strict_mode(StringRef& str, IColumn& col
return Status::OK();
}
+Status DataTypeTimeV2SerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
+ int64_t start, int64_t end,
+ const cctz::time_zone&)
const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*arrow_array, start, end);
+ }
+ auto& data = assert_cast<ColumnTimeV2&>(column).get_data();
+ if (arrow_array->type_id() == arrow::Type::TIME32) {
+ const auto* time_array = dynamic_cast<const
arrow::Time32Array*>(arrow_array);
+ if (time_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow Time32Array, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*time_array,
sizeof(arrow::Time32Array::value_type));
+ }
+ const auto type =
std::static_pointer_cast<arrow::Time32Type>(arrow_array->type());
+ int64_t micros_per_unit = 0;
+ int64_t units_per_day = 0;
+ switch (type->unit()) {
+ case arrow::TimeUnit::SECOND:
+ micros_per_unit = TimeValue::ONE_SECOND_MICROSECONDS;
+ units_per_day = 24 * 60 * 60;
+ break;
+ case arrow::TimeUnit::MILLI:
+ micros_per_unit = 1000;
+ units_per_day = 24 * 60 * 60 * 1000;
+ break;
+ default:
+ return Status::InvalidArgument("Unsupported Arrow Time32 unit:
{}", type->unit());
+ }
+ for (int64_t row = start; row < end; ++row) {
+ if (time_array->IsNull(row)) {
+ data.emplace_back(0);
+ continue;
+ }
+ const int64_t value = time_array->Value(row);
+ if (value < 0 || value >= units_per_day) {
+ return Status::InvalidArgument(
+ "Arrow Time32 value is outside the time-of-day range:
row={}, value={}",
+ row, value);
+ }
+ data.emplace_back(static_cast<TimeValue::TimeType>(value *
micros_per_unit));
+ }
+ return Status::OK();
+ }
+
+ if (arrow_array->type_id() == arrow::Type::TIME64) {
+ const auto* time_array = dynamic_cast<const
arrow::Time64Array*>(arrow_array);
+ if (time_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow Time64Array, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*time_array,
sizeof(arrow::Time64Array::value_type));
+ }
+ const auto type =
std::static_pointer_cast<arrow::Time64Type>(arrow_array->type());
+ int64_t units_per_day = 0;
+ switch (type->unit()) {
+ case arrow::TimeUnit::MICRO:
+ units_per_day = 24LL * 60 * 60 *
TimeValue::ONE_SECOND_MICROSECONDS;
+ break;
+ case arrow::TimeUnit::NANO:
+ units_per_day = 24LL * 60 * 60 * 1000 *
TimeValue::ONE_SECOND_MICROSECONDS;
+ break;
+ default:
+ return Status::InvalidArgument("Unsupported Arrow Time64 unit:
{}", type->unit());
+ }
+ for (int64_t row = start; row < end; ++row) {
+ if (time_array->IsNull(row)) {
+ data.emplace_back(0);
+ continue;
+ }
+ const int64_t value = time_array->Value(row);
+ if (value < 0 || value >= units_per_day) {
+ return Status::InvalidArgument(
+ "Arrow Time64 value is outside the time-of-day range:
row={}, value={}",
+ row, value);
+ }
+ const int64_t micros = type->unit() == arrow::TimeUnit::NANO ?
value / 1000 : value;
+ data.emplace_back(static_cast<TimeValue::TimeType>(micros));
+ }
+ return Status::OK();
+ }
+
+ return Status::InvalidArgument("Expected Arrow Time32Array or Time64Array,
got {}",
+ arrow_array->type()->name());
+}
+
Status DataTypeTimeV2SerDe::read_column_from_decoded_values(IColumn& column,
const
DecodedColumnView& view) const {
if (view.value_kind != DecodedValueKind::INT32 && view.value_kind !=
DecodedValueKind::INT64) {
diff --git a/be/src/core/data_type_serde/data_type_time_serde.h
b/be/src/core/data_type_serde/data_type_time_serde.h
index 90ebe128672..39c6bb69d84 100644
--- a/be/src/core/data_type_serde/data_type_time_serde.h
+++ b/be/src/core/data_type_serde/data_type_time_serde.h
@@ -47,6 +47,9 @@ public:
const FormatOptions& options,
const NullMap::value_type* null_map =
nullptr) const final;
+ Status read_column_from_arrow(IColumn& column, const arrow::Array*
arrow_array, int64_t start,
+ int64_t end, const cctz::time_zone& ctz)
const override;
+
template <typename IntDataType>
Status from_int_batch(const typename IntDataType::ColumnType& int_col,
ColumnNullable& target_col) const;
diff --git a/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
b/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
index 1e84fef5b05..d83bc0745d9 100644
--- a/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
@@ -417,7 +417,7 @@ Status
DataTypeTimeStampTzSerDe::read_column_from_arrow(IColumn& column,
int64_t start, int64_t
end,
const cctz::time_zone&
ctz) const {
if (config::enable_arrow_input_validation) {
- check_arrow_no_offset(*arrow_array);
+ check_arrow_array_range(*arrow_array, start, end);
}
if (arrow_array->type()->id() != arrow::Type::TIMESTAMP) {
LOG(WARNING) << "not support convert to timestamptz from arrow type:"
@@ -426,6 +426,9 @@ Status
DataTypeTimeStampTzSerDe::read_column_from_arrow(IColumn& column,
arrow_array->type()->id());
}
const auto* concrete_array = assert_cast<const
arrow::TimestampArray*>(arrow_array);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*concrete_array,
sizeof(arrow::TimestampArray::value_type));
+ }
const auto type =
std::static_pointer_cast<arrow::TimestampType>(arrow_array->type());
// Scale each unit to the microseconds the column stores. NANO is divided
rather than refused:
// sub-microsecond precision is beyond what any Doris datetime type keeps,
and rejecting the
diff --git a/be/src/core/data_type_serde/data_type_varbinary_serde.cpp
b/be/src/core/data_type_serde/data_type_varbinary_serde.cpp
index 14d5edda0af..16ff1b20e8a 100644
--- a/be/src/core/data_type_serde/data_type_varbinary_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_varbinary_serde.cpp
@@ -17,7 +17,11 @@
#include "core/data_type_serde/data_type_varbinary_serde.h"
+#include <cstring>
+
+#include "common/config.h"
#include "core/column/column_varbinary.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/parquet_decode_source.h"
namespace doris {
@@ -184,6 +188,85 @@ Status DataTypeVarbinarySerDe::write_column_to_arrow(const
IColumn& column, cons
return Status::OK();
}
+Status DataTypeVarbinarySerDe::read_column_from_arrow(IColumn& column,
+ const arrow::Array*
arrow_array,
+ int64_t start, int64_t
end,
+ const cctz::time_zone&
ctz) const {
+ auto& varbinary_column = assert_cast<ColumnVarbinary&>(column);
+ if (arrow_array->type_id() == arrow::Type::STRING ||
+ arrow_array->type_id() == arrow::Type::BINARY) {
+ const auto* concrete_array = assert_cast<const
arrow::BinaryArray*>(arrow_array);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ check_arrow_binary_offsets_buffer(*concrete_array);
+ }
+ const auto& buffer = concrete_array->value_data();
+ const size_t buffer_size = buffer ?
static_cast<size_t>(buffer->size()) : 0;
+ const uint8_t* offsets_data = concrete_array->value_offsets()->data();
+ constexpr size_t offset_size = sizeof(int32_t);
+
+ for (auto offset_i = start; offset_i < end; ++offset_i) {
+ if (!concrete_array->IsNull(offset_i)) {
+ int32_t start_offset = 0;
+ int32_t end_offset = 0;
+ memcpy(&start_offset, offsets_data + offset_i * offset_size,
offset_size);
+ memcpy(&end_offset, offsets_data + (offset_i + 1) *
offset_size, offset_size);
+ const int32_t length = end_offset - start_offset;
+ if (config::enable_arrow_input_validation) {
+ check_arrow_value_range(*concrete_array, start_offset,
length, buffer_size);
+ }
+ varbinary_column.insert_data(
+ reinterpret_cast<const char*>(buffer->data() +
start_offset), length);
+ } else {
+ varbinary_column.insert_default();
+ }
+ }
+ } else if (arrow_array->type_id() == arrow::Type::FIXED_SIZE_BINARY) {
+ const auto* concrete_array = assert_cast<const
arrow::FixedSizeBinaryArray*>(arrow_array);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ check_arrow_fixed_width_buffer(*concrete_array,
+
static_cast<size_t>(concrete_array->byte_width()));
+ }
+ const uint32_t width = concrete_array->byte_width();
+ for (auto offset_i = start; offset_i < end; ++offset_i) {
+ if (!concrete_array->IsNull(offset_i)) {
+ varbinary_column.insert_data(
+ reinterpret_cast<const
char*>(concrete_array->GetValue(offset_i)), width);
+ } else {
+ varbinary_column.insert_default();
+ }
+ }
+ } else if (arrow_array->type_id() == arrow::Type::LARGE_STRING ||
+ arrow_array->type_id() == arrow::Type::LARGE_BINARY) {
+ const auto* concrete_array = assert_cast<const
arrow::LargeBinaryArray*>(arrow_array);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ check_arrow_binary_offsets_buffer(*concrete_array);
+ }
+ const auto& buffer = concrete_array->value_data();
+ const size_t buffer_size = buffer ?
static_cast<size_t>(buffer->size()) : 0;
+ for (auto offset_i = start; offset_i < end; ++offset_i) {
+ if (!concrete_array->IsNull(offset_i)) {
+ const auto value_offset =
concrete_array->value_offset(offset_i);
+ const auto value_length =
concrete_array->value_length(offset_i);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_value_range(*concrete_array, value_offset,
value_length,
+ buffer_size);
+ }
+ varbinary_column.insert_data(
+ reinterpret_cast<const char*>(buffer->data() +
value_offset), value_length);
+ } else {
+ varbinary_column.insert_default();
+ }
+ }
+ } else {
+ return Status::InvalidArgument("Unsupported arrow type for varbinary
column: {}",
+ arrow_array->type()->name());
+ }
+ return Status::OK();
+}
+
Status DataTypeVarbinarySerDe::write_column_to_orc(const std::string& timezone,
const IColumn& column,
const NullMap* null_map,
orc::ColumnVectorBatch*
orc_col_batch,
diff --git a/be/src/core/data_type_serde/data_type_varbinary_serde.h
b/be/src/core/data_type_serde/data_type_varbinary_serde.h
index 6d23159c8ea..b4f5f26eab5 100644
--- a/be/src/core/data_type_serde/data_type_varbinary_serde.h
+++ b/be/src/core/data_type_serde/data_type_varbinary_serde.h
@@ -76,10 +76,7 @@ public:
const cctz::time_zone& ctz) const override;
Status read_column_from_arrow(IColumn& column, const arrow::Array*
arrow_array, int64_t start,
- int64_t end, const cctz::time_zone& ctz)
const override {
- return Status::Error(ErrorCode::NOT_IMPLEMENTED_ERROR,
- "read_column_from_arrow with type " +
column.get_name());
- }
+ int64_t end, const cctz::time_zone& ctz)
const override;
Status read_column_from_parquet(IColumn& column, ParquetDecodeSource&
source,
const ParquetDecodeContext& context,
size_t num_values,
diff --git a/be/test/core/data_type_serde/data_type_serde_array_test.cpp
b/be/test/core/data_type_serde/data_type_serde_array_test.cpp
index f6b4a666f96..3478f8292d4 100644
--- a/be/test/core/data_type_serde/data_type_serde_array_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_array_test.cpp
@@ -15,12 +15,21 @@
// specific language governing permissions and limitations
// under the License.
+#include <arrow/api.h>
+#include <cctz/time_zone.h>
#include <gtest/gtest.h>
+#include <array>
#include <cstring>
+#include <memory>
+#include "core/assert_cast.h"
+#include "core/column/column_array.h"
+#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
#include "core/column/column_vector.h"
+#include "core/data_type_serde/data_type_array_serde.h"
+#include "core/data_type_serde/data_type_nullable_serde.h"
#include "core/data_type_serde/data_type_number_serde.h"
#include "core/data_type_serde/data_type_serde.h"
#include "core/data_type_serde/data_type_string_serde.h"
@@ -175,4 +184,113 @@ TEST_F(DataTypeArraySerDeFieldTest,
empty_array_is_null_element_type) {
EXPECT_FALSE(info.need_convert);
}
+TEST_F(DataTypeArraySerDeFieldTest, ReadArrowFixedSizeAndLargeList) {
+ auto nested_serde = std::make_shared<DataTypeNullableSerDe>(
+ std::make_shared<DataTypeNumberSerDe<TYPE_FLOAT>>());
+ DataTypeArraySerDe serde(nested_serde);
+ cctz::time_zone tz;
+
+ const auto expect_array = [](const ColumnArray& column,
+ const std::vector<ColumnArray::Offset64>&
expected_offsets,
+ const std::vector<float>& expected_values) {
+ const auto& offsets = column.get_offsets();
+ ASSERT_EQ(expected_offsets.size(), offsets.size());
+ for (size_t i = 0; i < expected_offsets.size(); ++i) {
+ EXPECT_EQ(expected_offsets[i], offsets[i]);
+ }
+ const auto& nullable_values = assert_cast<const
ColumnNullable&>(column.get_data());
+ const auto& values =
+ assert_cast<const
ColumnFloat32&>(nullable_values.get_nested_column()).get_data();
+ ASSERT_EQ(expected_values.size(), values.size());
+ for (size_t i = 0; i < expected_values.size(); ++i) {
+ EXPECT_EQ(0, nullable_values.get_null_map_data()[i]);
+ EXPECT_FLOAT_EQ(expected_values[i], values[i]);
+ }
+ };
+
+ // Lance vectors are Arrow FixedSizeList: every embedding has exactly
three floats.
+ {
+ auto value_builder = std::make_shared<arrow::FloatBuilder>();
+ arrow::FixedSizeListBuilder builder(arrow::default_memory_pool(),
value_builder, 3);
+ for (const std::array<float, 3>& embedding :
+ {std::array<float, 3> {0.0F, 0.0F, 0.0F}, std::array<float, 3>
{1.0F, 0.0F, 0.0F},
+ std::array<float, 3> {-1.5F, 0.25F, 3.75F}}) {
+ ASSERT_TRUE(builder.Append().ok());
+ for (float value : embedding) {
+ ASSERT_TRUE(value_builder->Append(value).ok());
+ }
+ }
+ std::shared_ptr<arrow::FixedSizeListArray> arrow_array;
+ ASSERT_TRUE(builder.Finish(&arrow_array).ok());
+
+ auto column = ColumnArray::create(
+ ColumnNullable::create(ColumnFloat32::create(),
ColumnUInt8::create()),
+ ColumnOffset64::create());
+ ASSERT_TRUE(serde.read_column_from_arrow(*column, arrow_array.get(), 0,
+ arrow_array->length(), tz)
+ .ok());
+ expect_array(*column, {3, 6, 9}, {0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
-1.5F, 0.25F, 3.75F});
+ }
+
+ // LargeList uses 64-bit Arrow offsets but has the same Doris ARRAY
representation.
+ {
+ auto value_builder = std::make_shared<arrow::FloatBuilder>();
+ arrow::LargeListBuilder builder(arrow::default_memory_pool(),
value_builder);
+ ASSERT_TRUE(builder.Append().ok());
+ ASSERT_TRUE(value_builder->Append(1.0F).ok());
+ ASSERT_TRUE(value_builder->Append(2.0F).ok());
+ ASSERT_TRUE(builder.Append().ok());
+ ASSERT_TRUE(value_builder->Append(3.0F).ok());
+ ASSERT_TRUE(builder.Append().ok());
+ std::shared_ptr<arrow::LargeListArray> arrow_array;
+ ASSERT_TRUE(builder.Finish(&arrow_array).ok());
+
+ auto column = ColumnArray::create(
+ ColumnNullable::create(ColumnFloat32::create(),
ColumnUInt8::create()),
+ ColumnOffset64::create());
+ ASSERT_TRUE(serde.read_column_from_arrow(*column, arrow_array.get(), 0,
+ arrow_array->length(), tz)
+ .ok());
+ expect_array(*column, {2, 3, 3}, {1.0F, 2.0F, 3.0F});
+ }
+}
+
+// External Arrow producers may expose offsets through Buffer::Wrap without
preserving the natural
+// alignment of int32_t or int64_t. Run with UBSan enabled to catch typed
loads from such buffers.
+TEST_F(DataTypeArraySerDeFieldTest, ReadArrowListWithUnalignedOffsets) {
+ auto nested_serde = std::make_shared<DataTypeNullableSerDe>(
+ std::make_shared<DataTypeNumberSerDe<TYPE_FLOAT>>());
+ DataTypeArraySerDe serde(nested_serde);
+
+ std::vector<float> values_data {1.0F, 2.0F, 3.0F};
+ const auto values_buffer = arrow::Buffer::Wrap(values_data);
+ const auto values = std::make_shared<arrow::FloatArray>(3, values_buffer);
+ const auto read_array = [&](const auto& arrow_array) {
+ auto column = ColumnArray::create(
+ ColumnNullable::create(ColumnFloat32::create(),
ColumnUInt8::create()),
+ ColumnOffset64::create());
+ EXPECT_TRUE(serde.read_column_from_arrow(*column, arrow_array.get(), 0,
+ arrow_array->length(),
cctz::utc_time_zone())
+ .ok());
+ EXPECT_EQ(column->get_offsets(), (ColumnArray::Offsets64 {2, 3, 3}));
+ };
+
+ const std::array<int32_t, 4> list_offsets {0, 2, 3, 3};
+ std::vector<uint8_t> list_offsets_storage(sizeof(list_offsets) + 1);
+ memcpy(list_offsets_storage.data() + 1, list_offsets.data(),
sizeof(list_offsets));
+ const auto list_offsets_buffer =
+ arrow::Buffer::Wrap(list_offsets_storage.data() + 1,
sizeof(list_offsets));
+
read_array(std::make_shared<arrow::ListArray>(arrow::list(arrow::float32()), 3,
+ list_offsets_buffer,
values));
+
+ const std::array<int64_t, 4> large_list_offsets {0, 2, 3, 3};
+ std::vector<uint8_t> large_list_offsets_storage(sizeof(large_list_offsets)
+ 1);
+ memcpy(large_list_offsets_storage.data() + 1, large_list_offsets.data(),
+ sizeof(large_list_offsets));
+ const auto large_list_offsets_buffer =
+ arrow::Buffer::Wrap(large_list_offsets_storage.data() + 1,
sizeof(large_list_offsets));
+
read_array(std::make_shared<arrow::LargeListArray>(arrow::large_list(arrow::float32()),
3,
+
large_list_offsets_buffer, values));
+}
+
} // namespace doris
diff --git
a/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
b/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
index fdce7af8f89..d6b2e4f57bd 100644
--- a/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
@@ -34,12 +34,17 @@
#include "core/column/column_map.h"
#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
+#include "core/column/column_varbinary.h"
#include "core/data_type/primitive_type.h"
#include "core/data_type_serde/data_type_array_serde.h"
+#include "core/data_type_serde/data_type_datev2_serde.h"
#include "core/data_type_serde/data_type_map_serde.h"
#include "core/data_type_serde/data_type_nullable_serde.h"
#include "core/data_type_serde/data_type_number_serde.h"
#include "core/data_type_serde/data_type_string_serde.h"
+#include "core/data_type_serde/data_type_time_serde.h"
+#include "core/data_type_serde/data_type_timestamptz_serde.h"
+#include "core/data_type_serde/data_type_varbinary_serde.h"
namespace doris {
namespace {
@@ -69,7 +74,8 @@ void expect_invalid_arrow(Func&& func, std::string_view
message) {
EXPECT_TRUE(thrown) << message;
}
-std::shared_ptr<arrow::Buffer> wrap_offsets(const std::vector<int32_t>&
offsets) {
+template <typename OffsetType>
+std::shared_ptr<arrow::Buffer> wrap_offsets(const std::vector<OffsetType>&
offsets) {
return arrow::Buffer::Wrap(offsets);
}
@@ -86,6 +92,127 @@ struct StringArrayHolder {
std::shared_ptr<arrow::StringArray> array;
};
+struct LargeListArrayHolder {
+ LargeListArrayHolder(int64_t length, std::vector<int64_t> offsets_,
+ std::vector<int64_t> values_)
+ : offsets(std::move(offsets_)), values(std::move(values_)) {
+ auto values_array =
+ std::make_shared<arrow::Int64Array>(values.size(),
arrow::Buffer::Wrap(values));
+ array =
std::make_shared<arrow::LargeListArray>(arrow::large_list(arrow::int64()),
length,
+ wrap_offsets(offsets),
values_array);
+ }
+
+ std::vector<int64_t> offsets;
+ std::vector<int64_t> values;
+ std::shared_ptr<arrow::LargeListArray> array;
+};
+
+void expect_invalid_large_list_offsets(int64_t length, std::vector<int64_t>
offsets,
+ std::vector<int64_t> values,
std::string_view message) {
+ LargeListArrayHolder array(length, std::move(offsets), std::move(values));
+ auto column = ColumnArray::create(
+ ColumnNullable::create(ColumnInt64::create(),
ColumnUInt8::create()),
+ ColumnOffset64::create());
+ auto nested_serde = std::make_shared<DataTypeNullableSerDe>(
+ std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>());
+ DataTypeArraySerDe serde(nested_serde);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.array.get(), 0,
+ length,
cctz::utc_time_zone()));
+ },
+ message);
+}
+
+template <typename ArrowArrayType, PrimitiveType DorisType, typename
DorisColumnType>
+void expect_invalid_short_numeric_buffer(std::string_view message) {
+ using ArrowValueType = typename ArrowArrayType::value_type;
+ std::vector<ArrowValueType> values = {1};
+ auto array = std::make_shared<ArrowArrayType>(2,
arrow::Buffer::Wrap(values));
+ auto column = DorisColumnType::create();
+ DataTypeNumberSerDe<DorisType> serde;
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ message);
+}
+
+template <typename ArrowArrayType, PrimitiveType DorisType, typename
DorisColumnType>
+void expect_invalid_missing_numeric_validity_bitmap(std::string_view message) {
+ using ArrowValueType = typename ArrowArrayType::value_type;
+ std::vector<ArrowValueType> values = {1, 2};
+ auto array = std::make_shared<ArrowArrayType>(2,
arrow::Buffer::Wrap(values),
+
std::shared_ptr<arrow::Buffer>(), 1);
+ auto column = DorisColumnType::create();
+ DataTypeNumberSerDe<DorisType> serde;
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ message);
+}
+
+template <typename DorisColumnType, typename SerDeType>
+void expect_invalid_temporal_arrow_buffers(const
std::shared_ptr<arrow::DataType>& arrow_type,
+ size_t value_width, SerDeType&
serde,
+ std::string_view type_name) {
+ std::vector<uint8_t> short_values(value_width, 0);
+ auto short_data = arrow::ArrayData::Make(
+ arrow_type, 2, {std::shared_ptr<arrow::Buffer>(),
arrow::Buffer::Wrap(short_values)},
+ 0);
+ auto short_array = arrow::MakeArray(short_data);
+ auto short_column = DorisColumnType::create();
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*short_column,
short_array.get(), 0,
+ 2,
cctz::utc_time_zone()));
+ },
+ std::string(type_name) + " short values buffer should be
rejected");
+
+ std::vector<uint8_t> values(value_width * 2, 0);
+ auto missing_validity_data = arrow::ArrayData::Make(
+ arrow_type, 2, {std::shared_ptr<arrow::Buffer>(),
arrow::Buffer::Wrap(values)}, 1);
+ auto missing_validity_array = arrow::MakeArray(missing_validity_data);
+ auto missing_validity_column = DorisColumnType::create();
+ expect_invalid_arrow(
+ [&] {
+
static_cast<void>(serde.read_column_from_arrow(*missing_validity_column,
+
missing_validity_array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ std::string(type_name) + " missing validity bitmap should be
rejected");
+
+ auto invalid_range_data = arrow::ArrayData::Make(
+ arrow_type, 1, {std::shared_ptr<arrow::Buffer>(),
arrow::Buffer::Wrap(values)}, 0);
+ auto invalid_range_array = arrow::MakeArray(invalid_range_data);
+ auto invalid_range_column = DorisColumnType::create();
+ expect_invalid_arrow(
+ [&] {
+
static_cast<void>(serde.read_column_from_arrow(*invalid_range_column,
+
invalid_range_array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ std::string(type_name) + " invalid read range should be rejected");
+}
+
+void expect_invalid_varbinary_arrow(const arrow::Array& array, int64_t start,
int64_t end,
+ std::string_view message) {
+ auto column = ColumnVarbinary::create();
+ DataTypeVarbinarySerDe serde;
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
&array, start, end,
+
cctz::utc_time_zone()));
+ },
+ message);
+}
+
} // namespace
TEST(DataTypeSerDeArrowValidationTest, RejectsShortStringOffsetsBuffer) {
@@ -153,6 +280,108 @@ TEST(DataTypeSerDeArrowValidationTest,
RejectsShortFixedWidthDataBuffer) {
"short int64 data buffer should be rejected");
}
+TEST(DataTypeSerDeArrowValidationTest,
RejectsShortEarlyReturnNumericDataBuffers) {
+ ScopedArrowInputValidation validation(true);
+
+ expect_invalid_short_numeric_buffer<arrow::HalfFloatArray, TYPE_FLOAT,
ColumnFloat32>(
+ "short half-float data buffer should be rejected");
+ expect_invalid_short_numeric_buffer<arrow::UInt8Array, TYPE_SMALLINT,
ColumnInt16>(
+ "short uint8 data buffer should be rejected");
+ expect_invalid_short_numeric_buffer<arrow::UInt16Array, TYPE_INT,
ColumnInt32>(
+ "short uint16 data buffer should be rejected");
+ expect_invalid_short_numeric_buffer<arrow::UInt32Array, TYPE_BIGINT,
ColumnInt64>(
+ "short uint32 data buffer should be rejected");
+ expect_invalid_short_numeric_buffer<arrow::UInt64Array, TYPE_LARGEINT,
ColumnInt128>(
+ "short uint64 data buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest,
RejectsMissingEarlyReturnNumericValidityBitmaps) {
+ ScopedArrowInputValidation validation(true);
+
+ expect_invalid_missing_numeric_validity_bitmap<arrow::HalfFloatArray,
TYPE_FLOAT,
+ ColumnFloat32>(
+ "missing half-float validity bitmap should be rejected");
+ expect_invalid_missing_numeric_validity_bitmap<arrow::UInt8Array,
TYPE_SMALLINT, ColumnInt16>(
+ "missing unsigned widening validity bitmap should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsMalformedNewTemporalArrays) {
+ ScopedArrowInputValidation validation(true);
+
+ DataTypeTimeV2SerDe time_serde;
+
expect_invalid_temporal_arrow_buffers<ColumnTimeV2>(arrow::time32(arrow::TimeUnit::SECOND),
+
sizeof(arrow::Time32Array::value_type),
+ time_serde, "time32");
+
expect_invalid_temporal_arrow_buffers<ColumnTimeV2>(arrow::time64(arrow::TimeUnit::MICRO),
+
sizeof(arrow::Time64Array::value_type),
+ time_serde, "time64");
+
+ DataTypeTimeStampTzSerDe timestamptz_serde(6);
+ expect_invalid_temporal_arrow_buffers<ColumnTimeStampTz>(
+ arrow::timestamp(arrow::TimeUnit::MICRO),
sizeof(arrow::TimestampArray::value_type),
+ timestamptz_serde, "timestamp");
+
+ DataTypeDateV2SerDe date_serde;
+ expect_invalid_temporal_arrow_buffers<ColumnDateV2>(
+ arrow::date64(), sizeof(arrow::Date64Array::value_type),
date_serde, "date64");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsMalformedVarbinaryArrays) {
+ ScopedArrowInputValidation validation(true);
+
+ std::string values = "abc";
+ const auto value_buffer = arrow::Buffer::Wrap(values.data(),
values.size());
+
+ std::vector<int32_t> short_offsets = {0};
+ arrow::BinaryArray short_binary_offsets(1, wrap_offsets(short_offsets),
value_buffer);
+ expect_invalid_varbinary_arrow(short_binary_offsets, 0, 1,
+ "short binary offsets buffer should be
rejected");
+
+ std::vector<int32_t> negative_offsets = {-1, 0};
+ arrow::BinaryArray negative_binary_offsets(1,
wrap_offsets(negative_offsets), value_buffer);
+ expect_invalid_varbinary_arrow(negative_binary_offsets, 0, 1,
+ "negative binary offset should be
rejected");
+
+ std::vector<int32_t> non_monotonic_offsets = {2, 1};
+ arrow::BinaryArray non_monotonic_binary_offsets(1,
wrap_offsets(non_monotonic_offsets),
+ value_buffer);
+ expect_invalid_varbinary_arrow(non_monotonic_binary_offsets, 0, 1,
+ "non-monotonic binary offsets should be
rejected");
+
+ std::vector<int32_t> oversized_offsets = {0, 4};
+ arrow::BinaryArray oversized_binary_value(1,
wrap_offsets(oversized_offsets), value_buffer);
+ expect_invalid_varbinary_arrow(oversized_binary_value, 0, 1,
+ "binary value range beyond buffer should be
rejected");
+
+ std::vector<int64_t> short_large_offsets = {0};
+ arrow::LargeBinaryArray short_large_binary_offsets(1,
wrap_offsets(short_large_offsets),
+ value_buffer);
+ expect_invalid_varbinary_arrow(short_large_binary_offsets, 0, 1,
+ "short large-binary offsets buffer should
be rejected");
+
+ std::vector<int64_t> oversized_large_offsets = {0, 4};
+ arrow::LargeBinaryArray oversized_large_binary_value(1,
wrap_offsets(oversized_large_offsets),
+ value_buffer);
+ expect_invalid_varbinary_arrow(oversized_large_binary_value, 0, 1,
+ "large-binary value range beyond buffer
should be rejected");
+
+ std::vector<uint8_t> short_fixed_values(3, 0);
+ arrow::FixedSizeBinaryArray
short_fixed_binary(arrow::fixed_size_binary(3), 2,
+
arrow::Buffer::Wrap(short_fixed_values));
+ expect_invalid_varbinary_arrow(short_fixed_binary, 0, 2,
+ "short fixed-size binary buffer should be
rejected");
+
+ std::vector<int32_t> valid_offsets = {0, 1};
+ arrow::BinaryArray missing_validity(1, wrap_offsets(valid_offsets),
value_buffer,
+ std::shared_ptr<arrow::Buffer>(), 1);
+ expect_invalid_varbinary_arrow(missing_validity, 0, 1,
+ "missing varbinary validity bitmap should
be rejected");
+
+ arrow::BinaryArray valid_binary(1, wrap_offsets(valid_offsets),
value_buffer);
+ expect_invalid_varbinary_arrow(valid_binary, 0, 2,
+ "invalid varbinary read range should be
rejected");
+}
+
TEST(DataTypeSerDeArrowValidationTest, RejectsSlicedArrowArray) {
ScopedArrowInputValidation validation(true);
@@ -249,6 +478,19 @@ TEST(DataTypeSerDeArrowValidationTest,
RejectsListOffsetsBeyondValuesLength) {
"list offsets beyond values length should be rejected");
}
+TEST(DataTypeSerDeArrowValidationTest, RejectsMalformedLargeListOffsets) {
+ ScopedArrowInputValidation validation(true);
+
+ expect_invalid_large_list_offsets(1, {0}, {1},
+ "short large-list offsets buffer should
be rejected");
+ expect_invalid_large_list_offsets(1, {-1, 0}, {1},
+ "negative large-list offset should be
rejected");
+ expect_invalid_large_list_offsets(2, {0, 2, 1}, {1, 2},
+ "non-monotonic large-list offsets should
be rejected");
+ expect_invalid_large_list_offsets(1, {0, 2}, {1},
+ "large-list offsets beyond values length
should be rejected");
+}
+
TEST(DataTypeSerDeArrowValidationTest, RejectsMapOffsetsBeyondKeysLength) {
ScopedArrowInputValidation validation(true);
diff --git a/be/test/core/data_type_serde/data_type_serde_datetime_v2_test.cpp
b/be/test/core/data_type_serde/data_type_serde_datetime_v2_test.cpp
index dbe9c875834..88b6dcba6ae 100644
--- a/be/test/core/data_type_serde/data_type_serde_datetime_v2_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_datetime_v2_test.cpp
@@ -333,6 +333,122 @@ TEST_F(DataTypeDateTimeV2SerDeTest,
ArrowMemNotAlignedDate) {
EXPECT_TRUE(st.ok());
}
+TEST_F(DataTypeDateTimeV2SerDeTest, ArrowDate64ToDateV2) {
+ arrow::Date64Builder builder;
+ ASSERT_TRUE(builder.Append(-86400000).ok());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(86400000).ok());
+ ASSERT_TRUE(builder.Append(1785196800000).ok());
+ ASSERT_TRUE(builder.AppendNull().ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnDateV2::create();
+ cctz::time_zone tz;
+ auto status =
+ serde_date_v2->read_column_from_arrow(*column, array.get(), 0,
array->length(), tz);
+ ASSERT_TRUE(status.ok()) << status.to_string();
+
+ ASSERT_EQ(5, column->size());
+ const auto& values = column->get_data();
+ EXPECT_EQ(1969, values[0].year());
+ EXPECT_EQ(12, values[0].month());
+ EXPECT_EQ(31, values[0].day());
+ EXPECT_EQ(1970, values[1].year());
+ EXPECT_EQ(1, values[1].month());
+ EXPECT_EQ(1, values[1].day());
+ EXPECT_EQ(1970, values[2].year());
+ EXPECT_EQ(1, values[2].month());
+ EXPECT_EQ(2, values[2].day());
+ EXPECT_EQ(2026, values[3].year());
+ EXPECT_EQ(7, values[3].month());
+ EXPECT_EQ(28, values[3].day());
+}
+
+TEST_F(DataTypeDateTimeV2SerDeTest, RejectsInvalidDate64Values) {
+ const auto expect_invalid = [&](int64_t milliseconds, const char*
expected_message) {
+ arrow::Date64Builder builder;
+ ASSERT_TRUE(builder.Append(milliseconds).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnDateV2::create();
+ cctz::time_zone tz;
+ const auto status = serde_date_v2->read_column_from_arrow(*column,
array.get(), 0, 1, tz);
+ EXPECT_FALSE(status.ok());
+ EXPECT_NE(std::string::npos,
status.to_string().find(expected_message));
+ };
+
+ expect_invalid(1, "must contain whole days");
+ constexpr int64_t milliseconds_per_day = 86400000;
+ constexpr int64_t out_of_range =
+ std::numeric_limits<int64_t>::max() / milliseconds_per_day *
milliseconds_per_day;
+ expect_invalid(out_of_range, "outside the Doris DATE range");
+}
+
+TEST_F(DataTypeDateTimeV2SerDeTest, ArrowTimeToTimeV2) {
+ const auto read_time = [](arrow::ArrayBuilder* builder, int scale,
+ const std::vector<double>& expected) {
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder->Finish(&array).ok());
+ auto column = ColumnTimeV2::create();
+ DataTypeTimeV2SerDe serde(scale);
+ cctz::time_zone tz;
+ const auto status =
+ serde.read_column_from_arrow(*column, array.get(), 0,
array->length(), tz);
+ ASSERT_TRUE(status.ok()) << status.to_string();
+ ASSERT_EQ(expected.size(), column->size());
+ for (size_t row = 0; row < expected.size(); ++row) {
+ EXPECT_DOUBLE_EQ(expected[row], column->get_data()[row]);
+ }
+ };
+
+ arrow::Time32Builder
seconds_builder(arrow::time32(arrow::TimeUnit::SECOND),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(seconds_builder.Append(45296).ok());
+ ASSERT_TRUE(seconds_builder.AppendNull().ok());
+ read_time(&seconds_builder, 0, {45296000000.0, 0.0});
+
+ arrow::Time32Builder millis_builder(arrow::time32(arrow::TimeUnit::MILLI),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(millis_builder.Append(45296123).ok());
+ read_time(&millis_builder, 3, {45296123000.0});
+
+ arrow::Time64Builder micros_builder(arrow::time64(arrow::TimeUnit::MICRO),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(micros_builder.Append(45296123456).ok());
+ read_time(µs_builder, 6, {45296123456.0});
+
+ arrow::Time64Builder nanos_builder(arrow::time64(arrow::TimeUnit::NANO),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(nanos_builder.Append(45296123456789).ok());
+ read_time(&nanos_builder, 6, {45296123456.0});
+}
+
+TEST_F(DataTypeDateTimeV2SerDeTest, RejectsInvalidArrowTimeValues) {
+ const auto expect_invalid = [](arrow::ArrayBuilder* builder, int scale) {
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder->Finish(&array).ok());
+ auto column = ColumnTimeV2::create();
+ DataTypeTimeV2SerDe serde(scale);
+ cctz::time_zone tz;
+ const auto status =
+ serde.read_column_from_arrow(*column, array.get(), 0,
array->length(), tz);
+ EXPECT_FALSE(status.ok());
+ EXPECT_NE(std::string::npos, status.to_string().find("outside the
time-of-day range"));
+ };
+
+ arrow::Time32Builder
negative_builder(arrow::time32(arrow::TimeUnit::SECOND),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(negative_builder.Append(-1).ok());
+ expect_invalid(&negative_builder, 0);
+
+ arrow::Time64Builder
next_day_builder(arrow::time64(arrow::TimeUnit::MICRO),
+ arrow::default_memory_pool());
+ ASSERT_TRUE(next_day_builder.Append(86400000000).ok());
+ expect_invalid(&next_day_builder, 6);
+}
+
// Run with UBSan enabled to catch misalignment errors.
TEST_F(DataTypeDateTimeV2SerDeTest, ArrowMemNotAlignedDateTime) {
// 1.Prepare the data.
@@ -395,4 +511,25 @@ TEST_F(DataTypeDateTimeV2SerDeTest,
ReadArrowTimestampBeforeEpoch) {
EXPECT_EQ(insert_value, dest_column->get_element(0).to_string(6));
}
+TEST_F(DataTypeDateTimeV2SerDeTest,
ArrowTimezoneNaiveTimestampIgnoresSessionTimezone) {
+ auto timestamp_type = arrow::timestamp(arrow::TimeUnit::MICRO);
+ arrow::TimestampBuilder builder(timestamp_type,
arrow::default_memory_pool());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(45296123456).ok());
+ ASSERT_TRUE(builder.Append(-876544).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnDateTimeV2::create();
+ cctz::time_zone session_timezone;
+ ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone("+08:00",
session_timezone));
+ const auto status = serde_datetime_v2_6->read_column_from_arrow(
+ *column, array.get(), 0, array->length(), session_timezone);
+ ASSERT_TRUE(status.ok()) << status.to_string();
+ ASSERT_EQ(3, column->size());
+ EXPECT_EQ("1970-01-01 00:00:00.000000",
column->get_data()[0].to_string(6));
+ EXPECT_EQ("1970-01-01 12:34:56.123456",
column->get_data()[1].to_string(6));
+ EXPECT_EQ("1969-12-31 23:59:59.123456",
column->get_data()[2].to_string(6));
+}
+
} // namespace doris
diff --git a/be/test/core/data_type_serde/data_type_serde_number_test.cpp
b/be/test/core/data_type_serde/data_type_serde_number_test.cpp
index b1050d7119b..41724fd3b33 100644
--- a/be/test/core/data_type_serde/data_type_serde_number_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_number_test.cpp
@@ -620,6 +620,130 @@ TEST_F(DataTypeNumberSerDeTest,
RowStoreDateTimeJsonbWidth) {
});
}
+TEST_F(DataTypeNumberSerDeTest, ArrowFloat16ToFloat32) {
+ arrow::HalfFloatBuilder builder;
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append(0x0000).ok());
+ ASSERT_TRUE(builder.Append(0x8000).ok());
+ ASSERT_TRUE(builder.Append(0x3E00).ok());
+ ASSERT_TRUE(builder.Append(0x7BFF).ok());
+ ASSERT_TRUE(builder.Append(0x7C00).ok());
+ ASSERT_TRUE(builder.Append(0xFC00).ok());
+ ASSERT_TRUE(builder.Append(0x7E00).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto float_column = ColumnFloat32::create();
+ cctz::time_zone tz;
+ ASSERT_TRUE(serde_float32
+ ->read_column_from_arrow(*float_column, array.get(),
0, array->length(), tz)
+ .ok());
+
+ ASSERT_EQ(8, float_column->size());
+ EXPECT_FLOAT_EQ(0.0F, float_column->get_data()[0]);
+ EXPECT_FLOAT_EQ(0.0F, float_column->get_data()[1]);
+ EXPECT_FALSE(std::signbit(float_column->get_data()[1]));
+ EXPECT_FLOAT_EQ(-0.0F, float_column->get_data()[2]);
+ EXPECT_TRUE(std::signbit(float_column->get_data()[2]));
+ EXPECT_FLOAT_EQ(1.5F, float_column->get_data()[3]);
+ EXPECT_FLOAT_EQ(65504.0F, float_column->get_data()[4]);
+ EXPECT_EQ(std::numeric_limits<float>::infinity(),
float_column->get_data()[5]);
+ EXPECT_EQ(-std::numeric_limits<float>::infinity(),
float_column->get_data()[6]);
+ EXPECT_TRUE(std::isnan(float_column->get_data()[7]));
+}
+
+TEST_F(DataTypeNumberSerDeTest, ArrowUnsignedIntegersAreWidenedLosslessly) {
+ cctz::time_zone tz;
+
+ {
+ arrow::UInt8Builder builder;
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(127).ok());
+ ASSERT_TRUE(builder.Append(128).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<UInt8>::max()).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnInt16::create();
+ ASSERT_TRUE(
+ serde_int16->read_column_from_arrow(*column, array.get(), 0,
array->length(), tz)
+ .ok());
+ ASSERT_EQ(5, column->size());
+ EXPECT_EQ(0, column->get_data()[0]);
+ EXPECT_EQ(0, column->get_data()[1]);
+ EXPECT_EQ(127, column->get_data()[2]);
+ EXPECT_EQ(128, column->get_data()[3]);
+ EXPECT_EQ(255, column->get_data()[4]);
+ }
+
+ {
+ arrow::UInt16Builder builder;
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(32767).ok());
+ ASSERT_TRUE(builder.Append(32768).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<UInt16>::max()).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnInt32::create();
+ ASSERT_TRUE(
+ serde_int32->read_column_from_arrow(*column, array.get(), 0,
array->length(), tz)
+ .ok());
+ ASSERT_EQ(5, column->size());
+ EXPECT_EQ(0, column->get_data()[0]);
+ EXPECT_EQ(0, column->get_data()[1]);
+ EXPECT_EQ(32767, column->get_data()[2]);
+ EXPECT_EQ(32768, column->get_data()[3]);
+ EXPECT_EQ(65535, column->get_data()[4]);
+ }
+
+ {
+ arrow::UInt32Builder builder;
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<Int32>::max()).ok());
+ ASSERT_TRUE(builder.Append(UInt32(1) << 31).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<UInt32>::max()).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnInt64::create();
+ ASSERT_TRUE(
+ serde_int64->read_column_from_arrow(*column, array.get(), 0,
array->length(), tz)
+ .ok());
+ ASSERT_EQ(5, column->size());
+ EXPECT_EQ(0, column->get_data()[0]);
+ EXPECT_EQ(0, column->get_data()[1]);
+ EXPECT_EQ(std::numeric_limits<Int32>::max(), column->get_data()[2]);
+ EXPECT_EQ(Int64(1) << 31, column->get_data()[3]);
+ EXPECT_EQ(std::numeric_limits<UInt32>::max(), column->get_data()[4]);
+ }
+
+ {
+ arrow::UInt64Builder builder;
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append(0).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<Int64>::max()).ok());
+ ASSERT_TRUE(builder.Append(UInt64(1) << 63).ok());
+ ASSERT_TRUE(builder.Append(std::numeric_limits<UInt64>::max()).ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnInt128::create();
+ ASSERT_TRUE(
+ serde_int128->read_column_from_arrow(*column, array.get(), 0,
array->length(), tz)
+ .ok());
+ ASSERT_EQ(5, column->size());
+ EXPECT_EQ(Int128(0), column->get_data()[0]);
+ EXPECT_EQ(Int128(0), column->get_data()[1]);
+ EXPECT_EQ(static_cast<Int128>(std::numeric_limits<Int64>::max()),
column->get_data()[2]);
+ EXPECT_EQ(Int128(1) << 63, column->get_data()[3]);
+ EXPECT_EQ(static_cast<Int128>(std::numeric_limits<UInt64>::max()),
column->get_data()[4]);
+ }
+}
+
// to_olap_string / from_zonemap_string must round-trip finite floating-point
// extremes (±DBL_MAX / ±FLT_MAX). The old digits10+1 (16g/7g) formatter
rounded
// DBL_MAX up to 1.797693134862316e+308 — larger than the largest finite
double —
diff --git a/be/test/core/data_type_serde/data_type_serde_timestamptz_test.cpp
b/be/test/core/data_type_serde/data_type_serde_timestamptz_test.cpp
index 584106cc635..742715ef62e 100644
--- a/be/test/core/data_type_serde/data_type_serde_timestamptz_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_timestamptz_test.cpp
@@ -359,4 +359,40 @@ TEST_F(DataTypeTimeStampTzSerDeTest,
ReadArrowIgnoresValuesUnderNulls) {
ASSERT_EQ(2, dest_column->size());
EXPECT_EQ(source_value, dest_column->get_element(0));
}
+
+TEST_F(DataTypeTimeStampTzSerDeTest, ArrowTimestampToTimestampTz) {
+ const auto read_timestamp = [](arrow::TimeUnit::type unit, int64_t value,
+ const std::string& arrow_timezone,
+ const std::string& expected_utc,
+ const std::string& expected_session_time) {
+ auto type = arrow::timestamp(unit, arrow_timezone);
+ arrow::TimestampBuilder builder(type, arrow::default_memory_pool());
+ ASSERT_TRUE(builder.Append(value).ok());
+ ASSERT_TRUE(builder.AppendNull().ok());
+ std::shared_ptr<arrow::Array> array;
+ ASSERT_TRUE(builder.Finish(&array).ok());
+
+ auto column = ColumnTimeStampTz::create();
+ DataTypeTimeStampTzSerDe serde(6);
+ cctz::time_zone session_timezone;
+ ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone("+08:00",
session_timezone));
+ const auto status = serde.read_column_from_arrow(*column, array.get(),
0, array->length(),
+ session_timezone);
+ ASSERT_TRUE(status.ok()) << status.to_string();
+ ASSERT_EQ(2, column->size());
+
+ const auto utc = cctz::utc_time_zone();
+ EXPECT_EQ(expected_utc, column->get_data()[0].to_string(utc, 6));
+ EXPECT_EQ(expected_session_time,
column->get_data()[0].to_string(session_timezone, 6));
+ };
+
+ read_timestamp(arrow::TimeUnit::SECOND, 0, "UTC", "1970-01-01
00:00:00.000000+00:00",
+ "1970-01-01 08:00:00.000000+08:00");
+ read_timestamp(arrow::TimeUnit::MILLI, 123, "Asia/Shanghai", "1970-01-01
00:00:00.123000+00:00",
+ "1970-01-01 08:00:00.123000+08:00");
+ read_timestamp(arrow::TimeUnit::MICRO, -876544, "UTC", "1969-12-31
23:59:59.123456+00:00",
+ "1970-01-01 07:59:59.123456+08:00");
+ read_timestamp(arrow::TimeUnit::NANO, -876543211, "Asia/Shanghai",
+ "1969-12-31 23:59:59.123456+00:00", "1970-01-01
07:59:59.123456+08:00");
+}
} // namespace doris
diff --git a/be/test/core/data_type_serde/data_type_serde_varbinary_test.cpp
b/be/test/core/data_type_serde/data_type_serde_varbinary_test.cpp
index 6016bb4a37a..5f03f363c86 100644
--- a/be/test/core/data_type_serde/data_type_serde_varbinary_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_varbinary_test.cpp
@@ -170,7 +170,7 @@ TEST_F(DataTypeVarbinarySerDeTest,
MysqlTextAndBinaryAndConst) {
}
}
-TEST_F(DataTypeVarbinarySerDeTest, ArrowWriteSupportedReadNotImplemented) {
+TEST_F(DataTypeVarbinarySerDeTest, ArrowBinaryRoundTrip) {
DataTypeVarbinarySerDe serde;
auto col = ColumnVarbinary::create();
auto* vb = col.get();
@@ -185,8 +185,11 @@ TEST_F(DataTypeVarbinarySerDeTest,
ArrowWriteSupportedReadNotImplemented) {
std::shared_ptr<arrow::Array> arr;
ASSERT_TRUE(builder->Finish(&arr).ok());
- st = serde.read_column_from_arrow(*vb, arr.get(), 0, 1, tz);
- EXPECT_FALSE(st.ok());
+ auto read_column = ColumnVarbinary::create();
+ st = serde.read_column_from_arrow(*read_column, arr.get(), 0, 1, tz);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ ASSERT_EQ(1U, read_column->size());
+ EXPECT_EQ(v, read_column->get_data_at(0).to_string());
auto* binary_array = dynamic_cast<arrow::BinaryArray*>(arr.get());
ASSERT_NE(binary_array, nullptr);
@@ -198,6 +201,49 @@ TEST_F(DataTypeVarbinarySerDeTest,
ArrowWriteSupportedReadNotImplemented) {
EXPECT_EQ(memcmp(raw, view.data, view.size), 0);
}
+TEST_F(DataTypeVarbinarySerDeTest, ArrowReadSupportsLargeAndFixedSizeBinary) {
+ DataTypeVarbinarySerDe serde;
+ cctz::time_zone tz;
+ const auto expect_values = [](const ColumnVarbinary& column,
+ const std::vector<std::string>& expected) {
+ ASSERT_EQ(expected.size(), column.size());
+ for (size_t i = 0; i < expected.size(); ++i) {
+ EXPECT_EQ(expected[i], column.get_data_at(i).to_string());
+ }
+ };
+
+ {
+ arrow::LargeBinaryBuilder builder;
+ ASSERT_TRUE(builder.Append("large").ok());
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append("binary").ok());
+ std::shared_ptr<arrow::Array> arrow_array;
+ ASSERT_TRUE(builder.Finish(&arrow_array).ok());
+
+ auto column = ColumnVarbinary::create();
+ ASSERT_TRUE(serde.read_column_from_arrow(*column, arrow_array.get(), 0,
+ arrow_array->length(), tz)
+ .ok());
+ expect_values(*column, {"large", "", "binary"});
+ }
+
+ {
+ auto type = arrow::fixed_size_binary(3);
+ arrow::FixedSizeBinaryBuilder builder(type,
arrow::default_memory_pool());
+ ASSERT_TRUE(builder.Append("abc").ok());
+ ASSERT_TRUE(builder.AppendNull().ok());
+ ASSERT_TRUE(builder.Append("xyz").ok());
+ std::shared_ptr<arrow::Array> arrow_array;
+ ASSERT_TRUE(builder.Finish(&arrow_array).ok());
+
+ auto column = ColumnVarbinary::create();
+ ASSERT_TRUE(serde.read_column_from_arrow(*column, arrow_array.get(), 0,
+ arrow_array->length(), tz)
+ .ok());
+ expect_values(*column, {"abc", "", "xyz"});
+ }
+}
+
TEST_F(DataTypeVarbinarySerDeTest, OrcWriteSupported) {
DataTypeVarbinarySerDe serde;
auto col = ColumnVarbinary::create();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]