This is an automated email from the ASF dual-hosted git repository.
924060929 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 5aa3c6c0d96 [Feature](func) Support REGEXP_EXTRACT_ALL_ARRAY (#61156)
5aa3c6c0d96 is described below
commit 5aa3c6c0d9656a58c9953cd1b939ce61e3ce60a1
Author: linrrarity <[email protected]>
AuthorDate: Fri Jul 10 19:10:30 2026 +0800
[Feature](func) Support REGEXP_EXTRACT_ALL_ARRAY (#61156)
1. Support function `REGEXP_EXTRACT_ALL_ARRAY`
doc: https://github.com/apache/doris-website/pull/3460
function `regexp_extract_all` returns `string` type,
`regexp_extract_all_array` behaves the same but returns an
`array<string>` type.
```sql
-- returns a String type
Doris> SELECT regexp_extract_all('AbCdE', '([[:lower:]]+)C([[:lower:]]+)');
+--------------------------------------------------------------+
| regexp_extract_all('AbCdE', '([[:lower:]]+)C([[:lower:]]+)') |
+--------------------------------------------------------------+
| ['b'] |
+--------------------------------------------------------------+
1 row in set (0.13 sec)
Doris> SELECT array_size(regexp_extract_all('AbCdE',
'([[:lower:]]+)C([[:lower:]]+)'));
ERROR 1105 (HY000): errCode = 2, detailMessage = Can not find the
compatibility function signature: cardinality(VARCHAR(65533))
-- returns an Array<String> type
Doris> SELECT regexp_extract_all_array('AbCdE',
'([[:lower:]]+)C([[:lower:]]+)');
+--------------------------------------------------------------------+
| regexp_extract_all_array('AbCdE', '([[:lower:]]+)C([[:lower:]]+)') |
+--------------------------------------------------------------------+
| ["b"] |
+--------------------------------------------------------------------+
1 row in set (1.08 sec)
Doris> SELECT array_size(regexp_extract_all_array('AbCdE',
'([[:lower:]]+)C([[:lower:]]+)'));
+--------------------------------------------------------------------------------+
| array_size(regexp_extract_all_array('AbCdE',
'([[:lower:]]+)C([[:lower:]]+)')) |
+--------------------------------------------------------------------------------+
|
1 |
+--------------------------------------------------------------------------------+
```
2. Refactor regexp function implementation
- Generalize `regexp_extract` / `regexp_extract_all` execution through a
shared function wrapper.
- Split `regexp_extract_all` output handling into string output and
array output handlers.
- Merge `regexp_replace` and `regexp_replace_one` implementation with a
template parameter.
---
be/src/exprs/function/function_regexp.cpp | 461 +++++++++++----------
be/test/exprs/function/function_like_test.cpp | 151 +++++++
.../doris/catalog/BuiltinScalarFunctions.java | 2 +
.../functions/scalar/RegexpExtractAllArray.java | 80 ++++
.../expressions/visitor/ScalarFunctionVisitor.java | 5 +
.../test_string_function_regexp.out | 56 +++
.../test_string_function_regexp.groovy | 18 +-
7 files changed, 551 insertions(+), 222 deletions(-)
diff --git a/be/src/exprs/function/function_regexp.cpp
b/be/src/exprs/function/function_regexp.cpp
index 0476336f7ca..206f51ce0e7 100644
--- a/be/src/exprs/function/function_regexp.cpp
+++ b/be/src/exprs/function/function_regexp.cpp
@@ -33,12 +33,14 @@
#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_execute_util.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_array.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
@@ -413,8 +415,10 @@ public:
}
};
+template <bool ReplaceOne>
struct RegexpReplaceImpl {
- static constexpr auto name = "regexp_replace";
+ static constexpr auto name = ReplaceOne ? "regexp_replace_one" :
"regexp_replace";
+
static void execute_impl(FunctionContext* context, ColumnPtr
argument_columns[],
const StringRef& options_value, size_t
input_rows_count,
ColumnString::Chars& result_data,
ColumnString::Offsets& result_offset,
@@ -424,14 +428,11 @@ struct RegexpReplaceImpl {
const auto* replace_col =
check_and_get_column<ColumnString>(argument_columns[2].get());
for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
_execute_inner_loop<false>(context, str_col, pattern_col,
replace_col, options_value,
result_data, result_offset, null_map,
i);
}
}
+
static void execute_impl_const_args(FunctionContext* context, ColumnPtr
argument_columns[],
const StringRef& options_value, size_t
input_rows_count,
ColumnString::Chars& result_data,
@@ -441,14 +442,11 @@ struct RegexpReplaceImpl {
const auto* replace_col =
check_and_get_column<ColumnString>(argument_columns[2].get());
for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
_execute_inner_loop<true>(context, str_col, pattern_col,
replace_col, options_value,
result_data, result_offset, null_map, i);
}
}
+
template <bool Const>
static void _execute_inner_loop(FunctionContext* context, const
ColumnString* str_col,
const ColumnString* pattern_col,
@@ -476,133 +474,93 @@ struct RegexpReplaceImpl {
replace_col->get_data_at(index_check_const(index_now,
Const)).to_string_view());
std::string result_str(str_col->get_data_at(index_now).to_string());
- re2::RE2::GlobalReplace(&result_str, *re, replace_str);
+ if constexpr (ReplaceOne) {
+ re2::RE2::Replace(&result_str, *re, replace_str);
+ } else {
+ re2::RE2::GlobalReplace(&result_str, *re, replace_str);
+ }
StringOP::push_value_string(result_str, index_now, result_data,
result_offset);
}
};
-struct RegexpReplaceOneImpl {
- static constexpr auto name = "regexp_replace_one";
+template <bool ReturnNull>
+struct RegexpExtractImpl {
+ static constexpr auto name = ReturnNull ? "regexp_extract_or_null" :
"regexp_extract";
+ static constexpr size_t num_args = 3;
+ static constexpr size_t PATTERN_ARG_IDX = 1;
- static void execute_impl(FunctionContext* context, ColumnPtr
argument_columns[],
- const StringRef& options_value, size_t
input_rows_count,
- ColumnString::Chars& result_data,
ColumnString::Offsets& result_offset,
- NullMap& null_map) {
- const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
- const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
- const auto* replace_col =
check_and_get_column<ColumnString>(argument_columns[2].get());
- // 3 args
- for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
- _execute_inner_loop<false>(context, str_col, pattern_col,
replace_col, options_value,
- result_data, result_offset, null_map,
i);
- }
- }
+ static DataTypePtr return_type() { return
make_nullable(std::make_shared<DataTypeString>()); }
- static void execute_impl_const_args(FunctionContext* context, ColumnPtr
argument_columns[],
- const StringRef& options_value, size_t
input_rows_count,
- ColumnString::Chars& result_data,
- ColumnString::Offsets& result_offset,
NullMap& null_map) {
- const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
- const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
- const auto* replace_col =
check_and_get_column<ColumnString>(argument_columns[2].get());
- // 3 args
- for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
- _execute_inner_loop<true>(context, str_col, pattern_col,
replace_col, options_value,
- result_data, result_offset, null_map, i);
- }
- }
- template <bool Const>
- static void _execute_inner_loop(FunctionContext* context, const
ColumnString* str_col,
- const ColumnString* pattern_col,
- const ColumnString* replace_col, const
StringRef& options_value,
- ColumnString::Chars& result_data,
- ColumnString::Offsets& result_offset,
NullMap& null_map,
- const size_t index_now) {
- re2::RE2* re = reinterpret_cast<re2::RE2*>(
- context->get_function_state(FunctionContext::THREAD_LOCAL));
- std::unique_ptr<re2::RE2> scoped_re; // destroys re if state->re is
nullptr
- if (re == nullptr) {
- std::string error_str;
- const auto& pattern =
pattern_col->get_data_at(index_check_const(index_now, Const));
- bool st = StringFunctions::compile_regex(pattern, &error_str,
StringRef(),
- options_value, scoped_re);
- if (!st) {
- context->add_warning(error_str.c_str());
- StringOP::push_null_string(index_now, result_data,
result_offset, null_map);
- return;
- }
- re = scoped_re.get();
+ static Status execute(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ uint32_t result, size_t input_rows_count) {
+ bool col_const[3];
+ ColumnPtr argument_columns[3];
+ for (int i = 0; i < 3; ++i) {
+ col_const[i] =
is_column_const(*block.get_by_position(arguments[i]).column);
}
+ argument_columns[0] = col_const[0] ? static_cast<const ColumnConst&>(
+
*block.get_by_position(arguments[0]).column)
+ .convert_to_full_column()
+ :
block.get_by_position(arguments[0]).column;
- re2::StringPiece replace_str = re2::StringPiece(
- replace_col->get_data_at(index_check_const(index_now,
Const)).to_string_view());
+ auto result_null_map = ColumnUInt8::create(input_rows_count, 0);
+ auto result_data_column = ColumnString::create();
+ auto& result_data = result_data_column->get_chars();
+ auto& result_offset = result_data_column->get_offsets();
+ result_offset.resize(input_rows_count);
+ auto& null_map = result_null_map->get_data();
- std::string result_str(str_col->get_data_at(index_now).to_string());
- re2::RE2::Replace(&result_str, *re, replace_str);
- StringOP::push_value_string(result_str, index_now, result_data,
result_offset);
- }
-};
+ default_preprocess_parameter_columns(argument_columns, col_const, {1,
2}, block, arguments);
-template <bool ReturnNull>
-struct RegexpExtractImpl {
- static constexpr auto name = ReturnNull ? "regexp_extract_or_null" :
"regexp_extract";
- // 3 args
- static void execute_impl(FunctionContext* context, ColumnPtr
argument_columns[],
- size_t input_rows_count, ColumnString::Chars&
result_data,
- ColumnString::Offsets& result_offset, NullMap&
null_map) {
- const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
- const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
- const auto* index_col =
check_and_get_column<ColumnInt64>(argument_columns[2].get());
- for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
- const auto& index_data = index_col->get_int(i);
- if (index_data < 0) {
- ReturnNull ? StringOP::push_null_string(i, result_data,
result_offset, null_map)
- : StringOP::push_empty_string(i, result_data,
result_offset);
- continue;
- }
- _execute_inner_loop<false>(context, str_col, pattern_col,
index_data, result_data,
- result_offset, null_map, i);
+ if (col_const[1] && col_const[2]) {
+ _execute_loop<true>(context, argument_columns, input_rows_count,
result_data,
+ result_offset, null_map);
+ } else {
+ _execute_loop<false>(context, argument_columns, input_rows_count,
result_data,
+ result_offset, null_map);
}
+
+ block.get_by_position(result).column =
+ ColumnNullable::create(std::move(result_data_column),
std::move(result_null_map));
+ return Status::OK();
}
- static void execute_impl_const_args(FunctionContext* context, ColumnPtr
argument_columns[],
- size_t input_rows_count,
ColumnString::Chars& result_data,
- ColumnString::Offsets& result_offset,
NullMap& null_map) {
+private:
+ template <bool Const>
+ static void _execute_loop(FunctionContext* context, ColumnPtr
argument_columns[],
+ size_t input_rows_count, ColumnString::Chars&
result_data,
+ ColumnString::Offsets& result_offset, NullMap&
null_map) {
const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
const auto* index_col =
check_and_get_column<ColumnInt64>(argument_columns[2].get());
- const auto& index_data = index_col->get_int(0);
- if (index_data < 0) {
+ if constexpr (Const) {
+ const auto& index_data = index_col->get_int(0);
+ if (index_data < 0) {
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ ReturnNull ? StringOP::push_null_string(i, result_data,
result_offset, null_map)
+ : StringOP::push_empty_string(i, result_data,
result_offset);
+ }
+ return;
+ }
for (size_t i = 0; i < input_rows_count; ++i) {
- ReturnNull ? StringOP::push_null_string(i, result_data,
result_offset, null_map)
- : StringOP::push_empty_string(i, result_data,
result_offset);
+ _execute_inner_loop<true>(context, str_col, pattern_col,
index_data, result_data,
+ result_offset, null_map, i);
}
- return;
- }
-
- for (size_t i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
+ } else {
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ const auto& index_data = index_col->get_int(i);
+ if (index_data < 0) {
+ ReturnNull ? StringOP::push_null_string(i, result_data,
result_offset, null_map)
+ : StringOP::push_empty_string(i, result_data,
result_offset);
+ continue;
+ }
+ _execute_inner_loop<false>(context, str_col, pattern_col,
index_data, result_data,
+ result_offset, null_map, i);
}
-
- _execute_inner_loop<true>(context, str_col, pattern_col,
index_data, result_data,
- result_offset, null_map, i);
}
}
+
template <bool Const>
static void _execute_inner_loop(FunctionContext* context, const
ColumnString* str_col,
const ColumnString* pattern_col, const
Int64 index_data,
@@ -651,66 +609,183 @@ struct RegexpExtractImpl {
}
};
-struct RegexpExtractAllImpl {
- static constexpr auto name = "regexp_extract_all";
+// Output handler for existing string-formatted result: "['a','b']"
+struct RegexpExtractAllStringOutput {
+ static constexpr const char* func_name = "regexp_extract_all";
+ static DataTypePtr return_type() { return
make_nullable(std::make_shared<DataTypeString>()); }
- size_t get_number_of_arguments() const { return 2; }
+ ColumnString::Chars& result_data;
+ ColumnString::Offsets& result_offset;
- static void execute_impl(FunctionContext* context, ColumnPtr
argument_columns[],
- size_t input_rows_count, ColumnString::Chars&
result_data,
- ColumnString::Offsets& result_offset, NullMap&
null_map) {
- const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
- const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
- for (int i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
+ void push_empty(size_t index) {
+ StringOP::push_empty_string(index, result_data, result_offset);
+ }
+ void push_null(size_t index, NullMap& null_map) {
+ StringOP::push_null_string(index, result_data, result_offset,
null_map);
+ }
+ void push_matches(size_t index, const std::vector<std::string>& matches) {
+ size_t total_size = 2; // '[' and ']'
+ for (const auto& m : matches) {
+ total_size += m.size() + 3; // "'xxx',"
+ }
+
+ size_t old_size = result_data.size();
+ result_data.resize(old_size + total_size);
+ char* pos = reinterpret_cast<char*>(&result_data[old_size]);
+
+ *pos++ = '[';
+ for (size_t j = 0; j < matches.size(); ++j) {
+ if (j > 0) {
+ *pos++ = ',';
}
- _execute_inner_loop<false>(context, str_col, pattern_col,
result_data, result_offset,
- null_map, i);
+ *pos++ = '\'';
+ memcpy(pos, matches[j].data(), matches[j].size());
+ pos += matches[j].size();
+ *pos++ = '\'';
}
+ *pos++ = ']';
+
+ result_data.resize(old_size + static_cast<size_t>(pos -
reinterpret_cast<char*>(
+
&result_data[old_size])));
+ result_offset[index] =
static_cast<ColumnString::Offset>(result_data.size());
}
- static void execute_impl_const_args(FunctionContext* context, ColumnPtr
argument_columns[],
- size_t input_rows_count,
ColumnString::Chars& result_data,
- ColumnString::Offsets& result_offset,
NullMap& null_map) {
+ struct State {
+ ColumnString::MutablePtr data_column;
+ explicit State(size_t rows) : data_column(ColumnString::create()) {
+ data_column->get_offsets().resize(rows);
+ }
+ RegexpExtractAllStringOutput create_handler() {
+ return {.result_data = data_column->get_chars(),
+ .result_offset = data_column->get_offsets()};
+ }
+ ColumnPtr finalize(ColumnUInt8::MutablePtr null_map) {
+ return ColumnNullable::create(std::move(data_column),
std::move(null_map));
+ }
+ };
+};
+
+// Output handler for proper Array<Nullable<String>> result
+struct RegexpExtractAllArrayOutput {
+ static constexpr const char* func_name = "regexp_extract_all_array";
+ static DataTypePtr return_type() {
+ return make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+ }
+
+ ColumnString& nested_col;
+ ColumnArray::Offsets64& array_offsets;
+ NullMap& nested_null_map;
+ UInt64 current_offset = 0;
+
+ void push_empty(size_t index) { array_offsets.push_back(current_offset); }
+ void push_null(size_t index, NullMap& null_map) {
+ null_map[index] = 1;
+ array_offsets.push_back(current_offset);
+ }
+ void push_matches(size_t index, const std::vector<std::string>& matches) {
+ for (const auto& m : matches) {
+ nested_col.insert_data(m.data(), m.size());
+ nested_null_map.push_back(0);
+ current_offset++;
+ }
+ array_offsets.push_back(current_offset);
+ }
+
+ struct State {
+ ColumnArray::MutablePtr array_column;
+ ColumnNullable* nested_nullable;
+ explicit State(size_t /*rows*/) {
+ auto nullable_str =
make_nullable(std::make_shared<DataTypeString>());
+ array_column = ColumnArray::create(nullable_str->create_column(),
+
ColumnArray::ColumnOffsets::create());
+ nested_nullable =
assert_cast<ColumnNullable*>(&array_column->get_data());
+ }
+ RegexpExtractAllArrayOutput create_handler() {
+ return {.nested_col =
assert_cast<ColumnString&>(nested_nullable->get_nested_column()),
+ .array_offsets = array_column->get_offsets(),
+ .nested_null_map = nested_nullable->get_null_map_data()};
+ }
+ ColumnPtr finalize(ColumnUInt8::MutablePtr null_map) {
+ return ColumnNullable::create(std::move(array_column),
std::move(null_map));
+ }
+ };
+};
+
+// Handler controls return type & column layout
+template <typename Handler>
+struct RegexpExtractAllImpl {
+ static constexpr auto name = Handler::func_name;
+ static constexpr size_t num_args = 2;
+ static constexpr size_t PATTERN_ARG_IDX = 1;
+
+ static DataTypePtr return_type() { return Handler::return_type(); }
+
+ static Status execute(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ uint32_t result, size_t input_rows_count) {
+ bool col_const[2];
+ ColumnPtr argument_columns[2];
+ for (int i = 0; i < 2; ++i) {
+ col_const[i] =
is_column_const(*block.get_by_position(arguments[i]).column);
+ }
+ argument_columns[0] = col_const[0] ? static_cast<const ColumnConst&>(
+
*block.get_by_position(arguments[0]).column)
+ .convert_to_full_column()
+ :
block.get_by_position(arguments[0]).column;
+
+ default_preprocess_parameter_columns(argument_columns, col_const, {1},
block, arguments);
+
const auto* str_col =
check_and_get_column<ColumnString>(argument_columns[0].get());
const auto* pattern_col =
check_and_get_column<ColumnString>(argument_columns[1].get());
- for (int i = 0; i < input_rows_count; ++i) {
- if (null_map[i]) {
- StringOP::push_null_string(i, result_data, result_offset,
null_map);
- continue;
- }
- _execute_inner_loop<true>(context, str_col, pattern_col,
result_data, result_offset,
- null_map, i);
- }
+
+ auto outer_null_map = ColumnUInt8::create(input_rows_count, 0);
+ auto& null_map_data = outer_null_map->get_data();
+
+ typename Handler::State state(input_rows_count);
+ auto handler = state.create_handler();
+
+ std::visit(
+ [&](auto is_const) {
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if (null_map_data[i]) {
+ handler.push_null(i, null_map_data);
+ continue;
+ }
+ regexp_extract_all_inner_loop<is_const>(context,
str_col, pattern_col,
+ handler,
null_map_data, i);
+ }
+ },
+ make_bool_variant(col_const[1]));
+
+ block.get_by_position(result).column =
state.finalize(std::move(outer_null_map));
+ return Status::OK();
}
- template <bool Const>
- static void _execute_inner_loop(FunctionContext* context, const
ColumnString* str_col,
- const ColumnString* pattern_col,
- ColumnString::Chars& result_data,
- ColumnString::Offsets& result_offset,
NullMap& null_map,
- const size_t index_now) {
+
+private:
+ template <bool is_const>
+ static void regexp_extract_all_inner_loop(FunctionContext* context, const
ColumnString* str_col,
+ const ColumnString* pattern_col,
Handler& handler,
+ NullMap& null_map, const size_t
index_now) {
auto* engine = reinterpret_cast<RegexpExtractEngine*>(
context->get_function_state(FunctionContext::THREAD_LOCAL));
std::unique_ptr<RegexpExtractEngine> scoped_engine;
if (engine == nullptr) {
std::string error_str;
- const auto& pattern =
pattern_col->get_data_at(index_check_const(index_now, Const));
+ const auto& pattern =
pattern_col->get_data_at(index_check_const(index_now, is_const));
scoped_engine = std::make_unique<RegexpExtractEngine>();
bool st = RegexpExtractEngine::compile(pattern, &error_str,
*scoped_engine,
context->state()->enable_extended_regex());
if (!st) {
context->add_warning(error_str.c_str());
- StringOP::push_null_string(index_now, result_data,
result_offset, null_map);
+ handler.push_null(index_now, null_map);
return;
}
engine = scoped_engine.get();
}
if (engine->number_of_capturing_groups() == 0) {
- StringOP::push_empty_string(index_now, result_data, result_offset);
+ handler.push_empty(index_now);
return;
}
const auto& str = str_col->get_data_at(index_now);
@@ -718,19 +793,10 @@ struct RegexpExtractAllImpl {
engine->match_all_and_extract(str.data, str.size, res_matches);
if (res_matches.empty()) {
- StringOP::push_empty_string(index_now, result_data, result_offset);
+ handler.push_empty(index_now);
return;
}
-
- std::string res = "[";
- for (int j = 0; j < res_matches.size(); ++j) {
- res += "'" + res_matches[j] + "'";
- if (j < res_matches.size() - 1) {
- res += ",";
- }
- }
- res += "]";
- StringOP::push_value_string(std::string_view(res), index_now,
result_data, result_offset);
+ handler.push_matches(index_now, res_matches);
}
};
@@ -744,22 +810,18 @@ public:
String get_name() const override { return name; }
- size_t get_number_of_arguments() const override {
- if constexpr (std::is_same_v<Impl, RegexpExtractAllImpl>) {
- return 2;
- }
- return 3;
- }
+ size_t get_number_of_arguments() const override { return Impl::num_args; }
DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
- return make_nullable(std::make_shared<DataTypeString>());
+ return Impl::return_type();
}
Status open(FunctionContext* context, FunctionContext::FunctionStateScope
scope) override {
if (scope == FunctionContext::THREAD_LOCAL) {
- if (context->is_col_constant(1)) {
+ if (context->is_col_constant(Impl::PATTERN_ARG_IDX)) {
DCHECK(!context->get_function_state(scope));
- const auto pattern_col =
context->get_constant_col(1)->column_ptr;
+ const auto pattern_col =
+
context->get_constant_col(Impl::PATTERN_ARG_IDX)->column_ptr;
const auto& pattern = pattern_col->get_data_at(0);
if (pattern.size == 0) {
return Status::OK();
@@ -769,6 +831,7 @@ public:
auto engine = std::make_shared<RegexpExtractEngine>();
bool st = RegexpExtractEngine::compile(pattern, &error_str,
*engine,
context->state()->enable_extended_regex());
+
if (!st) {
context->set_error(error_str.c_str());
return Status::InvalidArgument(error_str);
@@ -781,65 +844,21 @@ public:
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
- size_t argument_size = arguments.size();
-
- auto result_null_map = ColumnUInt8::create(input_rows_count, 0);
- auto result_data_column = ColumnString::create();
- auto& result_data = result_data_column->get_chars();
- auto& result_offset = result_data_column->get_offsets();
- result_offset.resize(input_rows_count);
-
- bool col_const[3];
- ColumnPtr argument_columns[3];
- for (int i = 0; i < argument_size; ++i) {
- col_const[i] =
is_column_const(*block.get_by_position(arguments[i]).column);
- }
- argument_columns[0] = col_const[0] ? static_cast<const ColumnConst&>(
-
*block.get_by_position(arguments[0]).column)
- .convert_to_full_column()
- :
block.get_by_position(arguments[0]).column;
- if constexpr (std::is_same_v<Impl, RegexpExtractAllImpl>) {
- default_preprocess_parameter_columns(argument_columns, col_const,
{1}, block,
- arguments);
- } else {
- default_preprocess_parameter_columns(argument_columns, col_const,
{1, 2}, block,
- arguments);
- }
-
- if constexpr (std::is_same_v<Impl, RegexpExtractAllImpl>) {
- if (col_const[1]) {
- Impl::execute_impl_const_args(context, argument_columns,
input_rows_count,
- result_data, result_offset,
- result_null_map->get_data());
- } else {
- Impl::execute_impl(context, argument_columns,
input_rows_count, result_data,
- result_offset, result_null_map->get_data());
- }
- } else {
- if (col_const[1] && col_const[2]) {
- Impl::execute_impl_const_args(context, argument_columns,
input_rows_count,
- result_data, result_offset,
- result_null_map->get_data());
- } else {
- Impl::execute_impl(context, argument_columns,
input_rows_count, result_data,
- result_offset, result_null_map->get_data());
- }
- }
-
- block.get_by_position(result).column =
- ColumnNullable::create(std::move(result_data_column),
std::move(result_null_map));
- return Status::OK();
+ return Impl::execute(context, block, arguments, result,
input_rows_count);
}
};
void register_function_regexp_extract(SimpleFunctionFactory& factory) {
- factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl,
ThreeParamTypes>>();
- factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl,
FourParamTypes>>();
- factory.register_function<FunctionRegexpReplace<RegexpReplaceOneImpl,
ThreeParamTypes>>();
- factory.register_function<FunctionRegexpReplace<RegexpReplaceOneImpl,
FourParamTypes>>();
+ factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl<false>,
ThreeParamTypes>>();
+ factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl<false>,
FourParamTypes>>();
+ factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl<true>,
ThreeParamTypes>>();
+ factory.register_function<FunctionRegexpReplace<RegexpReplaceImpl<true>,
FourParamTypes>>();
factory.register_function<FunctionRegexpFunctionality<RegexpExtractImpl<true>>>();
factory.register_function<FunctionRegexpFunctionality<RegexpExtractImpl<false>>>();
-
factory.register_function<FunctionRegexpFunctionality<RegexpExtractAllImpl>>();
+ factory.register_function<
+
FunctionRegexpFunctionality<RegexpExtractAllImpl<RegexpExtractAllStringOutput>>>();
+ factory.register_function<
+
FunctionRegexpFunctionality<RegexpExtractAllImpl<RegexpExtractAllArrayOutput>>>();
factory.register_function<FunctionRegexpCount>();
}
diff --git a/be/test/exprs/function/function_like_test.cpp
b/be/test/exprs/function/function_like_test.cpp
index 881886831ec..82618a790e9 100644
--- a/be/test/exprs/function/function_like_test.cpp
+++ b/be/test/exprs/function/function_like_test.cpp
@@ -20,6 +20,7 @@
#include "core/column/column_string.h"
#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
@@ -248,6 +249,156 @@ TEST(FunctionLikeTest, regexp_extract_all) {
}
}
+TEST(FunctionLikeTest, regexp_extract_all_array) {
+ std::string func_name = "regexp_extract_all_array";
+ auto str_type = std::make_shared<DataTypeString>();
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ auto run_case = [&](const std::string& str, const std::string& pattern,
+ const std::string& expected, bool expect_null = false)
{
+ auto col_str = ColumnString::create();
+ col_str->insert_data(str.data(), str.size());
+ auto col_pattern = ColumnString::create();
+ col_pattern->insert_data(pattern.data(), pattern.size());
+
+ Block block;
+ block.insert({std::move(col_str), str_type, "str"});
+ block.insert({ColumnConst::create(std::move(col_pattern), 1),
str_type, "pattern"});
+ block.insert({nullptr, return_type, "result"});
+
+ ColumnsWithTypeAndName arg_cols = {block.get_by_position(0),
block.get_by_position(1)};
+ auto func =
+ SimpleFunctionFactory::instance().get_function(func_name,
arg_cols, return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ std::vector<DataTypePtr> arg_types = {str_type, str_type};
+ FunctionUtils fn_utils({}, arg_types, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ fn_ctx->set_constant_cols(
+ {nullptr,
std::make_shared<ColumnPtrWrapper>(block.get_by_position(1).column)});
+
+ ASSERT_EQ(Status::OK(), func->open(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ ASSERT_EQ(Status::OK(), func->open(fn_ctx,
FunctionContext::THREAD_LOCAL));
+ ASSERT_EQ(Status::OK(), func->execute(fn_ctx, block, {0, 1}, 2, 1));
+
+ auto result_col = block.get_by_position(2).column;
+ ASSERT_TRUE(result_col.get() != nullptr);
+ if (expect_null) {
+ EXPECT_TRUE(result_col->is_null_at(0));
+ } else {
+ ASSERT_FALSE(result_col->is_null_at(0));
+ auto result_str = return_type->to_string(*result_col, 0);
+ EXPECT_EQ(expected, result_str)
+ << "input: '" << str << "', pattern: '" << pattern << "'";
+ }
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ };
+
+ run_case("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd", "x=([0-9]+)([a-z]+)",
"[\"18\", \"17\"]");
+ run_case("x=a3&x=18abc&x=2&y=3&x=4", "^x=([a-z]+)([0-9]+)", "[\"a\"]");
+ run_case("http://a.m.baidu.com/i41915173660.htm", "i([0-9]+)",
"[\"41915173660\"]");
+ run_case("http://a.m.baidu.com/i41915i73660.htm", "i([0-9]+)",
"[\"41915\", \"73660\"]");
+ run_case("hitdecisiondlist", "(i)(.*?)(e)", "[\"i\"]");
+ run_case("no_match_here", "x=([0-9]+)", "[]");
+ run_case("abc", "([a-z]+)", "[\"abc\"]");
+
+ // Helper for testing null input propagation
+ auto nullable_str_type = make_nullable(str_type);
+ auto run_null_case = [&](bool null_str, bool null_pattern) {
+ ColumnPtr col_str;
+ DataTypePtr str_col_type;
+ if (null_str) {
+ auto col = ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ col->insert_default();
+ col_str = std::move(col);
+ str_col_type = nullable_str_type;
+ } else {
+ auto col = ColumnString::create();
+ col->insert_data("abc", 3);
+ col_str = std::move(col);
+ str_col_type = str_type;
+ }
+
+ ColumnPtr col_pattern;
+ DataTypePtr pattern_col_type;
+ if (null_pattern) {
+ auto col = ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ col->insert_default();
+ col_pattern = ColumnConst::create(std::move(col), 1);
+ pattern_col_type = nullable_str_type;
+ } else {
+ auto col = ColumnString::create();
+ col->insert_data("([a-z]+)", 8);
+ col_pattern = ColumnConst::create(std::move(col), 1);
+ pattern_col_type = str_type;
+ }
+
+ Block block;
+ block.insert({col_str, str_col_type, "str"});
+ block.insert({col_pattern, pattern_col_type, "pattern"});
+ block.insert({nullptr, return_type, "result"});
+
+ ColumnsWithTypeAndName arg_cols = {block.get_by_position(0),
block.get_by_position(1)};
+ auto func =
+ SimpleFunctionFactory::instance().get_function(func_name,
arg_cols, return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ std::vector<DataTypePtr> arg_types = {str_col_type, pattern_col_type};
+ FunctionUtils fn_utils({}, arg_types, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ fn_ctx->set_constant_cols(
+ {nullptr,
std::make_shared<ColumnPtrWrapper>(block.get_by_position(1).column)});
+
+ ASSERT_EQ(Status::OK(), func->open(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ ASSERT_EQ(Status::OK(), func->open(fn_ctx,
FunctionContext::THREAD_LOCAL));
+ ASSERT_EQ(Status::OK(), func->execute(fn_ctx, block, {0, 1}, 2, 1));
+
+ EXPECT_TRUE(block.get_by_position(2).column->is_null_at(0))
+ << "Expected null for null_str=" << null_str << "
null_pattern=" << null_pattern;
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ };
+
+ // NULL input string → null result
+ run_null_case(true, false);
+ // NULL pattern → null result
+ run_null_case(false, true);
+
+ // Invalid const pattern → open() should fail
+ {
+ auto col_str = ColumnString::create();
+ col_str->insert_data("abc", 3);
+ auto col_pattern = ColumnString::create();
+ col_pattern->insert_data("(", 1);
+ Block block;
+ block.insert({std::move(col_str), str_type, "str"});
+ block.insert({ColumnConst::create(std::move(col_pattern), 1),
str_type, "pattern"});
+ block.insert({nullptr, return_type, "result"});
+
+ ColumnsWithTypeAndName arg_cols = {block.get_by_position(0),
block.get_by_position(1)};
+ auto func =
+ SimpleFunctionFactory::instance().get_function(func_name,
arg_cols, return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ std::vector<DataTypePtr> arg_types = {str_type, str_type};
+ FunctionUtils fn_utils({}, arg_types, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ fn_ctx->set_constant_cols(
+ {nullptr,
std::make_shared<ColumnPtrWrapper>(block.get_by_position(1).column)});
+
+ ASSERT_EQ(Status::OK(), func->open(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ // Invalid pattern should cause open() to fail for THREAD_LOCAL scope
+ EXPECT_NE(Status::OK(), func->open(fn_ctx,
FunctionContext::THREAD_LOCAL));
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx,
FunctionContext::FRAGMENT_LOCAL));
+ }
+}
+
TEST(FunctionLikeTest, regexp_replace) {
std::string func_name = "regexp_replace";
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
index ec43198e21c..af6a32e993c 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
@@ -426,6 +426,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.RandomBytes;
import org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpCount;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtract;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractAll;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractAllArray;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractOrNull;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpReplace;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpReplaceOne;
@@ -1008,6 +1009,7 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(RegexpCount.class, "regexp_count"),
scalar(RegexpExtract.class, "regexp_extract"),
scalar(RegexpExtractAll.class, "regexp_extract_all"),
+ scalar(RegexpExtractAllArray.class, "regexp_extract_all_array"),
scalar(RegexpExtractOrNull.class, "regexp_extract_or_null"),
scalar(RegexpReplace.class, "regexp_replace"),
scalar(RegexpReplaceOne.class, "regexp_replace_one"),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAllArray.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAllArray.java
new file mode 100644
index 00000000000..58f395bb716
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAllArray.java
@@ -0,0 +1,80 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
+import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
+import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.VarcharType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * ScalarFunction 'regexp_extract_all_array'.
+ * Returns all matches of a regex pattern as an Array<String> instead of
a string-formatted array.
+ */
+public class RegexpExtractAllArray extends ScalarFunction
+ implements BinaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(ArrayType.of(VarcharType.SYSTEM_DEFAULT))
+ .args(VarcharType.SYSTEM_DEFAULT,
VarcharType.SYSTEM_DEFAULT),
+ FunctionSignature.ret(ArrayType.of(StringType.INSTANCE))
+ .args(StringType.INSTANCE, StringType.INSTANCE)
+ );
+
+ /**
+ * constructor with 2 arguments.
+ */
+ public RegexpExtractAllArray(Expression arg0, Expression arg1) {
+ super("regexp_extract_all_array", arg0, arg1);
+ }
+
+ /** constructor for withChildren and reuse signature */
+ private RegexpExtractAllArray(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public RegexpExtractAllArray withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 2);
+ return new RegexpExtractAllArray(getFunctionParams(children));
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitRegexpExtractAllArray(this, context);
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
index a9b7b920376..68afa81f4fd 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
@@ -446,6 +446,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.RandomBytes;
import org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpCount;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtract;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractAll;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractAllArray;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpExtractOrNull;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpReplace;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.RegexpReplaceOne;
@@ -2207,6 +2208,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(regexpExtractAll, context);
}
+ default R visitRegexpExtractAllArray(RegexpExtractAllArray
regexpExtractAllArray, C context) {
+ return visitScalarFunction(regexpExtractAllArray, context);
+ }
+
default R visitRegexpExtractOrNull(RegexpExtractOrNull
regexpExtractOrNull, C context) {
return visitScalarFunction(regexpExtractOrNull, context);
}
diff --git
a/regression-test/data/query_p0/sql_functions/string_functions/test_string_function_regexp.out
b/regression-test/data/query_p0/sql_functions/string_functions/test_string_function_regexp.out
index d5ec2fc6d56..3a2754b72e1 100644
---
a/regression-test/data/query_p0/sql_functions/string_functions/test_string_function_regexp.out
+++
b/regression-test/data/query_p0/sql_functions/string_functions/test_string_function_regexp.out
@@ -262,6 +262,62 @@ aXb
-- !sql_regexp_extract_all_10 --
['aXb','cXd']
+-- !regexp_extract_all_array_1 --
+["18", "17"]
+
+-- !regexp_extract_all_array_2 --
+["41915", "73660"]
+
+-- !regexp_extract_all_array_3 --
+["abc", "def", "ghi"]
+
+-- !regexp_extract_all_array_4 --
+[]
+
+-- !regexp_extract_all_array_5 --
+\N
+
+-- !regexp_extract_all_array_6 --
+\N
+
+-- !regexp_extract_all_array_7 --
+["ab", "c", "c", "c"]
+
+-- !regexp_extract_all_array_8 --
+\N
+[]
+["Emmy", "eillish"]
+["It", "s", "ok"]
+["It", "s", "true"]
+["billie", "eillish"]
+["billie", "eillish"]
+
+-- !regexp_extract_all_array_9 --
+\N
+[]
+["mmy", "eillish"]
+["t", "s", "ok"]
+["t", "s", "true"]
+["billie", "eillish"]
+["billie", "eillish"]
+
+-- !regexp_extract_all_array_10 --
+\N 5 \N
+ 6 []
+Emmy eillish 3 ["Emmy", "eillish"]
+It's ok 2 ["It", "s", "ok"]
+It's true 4 ["It", "s", "true"]
+billie eillish \N ["billie", "eillish"]
+billie eillish 1 ["billie", "eillish"]
+
+-- !regexp_extract_all_array_11 --
+[]
+[]
+[]
+[]
+[]
+[]
+
-- !sql --
a-b-c
diff --git
a/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function_regexp.groovy
b/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function_regexp.groovy
index 3a219a2e619..09a5a2f7e58 100644
---
a/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function_regexp.groovy
+++
b/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function_regexp.groovy
@@ -128,6 +128,22 @@ suite("test_string_function_regexp") {
qt_sql_regexp_extract_all_9 "SELECT REGEXP_EXTRACT_ALL(concat('aXb',
char(10), 'cXd'), '(?-s)(\\\\w.\\\\w)');"
qt_sql_regexp_extract_all_10 "SELECT REGEXP_EXTRACT_ALL(concat('aXb',
char(10), 'cXd'), '(\\\\w.\\\\w)');"
+ test {
+ sql 'SELECT REGEXP_EXTRACT_ALL_ARRAY(\'Apache/Doris\',
\'([a-zA-Z_+-]+(?:\\/[a-zA-Z_0-9+-]+)*)(?=s|$)\');'
+ exception "Invalid regex pattern"
+ }
+ qt_regexp_extract_all_array_1 "SELECT
regexp_extract_all_array('x=a3&x=18abc&x=2&y=3&x=4&x=17bcd',
'x=([0-9]+)([a-z]+)');"
+ qt_regexp_extract_all_array_2 "SELECT
regexp_extract_all_array('http://a.m.baidu.com/i41915i73660.htm', 'i([0-9]+)');"
+ qt_regexp_extract_all_array_3 "SELECT regexp_extract_all_array('abc=111,
def=222, ghi=333', '(\"[^\"]+\"|\\\\w+)=(\"[^\"]+\"|\\\\w+)');"
+ qt_regexp_extract_all_array_4 "select
regexp_extract_all_array('xxfs','f');"
+ qt_regexp_extract_all_array_5 "select regexp_extract_all_array(NULL,
'pattern');"
+ qt_regexp_extract_all_array_6 "select regexp_extract_all_array('text',
NULL);"
+ qt_regexp_extract_all_array_7 "select
regexp_extract_all_array('abcdfesscca', '(ab|c|)');"
+ qt_regexp_extract_all_array_8 "SELECT regexp_extract_all_array(k,
'(\\\\w+)') from test_string_function_regexp ORDER BY k;"
+ qt_regexp_extract_all_array_9 "SELECT regexp_extract_all_array(k,
'([a-z]+)') from test_string_function_regexp ORDER BY k;"
+ qt_regexp_extract_all_array_10 "SELECT k, v, regexp_extract_all_array(k,
'(\\\\w+)') from test_string_function_regexp ORDER BY k, v;"
+ qt_regexp_extract_all_array_11 "SELECT regexp_extract_all_array(k,
concat('^', k)) from test_string_function_regexp WHERE k IS NOT NULL ORDER BY
k;"
+
qt_sql "SELECT regexp_replace('a b c', \" \", \"-\");"
qt_sql "SELECT regexp_replace('a b c','(b)','<\\\\1>');"
@@ -239,4 +255,4 @@ suite("test_string_function_regexp") {
qt_sql_field4 "SELECT FIELD('21','2130', '2131', '21');"
qt_sql_field5 "SELECT FIELD(21, 2130, 21, 2131);"
-}
\ No newline at end of file
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]