feiniaofeiafei commented on code in PR #65245:
URL: https://github.com/apache/doris/pull/65245#discussion_r3534640049


##########
be/src/exprs/aggregate/aggregate_function_array_agg.cpp:
##########
@@ -25,9 +27,19 @@
 namespace doris {
 
 template <PrimitiveType T>
-AggregateFunctionPtr do_create_agg_function_collect(const DataTypes& 
argument_types,
+AggregateFunctionPtr do_create_agg_function_collect(bool distinct, const 
DataTypes& argument_types,
                                                     const bool 
result_is_nullable,
                                                     const 
AggregateFunctionAttr& attr) {
+    if (distinct) {
+        if constexpr (T == INVALID_TYPE) {
+            throw Exception(ErrorCode::INTERNAL_ERROR,
+                            "unexpected type for array_agg distinct, please 
check the input");
+        } else {
+            return creator_without_type::create<

Review Comment:
   **P1 — `array_agg(DISTINCT nullable_col)` drops NULL.** The normal nullable 
`array_agg` path uses `create_ignore_nullable` and lets 
`AggregateFunctionArrayAggData` preserve the null map. This new set-based path 
uses the regular nullable adapter, which skips null input rows before the inner 
aggregate sees them, and `AggregateFunctionCollectSetData` has no state for a 
distinct NULL. As a result, the multi-distinct plan returns an array without 
NULL, while the existing single-distinct path should retain one NULL. Please 
preserve one distinct NULL explicitly and add a regression case with nullable 
input.



##########
regression-test/suites/nereids_p0/multi_distinct/multi_distinct_collect_list.groovy:
##########
@@ -0,0 +1,105 @@
+// 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.
+
+suite('multi_distinct_collect_list') {
+    sql "drop table if exists multi_distinct_cl;"
+    sql """
+    CREATE TABLE multi_distinct_cl (
+        g int,
+        a varchar(16),
+        b varchar(16)
+        ) ENGINE = OLAP
+        DUPLICATE KEY(g) COMMENT 'OLAP'
+        DISTRIBUTED BY HASH(g) BUCKETS 2
+        PROPERTIES (
+        "replication_allocation" = "tag.location.default: 1"
+        );
+    """
+
+    // g=1: a in (x,x,y) b in (p,p,q); g=2: a in (m,m,n) b in (r,r,s) -- 
duplicates per group make dedup observable.
+    sql """
+    insert into multi_distinct_cl values
+        (1, 'x', 'p'), (1, 'x', 'p'), (1, 'y', 'q'),
+        (2, 'm', 'r'), (2, 'm', 'r'), (2, 'n', 's');
+    """
+
+    // (a) Multiple distinct aggregates in one GROUP BY now plan (previously 
threw "can't support multi distinct"); explain proves the multi_distinct_* 
rewrite fired.
+    explain {
+        sql "select g, collect_list(distinct a), collect_list(distinct b) from 
multi_distinct_cl group by g"
+        contains "multi_distinct_collect_list"
+    }
+    explain {
+        sql "select g, array_agg(distinct a), array_agg(distinct b) from 
multi_distinct_cl group by g"
+        contains "multi_distinct_array_agg"
+    }
+    // Mixed collect_list + array_agg, both DISTINCT, still on the 
multi-distinct path.
+    explain {
+        sql "select g, collect_list(distinct a), array_agg(distinct b) from 
multi_distinct_cl group by g"
+        contains "multi_distinct_collect_list"
+    }
+
+    // (b) DISTINCT dedups. Two distinct args (a AND b) exercise the 
multi-distinct path (getDistinctArguments().size() > 1); array_sort() makes 
element order deterministic.
+    def cl = sql """
+        select g,
+               array_sort(collect_list(distinct a)) la,
+               array_sort(collect_list(distinct b)) lb
+        from multi_distinct_cl
+        group by g
+        order by g
+    """
+    assertEquals(2, cl.size())

Review Comment:
   **P2 — This result check does not follow the regression-test convention.** 
Deterministic expected results should use `qt_`/`order_qt_` and a generated 
`.out` file instead of manual `assertEquals` checks. Please convert these 
result assertions accordingly. The test data is also entirely non-null, so it 
misses the `array_agg(DISTINCT nullable_col)` semantic regression above; please 
add nullable input and the supported/unsupported type boundary cases.



##########
be/src/exprs/aggregate/aggregate_function_array_agg.cpp:
##########
@@ -25,9 +27,19 @@
 namespace doris {
 
 template <PrimitiveType T>
-AggregateFunctionPtr do_create_agg_function_collect(const DataTypes& 
argument_types,
+AggregateFunctionPtr do_create_agg_function_collect(bool distinct, const 
DataTypes& argument_types,
                                                     const bool 
result_is_nullable,
                                                     const 
AggregateFunctionAttr& attr) {
+    if (distinct) {
+        if constexpr (T == INVALID_TYPE) {

Review Comment:
   **P2 — FE and BE advertise different type support.** `MultiDistinctArrayAgg` 
declares an `AnyDataType` argument, so FE can accept types that 
`dispatch_switch_all` does not dispatch, but this branch turns them into an 
`INTERNAL_ERROR` at execution time. Please either implement the distinct state 
for every FE-advertised type or reject unsupported types during FE analysis 
with a user-facing error. An accepted query must not fail later with an 
internal error.



##########
be/src/exprs/aggregate/aggregate_function_array_agg.cpp:
##########
@@ -44,23 +56,25 @@ AggregateFunctionPtr 
create_aggregate_function_array_agg(const std::string& name
                                                          const DataTypePtr& 
result_type,
                                                          const bool 
result_is_nullable,
                                                          const 
AggregateFunctionAttr& attr) {
+    bool distinct = name == "multi_distinct_array_agg";
     AggregateFunctionPtr agg_fn;
     auto call = [&](const auto& type) -> bool {
         using DispatcType = std::decay_t<decltype(type)>;
-        agg_fn = 
do_create_agg_function_collect<DispatcType::PType>(argument_types,
+        agg_fn = do_create_agg_function_collect<DispatcType::PType>(distinct, 
argument_types,
                                                                     
result_is_nullable, attr);
         return true;
     };
 
     if (!dispatch_switch_all(argument_types[0]->get_primitive_type(), call)) {
         // We do not care what the real type is.
-        agg_fn = do_create_agg_function_collect<INVALID_TYPE>(argument_types, 
result_is_nullable,
-                                                              attr);
+        agg_fn = do_create_agg_function_collect<INVALID_TYPE>(distinct, 
argument_types,
+                                                              
result_is_nullable, attr);
     }
     return agg_fn;
 }
 
 void register_aggregate_function_array_agg(AggregateFunctionSimpleFactory& 
factory) {
     factory.register_function_both("array_agg", 
create_aggregate_function_array_agg);
+    factory.register_function_both("multi_distinct_array_agg", 
create_aggregate_function_array_agg);

Review Comment:
   **P3 — The registered function is reported as `collect_set` by BE.** This 
implementation instantiates 
`AggregateFunctionCollect<AggregateFunctionCollectSetData<...>>`, whose 
`get_name()` returns `collect_set`. Therefore `multi_distinct_array_agg` (and 
similarly `multi_distinct_collect_list`) is mislabeled in profiles and 
error/debug output. Please preserve the registered function identity, for 
example by passing/storing the logical function name or using a dedicated 
implementation whose `get_name()` returns the correct name.



-- 
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]

Reply via email to