You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mo...@apache.org on 2022/12/27 14:22:09 UTC
[doris] branch branch-1.2-lts updated: [feature](function)Support negative index for function split_part (#13914)
This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch branch-1.2-lts
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-1.2-lts by this push:
new 687d9d3300 [feature](function)Support negative index for function split_part (#13914)
687d9d3300 is described below
commit 687d9d3300d3d336ddb739094c1b7764907418bf
Author: Yulei-Yang <yu...@gmail.com>
AuthorDate: Mon Dec 12 09:56:09 2022 +0800
[feature](function)Support negative index for function split_part (#13914)
---
be/src/exprs/string_functions.cpp | 107 ++++++++--
be/src/vec/functions/function_string.h | 221 +++++++++++++--------
be/test/exprs/string_functions_test.cpp | 49 +++++
be/test/vec/function/function_string_test.cpp | 17 --
.../sql-functions/string-functions/split_part.md | 33 ++-
.../sql-functions/string-functions/split_part.md | 31 ++-
.../string_functions/test_string_function.out | 33 +++
.../string_functions/test_string_function.groovy | 15 +-
8 files changed, 380 insertions(+), 126 deletions(-)
diff --git a/be/src/exprs/string_functions.cpp b/be/src/exprs/string_functions.cpp
index b980ad4a7f..0db6741f05 100644
--- a/be/src/exprs/string_functions.cpp
+++ b/be/src/exprs/string_functions.cpp
@@ -920,35 +920,98 @@ static int index_of(const uint8_t* source, int source_offset, int source_count,
return -1;
}
+static int last_index_of(const uint8_t* source, int source_len, const uint8_t* target,
+ int target_len, int to_index) {
+ if (to_index < 0) {
+ return -1;
+ }
+ if (to_index >= source_len) {
+ to_index = source_len - 1;
+ }
+ if (target_len == 0) {
+ return to_index;
+ }
+ const uint8_t last = target[target_len - 1];
+ int min = target_len;
+ for (int i = to_index; i >= min; i--) {
+ while (i >= min && source[i] != last) {
+ i--; // Look for last character
+ }
+ if (i >= min) { // Found first character, now look at the rest of v2
+ int j = i - 1;
+ int end = j - target_len + 1;
+ for (int k = target_len - 2; j > end && source[j] == target[k];) {
+ j--;
+ k--;
+ }
+ if (j == end) {
+ return i - target_len + 1;
+ }
+ }
+ }
+ return -1;
+}
+
StringVal StringFunctions::split_part(FunctionContext* context, const StringVal& content,
const StringVal& delimiter, const IntVal& field) {
- if (content.is_null || delimiter.is_null || field.is_null || field.val <= 0) {
+ if (content.is_null || delimiter.is_null || field.is_null || field.val == 0) {
return StringVal::null();
}
- std::vector<int> find(field.val, -1); //store substring position
- int from = 0;
- for (int i = 1; i <= field.val; i++) { // find
- int last_index = i - 1;
- find[last_index] =
- index_of(content.ptr, 0, content.len, delimiter.ptr, 0, delimiter.len, from);
- from = find[last_index] + delimiter.len;
- if (find[last_index] == -1) {
- break;
+
+ if (field.val > 0) {
+ int from = 0;
+ std::vector<int> find(field.val, -1); //store substring position
+ for (int i = 1; i <= field.val; i++) { // find
+ int last_index = i - 1;
+ find[last_index] =
+ index_of(content.ptr, 0, content.len, delimiter.ptr, 0, delimiter.len, from);
+ from = find[last_index] + delimiter.len;
+ if (find[last_index] == -1) {
+ break;
+ }
}
- }
- if ((field.val > 1 && find[field.val - 2] == -1) ||
- (field.val == 1 && find[field.val - 1] == -1)) {
- // field not find return null
- return StringVal::null();
- }
- int start_pos;
- if (field.val == 1) { // find need split first part
- start_pos = 0;
+ if ((field.val > 1 && find[field.val - 2] == -1) ||
+ (field.val == 1 && find[field.val - 1] == -1)) {
+ // field not find return null
+ return StringVal::null();
+ }
+ int start_pos;
+ if (field.val == 1) { // find need split first part
+ start_pos = 0;
+ } else {
+ start_pos = find[field.val - 2] + delimiter.len;
+ }
+ int len = (find[field.val - 1] == -1 ? content.len : find[field.val - 1]) - start_pos;
+ return StringVal(content.ptr + start_pos, len);
} else {
- start_pos = find[field.val - 2] + delimiter.len;
+ int to = content.len;
+ int abs_field = -field.val;
+ std::vector<int> find(abs_field, -1); //store substring position
+ for (int i = 1; i <= abs_field; i++) { // find
+ int last_index = i - 1;
+ find[last_index] =
+ last_index_of(content.ptr, content.len, delimiter.ptr, delimiter.len, to);
+ to = find[last_index] - delimiter.len;
+ if (find[last_index] == -1) {
+ break;
+ }
+ }
+ if ((abs_field > 1 && find[abs_field - 2] == -1) ||
+ (abs_field == 1 && find[abs_field - 1] == -1)) {
+ // field not find return null
+ return StringVal::null();
+ }
+ int end_pos;
+ if (abs_field == 1) { // find need split first part
+ end_pos = content.len - 1;
+ } else {
+ end_pos = find[abs_field - 2] - 1;
+ }
+ int len =
+ end_pos - (find[abs_field - 1] == -1 ? 0 : find[abs_field - 1] + delimiter.len) + 1;
+
+ return StringVal(content.ptr + end_pos - len + 1, len);
}
- int len = (find[field.val - 1] == -1 ? content.len : find[field.val - 1]) - start_pos;
- return StringVal(content.ptr + start_pos, len);
}
StringVal StringFunctions::replace(FunctionContext* context, const StringVal& origStr,
diff --git a/be/src/vec/functions/function_string.h b/be/src/vec/functions/function_string.h
index 01760840b7..73089500a6 100644
--- a/be/src/vec/functions/function_string.h
+++ b/be/src/vec/functions/function_string.h
@@ -1234,8 +1234,8 @@ public:
return make_nullable(std::make_shared<DataTypeString>());
}
- bool use_default_implementation_for_nulls() const override { return false; }
- bool use_default_implementation_for_constants() const override { return true; }
+ bool use_default_implementation_for_nulls() const override { return true; }
+ bool use_default_implementation_for_constants() const override { return false; }
Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments,
size_t result, size_t input_rows_count) override {
@@ -1251,97 +1251,151 @@ public:
auto& res_chars = res->get_chars();
res_offsets.resize(input_rows_count);
- size_t argument_size = arguments.size();
- ColumnPtr argument_columns[argument_size];
- for (size_t i = 0; i < argument_size; ++i) {
- argument_columns[i] =
- block.get_by_position(arguments[i]).column->convert_to_full_column_if_const();
- if (auto* nullable = check_and_get_column<const ColumnNullable>(*argument_columns[i])) {
- // Danger: Here must dispose the null map data first! Because
- // argument_columns[i]=nullable->get_nested_column_ptr(); will release the mem
- // of column nullable mem of null map
- VectorizedUtils::update_null_map(null_map->get_data(),
- nullable->get_null_map_data());
- argument_columns[i] = nullable->get_nested_column_ptr();
+ ColumnPtr content_column =
+ block.get_by_position(arguments[0]).column->convert_to_full_column_if_const();
+
+ if (auto* nullable = check_and_get_column<const ColumnNullable>(*content_column)) {
+ // Danger: Here must dispose the null map data first! Because
+ // argument_columns[0]=nullable->get_nested_column_ptr(); will release the mem
+ // of column nullable mem of null map
+ VectorizedUtils::update_null_map(null_map->get_data(), nullable->get_null_map_data());
+ content_column = nullable->get_nested_column_ptr();
+ }
+
+ for (size_t i = 1; i <= 2; i++) {
+ ColumnPtr columnPtr = remove_nullable(block.get_by_position(arguments[i]).column);
+
+ if (!is_column_const(*columnPtr)) {
+ return Status::RuntimeError("Argument at index {} for function {} must be constant",
+ i + 1, get_name());
}
}
- auto str_col = assert_cast<const ColumnString*>(argument_columns[0].get());
+ auto str_col = assert_cast<const ColumnString*>(content_column.get());
- auto delimiter_col = assert_cast<const ColumnString*>(argument_columns[1].get());
+ const IColumn& delimiter_col = *block.get_by_position(arguments[1]).column;
+ const auto* delimiter_const = typeid_cast<const ColumnConst*>(&delimiter_col);
+ auto delimiter = delimiter_const->get_field().get<String>();
+ int32_t delimiter_size = delimiter.size();
- auto part_num_col = assert_cast<const ColumnInt32*>(argument_columns[2].get());
- auto& part_num_col_data = part_num_col->get_data();
+ const IColumn& part_num_col = *block.get_by_position(arguments[2]).column;
+ const auto* part_num_col_const = typeid_cast<const ColumnConst*>(&part_num_col);
+ auto part_number = part_num_col_const->get_field().get<Int32>();
- for (size_t i = 0; i < input_rows_count; ++i) {
- if (part_num_col_data[i] <= 0) {
- StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
- continue;
- }
+ if (part_number >= 0) {
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if (part_number == 0) {
+ StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
+ continue;
+ }
- auto delimiter = delimiter_col->get_data_at(i);
- auto delimiter_str = delimiter_col->get_data_at(i).to_string();
- auto part_number = part_num_col_data[i];
- auto str = str_col->get_data_at(i);
- if (delimiter.size == 0) {
- StringOP::push_empty_string(i, res_chars, res_offsets);
- } else if (delimiter.size == 1) {
- // If delimiter is a char, use memchr to split
- int32_t pre_offset = -1;
- int32_t offset = -1;
- int32_t num = 0;
- while (num < part_number) {
- pre_offset = offset;
- size_t n = str.size - offset - 1;
- const char* pos = reinterpret_cast<const char*>(
- memchr(str.data + offset + 1, delimiter_str[0], n));
- if (pos != nullptr) {
- offset = pos - str.data;
- num++;
- } else {
- offset = str.size;
- num = (num == 0) ? 0 : num + 1;
- break;
+ auto str = str_col->get_data_at(i);
+ if (delimiter_size == 0) {
+ StringOP::push_empty_string(i, res_chars, res_offsets);
+ } else if (delimiter_size == 1) {
+ // If delimiter is a char, use memchr to split
+ int32_t pre_offset = -1;
+ int32_t offset = -1;
+ int32_t num = 0;
+ while (num < part_number) {
+ pre_offset = offset;
+ size_t n = str.size - offset - 1;
+ const char* pos = reinterpret_cast<const char*>(
+ memchr(str.data + offset + 1, delimiter[0], n));
+ if (pos != nullptr) {
+ offset = pos - str.data;
+ num++;
+ } else {
+ offset = str.size;
+ num = (num == 0) ? 0 : num + 1;
+ break;
+ }
}
- }
- if (num == part_number) {
- StringOP::push_value_string(
- std::string_view {
- reinterpret_cast<const char*>(str.data + pre_offset + 1),
- (size_t)offset - pre_offset - 1},
- i, res_chars, res_offsets);
+ if (num == part_number) {
+ StringOP::push_value_string(
+ std::string_view {
+ reinterpret_cast<const char*>(str.data + pre_offset + 1),
+ (size_t)offset - pre_offset - 1},
+ i, res_chars, res_offsets);
+ } else {
+ StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
+ }
} else {
- StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
- }
- } else {
- // If delimiter is a string, use memmem to split
- int32_t pre_offset = -delimiter.size;
- int32_t offset = -delimiter.size;
- int32_t num = 0;
- while (num < part_number) {
- pre_offset = offset;
- size_t n = str.size - offset - delimiter.size;
- char* pos = reinterpret_cast<char*>(memmem(str.data + offset + delimiter.size,
- n, delimiter.data, delimiter.size));
- if (pos != nullptr) {
- offset = pos - str.data;
- num++;
+ // If delimiter is a string, use memmem to split
+ int32_t pre_offset = -delimiter_size;
+ int32_t offset = pre_offset;
+ int32_t num = 0;
+ while (num < part_number) {
+ pre_offset = offset;
+ size_t n = str.size - offset - delimiter_size;
+ char* pos = reinterpret_cast<char*>(
+ memmem(str.data + offset + delimiter_size, n, delimiter.c_str(),
+ delimiter_size));
+ if (pos != nullptr) {
+ offset = pos - str.data;
+ num++;
+ } else {
+ offset = str.size;
+ num = (num == 0) ? 0 : num + 1;
+ break;
+ }
+ }
+
+ if (num == part_number) {
+ StringOP::push_value_string(
+ std::string_view {reinterpret_cast<const char*>(
+ str.data + pre_offset + delimiter_size),
+ (size_t)offset - pre_offset - delimiter_size},
+ i, res_chars, res_offsets);
} else {
- offset = str.size;
- num = (num == 0) ? 0 : num + 1;
- break;
+ StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
}
}
-
- if (num == part_number) {
- StringOP::push_value_string(
- std::string_view {reinterpret_cast<const char*>(str.data + pre_offset +
- delimiter.size),
- (size_t)offset - pre_offset - delimiter.size},
- i, res_chars, res_offsets);
+ }
+ } else {
+ part_number = -part_number;
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if (delimiter_size == 0) {
+ StringOP::push_empty_string(i, res_chars, res_offsets);
} else {
- StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
+ auto str = str_col->get_data_at(i);
+ auto str_str = str.to_string();
+ int32_t offset = str.size;
+ int32_t pre_offset = offset;
+ int32_t num = 0;
+ auto substr = str_str;
+ while (num <= part_number && offset >= 0) {
+ offset = (int)substr.rfind(delimiter, offset);
+ if (offset != -1) {
+ if (++num == part_number) {
+ break;
+ }
+ pre_offset = offset;
+ offset = offset - 1;
+ substr = str_str.substr(0, pre_offset);
+ } else {
+ break;
+ }
+ }
+ num = (offset == -1 && num != 0) ? num + 1 : num;
+
+ if (num == part_number) {
+ if (offset == -1) {
+ StringOP::push_value_string(
+ std::string_view {reinterpret_cast<const char*>(str.data),
+ (size_t)pre_offset},
+ i, res_chars, res_offsets);
+ } else {
+ StringOP::push_value_string(
+ std::string_view {str_str.substr(
+ offset + delimiter_size,
+ (size_t)pre_offset - offset - delimiter_size)},
+ i, res_chars, res_offsets);
+ }
+ } else {
+ StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
+ }
}
}
}
@@ -1717,10 +1771,11 @@ public:
if (!success) {
// url is malformed, or url_part is invalid.
if (url_part == UrlParser::INVALID) {
- return Status::RuntimeError(
- "Invalid URL part: {}\n{}", std::string(part.data, part.size),
- "(Valid URL parts are 'PROTOCOL', 'HOST', 'PATH', 'REF', 'AUTHORITY', "
- "'FILE', 'USERINFO', 'PORT' and 'QUERY')");
+ return Status::RuntimeError("Invalid URL part: {}\n{}",
+ std::string(part.data, part.size),
+ "(Valid URL parts are 'PROTOCOL', 'HOST', "
+ "'PATH', 'REF', 'AUTHORITY', "
+ "'FILE', 'USERINFO', 'PORT' and 'QUERY')");
} else {
StringOP::push_null_string(i, res_chars, res_offsets, null_map_data);
continue;
diff --git a/be/test/exprs/string_functions_test.cpp b/be/test/exprs/string_functions_test.cpp
index fa6d912b4e..89c58ecfb3 100644
--- a/be/test/exprs/string_functions_test.cpp
+++ b/be/test/exprs/string_functions_test.cpp
@@ -187,6 +187,55 @@ TEST_F(StringFunctionsTest, split_part) {
AnyValUtil::from_string(ctx, std::string("#123")),
StringFunctions::split_part(context, StringVal("abc###123###234"), StringVal("##"), 2));
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("234")),
+ StringFunctions::split_part(context, StringVal("abc###123###234"), StringVal("##"),
+ -1));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("123#")),
+ StringFunctions::split_part(context, StringVal("abc###123###234"), StringVal("##"),
+ -2));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("abc#")),
+ StringFunctions::split_part(context, StringVal("abc###123###234"), StringVal("##"),
+ -3));
+
+ EXPECT_EQ(StringVal::null(), StringFunctions::split_part(context, StringVal("abc###123###234"),
+ StringVal("##"), -4));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("234")),
+ StringFunctions::split_part(context, StringVal("abc#123##234"), StringVal("#"), -1));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("")),
+ StringFunctions::split_part(context, StringVal("abc#123##234"), StringVal("#"), -2));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("123")),
+ StringFunctions::split_part(context, StringVal("abc#123##234"), StringVal("#"), -3));
+
+ EXPECT_EQ(AnyValUtil::from_string(ctx, std::string("abc")),
+ StringFunctions::split_part(context, StringVal("abc#123##234"), StringVal("#"), -4));
+
+ EXPECT_EQ(StringVal::null(),
+ StringFunctions::split_part(context, StringVal("abc#123##234"), StringVal("#"), -5));
+
+ EXPECT_EQ(StringVal::null(), StringFunctions::split_part(context, StringVal("abc#123##234"),
+ StringVal("#"), IntVal::null()));
+
+ EXPECT_EQ(StringVal::null(), StringFunctions::split_part(context, StringVal("abc#123##234"),
+ StringVal::null(), -1));
+
+ EXPECT_EQ(
+ AnyValUtil::from_string(ctx, std::string("")),
+ StringFunctions::split_part(context, StringVal("2019年9月-12月"), StringVal("月"), -1));
+ EXPECT_EQ(
+ AnyValUtil::from_string(ctx, std::string("-12")),
+ StringFunctions::split_part(context, StringVal("2019年9月-12月"), StringVal("月"), -2));
+
+ EXPECT_EQ(
+ AnyValUtil::from_string(ctx, std::string("2019年9")),
+ StringFunctions::split_part(context, StringVal("2019年9月-12月"), StringVal("月"), -3));
+
+ EXPECT_EQ(StringVal::null(), StringFunctions::split_part(context, StringVal("2019年9月-12月"),
+ StringVal("月"), -4));
delete context;
}
diff --git a/be/test/vec/function/function_string_test.cpp b/be/test/vec/function/function_string_test.cpp
index e996bb13f9..c499718ff1 100644
--- a/be/test/vec/function/function_string_test.cpp
+++ b/be/test/vec/function/function_string_test.cpp
@@ -571,23 +571,6 @@ TEST(function_string_test, function_find_in_set_test) {
check_function<DataTypeInt32, true>(func_name, input_types, data_set);
}
-TEST(function_string_test, function_string_splitpart_test) {
- std::string func_name = "split_part";
- InputTypeSet input_types = {TypeIndex::String, TypeIndex::String, TypeIndex::Int32};
-
- DataSet data_set = {
- {{std::string("prefix_string1"), std::string("_"), 2}, std::string("string1")},
- {{std::string("prefix__string2"), std::string("__"), 2}, std::string("string2")},
- {{std::string("prefix__string2"), std::string("_"), 2}, std::string("")},
- {{std::string("prefix_string2"), std::string("__"), 1}, Null()},
- {{Null(), std::string("__"), 1}, Null()},
- {{std::string("prefix_string"), Null(), 1}, Null()},
- {{std::string("prefix_string"), std::string("__"), Null()}, Null()},
- {{std::string("prefix_string"), std::string("__"), -1}, Null()}};
-
- check_function<DataTypeString, true>(func_name, input_types, data_set);
-}
-
TEST(function_string_test, function_md5sum_test) {
std::string func_name = "md5sum";
diff --git a/docs/en/docs/sql-manual/sql-functions/string-functions/split_part.md b/docs/en/docs/sql-manual/sql-functions/string-functions/split_part.md
index 67a7bc721a..818cf6c9dc 100644
--- a/docs/en/docs/sql-manual/sql-functions/string-functions/split_part.md
+++ b/docs/en/docs/sql-manual/sql-functions/string-functions/split_part.md
@@ -31,7 +31,9 @@ under the License.
`VARCHAR split party (VARCHAR content, VARCHAR delimiter, INT field)`
-Returns the specified partition (counting from the beginning) by splitting the string according to the partitioner.
+Returns the specified partition by splitting the string according to the delimiter. If field is positive, splitting and counting from the beginning of content, otherwise from the ending.
+
+`delimiter` and `field` parameter should be constant.
### example
@@ -64,6 +66,35 @@ mysql> select split_part("abca", "a", 1);
+----------------------------+
| |
+----------------------------+
+
+mysql> select split_part("prefix_string", "_", -1);
++--------------------------------------+
+| split_part('prefix_string', '_', -1) |
++--------------------------------------+
+| string |
++--------------------------------------+
+
+
+mysql> select split_part("prefix_string", "_", -2);
++--------------------------------------+
+| split_part('prefix_string', '_', -2) |
++--------------------------------------+
+| prefix |
++--------------------------------------+
+
+mysql> select split_part("abc##123###234", "##", -1);
++----------------------------------------+
+| split_part('abc##123###234', '##', -1) |
++----------------------------------------+
+| 234 |
++----------------------------------------+
+
+mysql> select split_part("abc##123###234", "##", -2);
++----------------------------------------+
+| split_part('abc##123###234', '##', -2) |
++----------------------------------------+
+| 123# |
++----------------------------------------+
```
### keywords
SPLIT_PART,SPLIT,PART
diff --git a/docs/zh-CN/docs/sql-manual/sql-functions/string-functions/split_part.md b/docs/zh-CN/docs/sql-manual/sql-functions/string-functions/split_part.md
index 00e5620f53..0291ae29ca 100644
--- a/docs/zh-CN/docs/sql-manual/sql-functions/string-functions/split_part.md
+++ b/docs/zh-CN/docs/sql-manual/sql-functions/string-functions/split_part.md
@@ -31,7 +31,8 @@ under the License.
`VARCHAR split_part(VARCHAR content, VARCHAR delimiter, INT field)`
-根据分割符拆分字符串, 返回指定的分割部分(从一开始计数)。
+根据分割符拆分字符串, 返回指定的分割部分(从一或负一开始计数)。field字段支持负数,代表从右往左倒着截取并取数。
+`delimiter` 和 `field` 参数需要是常量, 不支持变量。
### example
@@ -64,6 +65,34 @@ mysql> select split_part("abca", "a", 1);
+----------------------------+
| |
+----------------------------+
+
+mysql> select split_part("prefix_string", "_", -1);
++--------------------------------------+
+| split_part('prefix_string', '_', -1) |
++--------------------------------------+
+| string |
++--------------------------------------+
+
+mysql> select split_part("prefix_string", "_", -2);
++--------------------------------------+
+| split_part('prefix_string', '_', -2) |
++--------------------------------------+
+| prefix |
++--------------------------------------+
+
+mysql> select split_part("abc##123###234", "##", -1);
++----------------------------------------+
+| split_part('abc##123###234', '##', -1) |
++----------------------------------------+
+| 234 |
++----------------------------------------+
+
+mysql> select split_part("abc##123###234", "##", -2);
++----------------------------------------+
+| split_part('abc##123###234', '##', -2) |
++----------------------------------------+
+| 123# |
++----------------------------------------+
```
### keywords
SPLIT_PART,SPLIT,PART
diff --git a/regression-test/data/query_p0/sql_functions/string_functions/test_string_function.out b/regression-test/data/query_p0/sql_functions/string_functions/test_string_function.out
index 8b2a4c8bb0..2ef20aff92 100644
--- a/regression-test/data/query_p0/sql_functions/string_functions/test_string_function.out
+++ b/regression-test/data/query_p0/sql_functions/string_functions/test_string_function.out
@@ -224,6 +224,39 @@ hello
-- !sql --
world
+-- !sql --
+\\N
+
+-- !sql --
+world
+
+-- !sql --
+hello
+
+-- !sql --
+\\N
+
+-- !sql --
+\\N
+
+-- !sql --
+abc
+
+-- !sql --
+#xyz
+
+-- !sql --
+\\N
+
+-- !sql --
+xyz
+
+-- !sql --
+123#
+
+-- !sql --
+\\N
+
-- !sql --
true
diff --git a/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function.groovy b/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function.groovy
index fb0df8f38f..7c78210608 100644
--- a/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function.groovy
+++ b/regression-test/suites/query_p0/sql_functions/string_functions/test_string_function.groovy
@@ -117,8 +117,19 @@ suite("test_string_function") {
qt_sql "SELECT REVERSE('hello');"
- qt_sql "select split_part(\"hello world\", \" \", 1);"
- qt_sql "select split_part(\"hello world\", \" \", 2);"
+ qt_sql "select split_part('hello world', ' ', 1)"
+ qt_sql "select split_part('hello world', ' ', 2)"
+ qt_sql "select split_part('hello world', ' ', 0)"
+ qt_sql "select split_part('hello world', ' ', -1)"
+ qt_sql "select split_part('hello world', ' ', -2)"
+ qt_sql "select split_part('hello world', ' ', -3)"
+ qt_sql "select split_part('abc##123###xyz', '##', 0)"
+ qt_sql "select split_part('abc##123###xyz', '##', 1)"
+ qt_sql "select split_part('abc##123###xyz', '##', 3)"
+ qt_sql "select split_part('abc##123###xyz', '##', 5)"
+ qt_sql "select split_part('abc##123###xyz', '##', -1)"
+ qt_sql "select split_part('abc##123###xyz', '##', -2)"
+ qt_sql "select split_part('abc##123###xyz', '##', -4)"
qt_sql "select starts_with(\"hello world\",\"hello\");"
qt_sql "select starts_with(\"hello world\",\"world\");"
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org