This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 0d17e0b9afd branch-4.2: [fix](be) Report sequence pattern iteration
exhaustion as an error #69834 (#70254)
0d17e0b9afd is described below
commit 0d17e0b9afd53f4002aebdad1ee8ad74d1c47206
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Nov 6 11:18:42 2026 +0800
branch-4.2: [fix](be) Report sequence pattern iteration exhaustion as an
error #69834 (#70254)
Cherry-picked from #69834
Co-authored-by: Jerry Hu <[email protected]>
---
.../aggregate/aggregate_function_sequence_match.h | 20 ++++---
.../exprs/aggregate/vec_sequence_match_test.cpp | 68 ++++++++++++++++++++++
.../test_sequence_match_iteration_limit.out | 6 ++
.../test_sequence_match_iteration_limit.groovy | 61 +++++++++++++++++++
4 files changed, 146 insertions(+), 9 deletions(-)
diff --git a/be/src/exprs/aggregate/aggregate_function_sequence_match.h
b/be/src/exprs/aggregate/aggregate_function_sequence_match.h
index 4ec45ad323b..b15dae805f9 100644
--- a/be/src/exprs/aggregate/aggregate_function_sequence_match.h
+++ b/be/src/exprs/aggregate/aggregate_function_sequence_match.h
@@ -68,7 +68,7 @@ struct ComparePairFirst final {
static constexpr size_t MAX_EVENTS = 32;
-/// Max number of iterations to match the pattern against a sequence,
exception thrown when exceeded
+/// Max number of iterations to match the pattern against a sequence;
exceeding it is an error.
constexpr auto sequence_match_max_iterations = 1000000l;
template <PrimitiveType T, typename Derived>
@@ -472,10 +472,11 @@ public:
}
if (++i > sequence_match_max_iterations) {
- LOG(WARNING)
- << "Pattern application proves too difficult,
exceeding max iterations (" +
-
std::to_string(sequence_match_max_iterations) + ")";
- return false;
+ // The only matching event may follow the budget (e.g. a
million earlier events).
+ // Exhausting the search is not proof that the pattern does
not match.
+ throw Exception(ErrorCode::INVALID_ARGUMENT,
+ "sequence pattern evaluation exceeded max
iterations ({})",
+ sequence_match_max_iterations);
}
}
@@ -530,10 +531,11 @@ public:
}
if (limit_iterations && ++events_processed >
sequence_match_max_iterations) {
- LOG(WARNING) << "Pattern application proves too difficult,
exceeding max "
- "iterations are " +
-
std::to_string(sequence_match_max_iterations);
- return false;
+ // A deterministic event may still occur after the
inspected prefix.
+ // Report the exhausted budget instead of treating it as a
missing event.
+ throw Exception(ErrorCode::INVALID_ARGUMENT,
+ "sequence pattern evaluation exceeded max
iterations ({})",
+ sequence_match_max_iterations);
}
}
diff --git a/be/test/exprs/aggregate/vec_sequence_match_test.cpp
b/be/test/exprs/aggregate/vec_sequence_match_test.cpp
index 63f642102d1..ba592dd7c2a 100644
--- a/be/test/exprs/aggregate/vec_sequence_match_test.cpp
+++ b/be/test/exprs/aggregate/vec_sequence_match_test.cpp
@@ -19,6 +19,7 @@
#include <gtest/gtest-test-part.h>
#include <memory>
+#include <string>
#include <vector>
#include "core/column/column_nullable.h"
@@ -98,6 +99,42 @@ public:
assert_add_throws(agg_function, columns.data());
}
+ void assert_time_condition_result(const AggregateFunctionPtr& agg_function,
+ const IColumn* columns[], int
event1_count) {
+ std::unique_ptr<char[]> memory(new char[agg_function->size_of_data()]);
+ AggregateDataPtr place = memory.get();
+ agg_function->create(place);
+ for (int i = 0; i < event1_count; ++i) {
+ agg_function->add(place, columns, 0, arena);
+ }
+ agg_function->add(place, columns, 1, arena);
+
+ bool limit_exceeded = false;
+ try {
+ if (agg_function == agg_function_sequence_match) {
+ ColumnUInt8 result;
+ agg_function->insert_result_into(place, result);
+ if (event1_count < 999998) {
+ EXPECT_EQ(result.get_data()[0], event1_count == 0 ? 0 : 1)
+ << "event1_count=" << event1_count;
+ }
+ } else {
+ ColumnInt64 result;
+ agg_function->insert_result_into(place, result);
+ if (event1_count < 999998) {
+ EXPECT_EQ(result.get_data()[0], event1_count == 0 ? 0 : 1)
+ << "event1_count=" << event1_count;
+ }
+ }
+ } catch (const Exception& e) {
+ limit_exceeded = true;
+ EXPECT_EQ(e.code(), ErrorCode::INVALID_ARGUMENT);
+ EXPECT_NE(e.message().find("exceeded max iterations (1000000)"),
std::string::npos);
+ }
+ EXPECT_EQ(limit_exceeded, event1_count >= 999998) << "event1_count="
<< event1_count;
+ agg_function->destroy(place);
+ }
+
Arena arena;
};
@@ -667,4 +704,35 @@ TEST_F(VSequenceMatchTest,
testMatchRejectsInvalidPatternRegardlessOfNullTimesta
}
}
+TEST_F(VSequenceMatchTest, testTimeConditionIterationLimitReportsError) {
+ auto column_pattern = ColumnString::create();
+
column_pattern->insert(Field::create_field<TYPE_STRING>("(?1)(?t>=1)(?2)"));
+
+ auto column_timestamp = ColumnDateTimeV2::create();
+ for (int second = 0; second < 2; ++second) {
+ DateV2Value<DateTimeV2ValueType> time_value;
+ time_value.unchecked_set_time(2026, 1, 1, 0, 0, second);
+ column_timestamp->insert_data((char*)&time_value, 0);
+ }
+
+ auto column_event1 = ColumnUInt8::create();
+ column_event1->insert_value(1);
+ column_event1->insert_value(0);
+ auto column_event2 = ColumnUInt8::create();
+ column_event2->insert_value(0);
+ column_event2->insert_value(1);
+ auto column_event3 = ColumnUInt8::create();
+ column_event3->insert_value(0);
+ column_event3->insert_value(0);
+
+ const IColumn* columns[] = {column_pattern.get(), column_timestamp.get(),
column_event1.get(),
+ column_event2.get(), column_event3.get()};
+ for (int event1_count : {0, 1000, 999997, 999998, 1000001}) {
+ for (const auto& agg_function :
+ {agg_function_sequence_match, agg_function_sequence_count}) {
+ assert_time_condition_result(agg_function, columns, event1_count);
+ }
+ }
+}
+
} // namespace doris
diff --git
a/regression-test/data/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.out
b/regression-test/data/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.out
new file mode 100644
index 00000000000..75dff20dfe6
--- /dev/null
+++
b/regression-test/data/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.out
@@ -0,0 +1,6 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !small_match --
+true 1
+
+-- !small_no_match --
+false 0
diff --git
a/regression-test/suites/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.groovy
b/regression-test/suites/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.groovy
new file mode 100644
index 00000000000..2bfab137d96
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/aggregate_functions/test_sequence_match_iteration_limit.groovy
@@ -0,0 +1,61 @@
+// 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("test_sequence_match_iteration_limit") {
+ sql "SET query_timeout = 120"
+ sql "DROP TABLE IF EXISTS sequence_match_iteration_limit_test"
+ sql """
+ CREATE TABLE sequence_match_iteration_limit_test (
+ id BIGINT NOT NULL,
+ ts DATETIMEV2(0) NOT NULL,
+ evt TINYINT NOT NULL
+ ) DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ INSERT INTO sequence_match_iteration_limit_test
+ SELECT number,
+ IF(number < 1000001, CAST('2026-01-01 00:00:00' AS
DATETIMEV2(0)),
+ CAST('2026-01-01 00:00:01' AS
DATETIMEV2(0))),
+ CAST(IF(number < 1000001, 1, 2) AS TINYINT)
+ FROM numbers('number' = '1000002')
+ """
+
+ // A single early event 1 and the final event 2 form one valid match.
+ qt_small_match """
+ SELECT sequence_match('(?1)(?t>=1)(?2)', ts, evt = 1, evt = 2),
+ sequence_count('(?1)(?t>=1)(?2)', ts, evt = 1, evt = 2)
+ FROM sequence_match_iteration_limit_test WHERE id IN (0, 1000001)
+ """
+ qt_small_no_match """
+ SELECT sequence_match('(?1)(?t>=2)(?2)', ts, evt = 1, evt = 2),
+ sequence_count('(?1)(?t>=2)(?2)', ts, evt = 1, evt = 2)
+ FROM sequence_match_iteration_limit_test WHERE id IN (0, 1000001)
+ """
+
+ // The final event must not be reported as absent when the search budget
is exhausted.
+ test {
+ sql """SELECT sequence_match('(?1)(?t>=1)(?2)', ts, evt = 1, evt = 2)
+ FROM sequence_match_iteration_limit_test"""
+ exception "exceeded max iterations"
+ }
+ test {
+ sql """SELECT sequence_count('(?1)(?t>=1)(?2)', ts, evt = 1, evt = 2)
+ FROM sequence_match_iteration_limit_test"""
+ exception "exceeded max iterations"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]