diff --git a/be/src/exprs/function/function_regexp.cpp b/be/src/exprs/function/function_regexp.cpp index 206f51ce0e7d46..f21fe5537ac0c7 100644 --- a/be/src/exprs/function/function_regexp.cpp +++ b/be/src/exprs/function/function_regexp.cpp @@ -136,13 +136,17 @@ struct RegexpExtractEngine { return false; } - // Match all occurrences and extract the first capturing group - void match_all_and_extract(const char* data, size_t size, + // Match all occurrences and extract the capturing group with the given index. + // index 0 means the whole match, 1 means the first capturing group (the default), and so on. + void match_all_and_extract(const char* data, size_t size, int index, std::vector& results) const { + if (index < 0) { + return; + } if (is_re2()) { int max_matches = 1 + re2_regex->NumberOfCapturingGroups(); - if (max_matches < 2) { - return; // No capturing groups + if (index >= max_matches) { + return; } size_t pos = 0; @@ -159,9 +163,9 @@ struct RegexpExtractEngine { pos += 1; continue; } - // Extract first capturing group - if (matches.size() > 1 && !matches[1].empty()) { - results.emplace_back(matches[1].data(), matches[1].size()); + // Extract the capturing group with the given index + if (static_cast(index) < matches.size() && !matches[index].empty()) { + results.emplace_back(matches[index].data(), matches[index].size()); } // Move position forward auto offset = std::string(str_pos, str_size) @@ -174,8 +178,8 @@ struct RegexpExtractEngine { boost::match_results matches; while (boost::regex_search(search_start, search_end, matches, *boost_regex)) { - if (matches.size() > 1 && matches[1].matched) { - results.emplace_back(matches[1].str()); + if (static_cast(index) < matches.size() && matches[index].matched) { + results.emplace_back(matches[index].str()); } if (matches[0].length() == 0) { if (search_start == search_end) { @@ -716,16 +720,16 @@ struct RegexpExtractAllArrayOutput { template struct RegexpExtractAllImpl { static constexpr auto name = Handler::func_name; - static constexpr size_t num_args = 2; + static constexpr size_t num_args = 3; 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) { + 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( @@ -733,10 +737,11 @@ struct RegexpExtractAllImpl { .convert_to_full_column() : block.get_by_position(arguments[0]).column; - default_preprocess_parameter_columns(argument_columns, col_const, {1}, block, arguments); + default_preprocess_parameter_columns(argument_columns, col_const, {1, 2}, block, arguments); const auto* str_col = check_and_get_column(argument_columns[0].get()); const auto* pattern_col = check_and_get_column(argument_columns[1].get()); + const auto* index_col = check_and_get_column(argument_columns[2].get()); auto outer_null_map = ColumnUInt8::create(input_rows_count, 0); auto& null_map_data = outer_null_map->get_data(); @@ -751,11 +756,13 @@ struct RegexpExtractAllImpl { handler.push_null(i, null_map_data); continue; } + const auto index_data = index_col->get_int(index_check_const(i, is_const)); regexp_extract_all_inner_loop(context, str_col, pattern_col, - handler, null_map_data, i); + index_data, handler, null_map_data, + i); } }, - make_bool_variant(col_const[1])); + make_bool_variant(col_const[1] && col_const[2])); block.get_by_position(result).column = state.finalize(std::move(outer_null_map)); return Status::OK(); @@ -764,8 +771,14 @@ struct RegexpExtractAllImpl { private: template static void regexp_extract_all_inner_loop(FunctionContext* context, const ColumnString* str_col, - const ColumnString* pattern_col, Handler& handler, + const ColumnString* pattern_col, + const Int64 index_data, Handler& handler, NullMap& null_map, const size_t index_now) { + if (index_data < 0) { + handler.push_null(index_now, null_map); + return; + } + auto* engine = reinterpret_cast( context->get_function_state(FunctionContext::THREAD_LOCAL)); std::unique_ptr scoped_engine; @@ -784,13 +797,16 @@ struct RegexpExtractAllImpl { engine = scoped_engine.get(); } - if (engine->number_of_capturing_groups() == 0) { + // index 0 extracts the whole match, so patterns without capturing groups are + // only rejected when a positive group index is requested. + if (index_data > engine->number_of_capturing_groups()) { handler.push_empty(index_now); return; } const auto& str = str_col->get_data_at(index_now); std::vector res_matches; - engine->match_all_and_extract(str.data, str.size, res_matches); + engine->match_all_and_extract(str.data, str.size, static_cast(index_data), + res_matches); if (res_matches.empty()) { handler.push_empty(index_now); diff --git a/be/test/exprs/function/function_like_test.cpp b/be/test/exprs/function/function_like_test.cpp index 82618a790e99e7..16033cc2bf6ee1 100644 --- a/be/test/exprs/function/function_like_test.cpp +++ b/be/test/exprs/function/function_like_test.cpp @@ -221,27 +221,53 @@ TEST(FunctionLikeTest, regexp_extract_all) { std::string func_name = "regexp_extract_all"; DataSet data_set = { - {{std::string("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd"), std::string("x=([0-9]+)([a-z]+)")}, + {{std::string("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd"), std::string("x=([0-9]+)([a-z]+)"), + (int64_t)1}, std::string("['18','17']")}, - {{std::string("x=a3&x=18abc&x=2&y=3&x=4"), std::string("^x=([a-z]+)([0-9]+)")}, + {{std::string("x=a3&x=18abc&x=2&y=3&x=4"), std::string("^x=([a-z]+)([0-9]+)"), (int64_t)1}, std::string("['a']")}, - {{std::string("http://a.m.baidu.com/i41915173660.htm"), std::string("i([0-9]+)")}, + {{std::string("http://a.m.baidu.com/i41915173660.htm"), std::string("i([0-9]+)"), + (int64_t)1}, std::string("['41915173660']")}, - {{std::string("http://a.m.baidu.com/i41915i73660.htm"), std::string("i([0-9]+)")}, + {{std::string("http://a.m.baidu.com/i41915i73660.htm"), std::string("i([0-9]+)"), + (int64_t)1}, std::string("['41915','73660']")}, - {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)")}, std::string("['i']")}, - {{std::string("hitdecisioendlist"), std::string("(i)(.*?)(e)")}, + {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)"), (int64_t)1}, + std::string("['i']")}, + {{std::string("hitdecisioendlist"), std::string("(i)(.*?)(e)"), (int64_t)1}, std::string("['i','i']")}, - {{std::string("hitdecisioendliset"), std::string("(i)(.*?)(e)")}, + {{std::string("hitdecisioendliset"), std::string("(i)(.*?)(e)"), (int64_t)1}, std::string("['i','i','i']")}, + + // group index 0 extracts the whole match + {{std::string("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd"), std::string("x=([0-9]+)([a-z]+)"), + (int64_t)0}, + std::string("['x=18abc','x=17bcd']")}, + {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)"), (int64_t)0}, + std::string("['itde']")}, + // other capturing groups + {{std::string("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd"), std::string("x=([0-9]+)([a-z]+)"), + (int64_t)2}, + std::string("['abc','bcd']")}, + {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)"), (int64_t)2}, + std::string("['td']")}, + // group index out of range returns an empty result + {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)"), (int64_t)3}, + std::string("")}, + // group index 0 also works for patterns without capturing groups + {{std::string("ab1cd22"), std::string("[0-9]+"), (int64_t)0}, + std::string("['1','22']")}, + // negative group index returns null + {{std::string("hitdecisiondlist"), std::string("(i)(.*?)(e)"), (int64_t)-1}, Null()}, // null - {{std::string("abc"), Null()}, Null()}, - {{Null(), std::string("i([0-9]+)")}, Null()}}; + {{std::string("abc"), Null(), (int64_t)1}, Null()}, + {{Null(), std::string("i([0-9]+)"), (int64_t)1}, Null()}}; // pattern is constant value InputTypeSet const_pattern_input_types = {PrimitiveType::TYPE_VARCHAR, - Consted {PrimitiveType::TYPE_VARCHAR}}; + Consted {PrimitiveType::TYPE_VARCHAR}, + PrimitiveType::TYPE_BIGINT}; for (const auto& line : data_set) { DataSet const_pattern_dataset = {line}; static_cast(check_function(func_name, const_pattern_input_types, @@ -255,34 +281,40 @@ TEST(FunctionLikeTest, regexp_extract_all_array) { auto return_type = make_nullable( std::make_shared(make_nullable(std::make_shared()))); - auto run_case = [&](const std::string& str, const std::string& pattern, + auto bigint_type = std::make_shared(); + auto run_case = [&](const std::string& str, const std::string& pattern, int64_t idx, 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()); + auto col_idx = ColumnInt64::create(); + col_idx->insert_value(idx); 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({ColumnConst::create(std::move(col_idx), 1), bigint_type, "idx"}); block.insert({nullptr, return_type, "result"}); - ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1)}; + ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1), + block.get_by_position(2)}; auto func = SimpleFunctionFactory::instance().get_function(func_name, arg_cols, return_type); ASSERT_TRUE(func != nullptr); - std::vector arg_types = {str_type, str_type}; + std::vector arg_types = {str_type, str_type, bigint_type}; FunctionUtils fn_utils({}, arg_types, false); auto* fn_ctx = fn_utils.get_fn_ctx(); fn_ctx->set_constant_cols( - {nullptr, std::make_shared(block.get_by_position(1).column)}); + {nullptr, std::make_shared(block.get_by_position(1).column), + std::make_shared(block.get_by_position(2).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)); + ASSERT_EQ(Status::OK(), func->execute(fn_ctx, block, {0, 1, 2}, 3, 1)); - auto result_col = block.get_by_position(2).column; + auto result_col = block.get_by_position(3).column; ASSERT_TRUE(result_col.get() != nullptr); if (expect_null) { EXPECT_TRUE(result_col->is_null_at(0)); @@ -290,20 +322,29 @@ TEST(FunctionLikeTest, regexp_extract_all_array) { 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 << "'"; + << "input: '" << str << "', pattern: '" << pattern << "', idx: " << idx; } static_cast(func->close(fn_ctx, FunctionContext::THREAD_LOCAL)); static_cast(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\"]"); + run_case("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd", "x=([0-9]+)([a-z]+)", 1, "[\"18\", \"17\"]"); + run_case("x=a3&x=18abc&x=2&y=3&x=4", "^x=([a-z]+)([0-9]+)", 1, "[\"a\"]"); + run_case("http://a.m.baidu.com/i41915173660.htm", "i([0-9]+)", 1, "[\"41915173660\"]"); + run_case("http://a.m.baidu.com/i41915i73660.htm", "i([0-9]+)", 1, "[\"41915\", \"73660\"]"); + run_case("hitdecisiondlist", "(i)(.*?)(e)", 1, "[\"i\"]"); + run_case("no_match_here", "x=([0-9]+)", 1, "[]"); + run_case("abc", "([a-z]+)", 1, "[\"abc\"]"); + // group index 0 extracts the whole match + run_case("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd", "x=([0-9]+)([a-z]+)", 0, + "[\"x=18abc\", \"x=17bcd\"]"); + // other capturing groups + run_case("x=a3&x=18abc&x=2&y=3&x=4&x=17bcd", "x=([0-9]+)([a-z]+)", 2, "[\"abc\", \"bcd\"]"); + // group index out of range returns an empty array + run_case("hitdecisiondlist", "(i)(.*?)(e)", 3, "[]"); + // negative group index returns null + run_case("hitdecisiondlist", "(i)(.*?)(e)", -1, "", true); // Helper for testing null input propagation auto nullable_str_type = make_nullable(str_type); @@ -339,24 +380,31 @@ TEST(FunctionLikeTest, regexp_extract_all_array) { Block block; block.insert({col_str, str_col_type, "str"}); block.insert({col_pattern, pattern_col_type, "pattern"}); + auto col_idx = ColumnInt64::create(); + col_idx->insert_value(1); + block.insert({ColumnConst::create(std::move(col_idx), 1), + std::make_shared(), "idx"}); block.insert({nullptr, return_type, "result"}); - ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1)}; + ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1), + block.get_by_position(2)}; auto func = SimpleFunctionFactory::instance().get_function(func_name, arg_cols, return_type); ASSERT_TRUE(func != nullptr); - std::vector arg_types = {str_col_type, pattern_col_type}; + std::vector arg_types = {str_col_type, pattern_col_type, + std::make_shared()}; FunctionUtils fn_utils({}, arg_types, false); auto* fn_ctx = fn_utils.get_fn_ctx(); fn_ctx->set_constant_cols( - {nullptr, std::make_shared(block.get_by_position(1).column)}); + {nullptr, std::make_shared(block.get_by_position(1).column), + std::make_shared(block.get_by_position(2).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)); + ASSERT_EQ(Status::OK(), func->execute(fn_ctx, block, {0, 1, 2}, 3, 1)); - EXPECT_TRUE(block.get_by_position(2).column->is_null_at(0)) + EXPECT_TRUE(block.get_by_position(3).column->is_null_at(0)) << "Expected null for null_str=" << null_str << " null_pattern=" << null_pattern; static_cast(func->close(fn_ctx, FunctionContext::THREAD_LOCAL)); @@ -374,21 +422,26 @@ TEST(FunctionLikeTest, regexp_extract_all_array) { col_str->insert_data("abc", 3); auto col_pattern = ColumnString::create(); col_pattern->insert_data("(", 1); + auto col_idx = ColumnInt64::create(); + col_idx->insert_value(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({ColumnConst::create(std::move(col_idx), 1), bigint_type, "idx"}); block.insert({nullptr, return_type, "result"}); - ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1)}; + ColumnsWithTypeAndName arg_cols = {block.get_by_position(0), block.get_by_position(1), + block.get_by_position(2)}; auto func = SimpleFunctionFactory::instance().get_function(func_name, arg_cols, return_type); ASSERT_TRUE(func != nullptr); - std::vector arg_types = {str_type, str_type}; + std::vector arg_types = {str_type, str_type, bigint_type}; FunctionUtils fn_utils({}, arg_types, false); auto* fn_ctx = fn_utils.get_fn_ctx(); fn_ctx->set_constant_cols( - {nullptr, std::make_shared(block.get_by_position(1).column)}); + {nullptr, std::make_shared(block.get_by_position(1).column), + std::make_shared(block.get_by_position(2).column)}); ASSERT_EQ(Status::OK(), func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL)); // Invalid pattern should cause open() to fail for THREAD_LOCAL scope diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAll.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAll.java index 9492e34e5f1ab2..ab3d417df173d3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAll.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/RegexpExtractAll.java @@ -22,8 +22,9 @@ 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.literal.BigIntLiteral; import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.BigIntType; import org.apache.doris.nereids.types.StringType; import org.apache.doris.nereids.types.VarcharType; @@ -36,19 +37,31 @@ * ScalarFunction 'regexp_extract_all'. This class is generated by GenerateFunction. */ public class RegexpExtractAll extends ScalarFunction - implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + implements ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { public static final List SIGNATURES = ImmutableList.of( FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT) .args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT), - FunctionSignature.ret(StringType.INSTANCE).args(StringType.INSTANCE, StringType.INSTANCE) + FunctionSignature.ret(StringType.INSTANCE).args(StringType.INSTANCE, StringType.INSTANCE), + FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT) + .args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT, BigIntType.INSTANCE), + FunctionSignature.ret(StringType.INSTANCE) + .args(StringType.INSTANCE, StringType.INSTANCE, BigIntType.INSTANCE) ); /** * constructor with 2 arguments. + * The optional group index follows Spark semantics and defaults to 1 (the first capturing group). */ public RegexpExtractAll(Expression arg0, Expression arg1) { - super("regexp_extract_all", arg0, arg1); + super("regexp_extract_all", arg0, arg1, new BigIntLiteral(1)); + } + + /** + * constructor with 3 arguments. + */ + public RegexpExtractAll(Expression arg0, Expression arg1, Expression arg2) { + super("regexp_extract_all", arg0, arg1, arg2); } /** constructor for withChildren and reuse signature */ @@ -61,7 +74,7 @@ private RegexpExtractAll(ScalarFunctionParams functionParams) { */ @Override public RegexpExtractAll withChildren(List children) { - Preconditions.checkArgument(children.size() == 2); + Preconditions.checkArgument(children.size() == 2 || children.size() == 3); return new RegexpExtractAll(getFunctionParams(children)); } 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 index 58f395bb71652f..773a684499cb18 100644 --- 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 @@ -22,9 +22,10 @@ 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.literal.BigIntLiteral; import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; import org.apache.doris.nereids.types.ArrayType; +import org.apache.doris.nereids.types.BigIntType; import org.apache.doris.nereids.types.StringType; import org.apache.doris.nereids.types.VarcharType; @@ -38,20 +39,32 @@ * 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 { + implements ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { public static final List 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) + .args(StringType.INSTANCE, StringType.INSTANCE), + FunctionSignature.ret(ArrayType.of(VarcharType.SYSTEM_DEFAULT)) + .args(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT, BigIntType.INSTANCE), + FunctionSignature.ret(ArrayType.of(StringType.INSTANCE)) + .args(StringType.INSTANCE, StringType.INSTANCE, BigIntType.INSTANCE) ); /** * constructor with 2 arguments. + * The optional group index follows Spark semantics and defaults to 1 (the first capturing group). */ public RegexpExtractAllArray(Expression arg0, Expression arg1) { - super("regexp_extract_all_array", arg0, arg1); + super("regexp_extract_all_array", arg0, arg1, new BigIntLiteral(1)); + } + + /** + * constructor with 3 arguments. + */ + public RegexpExtractAllArray(Expression arg0, Expression arg1, Expression arg2) { + super("regexp_extract_all_array", arg0, arg1, arg2); } /** constructor for withChildren and reuse signature */ @@ -64,7 +77,7 @@ private RegexpExtractAllArray(ScalarFunctionParams functionParams) { */ @Override public RegexpExtractAllArray withChildren(List children) { - Preconditions.checkArgument(children.size() == 2); + Preconditions.checkArgument(children.size() == 2 || children.size() == 3); return new RegexpExtractAllArray(getFunctionParams(children)); } 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 3a2754b72e1390..a56484f9b8711d 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 @@ -511,3 +511,21 @@ Ben -- !sql_field5 -- 2 +-- !sql_regexp_extract_all_group0 -- +['x=18abc','x=17bcd'] + +-- !sql_regexp_extract_all_group2 -- +['abc','bcd'] + +-- !sql_regexp_extract_all_group_negative -- +\N + +-- !sql_regexp_extract_all_array_group0 -- +["x=18abc", "x=17bcd"] + +-- !sql_regexp_extract_all_array_group2 -- +["abc", "bcd"] + +-- !sql_regexp_extract_all_array_group_out_of_range -- +[] + 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 09a5a2f7e58014..18406aee0cff96 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 @@ -255,4 +255,11 @@ suite("test_string_function_regexp") { qt_sql_field4 "SELECT FIELD('21','2130', '2131', '21');" qt_sql_field5 "SELECT FIELD(21, 2130, 21, 2131);" + + qt_sql_regexp_extract_all_group0 "select regexp_extract_all('x=a3&x=18abc&x=2&y=3&x=4&x=17bcd', 'x=([0-9]+)([a-z]+)', 0);" + qt_sql_regexp_extract_all_group2 "select regexp_extract_all('x=a3&x=18abc&x=2&y=3&x=4&x=17bcd', 'x=([0-9]+)([a-z]+)', 2);" + qt_sql_regexp_extract_all_group_negative "select regexp_extract_all('abc', '(b)', -1);" + qt_sql_regexp_extract_all_array_group0 "select regexp_extract_all_array('x=a3&x=18abc&x=2&y=3&x=4&x=17bcd', 'x=([0-9]+)([a-z]+)', 0);" + qt_sql_regexp_extract_all_array_group2 "select regexp_extract_all_array('x=a3&x=18abc&x=2&y=3&x=4&x=17bcd', 'x=([0-9]+)([a-z]+)', 2);" + qt_sql_regexp_extract_all_array_group_out_of_range "select regexp_extract_all_array('hitdecisiondlist', '(i)(.*?)(e)', 3);" }