HappenLee commented on code in PR #66942: URL: https://github.com/apache/doris/pull/66942#discussion_r3841801530
########## be/src/exprs/aggregate/aggregate_function_state_combine.h: ########## @@ -0,0 +1,220 @@ +// 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 <utility> + +#include "exprs/aggregate/aggregate_function.h" + +namespace doris { +const static std::string AGG_COMBINE_SUFFIX = "_combine"; + +class AggregateStateCombine final : public IAggregateFunctionHelper<AggregateStateCombine> { +public: + AggregateStateCombine(AggregateFunctionPtr function, const DataTypes& argument_types_, + DataTypePtr return_type) + : IAggregateFunctionHelper(argument_types_), + _function(std::move(function)), + _return_type(std::move(return_type)) {} + + static AggregateFunctionPtr create(AggregateFunctionPtr function, + const DataTypes& argument_types_, + const DataTypePtr& return_type) { + if (function == nullptr) { + return nullptr; + } + return std::make_shared<AggregateStateCombine>(function, argument_types_, return_type); + } + + void set_version(const int version_) override { + IAggregateFunctionHelper::set_version(version_); + _function->set_version(version_); + } + + void create(AggregateDataPtr __restrict place) const override { _function->create(place); } + + void destroy_vec(AggregateDataPtr __restrict place, + const size_t num_rows) const noexcept override { + _function->destroy_vec(place, num_rows); + } + + String get_name() const override { return _function->get_name() + AGG_COMBINE_SUFFIX; } + + DataTypePtr get_return_type() const override { return _return_type; } + + void add(AggregateDataPtr __restrict place, const IColumn** columns, ssize_t row_num, + Arena& arena) const override { + _function->add(place, columns, row_num, arena); + } + + void add_batch(size_t batch_size, AggregateDataPtr* places, size_t place_offset, + const IColumn** columns, Arena& arena, bool agg_many) const override { + _function->add_batch(batch_size, places, place_offset, columns, arena, agg_many); + } + + void add_batch_selected(size_t batch_size, AggregateDataPtr* places, size_t place_offset, + const IColumn** columns, Arena& arena) const override { + _function->add_batch_selected(batch_size, places, place_offset, columns, arena); + } + + void add_batch_single_place(size_t batch_size, AggregateDataPtr place, const IColumn** columns, + Arena& arena) const override { + _function->add_batch_single_place(batch_size, place, columns, arena); + } + + void add_batch_range(size_t batch_begin, size_t batch_end, AggregateDataPtr place, + const IColumn** columns, Arena& arena, bool has_null) override { + _function->add_batch_range(batch_begin, batch_end, place, columns, arena, has_null); + } + + void add_range_single_place(int64_t partition_start, int64_t partition_end, int64_t frame_start, + int64_t frame_end, AggregateDataPtr place, const IColumn** columns, + Arena& arena, UInt8* use_null_result, + UInt8* could_use_previous_result) const override { + _function->add_range_single_place(partition_start, partition_end, frame_start, frame_end, + place, columns, arena, use_null_result, + could_use_previous_result); + } + + void reset(AggregateDataPtr place) const override { _function->reset(place); } + + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, + Arena& arena) const override { + _function->merge(place, rhs, arena); + } + + void merge_vec(const AggregateDataPtr __restrict* __restrict places, size_t offset, + ConstAggregateDataPtr __restrict rhs, Arena& arena, + const size_t num_rows) const override { + _function->merge_vec(places, offset, rhs, arena, num_rows); + } + + void merge_vec_selected(const AggregateDataPtr __restrict* __restrict places, size_t offset, + ConstAggregateDataPtr __restrict rhs, Arena& arena, + const size_t num_rows) const override { + _function->merge_vec_selected(places, offset, rhs, arena, num_rows); + } + + void serialize(ConstAggregateDataPtr __restrict place, BufferWritable& buf) const override { + _function->serialize(place, buf); + } + + void serialize_vec(const std::vector<AggregateDataPtr>& places, size_t offset, + BufferWritable& buf, const size_t num_rows) const override { + _function->serialize_vec(places, offset, buf, num_rows); + } + + void serialize_to_column(const std::vector<AggregateDataPtr>& places, size_t offset, + MutableColumnPtr& dst, const size_t num_rows) const override { + _function->serialize_to_column(places, offset, dst, num_rows); + } + + void serialize_without_key_to_column(ConstAggregateDataPtr __restrict place, + IColumn& to) const override { + _function->serialize_without_key_to_column(place, to); + } + + void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf, + Arena& arena) const override { + _function->deserialize(place, buf, arena); + } + + void deserialize_vec(AggregateDataPtr places, const ColumnString* column, Arena& arena, + size_t num_rows) const override { + _function->deserialize_vec(places, column, arena, num_rows); + } + + void deserialize_and_merge_vec(const AggregateDataPtr* places, size_t offset, + AggregateDataPtr rhs, const IColumn* column, Arena& arena, + const size_t num_rows) const override { + _function->deserialize_and_merge_vec(places, offset, rhs, column, arena, num_rows); + } + + void deserialize_and_merge_vec_selected(const AggregateDataPtr* places, size_t offset, + AggregateDataPtr rhs, const IColumn* column, + Arena& arena, const size_t num_rows) const override { + _function->deserialize_and_merge_vec_selected(places, offset, rhs, column, arena, num_rows); + } + + void deserialize_and_merge(AggregateDataPtr __restrict place, AggregateDataPtr __restrict rhs, + BufferReadable& buf, Arena& arena) const override { + _function->deserialize_and_merge(place, rhs, buf, arena); + } + + void deserialize_and_merge_from_column_range(AggregateDataPtr __restrict place, + const IColumn& column, size_t begin, size_t end, + Arena& arena) const override { + _function->deserialize_and_merge_from_column_range(place, column, begin, end, arena); + } + + void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn& to) const override { Review Comment: Fixed. The orthogonal bitmap aggregate family now implements `NotSupportAggState`, so `_state`, `_merge`, `_union`, and `_combine` are rejected during FE function resolution instead of entering an unsupported terminal-serialization path. The unit test covers all affected orthogonal aggregate classes. ########## be/src/exprs/aggregate/aggregate_function_state_combine.h: ########## @@ -0,0 +1,220 @@ +// 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 <utility> + +#include "exprs/aggregate/aggregate_function.h" + +namespace doris { +const static std::string AGG_COMBINE_SUFFIX = "_combine"; + +class AggregateStateCombine final : public IAggregateFunctionHelper<AggregateStateCombine> { +public: + AggregateStateCombine(AggregateFunctionPtr function, const DataTypes& argument_types_, + DataTypePtr return_type) + : IAggregateFunctionHelper(argument_types_), + _function(std::move(function)), + _return_type(std::move(return_type)) {} + + static AggregateFunctionPtr create(AggregateFunctionPtr function, + const DataTypes& argument_types_, + const DataTypePtr& return_type) { + if (function == nullptr) { + return nullptr; + } + return std::make_shared<AggregateStateCombine>(function, argument_types_, return_type); + } + + void set_version(const int version_) override { + IAggregateFunctionHelper::set_version(version_); + _function->set_version(version_); + } + + void create(AggregateDataPtr __restrict place) const override { _function->create(place); } + + void destroy_vec(AggregateDataPtr __restrict place, + const size_t num_rows) const noexcept override { + _function->destroy_vec(place, num_rows); + } + + String get_name() const override { return _function->get_name() + AGG_COMBINE_SUFFIX; } + + DataTypePtr get_return_type() const override { return _return_type; } + + void add(AggregateDataPtr __restrict place, const IColumn** columns, ssize_t row_num, + Arena& arena) const override { + _function->add(place, columns, row_num, arena); + } + + void add_batch(size_t batch_size, AggregateDataPtr* places, size_t place_offset, + const IColumn** columns, Arena& arena, bool agg_many) const override { + _function->add_batch(batch_size, places, place_offset, columns, arena, agg_many); + } + + void add_batch_selected(size_t batch_size, AggregateDataPtr* places, size_t place_offset, + const IColumn** columns, Arena& arena) const override { + _function->add_batch_selected(batch_size, places, place_offset, columns, arena); + } + + void add_batch_single_place(size_t batch_size, AggregateDataPtr place, const IColumn** columns, + Arena& arena) const override { + _function->add_batch_single_place(batch_size, place, columns, arena); + } + + void add_batch_range(size_t batch_begin, size_t batch_end, AggregateDataPtr place, + const IColumn** columns, Arena& arena, bool has_null) override { + _function->add_batch_range(batch_begin, batch_end, place, columns, arena, has_null); + } + + void add_range_single_place(int64_t partition_start, int64_t partition_end, int64_t frame_start, + int64_t frame_end, AggregateDataPtr place, const IColumn** columns, + Arena& arena, UInt8* use_null_result, + UInt8* could_use_previous_result) const override { + _function->add_range_single_place(partition_start, partition_end, frame_start, frame_end, + place, columns, arena, use_null_result, + could_use_previous_result); + } + + void reset(AggregateDataPtr place) const override { _function->reset(place); } + + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, + Arena& arena) const override { + _function->merge(place, rhs, arena); + } + + void merge_vec(const AggregateDataPtr __restrict* __restrict places, size_t offset, + ConstAggregateDataPtr __restrict rhs, Arena& arena, + const size_t num_rows) const override { + _function->merge_vec(places, offset, rhs, arena, num_rows); + } + + void merge_vec_selected(const AggregateDataPtr __restrict* __restrict places, size_t offset, + ConstAggregateDataPtr __restrict rhs, Arena& arena, + const size_t num_rows) const override { + _function->merge_vec_selected(places, offset, rhs, arena, num_rows); + } + + void serialize(ConstAggregateDataPtr __restrict place, BufferWritable& buf) const override { + _function->serialize(place, buf); + } + + void serialize_vec(const std::vector<AggregateDataPtr>& places, size_t offset, + BufferWritable& buf, const size_t num_rows) const override { + _function->serialize_vec(places, offset, buf, num_rows); + } + + void serialize_to_column(const std::vector<AggregateDataPtr>& places, size_t offset, + MutableColumnPtr& dst, const size_t num_rows) const override { + _function->serialize_to_column(places, offset, dst, num_rows); + } + + void serialize_without_key_to_column(ConstAggregateDataPtr __restrict place, + IColumn& to) const override { + _function->serialize_without_key_to_column(place, to); + } + + void deserialize(AggregateDataPtr __restrict place, BufferReadable& buf, + Arena& arena) const override { + _function->deserialize(place, buf, arena); + } + + void deserialize_vec(AggregateDataPtr places, const ColumnString* column, Arena& arena, + size_t num_rows) const override { + _function->deserialize_vec(places, column, arena, num_rows); + } + + void deserialize_and_merge_vec(const AggregateDataPtr* places, size_t offset, + AggregateDataPtr rhs, const IColumn* column, Arena& arena, + const size_t num_rows) const override { + _function->deserialize_and_merge_vec(places, offset, rhs, column, arena, num_rows); + } + + void deserialize_and_merge_vec_selected(const AggregateDataPtr* places, size_t offset, + AggregateDataPtr rhs, const IColumn* column, + Arena& arena, const size_t num_rows) const override { + _function->deserialize_and_merge_vec_selected(places, offset, rhs, column, arena, num_rows); + } + + void deserialize_and_merge(AggregateDataPtr __restrict place, AggregateDataPtr __restrict rhs, + BufferReadable& buf, Arena& arena) const override { + _function->deserialize_and_merge(place, rhs, buf, arena); + } + + void deserialize_and_merge_from_column_range(AggregateDataPtr __restrict place, + const IColumn& column, size_t begin, size_t end, + Arena& arena) const override { + _function->deserialize_and_merge_from_column_range(place, column, begin, end, arena); + } + + void insert_result_into(ConstAggregateDataPtr __restrict place, IColumn& to) const override { + _function->serialize_without_key_to_column(place, to); + } + + void streaming_agg_serialize_to_column(const IColumn** columns, MutableColumnPtr& dst, + const size_t num_rows, Arena& arena) const override { + _function->streaming_agg_serialize_to_column(columns, dst, num_rows, arena); + } + + void destroy(AggregateDataPtr __restrict place) const noexcept override { + _function->destroy(place); + } + + bool is_trivial() const override { return _function->is_trivial(); } + + size_t size_of_data() const override { return _function->size_of_data(); } + + size_t align_of_data() const override { return _function->align_of_data(); } + + void check_input_columns_type(const IColumn** columns) const override { + _function->check_input_columns_type(columns); + } + + MutableColumnPtr create_serialize_column() const override { + return _function->create_serialize_column(); + } + + DataTypePtr get_serialized_type() const override { return _function->get_serialized_type(); } + + bool supported_incremental_mode() const override { + return _function->supported_incremental_mode(); + } + + void execute_function_with_incremental(int64_t partition_start, int64_t partition_end, + int64_t frame_start, int64_t frame_end, + AggregateDataPtr place, const IColumn** columns, + Arena& arena, bool previous_is_nul, bool end_is_nul, + bool has_null, UInt8* use_null_result, + UInt8* could_use_previous_result) const override { + _function->execute_function_with_incremental( + partition_start, partition_end, frame_start, frame_end, place, columns, arena, + previous_is_nul, end_is_nul, has_null, use_null_result, could_use_previous_result); + } + + void set_query_context(QueryContext* context) override { Review Comment: Fixed by rejecting `ai_agg_state` and `ai_agg_combine` during FE function resolution. `AIAgg` now implements `NotSupportAggStateCreation`, which prevents a wrapped AI aggregate without the required query context from reaching BE. Full AI AggState support can be added later after its context and canonical arguments are modeled correctly. ########## be/src/exprs/vectorized_agg_fn.cpp: ########## @@ -174,46 +175,81 @@ Status AggFnEvaluator::prepare(RuntimeState* state, const RowDescriptor& desc, } else if (_fn.binary_type == TFunctionBinaryType::RPC) { _function = AggregateRpcUdaf::create(_fn, argument_types, _data_type); } else if (_fn.binary_type == TFunctionBinaryType::AGG_STATE) { - if (argument_types.size() != 1) { - return Status::InternalError("Agg state Function must input 1 argument but get {}", - argument_types.size()); - } - if (argument_types[0]->is_nullable()) { - return Status::InternalError("Agg state function input type must be not nullable"); - } - if (argument_types[0]->get_primitive_type() != PrimitiveType::TYPE_AGG_STATE) { - return Status::InternalError( - "Agg state function input type must be agg_state but get {}", - argument_types[0]->get_family_name()); - } - - std::string type_function_name = - assert_cast<const DataTypeAggState*>(argument_types[0].get())->get_function_name(); - if (type_function_name + AGG_UNION_SUFFIX == _fn.name.function_name) { + if (match_suffix(_fn.name.function_name, AGG_COMBINE_SUFFIX)) { if (_data_type->is_nullable()) { return Status::InternalError( - "Union function return type must be not nullable, real={}", + "Combine function return type must be not nullable, real={}", _data_type->get_name()); } if (_data_type->get_primitive_type() != PrimitiveType::TYPE_AGG_STATE) { return Status::InternalError( - "Union function return type must be AGG_STATE, real={}", + "Combine function return type must be AGG_STATE, real={}", _data_type->get_name()); } - _function = get_agg_state_function<AggregateStateUnion>(argument_types, _data_type); - } else if (type_function_name + AGG_MERGE_SUFFIX == _fn.name.function_name) { - auto type = assert_cast<const DataTypeAggState*>(argument_types[0].get()) - ->get_nested_function() - ->get_return_type(); - if (!type->equals(*_data_type)) { - return Status::InternalError("{}'s expect return type is {}, but input {}", - argument_types[0]->get_name(), type->get_name(), + const auto* state_type = assert_cast<const DataTypeAggState*>(_data_type.get()); + if (state_type->get_function_name() + AGG_COMBINE_SUFFIX != _fn.name.function_name) { + return Status::InternalError("{} not match return type {}", _fn.name.function_name, _data_type->get_name()); } - _function = get_agg_state_function<AggregateStateMerge>(argument_types, _data_type); + const auto& expected_argument_types = state_type->get_sub_types(); + if (argument_types.size() != expected_argument_types.size()) { + return Status::InternalError("Combine function {} expects {} arguments but gets {}", + _fn.name.function_name, expected_argument_types.size(), + argument_types.size()); + } + for (size_t i = 0; i < argument_types.size(); ++i) { + if (!argument_types[i]->equals(*expected_argument_types[i])) { + return Status::InternalError( + "Combine function {} argument {} expects {}, but gets {}", + _fn.name.function_name, i, expected_argument_types[i]->get_name(), + argument_types[i]->get_name()); + } + } + _function = AggregateStateCombine::create(state_type->get_nested_function(), Review Comment: Fixed. `WindowFunctionChecker` now explicitly rejects `CombineCombinator`, so `_combine` cannot be planned as a window function until a window-aware nested state is implemented. A unit test verifies the analysis rejection. ########## fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/rollup/SingleCombinatorRollupHandler.java: ########## @@ -53,7 +54,8 @@ public boolean canRollup(AggregateFunction queryAggregateFunction, return false; } if (!(queryAggregateFunction instanceof Combinator) - && (viewFunction instanceof UnionCombinator || viewFunction instanceof StateCombinator)) { + && (viewFunction instanceof UnionCombinator || viewFunction instanceof StateCombinator + || viewFunction instanceof CombineCombinator)) { Review Comment: Fixed. Synchronous MV analysis now explicitly rejects `CombineCombinator` instead of storing it through the existing aggregate-state rewrite path. A regression test covers the expected analysis error. ########## fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/AggCombinerFunctionBuilder.java: ########## @@ -65,7 +68,11 @@ public Class<? extends BoundFunction> functionClass() { @Override public boolean canApply(List<?> arguments) { - if (combinatorSuffix.equalsIgnoreCase(STATE) || combinatorSuffix.equalsIgnoreCase(FOREACH)) { + if (combinatorSuffix.equalsIgnoreCase(COMBINE)) { + // DataTypeAggState needs at least one subtype, so zero-argument aggregates such as + // count(*) cannot produce an AggState yet. count_combine(1) remains supported. + return !arguments.isEmpty() && nestedBuilder.canApply(arguments); Review Comment: Fixed. `AggCombinerFunctionBuilder.canApply` first verifies that the nested builder represents an `AggregateFunction`. Scalar names such as `abs_combine` now fail normal function resolution instead of reaching the aggregate cast and throwing an internal exception. Covered by unit test. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
