This is an automated email from the ASF dual-hosted git repository.
HappenLee 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 5378480d492 [fix](be) Support complex array_agg state serialization
(#66601)
5378480d492 is described below
commit 5378480d49253aa8709739e1ee09766c67d53f0e
Author: HappenLee <[email protected]>
AuthorDate: Tue Aug 11 22:19:19 2026 +0800
[fix](be) Support complex array_agg state serialization (#66601)
Problem Summary: A two-phase `array_agg_foreach` query over nested
arrays in an ordinary OLAP table failed with `array_agg not support
write`. The generic complex-type `array_agg` state did not implement
serialization, deserialization, or merge, so it could not cross
aggregation phases or grow the ForEach state array. This change
serializes complex states with the native DataType format using the
active BE execution version and merges state columns by value. The query
now completes without using Table Stream.
### Release note
Fix `array_agg` and `array_agg_foreach` failures for complex element
types in multi-phase aggregation.
### Check List (For Author)
- Test: Unit Test and Regression test
- Unit Test: `GLIBC_COMPATIBILITY=OFF ./run-be-ut.sh -j 48 --run
--filter='AggregateFunctionArrayAggTest.*'` (7 tests passed)
- Regression test: `./run-regression-test.sh --run -d function_p0 -s
test_agg_foreach` (1 suite passed)
- ASAN BE build passed
- FE build and Checkstyle passed
- Behavior changed: Yes. Complex `array_agg` states now support
serialization and merge.
- Does this need documentation: No
---
.../exprs/aggregate/aggregate_function_array_agg.h | 68 +++++--
.../exprs/aggregate/aggregate_function_foreach.h | 19 +-
be/test/exprs/aggregate/agg_array_agg_test.cpp | 219 ++++++++++++++++++++-
.../aggregate_function_exception_test.cpp | 70 ++++++-
.../suites/function_p0/test_agg_foreach.groovy | 2 +-
5 files changed, 345 insertions(+), 33 deletions(-)
diff --git a/be/src/exprs/aggregate/aggregate_function_array_agg.h
b/be/src/exprs/aggregate/aggregate_function_array_agg.h
index f2c7a1feadb..002e11cdd13 100644
--- a/be/src/exprs/aggregate/aggregate_function_array_agg.h
+++ b/be/src/exprs/aggregate/aggregate_function_array_agg.h
@@ -17,6 +17,7 @@
#pragma once
+#include "common/check.h"
#include "core/assert_cast.h"
#include "core/column/column.h"
#include "core/column/column_array.h"
@@ -37,6 +38,7 @@ class Arena;
template <PrimitiveType T>
struct AggregateFunctionArrayAggData {
static constexpr PrimitiveType PType = T;
+ static constexpr bool use_native_serde = false;
using ElementType = typename PrimitiveTypeTraits<T>::CppType;
using ColVecType = typename PrimitiveTypeTraits<T>::ColumnType;
using Self = AggregateFunctionArrayAggData<T>;
@@ -136,6 +138,7 @@ template <PrimitiveType T>
requires(is_string_type(T))
struct AggregateFunctionArrayAggData<T> {
static constexpr PrimitiveType PType = T;
+ static constexpr bool use_native_serde = false;
using ElementType = StringRef;
using ColVecType = ColumnString;
using Self = AggregateFunctionArrayAggData<T>;
@@ -231,14 +234,13 @@ template <PrimitiveType T>
!is_date_type(T) && !is_ip(T))
struct AggregateFunctionArrayAggData<T> {
static constexpr PrimitiveType PType = T;
+ static constexpr bool use_native_serde = true;
using ElementType = StringRef;
using Self = AggregateFunctionArrayAggData<T>;
MutableColumnPtr column_data;
- AggregateFunctionArrayAggData(const DataTypes& argument_types) {
- DataTypePtr column_type = argument_types[0];
- column_data = column_type->create_column();
- }
+ AggregateFunctionArrayAggData(const DataTypes& argument_types)
+ : column_data(argument_types[0]->create_column()) {}
void add(const IColumn& column, size_t row_num) {
column_data->insert_from(column, row_num); }
@@ -247,9 +249,7 @@ struct AggregateFunctionArrayAggData<T> {
const auto& to_nested_col = to_arr.get_data();
auto start = to_arr.get_offsets()[row_num - 1];
auto end = start + to_arr.get_offsets()[row_num] -
to_arr.get_offsets()[row_num - 1];
- for (auto i = start; i < end; ++i) {
- column_data->insert_from(to_nested_col, i);
- }
+ column_data->insert_range_from(to_nested_col, start, end - start);
}
void reset() { column_data->clear(); }
@@ -257,23 +257,42 @@ struct AggregateFunctionArrayAggData<T> {
void insert_result_into(IColumn& to) const {
auto& to_arr = assert_cast<ColumnArray&,
TypeCheckOnRelease::DISABLE>(to);
auto& to_nested_col = to_arr.get_data();
- size_t num_rows = column_data->size();
- for (size_t i = 0; i < num_rows; ++i) {
- to_nested_col.insert_from(*column_data, i);
- }
+ to_nested_col.insert_range_from(*column_data, 0, column_data->size());
to_arr.get_offsets().push_back(to_nested_col.size());
}
- void write(BufferWritable& buf) const {
- throw Exception(ErrorCode::NOT_IMPLEMENTED_ERROR, "array_agg not
support write");
+ void write(BufferWritable& buf, const IDataType& column_type, int
be_exec_version) const {
+ const auto max_serialized_bytes = cast_set<size_t>(
+ column_type.get_uncompressed_serialized_bytes(*column_data,
be_exec_version));
+ buf.resize(sizeof(UInt64) + max_serialized_bytes);
+ auto* serialized_data = buf.data() + sizeof(UInt64);
+ const char* end = nullptr;
+ try {
+ end = column_type.serialize(*column_data, serialized_data,
be_exec_version);
+ } catch (...) {
+ buf.resize(0);
+ throw;
+ }
+ DORIS_CHECK_LE(end, serialized_data + max_serialized_bytes);
+ const auto serialized_bytes = static_cast<UInt64>(end -
serialized_data);
+ memcpy(buf.data(), &serialized_bytes, sizeof(serialized_bytes));
+ const auto frame_bytes = sizeof(serialized_bytes) +
cast_set<size_t>(serialized_bytes);
+ buf.resize(frame_bytes);
+ buf.add_offset(frame_bytes);
}
- void read(BufferReadable& buf) {
- throw Exception(ErrorCode::NOT_IMPLEMENTED_ERROR, "array_agg not
support read");
+ void read(BufferReadable& buf, const IDataType& column_type, int
be_exec_version) {
+ DORIS_CHECK(column_data->empty());
+ UInt64 serialized_bytes = 0;
+ buf.read_binary(serialized_bytes);
+ const auto* serialized_data = buf.data();
+ const auto* end = column_type.deserialize(serialized_data,
&column_data, be_exec_version);
+ DORIS_CHECK_EQ(end, serialized_data + serialized_bytes);
+ buf.add_offset(serialized_bytes);
}
void merge(const Self& rhs) {
- throw Exception(ErrorCode::NOT_IMPLEMENTED_ERROR, "array_agg not
support merge");
+ column_data->insert_range_from(*rhs.column_data, 0,
rhs.column_data->size());
}
};
@@ -285,9 +304,10 @@ class AggregateFunctionArrayAgg final
UnaryExpression,
NotNullableAggregateFunction {
public:
+ using Base = IAggregateFunctionDataHelper<Data,
AggregateFunctionArrayAgg<Data>, true>;
+
AggregateFunctionArrayAgg(const DataTypes& argument_types_)
- : IAggregateFunctionDataHelper<Data,
AggregateFunctionArrayAgg<Data>, true>(
- {argument_types_}),
+ : Base({argument_types_}),
return_type(std::make_shared<DataTypeArray>(make_nullable(argument_types_[0])))
{}
std::string get_name() const override { return "array_agg"; }
@@ -305,12 +325,20 @@ public:
}
void serialize(ConstAggregateDataPtr __restrict place, BufferWritable&
buf) const override {
- this->data(place).write(buf);
+ if constexpr (Data::use_native_serde) {
+ this->data(place).write(buf, *this->argument_types[0],
this->version);
+ } else {
+ this->data(place).write(buf);
+ }
}
void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf,
Arena&) const override {
- this->data(place).read(buf);
+ if constexpr (Data::use_native_serde) {
+ this->data(place).read(buf, *this->argument_types[0],
this->version);
+ } else {
+ this->data(place).read(buf);
+ }
}
void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn&
to) const override {
diff --git a/be/src/exprs/aggregate/aggregate_function_foreach.h
b/be/src/exprs/aggregate/aggregate_function_foreach.h
index 28313dee8b6..d6324cd7694 100644
--- a/be/src/exprs/aggregate/aggregate_function_foreach.h
+++ b/be/src/exprs/aggregate/aggregate_function_foreach.h
@@ -97,24 +97,25 @@ protected:
char* new_state =
arena.aligned_alloc(allocation_size,
nested_function->align_of_data());
- size_t i;
+ size_t num_created = 0;
try {
- for (i = 0; i < new_size; ++i) {
- nested_function->create(&new_state[i *
nested_size_of_data]);
+ for (; num_created < new_size; ++num_created) {
+ nested_function->create(&new_state[num_created *
nested_size_of_data]);
}
- } catch (...) {
- size_t cleanup_size = i;
- for (i = 0; i < cleanup_size; ++i) {
+ for (size_t i = 0; i < old_size; ++i) {
+ nested_function->merge(&new_state[i * nested_size_of_data],
+ &old_state[i *
nested_size_of_data], arena);
+ }
+ } catch (...) {
+ for (size_t i = 0; i < num_created; ++i) {
nested_function->destroy(&new_state[i *
nested_size_of_data]);
}
throw;
}
- for (i = 0; i < old_size; ++i) {
- nested_function->merge(&new_state[i * nested_size_of_data],
- &old_state[i * nested_size_of_data],
arena);
+ for (size_t i = 0; i < old_size; ++i) {
nested_function->destroy(&old_state[i * nested_size_of_data]);
}
diff --git a/be/test/exprs/aggregate/agg_array_agg_test.cpp
b/be/test/exprs/aggregate/agg_array_agg_test.cpp
index d65565a4c99..84a3d823f68 100644
--- a/be/test/exprs/aggregate/agg_array_agg_test.cpp
+++ b/be/test/exprs/aggregate/agg_array_agg_test.cpp
@@ -17,13 +17,14 @@
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
#include <memory>
#include <ostream>
#include <string>
+#include "agent/be_exec_version_manager.h"
#include "common/logging.h"
#include "core/arena.h"
#include "core/column/column_array.h"
@@ -34,16 +35,21 @@
#include "core/data_type/data_type_date.h"
#include "core/data_type/data_type_date_time.h"
#include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_map.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
+#include "core/data_type/data_type_struct.h"
#include "core/string_buffer.hpp"
#include "core/types.h"
#include "exprs/aggregate/agg_function_test.h"
#include "exprs/aggregate/aggregate_function.h"
+#include "exprs/aggregate/aggregate_function_array_agg.h"
#include "exprs/aggregate/aggregate_function_simple_factory.h"
#include "exprs/aggregate/aggregate_function_sort.h"
#include "gtest/gtest_pred_impl.h"
+#include "runtime/memory/mem_tracker_limiter.h"
+#include "runtime/thread_context.h"
namespace doris {
class IColumn;
@@ -53,6 +59,83 @@ namespace doris {
struct AggregateFunctionArrayAggTest : public AggregateFunctiontest {};
+namespace {
+
+Field array_field(std::initializer_list<Field> values) {
+ return Field::create_field<TYPE_ARRAY>(Array(values));
+}
+
+Field map_field(std::initializer_list<Field> keys,
std::initializer_list<Field> values) {
+ return Field::create_field<TYPE_MAP>(Map {array_field(keys),
array_field(values)});
+}
+
+void add_column_to_state(const IAggregateFunction& function, AggregateDataPtr
state,
+ const IColumn& column, Arena& arena) {
+ const IColumn* columns[] = {&column};
+ for (size_t row = 0; row < column.size(); ++row) {
+ function.add(state, columns, row, arena);
+ }
+}
+
+void check_complex_array_agg_state(const DataTypePtr& data_type,
+ std::initializer_list<Field> source_values,
+ std::initializer_list<Field> rhs_values) {
+ SCOPED_TRACE(data_type->get_name());
+ const auto nullable_type = make_nullable(data_type);
+ auto function = AggregateFunctionSimpleFactory::instance().get(
+ "array_agg", {nullable_type}, nullptr, false,
+ BeExecVersionManager::get_newest_version());
+ ASSERT_NE(function, nullptr);
+ function->set_version(BeExecVersionManager::get_newest_version());
+
+ auto source_column = nullable_type->create_column();
+ for (const auto& value : source_values) {
+ source_column->insert(value);
+ }
+ auto rhs_column = nullable_type->create_column();
+ for (const auto& value : rhs_values) {
+ rhs_column->insert(value);
+ }
+
+ Arena arena;
+ AggregateFunctionGuard source(function.get());
+ AggregateFunctionGuard restored(function.get());
+ AggregateFunctionGuard rhs(function.get());
+ add_column_to_state(*function, source.data(), *source_column, arena);
+ add_column_to_state(*function, rhs.data(), *rhs_column, arena);
+
+ auto serialized_column = ColumnString::create();
+ BufferWritable writer(*serialized_column);
+ function->serialize(source.data(), writer);
+ writer.commit();
+ ASSERT_EQ(serialized_column->size(), 1);
+
+ auto serialized_data = serialized_column->get_data_at(0);
+ BufferReadable reader(serialized_data);
+ function->deserialize(restored.data(), reader, arena);
+ function->merge(restored.data(), rhs.data(), arena);
+
+ auto result_column = function->get_return_type()->create_column();
+ function->insert_result_into(restored.data(), *result_column);
+ auto expected_column = function->get_return_type()->create_column();
+ Array expected_values(source_values);
+ expected_values.insert(expected_values.end(), rhs_values.begin(),
rhs_values.end());
+ expected_column->insert(Field::create_field<TYPE_ARRAY>(expected_values));
+ EXPECT_TRUE(ColumnHelper::column_equal(std::move(result_column),
std::move(expected_column)));
+}
+
+class ThrowOnSerializeDataType final : public DataTypeString {
+public:
+ char* serialize(const IColumn&, char* buf, int) const override {
+ *buf = 1;
+ throw Exception(ErrorCode::MEM_ALLOC_FAILED, "mock serialize
allocation failure");
+ }
+};
+
+static_assert(sizeof(AggregateFunctionArrayAggData<INVALID_TYPE>) ==
sizeof(MutableColumnPtr));
+
+} // namespace
+
TEST_F(AggregateFunctionArrayAggTest, test_array_agg_aint64) {
create_agg("array_agg", false, {std::make_shared<DataTypeInt64>()},
std::make_shared<DataTypeInt64>());
@@ -201,6 +284,138 @@ TEST_F(AggregateFunctionArrayAggTest,
test_array_agg_aint64_foreach) {
ColumnWithTypeAndName(std::move(array_array_column),
array_array_data_type, "column"));
}
+TEST_F(AggregateFunctionArrayAggTest,
complex_type_state_serialize_deserialize_and_merge) {
+ auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+ auto nullable_string = make_nullable(std::make_shared<DataTypeString>());
+
+ Array streamvbyte_values;
+ for (int32_t value = 0; value < 65; ++value) {
+ streamvbyte_values.emplace_back(Field::create_field<TYPE_INT>(value));
+ }
+
check_complex_array_agg_state(std::make_shared<DataTypeArray>(nullable_int),
+
{Field::create_field<TYPE_ARRAY>(std::move(streamvbyte_values))},
+ {});
+
+ check_complex_array_agg_state(
+ std::make_shared<DataTypeArray>(nullable_int),
+ {array_field({Field::create_field<TYPE_INT>(1), Field()}),
Field()},
+ {array_field({Field::create_field<TYPE_INT>(2),
Field::create_field<TYPE_INT>(3)})});
+
+ check_complex_array_agg_state(
+ std::make_shared<DataTypeStruct>(DataTypes {nullable_int,
nullable_string}),
+ {Field::create_field<TYPE_STRUCT>(Struct
{Field::create_field<TYPE_INT>(1),
+
Field::create_field<TYPE_STRING>("one")}),
+ Field()},
+ {Field::create_field<TYPE_STRUCT>(
+ Struct {Field(),
Field::create_field<TYPE_STRING>("two")})});
+
+
check_complex_array_agg_state(std::make_shared<DataTypeMap>(nullable_string,
nullable_int),
+
{map_field({Field::create_field<TYPE_STRING>("one"),
+
Field::create_field<TYPE_STRING>("null")},
+
{Field::create_field<TYPE_INT>(1), Field()}),
+ Field()},
+
{map_field({Field::create_field<TYPE_STRING>("two")},
+
{Field::create_field<TYPE_INT>(2)})});
+}
+
+TEST_F(AggregateFunctionArrayAggTest,
foreach_complex_type_state_growth_and_round_trip) {
+ auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+ auto nullable_inner_array =
make_nullable(std::make_shared<DataTypeArray>(nullable_int));
+ auto input_type = std::make_shared<DataTypeArray>(nullable_inner_array);
+ auto function = AggregateFunctionSimpleFactory::instance().get(
+ "array_agg_foreach", {input_type}, input_type, false,
+ BeExecVersionManager::get_newest_version(), {.is_foreach = true,
.column_names = {}});
+ ASSERT_NE(function, nullptr);
+ function->set_version(BeExecVersionManager::get_newest_version());
+
+ auto input_column = input_type->create_column();
+
input_column->insert(array_field({array_field({Field::create_field<TYPE_INT>(1)})}));
+ input_column->insert(array_field(
+ {array_field({Field::create_field<TYPE_INT>(2)}),
+ array_field({Field::create_field<TYPE_INT>(3),
Field::create_field<TYPE_INT>(4)}),
+ Field()}));
+
+ Arena arena;
+ AggregateFunctionGuard source(function.get());
+ AggregateFunctionGuard restored(function.get());
+ AggregateFunctionGuard merged(function.get());
+ add_column_to_state(*function, source.data(), *input_column, arena);
+
+ auto serialized_column = ColumnString::create();
+ BufferWritable writer(*serialized_column);
+ function->serialize(source.data(), writer);
+ writer.commit();
+
+ auto serialized_data = serialized_column->get_data_at(0);
+ BufferReadable reader(serialized_data);
+ function->deserialize(restored.data(), reader, arena);
+ function->merge(merged.data(), restored.data(), arena);
+
+ auto result_column = function->get_return_type()->create_column();
+ function->insert_result_into(merged.data(), *result_column);
+ auto expected_column = function->get_return_type()->create_column();
+ expected_column->insert(
+
array_field({array_field({array_field({Field::create_field<TYPE_INT>(1)}),
+
array_field({Field::create_field<TYPE_INT>(2)})}),
+
array_field({array_field({Field::create_field<TYPE_INT>(3),
+
Field::create_field<TYPE_INT>(4)})}),
+ array_field({Field()})}));
+ EXPECT_TRUE(ColumnHelper::column_equal(std::move(result_column),
std::move(expected_column)));
+}
+
+TEST_F(AggregateFunctionArrayAggTest,
complex_type_state_write_rolls_back_on_failure) {
+ auto data_type = std::make_shared<ThrowOnSerializeDataType>();
+ AggregateFunctionArrayAggData<INVALID_TYPE> data(DataTypes {data_type});
+ auto serialized_column = ColumnString::create();
+ BufferWritable writer(*serialized_column);
+ writer.write_c_string("prefix");
+
+ EXPECT_THROW(data.write(writer, *data_type,
BeExecVersionManager::get_newest_version()),
+ Exception);
+ EXPECT_EQ(serialized_column->get_chars().size(), 6);
+
+ writer.commit();
+ EXPECT_EQ(serialized_column->get_data_at(0).to_string(), "prefix");
+}
+
+TEST_F(AggregateFunctionArrayAggTest,
large_complex_type_state_serialization_is_tracked) {
+ constexpr size_t PAYLOAD_BYTES = 2 * 1024 * 1024;
+ auto nullable_string = make_nullable(std::make_shared<DataTypeString>());
+ auto nullable_inner_array =
make_nullable(std::make_shared<DataTypeArray>(nullable_string));
+ auto input_type = std::make_shared<DataTypeArray>(nullable_inner_array);
+ auto function = AggregateFunctionSimpleFactory::instance().get(
+ "array_agg_foreach", {input_type}, input_type, false,
+ BeExecVersionManager::get_newest_version(), {.is_foreach = true,
.column_names = {}});
+ ASSERT_NE(function, nullptr);
+ function->set_version(BeExecVersionManager::get_newest_version());
+
+ auto input_column = input_type->create_column();
+ input_column->insert(array_field(
+
{array_field({Field::create_field<TYPE_STRING>(String(PAYLOAD_BYTES, 'x'))})}));
+ Arena arena;
+ AggregateFunctionGuard state(function.get());
+ add_column_to_state(*function, state.data(), *input_column, arena);
+
+ auto tracker =
MemTrackerLimiter::create_shared(MemTrackerLimiter::Type::OTHER,
+
"ArrayAggLargeStateSerializationTest");
+ auto switch_tracker = SwitchThreadMemTrackerLimiter(tracker);
+ thread_context()->thread_mem_tracker_mgr->flush_untracked_mem();
+ const auto baseline = tracker->consumption();
+ {
+ auto serialized_column = ColumnString::create();
+ BufferWritable writer(*serialized_column);
+ function->serialize(state.data(), writer);
+ writer.commit();
+ thread_context()->thread_mem_tracker_mgr->flush_untracked_mem();
+
+ const auto tracked_bytes = tracker->consumption() - baseline;
+ EXPECT_GT(tracked_bytes, PAYLOAD_BYTES);
+ EXPECT_GE(tracked_bytes, serialized_column->allocated_bytes());
+ }
+ thread_context()->thread_mem_tracker_mgr->flush_untracked_mem();
+ EXPECT_EQ(tracker->consumption(), baseline);
+}
+
TEST(AggregateFunctionSortDataTest, merge_does_not_share_rhs_block) {
auto data_type = std::make_shared<DataTypeInt64>();
Block prototype({ColumnWithTypeAndName(data_type->create_column(),
data_type, "value"),
diff --git a/be/test/exprs/aggregate/aggregate_function_exception_test.cpp
b/be/test/exprs/aggregate/aggregate_function_exception_test.cpp
index 21ee64dba4a..d571e8c0320 100644
--- a/be/test/exprs/aggregate/aggregate_function_exception_test.cpp
+++ b/be/test/exprs/aggregate/aggregate_function_exception_test.cpp
@@ -17,10 +17,14 @@
#include <gtest/gtest.h>
+#include <memory>
#include <vector>
#include "core/arena.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_string.h"
#include "exprs/aggregate/aggregate_function.h"
+#include "exprs/aggregate/aggregate_function_foreach.h"
namespace doris {
@@ -31,14 +35,17 @@ struct TrackingAggregateState {
static void reset_counters() {
construct_count = 0;
destroy_count = 0;
+ merge_count = 0;
}
static int construct_count;
static int destroy_count;
+ static int merge_count;
};
int TrackingAggregateState::construct_count = 0;
int TrackingAggregateState::destroy_count = 0;
+int TrackingAggregateState::merge_count = 0;
class ThrowOnDeserializeAggregateFunction final
: public IAggregateFunctionDataHelper<TrackingAggregateState,
@@ -73,6 +80,34 @@ public:
void insert_result_into(ConstAggregateDataPtr, IColumn&) const override {}
};
+class ThrowOnSecondMergeAggregateFunction final
+ : public IAggregateFunctionDataHelper<TrackingAggregateState,
+
ThrowOnSecondMergeAggregateFunction> {
+public:
+ ThrowOnSecondMergeAggregateFunction()
+ : IAggregateFunctionDataHelper<TrackingAggregateState,
+
ThrowOnSecondMergeAggregateFunction>(
+ DataTypes {std::make_shared<DataTypeString>()}) {}
+
+ String get_name() const override { return "throw_on_second_merge"; }
+
+ DataTypePtr get_return_type() const override { return
std::make_shared<DataTypeString>(); }
+
+ void add(AggregateDataPtr, const IColumn**, ssize_t, Arena&) const
override {}
+
+ void merge(AggregateDataPtr, ConstAggregateDataPtr, Arena&) const override
{
+ if (++TrackingAggregateState::merge_count == 2) {
+ throw Exception(ErrorCode::MEM_ALLOC_FAILED, "mock merge
allocation failure");
+ }
+ }
+
+ void serialize(ConstAggregateDataPtr, BufferWritable&) const override {}
+
+ void deserialize(AggregateDataPtr, BufferReadable&, Arena&) const override
{}
+
+ void insert_result_into(ConstAggregateDataPtr, IColumn&) const override {}
+};
+
class AggregateFunctionExceptionTest : public testing::Test {
protected:
void SetUp() override { TrackingAggregateState::reset_counters(); }
@@ -159,4 +194,37 @@ TEST_F(AggregateFunctionExceptionTest,
EXPECT_EQ(TrackingAggregateState::construct_count,
TrackingAggregateState::destroy_count);
}
-} // namespace doris
\ No newline at end of file
+TEST_F(AggregateFunctionExceptionTest,
ForEachGrowthPreservesOldStatesWhenMergeThrows) {
+ auto nested_function =
std::make_shared<ThrowOnSecondMergeAggregateFunction>();
+ auto input_type =
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>());
+ AggregateFunctionForEach foreach_function(nested_function, DataTypes
{input_type});
+ auto input_column = input_type->create_column();
+ input_column->insert(
+ Field::create_field<TYPE_ARRAY>(Array
{Field::create_field<TYPE_STRING>(String("a")),
+
Field::create_field<TYPE_STRING>(String("b"))}));
+ input_column->insert(
+ Field::create_field<TYPE_ARRAY>(Array
{Field::create_field<TYPE_STRING>(String("c")),
+
Field::create_field<TYPE_STRING>(String("d")),
+
Field::create_field<TYPE_STRING>(String("e"))}));
+ const IColumn* columns[] = {input_column.get()};
+
+ {
+ AggregateFunctionGuard state(&foreach_function);
+ foreach_function.add(state.data(), columns, 0, arena);
+
+ try {
+ foreach_function.add(state.data(), columns, 1, arena);
+ FAIL() << "Expected doris::Exception";
+ } catch (const doris::Exception& e) {
+ EXPECT_EQ(e.code(), doris::ErrorCode::MEM_ALLOC_FAILED);
+ }
+
+ EXPECT_EQ(TrackingAggregateState::merge_count, 2);
+ EXPECT_EQ(TrackingAggregateState::construct_count, 5);
+ EXPECT_EQ(TrackingAggregateState::destroy_count, 3);
+ }
+
+ EXPECT_EQ(TrackingAggregateState::construct_count,
TrackingAggregateState::destroy_count);
+}
+
+} // namespace doris
diff --git a/regression-test/suites/function_p0/test_agg_foreach.groovy
b/regression-test/suites/function_p0/test_agg_foreach.groovy
index d2280e0c9ce..72fea2f4cfc 100644
--- a/regression-test/suites/function_p0/test_agg_foreach.groovy
+++ b/regression-test/suites/function_p0/test_agg_foreach.groovy
@@ -119,7 +119,7 @@ suite("test_agg_foreach") {
qt_sql """select array_agg_foreach(s) from foreach_table;"""
qt_array_agg_nested """
- select /*+ SET_VAR(parallel_pipeline_task_num=1) */
+ select /*+ SET_VAR(agg_phase=2, parallel_pipeline_task_num=1) */
size(array_agg_foreach(b)),
size(element_at(array_agg_foreach(b), 1)),
size(element_at(array_agg_foreach(b), 2)),
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]