This is an automated email from the ASF dual-hosted git repository.

mrhhsg 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 dac77e06562 [fix](be) Report sequence pattern iteration exhaustion as 
an error (#69834)
dac77e06562 is described below

commit dac77e06562bdb35690b49c02475822e45dd0d43
Author: Jerry Hu <[email protected]>
AuthorDate: Thu Nov 5 14:39:26 2026 +0800

    [fix](be) Report sequence pattern iteration exhaustion as an error (#69834)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Problem Summary: For time-constrained `sequence_match` and
    `sequence_count`, both the deterministic prefilter and the backtracking
    search returned `false` when their one-million-iteration budget was
    exceeded. A valid event can still appear later in the sequence, so a
    completed query could incorrectly return `false`, `0`, or a partial
    count. Report `INVALID_ARGUMENT` from both exhaustion paths instead of
    returning an incomplete result. The existing iteration limit, comparison
    boundary, and results for searches that complete within the budget are
    unchanged.
    
    ### Release note
    
    Time-constrained `sequence_match` and `sequence_count` now return an
    explicit error when pattern evaluation exceeds one million iterations
    instead of silently returning an incomplete result.
    
    ### Check List (For Author)
    
    - Test:
    - Unit Test: `./run-be-ut.sh --run --filter='VSequenceMatchTest.*' -j
    16` (13/13 passed).
    - Regression test: `test_sequence_match_iteration_limit` in
    `query_p0/sql_functions/aggregate_functions`; generated expected output
    and reran the suite (1/1 passed). The suite checks both SQL errors and
    normal match/no-match results.
    - Build and checks: ASAN BE+FE build with FE Checkstyle; BE
    clang-format, check-format, build hygiene, clang-tidy, and
    committed-diff checks passed.
    - Behavior changed: Yes. Iteration-budget exhaustion now fails the query
    with `INVALID_ARGUMENT` rather than returning an incomplete result.
    - Does this need documentation: No
---
 .../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 0b9f82d5a7a..086cd4e7ce7 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>
@@ -482,10 +482,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);
             }
         }
 
@@ -540,10 +541,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 5a8a35f422a..8908dc8d51c 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"
@@ -94,6 +95,42 @@ public:
         agg_function->destroy(place);
     }
 
+    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;
 };
 
@@ -663,4 +700,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]

Reply via email to