This is an automated email from the ASF dual-hosted git repository.
luwei16 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 f4c458948ac [fix](binlog) Avoid mutating source columns during OLAP
conversion (#66648)
f4c458948ac is described below
commit f4c458948ac3b15860f70ccc71a5e4722759ab3c
Author: Luwei <[email protected]>
AuthorDate: Sat Aug 15 20:57:02 2026 +0800
[fix](binlog) Avoid mutating source columns during OLAP conversion (#66648)
### What problem does this PR solve?
Issue Number: close #66368
Related PR: None
Problem Summary: ROW binlog DATA and ROW_BINLOG flush tasks can convert
the same Block concurrently. The simple OLAP converter requested mutable
ownership for every nullable nested column and normalized floating-point
NaNs in place, so shared columns could trip the COW ownership assertion
and source data could be modified. Read simple source columns through
const pointers, retain zero-copy slices for non-floating and NaN-free
floating columns, and normalize NaNs only in a reusable converter-owned
slice buffer.
### Release note
Fix ROW binlog loads that could fail with a COW ownership assertion
during concurrent segment flush.
### Check List (For Author)
- Test: Unit Test
- ./run-be-ut.sh --run --filter=OlapDataConvertorTest.*
- Behavior changed: Yes. OLAP conversion no longer mutates source
columns while preserving normalized floating-point NaN output.
- Does this need documentation: No
---
be/src/storage/iterator/olap_data_convertor.h | 51 ++-
.../storage/iterator/olap_data_convertor_test.cpp | 405 +++++++++++++++++++++
2 files changed, 437 insertions(+), 19 deletions(-)
diff --git a/be/src/storage/iterator/olap_data_convertor.h
b/be/src/storage/iterator/olap_data_convertor.h
index 81614010e7b..99b24c48e84 100644
--- a/be/src/storage/iterator/olap_data_convertor.h
+++ b/be/src/storage/iterator/olap_data_convertor.h
@@ -23,6 +23,8 @@
#include <string.h>
#include <algorithm>
+#include <cmath>
+#include <limits>
#include <memory>
#include <ostream>
#include <utility>
@@ -301,36 +303,49 @@ private:
}
Status convert_to_olap() override {
- typename PrimitiveTypeTraits<T>::ColumnType* column_data = nullptr;
+ using ColumnType = typename PrimitiveTypeTraits<T>::ColumnType;
+
+ const ColumnType* column_data = nullptr;
if (_nullmap) {
- auto nullable_column =
+ const auto* nullable_column =
assert_cast<const
ColumnNullable*>(_typed_column.column.get());
- _mutable_column =
std::move(nullable_column->get_nested_column()).mutate();
- column_data = assert_cast<typename
PrimitiveTypeTraits<T>::ColumnType*>(
- _mutable_column.get());
+ column_data = assert_cast<const
ColumnType*>(&nullable_column->get_nested_column());
} else {
- _mutable_column = std::move(*_typed_column.column).mutate();
- column_data = assert_cast<typename
PrimitiveTypeTraits<T>::ColumnType*>(
- _mutable_column.get());
+ column_data = assert_cast<const
ColumnType*>(_typed_column.column.get());
}
assert(column_data);
- _values = (typename
PrimitiveTypeTraits<T>::CppType*)(column_data->get_data().data()) +
- _row_pos;
+ _values = column_data->get_data().data() + _row_pos;
+ // DATA and ROW_BINLOG flush tasks may convert the same source
Block concurrently,
+ // so this converter must never mutate the source column or its
nullable nested column.
+ // Most simple types can expose the requested source slice
directly with zero copy.
+ // FLOAT/DOUBLE are special because storage requires a canonical
NaN representation:
+ // scan only the requested slice and keep the zero-copy path when
it contains no NaN;
+ // otherwise, normalize a copy in the converter-owned reusable
buffer. The buffer is a
+ // member because SegmentWriter may read the accessor after
convert_to_olap() returns.
if constexpr (T == TYPE_FLOAT || T == TYPE_DOUBLE) {
- for (size_t i = 0; i < _num_rows; ++i) {
- if (std::isnan(_values[i])) {
- _values[i] = std::numeric_limits<
- typename
PrimitiveTypeTraits<T>::CppType>::quiet_NaN();
+ _converted_values.clear();
+ const CppType* values_end = _values + _num_rows;
+ const CppType* first_nan = std::find_if(
+ _values, values_end, [](CppType value) { return
std::isnan(value); });
+ if (UNLIKELY(first_nan != values_end)) {
+ _converted_values.assign(_values, values_end);
+ auto* converted_value = _converted_values.data() +
(first_nan - _values);
+ for (; converted_value != _converted_values.end();
++converted_value) {
+ if (std::isnan(*converted_value)) {
+ *converted_value =
std::numeric_limits<CppType>::quiet_NaN();
+ }
}
+ _values = _converted_values.data();
}
}
return Status::OK();
}
protected:
- typename PrimitiveTypeTraits<T>::CppType* _values = nullptr;
- MutableColumnPtr _mutable_column = nullptr;
+ using CppType = typename PrimitiveTypeTraits<T>::CppType;
+ const CppType* _values = nullptr;
+ PaddedPODArray<CppType> _converted_values;
};
class OlapColumnDataConvertorDateV2 : public OlapColumnDataConvertorBase {
@@ -435,9 +450,7 @@ private:
}
assert(column_data);
- this->_values =
- (typename
PrimitiveTypeTraits<T>::CppType*)(column_data->get_data().data()) +
- this->_row_pos;
+ this->_values = column_data->get_data().data() + this->_row_pos;
return Status::OK();
}
};
diff --git a/be/test/storage/iterator/olap_data_convertor_test.cpp
b/be/test/storage/iterator/olap_data_convertor_test.cpp
new file mode 100644
index 00000000000..4b775cd98f4
--- /dev/null
+++ b/be/test/storage/iterator/olap_data_convertor_test.cpp
@@ -0,0 +1,405 @@
+// 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 "storage/iterator/olap_data_convertor.h"
+
+#include <gtest/gtest.h>
+
+#include <barrier>
+#include <bit>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <thread>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "core/column/column_array.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_number.h"
+#include "storage/tablet/tablet_schema.h"
+
+namespace doris {
+namespace {
+
+template <PrimitiveType T>
+using CppType = typename PrimitiveTypeTraits<T>::CppType;
+
+template <PrimitiveType T>
+using SimpleConvertor =
OlapBlockDataConvertor::OlapColumnDataConvertorSimple<T>;
+
+static_assert(std::is_same_v<decltype(SimpleConvertor<TYPE_BIGINT>::_values),
const int64_t*>);
+
+template <PrimitiveType T>
+struct NumericSource {
+ ColumnWithTypeAndName typed_column;
+ const CppType<T>* values;
+ const IColumn* nested_column;
+};
+
+template <PrimitiveType T>
+NumericSource<T> create_numeric_source(std::vector<CppType<T>> values,
+ std::vector<UInt8> null_map = {}) {
+ auto nested_column = PrimitiveTypeTraits<T>::ColumnType::create();
+ nested_column->get_data().assign(values.begin(), values.end());
+ const CppType<T>* source_values = nested_column->get_data().data();
+ const IColumn* source_nested_column = nested_column.get();
+
+ DataTypePtr data_type = std::make_shared<typename
PrimitiveTypeTraits<T>::DataType>();
+ ColumnPtr column;
+ if (null_map.empty()) {
+ column = std::move(nested_column);
+ } else {
+ auto null_map_column = ColumnUInt8::create();
+ null_map_column->get_data().assign(null_map.begin(), null_map.end());
+ column = ColumnNullable::create(std::move(nested_column),
std::move(null_map_column));
+ data_type = std::make_shared<DataTypeNullable>(data_type);
+ }
+ return {{std::move(column), std::move(data_type), "value"},
+ source_values,
+ source_nested_column};
+}
+
+TabletColumn create_tablet_column(FieldType type, bool nullable) {
+ TabletColumn column(FieldAggregationMethod::OLAP_FIELD_AGGREGATION_NONE,
type, nullable);
+ column.set_unique_id(0);
+ column.set_name("value");
+ const int32_t length =
+ type == FieldType::OLAP_FIELD_TYPE_FLOAT ? sizeof(float) :
sizeof(int64_t);
+ column.set_length(length);
+ column.set_index_length(length);
+ return column;
+}
+
+template <PrimitiveType T>
+FieldType field_type() {
+ if constexpr (T == TYPE_BIGINT) {
+ return FieldType::OLAP_FIELD_TYPE_BIGINT;
+ } else if constexpr (T == TYPE_FLOAT) {
+ return FieldType::OLAP_FIELD_TYPE_FLOAT;
+ } else {
+ static_assert(T == TYPE_DOUBLE);
+ return FieldType::OLAP_FIELD_TYPE_DOUBLE;
+ }
+}
+
+template <PrimitiveType T>
+SimpleConvertor<T>* get_simple_convertor(OlapBlockDataConvertor& convertor) {
+ return assert_cast<SimpleConvertor<T>*>(convertor._convertors[0].get());
+}
+
+template <PrimitiveType T>
+std::pair<Status, IOlapColumnDataAccessor*> convert(OlapBlockDataConvertor&
convertor,
+ const NumericSource<T>&
source, size_t row_pos,
+ size_t num_rows) {
+ auto status =
convertor.set_source_content_with_specifid_column(source.typed_column, row_pos,
+ num_rows,
0);
+ if (!status.ok()) {
+ return {std::move(status), nullptr};
+ }
+ return convertor.convert_column_data(0);
+}
+
+template <typename T>
+using UIntOfSize = std::conditional_t<sizeof(T) == sizeof(uint32_t), uint32_t,
uint64_t>;
+
+template <typename T>
+T from_bits(UIntOfSize<T> bits) {
+ return std::bit_cast<T>(bits);
+}
+
+template <typename T>
+UIntOfSize<T> bits_of(T value) {
+ return std::bit_cast<UIntOfSize<T>>(value);
+}
+
+template <PrimitiveType T>
+// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest
assertions inflate it.
+void expect_float_without_nan_is_zero_copy(bool nullable) {
+ using Value = CppType<T>;
+ const std::vector<Value> values = {Value(1.25), Value(-2.5), Value(0),
Value(9.75)};
+ auto source = create_numeric_source<T>(
+ values, nullable ? std::vector<UInt8> {0, 1, 0, 0} :
std::vector<UInt8> {});
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(create_tablet_column(field_type<T>(),
nullable));
+
+ auto [status, accessor] = convert(convertor, source, 1, 2);
+ ASSERT_TRUE(status.ok()) << status;
+ ASSERT_NE(nullptr, accessor);
+ EXPECT_EQ(source.values + 1, accessor->get_data());
+ EXPECT_EQ(Value(-2.5), static_cast<const Value*>(accessor->get_data())[0]);
+ EXPECT_EQ(Value(0), static_cast<const Value*>(accessor->get_data())[1]);
+ if (nullable) {
+ ASSERT_NE(nullptr, accessor->get_nullmap());
+ EXPECT_EQ(1, accessor->get_nullmap()[0]);
+ EXPECT_EQ(nullptr, accessor->get_data_at(0));
+ EXPECT_EQ(source.values + 2, accessor->get_data_at(1));
+ } else {
+ EXPECT_EQ(nullptr, accessor->get_nullmap());
+ }
+ EXPECT_EQ(0,
get_simple_convertor<T>(convertor)->_converted_values.allocated_bytes());
+ EXPECT_EQ(values, std::vector<Value>(source.values, source.values +
values.size()));
+}
+
+template <PrimitiveType T>
+// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest
assertions inflate it.
+void expect_nan_payloads_are_normalized_without_source_mutation() {
+ using Value = CppType<T>;
+ using UInt = UIntOfSize<Value>;
+ const UInt nan_bits_1 =
+ sizeof(Value) == sizeof(float) ? UInt(0x7fc00011U) :
UInt(0x7ff8000000000011ULL);
+ const UInt nan_bits_2 =
+ sizeof(Value) == sizeof(float) ? UInt(0x7fa12345U) :
UInt(0x7ff123456789abcdULL);
+ const UInt nan_bits_3 =
+ sizeof(Value) == sizeof(float) ? UInt(0xffc54321U) :
UInt(0xfff8123456789abcULL);
+ const std::vector<Value> values = {Value(1.5),
from_bits<Value>(nan_bits_1),
+ from_bits<Value>(nan_bits_2),
Value(-8.25),
+ from_bits<Value>(nan_bits_3)};
+ std::vector<UInt> source_bits;
+ for (Value value : values) {
+ source_bits.push_back(bits_of(value));
+ }
+
+ auto source = create_numeric_source<T>(values, {0, 0, 1, 0, 0});
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(create_tablet_column(field_type<T>(),
true));
+
+ auto [status, accessor] = convert(convertor, source, 0, values.size());
+ ASSERT_TRUE(status.ok()) << status;
+ ASSERT_NE(nullptr, accessor);
+ ASSERT_NE(source.values, accessor->get_data());
+ const auto* converted_values = static_cast<const
Value*>(accessor->get_data());
+ const UInt quiet_nan_bits =
bits_of(std::numeric_limits<Value>::quiet_NaN());
+ EXPECT_EQ(bits_of(Value(1.5)), bits_of(converted_values[0]));
+ EXPECT_EQ(quiet_nan_bits, bits_of(converted_values[1]));
+ EXPECT_EQ(quiet_nan_bits, bits_of(converted_values[2]));
+ EXPECT_EQ(bits_of(Value(-8.25)), bits_of(converted_values[3]));
+ EXPECT_EQ(quiet_nan_bits, bits_of(converted_values[4]));
+ EXPECT_EQ(nullptr, accessor->get_data_at(2));
+
+ for (size_t i = 0; i < source_bits.size(); ++i) {
+ EXPECT_EQ(source_bits[i], bits_of(source.values[i]));
+ }
+ EXPECT_EQ(values.size(),
get_simple_convertor<T>(convertor)->_converted_values.size());
+}
+
+} // namespace
+
+TEST(OlapDataConvertorTest, ConcurrentNullableBigIntSourceIsReadOnly) {
+ const std::vector<int64_t> values = {10, 20, 30, 40};
+ auto source = create_numeric_source<TYPE_BIGINT>(values, {0, 1, 0, 0});
+ ASSERT_EQ(1, source.nested_column->use_count());
+
+ OlapBlockDataConvertor data_convertor;
+ OlapBlockDataConvertor row_binlog_convertor;
+ const TabletColumn tablet_column =
+ create_tablet_column(FieldType::OLAP_FIELD_TYPE_BIGINT, true);
+ data_convertor.add_column_data_convertor(tablet_column);
+ row_binlog_convertor.add_column_data_convertor(tablet_column);
+ ASSERT_TRUE(data_convertor
+
.set_source_content_with_specifid_column(source.typed_column, 0,
+
values.size(), 0)
+ .ok());
+ ASSERT_TRUE(row_binlog_convertor
+
.set_source_content_with_specifid_column(source.typed_column, 0,
+
values.size(), 0)
+ .ok());
+
+ std::barrier start(2);
+ std::pair<Status, IOlapColumnDataAccessor*> data_result;
+ std::pair<Status, IOlapColumnDataAccessor*> row_binlog_result;
+ std::thread data_thread([&] {
+ start.arrive_and_wait();
+ data_result = data_convertor.convert_column_data(0);
+ });
+ std::thread row_binlog_thread([&] {
+ start.arrive_and_wait();
+ row_binlog_result = row_binlog_convertor.convert_column_data(0);
+ });
+ data_thread.join();
+ row_binlog_thread.join();
+
+ ASSERT_TRUE(data_result.first.ok()) << data_result.first;
+ ASSERT_TRUE(row_binlog_result.first.ok()) << row_binlog_result.first;
+ EXPECT_EQ(source.values, data_result.second->get_data());
+ EXPECT_EQ(source.values, row_binlog_result.second->get_data());
+ EXPECT_EQ(1, source.nested_column->use_count());
+ EXPECT_EQ(values, std::vector<int64_t>(source.values, source.values +
values.size()));
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest
assertions inflate it.
+TEST(OlapDataConvertorTest, BigIntNullableAndNonNullableAreZeroCopy) {
+ for (bool nullable : {false, true}) {
+ const std::vector<int64_t> values = {11, 22, 33, 44};
+ auto source = create_numeric_source<TYPE_BIGINT>(
+ values, nullable ? std::vector<UInt8> {0, 1, 0, 0} :
std::vector<UInt8> {});
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(
+ create_tablet_column(FieldType::OLAP_FIELD_TYPE_BIGINT,
nullable));
+
+
ASSERT_TRUE(convertor.set_source_content_with_specifid_column(source.typed_column,
1, 2, 0)
+ .ok());
+ const auto source_use_count = source.nested_column->use_count();
+ auto [status, accessor] = convertor.convert_column_data(0);
+ ASSERT_TRUE(status.ok()) << status;
+ ASSERT_NE(nullptr, accessor);
+ EXPECT_EQ(source.values + 1, accessor->get_data());
+ EXPECT_EQ(values, std::vector<int64_t>(source.values, source.values +
values.size()));
+ EXPECT_EQ(source_use_count, source.nested_column->use_count());
+ if (nullable) {
+ EXPECT_EQ(nullptr, accessor->get_data_at(0));
+ EXPECT_EQ(source.values + 2, accessor->get_data_at(1));
+ } else {
+ EXPECT_EQ(source.values + 1, accessor->get_data_at(0));
+ }
+ }
+}
+
+TEST(OlapDataConvertorTest, FloatAndDoubleWithoutNanAreZeroCopy) {
+ expect_float_without_nan_is_zero_copy<TYPE_FLOAT>(false);
+ expect_float_without_nan_is_zero_copy<TYPE_FLOAT>(true);
+ expect_float_without_nan_is_zero_copy<TYPE_DOUBLE>(false);
+ expect_float_without_nan_is_zero_copy<TYPE_DOUBLE>(true);
+}
+
+TEST(OlapDataConvertorTest, FloatAndDoubleNanPayloadsDoNotMutateSource) {
+ expect_nan_payloads_are_normalized_without_source_mutation<TYPE_FLOAT>();
+ expect_nan_payloads_are_normalized_without_source_mutation<TYPE_DOUBLE>();
+}
+
+TEST(OlapDataConvertorTest, OnlyCurrentFloatSliceIsInspectedAndCopied) {
+ const auto nan_before = from_bits<float>(0x7fc00011U);
+ const auto nan_after = from_bits<float>(0x7fa12345U);
+ const std::vector<float> values = {nan_before, 1.0F, 2.0F, nan_after};
+ const std::vector<uint32_t> source_bits = {bits_of(nan_before),
bits_of(1.0F), bits_of(2.0F),
+ bits_of(nan_after)};
+ auto source = create_numeric_source<TYPE_FLOAT>(values);
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(
+ create_tablet_column(FieldType::OLAP_FIELD_TYPE_FLOAT, false));
+
+ auto [no_nan_status, no_nan_accessor] = convert(convertor, source, 1, 2);
+ ASSERT_TRUE(no_nan_status.ok()) << no_nan_status;
+ EXPECT_EQ(source.values + 1, no_nan_accessor->get_data());
+ EXPECT_EQ(0,
get_simple_convertor<TYPE_FLOAT>(convertor)->_converted_values.allocated_bytes());
+
+ auto [nan_status, nan_accessor] = convert(convertor, source, 3, 1);
+ ASSERT_TRUE(nan_status.ok()) << nan_status;
+ ASSERT_NE(source.values + 3, nan_accessor->get_data());
+ EXPECT_EQ(1,
get_simple_convertor<TYPE_FLOAT>(convertor)->_converted_values.size());
+ EXPECT_EQ(bits_of(std::numeric_limits<float>::quiet_NaN()),
+ bits_of(*static_cast<const float*>(nan_accessor->get_data())));
+ for (size_t i = 0; i < source_bits.size(); ++i) {
+ EXPECT_EQ(source_bits[i], bits_of(source.values[i]));
+ }
+}
+
+TEST(OlapDataConvertorTest,
ReusesFloatingPointBufferAcrossNanAndNonNanBatches) {
+ const auto nan_1 = from_bits<double>(0x7ff8000000000011ULL);
+ const auto nan_2 = from_bits<double>(0x7ff123456789abcdULL);
+ const std::vector<double> values = {nan_1, 1.0, 2.0, 3.0, nan_2, 4.0};
+ std::vector<uint64_t> source_bits;
+ for (double value : values) {
+ source_bits.push_back(bits_of(value));
+ }
+ auto source = create_numeric_source<TYPE_DOUBLE>(values);
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(
+ create_tablet_column(FieldType::OLAP_FIELD_TYPE_DOUBLE, false));
+
+ auto [first_status, first_accessor] = convert(convertor, source, 0, 2);
+ ASSERT_TRUE(first_status.ok()) << first_status;
+ const void* first_buffer = first_accessor->get_data();
+ ASSERT_NE(source.values, first_buffer);
+ const size_t first_capacity =
+
get_simple_convertor<TYPE_DOUBLE>(convertor)->_converted_values.capacity();
+
+ auto [second_status, second_accessor] = convert(convertor, source, 2, 2);
+ ASSERT_TRUE(second_status.ok()) << second_status;
+ EXPECT_EQ(source.values + 2, second_accessor->get_data());
+ EXPECT_EQ(0,
get_simple_convertor<TYPE_DOUBLE>(convertor)->_converted_values.size());
+ EXPECT_EQ(first_capacity,
+
get_simple_convertor<TYPE_DOUBLE>(convertor)->_converted_values.capacity());
+
+ auto [third_status, third_accessor] = convert(convertor, source, 4, 2);
+ ASSERT_TRUE(third_status.ok()) << third_status;
+ EXPECT_EQ(first_buffer, third_accessor->get_data());
+ EXPECT_EQ(first_capacity,
+
get_simple_convertor<TYPE_DOUBLE>(convertor)->_converted_values.capacity());
+ for (size_t i = 0; i < source_bits.size(); ++i) {
+ EXPECT_EQ(source_bits[i], bits_of(source.values[i]));
+ }
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity) -- GTest
assertions inflate it.
+TEST(OlapDataConvertorTest, ArrayWithNullableDoubleLeafDoesNotMutateSource) {
+ const auto nan_1 = from_bits<double>(0x7ff8000000000011ULL);
+ const auto nan_2 = from_bits<double>(0xfff8123456789abcULL);
+ const std::vector<double> values = {nan_1, 7.5, nan_2};
+ auto nested_values = ColumnFloat64::create();
+ nested_values->get_data().assign(values.begin(), values.end());
+ const double* source_values = nested_values->get_data().data();
+ const std::vector<uint64_t> source_bits = {bits_of(source_values[0]),
bits_of(source_values[1]),
+ bits_of(source_values[2])};
+ const std::vector<UInt8> null_map = {0, 1, 0};
+ auto nested_null_map = ColumnUInt8::create();
+ nested_null_map->get_data().assign(null_map.begin(), null_map.end());
+ auto nullable_values =
+ ColumnNullable::create(std::move(nested_values),
std::move(nested_null_map));
+ const std::vector<UInt64> array_offsets = {2, 3};
+ auto offsets = ColumnOffset64::create();
+ offsets->get_data().assign(array_offsets.begin(), array_offsets.end());
+ ColumnPtr array_column = ColumnArray::create(std::move(nullable_values),
std::move(offsets));
+ DataTypePtr array_type = std::make_shared<DataTypeArray>(
+
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeFloat64>()));
+ ColumnWithTypeAndName typed_array {std::move(array_column),
std::move(array_type), "array"};
+
+ TabletColumn
item_column(FieldAggregationMethod::OLAP_FIELD_AGGREGATION_NONE,
+ FieldType::OLAP_FIELD_TYPE_DOUBLE, true);
+ item_column.set_name("item");
+ item_column.set_length(sizeof(double));
+ item_column.set_index_length(sizeof(double));
+ TabletColumn array_tablet_column;
+ array_tablet_column.set_name("array");
+ array_tablet_column.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
+ array_tablet_column.set_is_nullable(false);
+ array_tablet_column.add_sub_column(item_column);
+
+ OlapBlockDataConvertor convertor;
+ convertor.add_column_data_convertor(array_tablet_column);
+ ASSERT_TRUE(convertor.set_source_content_with_specifid_column(typed_array,
0, 2, 0).ok());
+ auto [status, accessor] = convertor.convert_column_data(0);
+ ASSERT_TRUE(status.ok()) << status;
+ ASSERT_NE(nullptr, accessor);
+ const auto* array_result = static_cast<const void*
const*>(accessor->get_data());
+ const auto* converted_values = static_cast<const double*>(array_result[2]);
+ const auto* converted_null_map = static_cast<const
UInt8*>(array_result[3]);
+ ASSERT_NE(source_values, converted_values);
+ EXPECT_EQ(bits_of(std::numeric_limits<double>::quiet_NaN()),
bits_of(converted_values[0]));
+ EXPECT_EQ(bits_of(7.5), bits_of(converted_values[1]));
+ EXPECT_EQ(bits_of(std::numeric_limits<double>::quiet_NaN()),
bits_of(converted_values[2]));
+ ASSERT_NE(nullptr, converted_null_map);
+ EXPECT_EQ(0, converted_null_map[0]);
+ EXPECT_EQ(1, converted_null_map[1]);
+ EXPECT_EQ(0, converted_null_map[2]);
+ for (size_t i = 0; i < source_bits.size(); ++i) {
+ EXPECT_EQ(source_bits[i], bits_of(source_values[i]));
+ }
+}
+
+} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]