Skip to content

Commit 034800e

Browse files
committed
[fix](fe) Fix NoSuchElementException when count with MATCH_ALL expression
Issue Number: close #??? Problem Summary: When executing explain on SQL like: SELECT count(col MATCH_ALL 'xxx') FROM table WHERE ... The Nereids optimizer throws NoSuchElementException because storageLayerAggregate rule tries to get originalColumn from a SlotReference that represents a virtual column (generated from MATCH_ALL expression). The virtual column has no physical column in the table schema, so getOriginalColumn() returns Optional.empty(). The fix adds proper isPresent() checks before calling get() on Optional. Release note: Fix a bug where count(MATCH_ALL) causes explain to fail with NoSuchElementException Test: - Added regression test test_count_match_all_pushdown to cover this case
1 parent 0e4ffd0 commit 034800e

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

fe/fe-core/src/main/java/org/apache/doris/nereids/rules/implementation/AggregateStrategies.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,11 @@ private boolean checkWhetherPushDownMinMax(Set<AggregateFunction> aggregateFunct
513513
SlotReference.class::isInstance);
514514
List<SlotReference> usedSlotInTable = (List<SlotReference>) Project.findProject(aggUsedSlots, outPutSlots);
515515
for (SlotReference slot : usedSlotInTable) {
516-
Column column = slot.getOriginalColumn().get();
516+
Optional<Column> optionalColumn = slot.getOriginalColumn();
517+
if (!optionalColumn.isPresent()) {
518+
return false;
519+
}
520+
Column column = optionalColumn.get();
517521
PrimitiveType colType = column.getType().getPrimitiveType();
518522
if (colType.isComplexType() || colType.isHllType() || colType.isBitmapType()) {
519523
return false;
@@ -687,7 +691,13 @@ private LogicalAggregate<? extends Plan> storageLayerAggregate(
687691
logicalScan.getOutput());
688692

689693
for (SlotReference slot : usedSlotInTable) {
690-
Column column = slot.getOriginalColumn().get();
694+
Optional<Column> optionalColumn = slot.getOriginalColumn();
695+
if (!optionalColumn.isPresent()) {
696+
// virtual columns (e.g., generated from MATCH_ALL expressions) do not have
697+
// an original column and cannot be pushed down to storage layer aggregate
698+
return canNotPush;
699+
}
700+
Column column = optionalColumn.get();
691701
if (column.isAggregated()) {
692702
return canNotPush;
693703
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- This file is automatically generated. You should know what you did if you want to edit this
2+
-- !match_all_count_1 --
3+
3
4+
5+
-- !match_all_count_2 --
6+
3
7+
8+
-- !match_any_count --
9+
3
10+
11+
-- !match_phrase_count --
12+
3
13+
14+
-- !match_all_with_where --
15+
2
16+
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
suite("test_count_match_all_pushdown", "p0") {
19+
// Test that count(col MATCH_ALL 'xxx') should not push down storage layer aggregate.
20+
// MATCH_ALL generates a virtual column in OlapScan without originalColumn,
21+
// which previously caused NoSuchElementException on Optional.get().
22+
23+
sql "DROP TABLE IF EXISTS test_count_match_all_pushdown"
24+
sql """
25+
CREATE TABLE test_count_match_all_pushdown (
26+
`id` INT NOT NULL,
27+
`content` TEXT NOT NULL,
28+
`nullable_content` TEXT NULL,
29+
INDEX content_idx (`content`) USING INVERTED PROPERTIES("parser" = "english") COMMENT '',
30+
INDEX nullable_content_idx (`nullable_content`) USING INVERTED PROPERTIES("parser" = "english") COMMENT ''
31+
) ENGINE=OLAP
32+
DUPLICATE KEY(`id`)
33+
DISTRIBUTED BY HASH(`id`) BUCKETS 1
34+
PROPERTIES (
35+
"replication_allocation" = "tag.location.default: 1"
36+
);
37+
"""
38+
39+
sql """
40+
INSERT INTO test_count_match_all_pushdown VALUES
41+
(1, 'hello world', 'foo bar'),
42+
(2, 'hello doris', 'foo baz'),
43+
(3, 'world doris', NULL);
44+
"""
45+
46+
sql "SET disable_nereids_rules='REWRITE_SIMPLE_AGG_TO_CONSTANT'"
47+
48+
// count(MATCH_ALL expr) should not be pushed down to storage layer
49+
explain {
50+
sql("select count(content MATCH_ALL 'hello world') from test_count_match_all_pushdown;")
51+
contains "pushAggOp=NONE"
52+
}
53+
54+
explain {
55+
sql("select count(content MATCH_ALL 'hello') from test_count_match_all_pushdown;")
56+
contains "pushAggOp=NONE"
57+
}
58+
59+
// count(MATCH_ANY expr) should not be pushed down either
60+
explain {
61+
sql("select count(content MATCH_ANY 'hello world') from test_count_match_all_pushdown;")
62+
contains "pushAggOp=NONE"
63+
}
64+
65+
// count(MATCH_PHRASE expr) should not be pushed down either
66+
explain {
67+
sql("select count(content MATCH_PHRASE 'hello world') from test_count_match_all_pushdown;")
68+
contains "pushAggOp=NONE"
69+
}
70+
71+
// Verify actual execution returns correct results
72+
order_qt_match_all_count_1 "select count(content MATCH_ALL 'hello world') from test_count_match_all_pushdown;"
73+
order_qt_match_all_count_2 "select count(content MATCH_ALL 'hello') from test_count_match_all_pushdown;"
74+
order_qt_match_any_count "select count(content MATCH_ANY 'hello world') from test_count_match_all_pushdown;"
75+
order_qt_match_phrase_count "select count(content MATCH_PHRASE 'hello world') from test_count_match_all_pushdown;"
76+
77+
// Test with complex WHERE clause (the original bug scenario)
78+
order_qt_match_all_with_where """
79+
select count(content MATCH_ALL 'hello') from test_count_match_all_pushdown
80+
where (case nullable_content when 'foo bar' then 1 end) = 1
81+
or not ((case id when 1 then 1 end) = 1 and (nullable_content is null));
82+
"""
83+
}

0 commit comments

Comments
 (0)