This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 2f63ec48090 [fix](be) Backport BE fixes and refinements to branch-4.1
(#65754)
2f63ec48090 is described below
commit 2f63ec4809019c9556cc0932902ad2bd3b959051
Author: Mryange <[email protected]>
AuthorDate: Thu Jul 23 14:14:52 2026 +0800
[fix](be) Backport BE fixes and refinements to branch-4.1 (#65754)
### What problem does this PR solve?
Backport a small group of BE correctness fixes, column refinements,
decimal cast optimization, and Arrow input validation to branch-4.1.
Picked changes:
- [#64721](https://github.com/apache/doris/pull/64721)
([commit](https://github.com/apache/doris/commit/9816a5a20b684128b7333e2aacd3f9d08a97c7be)):
Check block column and type pointers.
- [#65047](https://github.com/apache/doris/pull/65047)
([commit](https://github.com/apache/doris/commit/e23b4030540182544a00cdbdc6742e9f140ef730)):
Make `filter_by_selector` const.
- [#65410](https://github.com/apache/doris/pull/65410)
([commit](https://github.com/apache/doris/commit/5d56602a645db2cd02b5f5947c2e11431da7fd70)):
Speed up zero-scale decimal casts.
- [#64796](https://github.com/apache/doris/pull/64796)
([commit](https://github.com/apache/doris/commit/43a31acc4b45647b84d5298cfd0e2ec1aca1540a)):
Validate Arrow input buffers before column conversion.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] 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/benchmark/benchmark_arrow_validation.hpp | 382 +++++++++++++++++++++
be/benchmark/benchmark_main.cpp | 1 +
be/src/common/config.cpp | 3 +
be/src/common/config.h | 3 +
be/src/core/block/block.cpp | 15 +
be/src/core/block/block.h | 2 +
be/src/core/column/column.h | 3 +-
be/src/core/column/column_decimal.h | 3 +-
be/src/core/column/column_dictionary.h | 17 +-
be/src/core/column/column_nullable.cpp | 10 +-
be/src/core/column/column_nullable.h | 3 +-
be/src/core/column/column_string.cpp | 3 +-
be/src/core/column/column_string.h | 3 +-
be/src/core/column/column_vector.h | 3 +-
be/src/core/data_type_serde/arrow_validation.h | 320 +++++++++++++++++
.../core/data_type_serde/data_type_array_serde.cpp | 5 +
.../data_type_date_or_datetime_serde.cpp | 5 +
.../data_type_serde/data_type_datetimev2_serde.cpp | 5 +
.../data_type_serde/data_type_datev2_serde.cpp | 5 +
.../data_type_serde/data_type_decimal_serde.cpp | 5 +
.../core/data_type_serde/data_type_ipv4_serde.cpp | 5 +
.../core/data_type_serde/data_type_ipv6_serde.cpp | 5 +
.../core/data_type_serde/data_type_jsonb_serde.cpp | 5 +
.../core/data_type_serde/data_type_map_serde.cpp | 5 +
.../data_type_serde/data_type_nullable_serde.cpp | 6 +
.../data_type_serde/data_type_number_serde.cpp | 26 +-
.../data_type_serde/data_type_string_serde.cpp | 39 ++-
.../data_type_serde/data_type_struct_serde.cpp | 5 +
be/src/exec/operator/operator.h | 2 +
be/src/exprs/function/cast/cast_to_decimal.h | 11 +
be/test/core/column/column_dictionary_test.cpp | 12 +-
be/test/core/column/column_nullable_test.cpp | 3 +-
be/test/core/column/column_string_test.cpp | 4 +-
be/test/core/data_type/block_check_type.cpp | 20 +-
.../data_type_serde_arrow_validation_test.cpp | 275 +++++++++++++++
35 files changed, 1180 insertions(+), 39 deletions(-)
diff --git a/be/benchmark/benchmark_arrow_validation.hpp
b/be/benchmark/benchmark_arrow_validation.hpp
new file mode 100644
index 00000000000..f5a2da45fb4
--- /dev/null
+++ b/be/benchmark/benchmark_arrow_validation.hpp
@@ -0,0 +1,382 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <arrow/array/array_binary.h>
+#include <arrow/array/array_nested.h>
+#include <arrow/array/array_primitive.h>
+#include <arrow/array/builder_binary.h>
+#include <arrow/array/builder_nested.h>
+#include <arrow/array/builder_primitive.h>
+#include <arrow/buffer.h>
+#include <arrow/type.h>
+#include <benchmark/benchmark.h>
+#include <cctz/time_zone.h>
+
+#include <chrono>
+#include <cstdint>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_array.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
+#include "core/data_type/primitive_type.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+enum class ArrowValidationBenchMode { OLD, CHECK_DISABLED, CHECK_ENABLED };
+
+void throw_if_not_ok(const arrow::Status& status) {
+ if (!status.ok()) {
+ throw std::runtime_error(status.ToString());
+ }
+}
+
+void throw_if_not_ok(const Status& status) {
+ if (!status.ok()) {
+ throw std::runtime_error(status.to_string());
+ }
+}
+
+void keep_column_size(size_t size) {
+ benchmark::DoNotOptimize(size);
+}
+
+template <typename Func>
+double measure_seconds(Func&& func) {
+ const auto start = std::chrono::steady_clock::now();
+ std::forward<Func>(func)();
+ const auto finish = std::chrono::steady_clock::now();
+ return std::chrono::duration<double>(finish - start).count();
+}
+
+std::shared_ptr<arrow::StringArray> make_string_array(int64_t rows) {
+ arrow::StringBuilder builder;
+ throw_if_not_ok(builder.Reserve(rows));
+ for (int64_t i = 0; i < rows; ++i) {
+ const std::string value = "arrow_validation_" + std::to_string(i %
1000);
+ throw_if_not_ok(builder.Append(value));
+ }
+ std::shared_ptr<arrow::Array> array;
+ throw_if_not_ok(builder.Finish(&array));
+ return std::static_pointer_cast<arrow::StringArray>(array);
+}
+
+std::shared_ptr<arrow::Int64Array> make_int64_array(int64_t rows) {
+ arrow::Int64Builder builder;
+ throw_if_not_ok(builder.Reserve(rows));
+ for (int64_t i = 0; i < rows; ++i) {
+ throw_if_not_ok(builder.Append(i));
+ }
+ std::shared_ptr<arrow::Array> array;
+ throw_if_not_ok(builder.Finish(&array));
+ return std::static_pointer_cast<arrow::Int64Array>(array);
+}
+
+std::shared_ptr<arrow::BooleanArray> make_boolean_array(int64_t rows) {
+ arrow::BooleanBuilder builder;
+ throw_if_not_ok(builder.Reserve(rows));
+ for (int64_t i = 0; i < rows; ++i) {
+ throw_if_not_ok(builder.Append((i & 1) == 0));
+ }
+ std::shared_ptr<arrow::Array> array;
+ throw_if_not_ok(builder.Finish(&array));
+ return std::static_pointer_cast<arrow::BooleanArray>(array);
+}
+
+std::shared_ptr<arrow::ListArray> make_string_list_array(int64_t rows) {
+ auto value_builder = std::make_shared<arrow::StringBuilder>();
+ arrow::ListBuilder builder(arrow::default_memory_pool(), value_builder);
+ throw_if_not_ok(builder.Reserve(rows));
+ throw_if_not_ok(value_builder->Reserve(rows * 3));
+ for (int64_t i = 0; i < rows; ++i) {
+ throw_if_not_ok(builder.Append());
+ for (int64_t j = 0; j < 3; ++j) {
+ const std::string value = "item_" + std::to_string((i + j) % 1000);
+ throw_if_not_ok(value_builder->Append(value));
+ }
+ }
+ std::shared_ptr<arrow::Array> array;
+ throw_if_not_ok(builder.Finish(&array));
+ return std::static_pointer_cast<arrow::ListArray>(array);
+}
+
+std::shared_ptr<arrow::MapArray> make_string_int_map_array(int64_t rows) {
+ auto keys = make_string_array(rows * 2);
+ auto items = make_int64_array(rows * 2);
+ std::vector<int32_t> offsets(rows + 1);
+ for (int64_t i = 0; i <= rows; ++i) {
+ offsets[i] = static_cast<int32_t>(i * 2);
+ }
+ auto offsets_buffer = arrow::Buffer::FromVector(std::move(offsets));
+ auto map_type = arrow::map(arrow::utf8(), arrow::int64());
+ return std::make_shared<arrow::MapArray>(map_type, rows, offsets_buffer,
keys, items);
+}
+
+void read_string_old(ColumnString& column, const arrow::BinaryArray& array,
int64_t start,
+ int64_t end) {
+ std::shared_ptr<arrow::Buffer> buffer = array.value_data();
+ const uint8_t* offsets_data = array.value_offsets()->data();
+ const size_t offset_size = sizeof(int32_t);
+
+ for (auto offset_i = start; offset_i < end; ++offset_i) {
+ if (!array.IsNull(offset_i)) {
+ auto start_offset = unaligned_load<int32_t>(offsets_data +
offset_i * offset_size);
+ auto end_offset = unaligned_load<int32_t>(offsets_data + (offset_i
+ 1) * offset_size);
+ int32_t length = end_offset - start_offset;
+ const auto* raw_data = buffer->data() + start_offset;
+ column.insert_data(reinterpret_cast<const char*>(raw_data),
length);
+ } else {
+ column.insert_default();
+ }
+ }
+}
+
+void read_int64_old(ColumnInt64& column, const arrow::Array& array, int64_t
start, int64_t end) {
+ auto& col_data = column.get_data();
+ std::shared_ptr<arrow::Buffer> buffer = array.data()->buffers[1];
+ const auto* raw_data = reinterpret_cast<const int64_t*>(buffer->data()) +
start;
+ col_data.insert(raw_data, raw_data + end - start);
+}
+
+void read_boolean_old(ColumnUInt8& column, const arrow::BooleanArray& array,
int64_t start,
+ int64_t end) {
+ auto& col_data = column.get_data();
+ for (int64_t bool_i = start; bool_i != end; ++bool_i) {
+ col_data.emplace_back(array.Value(bool_i));
+ }
+}
+
+void read_array_string_old(ColumnArray& column, const arrow::ListArray& array,
int64_t start,
+ int64_t end) {
+ auto& offsets_data = column.get_offsets();
+ auto arrow_offsets_array = array.offsets();
+ auto* arrow_offsets =
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+ 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);
+ offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
+ }
+ auto& nested_nullable = assert_cast<ColumnNullable&>(column.get_data());
+
read_string_old(assert_cast<ColumnString&>(nested_nullable.get_nested_column()),
+ static_cast<const arrow::BinaryArray&>(*array.values()),
+ arrow_nested_start_offset, arrow_nested_end_offset);
+ auto& null_map = nested_nullable.get_null_map_data();
+ null_map.resize_fill(null_map.size() + arrow_nested_end_offset -
arrow_nested_start_offset, 0);
+}
+
+void read_map_string_int_old(ColumnMap& column, const arrow::MapArray& array,
int64_t start,
+ int64_t end) {
+ auto& offsets_data = column.get_offsets();
+ auto arrow_offsets_array = array.offsets();
+ auto* arrow_offsets =
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+ 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 (int64_t 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);
+ offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
+ }
+ read_string_old(assert_cast<ColumnString&>(column.get_keys()),
+ static_cast<const arrow::BinaryArray&>(*array.keys()),
+ arrow_nested_start_offset, arrow_nested_end_offset);
+ read_int64_old(assert_cast<ColumnInt64&>(column.get_values()),
*array.items(),
+ arrow_nested_start_offset, arrow_nested_end_offset);
+}
+
+template <ArrowValidationBenchMode mode, typename DataType, typename
ArrowArray>
+void run_serde_benchmark(benchmark::State& state, const
std::shared_ptr<DataType>& type,
+ const std::shared_ptr<ArrowArray>& array) {
+ const bool old_config = config::enable_arrow_input_validation;
+ if constexpr (mode == ArrowValidationBenchMode::CHECK_DISABLED) {
+ config::enable_arrow_input_validation = false;
+ } else if constexpr (mode == ArrowValidationBenchMode::CHECK_ENABLED) {
+ config::enable_arrow_input_validation = true;
+ }
+
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = type->create_column();
+ throw_if_not_ok(type->get_serde()->read_column_from_arrow(
+ *column, array.get(), 0, array->length(),
cctz::utc_time_zone()));
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ config::enable_arrow_input_validation = old_config;
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_String(benchmark::State& state) {
+ auto array = make_string_array(state.range(0));
+ auto type = std::make_shared<DataTypeString>();
+
+ if constexpr (mode == ArrowValidationBenchMode::OLD) {
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = ColumnString::create();
+ read_string_old(*column, *array, 0, array->length());
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ } else {
+ run_serde_benchmark<mode>(state, type, array);
+ }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_Int64(benchmark::State& state) {
+ auto array = make_int64_array(state.range(0));
+ auto type = std::make_shared<DataTypeInt64>();
+
+ if constexpr (mode == ArrowValidationBenchMode::OLD) {
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = ColumnInt64::create();
+ read_int64_old(*column, *array, 0, array->length());
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ } else {
+ run_serde_benchmark<mode>(state, type, array);
+ }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_Boolean(benchmark::State& state) {
+ auto array = make_boolean_array(state.range(0));
+ auto type = std::make_shared<DataTypeBool>();
+
+ if constexpr (mode == ArrowValidationBenchMode::OLD) {
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = ColumnUInt8::create();
+ read_boolean_old(*column, *array, 0, array->length());
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ } else {
+ run_serde_benchmark<mode>(state, type, array);
+ }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_ArrayString(benchmark::State& state) {
+ auto array = make_string_list_array(state.range(0));
+ auto type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>());
+
+ if constexpr (mode == ArrowValidationBenchMode::OLD) {
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = type->create_column();
+ read_array_string_old(assert_cast<ColumnArray&>(*column),
*array, 0,
+ array->length());
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ } else {
+ run_serde_benchmark<mode>(state, type, array);
+ }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_MapStringInt(benchmark::State& state) {
+ auto array = make_string_int_map_array(state.range(0));
+ auto type =
std::make_shared<DataTypeMap>(std::make_shared<DataTypeString>(),
+
std::make_shared<DataTypeInt64>());
+
+ if constexpr (mode == ArrowValidationBenchMode::OLD) {
+ for (auto _ : state) {
+ const double seconds = measure_seconds([&] {
+ auto column = type->create_column();
+ read_map_string_int_old(assert_cast<ColumnMap&>(*column),
*array, 0,
+ array->length());
+ keep_column_size(column->size());
+ });
+ state.SetIterationTime(seconds);
+ }
+ } else {
+ run_serde_benchmark<mode>(state, type, array);
+ }
+}
+
+#define REGISTER_ARROW_VALIDATION_BENCH(FUNC, MODE, NAME) \
+ BENCHMARK_TEMPLATE(FUNC, ArrowValidationBenchMode::MODE) \
+ ->Name("BM_ArrowValidation_" #NAME "_" #MODE "/rows:4096") \
+ ->Arg(4096) \
+ ->UseManualTime(); \
+ BENCHMARK_TEMPLATE(FUNC, ArrowValidationBenchMode::MODE) \
+ ->Name("BM_ArrowValidation_" #NAME "_" #MODE "/rows:65536") \
+ ->Arg(65536) \
+ ->UseManualTime()
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, OLD, String);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, CHECK_DISABLED,
String);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, CHECK_ENABLED,
String);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, OLD, Int64);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, CHECK_DISABLED,
Int64);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, CHECK_ENABLED,
Int64);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, OLD, Boolean);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, CHECK_DISABLED,
Boolean);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, CHECK_ENABLED,
Boolean);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString, OLD,
ArrayString);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString,
CHECK_DISABLED, ArrayString);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString, CHECK_ENABLED,
ArrayString);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt, OLD,
MapStringInt);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt,
CHECK_DISABLED, MapStringInt);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt,
CHECK_ENABLED, MapStringInt);
+
+#undef REGISTER_ARROW_VALIDATION_BENCH
+
+} // namespace
+} // namespace doris
diff --git a/be/benchmark/benchmark_main.cpp b/be/benchmark/benchmark_main.cpp
index caf5459c46a..ff57d89c802 100644
--- a/be/benchmark/benchmark_main.cpp
+++ b/be/benchmark/benchmark_main.cpp
@@ -17,6 +17,7 @@
#include <benchmark/benchmark.h>
+#include "benchmark_arrow_validation.hpp"
#include "benchmark_bit_pack.hpp"
#include "benchmark_column_array_view.hpp"
#include "benchmark_column_array_view_distance.hpp"
diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index 59dffdf1ac3..1beaf56941f 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -71,6 +71,9 @@ DEFINE_Int32(brpc_port, "8060");
DEFINE_Int32(arrow_flight_sql_port, "8050");
+// Validate Arrow input buffers in opted-in Arrow readers before converting
them to Doris columns.
+DEFINE_Bool(enable_arrow_input_validation, "true");
+
DEFINE_Int32(cdc_client_port, "9096");
DEFINE_String(cdc_client_java_opts, "");
diff --git a/be/src/common/config.h b/be/src/common/config.h
index 033b755409c..e7b43146d68 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -119,6 +119,9 @@ DECLARE_Int32(brpc_port);
// Default -1, do not start arrow flight sql server.
DECLARE_Int32(arrow_flight_sql_port);
+// Validate Arrow input buffers in opted-in Arrow readers before converting
them to Doris columns.
+DECLARE_Bool(enable_arrow_input_validation);
+
// port for cdc client scan oltp cdc data
DECLARE_Int32(cdc_client_port);
diff --git a/be/src/core/block/block.cpp b/be/src/core/block/block.cpp
index f5208a8e071..f2de6f9cde9 100644
--- a/be/src/core/block/block.cpp
+++ b/be/src/core/block/block.cpp
@@ -341,6 +341,21 @@ Status Block::check_type_and_column() const {
return Status::OK();
}
+Status Block::check_column_and_type_not_null() const {
+ for (size_t i = 0; i != data.size(); ++i) {
+ const auto& elem = data[i];
+ if (!elem.column) {
+ return Status::InternalError("Column in block is nullptr, column
index: {}, name: {}",
+ i, elem.name);
+ }
+ if (!elem.type) {
+ return Status::InternalError("Type in block is nullptr, column
index: {}, name: {}", i,
+ elem.name);
+ }
+ }
+ return Status::OK();
+}
+
size_t Block::rows() const {
for (const auto& elem : data) {
if (elem.column) {
diff --git a/be/src/core/block/block.h b/be/src/core/block/block.h
index 5545465cef8..ffd3adc672f 100644
--- a/be/src/core/block/block.h
+++ b/be/src/core/block/block.h
@@ -186,6 +186,8 @@ public:
Status check_type_and_column() const;
+ Status check_column_and_type_not_null() const;
+
/// Approximate number of bytes used by column data in memory.
/// This reflects the actual data footprint (e.g. string contents, numeric
arrays)
/// and is the metric used by adaptive batch size byte budgets.
diff --git a/be/src/core/column/column.h b/be/src/core/column/column.h
index 9e25b42d0d1..9547ff1a7ca 100644
--- a/be/src/core/column/column.h
+++ b/be/src/core/column/column.h
@@ -446,7 +446,8 @@ public:
* // nullable -> predict_column
* // string (dictionary) -> column_dictionary
*/
- virtual Status filter_by_selector(const uint16_t* sel, size_t sel_size,
IColumn* col_ptr) {
+ virtual Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const {
throw doris::Exception(ErrorCode::NOT_IMPLEMENTED_ERROR,
"Method filter_by_selector is not supported for
{}, only "
"column_nullable, column_dictionary and
predict_column support",
diff --git a/be/src/core/column/column_decimal.h
b/be/src/core/column/column_decimal.h
index 55e429f3183..58116b942a6 100644
--- a/be/src/core/column/column_decimal.h
+++ b/be/src/core/column/column_decimal.h
@@ -149,7 +149,8 @@ public:
memset(data.data() + old_size, 0, length * sizeof(data[0]));
}
- Status filter_by_selector(const uint16_t* sel, size_t sel_size, IColumn*
col_ptr) override {
+ Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const override {
Self* output = assert_cast<Self*>(col_ptr);
auto& res_data = output->get_data();
DCHECK(res_data.empty())
diff --git a/be/src/core/column/column_dictionary.h
b/be/src/core/column/column_dictionary.h
index 3f6987d6baa..5ec603225cf 100644
--- a/be/src/core/column/column_dictionary.h
+++ b/be/src/core/column/column_dictionary.h
@@ -153,19 +153,23 @@ public:
"permute not supported in ColumnDictionary");
}
- Status filter_by_selector(const uint16_t* sel, size_t sel_size, IColumn*
col_ptr) override {
+ Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const override {
auto* res_col = assert_cast<ColumnString*>(col_ptr);
- _strings.resize(sel_size);
+ if (sel_size == 0) {
+ return Status::OK();
+ }
+ std::vector<StringRef> strings(sel_size);
size_t length = 0;
for (size_t i = 0; i != sel_size; ++i) {
- auto& value = _dict.get_value(_codes[sel[i]]);
- _strings[i].data = value.data;
- _strings[i].size = value.size;
+ const auto& value = _dict.get_value(_codes[sel[i]]);
+ strings[i].data = value.data;
+ strings[i].size = value.size;
length += value.size;
}
res_col->get_offsets().reserve(sel_size +
res_col->get_offsets().size());
res_col->get_chars().reserve(length + res_col->get_chars().size());
- res_col->insert_many_strings_without_reserve(_strings.data(),
sel_size);
+ res_col->insert_many_strings_without_reserve(strings.data(), sel_size);
return Status::OK();
}
@@ -455,7 +459,6 @@ private:
Dictionary _dict;
Container _codes;
std::pair<RowsetId, uint32_t> _rowset_segment_id;
- std::vector<StringRef> _strings;
};
} // namespace doris
diff --git a/be/src/core/column/column_nullable.cpp
b/be/src/core/column/column_nullable.cpp
index 7a560dde4b8..01d0fc072ed 100644
--- a/be/src/core/column/column_nullable.cpp
+++ b/be/src/core/column/column_nullable.cpp
@@ -471,17 +471,15 @@ size_t ColumnNullable::filter(const Filter& filter) {
return data_result_size;
}
-Status ColumnNullable::filter_by_selector(const uint16_t* sel, size_t
sel_size, IColumn* col_ptr) {
+Status ColumnNullable::filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const {
auto* nullable_col_ptr = assert_cast<ColumnNullable*>(col_ptr);
- // Access the nested column via const path to avoid assert_mutable_ref
(which requires
- // exclusive ownership). The output col_ptr was just created, so its
nested column is exclusive.
- auto nest_col_raw = const_cast<IColumn*>(
- static_cast<const
IColumn::WrappedPtr&>(nullable_col_ptr->_nested_column).get());
+ auto nested_column = nullable_col_ptr->get_nested_column_ptr();
/// `get_null_map_data` will set `_need_update_has_null` to true
auto& res_nullmap = nullable_col_ptr->get_null_map_data();
- RETURN_IF_ERROR(get_nested_column().filter_by_selector(sel, sel_size,
nest_col_raw));
+ RETURN_IF_ERROR(get_nested_column().filter_by_selector(sel, sel_size,
nested_column.get()));
DCHECK(res_nullmap.empty());
res_nullmap.resize(sel_size);
auto& cur_nullmap = get_null_map_column().get_data();
diff --git a/be/src/core/column/column_nullable.h
b/be/src/core/column/column_nullable.h
index 34ee0dffebc..3e7afb904e6 100644
--- a/be/src/core/column/column_nullable.h
+++ b/be/src/core/column/column_nullable.h
@@ -206,7 +206,8 @@ public:
size_t filter(const Filter& filter) override;
- Status filter_by_selector(const uint16_t* sel, size_t sel_size, IColumn*
col_ptr) override;
+ Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const override;
MutableColumnPtr permute(const Permutation& perm, size_t limit) const
override;
// ColumnPtr index(const IColumn & indexes, size_t limit) const
override;
int compare_at(size_t n, size_t m, const IColumn& rhs_, int
null_direction_hint) const override;
diff --git a/be/src/core/column/column_string.cpp
b/be/src/core/column/column_string.cpp
index bb921b93d1a..c8b5339ac32 100644
--- a/be/src/core/column/column_string.cpp
+++ b/be/src/core/column/column_string.cpp
@@ -372,7 +372,8 @@ size_t ColumnStr<T>::filter(const IColumn::Filter& filter) {
}
template <typename T>
-Status ColumnStr<T>::filter_by_selector(const uint16_t* sel, size_t sel_size,
IColumn* col_ptr) {
+Status ColumnStr<T>::filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const {
if constexpr (std::is_same_v<UInt32, T>) {
auto* col = static_cast<ColumnStr<T>*>(col_ptr);
Chars& res_chars = col->chars;
diff --git a/be/src/core/column/column_string.h
b/be/src/core/column/column_string.h
index 5b748ae18e9..8972a4ca210 100644
--- a/be/src/core/column/column_string.h
+++ b/be/src/core/column/column_string.h
@@ -482,7 +482,8 @@ public:
ColumnPtr filter(const IColumn::Filter& filt, ssize_t result_size_hint)
const override;
size_t filter(const IColumn::Filter& filter) override;
- Status filter_by_selector(const uint16_t* sel, size_t sel_size, IColumn*
col_ptr) override;
+ Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const override;
MutableColumnPtr permute(const IColumn::Permutation& perm, size_t limit)
const override;
diff --git a/be/src/core/column/column_vector.h
b/be/src/core/column/column_vector.h
index 22a807f6929..5112ad82a42 100644
--- a/be/src/core/column/column_vector.h
+++ b/be/src/core/column/column_vector.h
@@ -271,7 +271,8 @@ public:
void insert_value(const value_type value) { data.push_back(value); }
- Status filter_by_selector(const uint16_t* sel, size_t sel_size, IColumn*
col_ptr) override {
+ Status filter_by_selector(const uint16_t* sel, size_t sel_size,
+ IColumn* col_ptr) const override {
Self* output = assert_cast<Self*>(col_ptr);
auto& res_data = output->get_data();
DCHECK(res_data.empty())
diff --git a/be/src/core/data_type_serde/arrow_validation.h
b/be/src/core/data_type_serde/arrow_validation.h
new file mode 100644
index 00000000000..9d8b09b0cf1
--- /dev/null
+++ b/be/src/core/data_type_serde/arrow_validation.h
@@ -0,0 +1,320 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <arrow/array/array_base.h>
+#include <arrow/array/array_binary.h>
+#include <arrow/array/array_nested.h>
+#include <arrow/array/array_primitive.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "common/compiler_util.h"
+#include "common/exception.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace arrow_validation_detail {
+
+inline std::string arrow_type_name(const arrow::Array& array) {
+ return array.type() ? array.type()->name() : "unknown";
+}
+
+inline void throw_invalid_arrow(std::string_view arrow_type, std::string_view
message) {
+ throw Exception(ErrorCode::INVALID_ARGUMENT, "Invalid Arrow {}: {}",
arrow_type, message);
+}
+
+inline void throw_invalid_arrow(const arrow::Array& array, std::string_view
message) {
+ throw_invalid_arrow(arrow_type_name(array), message);
+}
+
+template <typename... Args>
+inline void throw_invalid_arrow(std::string_view arrow_type, std::string_view
format,
+ Args&&... args) {
+ throw Exception(ErrorCode::INVALID_ARGUMENT, "Invalid Arrow {}: {}",
arrow_type,
+ fmt::format(std::string(format),
std::forward<Args>(args)...));
+}
+
+template <typename... Args>
+inline void throw_invalid_arrow(const arrow::Array& array, std::string_view
format,
+ Args&&... args) {
+ throw_invalid_arrow(arrow_type_name(array), format,
std::forward<Args>(args)...);
+}
+
+inline void check_arrow_length_and_offset(const arrow::Array& array) {
+ if (UNLIKELY(array.length() < 0 || array.offset() < 0)) {
+ throw_invalid_arrow(array, "negative length or offset: length={},
offset={}",
+ array.length(), array.offset());
+ }
+}
+
+inline void check_arrow_no_offset(const arrow::Array& array) {
+ check_arrow_length_and_offset(array);
+ if (UNLIKELY(array.offset() != 0)) {
+ throw_invalid_arrow(array, "non-zero array offset is not supported:
offset={}",
+ array.offset());
+ }
+}
+
+inline void check_add_overflow(size_t left, size_t right, const arrow::Array&
array,
+ std::string_view item) {
+ if (UNLIKELY(left > std::numeric_limits<size_t>::max() - right)) {
+ throw_invalid_arrow(array, "{} size overflow", item);
+ }
+}
+
+inline std::shared_ptr<arrow::Int32Array> get_int32_offsets_array(const
arrow::Array& array) {
+ auto offsets_array = static_cast<const arrow::ListArray&>(array).offsets();
+ auto offsets = std::dynamic_pointer_cast<arrow::Int32Array>(offsets_array);
+ if (UNLIKELY(!offsets)) {
+ throw_invalid_arrow(array, "offsets array is not Int32Array");
+ }
+ return offsets;
+}
+
+} // namespace arrow_validation_detail
+
+inline void check_arrow_no_offset(const arrow::Array& array) {
+ arrow_validation_detail::check_arrow_no_offset(array);
+}
+
+// Validate the caller's requested read range before any Arrow buffer access.
+// This rejects negative or out-of-array start/end values up front.
+inline void check_arrow_array_range(const arrow::Array& array, int64_t start,
int64_t end) {
+ arrow_validation_detail::check_arrow_no_offset(array);
+ if (UNLIKELY(start < 0 || end < start || end > array.length())) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "read range is invalid: start={}, end={}, length={}",
start, end,
+ array.length());
+ }
+}
+
+// Validate buffers[0] when a validity bitmap exists. This must run before
+// IsNull()/IsValid()/null_count(), because those APIs may scan the bitmap.
+inline void check_arrow_validity_bitmap(const arrow::Array& array) {
+ arrow_validation_detail::check_arrow_length_and_offset(array);
+ const auto& buffers = array.data()->buffers;
+ if (buffers.empty() || !buffers[0]) {
+ if (UNLIKELY(array.data()->null_count > 0)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "validity bitmap is missing but null_count={}",
+ array.data()->null_count.load());
+ }
+ return;
+ }
+
+ const size_t offset = static_cast<size_t>(array.offset());
+ const size_t length = static_cast<size_t>(array.length());
+ arrow_validation_detail::check_add_overflow(offset, length, array,
"validity bitmap");
+ const size_t count = offset + length;
+ const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0);
+ const size_t available = static_cast<size_t>(buffers[0]->size());
+ if (UNLIKELY(available < required)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "validity bitmap too small: {} bytes available, {}
required", available,
+ required);
+ }
+}
+
+// Validate buffers[1] for fixed-width arrays before raw_values(), Value(), or
+// direct buffer memcpy. elem_size is the physical byte width of one value.
+inline void check_arrow_fixed_width_buffer(const arrow::Array& array, size_t
elem_size) {
+ check_arrow_validity_bitmap(array);
+ const auto& buffers = array.data()->buffers;
+ if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+ if (array.length() == 0) {
+ return;
+ }
+ arrow_validation_detail::throw_invalid_arrow(array, "data buffer is
missing");
+ }
+
+ const size_t offset = static_cast<size_t>(array.offset());
+ const size_t length = static_cast<size_t>(array.length());
+ arrow_validation_detail::check_add_overflow(offset, length, array, "data
buffer");
+ const size_t count = offset + length;
+ if (UNLIKELY(elem_size != 0 && count > std::numeric_limits<size_t>::max()
/ elem_size)) {
+ arrow_validation_detail::throw_invalid_arrow(array, "data buffer size
overflow");
+ }
+ const size_t required = elem_size * count;
+ const size_t available = static_cast<size_t>(buffers[1]->size());
+ if (UNLIKELY(available < required)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "data buffer too small: {} bytes available, {}
required", available,
+ required);
+ }
+}
+
+// Validate the bit-packed boolean data bitmap before BooleanArray::Value().
+inline void check_arrow_boolean_buffer(const arrow::Array& array) {
+ check_arrow_validity_bitmap(array);
+ const auto& buffers = array.data()->buffers;
+ if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+ if (array.length() == 0) {
+ return;
+ }
+ arrow_validation_detail::throw_invalid_arrow(array, "data bitmap is
missing");
+ }
+
+ const size_t offset = static_cast<size_t>(array.offset());
+ const size_t length = static_cast<size_t>(array.length());
+ arrow_validation_detail::check_add_overflow(offset, length, array, "data
bitmap");
+ const size_t count = offset + length;
+ const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0);
+ const size_t available = static_cast<size_t>(buffers[1]->size());
+ if (UNLIKELY(available < required)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "data bitmap too small: {} bytes available, {}
required", available,
+ required);
+ }
+}
+
+// Validate one variable-width value range after reading offsets and before
+// forming value_data() + offset.
+inline void check_arrow_value_range(const arrow::Array& array, int64_t offset,
int64_t length,
+ size_t buffer_size) {
+ if (UNLIKELY(offset < 0 || length < 0)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "value range has negative offset or length: offset={},
length={}", offset,
+ length);
+ }
+ const size_t safe_offset = static_cast<size_t>(offset);
+ const size_t safe_length = static_cast<size_t>(length);
+ if (UNLIKELY(safe_offset > buffer_size || safe_length > buffer_size -
safe_offset)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "value range exceeds data buffer: offset={}, length={},
buffer_size={}",
+ offset, length, buffer_size);
+ }
+}
+
+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
+// 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) {
+ const auto* data = reinterpret_cast<const uint8_t*>(offsets.raw_values());
+ return unaligned_load<int32_t>(data + index * sizeof(int32_t));
+}
+
+inline int64_t check_arrow_offsets_range(const arrow::Int32Array& offsets,
int64_t start,
+ int64_t end) {
+ check_arrow_array_range(offsets, 0, offsets.length());
+ check_arrow_fixed_width_buffer(offsets, sizeof(int32_t));
+ 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);
+ 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);
+ if (UNLIKELY(current_offset < previous_offset)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ offsets,
+ "offsets are not monotonically non-decreasing:
offset[{}]={} < offset[{}]={}",
+ i, current_offset, i - 1, previous_offset);
+ }
+ previous_offset = current_offset;
+ }
+ return previous_offset;
+}
+
+} // namespace arrow_validation_detail
+
+// Validate List offsets before reading offsets or recursing into values.
+inline void check_arrow_list_offsets(const arrow::ListArray& array, int64_t
start, int64_t end) {
+ 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);
+ 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 Map offsets before reading offsets or recursing into keys/items.
+inline void check_arrow_map_offsets(const arrow::MapArray& array, int64_t
start, int64_t end) {
+ 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);
+ const int64_t keys_length = array.keys() ? array.keys()->length() : 0;
+ if (UNLIKELY(last_offset > keys_length)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "offsets exceed keys length: last_offset={},
keys_length={}", last_offset,
+ keys_length);
+ }
+ const int64_t items_length = array.items() ? array.items()->length() : 0;
+ if (UNLIKELY(last_offset > items_length)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "offsets exceed items length: last_offset={},
items_length={}", last_offset,
+ items_length);
+ }
+}
+
+// Validate String/Binary offsets buffer before value_offset(), value_length(),
+// raw_value_offsets(), or manual offset reads. Variable-width Arrow arrays
need
+// offset + length + 1 offset entries.
+template <typename ArrowBinaryArray>
+inline void check_arrow_binary_offsets_buffer(const ArrowBinaryArray& array) {
+ check_arrow_validity_bitmap(array);
+ const auto& buffers = array.data()->buffers;
+ if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+ arrow_validation_detail::throw_invalid_arrow(array, "offsets buffer is
missing");
+ }
+
+ const size_t offset = static_cast<size_t>(array.offset());
+ const size_t length = static_cast<size_t>(array.length());
+ if (UNLIKELY(offset > std::numeric_limits<size_t>::max() - length)) {
+ arrow_validation_detail::throw_invalid_arrow(array, "offsets entry
count overflow");
+ }
+ const size_t count = offset + length;
+ if (UNLIKELY(count == std::numeric_limits<size_t>::max())) {
+ arrow_validation_detail::throw_invalid_arrow(array, "offsets entry
count overflow");
+ }
+ const size_t count_plus_one = count + 1;
+ if (UNLIKELY(count_plus_one > std::numeric_limits<size_t>::max() /
+ sizeof(typename
ArrowBinaryArray::offset_type))) {
+ arrow_validation_detail::throw_invalid_arrow(array, "offsets buffer
size overflow");
+ }
+
+ const size_t required = count_plus_one * sizeof(typename
ArrowBinaryArray::offset_type);
+ const size_t available = static_cast<size_t>(buffers[1]->size());
+ if (UNLIKELY(available < required)) {
+ arrow_validation_detail::throw_invalid_arrow(
+ array, "offsets buffer too small: {} bytes available, {}
required", available,
+ required);
+ }
+}
+
+} // namespace doris
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 a97c3178899..55c77c56bee 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
@@ -19,6 +19,7 @@
#include <arrow/array/builder_nested.h>
+#include "common/config.h"
#include "common/status.h"
#include "core/assert_cast.h"
#include "core/column/column.h"
@@ -27,6 +28,7 @@
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/get_least_supertype.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/complex_type_deserialize_util.h"
#include "core/string_ref.h"
#include "exprs/function/function_helpers.h"
@@ -324,6 +326,9 @@ Status DataTypeArraySerDe::read_column_from_arrow(IColumn&
column, const arrow::
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);
diff --git a/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
b/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
index c7c9eb19630..d99f8340a56 100644
--- a/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
@@ -20,10 +20,12 @@
#include <arrow/builder.h>
#include <cctz/time_zone.h>
+#include "common/config.h"
#include "common/status.h"
#include "core/column/column_const.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/data_type_number.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/value/vdatetime_value.h"
#include "exprs/function/cast/cast_base.h"
#include "exprs/function/cast/cast_to_date_or_datetime_impl.hpp"
@@ -192,6 +194,9 @@ Status
DataTypeDateSerDe<T>::_read_column_from_arrow(IColumn& column,
const arrow::Array*
arrow_array, int64_t start,
int64_t end,
const cctz::time_zone&
ctz) const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& col_data = static_cast<ColumnVector<T>&>(column).get_data();
int64_t divisor = 1;
int64_t multiplier = 1;
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 999046503fb..5b372cce23b 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
@@ -23,11 +23,13 @@
#include <chrono> // IWYU pragma: keep
#include <cstdint>
+#include "common/config.h"
#include "common/status.h"
#include "core/column/column_const.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/types.h"
#include "core/value/vdatetime_value.h"
@@ -488,6 +490,9 @@ Status
DataTypeDateTimeV2SerDe::read_column_from_arrow(IColumn& column,
const arrow::Array*
arrow_array,
int64_t start, int64_t
end,
const cctz::time_zone&
ctz) const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& col_data = static_cast<ColumnDateTimeV2&>(column).get_data();
int64_t divisor = 1;
if (arrow_array->type()->id() == arrow::Type::TIMESTAMP) {
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 04fc37a9bb3..a7151ff020c 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
@@ -23,10 +23,12 @@
#include <cstdint>
+#include "common/config.h"
#include "core/column/column_const.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/define_primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/decoded_column_view.h"
#include "core/types.h"
#include "core/value/vdatetime_value.h"
@@ -111,6 +113,9 @@ Status DataTypeDateV2SerDe::write_column_to_arrow(const
IColumn& column, const N
Status DataTypeDateV2SerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
int64_t start, int64_t end,
const cctz::time_zone& ctz)
const {
+ if (config::enable_arrow_input_validation) {
+ 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());
diff --git a/be/src/core/data_type_serde/data_type_decimal_serde.cpp
b/be/src/core/data_type_serde/data_type_decimal_serde.cpp
index 6393e9965fc..e547c67b873 100644
--- a/be/src/core/data_type_serde/data_type_decimal_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_decimal_serde.cpp
@@ -26,12 +26,14 @@
#include "arrow/type.h"
#include "common/cast_set.h"
+#include "common/config.h"
#include "common/consts.h"
#include "core/column/column.h"
#include "core/column/column_decimal.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/define_primitive_type.h"
#include "core/data_type/storage_field_type.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/decoded_column_view.h"
#include "core/types.h"
#include "exec/common/arithmetic_overflow.h"
@@ -455,6 +457,9 @@ Status
DataTypeDecimalSerDe<T>::read_column_from_arrow(IColumn& column,
const arrow::Array*
arrow_array,
int64_t start, int64_t
end,
const cctz::time_zone&
ctz) const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& column_data = static_cast<ColumnDecimal<T>&>(column).get_data();
// Decimal<Int128> for decimalv2
// Decimal<Int128I> for deicmalv3
diff --git a/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
b/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
index fdb091fe835..096715a1c6f 100644
--- a/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
@@ -19,7 +19,9 @@
#include <arrow/builder.h>
+#include "common/config.h"
#include "core/column/column_const.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/types.h"
#include "exprs/function/cast/cast_to_ip.h"
#include "exprs/function/cast/cast_to_string.h"
@@ -114,6 +116,9 @@ Status DataTypeIPv4SerDe::write_column_to_arrow(const
IColumn& column, const Nul
Status DataTypeIPv4SerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
int64_t start, int64_t end,
const cctz::time_zone& ctz)
const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& col_data = assert_cast<ColumnIPv4&>(column).get_data();
int64_t row_count = end - start;
/// buffers[0] is a null bitmap and buffers[1] are actual values
diff --git a/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
b/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
index febf802d0e0..d53577cbefc 100644
--- a/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
@@ -22,7 +22,9 @@
#include <cstddef>
#include <string>
+#include "common/config.h"
#include "core/column/column_const.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/types.h"
#include "exprs/function/cast/cast_to_ip.h"
#include "exprs/function/cast/cast_to_string.h"
@@ -143,6 +145,9 @@ Status DataTypeIPv6SerDe::write_column_to_arrow(const
IColumn& column, const Nul
Status DataTypeIPv6SerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
int64_t start, int64_t end,
const cctz::time_zone& ctz)
const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& col_data = assert_cast<ColumnIPv6&>(column).get_data();
const auto* concrete_array = assert_cast<const
arrow::StringArray*>(arrow_array);
std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
diff --git a/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
b/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
index 7dc0e4cfd30..c728532eafb 100644
--- a/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
@@ -26,8 +26,10 @@
#include <memory>
#include "arrow/array/builder_binary.h"
+#include "common/config.h"
#include "common/exception.h"
#include "common/status.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/value/jsonb_value.h"
#include "exprs/json_functions.h"
#include "util/jsonb_parser_simd.h"
@@ -121,6 +123,9 @@ Status DataTypeJsonbSerDe::write_column_to_arrow(const
IColumn& column, const Nu
Status DataTypeJsonbSerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
int64_t start, int64_t end,
const cctz::time_zone& ctz)
const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
if (arrow_array->type_id() == arrow::Type::STRING ||
arrow_array->type_id() == arrow::Type::BINARY) {
const auto* concrete_array = dynamic_cast<const
arrow::BinaryArray*>(arrow_array);
diff --git a/be/src/core/data_type_serde/data_type_map_serde.cpp
b/be/src/core/data_type_serde/data_type_map_serde.cpp
index 5f00a887ff0..1a2e0643d99 100644
--- a/be/src/core/data_type_serde/data_type_map_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_map_serde.cpp
@@ -18,11 +18,13 @@
#include "core/data_type_serde/data_type_map_serde.h"
#include "arrow/array/builder_nested.h"
+#include "common/config.h"
#include "common/exception.h"
#include "common/status.h"
#include "core/column/column.h"
#include "core/column/column_const.h"
#include "core/column/column_map.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/complex_type_deserialize_util.h"
#include "core/string_ref.h"
#include "util/jsonb_document.h"
@@ -389,6 +391,9 @@ Status DataTypeMapSerDe::read_column_from_arrow(IColumn&
column, const arrow::Ar
const auto* concrete_map = dynamic_cast<const
arrow::MapArray*>(arrow_array);
auto arrow_offsets_array = concrete_map->offsets();
auto* arrow_offsets =
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+ if (config::enable_arrow_input_validation) {
+ check_arrow_map_offsets(*concrete_map, start, end);
+ }
auto prev_size = offsets_data.back();
const auto* base_offsets_ptr = reinterpret_cast<const
uint8_t*>(arrow_offsets->raw_values());
diff --git a/be/src/core/data_type_serde/data_type_nullable_serde.cpp
b/be/src/core/data_type_serde/data_type_nullable_serde.cpp
index b805467bf15..d157fed7730 100644
--- a/be/src/core/data_type_serde/data_type_nullable_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_nullable_serde.cpp
@@ -24,11 +24,13 @@
#include <boost/iterator/iterator_facade.hpp>
#include <vector>
+#include "common/config.h"
#include "core/assert_cast.h"
#include "core/column/column.h"
#include "core/column/column_const.h"
#include "core/column/column_nullable.h"
#include "core/column/column_vector.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/data_type_serde.h"
#include "core/data_type_serde/data_type_string_serde.h"
#include "core/data_type_serde/decoded_column_view.h"
@@ -344,6 +346,10 @@ Status
DataTypeNullableSerDe::read_column_from_arrow(IColumn& column,
const arrow::Array*
arrow_array, int64_t start,
int64_t end,
const cctz::time_zone&
ctz) const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*arrow_array, start, end);
+ check_arrow_validity_bitmap(*arrow_array);
+ }
auto& col = reinterpret_cast<ColumnNullable&>(column);
NullMap& map_data = col.get_null_map_data();
for (auto i = start; i < end; ++i) {
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 7a4db5c4b4a..af6ae9d7f43 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
@@ -23,12 +23,14 @@
#include <limits>
#include <type_traits>
+#include "common/config.h"
#include "common/exception.h"
#include "common/status.h"
#include "core/column/column_nullable.h"
#include "core/data_type/define_primitive_type.h"
#include "core/data_type/primitive_type.h"
#include "core/data_type/storage_field_type.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/data_type_serde.h"
#include "core/data_type_serde/decoded_column_view.h"
#include "core/packed_int128.h"
@@ -404,13 +406,19 @@ Status
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
const arrow::Array*
arrow_array,
int64_t start, int64_t
end,
const cctz::time_zone&
ctz) const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*arrow_array, start, end);
+ }
auto row_count = end - start;
auto& col_data = static_cast<ColumnType&>(column).get_data();
// now uint8 for bool
if constexpr (T == TYPE_BOOLEAN) {
const auto* concrete_array = dynamic_cast<const
arrow::BooleanArray*>(arrow_array);
- for (size_t bool_i = 0; bool_i !=
static_cast<size_t>(concrete_array->length()); ++bool_i) {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_boolean_buffer(*concrete_array);
+ }
+ for (int64_t bool_i = start; bool_i != end; ++bool_i) {
col_data.emplace_back(concrete_array->Value(bool_i));
}
return Status::OK();
@@ -419,7 +427,11 @@ Status
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
// only for largeint(int128) type
if (arrow_array->type_id() == arrow::Type::STRING) {
const auto* concrete_array = dynamic_cast<const
arrow::StringArray*>(arrow_array);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_binary_offsets_buffer(*concrete_array);
+ }
std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
+ const size_t buffer_size = buffer ?
static_cast<size_t>(buffer->size()) : 0;
CastParameters params;
const auto* offsets_data = concrete_array->value_offsets()->data();
@@ -431,13 +443,17 @@ Status
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
memcpy(&start_offset, offsets_data + offset_i * offset_size,
offset_size);
memcpy(&end_offset, offsets_data + (offset_i + 1) *
offset_size, offset_size);
- const auto* raw_data = buffer->data() + start_offset;
const auto raw_data_len = end_offset - start_offset;
-
+ if (config::enable_arrow_input_validation) {
+ check_arrow_value_range(*concrete_array, start_offset,
raw_data_len,
+ buffer_size);
+ }
if (raw_data_len == 0) {
col_data.emplace_back(
typename PrimitiveTypeTraits<T>::CppType()); //
Int128() is NULL
} else {
+ const auto* raw_data =
+ reinterpret_cast<const char*>(buffer->data() +
start_offset);
if constexpr (T == TYPE_DATETIMEV2 || T ==
TYPE_TIMESTAMPTZ) {
StringRef str_ref(raw_data, raw_data_len);
UInt64 val = 0;
@@ -488,6 +504,10 @@ Status
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
}
/// buffers[0] is a null bitmap and buffers[1] are actual values
+ if (config::enable_arrow_input_validation) {
+ check_arrow_fixed_width_buffer(*arrow_array,
+ sizeof(typename
PrimitiveTypeTraits<T>::CppType));
+ }
std::shared_ptr<arrow::Buffer> buffer = arrow_array->data()->buffers[1];
// Handle empty array case: buffer can be null when row_count is 0.
diff --git a/be/src/core/data_type_serde/data_type_string_serde.cpp
b/be/src/core/data_type_serde/data_type_string_serde.cpp
index 7f9a82c23cc..80cf74634f0 100644
--- a/be/src/core/data_type_serde/data_type_string_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_string_serde.cpp
@@ -20,8 +20,10 @@
#include <array>
#include <cstring>
+#include "common/config.h"
#include "core/column/column_string.h"
#include "core/data_type/define_primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/decoded_column_view.h"
#include "util/jsonb_document_cast.h"
#include "util/jsonb_utils.h"
@@ -406,7 +408,12 @@ Status
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
if (arrow_array->type_id() == arrow::Type::STRING ||
arrow_array->type_id() == arrow::Type::BINARY) {
const auto* concrete_array = dynamic_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);
+ }
std::shared_ptr<arrow::Buffer> 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();
const size_t offset_size = sizeof(int32_t);
@@ -418,16 +425,23 @@ Status
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
memcpy(&end_offset, offsets_data + (offset_i + 1) *
offset_size, offset_size);
int32_t length = end_offset - start_offset;
- const auto* raw_data = buffer->data() + start_offset;
-
- assert_cast<ColumnType&>(column).insert_data(
- reinterpret_cast<const char*>(raw_data), length);
+ if (config::enable_arrow_input_validation) {
+ check_arrow_value_range(*concrete_array, start_offset,
length, buffer_size);
+ }
+ // insert_data() does not read the input pointer when length
is zero.
+ const auto* raw_data = reinterpret_cast<const
char*>(buffer->data() + start_offset);
+ assert_cast<ColumnType&>(column).insert_data(raw_data, length);
} else {
assert_cast<ColumnType&>(column).insert_default();
}
}
} else if (arrow_array->type_id() == arrow::Type::FIXED_SIZE_BINARY) {
const auto* concrete_array = dynamic_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()));
+ }
uint32_t width = concrete_array->byte_width();
for (auto offset_i = start; offset_i < end; ++offset_i) {
@@ -441,13 +455,24 @@ Status
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
} else if (arrow_array->type_id() == arrow::Type::LARGE_STRING ||
arrow_array->type_id() == arrow::Type::LARGE_BINARY) {
const auto* concrete_array = dynamic_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);
+ }
std::shared_ptr<arrow::Buffer> 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* raw_data = buffer->data() +
concrete_array->value_offset(offset_i);
- assert_cast<ColumnType&>(column).insert_data(
- (char*)raw_data,
concrete_array->value_length(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);
+ }
+ // insert_data() does not read the input pointer when length
is zero.
+ const auto* raw_data = reinterpret_cast<const
char*>(buffer->data() + value_offset);
+ assert_cast<ColumnType&>(column).insert_data(raw_data,
value_length);
} else {
assert_cast<ColumnType&>(column).insert_default();
}
diff --git a/be/src/core/data_type_serde/data_type_struct_serde.cpp
b/be/src/core/data_type_serde/data_type_struct_serde.cpp
index ee0643bc6aa..e03aa9cb963 100644
--- a/be/src/core/data_type_serde/data_type_struct_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_struct_serde.cpp
@@ -20,10 +20,12 @@
#include <algorithm>
#include "arrow/array/builder_nested.h"
+#include "common/config.h"
#include "common/status.h"
#include "core/column/column.h"
#include "core/column/column_const.h"
#include "core/column/column_struct.h"
+#include "core/data_type_serde/arrow_validation.h"
#include "core/data_type_serde/complex_type_deserialize_util.h"
#include "core/data_type_serde/data_type_serde.h"
#include "core/string_ref.h"
@@ -418,6 +420,9 @@ Status DataTypeStructSerDe::write_column_to_arrow(const
IColumn& column, const N
Status DataTypeStructSerDe::read_column_from_arrow(IColumn& column, const
arrow::Array* arrow_array,
int64_t start, int64_t end,
const cctz::time_zone& ctz)
const {
+ if (config::enable_arrow_input_validation) {
+ check_arrow_no_offset(*arrow_array);
+ }
auto& struct_column = static_cast<ColumnStruct&>(column);
const auto* concrete_struct = dynamic_cast<const
arrow::StructArray*>(arrow_array);
DCHECK_EQ(struct_column.tuple_size(), concrete_struct->num_fields());
diff --git a/be/src/exec/operator/operator.h b/be/src/exec/operator/operator.h
index e8ee7195467..8c63b9d8a71 100644
--- a/be/src/exec/operator/operator.h
+++ b/be/src/exec/operator/operator.h
@@ -634,6 +634,7 @@ public:
}
[[nodiscard]] Status sink(RuntimeState* state, Block* block, bool eos) {
+ RETURN_IF_ERROR(block->check_column_and_type_not_null());
RETURN_IF_ERROR(block->check_type_and_column());
return sink_impl(state, block, eos);
}
@@ -881,6 +882,7 @@ public:
Status terminate(RuntimeState* state) override;
[[nodiscard]] Status get_block(RuntimeState* state, Block* block, bool*
eos) {
RETURN_IF_ERROR(get_block_impl(state, block, eos));
+ RETURN_IF_ERROR(block->check_column_and_type_not_null());
RETURN_IF_ERROR(block->check_type_and_column());
return Status::OK();
}
diff --git a/be/src/exprs/function/cast/cast_to_decimal.h
b/be/src/exprs/function/cast/cast_to_decimal.h
index 17e9af68723..7a65d07a5db 100644
--- a/be/src/exprs/function/cast/cast_to_decimal.h
+++ b/be/src/exprs/function/cast/cast_to_decimal.h
@@ -781,6 +781,17 @@ public:
params.is_strict = (CastMode == CastModeType::StrictMode);
size_t size = vec_from.size();
+ if constexpr (IsDataTypeDecimalV3<ToDataType>) {
+ if (to_scale == 0 && !narrow_integral) {
+ for (size_t i = 0; i < size; i++) {
+ vec_to_data[i].value =
+ static_cast<typename
ToFieldType::NativeType>(vec_from_data[i]);
+ }
+ block.get_by_position(result).column = std::move(col_to);
+ return Status::OK();
+ }
+ }
+
RETURN_IF_ERROR(std::visit(
[&](auto multiply_may_overflow, auto narrow_integral) {
for (size_t i = 0; i < size; i++) {
diff --git a/be/test/core/column/column_dictionary_test.cpp
b/be/test/core/column/column_dictionary_test.cpp
index e802eae7541..389485e2181 100644
--- a/be/test/core/column/column_dictionary_test.cpp
+++ b/be/test/core/column/column_dictionary_test.cpp
@@ -248,8 +248,9 @@ TEST_F(ColumnDictionaryTest, permute) {
}
TEST_F(ColumnDictionaryTest, filter_by_selector) {
auto test_func = [&](const auto& source_column) {
- auto src_size = source_column->size();
- const auto& codes_data = source_column->get_data();
+ const auto& source = *source_column;
+ auto src_size = source.size();
+ const auto& codes_data = source.get_data();
EXPECT_TRUE(src_size <= UINT16_MAX);
auto target_column = ColumnString::create();
@@ -262,13 +263,12 @@ TEST_F(ColumnDictionaryTest, filter_by_selector) {
size_t sel_size = src_size / 2;
indices.resize(sel_size);
- auto status =
- source_column->filter_by_selector(indices.data(), sel_size,
target_column.get());
+ auto status = source.filter_by_selector(indices.data(), sel_size,
target_column.get());
EXPECT_TRUE(status.ok());
EXPECT_EQ(target_column->size(), sel_size);
for (size_t i = 0; i != sel_size; ++i) {
auto real_data = target_column->get_data_at(i);
- auto expect_data =
source_column->get_value(codes_data[indices[i]]);
+ auto expect_data = source.get_value(codes_data[indices[i]]);
if (real_data != expect_data) {
std::cout << "index: " << i << ", real_data: " <<
real_data.to_string()
<< "\nexpect_data: " << expect_data.to_string() <<
std::endl;
@@ -364,4 +364,4 @@ std::vector<StringRef> ColumnDictionaryTest::dict_array;
ColumnDictI32::MutablePtr ColumnDictionaryTest::column_dict_char;
ColumnDictI32::MutablePtr ColumnDictionaryTest::column_dict_varchar;
ColumnDictI32::MutablePtr ColumnDictionaryTest::column_dict_str;
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git a/be/test/core/column/column_nullable_test.cpp
b/be/test/core/column/column_nullable_test.cpp
index 96db41296ef..77f167c9ea8 100644
--- a/be/test/core/column/column_nullable_test.cpp
+++ b/be/test/core/column/column_nullable_test.cpp
@@ -113,7 +113,8 @@ TEST(ColumnNullableTest, PredicateTest) {
EXPECT_FALSE(null_dst->has_null());
uint16_t selector[] = {5, 8}; // both null
- EXPECT_EQ(nullable_pred->filter_by_selector(selector, 2, null_dst.get()),
Status::OK());
+ const IColumn& nullable_src = *nullable_pred;
+ EXPECT_EQ(nullable_src.filter_by_selector(selector, 2, null_dst.get()),
Status::OK());
// filter_by_selector must announce to update has_null to make below right.
EXPECT_TRUE(null_dst->has_null());
}
diff --git a/be/test/core/column/column_string_test.cpp
b/be/test/core/column/column_string_test.cpp
index 3581be59893..dcd57487c84 100644
--- a/be/test/core/column/column_string_test.cpp
+++ b/be/test/core/column/column_string_test.cpp
@@ -993,8 +993,8 @@ TEST_F(ColumnStringTest, filter_by_selector) {
}
std::cout << std::endl;
- auto status =
- source_column->filter_by_selector(indices.data(), sel_size,
target_column.get());
+ const auto& source = *source_column;
+ auto status = source.filter_by_selector(indices.data(), sel_size,
target_column.get());
EXPECT_TRUE(status.ok());
EXPECT_EQ(target_column->size(), sel_size);
for (size_t i = 0; i != sel_size; ++i) {
diff --git a/be/test/core/data_type/block_check_type.cpp
b/be/test/core/data_type/block_check_type.cpp
index 2756d8714c7..6915aabb92f 100644
--- a/be/test/core/data_type/block_check_type.cpp
+++ b/be/test/core/data_type/block_check_type.cpp
@@ -39,4 +39,22 @@ TEST(BlockCheckType, test1) {
EXPECT_FALSE(st.ok());
std::cout << st.msg() << std::endl;
}
-} // namespace doris
\ No newline at end of file
+
+TEST(BlockCheckType, CheckColumnAndTypeNotNull) {
+ auto block = Block {
+ ColumnHelper::create_column_with_name<DataTypeInt32>({1, 2, 3, 4}),
+ ColumnHelper::create_column_with_name<DataTypeInt64>({1, 2, 3, 4}),
+ };
+
+ EXPECT_TRUE(block.check_column_and_type_not_null());
+
+ block.get_by_position(0).column = nullptr;
+ auto st = block.check_column_and_type_not_null();
+ EXPECT_FALSE(st.ok());
+
+ block.get_by_position(0).column =
ColumnHelper::create_column<DataTypeInt32>({1, 2, 3, 4});
+ block.get_by_position(1).type = nullptr;
+ st = block.check_column_and_type_not_null();
+ EXPECT_FALSE(st.ok());
+}
+} // 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
new file mode 100644
index 00000000000..fdce7af8f89
--- /dev/null
+++ b/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
@@ -0,0 +1,275 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <arrow/api.h>
+#include <cctz/time_zone.h>
+#include <gtest/gtest-message.h>
+#include <gtest/gtest-test-part.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "common/config.h"
+#include "common/exception.h"
+#include "core/column/column_array.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.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_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"
+
+namespace doris {
+namespace {
+
+class ScopedArrowInputValidation {
+public:
+ explicit ScopedArrowInputValidation(bool enabled)
+ : _old_value(config::enable_arrow_input_validation) {
+ config::enable_arrow_input_validation = enabled;
+ }
+
+ ~ScopedArrowInputValidation() { config::enable_arrow_input_validation =
_old_value; }
+
+private:
+ bool _old_value;
+};
+
+template <typename Func>
+void expect_invalid_arrow(Func&& func, std::string_view message) {
+ bool thrown = false;
+ try {
+ std::forward<Func>(func)();
+ } catch (const Exception& e) {
+ thrown = true;
+ EXPECT_EQ(e.code(), ErrorCode::INVALID_ARGUMENT) << e.to_string();
+ }
+ EXPECT_TRUE(thrown) << message;
+}
+
+std::shared_ptr<arrow::Buffer> wrap_offsets(const std::vector<int32_t>&
offsets) {
+ return arrow::Buffer::Wrap(offsets);
+}
+
+struct StringArrayHolder {
+ StringArrayHolder(std::vector<int32_t> offsets_, std::string_view values_)
+ : offsets(std::move(offsets_)), values(values_) {
+ auto value_buffer = arrow::Buffer::Wrap(values.data(), values.size());
+ array = std::make_shared<arrow::StringArray>(offsets.size() - 1,
wrap_offsets(offsets),
+ value_buffer);
+ }
+
+ std::vector<int32_t> offsets;
+ std::string values;
+ std::shared_ptr<arrow::StringArray> array;
+};
+
+} // namespace
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortStringOffsetsBuffer) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<int32_t> offsets = {0};
+ std::string_view values = "abc";
+ auto value_buffer = arrow::Buffer::Wrap(values.data(), values.size());
+ auto array = std::make_shared<arrow::StringArray>(1,
wrap_offsets(offsets), value_buffer);
+ auto column = ColumnString::create();
+ DataTypeStringSerDe serde(TYPE_STRING);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 1,
+
cctz::utc_time_zone()));
+ },
+ "short string offsets buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsStringValueRangeBeyondBuffer) {
+ ScopedArrowInputValidation validation(true);
+
+ StringArrayHolder array({0, 8}, "abc");
+ auto column = ColumnString::create();
+ DataTypeStringSerDe serde(TYPE_STRING);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.array.get(), 0, 1,
+
cctz::utc_time_zone()));
+ },
+ "string value range beyond data buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsNonMonotonicStringOffsets) {
+ ScopedArrowInputValidation validation(true);
+
+ StringArrayHolder array({3, 1}, "abcd");
+ auto column = ColumnString::create();
+ DataTypeStringSerDe serde(TYPE_STRING);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.array.get(), 0, 1,
+
cctz::utc_time_zone()));
+ },
+ "non-monotonic string offsets should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortFixedWidthDataBuffer) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<int64_t> values = {1};
+ auto data_buffer = arrow::Buffer::Wrap(values);
+ auto array = std::make_shared<arrow::Int64Array>(2, data_buffer);
+ auto column = ColumnInt64::create();
+ DataTypeNumberSerDe<TYPE_BIGINT> serde;
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ "short int64 data buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsSlicedArrowArray) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<int64_t> values = {1, 2, 3};
+ auto original = std::make_shared<arrow::Int64Array>(3,
arrow::Buffer::Wrap(values));
+ auto sliced = original->Slice(1, 2);
+ auto column = ColumnInt64::create();
+ DataTypeNumberSerDe<TYPE_BIGINT> serde;
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
sliced.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ "sliced Arrow array should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortBooleanDataBitmap) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<uint8_t> bits = {0xFF};
+ auto data_buffer = arrow::Buffer::Wrap(bits);
+ auto array = std::make_shared<arrow::BooleanArray>(9, data_buffer);
+ auto column = ColumnUInt8::create();
+ DataTypeNumberSerDe<TYPE_BOOLEAN> serde;
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 9,
+
cctz::utc_time_zone()));
+ },
+ "short boolean data bitmap should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortValidityBitmap) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<uint8_t> validity = {0xFF};
+ std::vector<int64_t> values(9, 1);
+ auto validity_buffer = arrow::Buffer::Wrap(validity);
+ auto data_buffer = arrow::Buffer::Wrap(values);
+ auto array = std::make_shared<arrow::Int64Array>(9, data_buffer,
validity_buffer);
+ auto column = ColumnNullable::create(ColumnInt64::create(),
ColumnUInt8::create());
+ auto nested_serde = std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>();
+ DataTypeNullableSerDe serde(nested_serde);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 9,
+
cctz::utc_time_zone()));
+ },
+ "short validity bitmap should be rejected before IsNull");
+}
+
+TEST(DataTypeSerDeArrowValidationTest,
RejectsMissingValidityBitmapWithNullCount) {
+ ScopedArrowInputValidation validation(true);
+
+ std::vector<int64_t> values = {1, 2};
+ auto data_buffer = arrow::Buffer::Wrap(values);
+ auto array = std::make_shared<arrow::Int64Array>(2, data_buffer,
+
std::shared_ptr<arrow::Buffer>(), 1);
+ auto column = ColumnNullable::create(ColumnInt64::create(),
ColumnUInt8::create());
+ auto nested_serde = std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>();
+ DataTypeNullableSerDe serde(nested_serde);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 2,
+
cctz::utc_time_zone()));
+ },
+ "missing validity bitmap with positive null_count should be
rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsListOffsetsBeyondValuesLength) {
+ ScopedArrowInputValidation validation(true);
+
+ StringArrayHolder values({0, 1}, "a");
+ std::vector<int32_t> offsets = {0, 2};
+ auto offsets_buffer = wrap_offsets(offsets);
+ auto array =
std::make_shared<arrow::ListArray>(arrow::list(arrow::utf8()), 1,
offsets_buffer,
+ values.array);
+ auto column = ColumnArray::create(
+ ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create()),
+ ColumnOffset64::create());
+ auto nested_serde = std::make_shared<DataTypeNullableSerDe>(
+ std::make_shared<DataTypeStringSerDe>(TYPE_STRING));
+ DataTypeArraySerDe serde(nested_serde);
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 1,
+
cctz::utc_time_zone()));
+ },
+ "list offsets beyond values length should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsMapOffsetsBeyondKeysLength) {
+ ScopedArrowInputValidation validation(true);
+
+ StringArrayHolder keys({0, 1}, "k");
+ std::vector<int64_t> item_values = {1};
+ auto items = std::make_shared<arrow::Int64Array>(1,
arrow::Buffer::Wrap(item_values));
+ std::vector<int32_t> offsets = {0, 2};
+ auto offsets_buffer = wrap_offsets(offsets);
+ auto array = std::make_shared<arrow::MapArray>(arrow::map(arrow::utf8(),
arrow::int64()), 1,
+ offsets_buffer, keys.array,
items);
+ auto column = ColumnMap::create(ColumnString::create(),
ColumnInt64::create(),
+ ColumnOffset64::create());
+ DataTypeMapSerDe serde(std::make_shared<DataTypeStringSerDe>(TYPE_STRING),
+
std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>());
+
+ expect_invalid_arrow(
+ [&] {
+ static_cast<void>(serde.read_column_from_arrow(*column,
array.get(), 0, 1,
+
cctz::utc_time_zone()));
+ },
+ "map offsets beyond keys length should be rejected");
+}
+
+} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]