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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 383cf3395ae branch-4.1: [Fix](func) Keep multi-match regex cache owner 
alive (#66909) (#67025)
383cf3395ae is described below

commit 383cf3395aeecc8bc980ecc66a6c9b3872411559
Author: linrrarity <[email protected]>
AuthorDate: Mon Aug 24 09:45:20 2026 +0800

    branch-4.1: [Fix](func) Keep multi-match regex cache owner alive (#66909) 
(#67025)
    
    pick: https://github.com/apache/doris/pull/66909
---
 .../function/functions_multi_string_search.cpp     | 196 +--------------------
 ..._search.cpp => functions_multi_string_search.h} | 137 +-------------
 be/src/exprs/function/regexps.h                    |  12 +-
 .../functions_multi_string_search_test.cpp         |  64 +++++++
 4 files changed, 79 insertions(+), 330 deletions(-)

diff --git a/be/src/exprs/function/functions_multi_string_search.cpp 
b/be/src/exprs/function/functions_multi_string_search.cpp
index 16f9cb34450..49d0e7623a5 100644
--- a/be/src/exprs/function/functions_multi_string_search.cpp
+++ b/be/src/exprs/function/functions_multi_string_search.cpp
@@ -18,17 +18,12 @@
 // 
https://github.com/ClickHouse/ClickHouse/blob/master/src/Functions/FunctionsMultiStringSearch.h
 // and modified by Doris
 
-#include <hs/hs_common.h>
-#include <hs/hs_runtime.h>
+#include "exprs/function/functions_multi_string_search.h"
 
 #include <algorithm>
-#include <boost/iterator/iterator_facade.hpp>
 #include <cstddef>
-#include <limits>
 #include <memory>
-#include <optional>
 #include <utility>
-#include <vector>
 
 #include "common/status.h"
 #include "core/block/block.h"
@@ -41,15 +36,10 @@
 #include "core/column/column_string.h"
 #include "core/column/column_vector.h"
 #include "core/data_type/data_type.h"
-#include "core/data_type/data_type_number.h" // IWYU pragma: keep
-#include "core/field.h"
 #include "core/pod_array_fwd.h"
-#include "core/string_ref.h"
 #include "core/types.h"
-#include "exprs/aggregate/aggregate_function.h"
 #include "exprs/function/function.h"
 #include "exprs/function/function_helpers.h"
-#include "exprs/function/regexps.h"
 #include "exprs/function/simple_function_factory.h"
 
 namespace doris {
@@ -159,190 +149,6 @@ private:
     }
 };
 
-/// For more readable instantiations of MultiMatchAnyImpl<>
-struct MultiMatchTraits {
-    enum class Find { Any, AnyIndex };
-};
-
-template <PrimitiveType PType, MultiMatchTraits::Find Find, bool 
WithEditDistance>
-struct FunctionMultiMatchAnyImpl {
-    using ResultType = typename PrimitiveTypeTraits<PType>::CppType;
-    static constexpr PrimitiveType ResultPType = PType;
-
-    static constexpr bool FindAny = (Find == MultiMatchTraits::Find::Any);
-    static constexpr bool FindAnyIndex = (Find == 
MultiMatchTraits::Find::AnyIndex);
-
-    static constexpr auto name = "multi_match_any";
-
-    static auto get_return_type() {
-        return std::make_shared<typename 
PrimitiveTypeTraits<PType>::DataType>();
-    }
-
-    /**
-     * Prepares the regular expressions and scratch space for Hyperscan.
-     *
-     * This function takes a vector of needles (substrings to search for) and 
initializes
-     * the regular expressions and scratch space required for Hyperscan, a 
high-performance
-     * regular expression matching library.
-     *
-     */
-    static Status prepare_regexps_and_scratch(const std::vector<StringRef>& 
needles,
-                                              multiregexps::Regexps*& regexps,
-                                              multiregexps::ScratchPtr& 
smart_scratch) {
-        multiregexps::DeferredConstructedRegexpsPtr 
deferred_constructed_regexps =
-                multiregexps::getOrSet</*SaveIndices*/
-                                       FindAnyIndex, 
WithEditDistance>(needles, std::nullopt);
-        regexps = deferred_constructed_regexps->get();
-
-        hs_scratch_t* scratch = nullptr;
-        hs_error_t err = hs_clone_scratch(regexps->getScratch(), &scratch);
-
-        if (err != HS_SUCCESS) {
-            return Status::InternalError("could not clone scratch space for 
vectorscan");
-        }
-
-        smart_scratch.reset(scratch);
-        return Status::OK();
-    }
-
-    /**
-     * Static callback function to handle the match results of the hs_scan 
function.
-     *
-     * This function is called when a matching substring is found while 
scanning with
-     * Hyperscan. It updates the result based on the match information.
-     *
-     */
-    static int on_match([[maybe_unused]] unsigned int id, unsigned long long 
/* from */, // NOLINT
-                        unsigned long long /* to */,                           
          // NOLINT
-                        unsigned int /* flags */, void* context) {
-        if constexpr (FindAnyIndex) {
-            *reinterpret_cast<ResultType*>(context) = id;
-        } else if constexpr (FindAny) {
-            *reinterpret_cast<ResultType*>(context) = 1;
-        }
-        /// Once we hit the callback, there is no need to search for others.
-        return 1;
-    }
-
-    static Status vector_constant(const ColumnString::Chars& haystack_data,
-                                  const ColumnString::Offsets& 
haystack_offsets,
-                                  const Array& needles_arr, 
PaddedPODArray<ResultType>& res,
-                                  PaddedPODArray<UInt64>& offsets, bool 
allow_hyperscan,
-                                  size_t max_hyperscan_regexp_length,
-                                  size_t max_hyperscan_regexp_total_length) {
-        if (!allow_hyperscan) {
-            return Status::InvalidArgument("Hyperscan functions are disabled");
-        }
-
-        std::vector<StringRef> needles;
-        needles.reserve(needles_arr.size());
-        for (const auto& needle : needles_arr) {
-            const auto& tmp = needle.get<TYPE_STRING>();
-            needles.emplace_back(StringRef {tmp.data(), tmp.size()});
-        }
-
-        res.resize(haystack_offsets.size());
-
-        if (needles_arr.empty()) {
-            std::fill(res.begin(), res.end(), 0);
-            return Status::OK();
-        }
-
-        multiregexps::Regexps* regexps = nullptr;
-        multiregexps::ScratchPtr smart_scratch;
-        RETURN_IF_ERROR(prepare_regexps_and_scratch(needles, regexps, 
smart_scratch));
-
-        const size_t haystack_offsets_size = haystack_offsets.size();
-        UInt64 offset = 0;
-        for (size_t i = 0; i < haystack_offsets_size; ++i) {
-            UInt64 length = haystack_offsets[i] - offset;
-            /// vectorscan restriction.
-            if (length > std::numeric_limits<UInt32>::max()) {
-                return Status::InternalError("too long string to search");
-            }
-            /// zero the result, scan, check, update the offset.
-            res[i] = 0;
-            hs_error_t err = hs_scan(
-                    regexps->getDB(), reinterpret_cast<const 
char*>(haystack_data.data()) + offset,
-                    static_cast<unsigned>(length), 0, smart_scratch.get(), 
on_match, &res[i]);
-            if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) {
-                return Status::InternalError("failed to scan with vectorscan");
-            }
-            offset = haystack_offsets[i];
-        }
-
-        return Status::OK();
-    }
-
-    static Status vector_vector(const ColumnString::Chars& haystack_data,
-                                const ColumnString::Offsets& haystack_offsets,
-                                const IColumn& needles_data,
-                                const ColumnArray::Offsets64& needles_offsets,
-                                PaddedPODArray<ResultType>& res, 
PaddedPODArray<UInt64>& offsets,
-                                bool allow_hyperscan, size_t 
max_hyperscan_regexp_length,
-                                size_t max_hyperscan_regexp_total_length) {
-        if (!allow_hyperscan) {
-            return Status::InvalidArgument("Hyperscan functions are disabled");
-        }
-
-        res.resize(haystack_offsets.size());
-
-        size_t prev_haystack_offset = 0;
-        size_t prev_needles_offset = 0;
-
-        const auto& nested_column =
-                
check_and_get_column<ColumnNullable>(needles_data)->get_nested_column();
-        const auto* needles_data_string = 
check_and_get_column<ColumnString>(nested_column);
-
-        if (!needles_data_string) {
-            return Status::InvalidArgument("needles should be string column");
-        }
-
-        std::vector<StringRef> needles;
-        for (size_t i = 0; i < haystack_offsets.size(); ++i) {
-            needles.reserve(needles_offsets[i] - prev_needles_offset);
-
-            for (size_t j = prev_needles_offset; j < needles_offsets[i]; ++j) {
-                needles.emplace_back(needles_data_string->get_data_at(j));
-            }
-            if (needles.empty()) {
-                res[i] = 0;
-                prev_haystack_offset = haystack_offsets[i];
-                prev_needles_offset = needles_offsets[i];
-                continue;
-            }
-
-            multiregexps::Regexps* regexps = nullptr;
-            multiregexps::ScratchPtr smart_scratch;
-            RETURN_IF_ERROR(prepare_regexps_and_scratch(needles, regexps, 
smart_scratch));
-
-            const size_t cur_haystack_length = haystack_offsets[i] - 
prev_haystack_offset;
-
-            /// vectorscan restriction.
-            if (cur_haystack_length > std::numeric_limits<UInt32>::max()) {
-                return Status::InternalError("too long string to search");
-            }
-
-            /// zero the result, scan, check, update the offset.
-            res[i] = 0;
-            hs_error_t err = hs_scan(
-                    regexps->getDB(),
-                    reinterpret_cast<const char*>(haystack_data.data()) + 
prev_haystack_offset,
-                    static_cast<unsigned>(cur_haystack_length), 0, 
smart_scratch.get(), on_match,
-                    &res[i]);
-            if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) {
-                return Status::InternalError("failed to scan with vectorscan");
-            }
-
-            prev_haystack_offset = haystack_offsets[i];
-            prev_needles_offset = needles_offsets[i];
-            needles.clear();
-        }
-
-        return Status::OK();
-    }
-};
-
 using FunctionMultiMatchAny = 
FunctionsMultiStringSearch<FunctionMultiMatchAnyImpl<
         TYPE_TINYINT, MultiMatchTraits::Find::Any, /*WithEditDistance*/ 
false>>;
 
diff --git a/be/src/exprs/function/functions_multi_string_search.cpp 
b/be/src/exprs/function/functions_multi_string_search.h
similarity index 63%
copy from be/src/exprs/function/functions_multi_string_search.cpp
copy to be/src/exprs/function/functions_multi_string_search.h
index 16f9cb34450..f4c4e3e8b35 100644
--- a/be/src/exprs/function/functions_multi_string_search.cpp
+++ b/be/src/exprs/function/functions_multi_string_search.h
@@ -18,146 +18,30 @@
 // 
https://github.com/ClickHouse/ClickHouse/blob/master/src/Functions/FunctionsMultiStringSearch.h
 // and modified by Doris
 
-#include <hs/hs_common.h>
+#pragma once
+
 #include <hs/hs_runtime.h>
 
 #include <algorithm>
-#include <boost/iterator/iterator_facade.hpp>
 #include <cstddef>
 #include <limits>
 #include <memory>
 #include <optional>
-#include <utility>
 #include <vector>
 
 #include "common/status.h"
-#include "core/block/block.h"
-#include "core/block/column_numbers.h"
-#include "core/block/column_with_type_and_name.h"
-#include "core/column/column.h"
 #include "core/column/column_array.h"
-#include "core/column/column_const.h"
 #include "core/column/column_nullable.h"
 #include "core/column/column_string.h"
-#include "core/column/column_vector.h"
-#include "core/data_type/data_type.h"
-#include "core/data_type/data_type_number.h" // IWYU pragma: keep
+#include "core/data_type/data_type_number.h"
 #include "core/field.h"
 #include "core/pod_array_fwd.h"
 #include "core/string_ref.h"
 #include "core/types.h"
-#include "exprs/aggregate/aggregate_function.h"
-#include "exprs/function/function.h"
 #include "exprs/function/function_helpers.h"
 #include "exprs/function/regexps.h"
-#include "exprs/function/simple_function_factory.h"
 
 namespace doris {
-class FunctionContext;
-} // namespace doris
-
-namespace doris {
-
-template <typename Impl>
-class FunctionsMultiStringSearch : public IFunction {
-public:
-    static constexpr auto name = Impl::name;
-
-    static FunctionPtr create() { return 
std::make_shared<FunctionsMultiStringSearch>(); }
-
-    String get_name() const override { return name; }
-
-    size_t get_number_of_arguments() const override { return 2; }
-
-    bool use_default_implementation_for_nulls() const override { return false; 
}
-
-    DataTypePtr get_return_type_impl(const DataTypes& arguments) const 
override {
-        return Impl::get_return_type();
-    }
-
-    Status execute_impl(FunctionContext* context, Block& block, const 
ColumnNumbers& arguments,
-                        uint32_t result, size_t input_rows_count) const 
override {
-        auto haystack_column = block.get_by_position(arguments[0]).column;
-        auto needles_column = block.get_by_position(arguments[1]).column;
-
-        auto haystack_ptr = remove_nullable(haystack_column);
-        auto needles_ptr = remove_nullable(needles_column);
-
-        const auto* col_haystack_vector = 
check_and_get_column<ColumnString>(&*haystack_ptr);
-        const ColumnConst* col_haystack_const =
-                check_and_get_column_const<ColumnString>(&*haystack_ptr);
-
-        const auto* col_needles_vector = 
check_and_get_column<ColumnArray>(needles_ptr.get());
-        const ColumnConst* col_needles_const =
-                check_and_get_column_const<ColumnArray>(needles_ptr.get());
-
-        if (!col_needles_const && !col_needles_vector) {
-            return Status::InvalidArgument(
-                    "function '{}' encountered unsupported needles column, 
found {}", name,
-                    needles_column->get_name());
-        }
-
-        if (col_haystack_const && col_needles_vector) {
-            return Status::InvalidArgument(
-                    "function '{}' doesn't support search with non-constant 
needles "
-                    "in constant haystack",
-                    name);
-        }
-
-        auto col_res = ColumnVector<Impl::ResultPType>::create();
-        auto col_offsets = ColumnArray::ColumnOffsets::create();
-
-        auto& vec_res = col_res->get_data();
-        auto& offsets_res = col_offsets->get_data();
-
-        Status status;
-        if (col_needles_const) {
-            status = Impl::vector_constant(
-                    col_haystack_vector->get_chars(), 
col_haystack_vector->get_offsets(),
-                    col_needles_const->get_value<TYPE_ARRAY>(), vec_res, 
offsets_res,
-                    allow_hyperscan_, max_hyperscan_regexp_length_,
-                    max_hyperscan_regexp_total_length_);
-        } else {
-            status = Impl::vector_vector(
-                    col_haystack_vector->get_chars(), 
col_haystack_vector->get_offsets(),
-                    col_needles_vector->get_data(), 
col_needles_vector->get_offsets(), vec_res,
-                    offsets_res, allow_hyperscan_, 
max_hyperscan_regexp_length_,
-                    max_hyperscan_regexp_total_length_);
-        }
-
-        if (!status.ok()) {
-            return status;
-        }
-
-        handle_nullable_column(haystack_column, vec_res, input_rows_count);
-        handle_nullable_column(needles_column, vec_res, input_rows_count);
-
-        block.replace_by_position(result, std::move(col_res));
-
-        return status;
-    }
-
-private:
-    using ResultType = typename Impl::ResultType;
-
-    const bool allow_hyperscan_ = true;
-    const size_t max_hyperscan_regexp_length_ = 0;       // not limited
-    const size_t max_hyperscan_regexp_total_length_ = 0; // not limited
-
-    /// Handles nullable column by setting result to 0 if the input is null
-    void handle_nullable_column(const ColumnPtr& column, 
PaddedPODArray<ResultType>& vec_res,
-                                size_t input_rows_count) const {
-        if (column->is_nullable()) {
-            const auto* column_nullable = assert_cast<const 
ColumnNullable*>(column.get());
-            const auto& null_map = column_nullable->get_null_map_data();
-            for (size_t i = 0; i != input_rows_count; ++i) {
-                if (null_map[i] == 1) {
-                    vec_res[i] = 0;
-                }
-            }
-        }
-    }
-};
 
 /// For more readable instantiations of MultiMatchAnyImpl<>
 struct MultiMatchTraits {
@@ -187,7 +71,7 @@ struct FunctionMultiMatchAnyImpl {
      *
      */
     static Status prepare_regexps_and_scratch(const std::vector<StringRef>& 
needles,
-                                              multiregexps::Regexps*& regexps,
+                                              multiregexps::RegexpsPtr& 
regexps,
                                               multiregexps::ScratchPtr& 
smart_scratch) {
         multiregexps::DeferredConstructedRegexpsPtr 
deferred_constructed_regexps =
                 multiregexps::getOrSet</*SaveIndices*/
@@ -248,7 +132,7 @@ struct FunctionMultiMatchAnyImpl {
             return Status::OK();
         }
 
-        multiregexps::Regexps* regexps = nullptr;
+        multiregexps::RegexpsPtr regexps;
         multiregexps::ScratchPtr smart_scratch;
         RETURN_IF_ERROR(prepare_regexps_and_scratch(needles, regexps, 
smart_scratch));
 
@@ -291,7 +175,7 @@ struct FunctionMultiMatchAnyImpl {
         size_t prev_needles_offset = 0;
 
         const auto& nested_column =
-                
check_and_get_column<ColumnNullable>(needles_data)->get_nested_column();
+                assert_cast<const 
ColumnNullable&>(needles_data).get_nested_column();
         const auto* needles_data_string = 
check_and_get_column<ColumnString>(nested_column);
 
         if (!needles_data_string) {
@@ -312,7 +196,7 @@ struct FunctionMultiMatchAnyImpl {
                 continue;
             }
 
-            multiregexps::Regexps* regexps = nullptr;
+            multiregexps::RegexpsPtr regexps;
             multiregexps::ScratchPtr smart_scratch;
             RETURN_IF_ERROR(prepare_regexps_and_scratch(needles, regexps, 
smart_scratch));
 
@@ -343,11 +227,4 @@ struct FunctionMultiMatchAnyImpl {
     }
 };
 
-using FunctionMultiMatchAny = 
FunctionsMultiStringSearch<FunctionMultiMatchAnyImpl<
-        TYPE_TINYINT, MultiMatchTraits::Find::Any, /*WithEditDistance*/ 
false>>;
-
-void register_function_multi_string_search(SimpleFunctionFactory& factory) {
-    factory.register_function<FunctionMultiMatchAny>();
-}
-
 } // namespace doris
diff --git a/be/src/exprs/function/regexps.h b/be/src/exprs/function/regexps.h
index d521c7d9dec..7d3d6d22d98 100644
--- a/be/src/exprs/function/regexps.h
+++ b/be/src/exprs/function/regexps.h
@@ -67,24 +67,26 @@ private:
     ScratchPtr scratch;
 };
 
+using RegexpsPtr = std::shared_ptr<Regexps>;
+
 class DeferredConstructedRegexps {
 public:
     explicit DeferredConstructedRegexps(std::function<Regexps()> constructor_)
             : constructor(std::move(constructor_)) {}
 
-    Regexps* get() {
+    RegexpsPtr get() {
         std::lock_guard lock(mutex);
         if (regexps) {
-            return &*regexps;
+            return regexps;
         }
-        regexps = constructor();
-        return &*regexps;
+        regexps = std::make_shared<Regexps>(constructor());
+        return regexps;
     }
 
 private:
     std::mutex mutex;
     std::function<Regexps()> constructor;
-    std::optional<Regexps> regexps;
+    RegexpsPtr regexps;
 };
 
 using DeferredConstructedRegexpsPtr = 
std::shared_ptr<DeferredConstructedRegexps>;
diff --git a/be/test/exprs/function/functions_multi_string_search_test.cpp 
b/be/test/exprs/function/functions_multi_string_search_test.cpp
new file mode 100644
index 00000000000..b8223402885
--- /dev/null
+++ b/be/test/exprs/function/functions_multi_string_search_test.cpp
@@ -0,0 +1,64 @@
+// 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.
+
+#include "exprs/function/functions_multi_string_search.h"
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+namespace doris {
+
+TEST(FunctionsMultiStringSearchTest, KeepRegexpsAliveAfterCacheEviction) {
+    using Impl = FunctionMultiMatchAnyImpl<TYPE_TINYINT, 
MultiMatchTraits::Find::Any, false>;
+
+    std::vector<String> target_patterns = {"needle"};
+    std::vector<StringRef> target_refs = {{target_patterns[0].data(), 
target_patterns[0].size()}};
+    const size_t target_bucket =
+            multiregexps::GlobalCacheTable::getBucketIndexFor(target_patterns, 
std::nullopt);
+
+    std::vector<String> collision_patterns;
+    for (size_t i = 0;; ++i) {
+        collision_patterns = {"collision-" + std::to_string(i)};
+        if 
(multiregexps::GlobalCacheTable::getBucketIndexFor(collision_patterns, 
std::nullopt) ==
+            target_bucket) {
+            break;
+        }
+    }
+    std::vector<StringRef> collision_refs = {
+            {collision_patterns[0].data(), collision_patterns[0].size()}};
+
+    multiregexps::RegexpsPtr target_regexps;
+    multiregexps::ScratchPtr target_scratch;
+    ASSERT_TRUE(
+            Impl::prepare_regexps_and_scratch(target_refs, target_regexps, 
target_scratch).ok());
+
+    auto collision_owner = multiregexps::getOrSet<false, 
false>(collision_refs, std::nullopt);
+    ASSERT_NE(nullptr, collision_owner->get());
+
+    Impl::ResultType result = 0;
+    const std::string haystack = "find the needle";
+    const hs_error_t err = hs_scan(target_regexps->getDB(), haystack.data(),
+                                   static_cast<unsigned>(haystack.size()), 0, 
target_scratch.get(),
+                                   Impl::on_match, &result);
+
+    EXPECT_EQ(HS_SCAN_TERMINATED, err);
+    EXPECT_EQ(1, result);
+}
+
+} // namespace doris


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

Reply via email to