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

csun5285 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 fd4f8acc03e [fix](regexp) Apply extended regex fallback to 
non-constant patterns (#68501)
fd4f8acc03e is described below

commit fd4f8acc03ececbd9896043a3dbad9c976f1245f
Author: Jerry Hu <[email protected]>
AuthorDate: Mon Sep 28 11:17:23 2026 +0800

    [fix](regexp) Apply extended regex fallback to non-constant patterns 
(#68501)
    
    
    With `enable_extended_regex = true`, a REGEXP/RLIKE pattern that RE2
    rejects
    (for example lookaround assertions such as `(?<=foo)bar` or
    `foo(?=123)`) is
    compiled with Boost.Regex instead. That fallback only existed in the
    constant
    pattern path built during `open()`. When the pattern comes from a
    column, or
    from a constant that is only visible at execute time, `regexp_fn` and
    `regexp_fn_scalar` compiled the pattern with RE2 only and failed the
    query
    with `Invalid pattern: foo(?=123)` even though the same pattern worked
    as a
    constant.
    
    Reproduce:
    
    ```sql
    SET enable_extended_regex = true;
    CREATE TABLE regex_patterns (s VARCHAR(32), p VARCHAR(64))
    DISTRIBUTED BY HASH(s) BUCKETS 1 PROPERTIES ("replication_num" = "1");
    INSERT INTO regex_patterns VALUES ('foobar', '(?<=foo)bar'), ('foo123bar', 
'foo(?=123)');
    SELECT REGEXP('foobar', '(?<=foo)bar');            -- 1
    SELECT s, p, s REGEXP p FROM regex_patterns;       -- ERROR: Invalid 
pattern: foo(?=123)
    ```
    
    Fix: move the RE2-then-Boost compilation into a shared
    `FunctionLikeBase::compile_regex` helper and a matching `regex_search`
    helper, store `enable_extended_regex` in `LikeSearchState` next to
    `enable_hyperscan_fallback`, and use the helpers in the constant path,
    the
    per-batch `regexp_fn` path and the per-row `regexp_fn_scalar` path.
    After the
    fix the column query returns `1` for both rows; when the session
    variable is
    off, all three paths report the same
    `Invalid regex expression: ... try setting enable_extended_regex=true`
    error
    instead of the bare `Invalid pattern` message.
    
    Two related hardenings in the same helpers: RE2 no longer logs every
    rejected
    pattern to stderr (the error text is returned in the Status, and column
    patterns are compiled once per row), and a `boost::regex_error` raised
    while
    matching (Boost gives up on pathological patterns such as `(?=a)(a+)+b`
    once
    its backtracking budget is exhausted) is converted into a non-OK Status
    instead of escaping as a C++ exception, which would otherwise terminate
    the
    process when the predicate is evaluated on a scanner thread.
---
 be/src/exprs/function/like.cpp                     | 131 +++++++++++----------
 be/src/exprs/function/like.h                       |  17 +++
 be/test/exprs/function/function_like_test.cpp      | 104 ++++++++++++++--
 .../test_regexp_extended_nonconstant_pattern.out   |  28 +++++
 ...test_regexp_extended_nonconstant_pattern.groovy |  85 +++++++++++++
 5 files changed, 298 insertions(+), 67 deletions(-)

diff --git a/be/src/exprs/function/like.cpp b/be/src/exprs/function/like.cpp
index 2dfd08d3b51..c02f9875cc0 100644
--- a/be/src/exprs/function/like.cpp
+++ b/be/src/exprs/function/like.cpp
@@ -187,6 +187,7 @@ struct VectorEndsWithSearchState : public 
VectorPatternSearchState {
 Status LikeSearchState::clone(LikeSearchState& cloned) const {
     cloned.set_search_string(search_string);
     cloned.enable_hyperscan_fallback = enable_hyperscan_fallback;
+    cloned.enable_extended_regex = enable_extended_regex;
 
     std::string re_pattern;
     FunctionLike::convert_like_pattern(this, pattern_str, &re_pattern);
@@ -424,10 +425,8 @@ Status FunctionLikeBase::constant_regex_fn_scalar(const 
LikeSearchState* state,
         if (ret != HS_SUCCESS && ret != HS_SCAN_TERMINATED) {
             return Status::RuntimeError(fmt::format("hyperscan error: {}", 
ret));
         }
-    } else if (state->boost_regex) { // use boost::regex for advanced features
-        *result = boost::regex_search(val.data, val.data + val.size, 
*state->boost_regex);
-    } else { // fallback to re2
-        *result = RE2::PartialMatch(re2::StringPiece(val.data, val.size), 
*state->regex);
+    } else { // re2, or boost::regex for advanced features
+        RETURN_IF_ERROR(regex_search(state->regex.get(), 
state->boost_regex.get(), val, result));
     }
 
     return Status::OK();
@@ -435,15 +434,11 @@ Status FunctionLikeBase::constant_regex_fn_scalar(const 
LikeSearchState* state,
 
 Status FunctionLikeBase::regexp_fn_scalar(const LikeSearchState* state, const 
StringRef& val,
                                           const StringRef& pattern, unsigned 
char* result) {
-    RE2::Options opts;
-    opts.set_never_nl(false);
-    opts.set_dot_nl(true);
-    re2::RE2 re(re2::StringPiece(pattern.data, pattern.size), opts);
-    if (re.ok()) {
-        *result = RE2::PartialMatch(re2::StringPiece(val.data, val.size), re);
-    } else {
-        return Status::RuntimeError("Invalid pattern: {}", 
pattern.debug_string());
-    }
+    std::unique_ptr<re2::RE2> regex;
+    std::unique_ptr<boost::regex> boost_regex;
+    RETURN_IF_ERROR(compile_regex(std::string_view(pattern.data, pattern.size),
+                                  state->enable_extended_regex, &regex, 
&boost_regex));
+    RETURN_IF_ERROR(regex_search(regex.get(), boost_regex.get(), val, result));
 
     return Status::OK();
 }
@@ -462,17 +457,10 @@ Status FunctionLikeBase::constant_regex_fn(const 
LikeSearchState* state, const C
                 return Status::RuntimeError(fmt::format("hyperscan error: {}", 
ret));
             }
         }
-    } else if (state->boost_regex) { // use boost::regex for advanced features
+    } else { // re2, or boost::regex for advanced features
         for (size_t i = 0; i < sz; i++) {
-            const auto& str_ref = val.get_data_at(i);
-            *(result.data() + i) = boost::regex_search(str_ref.data, 
str_ref.data + str_ref.size,
-                                                       *state->boost_regex);
-        }
-    } else { // fallback to re2
-        for (size_t i = 0; i < sz; i++) {
-            const auto& str_ref = val.get_data_at(i);
-            *(result.data() + i) =
-                    RE2::PartialMatch(re2::StringPiece(str_ref.data, 
str_ref.size), *state->regex);
+            RETURN_IF_ERROR(regex_search(state->regex.get(), 
state->boost_regex.get(),
+                                         val.get_data_at(i), result.data() + 
i));
         }
     }
 
@@ -500,29 +488,71 @@ Status FunctionLikeBase::regexp_fn(const LikeSearchState* 
state, const ColumnStr
 
         hs_free_scratch(scratch);
         hs_free_database(database);
-    } else { // fallback to re2
+    } else { // fallback to re2, then boost::regex for advanced features
         if (!state->enable_hyperscan_fallback) {
             return hs_status;
         }
-        RE2::Options opts;
-        opts.set_never_nl(false);
-        opts.set_dot_nl(true);
-        re2::RE2 re(re_pattern, opts);
-        if (re.ok()) {
-            auto sz = val.size();
-            for (size_t i = 0; i < sz; i++) {
-                const auto& str_ref = val.get_data_at(i);
-                *(result.data() + i) =
-                        RE2::PartialMatch(re2::StringPiece(str_ref.data, 
str_ref.size), re);
-            }
-        } else {
-            return Status::RuntimeError("Invalid pattern: {}", 
pattern.debug_string());
+        std::unique_ptr<re2::RE2> regex;
+        std::unique_ptr<boost::regex> boost_regex;
+        RETURN_IF_ERROR(
+                compile_regex(re_pattern, state->enable_extended_regex, 
&regex, &boost_regex));
+        auto sz = val.size();
+        for (size_t i = 0; i < sz; i++) {
+            RETURN_IF_ERROR(regex_search(regex.get(), boost_regex.get(), 
val.get_data_at(i),
+                                         result.data() + i));
         }
     }
 
     return Status::OK();
 }
 
+Status FunctionLikeBase::compile_regex(std::string_view pattern, bool 
enable_extended_regex,
+                                       std::unique_ptr<re2::RE2>* regex,
+                                       std::unique_ptr<boost::regex>* 
boost_regex) {
+    RE2::Options opts;
+    opts.set_never_nl(false);
+    opts.set_dot_nl(true);
+    // A rejected pattern is reported through the returned Status (or handed 
to Boost.Regex), and
+    // non-constant patterns are compiled once per row, so keep RE2 from 
logging every rejection.
+    opts.set_log_errors(false);
+    *regex = std::make_unique<RE2>(re2::StringPiece(pattern.data(), 
pattern.size()), opts);
+    if ((*regex)->ok()) {
+        return Status::OK();
+    }
+    if (!enable_extended_regex) {
+        return Status::InternalError(
+                "Invalid regex expression: {}. Error: {}. If you need advanced 
regex features, "
+                "try setting enable_extended_regex=true",
+                pattern, (*regex)->error());
+    }
+
+    // RE2 failed, fallback to Boost.Regex
+    // This handles advanced regex features like zero-width assertions
+    regex->reset();
+    try {
+        *boost_regex =
+                std::make_unique<boost::regex>(pattern.data(), pattern.data() 
+ pattern.size());
+    } catch (const boost::regex_error& e) {
+        return Status::InternalError("Invalid regex expression: {}. Error: 
{}", pattern, e.what());
+    }
+    return Status::OK();
+}
+
+Status FunctionLikeBase::regex_search(const re2::RE2* regex, const 
boost::regex* boost_regex,
+                                      const StringRef& val, unsigned char* 
result) {
+    if (boost_regex != nullptr) {
+        try {
+            *result = boost::regex_search(val.data, val.data + val.size, 
*boost_regex);
+        } catch (const boost::regex_error& e) {
+            return Status::InternalError("Failed to match regex expression: 
{}. Error: {}",
+                                         boost_regex->str(), e.what());
+        }
+        return Status::OK();
+    }
+    *result = RE2::PartialMatch(re2::StringPiece(val.data, val.size), *regex);
+    return Status::OK();
+}
+
 // hyperscan compile expression to database and allocate scratch space
 bool FunctionLikeBase::should_fallback_to_re2(std::string_view regexp) {
     return is_hyperscan_regexp_expensive(regexp);
@@ -1015,6 +1045,7 @@ Status FunctionRegexpLike::open(FunctionContext* context,
     state->is_like_pattern = false;
     state->search_state.enable_hyperscan_fallback =
             context->state()->query_options().enable_hyperscan_fallback;
+    state->search_state.enable_extended_regex = 
context->state()->enable_extended_regex();
     state->function = regexp_fn;
     state->scalar_function = regexp_fn_scalar;
     if (context->is_col_constant(1)) {
@@ -1059,29 +1090,9 @@ Status FunctionRegexpLike::open(FunctionContext* context,
                 // reset hs_database to nullptr to indicate not use hyperscan
                 state->search_state.hs_database.reset();
                 state->search_state.hs_scratch.reset();
-                RE2::Options opts;
-                opts.set_never_nl(false);
-                opts.set_dot_nl(true);
-                state->search_state.regex = std::make_unique<RE2>(pattern_str, 
opts);
-                if (!state->search_state.regex->ok()) {
-                    if (!context->state()->enable_extended_regex()) {
-                        return Status::InternalError(
-                                "Invalid regex expression: {}. Error: {}. If 
you need advanced "
-                                "regex features, try setting 
enable_extended_regex=true",
-                                pattern_str, 
state->search_state.regex->error());
-                    }
-
-                    // RE2 failed, fallback to Boost.Regex
-                    // This handles advanced regex features like zero-width 
assertions
-                    state->search_state.regex.reset();
-                    try {
-                        state->search_state.boost_regex =
-                                std::make_unique<boost::regex>(pattern_str);
-                    } catch (const boost::regex_error& e) {
-                        return Status::InternalError("Invalid regex 
expression: {}. Error: {}",
-                                                     pattern_str, e.what());
-                    }
-                }
+                RETURN_IF_ERROR(compile_regex(
+                        pattern_str, state->search_state.enable_extended_regex,
+                        &state->search_state.regex, 
&state->search_state.boost_regex));
             }
             state->function = constant_regex_fn;
             state->scalar_function = constant_regex_fn_scalar;
diff --git a/be/src/exprs/function/like.h b/be/src/exprs/function/like.h
index 9759e7e563f..567f59ee1b6 100644
--- a/be/src/exprs/function/like.h
+++ b/be/src/exprs/function/like.h
@@ -185,6 +185,10 @@ struct LikeSearchState {
 
     bool enable_hyperscan_fallback = true;
 
+    /// Whether a pattern rejected by RE2 may fall back to Boost.Regex, 
mirrors the
+    /// `enable_extended_regex` session variable.
+    bool enable_extended_regex = false;
+
     /// Used for LIKE predicates if the pattern is a constant argument, and is 
either a
     /// constant string or has a constant string at the beginning or end of 
the pattern.
     /// This will be set in order to check for that pattern in the 
corresponding part of
@@ -366,6 +370,19 @@ protected:
     static Status regexp_fn_scalar(const LikeSearchState* state, const 
StringRef& val,
                                    const StringRef& pattern, unsigned char* 
result);
 
+    // Compile `pattern` with RE2. When RE2 rejects it and extended regex is 
enabled, fall back
+    // to Boost.Regex, which supports zero-width assertions such as `(?=...)` 
and `(?<=...)`.
+    // Exactly one of `regex` / `boost_regex` is set on success.
+    static Status compile_regex(std::string_view pattern, bool 
enable_extended_regex,
+                                std::unique_ptr<re2::RE2>* regex,
+                                std::unique_ptr<boost::regex>* boost_regex);
+
+    // Match `val` with whichever engine `compile_regex` produced. Boost.Regex 
can also fail while
+    // matching (e.g. the backtracking budget of a pathological pattern is 
exhausted), which is
+    // reported as a non-OK Status instead of an exception.
+    static Status regex_search(const re2::RE2* regex, const boost::regex* 
boost_regex,
+                               const StringRef& val, unsigned char* result);
+
     // hyperscan compile expression to database and allocate scratch space
     static Status hs_prepare(FunctionContext* context, const char* expression,
                              hs_database_t** database, hs_scratch_t** scratch);
diff --git a/be/test/exprs/function/function_like_test.cpp 
b/be/test/exprs/function/function_like_test.cpp
index 51a8a1ce56d..1543d519b99 100644
--- a/be/test/exprs/function/function_like_test.cpp
+++ b/be/test/exprs/function/function_like_test.cpp
@@ -20,6 +20,7 @@
 #include <string>
 #include <vector>
 
+#include "core/assert_cast.h"
 #include "core/block/block.h"
 #include "core/column/column_const.h"
 #include "core/column/column_string.h"
@@ -43,11 +44,27 @@ public:
     using FunctionLikeBase::should_fallback_to_re2;
 };
 
+// How the pattern column reaches the function: a plain column, a const column 
that is only
+// visible during execute(), or a const column already visible during open().
+enum class PatternMode { NON_CONST, CONST_AT_EXECUTE, CONST_AT_OPEN };
+
+static const char* pattern_mode_name(PatternMode mode) {
+    switch (mode) {
+    case PatternMode::NON_CONST:
+        return "non-constant pattern";
+    case PatternMode::CONST_AT_EXECUTE:
+        return "constant pattern during execute";
+    case PatternMode::CONST_AT_OPEN:
+        return "constant pattern during open";
+    }
+    return "";
+}
+
+// Runs `value <Function> pattern` for a single row with the given query 
options and returns the
+// match result through `result`; the Status of open()/execute_impl() is 
returned unchanged.
 template <typename Function>
-Status execute_pattern_with_fallback_disabled(const std::string& value, const 
std::string& pattern,
-                                              bool constant_known_at_open) {
-    TQueryOptions query_options;
-    query_options.__set_enable_hyperscan_fallback(false);
+Status execute_single_pattern(const TQueryOptions& query_options, const 
std::string& value,
+                              const std::string& pattern, PatternMode mode, 
uint8_t* result) {
     RuntimeState runtime_state(query_options, TQueryGlobals {});
 
     auto string_type = std::make_shared<DataTypeString>();
@@ -58,10 +75,12 @@ Status execute_pattern_with_fallback_disabled(const 
std::string& value, const st
     values->insert_data(value.data(), value.size());
     auto patterns = ColumnString::create();
     patterns->insert_data(pattern.data(), pattern.size());
-    ColumnPtr pattern_column = ColumnConst::create(std::move(patterns), 1);
+    ColumnPtr pattern_column = mode == PatternMode::NON_CONST
+                                       ? ColumnPtr(std::move(patterns))
+                                       : 
ColumnConst::create(std::move(patterns), 1);
 
     std::vector<std::shared_ptr<ColumnPtrWrapper>> constant_columns(2);
-    if (constant_known_at_open) {
+    if (mode == PatternMode::CONST_AT_OPEN) {
         constant_columns[1] = 
std::make_shared<ColumnPtrWrapper>(pattern_column);
     }
     context->set_constant_cols(constant_columns);
@@ -73,7 +92,31 @@ Status execute_pattern_with_fallback_disabled(const 
std::string& value, const st
     block.insert({std::move(values), string_type, "value"});
     block.insert({std::move(pattern_column), string_type, "pattern"});
     block.insert({nullptr, std::make_shared<DataTypeUInt8>(), "result"});
-    return function.execute_impl(context.get(), block, {0, 1}, 2, 1);
+    RETURN_IF_ERROR(function.execute_impl(context.get(), block, {0, 1}, 2, 1));
+
+    // execute_impl always installs a ColumnUInt8 at the result position.
+    *result = assert_cast<const 
ColumnUInt8&>(*block.get_by_position(2).column).get_element(0);
+    return Status::OK();
+}
+
+template <typename Function>
+Status execute_pattern_with_fallback_disabled(const std::string& value, const 
std::string& pattern,
+                                              bool constant_known_at_open) {
+    TQueryOptions query_options;
+    query_options.__set_enable_hyperscan_fallback(false);
+    uint8_t result = 0;
+    return execute_single_pattern<Function>(
+            query_options, value, pattern,
+            constant_known_at_open ? PatternMode::CONST_AT_OPEN : 
PatternMode::CONST_AT_EXECUTE,
+            &result);
+}
+
+Status execute_regexp_with_extended_regex(const std::string& value, const 
std::string& pattern,
+                                          bool enable_extended_regex, 
PatternMode mode,
+                                          uint8_t* result) {
+    TQueryOptions query_options;
+    query_options.__set_enable_extended_regex(enable_extended_regex);
+    return execute_single_pattern<FunctionRegexpLike>(query_options, value, 
pattern, mode, result);
 }
 
 template <typename Function>
@@ -315,6 +358,53 @@ TEST(FunctionLikeTest, regexp_empty_constant_pattern) {
     check_constant_pattern_batch<FunctionRegexpLike>({"abc", ""}, "", {1, 1});
 }
 
+TEST(FunctionLikeTest, regexp_extended_regex_all_pattern_modes) {
+    // Lookaround assertions are rejected by both Hyperscan and RE2, so they 
only work through
+    // the Boost.Regex fallback, which must be reachable no matter how the 
pattern is supplied.
+    struct Case {
+        std::string value;
+        std::string pattern;
+        uint8_t expected;
+    };
+    const std::vector<Case> cases = {{"foo123bar", "foo(?=123)", 1},
+                                     {"foo124bar", "foo(?=123)", 0},
+                                     {"foobar", "(?<=foo)bar", 1},
+                                     {"fobar", "(?<=foo)bar", 0}};
+
+    for (auto mode :
+         {PatternMode::NON_CONST, PatternMode::CONST_AT_EXECUTE, 
PatternMode::CONST_AT_OPEN}) {
+        SCOPED_TRACE(pattern_mode_name(mode));
+        for (const auto& c : cases) {
+            SCOPED_TRACE(c.value + " REGEXP " + c.pattern);
+            uint8_t result = 0;
+            auto status =
+                    execute_regexp_with_extended_regex(c.value, c.pattern, 
true, mode, &result);
+            EXPECT_TRUE(status.ok()) << status.to_string();
+            EXPECT_EQ(result, c.expected);
+
+            status = execute_regexp_with_extended_regex(c.value, c.pattern, 
false, mode, &result);
+            EXPECT_FALSE(status.ok());
+            EXPECT_NE(status.to_string().find("enable_extended_regex"), 
std::string::npos)
+                    << status.to_string();
+        }
+
+        // A pattern that is invalid for Boost.Regex too still fails.
+        uint8_t result = 0;
+        auto status = execute_regexp_with_extended_regex("abc", "(", true, 
mode, &result);
+        EXPECT_FALSE(status.ok());
+        EXPECT_NE(status.to_string().find("Invalid regex expression"), 
std::string::npos)
+                << status.to_string();
+
+        // Boost.Regex gives up on a pathological pattern while matching; that 
must surface as a
+        // Status, not as an exception escaping the function.
+        status = execute_regexp_with_extended_regex(std::string(60, 'a'), 
"(?=a)(a+)+b", true, mode,
+                                                    &result);
+        EXPECT_FALSE(status.ok());
+        EXPECT_NE(status.to_string().find("Failed to match regex expression"), 
std::string::npos)
+                << status.to_string();
+    }
+}
+
 TEST(FunctionLikeTest, regexp_constant_substring_boundaries) {
     check_constant_pattern_batch<FunctionRegexpLike>({"ab", "c", "", "abc", 
"", "xabcx"}, "abc",
                                                      {0, 0, 0, 1, 0, 1});
diff --git 
a/regression-test/data/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.out
 
b/regression-test/data/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.out
new file mode 100644
index 00000000000..5511ea0c942
--- /dev/null
+++ 
b/regression-test/data/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.out
@@ -0,0 +1,28 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !constant_pattern --
+1      foobar  true    false
+2      fobar   false   false
+3      foo123bar       false   true
+4      foo124bar       false   false
+5      foobar  true    false
+6      \N      \N      \N
+7      foobar  true    false
+
+-- !column_pattern --
+1      foobar  (?<=foo)bar     true    true    true
+2      fobar   (?<=foo)bar     false   false   false
+3      foo123bar       foo(?=123)      true    true    true
+4      foo124bar       foo(?=123)      false   false   false
+5      foobar  ^foo    true    true    true
+6      \N      foo(?=123)      \N      \N      \N
+7      foobar  \N      \N      \N      \N
+
+-- !column_pattern_filter --
+1
+3
+5
+
+-- !column_pattern_not --
+2
+4
+
diff --git 
a/regression-test/suites/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.groovy
 
b/regression-test/suites/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.groovy
new file mode 100644
index 00000000000..6f7fb88fde8
--- /dev/null
+++ 
b/regression-test/suites/query_p0/sql_functions/string_functions/test_regexp_extended_nonconstant_pattern.groovy
@@ -0,0 +1,85 @@
+// 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_regexp_extended_nonconstant_pattern") {
+    sql "DROP TABLE IF EXISTS test_regexp_extended_nonconstant_pattern"
+    sql """
+        CREATE TABLE test_regexp_extended_nonconstant_pattern (
+            id INT,
+            s VARCHAR(32),
+            p VARCHAR(64)
+        )
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        INSERT INTO test_regexp_extended_nonconstant_pattern VALUES
+            (1, 'foobar', '(?<=foo)bar'),
+            (2, 'fobar', '(?<=foo)bar'),
+            (3, 'foo123bar', 'foo(?=123)'),
+            (4, 'foo124bar', 'foo(?=123)'),
+            (5, 'foobar', '^foo'),
+            (6, NULL, 'foo(?=123)'),
+            (7, 'foobar', NULL)
+    """
+
+    sql "SET enable_extended_regex = true"
+    // Lookaround patterns work the same whether they are constants or read 
from a column.
+    order_qt_constant_pattern """
+        SELECT id, s, s REGEXP '(?<=foo)bar', REGEXP(s, 'foo(?=123)')
+        FROM test_regexp_extended_nonconstant_pattern
+    """
+    order_qt_column_pattern """
+        SELECT id, s, p, s REGEXP p, REGEXP(s, p), s RLIKE p
+        FROM test_regexp_extended_nonconstant_pattern
+    """
+    order_qt_column_pattern_filter """
+        SELECT id FROM test_regexp_extended_nonconstant_pattern WHERE s REGEXP 
p
+    """
+    order_qt_column_pattern_not """
+        SELECT id FROM test_regexp_extended_nonconstant_pattern WHERE NOT s 
REGEXP p
+    """
+    // A column pattern that Boost.Regex rejects as well ('(' from substr) is 
still an error.
+    test {
+        sql """
+            SELECT id, s REGEXP substr(p, 1, 1) FROM 
test_regexp_extended_nonconstant_pattern
+            WHERE id = 1
+        """
+        exception "Invalid regex expression: ("
+    }
+    // Boost.Regex exhausts its backtracking budget on this column pattern; 
the query must fail
+    // with an error instead of crashing the backend.
+    test {
+        sql """
+            SELECT id FROM test_regexp_extended_nonconstant_pattern
+            WHERE repeat('a', 60) REGEXP concat(substr(p, 1, 0), '(?=a)(a+)+b')
+        """
+        exception "Failed to match regex expression"
+    }
+
+    sql "SET enable_extended_regex = false"
+    // Without the session variable, column patterns report the same error as 
constant ones.
+    test {
+        sql "SELECT id, s REGEXP p FROM 
test_regexp_extended_nonconstant_pattern ORDER BY id"
+        exception "try setting enable_extended_regex=true"
+    }
+    test {
+        sql "SELECT id, s REGEXP '(?<=foo)bar' FROM 
test_regexp_extended_nonconstant_pattern ORDER BY id"
+        exception "try setting enable_extended_regex=true"
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to