This is an automated email from the ASF dual-hosted git repository.
dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.0 by this push:
new b085028a896 branch-3.0: [feature](functions) Pick funcs to 3.0 (#50481)
b085028a896 is described below
commit b085028a89648cc25b7782b36055dbf37960e2b4
Author: Socrates <[email protected]>
AuthorDate: Tue May 6 09:59:04 2025 +0800
branch-3.0: [feature](functions) Pick funcs to 3.0 (#50481)
pick prs:
#49119
#49122
#49142
#49262
---
be/cmake/thirdparty.cmake | 5 +-
.../function_date_or_datetime_computation.cpp | 3 +
.../function_date_or_datetime_computation.h | 208 ++++++++++++++++++
be/src/vec/functions/function_map.cpp | 156 ++++++++++++++
be/src/vec/functions/function_string.cpp | 1 +
be/src/vec/functions/function_string.h | 136 ++++++++++++
be/test/vec/function/function_string_test.cpp | 51 +++++
be/test/vec/function/function_time_test.cpp | 107 ++++++++++
.../doris/catalog/BuiltinScalarFunctions.java | 8 +
.../executable/DateTimeExtractAndTransform.java | 78 +++++++
.../functions/scalar/MonthsBetween.java | 83 ++++++++
.../expressions/functions/scalar/NextDay.java | 74 +++++++
.../expressions/functions/scalar/StrToMap.java | 110 ++++++++++
.../expressions/functions/scalar/XpathString.java | 72 +++++++
.../expressions/visitor/ScalarFunctionVisitor.java | 20 ++
.../nereids/rules/expression/FoldConstantTest.java | 57 +++++
.../string_functions/test_months_between.out | Bin 0 -> 4763 bytes
.../string_functions/test_next_day.out | Bin 0 -> 2863 bytes
.../string_functions/test_str_to_map.out | Bin 0 -> 17405 bytes
.../string_functions/test_xpath_string.out | Bin 0 -> 1850 bytes
.../fold_constant_date_arithmatic.groovy | 29 +++
.../string_functions/test_months_between.groovy | 147 +++++++++++++
.../string_functions/test_next_day.groovy | 129 ++++++++++++
.../string_functions/test_str_to_map.groovy | 232 +++++++++++++++++++++
.../string_functions/test_xpath_string.groovy | 149 +++++++++++++
25 files changed, 1854 insertions(+), 1 deletion(-)
diff --git a/be/cmake/thirdparty.cmake b/be/cmake/thirdparty.cmake
index 67db4aa2733..764719ae00c 100644
--- a/be/cmake/thirdparty.cmake
+++ b/be/cmake/thirdparty.cmake
@@ -174,4 +174,7 @@ endif()
add_thirdparty(icuuc LIB64)
add_thirdparty(icui18n LIB64)
-add_thirdparty(icudata LIB64)
\ No newline at end of file
+add_thirdparty(icudata LIB64)
+
+
+add_thirdparty(pugixml LIB64)
diff --git a/be/src/vec/functions/function_date_or_datetime_computation.cpp
b/be/src/vec/functions/function_date_or_datetime_computation.cpp
index f6bf806ad46..50e6db475f5 100644
--- a/be/src/vec/functions/function_date_or_datetime_computation.cpp
+++ b/be/src/vec/functions/function_date_or_datetime_computation.cpp
@@ -122,6 +122,8 @@ void
register_function_date_time_computation(SimpleFunctionFactory& factory) {
factory.register_function<FunctionMinutesDiff>();
factory.register_function<FunctionSecondsDiff>();
+ factory.register_function<FunctionNextDay>();
+
factory.register_function<FunctionToYearWeekTwoArgs>();
factory.register_function<FunctionToWeekTwoArgs>();
@@ -135,6 +137,7 @@ void
register_function_date_time_computation(SimpleFunctionFactory& factory) {
factory.register_function<FunctionMicroSecToDateTime>();
factory.register_function<FunctionMilliSecToDateTime>();
factory.register_function<FunctionSecToDateTime>();
+ factory.register_function<FunctionMonthsBetween>();
// alias
factory.register_alias("days_add", "date_add");
diff --git a/be/src/vec/functions/function_date_or_datetime_computation.h
b/be/src/vec/functions/function_date_or_datetime_computation.h
index ac18965749e..ab887812249 100644
--- a/be/src/vec/functions/function_date_or_datetime_computation.h
+++ b/be/src/vec/functions/function_date_or_datetime_computation.h
@@ -1183,4 +1183,212 @@ protected:
}
};
+class FunctionMonthsBetween : public IFunction {
+public:
+ static constexpr auto name = "months_between";
+ static FunctionPtr create() { return
std::make_shared<FunctionMonthsBetween>(); }
+ String get_name() const override { return name; }
+ size_t get_number_of_arguments() const override { return 3; }
+
+ DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
+ return std::make_shared<DataTypeFloat64>();
+ }
+
+ Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ size_t result, size_t input_rows_count) const override
{
+ CHECK_EQ(arguments.size(), 3);
+ auto res = ColumnFloat64::create();
+
+ bool date_consts[2];
+ date_consts[0] =
is_column_const(*block.get_by_position(arguments[0]).column);
+ date_consts[1] =
is_column_const(*block.get_by_position(arguments[1]).column);
+ ColumnPtr date_cols[2];
+ // convert const columns to full columns if necessary
+ default_preprocess_parameter_columns(date_cols, date_consts, {0, 1},
block, arguments);
+
+ const auto& [col3, col3_const] =
+ unpack_if_const(block.get_by_position(arguments[2]).column);
+
+ const auto& date1_col = *assert_cast<const
ColumnDateV2*>(date_cols[0].get());
+ const auto& date2_col = *assert_cast<const
ColumnDateV2*>(date_cols[1].get());
+ const auto& round_off_col = *assert_cast<const
ColumnBool*>(col3.get());
+
+ if (date_consts[0] && date_consts[1]) {
+ execute_vector<true, false>(input_rows_count, date1_col,
date2_col, round_off_col,
+ *res);
+ } else if (col3_const) {
+ execute_vector<false, true>(input_rows_count, date1_col,
date2_col, round_off_col,
+ *res);
+ } else {
+ execute_vector<false, false>(input_rows_count, date1_col,
date2_col, round_off_col,
+ *res);
+ }
+
+ block.replace_by_position(result, std::move(res));
+ return Status::OK();
+ }
+
+private:
+ template <bool is_date_const, bool is_round_off_const>
+ static void execute_vector(const size_t input_rows_count, const
ColumnDateV2& date1_col,
+ const ColumnDateV2& date2_col, const
ColumnBool& round_off_col,
+ ColumnFloat64& res) {
+ res.reserve(input_rows_count);
+ double months_between;
+ bool round_off;
+
+ if constexpr (is_date_const) {
+ auto dtv1 = binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(date1_col.get_element(0));
+ auto dtv2 = binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(date2_col.get_element(0));
+ months_between = calc_months_between(dtv1, dtv2);
+ }
+
+ if constexpr (is_round_off_const) {
+ round_off = round_off_col.get_element(0);
+ }
+
+ for (int i = 0; i < input_rows_count; ++i) {
+ if constexpr (!is_date_const) {
+ auto dtv1 =
+ binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(date1_col.get_element(i));
+ auto dtv2 =
+ binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(date2_col.get_element(i));
+ months_between = calc_months_between(dtv1, dtv2);
+ }
+ if constexpr (!is_round_off_const) {
+ round_off = round_off_col.get_element(i);
+ }
+ if (round_off) {
+ months_between = round_months_between(months_between);
+ }
+ res.insert_value(months_between);
+ }
+ }
+
+ static double calc_months_between(const DateV2Value<DateV2ValueType>& dtv1,
+ const DateV2Value<DateV2ValueType>&
dtv2) {
+ auto year_between = dtv1.year() - dtv2.year();
+ auto months_between = dtv1.month() - dtv2.month();
+ auto days_in_month1 = S_DAYS_IN_MONTH[dtv1.month()];
+ if (UNLIKELY(is_leap(dtv1.year()) && dtv1.month() == 2)) {
+ days_in_month1 = 29;
+ }
+ auto days_in_month2 = S_DAYS_IN_MONTH[dtv2.month()];
+ if (UNLIKELY(is_leap(dtv2.year()) && dtv2.month() == 2)) {
+ days_in_month2 = 29;
+ }
+ double days_between = 0;
+ // if date1 and date2 are all the last day of the month, days_between
is 0
+ if (UNLIKELY(dtv1.day() == days_in_month1 && dtv2.day() ==
days_in_month2)) {
+ days_between = 0;
+ } else {
+ days_between = (dtv1.day() - dtv2.day()) / (double)31.0;
+ }
+
+ // calculate months between
+ return year_between * 12 + months_between + days_between;
+ }
+
+ static double round_months_between(double months_between) {
+ return round(months_between * 100000000) / 100000000;
+ }
+};
+
+class FunctionNextDay : public IFunction {
+public:
+ static constexpr auto name = "next_day";
+ static FunctionPtr create() { return std::make_shared<FunctionNextDay>(); }
+ String get_name() const override { return name; }
+ size_t get_number_of_arguments() const override { return 2; }
+ DataTypePtr get_return_type_impl(const ColumnsWithTypeAndName& arguments)
const override {
+ return std::make_shared<DataTypeDateV2>();
+ }
+
+ Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ size_t result, size_t input_rows_count) const override
{
+ CHECK_EQ(arguments.size(), 2);
+ auto res = ColumnDateV2::create();
+ res->reserve(input_rows_count);
+ const auto& [left_col, left_const] =
+ unpack_if_const(block.get_by_position(arguments[0]).column);
+ const auto& [right_col, right_const] =
+ unpack_if_const(block.get_by_position(arguments[1]).column);
+ const auto& date_col = *assert_cast<const
ColumnDateV2*>(left_col.get());
+ const auto& week_col = *assert_cast<const
ColumnString*>(right_col.get());
+ Status status;
+ if (left_const && right_const) {
+ status = execute_vector<true, true>(input_rows_count, date_col,
week_col, *res);
+ } else if (left_const) {
+ status = execute_vector<true, false>(input_rows_count, date_col,
week_col, *res);
+ } else if (right_const) {
+ status = execute_vector<false, true>(input_rows_count, date_col,
week_col, *res);
+ } else {
+ status = execute_vector<false, false>(input_rows_count, date_col,
week_col, *res);
+ }
+ if (!status.ok()) {
+ return status;
+ }
+ block.replace_by_position(result, std::move(res));
+ return Status::OK();
+ }
+
+private:
+ static int day_of_week(const StringRef& weekday) {
+ static const std::unordered_map<std::string, int> weekday_map = {
+ {"MO", 1}, {"MON", 1}, {"MONDAY", 1}, {"TU", 2}, {"TUE",
2}, {"TUESDAY", 2},
+ {"WE", 3}, {"WED", 3}, {"WEDNESDAY", 3}, {"TH", 4}, {"THU",
4}, {"THURSDAY", 4},
+ {"FR", 5}, {"FRI", 5}, {"FRIDAY", 5}, {"SA", 6}, {"SAT",
6}, {"SATURDAY", 6},
+ {"SU", 7}, {"SUN", 7}, {"SUNDAY", 7}};
+ auto weekday_upper = weekday.to_string();
+ std::transform(weekday_upper.begin(), weekday_upper.end(),
weekday_upper.begin(),
+ ::toupper);
+ auto it = weekday_map.find(weekday_upper);
+ if (it == weekday_map.end()) {
+ return 0;
+ }
+ return it->second;
+ }
+ static Status compute_next_day(DateV2Value<DateV2ValueType>& dtv, const
int week_day) {
+ auto days_to_add = (week_day - (dtv.weekday() + 1) + 7) % 7;
+ days_to_add = days_to_add == 0 ? 7 : days_to_add;
+ dtv.date_add_interval<TimeUnit::DAY>(TimeInterval(TimeUnit::DAY,
days_to_add, false));
+ return Status::OK();
+ }
+
+ template <bool left_const, bool right_const>
+ static Status execute_vector(size_t input_rows_count, const ColumnDateV2&
left_col,
+ const ColumnString& right_col, ColumnDateV2&
res_col) {
+ DateV2Value<DateV2ValueType> dtv;
+ int week_day;
+ if constexpr (left_const) {
+ dtv = binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(left_col.get_element(0));
+ }
+ if constexpr (right_const) {
+ auto week = right_col.get_data_at(0);
+ week_day = day_of_week(week);
+ if (week_day == 0) {
+ return Status::InvalidArgument("Function {} failed to parse
weekday: {}", name,
+ week);
+ }
+ }
+
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if constexpr (!left_const) {
+ dtv = binary_cast<UInt32,
DateV2Value<DateV2ValueType>>(left_col.get_element(i));
+ }
+ if constexpr (!right_const) {
+ auto week = right_col.get_data_at(i);
+ week_day = day_of_week(week);
+ if (week_day == 0) {
+ return Status::InvalidArgument("Function {} failed to
parse weekday: {}", name,
+ week);
+ }
+ }
+ RETURN_IF_ERROR(compute_next_day(dtv, week_day));
+ res_col.insert_value(binary_cast<DateV2Value<DateV2ValueType>,
UInt32>(dtv));
+ }
+ return Status::OK();
+ }
+};
+
} // namespace doris::vectorized
diff --git a/be/src/vec/functions/function_map.cpp
b/be/src/vec/functions/function_map.cpp
index d781fc6cac4..3a685608412 100644
--- a/be/src/vec/functions/function_map.cpp
+++ b/be/src/vec/functions/function_map.cpp
@@ -23,10 +23,12 @@
#include <memory>
#include <ostream>
#include <string>
+#include <string_view>
#include <tuple>
#include <utility>
#include "common/status.h"
+#include "util/simd/vstring_function.h"
#include "vec/aggregate_functions/aggregate_function.h"
#include "vec/columns/column.h"
#include "vec/columns/column_array.h"
@@ -46,6 +48,7 @@
#include "vec/data_types/data_type_map.h"
#include "vec/data_types/data_type_nullable.h"
#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
#include "vec/functions/array/function_array_index.h"
#include "vec/functions/function.h"
#include "vec/functions/simple_function_factory.h"
@@ -286,12 +289,165 @@ public:
}
};
+class FunctionStrToMap : public IFunction {
+public:
+ static constexpr auto name = "str_to_map";
+ static FunctionPtr create() { return std::make_shared<FunctionStrToMap>();
}
+
+ String get_name() const override { return name; }
+
+ size_t get_number_of_arguments() const override { return 3; }
+
+ DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
+ return
std::make_shared<DataTypeMap>(make_nullable(std::make_shared<DataTypeString>()),
+
make_nullable(std::make_shared<DataTypeString>()));
+ }
+
+ Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ size_t result, size_t input_rows_count) const override
{
+ DCHECK(arguments.size() == 3);
+
+ bool cols_const[2];
+ ColumnPtr cols[2];
+ for (size_t i = 0; i < 2; ++i) {
+ cols_const[i] =
is_column_const(*block.get_by_position(arguments[i]).column);
+ }
+ // convert to full column if necessary
+ default_preprocess_parameter_columns(cols, cols_const, {0, 1}, block,
arguments);
+ const auto& [col3, col3_const] =
+ unpack_if_const(block.get_by_position(arguments[2]).column);
+
+ const auto& str_column = assert_cast<const
ColumnString*>(cols[0].get());
+ const auto& pair_delim_column = assert_cast<const
ColumnString*>(cols[1].get());
+ const auto& kv_delim_column = assert_cast<const
ColumnString*>(col3.get());
+
+ ColumnPtr result_col;
+ if (cols_const[0] && cols_const[1]) {
+ result_col = execute_vector<true, false>(input_rows_count,
*str_column,
+ *pair_delim_column,
*kv_delim_column);
+ } else if (col3_const) {
+ result_col = execute_vector<false, true>(input_rows_count,
*str_column,
+ *pair_delim_column,
*kv_delim_column);
+ } else {
+ result_col = execute_vector<false, false>(input_rows_count,
*str_column,
+ *pair_delim_column,
*kv_delim_column);
+ }
+
+ block.replace_by_position(result, std::move(result_col));
+
+ return Status::OK();
+ }
+
+private:
+ template <bool is_str_and_pair_delim_const, bool is_kv_delim_const>
+ static ColumnPtr execute_vector(const size_t input_rows_count, const
ColumnString& str_col,
+ const ColumnString& pair_delim_col,
+ const ColumnString& kv_delim_col) {
+ // map keys column
+ auto result_col_map_keys_data =
+ ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ result_col_map_keys_data->reserve(input_rows_count);
+ // map values column
+ auto result_col_map_vals_data =
+ ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ result_col_map_vals_data->reserve(input_rows_count);
+ // map offsets column
+ auto result_col_map_offsets = ColumnUInt64::create();
+ result_col_map_offsets->reserve(input_rows_count);
+
+ std::vector<std::string_view> kvs;
+ std::string_view kv_delim;
+ if constexpr (is_str_and_pair_delim_const) {
+ auto str = str_col.get_data_at(0).to_string_view();
+ auto pair_delim = pair_delim_col.get_data_at(0).to_string_view();
+ kvs = split_pair_by_delim(str, pair_delim);
+ }
+ if constexpr (is_kv_delim_const) {
+ kv_delim = kv_delim_col.get_data_at(0).to_string_view();
+ }
+
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if constexpr (!is_str_and_pair_delim_const) {
+ auto str = str_col.get_data_at(i).to_string_view();
+ auto pair_delim =
pair_delim_col.get_data_at(i).to_string_view();
+ kvs = split_pair_by_delim(str, pair_delim);
+ }
+ if constexpr (!is_kv_delim_const) {
+ kv_delim = kv_delim_col.get_data_at(i).to_string_view();
+ }
+
+ for (const auto& kv : kvs) {
+ auto kv_parts = split_kv_by_delim(kv, kv_delim);
+ if (kv_parts.size() == 2) {
+ result_col_map_keys_data->insert_data(kv_parts[0].data(),
kv_parts[0].size());
+ result_col_map_vals_data->insert_data(kv_parts[1].data(),
kv_parts[1].size());
+ } else {
+ result_col_map_keys_data->insert_data(kv.data(),
kv.size());
+ result_col_map_vals_data->insert_default();
+ }
+ }
+
result_col_map_offsets->insert_value(result_col_map_keys_data->size());
+ }
+
+ return ColumnMap::create(std::move(result_col_map_keys_data),
+ std::move(result_col_map_vals_data),
+ std::move(result_col_map_offsets));
+ }
+
+ static std::vector<std::string_view> split_pair_by_delim(const
std::string_view& str,
+ const
std::string_view& delim) {
+ if (str.empty()) {
+ return {str};
+ }
+ if (delim.empty()) {
+ std::vector<std::string_view> result;
+ size_t offset = 0;
+ while (offset < str.size()) {
+ auto len = get_utf8_byte_length(str[offset]);
+ result.push_back(str.substr(offset, len));
+ offset += len;
+ }
+ return result;
+ }
+ std::vector<std::string_view> result;
+ size_t offset = 0;
+ while (offset < str.size()) {
+ auto pos = str.find(delim, offset);
+ if (pos == std::string::npos) {
+ result.push_back(str.substr(offset));
+ break;
+ }
+ result.push_back(str.substr(offset, pos - offset));
+ offset = pos + delim.size();
+ }
+ return result;
+ }
+
+ static std::vector<std::string_view> split_kv_by_delim(const
std::string_view& str,
+ const
std::string_view& delim) {
+ if (str.empty()) {
+ return {str};
+ }
+ if (delim.empty()) {
+ auto len = get_utf8_byte_length(str[0]);
+ return {str.substr(0, len), str.substr(len)};
+ }
+ auto pos = str.find(delim);
+ if (pos == std::string::npos) {
+ return {str};
+ } else {
+ return {str.substr(0, pos), str.substr(pos + delim.size())};
+ }
+ }
+};
+
void register_function_map(SimpleFunctionFactory& factory) {
factory.register_function<FunctionMap>();
factory.register_function<FunctionMapContains<true>>();
factory.register_function<FunctionMapContains<false>>();
factory.register_function<FunctionMapEntries<true>>();
factory.register_function<FunctionMapEntries<false>>();
+ factory.register_function<FunctionStrToMap>();
}
} // namespace doris::vectorized
diff --git a/be/src/vec/functions/function_string.cpp
b/be/src/vec/functions/function_string.cpp
index 064b3a7af22..babb3730ea8 100644
--- a/be/src/vec/functions/function_string.cpp
+++ b/be/src/vec/functions/function_string.cpp
@@ -1367,6 +1367,7 @@ void register_function_string(SimpleFunctionFactory&
factory) {
factory.register_function<FunctionOverlay>();
factory.register_function<FunctionStrcmp>();
factory.register_function<FunctionNgramSearch>();
+ factory.register_function<FunctionXPathString>();
factory.register_alias(FunctionLeft::name, "strleft");
factory.register_alias(FunctionRight::name, "strright");
diff --git a/be/src/vec/functions/function_string.h
b/be/src/vec/functions/function_string.h
index 7202d356523..493d1549c4f 100644
--- a/be/src/vec/functions/function_string.h
+++ b/be/src/vec/functions/function_string.h
@@ -17,6 +17,7 @@
#pragma once
+#include <glog/logging.h>
#include <sys/types.h>
#include <algorithm>
@@ -85,6 +86,7 @@
#include <string_view>
#include "exprs/math_functions.h"
+#include "pugixml.hpp"
#include "udf/udf.h"
#include "util/md5.h"
#include "util/simd/vstring_function.h"
@@ -4837,4 +4839,138 @@ private:
}
};
+/// xpath_string(xml, xpath) -> String
+/// Returns the text content of the first node that matches the XPath
expression.
+/// Returns NULL if either xml or xpath is NULL.
+/// Returns empty string if the XPath expression matches no nodes.
+/// The text content includes the node and all its descendants.
+/// Example:
+/// xpath_string('<a><b>b1</b><b>b2</b></a>', '/a/b[1]') = 'b1'
+/// xpath_string('<a><b>b1</b><b>b2</b></a>', '/a/b[2]') = 'b2'
+/// xpath_string('<a><b>b1</b><b>b2</b></a>', '/a/c') = ''
+/// xpath_string('invalid xml', '/a/b[1]') = NULL
+/// xpath_string(NULL, '/a/b[1]') = NULL
+/// xpath_string('<a><b>b1</b><b>b2</b></a>', NULL) = NULL
+class FunctionXPathString : public IFunction {
+public:
+ static constexpr auto name = "xpath_string";
+ static FunctionPtr create() { return
std::make_shared<FunctionXPathString>(); }
+ String get_name() const override { return name; }
+ size_t get_number_of_arguments() const override { return 2; }
+ DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
+ return make_nullable(std::make_shared<DataTypeString>());
+ }
+
+ Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ size_t result, size_t input_rows_count) const override
{
+ CHECK_EQ(arguments.size(), 2);
+ auto col_res = ColumnNullable::create(ColumnString::create(),
ColumnUInt8::create());
+ const auto& [left_col, left_const] =
+ unpack_if_const(block.get_by_position(arguments[0]).column);
+ const auto& [right_col, right_const] =
+ unpack_if_const(block.get_by_position(arguments[1]).column);
+ const auto& xml_col = *assert_cast<const
ColumnString*>(left_col.get());
+ const auto& xpath_col = *assert_cast<const
ColumnString*>(right_col.get());
+
+ Status status;
+ if (left_const && right_const) {
+ status = execute_vector<true, true>(input_rows_count, xml_col,
xpath_col, *col_res);
+ } else if (left_const) {
+ status = execute_vector<true, false>(input_rows_count, xml_col,
xpath_col, *col_res);
+ } else if (right_const) {
+ status = execute_vector<false, true>(input_rows_count, xml_col,
xpath_col, *col_res);
+ } else {
+ status = execute_vector<false, false>(input_rows_count, xml_col,
xpath_col, *col_res);
+ }
+ if (!status.ok()) {
+ return status;
+ }
+
+ block.get_by_position(result).column = std::move(col_res);
+ return Status::OK();
+ }
+
+private:
+ // Build the text of the node and all its children.
+ static std::string get_text(const pugi::xml_node& node) {
+ std::string result;
+ build_text(node, result);
+ return result;
+ }
+
+ static void build_text(const pugi::xml_node& node, std::string& builder) {
+ if (node.type() == pugi::node_pcdata || node.type() ==
pugi::node_cdata) {
+ builder += node.value();
+ }
+ for (pugi::xml_node child : node.children()) {
+ build_text(child, builder);
+ }
+ }
+
+ static Status parse_xml(const StringRef& xml_str, pugi::xml_document&
xml_doc) {
+ pugi::xml_parse_result result = xml_doc.load_buffer(xml_str.data,
xml_str.size);
+ if (!result) {
+ return Status::InvalidArgument("Function {} failed to parse XML
string: {}", name,
+ result.description());
+ }
+ return Status::OK();
+ }
+
+ template <bool left_const, bool right_const>
+ static Status execute_vector(const size_t input_rows_count, const
ColumnString& xml_col,
+ const ColumnString& xpath_col,
ColumnNullable& res_col) {
+ pugi::xml_document xml_doc;
+ StringRef xpath_str;
+ // first check right_const, because we want to check empty input first
+ if constexpr (right_const) {
+ xpath_str = xpath_col.get_data_at(0);
+ if (xpath_str.empty()) {
+ // should return null if xpath_str is empty
+ res_col.insert_many_defaults(input_rows_count);
+ return Status::OK();
+ }
+ }
+ if constexpr (left_const) {
+ auto xml_str = xml_col.get_data_at(0);
+ if (xml_str.empty()) {
+ // should return null if xml_str is empty
+ res_col.insert_many_defaults(input_rows_count);
+ return Status::OK();
+ }
+ RETURN_IF_ERROR(parse_xml(xml_str, xml_doc));
+ }
+
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ if constexpr (!right_const) {
+ xpath_str = xpath_col.get_data_at(i);
+ if (xpath_str.empty()) {
+ // should return null if xpath_str is empty
+ res_col.insert_default();
+ continue;
+ }
+ }
+ if constexpr (!left_const) {
+ auto xml_str = xml_col.get_data_at(i);
+ if (xml_str.empty()) {
+ // should return null if xml_str is empty
+ res_col.insert_default();
+ continue;
+ }
+ RETURN_IF_ERROR(parse_xml(xml_str, xml_doc));
+ }
+ // NOTE!!!: don't use to_string_view(), because xpath_str maybe
not null-terminated
+ pugi::xpath_node node =
xml_doc.select_node(xpath_str.to_string().c_str());
+ if (!node) {
+ // should return empty string if not found
+ auto empty_str = std::string("");
+ res_col.insert_data(empty_str.data(), empty_str.size());
+ continue;
+ }
+ auto text = get_text(node.node());
+ res_col.insert_data(text.data(), text.size());
+ }
+ return Status::OK();
+ }
+};
+
} // namespace doris::vectorized
diff --git a/be/test/vec/function/function_string_test.cpp
b/be/test/vec/function/function_string_test.cpp
index 9f1ffe438a1..8c4b3c8a1c7 100644
--- a/be/test/vec/function/function_string_test.cpp
+++ b/be/test/vec/function/function_string_test.cpp
@@ -3372,4 +3372,55 @@ TEST(function_string_test, function_rpad_test) {
check_function_all_arg_comb<DataTypeString, true>(func_name, input_types,
data_set);
}
+TEST(function_string_test, function_xpath_string_test) {
+ std::string func_name = "xpath_string";
+ BaseInputTypeSet input_types = {TypeIndex::String, TypeIndex::String};
+
+ DataSet data_set = {
+ {{std::string("<a>123</a>"), std::string("/a")},
std::string("123")},
+ {{std::string("<a><b>123</b></a>"), std::string("/a/b")},
std::string("123")},
+ {{std::string("<a><b>123</b><c>456</c></a>"),
std::string("/a/c")}, std::string("456")},
+ {{std::string("<a><b>123</b><c>456</c></a>"),
std::string("/a/d")}, std::string("")},
+ {{std::string("<a><b>123</b><b>456</b></a>"),
std::string("/a/b[1]")},
+ std::string("123")},
+ {{std::string("<a><b>123</b><b>456</b></a>"),
std::string("/a/b[2]")},
+ std::string("456")},
+ {{std::string("<a><b>123</b><b>456</b></a>"),
std::string("/a/b[3]")}, std::string("")},
+ {{std::string("<a><b attr='val'>123</b></a>"),
std::string("/a/b[@attr]")},
+ std::string("123")},
+ {{std::string("<a><b attr='val'>123</b></a>"),
std::string("/a/b[@attr='val']")},
+ std::string("123")},
+ {{std::string("<a><b attr='val'>123</b></a>"),
std::string("/a/b[@attr='wrong']")},
+ std::string("")},
+ {{std::string("<a><!-- comment -->123</a>"), std::string("/a")},
std::string("123")},
+ {{std::string("<a><![CDATA[123]]></a>"), std::string("/a")},
std::string("123")},
+ {{std::string("<a>123<b>456</b>789</a>"), std::string("/a")},
std::string("123456789")},
+ {{std::string("<a> 123 </a>"), std::string("/a")}, std::string("
123 ")},
+ {{std::string("<a></a>"), std::string("/a")}, std::string("")},
+ {{std::string("<a/>"), std::string("/a")}, std::string("")},
+ {{std::string("<a>123</a>"), std::string("")}, Null()},
+ {{std::string(""), std::string("/a")}, Null()},
+ {{Null(), std::string("/a")}, Null()},
+ {{std::string("<a>123</a>"), Null()}, Null()},
+ {{std::string("<book><title>Intro to Hive</title><author>John "
+ "Doe</author><publisher>Tech
Press</publisher></book>"),
+ std::string("//title/text()")},
+ std::string("Intro to Hive")},
+ {{std::string("<book><title>Intro to Hive</title><author>John "
+ "Doe</author><publisher>Tech
Press</publisher></book>"),
+ std::string("//author/text()")},
+ std::string("John Doe")},
+ {{std::string("<book><title>Intro to Hive</title><author>John "
+ "Doe</author><publisher>Tech
Press</publisher></book>"),
+ std::string("//publisher/text()")},
+ std::string("Tech Press")},
+ {{std::string("<book><title>Intro to Hive</title><author>John "
+ "Doe</author><publisher>Tech
Press</publisher></book>"),
+ std::string("/book")},
+ std::string("Intro to HiveJohn DoeTech Press")},
+ {{Null(), Null()}, Null()}};
+
+ check_function_all_arg_comb<DataTypeString, true>(func_name, input_types,
data_set);
+}
+
} // namespace doris::vectorized
diff --git a/be/test/vec/function/function_time_test.cpp
b/be/test/vec/function/function_time_test.cpp
index 17f487287ec..1ea010e4afd 100644
--- a/be/test/vec/function/function_time_test.cpp
+++ b/be/test/vec/function/function_time_test.cpp
@@ -1785,4 +1785,111 @@ TEST(VTimestampFunctionsTest, year_of_week_test) {
}
}
+TEST(VTimestampFunctionsTest, months_between_test) {
+ std::string func_name = "months_between";
+ BaseInputTypeSet input_types = {TypeIndex::DateV2, TypeIndex::DateV2,
TypeIndex::UInt8};
+ DataSet data_set = {
+ {{std::string("2020-01-01"), std::string("2020-02-01"),
uint8_t(0)}, double(-1.0)},
+ {{std::string("2020-01-01"), std::string("2020-03-01"),
uint8_t(1)}, double(-2.0)},
+ {{std::string("2020-01-01"), std::string("2020-04-01"),
uint8_t(0)}, double(-3.0)},
+ {{std::string("2020-01-01"), std::string("2020-12-01"),
uint8_t(1)}, double(-11.0)},
+ {{std::string("2020-01-01"), std::string("2021-01-01"),
uint8_t(0)}, double(-12.0)},
+ {{std::string("2020-01-01"), std::string("2022-01-01"),
uint8_t(1)}, double(-24.0)},
+ {{std::string("2020-01-01"), std::string("2020-01-01"),
uint8_t(0)}, double(0.0)},
+ {{std::string("2020-12-01"), std::string("2020-01-01"),
uint8_t(1)}, double(11.0)},
+ {{std::string("2021-01-01"), std::string("2020-01-01"),
uint8_t(0)}, double(12.0)},
+ {{std::string("2022-01-01"), std::string("2020-01-01"),
uint8_t(1)}, double(24.0)},
+ {{std::string(""), std::string("2020-01-01"), uint8_t(1)}, Null()},
+ {{std::string("2020-01-01"), std::string(""), uint8_t(1)}, Null()},
+ {{Null(), std::string("2020-01-01"), uint8_t(1)}, Null()},
+ {{std::string("2020-01-01"), Null(), uint8_t(1)}, Null()}};
+ static_cast<void>(
+ check_function_all_arg_comb<DataTypeFloat64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VTimestampFunctionsTest, next_day_test) {
+ std::string func_name = "next_day";
+ BaseInputTypeSet input_types = {TypeIndex::DateV2, TypeIndex::String};
+ {
+ DataSet data_set = {{{std::string("2020-01-01"), std::string("MO")},
+ str_to_date_v2("2020-01-06", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("MON")},
+ str_to_date_v2("2020-01-06", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("MONDAY")},
+ str_to_date_v2("2020-01-06", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("TU")},
+ str_to_date_v2("2020-01-07", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("TUE")},
+ str_to_date_v2("2020-01-07", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("TUESDAY")},
+ str_to_date_v2("2020-01-07", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("WE")},
+ str_to_date_v2("2020-01-08", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("WED")},
+ str_to_date_v2("2020-01-08", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("WEDNESDAY")},
+ str_to_date_v2("2020-01-08", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("TH")},
+ str_to_date_v2("2020-01-02", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("THU")},
+ str_to_date_v2("2020-01-02", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("THURSDAY")},
+ str_to_date_v2("2020-01-02", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("FR")},
+ str_to_date_v2("2020-01-03", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("FRI")},
+ str_to_date_v2("2020-01-03", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("FRIDAY")},
+ str_to_date_v2("2020-01-03", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("SA")},
+ str_to_date_v2("2020-01-04", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("SAT")},
+ str_to_date_v2("2020-01-04", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("SATURDAY")},
+ str_to_date_v2("2020-01-04", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("SU")},
+ str_to_date_v2("2020-01-05", "%Y-%m-%d")},
+ {{std::string("2020-01-01"), std::string("SUN")},
+ str_to_date_v2("2020-01-05", "%Y-%m-%d")},
+ {{std::string("2020-01-01"),
std::string("SUNDAY")},
+ str_to_date_v2("2020-01-05", "%Y-%m-%d")},
+ {{std::string(""), std::string("MON")}, Null()},
+ {{Null(), std::string("MON")}, Null()}};
+ static_cast<void>(check_function_all_arg_comb<DataTypeDateV2,
true>(func_name, input_types,
+
data_set));
+ }
+ {
+ DataSet data_set = {// date over month
+ {{std::string("2020-01-28"), std::string("MON")},
+ str_to_date_v2("2020-02-03", "%Y-%m-%d")},
+ {{std::string("2020-01-31"), std::string("SAT")},
+ str_to_date_v2("2020-02-01", "%Y-%m-%d")},
+
+ // date over year
+ {{std::string("2020-12-28"), std::string("FRI")},
+ str_to_date_v2("2021-01-01", "%Y-%m-%d")},
+ {{std::string("2020-12-31"), std::string("THU")},
+ str_to_date_v2("2021-01-07", "%Y-%m-%d")},
+
+ // leap year(29 Feb)
+ {{std::string("2020-02-27"), std::string("SAT")},
+ str_to_date_v2("2020-02-29", "%Y-%m-%d")},
+ {{std::string("2020-02-29"), std::string("MON")},
+ str_to_date_v2("2020-03-02", "%Y-%m-%d")},
+
+ // non leap year(28 Feb)
+ {{std::string("2019-02-26"), std::string("THU")},
+ str_to_date_v2("2019-02-28", "%Y-%m-%d")},
+ {{std::string("2019-02-28"), std::string("SUN")},
+ str_to_date_v2("2019-03-03", "%Y-%m-%d")},
+
+ // date over month
+ {{std::string("2020-04-29"), std::string("FRI")},
+ str_to_date_v2("2020-05-01", "%Y-%m-%d")},
+ {{std::string("2020-05-31"), std::string("MON")},
+ str_to_date_v2("2020-06-01", "%Y-%m-%d")}};
+ static_cast<void>(check_function_all_arg_comb<DataTypeDateV2,
true>(func_name, input_types,
+
data_set));
+ }
+}
} // namespace doris::vectorized
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 8452c5ad358..1c1a820952f 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
@@ -315,6 +315,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthName;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsAdd;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsBetween;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatch;
@@ -323,6 +324,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiSearchAl
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash332;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Negative;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.NextDay;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NgramSearch;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NonNullable;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NormalCdf;
@@ -414,6 +416,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StX;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StY;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToDate;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToMap;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Strcmp;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubBitmap;
@@ -468,6 +471,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WidthBucket;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Xor;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.XpathString;
import org.apache.doris.nereids.trees.expressions.functions.scalar.XxHash32;
import org.apache.doris.nereids.trees.expressions.functions.scalar.XxHash64;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Year;
@@ -802,6 +806,7 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(MonthFloor.class, "month_floor"),
scalar(MonthName.class, "monthname"),
scalar(MonthsAdd.class, "months_add", "add_months"),
+ scalar(MonthsBetween.class, "months_between"),
scalar(MonthsDiff.class, "months_diff"),
scalar(MonthsSub.class, "months_sub"),
scalar(MultiMatchAny.class, "multi_match_any"),
@@ -809,6 +814,7 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(MurmurHash332.class, "murmur_hash3_32"),
scalar(MurmurHash364.class, "murmur_hash3_64"),
scalar(Negative.class, "negative"),
+ scalar(NextDay.class, "next_day"),
scalar(NonNullable.class, "non_nullable"),
scalar(NormalCdf.class, "normal_cdf"),
scalar(NotNullOrEmpty.class, "not_null_or_empty"),
@@ -907,6 +913,7 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(StartsWith.class, "starts_with"),
scalar(Strcmp.class, "strcmp"),
scalar(StrToDate.class, "str_to_date"),
+ scalar(StrToMap.class, "str_to_map"),
scalar(SubBitmap.class, "sub_bitmap"),
scalar(SubReplace.class, "sub_replace"),
scalar(Substring.class, "substr", "substring"),
@@ -962,6 +969,7 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(XxHash32.class, "xxhash_32"),
scalar(XxHash64.class, "xxhash_64"),
scalar(Xor.class, "xor"),
+ scalar(XpathString.class, "xpath_string"),
scalar(Year.class, "year"),
scalar(YearCeil.class, "year_ceil"),
scalar(YearFloor.class, "year_floor"),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
index 0be185d80f9..568740f9d35 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
@@ -24,15 +24,18 @@ import
org.apache.doris.nereids.rules.expression.rules.SupportJavaDateFormatter;
import org.apache.doris.nereids.trees.expressions.ExecFunction;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
import org.apache.doris.nereids.trees.expressions.literal.DateV2Literal;
import org.apache.doris.nereids.trees.expressions.literal.DecimalV3Literal;
+import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
import org.apache.doris.nereids.trees.expressions.literal.SmallIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
import org.apache.doris.nereids.types.DataType;
@@ -44,11 +47,13 @@ import org.apache.doris.nereids.types.VarcharType;
import org.apache.doris.nereids.util.DateUtils;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -58,6 +63,7 @@ import java.time.format.ResolverStyle;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
+import java.util.HashMap;
import java.util.Locale;
/**
@@ -65,6 +71,33 @@ import java.util.Locale;
* year, quarter, month, week, dayOfYear, dayOfweek, dayOfMonth, hour, minute,
second, microsecond
*/
public class DateTimeExtractAndTransform {
+
+ private static final HashMap<String, Integer> DAY_OF_WEEK = new
HashMap<>();
+
+ static {
+ DAY_OF_WEEK.put("MO", 1);
+ DAY_OF_WEEK.put("MON", 1);
+ DAY_OF_WEEK.put("MONDAY", 1);
+ DAY_OF_WEEK.put("TU", 2);
+ DAY_OF_WEEK.put("TUE", 2);
+ DAY_OF_WEEK.put("TUESDAY", 2);
+ DAY_OF_WEEK.put("WE", 3);
+ DAY_OF_WEEK.put("WED", 3);
+ DAY_OF_WEEK.put("WEDNESDAY", 3);
+ DAY_OF_WEEK.put("TH", 4);
+ DAY_OF_WEEK.put("THU", 4);
+ DAY_OF_WEEK.put("THURSDAY", 4);
+ DAY_OF_WEEK.put("FR", 5);
+ DAY_OF_WEEK.put("FRI", 5);
+ DAY_OF_WEEK.put("FRIDAY", 5);
+ DAY_OF_WEEK.put("SA", 6);
+ DAY_OF_WEEK.put("SAT", 6);
+ DAY_OF_WEEK.put("SATURDAY", 6);
+ DAY_OF_WEEK.put("SU", 7);
+ DAY_OF_WEEK.put("SUN", 7);
+ DAY_OF_WEEK.put("SUNDAY", 7);
+ }
+
/**
* datetime arithmetic function date-v2
*/
@@ -1185,4 +1218,49 @@ public class DateTimeExtractAndTransform {
public static Expression yearsDiff(DateTimeLiteral t1, DateTimeLiteral t2)
{
return new BigIntLiteral(ChronoUnit.YEARS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
}
+
+ /**
+ * months_between(date1, date2, round_off)
+ */
+ @ExecFunction(name = "months_between")
+ public static Expression monthsBetween(DateV2Literal t1, DateV2Literal t2,
BooleanLiteral roundOff) {
+ long yearBetween = t1.getYear() - t2.getYear();
+ long monthBetween = t1.getMonth() - t2.getMonth();
+ int daysInMonth1 = YearMonth.of((int) t1.getYear(), (int)
t1.getMonth()).lengthOfMonth();
+ int daysInMonth2 = YearMonth.of((int) t2.getYear(), (int)
t2.getMonth()).lengthOfMonth();
+ double dayBetween = 0;
+ if (t1.getDay() == daysInMonth1 && t2.getDay() == daysInMonth2) {
+ dayBetween = 0;
+ } else {
+ dayBetween = (t1.getDay() - t2.getDay()) / 31.0;
+ }
+ double result = yearBetween * 12 + monthBetween + dayBetween;
+ // rounded to 8 digits unless roundOff=false.
+ if (roundOff.getValue()) {
+ result = new BigDecimal(result).setScale(8,
RoundingMode.HALF_UP).doubleValue();
+ }
+ return new DoubleLiteral(result);
+ }
+
+ private static int getDayOfWeek(String day) {
+ Integer dayOfWeek = DAY_OF_WEEK.get(day.toUpperCase());
+ if (dayOfWeek == null) {
+ return 0;
+ }
+ return dayOfWeek;
+ }
+
+ /**
+ * date arithmetic function next_day
+ */
+ @ExecFunction(name = "next_day")
+ public static Expression nextDay(DateV2Literal date, StringLiteral day) {
+ int dayOfWeek = getDayOfWeek(day.getValue());
+ if (dayOfWeek == 0) {
+ throw new RuntimeException("Invalid day of week: " +
day.getValue());
+ }
+ int daysToAdd = (dayOfWeek - date.getDayOfWeek() + 7) % 7;
+ daysToAdd = daysToAdd == 0 ? 7 : daysToAdd;
+ return date.plusDays(daysToAdd);
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MonthsBetween.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MonthsBetween.java
new file mode 100644
index 00000000000..81d451602d8
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MonthsBetween.java
@@ -0,0 +1,83 @@
+// 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.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullableOnDateLikeV2Args;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.shape.TernaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.BooleanType;
+import org.apache.doris.nereids.types.DateV2Type;
+import org.apache.doris.nereids.types.DoubleType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * ScalarFunction 'months_between'.
+ */
+public class MonthsBetween extends ScalarFunction
+ implements TernaryExpression, ExplicitlyCastableSignature,
PropagateNullableOnDateLikeV2Args {
+
+ private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(DoubleType.INSTANCE).args(DateV2Type.INSTANCE,
DateV2Type.INSTANCE,
+ BooleanType.INSTANCE),
+
FunctionSignature.ret(DoubleType.INSTANCE).args(DateV2Type.INSTANCE,
DateV2Type.INSTANCE));
+
+ /**
+ * constructor with 2 arguments.
+ */
+ public MonthsBetween(Expression arg0, Expression arg1) {
+ super("months_between", arg0, arg1, Literal.of(true));
+ }
+
+ /**
+ * constructor with 3 arguments.
+ */
+ public MonthsBetween(Expression arg0, Expression arg1, Expression arg2) {
+ super("months_between", arg0, arg1, arg2);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public MonthsBetween withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 2 || children.size() ==
3);
+ if (children.size() == 2) {
+ return new MonthsBetween(children.get(0), children.get(1));
+ } else {
+ return new MonthsBetween(children.get(0), children.get(1),
children.get(2));
+ }
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitMonthsBetween(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/NextDay.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/NextDay.java
new file mode 100644
index 00000000000..7e30e4ffd24
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/NextDay.java
@@ -0,0 +1,74 @@
+// 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.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullableOnDateLikeV2Args;
+import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.DateV2Type;
+import org.apache.doris.nereids.types.StringType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * ScalarFunction 'next_day'.
+ * next_day(expr, dayOfWeek)
+ * - expr: A DATE expression.
+ * - dayOfWeek: A STRING expression identifying a day of the week.
+ * Returns the first DATE that is later than expr and has the same day of the
+ * week as dayOfWeek.
+ * dayOfWeek must be one of the following (case insensitive):
+ * 'SU', 'SUN', 'SUNDAY'
+ * 'MO', 'MON', 'MONDAY'
+ * 'TU', 'TUE', 'TUESDAY'
+ * 'WE', 'WED', 'WEDNESDAY'
+ * 'TH', 'THU', 'THURSDAY'
+ * 'FR', 'FRI', 'FRIDAY'
+ * 'SA', 'SAT', 'SATURDAY'
+ */
+public class NextDay extends ScalarFunction
+ implements BinaryExpression, ExplicitlyCastableSignature,
PropagateNullableOnDateLikeV2Args {
+ private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(DateV2Type.INSTANCE).args(DateV2Type.INSTANCE,
StringType.INSTANCE));
+
+ public NextDay(Expression arg0, Expression arg1) {
+ super("next_day", arg0, arg1);
+ }
+
+ @Override
+ public NextDay withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 2);
+ return new NextDay(children.get(0), children.get(1));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitNextDay(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StrToMap.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StrToMap.java
new file mode 100644
index 00000000000..89df45d01c0
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StrToMap.java
@@ -0,0 +1,110 @@
+// 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.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.MapType;
+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 'str_to_map'.
+ * str_to_map(expr [, pairDelim [, keyValueDelim] ] )
+ * - expr: An STRING expression.
+ * - pairDelim: An optional STRING literal defaulting to ',' that specifies how
+ * to split entries.
+ * - keyValueDelim: An optional STRING literal defaulting to ':' that specifies
+ * how to split each key-value pair.
+ * Returns:
+ * A MAP of STRING for both keys and values.
+ * Both pairDelim and keyValueDelim are treated as regular expressions.
+ */
+public class StrToMap extends ScalarFunction
+ implements ExplicitlyCastableSignature, PropagateNullable {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE))
+ .args(VarcharType.SYSTEM_DEFAULT),
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE)).args(VarcharType.SYSTEM_DEFAULT,
+ VarcharType.SYSTEM_DEFAULT),
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE)).args(VarcharType.SYSTEM_DEFAULT,
+ VarcharType.SYSTEM_DEFAULT,
+ VarcharType.SYSTEM_DEFAULT),
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE)).args(StringType.INSTANCE),
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE)).args(StringType.INSTANCE,
+ StringType.INSTANCE),
+ FunctionSignature.ret(MapType.of(StringType.INSTANCE,
StringType.INSTANCE)).args(StringType.INSTANCE,
+ StringType.INSTANCE,
+ StringType.INSTANCE));
+
+ /**
+ * constructor with 1 arguments.
+ */
+ public StrToMap(Expression arg0) {
+ super("str_to_map", arg0, Literal.of(","), Literal.of(":"));
+ }
+
+ /**
+ * constructor with 2 arguments.
+ */
+ public StrToMap(Expression arg0, Expression arg1) {
+ super("str_to_map", arg0, arg1, Literal.of(":"));
+ }
+
+ /**
+ * constructor with 3 arguments.
+ */
+ public StrToMap(Expression arg0, Expression arg1, Expression arg2) {
+ super("str_to_map", arg0, arg1, arg2);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public StrToMap withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1 || children.size() ==
2 || children.size() == 3);
+ if (children.size() == 1) {
+ return new StrToMap(children.get(0));
+ } else if (children.size() == 2) {
+ return new StrToMap(children.get(0), children.get(1));
+ } else {
+ return new StrToMap(children.get(0), children.get(1),
children.get(2));
+ }
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitStrToMap(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/XpathString.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/XpathString.java
new file mode 100644
index 00000000000..734af357d71
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/XpathString.java
@@ -0,0 +1,72 @@
+// 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.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+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 'xpath_string'.
+ */
+public class XpathString extends ScalarFunction
+ implements BinaryExpression, ExplicitlyCastableSignature,
AlwaysNullable {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT)
+ .args(VarcharType.SYSTEM_DEFAULT,
VarcharType.SYSTEM_DEFAULT),
+ FunctionSignature.ret(StringType.INSTANCE)
+ .args(StringType.INSTANCE, StringType.INSTANCE)
+ );
+
+ /**
+ * constructor with 2 arguments.
+ */
+ public XpathString(Expression arg0, Expression arg1) {
+ super("xpath_string", arg0, arg1);
+ }
+
+ /**
+ * withChildren.
+ */
+ @Override
+ public XpathString withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 2);
+ return new XpathString(children.get(0), children.get(1));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitXpathString(this, context);
+ }
+}
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 2890787ddb7..b061e2f8d6a 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
@@ -316,6 +316,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthCeil;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthFloor;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthName;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsAdd;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsBetween;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MultiMatch;
@@ -324,6 +325,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.MultiSearchAl
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash332;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.MurmurHash364;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Negative;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.NextDay;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NgramSearch;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NormalCdf;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.NotNullOrEmpty;
@@ -412,6 +414,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StX;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StY;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToDate;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToMap;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Strcmp;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubBitmap;
@@ -465,6 +468,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksDiff;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksSub;
import org.apache.doris.nereids.trees.expressions.functions.scalar.WidthBucket;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Xor;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.XpathString;
import org.apache.doris.nereids.trees.expressions.functions.scalar.XxHash32;
import org.apache.doris.nereids.trees.expressions.functions.scalar.XxHash64;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Year;
@@ -1084,6 +1088,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(monthsAdd, context);
}
+ default R visitMonthsBetween(MonthsBetween monthsBetween, C context) {
+ return visitScalarFunction(monthsBetween, context);
+ }
+
default R visitYearsAdd(YearsAdd yearsAdd, C context) {
return visitScalarFunction(yearsAdd, context);
}
@@ -1668,6 +1676,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(negative, context);
}
+ default R visitNextDay(NextDay nextDay, C context) {
+ return visitScalarFunction(nextDay, context);
+ }
+
default R visitNgramSearch(NgramSearch ngramSearch, C context) {
return visitScalarFunction(ngramSearch, context);
}
@@ -2024,6 +2036,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(strToDate, context);
}
+ default R visitStrToMap(StrToMap strToMap, C context) {
+ return visitScalarFunction(strToMap, context);
+ }
+
default R visitStringRegexPredicate(StringRegexPredicate
stringRegexPredicate, C context) {
return visitScalarFunction(stringRegexPredicate, context);
}
@@ -2302,6 +2318,10 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(xor, context);
}
+ default R visitXpathString(XpathString xpathString, C context) {
+ return visitScalarFunction(xpathString, context);
+ }
+
// struct function
default R visitCreateStruct(CreateStruct createStruct, C context) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java
index b8de53b78fe..55c7b279cd3 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/expression/FoldConstantTest.java
@@ -55,6 +55,8 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.Ln;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Locate;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Log;
import org.apache.doris.nereids.trees.expressions.functions.scalar.MinutesAdd;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MonthsBetween;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.NextDay;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Overlay;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Power;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.ReplaceEmpty;
@@ -70,6 +72,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.Tan;
import org.apache.doris.nereids.trees.expressions.functions.scalar.ToDays;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.UnixTimestamp;
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
@@ -276,6 +279,60 @@ class FoldConstantTest extends ExpressionRewriteTestHelper
{
toDays = new
ToDays(DateV2Literal.fromJavaDateType(LocalDateTime.of(9999, 12, 31, 1, 1, 1)));
rewritten = executor.rewrite(toDays, context);
Assertions.assertEquals(new IntegerLiteral(3652424), rewritten);
+
+ MonthsBetween monthsBetween = new MonthsBetween(
+ DateV2Literal.fromJavaDateType(LocalDateTime.of(1, 1,
1, 1, 1, 1)),
+ DateV2Literal.fromJavaDateType(LocalDateTime.of(2, 2,
1, 1, 1, 1)));
+ rewritten = executor.rewrite(monthsBetween, context);
+ Assertions.assertEquals(new DoubleLiteral(-13.0), rewritten);
+ monthsBetween = new
MonthsBetween(DateV2Literal.fromJavaDateType(LocalDateTime.of(1, 1, 1, 1, 1,
1)),
+ DateV2Literal.fromJavaDateType(LocalDateTime.of(2, 2,
1, 1, 1, 1)), BooleanLiteral.FALSE);
+ rewritten = executor.rewrite(monthsBetween, context);
+ Assertions.assertEquals(new DoubleLiteral(-13.0), rewritten);
+ monthsBetween = new
MonthsBetween(DateV2Literal.fromJavaDateType(LocalDateTime.of(2024, 3, 31, 1,
1, 1)),
+ DateV2Literal.fromJavaDateType(LocalDateTime.of(2024,
2, 29, 1, 1, 1)));
+ rewritten = executor.rewrite(monthsBetween, context);
+ Assertions.assertEquals(new DoubleLiteral(1.0), rewritten);
+ NextDay nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 1, 28, 1, 1, 1)),
+ StringLiteral.of("MON"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-02-03"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 1, 31, 1, 1, 1)),
+ StringLiteral.of("SAT"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-02-01"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 12, 28, 1, 1, 1)),
+ StringLiteral.of("FRI"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2021-01-01"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 12, 31, 1, 1, 1)),
+ StringLiteral.of("THU"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2021-01-07"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 2, 27, 1, 1, 1)),
+ StringLiteral.of("SAT"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-02-29"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 2, 29, 1, 1, 1)),
+ StringLiteral.of("MON"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-03-02"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2019, 2, 26, 1, 1, 1)),
+ StringLiteral.of("THU"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2019-02-28"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2019, 2, 28, 1, 1, 1)),
+ StringLiteral.of("SUN"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2019-03-03"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 4, 29, 1, 1, 1)),
+ StringLiteral.of("FRI"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-05-01"), rewritten);
+ nextDay = new
NextDay(DateV2Literal.fromJavaDateType(LocalDateTime.of(2020, 5, 31, 1, 1, 1)),
+ StringLiteral.of("MON"));
+ rewritten = executor.rewrite(nextDay, context);
+ Assertions.assertEquals(new DateV2Literal("2020-06-01"), rewritten);
}
@Test
diff --git
a/regression-test/data/query_p0/sql_functions/string_functions/test_months_between.out
b/regression-test/data/query_p0/sql_functions/string_functions/test_months_between.out
new file mode 100644
index 00000000000..0fddb567d46
Binary files /dev/null and
b/regression-test/data/query_p0/sql_functions/string_functions/test_months_between.out
differ
diff --git
a/regression-test/data/query_p0/sql_functions/string_functions/test_next_day.out
b/regression-test/data/query_p0/sql_functions/string_functions/test_next_day.out
new file mode 100644
index 00000000000..8bdb8678993
Binary files /dev/null and
b/regression-test/data/query_p0/sql_functions/string_functions/test_next_day.out
differ
diff --git
a/regression-test/data/query_p0/sql_functions/string_functions/test_str_to_map.out
b/regression-test/data/query_p0/sql_functions/string_functions/test_str_to_map.out
new file mode 100644
index 00000000000..6dd44129806
Binary files /dev/null and
b/regression-test/data/query_p0/sql_functions/string_functions/test_str_to_map.out
differ
diff --git
a/regression-test/data/query_p0/sql_functions/string_functions/test_xpath_string.out
b/regression-test/data/query_p0/sql_functions/string_functions/test_xpath_string.out
new file mode 100644
index 00000000000..3451f0737f4
Binary files /dev/null and
b/regression-test/data/query_p0/sql_functions/string_functions/test_xpath_string.out
differ
diff --git
a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_date_arithmatic.groovy
b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_date_arithmatic.groovy
index bae40fd6796..a78067259dd 100644
---
a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_date_arithmatic.groovy
+++
b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_date_arithmatic.groovy
@@ -49,4 +49,33 @@ suite("fold_constant_date_arithmatic") {
testFoldConst("select str_to_date('31/12/2020 11:59 PM', '%d/%m/%Y %h:%i
%p');")
testFoldConst("select str_to_date('20201231T235959', '%Y%m%dT%H%i%s');")
+ // test leap year and leap month edge cases
+ testFoldConst("select months_between('2020-02-29', '2020-02-29')")
+ testFoldConst("select months_between('2020-02-29', '2020-03-29')")
+ testFoldConst("select months_between('2020-01-29', '2020-02-29')")
+ testFoldConst("select months_between('2020-02-29', '2021-02-28')")
+ testFoldConst("select months_between('2019-02-28', '2020-02-29')")
+
+ // test with time components in leap year
+ testFoldConst("select months_between('2020-02-29 12:00:00', '2020-02-29
15:00:00')")
+ testFoldConst("select months_between('2020-02-29 23:59:59', '2020-03-29
00:00:00')")
+
+ // test with different round_off settings in leap year
+ testFoldConst("select months_between('2020-02-29', '2020-03-30', true)")
+ testFoldConst("select months_between('2020-02-29', '2020-03-30', false)")
+
+ // test across multiple leap years
+ testFoldConst("select months_between('2020-02-29', '2024-02-29')")
+ testFoldConst("select months_between('2020-02-29 23:59:59', '2024-02-29
00:00:00')")
+
+ // test case with last day of the month
+ testFoldConst("select months_between('2024-03-31', '2024-02-29')")
+ testFoldConst("select months_between('2024-03-30', '2024-02-29')")
+ testFoldConst("select months_between('2024-03-29', '2024-02-29')")
+
+ // Test next_day
+ testFoldConst("select next_day('2020-02-27', 'SAT');") // 2020-02-29 (leap
year)
+ testFoldConst("select next_day('2020-02-29', 'MON');") // 2020-03-02 (leap
year to next month)
+ testFoldConst("select next_day('2019-02-26', 'THU');") // 2019-02-28
(non-leap year)
+ testFoldConst("select next_day('2019-02-28', 'SUN');") // 2019-03-03
(non-leap year to next month)
}
diff --git
a/regression-test/suites/query_p0/sql_functions/string_functions/test_months_between.groovy
b/regression-test/suites/query_p0/sql_functions/string_functions/test_months_between.groovy
new file mode 100644
index 00000000000..aa760d97838
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/string_functions/test_months_between.groovy
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_months_between") {
+ sql "drop table if exists months_between_args;"
+ sql """
+ create table months_between_args (
+ k0 int,
+ date1_not_null datev2 not null,
+ date2_not_null datev2 not null,
+ datetime1_not_null datetimev2 not null,
+ datetime2_not_null datetimev2 not null,
+ round_off_not_null boolean not null,
+ date1_null datev2 null,
+ date2_null datev2 null,
+ datetime1_null datetimev2 null,
+ datetime2_null datetimev2 null,
+ round_off_null boolean null
+ )
+ DISTRIBUTED BY HASH(k0)
+ PROPERTIES
+ (
+ "replication_num" = "1"
+ );
+ """
+
+ order_qt_empty_nullable "select months_between(date1_null, date2_null),
months_between(date1_null, date2_null, round_off_null),
months_between(datetime1_null, datetime2_null, round_off_null),
months_between(date1_null, datetime2_null, round_off_null),
months_between(datetime1_null, date2_null, round_off_null) from
months_between_args"
+ order_qt_empty_not_nullable "select months_between(date1_not_null,
date2_not_null), months_between(date1_not_null, date2_not_null,
round_off_not_null), months_between(datetime1_not_null, datetime2_not_null,
round_off_not_null), months_between(date1_not_null, datetime2_not_null,
round_off_not_null), months_between(datetime1_not_null, date2_not_null,
round_off_not_null) from months_between_args"
+ order_qt_empty_partial_nullable "select months_between(date1_null,
date2_not_null), months_between(date1_not_null, date2_null),
months_between(date1_not_null, date2_not_null), months_between(datetime1_null,
datetime2_not_null), months_between(datetime1_not_null, date2_null),
months_between(datetime1_not_null, date2_not_null) from months_between_args"
+ order_qt_empty_nullable_no_null "select months_between(date1_null,
nullable(date2_not_null)), months_between(nullable(date1_not_null),
date2_null), months_between(date1_null, nullable(datetime2_not_null)),
months_between(nullable(datetime1_not_null), date2_null),
months_between(datetime1_null, nullable(date2_not_null)),
months_between(nullable(datetime1_not_null), datetime2_null) from
months_between_args"
+
+ sql "insert into months_between_args values (1, '2020-01-01',
'2020-02-01', '2020-01-01 00:00:00', '2020-02-01 00:00:00', true, null, null,
null, null, null)"
+
+ order_qt_nullable "select months_between(date1_null, date2_null),
months_between(date1_null, date2_null, round_off_null),
months_between(datetime1_null, datetime2_null, round_off_null),
months_between(date1_null, datetime2_null, round_off_null),
months_between(datetime1_null, date2_null, round_off_null) from
months_between_args"
+ order_qt_not_nullable "select months_between(date1_not_null,
date2_not_null), months_between(date1_not_null, date2_not_null,
round_off_not_null), months_between(datetime1_not_null, datetime2_not_null,
round_off_not_null), months_between(date1_not_null, datetime2_not_null,
round_off_not_null), months_between(datetime1_not_null, date2_not_null,
round_off_not_null) from months_between_args"
+ order_qt_partial_nullable "select months_between(date1_null,
date2_not_null), months_between(date1_not_null, date2_null),
months_between(date1_not_null, date2_not_null), months_between(datetime1_null,
datetime2_not_null), months_between(datetime1_not_null, date2_null),
months_between(datetime1_not_null, date2_not_null) from months_between_args"
+ order_qt_nullable_no_null "select months_between(date1_null,
nullable(date2_not_null)), months_between(nullable(date1_not_null),
date2_null), months_between(date1_null, nullable(datetime2_not_null)),
months_between(nullable(datetime1_not_null), date2_null),
months_between(datetime1_null, nullable(date2_not_null)),
months_between(nullable(datetime1_not_null), datetime2_null) from
months_between_args"
+
+ sql "truncate table months_between_args"
+
+ sql """
+ insert into months_between_args values
+ (1, '2020-01-01', '2020-02-01', '2020-01-01 00:00:00', '2020-02-01
00:00:00', true, null, null, null, null, null),
+ (2, '2020-01-01', '2020-02-01', '2020-01-01 00:00:00', '2020-02-01
00:00:00', false, '2020-01-01', '2020-02-01', '2020-01-01 00:00:00',
'2020-02-01 00:00:00', false),
+ (3, '2020-01-31', '2020-02-29', '2020-01-31 23:59:59', '2020-02-29
23:59:59', true, '2020-01-31', '2020-02-29', '2020-01-31 23:59:59', '2020-02-29
23:59:59', true),
+ (4, '2020-12-31', '2021-01-31', '2020-12-31 00:00:00', '2021-01-31
00:00:00', false, null, '2021-01-31', null, '2021-01-31 00:00:00', false),
+ (5, '1900-01-01', '2100-12-31', '1900-01-01 00:00:00', '2100-12-31
23:59:59', true, '1900-01-01', null, '1900-01-01 00:00:00', null, true),
+ (6, '2020-02-29', '2020-02-29', '2020-02-29 12:00:00', '2020-02-29
12:00:00', false, '2020-02-29', '2020-02-29', null, '2020-02-29 12:00:00',
null),
+ (7, '2020-01-15', '2020-02-15', '2020-01-15 15:30:00', '2020-02-15
15:30:00', true, null, null, '2020-01-15 15:30:00', '2020-02-15 15:30:00',
true),
+ (8, '2019-12-31', '2020-01-31', '2019-12-31 23:59:59', '2020-01-31
00:00:00', false, '2019-12-31', null, null, null, null),
+ (9, '2020-06-30', '2020-07-01', '2020-06-30 23:59:59', '2020-07-01
00:00:00', true, null, '2020-07-01', '2020-06-30 23:59:59', null, true),
+ (10, '2020-02-28', '2021-02-28', '2020-02-28 00:00:00', '2021-02-28
00:00:00', false, '2020-02-28', '2021-02-28', null, '2021-02-28 00:00:00',
false)
+ """
+
+ /// nullables
+ order_qt_nullable "select months_between(date1_null, date2_null),
months_between(date1_null, date2_null, round_off_null),
months_between(datetime1_null, datetime2_null, round_off_null),
months_between(date1_null, datetime2_null, round_off_null),
months_between(datetime1_null, date2_null, round_off_null) from
months_between_args"
+ order_qt_not_nullable "select months_between(date1_not_null,
date2_not_null), months_between(date1_not_null, date2_not_null,
round_off_not_null), months_between(datetime1_not_null, datetime2_not_null,
round_off_not_null), months_between(date1_not_null, datetime2_not_null,
round_off_not_null), months_between(datetime1_not_null, date2_not_null,
round_off_not_null) from months_between_args"
+ order_qt_partial_nullable "select months_between(date1_null,
date2_not_null), months_between(date1_not_null, date2_null),
months_between(date1_not_null, date2_not_null), months_between(datetime1_null,
datetime2_not_null), months_between(datetime1_not_null, date2_null),
months_between(datetime1_not_null, date2_not_null) from months_between_args"
+ order_qt_nullable_no_null "select months_between(date1_null,
nullable(date2_not_null)), months_between(nullable(date1_not_null),
date2_null), months_between(date1_null, nullable(datetime2_not_null)),
months_between(nullable(datetime1_not_null), date2_null),
months_between(datetime1_null, nullable(date2_not_null)),
months_between(nullable(datetime1_not_null), datetime2_null) from
months_between_args"
+
+ /// consts. most by BE-UT
+ order_qt_const_nullable "select months_between(NULL, NULL),
months_between(NULL, NULL, NULL), months_between(NULL, NULL, NULL),
months_between(NULL, NULL, NULL), months_between(NULL, NULL, NULL) from
months_between_args"
+ order_qt_partial_const_nullable "select months_between(NULL,
date2_not_null), months_between(date1_not_null, NULL), months_between(NULL,
datetime2_not_null, round_off_not_null), months_between(datetime1_not_null,
NULL, round_off_not_null) from months_between_args"
+ order_qt_const_not_nullable "select months_between('2020-01-01',
'2020-02-01'), months_between('2020-01-01', '2020-02-01', true),
months_between('2020-01-01 00:00:00', '2020-02-01 00:00:00', false),
months_between('2020-01-01', '2020-02-01 00:00:00', true),
months_between('2020-01-01 00:00:00', '2020-02-01', false) from
months_between_args"
+ order_qt_const_other_nullable "select months_between('2020-01-01',
date2_null), months_between(date1_null, '2020-02-01'),
months_between('2020-01-01', datetime2_null, round_off_null),
months_between(datetime1_null, '2020-02-01', round_off_null) from
months_between_args"
+ order_qt_const_other_not_nullable "select months_between('2020-01-01',
date2_not_null), months_between(date1_not_null, '2020-02-01'),
months_between('2020-01-01', datetime2_not_null, round_off_not_null),
months_between(datetime1_not_null, '2020-02-01', round_off_not_null) from
months_between_args"
+ order_qt_const_nullable_no_null "select
months_between(nullable('2020-01-01'), nullable('2020-02-01')),
months_between(nullable('2020-01-01'), nullable('2020-02-01'), nullable(true)),
months_between(nullable('2020-01-01 00:00:00'), nullable('2020-02-01
00:00:00'), nullable(false)) from months_between_args"
+ order_qt_const_partial_nullable_no_null "select
months_between('2020-01-01', nullable('2020-02-01')),
months_between(nullable('2020-01-01'), '2020-02-01'),
months_between('2020-01-01', nullable('2020-02-01 00:00:00'), nullable(true))
from months_between_args"
+ order_qt_const1 "select months_between('2020-01-01', date2_not_null),
months_between('2020-01-01', date2_not_null, round_off_not_null) from
months_between_args"
+ order_qt_const12 "select months_between('2020-01-01', '2020-02-01',
round_off_not_null) from months_between_args"
+ order_qt_const23 "select months_between(date1_not_null, '2020-02-01',
true) from months_between_args"
+ order_qt_const3 "select months_between(date1_not_null, date2_not_null,
true) from months_between_args"
+
+ /// test simple cases with date and datetime combinations
+ order_qt_date_date "select months_between('2020-01-01', '2020-02-01')"
+ order_qt_date_datetime "select months_between('2020-01-01', '2020-02-01
00:00:00')"
+ order_qt_datetime_date "select months_between(cast('2020-01-01 00:00:00'
as datetimev2), cast('2020-02-01' as datev2))"
+ order_qt_datetime_datetime "select months_between(cast('2020-01-01
00:00:00' as datetimev2), cast('2020-02-01 00:00:00' as datetimev2))"
+
+ order_qt_date_date_round "select months_between('2020-01-15',
'2020-02-15', true)"
+ order_qt_date_datetime_round "select months_between('2020-01-15',
'2020-02-15 12:00:00', true)"
+ order_qt_datetime_date_round "select months_between(cast('2020-01-15
12:00:00' as datetimev2), cast('2020-02-15' as datev2), true)"
+ order_qt_datetime_datetime_round "select months_between(cast('2020-01-15
12:00:00' as datetimev2), cast('2020-02-15 12:00:00' as datetimev2), true)"
+
+ order_qt_date_date_no_round "select months_between('2020-01-31',
'2020-02-29', false)"
+ order_qt_date_datetime_no_round "select months_between('2020-01-31',
'2020-02-29 23:59:59', false)"
+ order_qt_datetime_date_no_round "select months_between(cast('2020-01-31
23:59:59' as datetimev2), cast('2020-02-29' as datev2), false)"
+ order_qt_datetime_datetime_no_round "select
months_between(cast('2020-01-31 23:59:59' as datetimev2), cast('2020-02-29
23:59:59' as datetimev2), false)"
+
+ // test leap year and leap month edge cases
+ order_qt_leap_year_same "select months_between('2020-02-29', '2020-02-29')"
+ order_qt_leap_year_next "select months_between('2020-02-29', '2020-03-29')"
+ order_qt_leap_year_prev "select months_between('2020-01-29', '2020-02-29')"
+ order_qt_leap_year_next_year "select months_between('2020-02-29',
'2021-02-28')"
+ order_qt_leap_year_prev_year "select months_between('2019-02-28',
'2020-02-29')"
+
+ // test with time components in leap year
+ order_qt_leap_year_time "select months_between('2020-02-29 12:00:00',
'2020-02-29 15:00:00')"
+ order_qt_leap_year_time_next "select months_between('2020-02-29 23:59:59',
'2020-03-29 00:00:00')"
+
+ // test with different round_off settings in leap year
+ order_qt_leap_year_round "select months_between('2020-02-29',
'2020-03-30', true)"
+ order_qt_leap_year_no_round "select months_between('2020-02-29',
'2020-03-30', false)"
+
+ // test across multiple leap years
+ order_qt_multi_leap_years "select months_between('2020-02-29',
'2024-02-29')"
+ order_qt_multi_leap_years_time "select months_between('2020-02-29
23:59:59', '2024-02-29 00:00:00')"
+
+ // test case with last day of the month
+ order_qt_last_day_of_month1 "select months_between('2024-03-31',
'2024-02-29')"
+ order_qt_last_day_of_month2 "select months_between('2024-03-30',
'2024-02-29')"
+ order_qt_last_day_of_month3 "select months_between('2024-03-29',
'2024-02-29')"
+
+ /// Fold constant
+ check_fold_consistency "months_between('2020-01-01', '2020-02-01')"
+ // Test boundary cases with fold constant
+ check_fold_consistency "months_between('0001-01-01', '0001-01-01')" //
Same date at min year
+ check_fold_consistency "months_between('9999-12-31', '9999-12-31')" //
Same date at max year
+ check_fold_consistency "months_between('0001-01-01', '9999-12-31')" // Min
to max year
+ check_fold_consistency "months_between('9999-12-31', '0001-01-01')" // Max
to min year
+
+ // Test boundary cases with time components
+ check_fold_consistency "months_between('0001-01-01 00:00:00', '0001-01-01
23:59:59')" // Same date different times at min year
+ check_fold_consistency "months_between('9999-12-31 00:00:00', '9999-12-31
23:59:59')" // Same date different times at max year
+
+ // Test boundary cases with round_off
+ check_fold_consistency "months_between('0001-01-01', '0001-02-01', true)"
// Min year with rounding
+ check_fold_consistency "months_between('9999-12-31', '9999-11-30', false)"
// Max year without rounding
+
+ // Test boundary cases with leap years
+ check_fold_consistency "months_between('0004-02-29', '0004-03-01')" //
First leap year
+ check_fold_consistency "months_between('9996-02-29', '9996-03-01')" //
Last leap year
+}
diff --git
a/regression-test/suites/query_p0/sql_functions/string_functions/test_next_day.groovy
b/regression-test/suites/query_p0/sql_functions/string_functions/test_next_day.groovy
new file mode 100644
index 00000000000..c4ba8937971
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/string_functions/test_next_day.groovy
@@ -0,0 +1,129 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_next_day") {
+ sql "drop table if exists next_day_args;"
+ sql """
+ create table next_day_args (
+ k0 int,
+ a date not null,
+ b date null,
+ c datetime not null,
+ d datetime null,
+ e string not null,
+ f string null,
+ )
+ DISTRIBUTED BY HASH(k0)
+ PROPERTIES
+ (
+ "replication_num" = "1"
+ );
+ """
+
+ order_qt_empty_nullable "select next_day(a, e), next_day(c, e) from
next_day_args"
+ order_qt_empty_not_nullable "select next_day(b, f), next_day(d, f) from
next_day_args"
+ order_qt_empty_partial_nullable "select next_day(a, f), next_day(c, f)
from next_day_args"
+
+ sql "insert into next_day_args values (1, '2025-01-01', null, '2025-01-01
00:00:00', null, 'MONDAY', null), (2, '2025-01-02', '2025-01-02', '2025-01-02
00:00:00', '2025-01-02 00:00:00', 'TUESDAY', 'TUESDAY')"
+ order_qt_all_null "select next_day(b, f), next_day(d, f) from
next_day_args"
+
+ sql "truncate table next_day_args"
+
+ sql """
+ insert into next_day_args(k0, a, b, c, d, e, f) values
+ -- normal date
+ (1, '2024-03-15', '2024-03-15', '2024-03-15 10:00:00', '2024-03-15
10:00:00', 'MON', 'MONDAY'),
+
+ -- first week of 0000
+ (2, '0000-01-01', '0000-01-01', '0000-01-01 00:00:00', '0000-01-01
00:00:00', 'SUN', 'SUNDAY'),
+
+ -- last day of 0000
+ (3, '0000-12-31', '0000-12-31', '0000-12-31 23:59:59', '0000-12-31
23:59:59', 'FRI', 'FRIDAY'),
+
+ -- 0000-02-28
+ (4, '0000-02-28', '0000-02-28', '0000-02-28 12:00:00', '0000-02-28
12:00:00', 'MO', 'MONDAY'),
+
+ -- leap year date before and after
+ (5, '2024-02-28', '2024-02-28', '2024-02-28 00:00:00', '2024-02-28
00:00:00', 'WED', 'WEDNESDAY'),
+ (6, '2024-02-29', '2024-02-29', '2024-02-29 00:00:00', '2024-02-29
00:00:00', 'THU', 'THURSDAY'),
+
+ -- non leap year date before and after
+ (7, '2023-02-28', '2023-02-28', '2023-02-28 00:00:00', '2023-02-28
00:00:00', 'TUE', 'TUESDAY'),
+ (8, '2023-03-01', '2023-03-01', '2023-03-01 00:00:00', '2023-03-01
00:00:00', 'WE', 'WEDNESDAY'),
+
+ -- 1900 non leap year date before and after
+ (9, '1900-02-28', '1900-02-28', '1900-02-28 00:00:00', '1900-02-28
00:00:00', 'WED', 'WEDNESDAY'),
+ (10, '1900-03-01', '1900-03-01', '1900-03-01 00:00:00', '1900-03-01
00:00:00', 'THU', 'THURSDAY'),
+
+ -- last second of 9999
+ (11, '9999-12-31', '9999-12-31', '9999-12-31 23:59:59', '9999-12-31
23:59:59', 'FRI', 'FRIDAY'),
+
+ -- first second of 1970
+ (12, '1969-12-31', '1969-12-31', '1969-12-31 23:59:59', '1969-12-31
23:59:59', 'WED', 'WEDNESDAY'),
+ (13, '1970-01-01', '1970-01-01', '1970-01-01 00:00:00', '1970-01-01
00:00:00', 'THU', 'THURSDAY');
+ """
+
+ order_qt_nullable "select next_day(b, f), next_day(d, f) from
next_day_args"
+ order_qt_not_nullable "select next_day(a, e), next_day(c, e) from
next_day_args"
+ order_qt_partial_nullable "select next_day(a, f), next_day(c, f),
next_day(b, e), next_day(d, e) from next_day_args"
+ order_qt_nullable_no_null "select next_day(a, nullable(e)), next_day(c,
nullable(e)) from next_day_args"
+
+ /// consts. most by BE-UT
+ order_qt_const_nullable "select next_day(NULL, NULL) from next_day_args"
+ order_qt_partial_const_nullable "select next_day(NULL, e) from
next_day_args"
+ order_qt_const_not_nullable "select next_day('2025-01-01', 'MONDAY') from
next_day_args"
+ order_qt_const_other_nullable "select next_day('2025-01-01', f) from
next_day_args"
+ order_qt_const_other_not_nullable "select next_day(a, 'FRI') from
next_day_args"
+ order_qt_const_nullable_no_null "select next_day(nullable('2025-01-01'),
nullable('MON'))"
+ order_qt_const_nullable_no_null_multirows "select next_day(nullable(c),
nullable(e)) from next_day_args"
+ order_qt_const_partial_nullable_no_null "select next_day('2025-01-01',
nullable(e)) from next_day_args"
+
+ /// folding
+ check_fold_consistency "next_day('', 'SA')"
+ check_fold_consistency "next_day(NULL, NULL)"
+ check_fold_consistency "next_day(NULL, 'FRI')"
+ check_fold_consistency "next_day('2025-01-01', NULL)"
+ check_fold_consistency "next_day('2025-01-01', 'MONDAY')"
+ check_fold_consistency "next_day(nullable('2025-01-01'), nullable('SUN'))"
+ check_fold_consistency "next_day('2025-01-01', nullable('WE'))"
+ check_fold_consistency "next_day(nullable('2025-01-01'), 'TH')"
+ check_fold_consistency "next_day(nullable('2025-01-01 12:34:56'),
'MONDAY')"
+ // test cast date/datetime to date/datetime
+ check_fold_consistency "next_day(cast('2025-01-01' as date), 'MONDAY')"
+ check_fold_consistency "next_day(cast('2025-01-01' as datetime), 'MONDAY')"
+ check_fold_consistency "next_day(cast('2025-01-01 12:34:56' as date),
'MONDAY')"
+ check_fold_consistency "next_day(cast('2025-01-01 12:34:56' as datetime),
'MONDAY')"
+
+ /// wrong date
+ order_qt_wrong_date "select next_day('2025-02-31', 'MONDAY')"
+ order_qt_wrong_date "select next_day('2025-02-29', 'MONDAY')"
+ order_qt_wrong_date "select next_day('2025-03-32', 'MONDAY')"
+
+ /// error cases
+ test{
+ sql """ select next_day('2025-01-01', 'SO') """
+ exception "Function next_day failed to parse weekday: SO"
+ }
+ test{
+ sql """ select next_day('2025-01-01', 'MONDDY') """
+ exception "Function next_day failed to parse weekday: MONDDY"
+ }
+ test{
+ sql """ select next_day('2025-01-01', '') """
+ exception "Function next_day failed to parse weekday: "
+ }
+}
diff --git
a/regression-test/suites/query_p0/sql_functions/string_functions/test_str_to_map.groovy
b/regression-test/suites/query_p0/sql_functions/string_functions/test_str_to_map.groovy
new file mode 100644
index 00000000000..e11e2310b73
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/string_functions/test_str_to_map.groovy
@@ -0,0 +1,232 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_str_to_map") {
+ sql "drop table if exists str_to_map_args;"
+ sql """
+ create table str_to_map_args (
+ k0 int,
+ map_str_not_null string not null,
+ map_str_null string null,
+ key_delim_not_null string not null,
+ key_delim_null string null,
+ value_delim_not_null string not null,
+ value_delim_null string null
+ )
+ DISTRIBUTED BY HASH(k0)
+ PROPERTIES
+ (
+ "replication_num" = "1"
+ );
+ """
+
+ // Test empty table with different nullable combinations
+ order_qt_all_not_null "select str_to_map(map_str_not_null,
key_delim_not_null, value_delim_not_null) from str_to_map_args"
+
+ order_qt_all_args_null "select str_to_map(map_str_null, key_delim_null,
value_delim_null) from str_to_map_args"
+
+ order_qt_partial_null "select str_to_map(map_str_not_null, key_delim_null,
value_delim_null) from str_to_map_args"
+
+ order_qt_nullable_no_null "select str_to_map(nullable(map_str_not_null),
nullable(key_delim_not_null), nullable(value_delim_not_null)) from
str_to_map_args"
+
+ sql '''
+ insert into str_to_map_args values
+ (1, 'a:1,b:2,c:3', 'a:1,b:2,c:3', ',', ',', ':', ':'),
+ (2, '', '', ',', ',', ':', ':'), -- Empty string test
+ (3, 'a:1', 'a:1', ',', ',', ':', ':'), -- Single key-value pair
+ (4, 'a:1,b:2,b:3', 'a:1,b:2,b:3', ',', ',', ':', ':'), -- Duplicate
keys
+ (5, 'a:,b:,c:', 'a:,b:,c:', ',', ',', ':', ':'), -- Empty values
+ (6, ':1,:2,:3', ':1,:2,:3', ',', ',', ':', ':'), -- Empty keys
+ (7, 'a=1;b=2;c=3', 'a=1;b=2;c=3', ';', ';', '=', '='), -- Different
delimiters
+ (8, '中文:值,英文:value', '中文:值,英文:value', ',', ',', ':', ':'), -- Unicode
characters
+ (9, 'special@#:123,chars!:456', 'special@#:123,chars!:456', ',', ',',
':', ':'), -- Special characters in keys
+ (10, 'a:123!@#,b:456$%^', 'a:123!@#,b:456$%^', ',', ',', ':', ':'), --
Special characters in values
+ (11, 'verylongkey:verylongvalue,anotherlongkey:anotherlongvalue',
'verylongkey:verylongvalue,anotherlongkey:anotherlongvalue', ',', ',', ':',
':'), -- Long strings
+ (12, 'a::1,b::2', 'a::1,b::2', ',', ',', '::', '::'), --
Multi-character delimiter
+ (13, 'a:1\nb:2\nc:3', 'a:1\nb:2\nc:3', '\n', '\n', ':', ':'), --
Newline as delimiter
+ (14, 'a:1\tb:2\tc:3', 'a:1\tb:2\tc:3', '\t', '\t', ':', ':'), -- Tab
as delimiter
+ (15, ' a : 1 , b : 2 ', ' a : 1 , b : 2 ', ',', ',', ':', ':') --
Spaces in string
+ '''
+
+ // Test different nullable combinations with data
+ order_qt_all_not_null_data """
+ select str_to_map(map_str_not_null, key_delim_not_null,
value_delim_not_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_all_args_null_data """
+ select str_to_map(map_str_null, key_delim_null, value_delim_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_partial_null_data """
+ select str_to_map(map_str_not_null, key_delim_null, value_delim_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_nullable_no_null_data """
+ select str_to_map(nullable(map_str_not_null),
nullable(key_delim_not_null), nullable(value_delim_not_null))
+ from str_to_map_args
+ order by k0;
+ """
+
+ // Test mixed nullable combinations
+ order_qt_mixed_null_1 """
+ select str_to_map(map_str_null, key_delim_not_null,
value_delim_not_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_mixed_null_2 """
+ select str_to_map(map_str_not_null, key_delim_null,
value_delim_not_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_mixed_null_3 """
+ select str_to_map(map_str_not_null, key_delim_not_null,
value_delim_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ // Test with constant null values
+ order_qt_const_null_1 """
+ select str_to_map(null, key_delim_not_null, value_delim_not_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_const_null_2 """
+ select str_to_map(map_str_not_null, null, value_delim_not_null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ order_qt_const_null_3 """
+ select str_to_map(map_str_not_null, key_delim_not_null, null)
+ from str_to_map_args
+ order by k0;
+ """
+
+ /// consts. most by BE-UT
+ // Test const string with column delimiters
+ order_qt_const_str """
+ select str_to_map('a:1,b:2', key_delim_not_null, value_delim_not_null)
+ from str_to_map_args order by k0
+ """
+
+ // Test column string with const delimiters
+ order_qt_const_delims """
+ select str_to_map(map_str_not_null, ',', ':')
+ from str_to_map_args order by k0
+ """
+
+ // Test const string with one const delimiter and one column delimiter
+ order_qt_mixed_const1 """
+ select str_to_map('x=1;y=2', ';', value_delim_not_null)
+ from str_to_map_args order by k0
+ """
+
+ order_qt_mixed_const2 """
+ select str_to_map('p-1|q-2', key_delim_not_null, '-')
+ from str_to_map_args order by k0
+ """
+
+ // Test all const non-null arguments
+ order_qt_all_const """
+ select str_to_map('a=1|b=2', '|', '=')
+ from str_to_map_args order by k0
+ """
+
+ // Test const string with nullable column delimiters
+ order_qt_const_str_null_delims """
+ select str_to_map('m:1,n:2', key_delim_null, value_delim_null)
+ from str_to_map_args order by k0
+ """
+
+ // Test nullable column string with const delimiters
+ order_qt_null_str_const_delims '''
+ select str_to_map(map_str_null, '#', '$')
+ from str_to_map_args order by k0
+ '''
+
+ // Test basic str_to_map functionality with all parameters
+ qt_basic_1 "select str_to_map('a:1,b:2,c:3', ',', ':');"
+ qt_basic_2 "select str_to_map('key1=val1;key2=val2', ';', '=');"
+ qt_basic_3 "select str_to_map('x-1|y-2|z-3', '|', '-');"
+
+ // Test with default parameters (omitting both delimiters)
+ // Default pair delimiter is ',' and key-value delimiter is ':'
+ qt_default_both_1 "select str_to_map('a:1,b:2,c:3');"
+ qt_default_both_2 "select str_to_map('key1:value1,key2:value2');"
+ qt_default_both_3 "select str_to_map('x:1,y:2,z:');"
+ qt_default_both_4 "select str_to_map('');"
+
+ // Test with default key-value delimiter (omitting last parameter)
+ // Default key-value delimiter is ':'
+ qt_default_value_1 "select str_to_map('a:1;b:2;c:3', ';');"
+ qt_default_value_2 "select str_to_map('key:val|foo:bar', '|');"
+ qt_default_value_3 "select str_to_map('x:1#y:2#z:3', '#');"
+ qt_default_value_4 "select str_to_map('a:1...b:2...c:3', '...');"
+
+ // Test empty string cases
+ qt_empty_1 "select str_to_map('');"
+ qt_empty_2 "select str_to_map('a:1,,b:2');"
+ qt_empty_3 "select str_to_map('a:,b:2,c:');"
+ qt_empty_4 "select str_to_map(',,,');"
+
+ // Test missing key-value delimiter
+ qt_missing_value_1 "select str_to_map('a,b:2,c');"
+ qt_missing_value_2 "select str_to_map('val1,val2,val3');"
+ qt_missing_value_3 "select str_to_map('key1,key2:val2,key3');"
+
+ // Test with special characters
+ qt_special_1 "select str_to_map('\ta:1\n,\tb:2\n');"
+ qt_special_2 "select str_to_map('a\\nb:1,c\\td:2');"
+ qt_special_3 "select str_to_map('key1:value1,key2:value2', ',', ':');"
+
+ // Test with spaces
+ qt_spaces_1 "select str_to_map('a : 1, b : 2');"
+ qt_spaces_2 "select str_to_map(' a:1 , b:2 ');"
+ qt_spaces_3 "select str_to_map(' a:1, b:2 ');"
+ qt_spaces_4 "select str_to_map(' ');"
+
+ // Test with Unicode characters
+ qt_unicode_1 "select str_to_map('键1:值1,键2:值2');"
+ qt_unicode_2 "select str_to_map('标题①:内容①,标题②:内容②');"
+ qt_unicode_3 "select str_to_map('🔑:🔒,📝:📖');"
+ qt_unicode_4 "select str_to_map('あ:い,う:え');"
+
+ // Test with duplicate keys
+ qt_dup_1 "select str_to_map('a:1,b:2,a:3');"
+ qt_dup_2 "select str_to_map('key:val1,key:val2,key:val3');"
+ qt_dup_3 "select str_to_map('a:1,a:,a:3');"
+
+ // Test edge cases
+ qt_edge_1 "select str_to_map('a:1:2,b:3:4');"
+ qt_edge_2 "select str_to_map(':::');"
+ qt_edge_3 "select str_to_map('a:1:2');"
+ qt_edge_4 "select str_to_map('key::value');"
+ qt_edge_5 "select str_to_map(':');"
+
+ // Test extremely long strings
+ qt_long_1 "select str_to_map(repeat('a:1,', 1000));"
+ qt_long_2 "select str_to_map(concat(repeat('key', 100), ':',
repeat('value', 100)));"
+}
diff --git
a/regression-test/suites/query_p0/sql_functions/string_functions/test_xpath_string.groovy
b/regression-test/suites/query_p0/sql_functions/string_functions/test_xpath_string.groovy
new file mode 100644
index 00000000000..c73f70b26f2
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/string_functions/test_xpath_string.groovy
@@ -0,0 +1,149 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_xpath_string") {
+ sql "drop table if exists xpath_string_args;"
+ sql """
+ create table xpath_string_args (
+ k0 int,
+ xml_not_null string not null,
+ xml_null string null,
+ xpath_not_null string not null,
+ xpath_null string null
+ )
+ DISTRIBUTED BY HASH(k0)
+ PROPERTIES
+ (
+ "replication_num" = "1"
+ );
+ """
+
+ order_qt_empty_nullable "select xpath_string(xml_null, xpath_null) from
xpath_string_args"
+ order_qt_empty_not_nullable "select xpath_string(xml_not_null,
xpath_not_null) from xpath_string_args"
+ order_qt_empty_partial_nullable "select xpath_string(xml_null,
xpath_not_null), xpath_string(xml_not_null, xpath_null) from xpath_string_args"
+
+ sql "insert into xpath_string_args values (1, '<a><b>123</b></a>', null,
'/a/b', null)"
+
+ order_qt_all_null "select xpath_string(xml_null, xpath_null) from
xpath_string_args"
+ order_qt_all_not_null "select xpath_string(xml_not_null, xpath_not_null)
from xpath_string_args"
+ order_qt_partial_nullable "select xpath_string(xml_null, xpath_not_null),
xpath_string(xml_not_null, xpath_null) from xpath_string_args"
+ order_qt_nullable_no_null "select xpath_string(xml_null,
nullable(xpath_not_null)), xpath_string(nullable(xml_not_null), xpath_null)
from xpath_string_args"
+
+ sql "truncate table xpath_string_args"
+
+ sql """
+ insert into xpath_string_args values
+ (2, '<a>123</a>', '<a>456</a>', '/a', '/a'),
+ (3, '<a><b>123</b><c>456</c></a>', null, '/a/c', '/a/b'),
+ (4, '<a><b>123</b><c>456</c></a>', '<a><d>789</d></a>', '/a/d', null),
+ (5, '<a><b>123</b><b>456</b></a>', '<a><b>789</b></a>', '/a/b[1]',
'/a/b'),
+ (6, '<a><b>123</b><b>456</b></a>', null, '/a/b[2]', '/a/b[1]'),
+ (7, '<a><b attr="val">123</b></a>', '<a><b attr="other">456</b></a>',
'/a/b[@attr]', '/a/b[@attr="val"]'),
+ (8, '<a><!-- comment -->123</a>', '<a>456</a>', '/a', null),
+ (9, '<a><![CDATA[123]]></a>', null, '/a', '/a'),
+ (10, '<a>123<b>456</b>789</a>', '<a><b>test</b></a>', '/a', '/a/b'),
+ (11, '<a> 123 </a>', '<a>456</a>', '/a', null),
+ (12, '<book><title>Intro to Hive</title><author>John
Doe</author></book>',
+ '<book><title>SQL Guide</title></book>',
+ '//title/text()',
+ '//author/text()'),
+ (13, '<root><user
id="1"><name>Alice</name><age>25</age></user></root>',
+ '<root><user id="2"><name>Bob</name></user></root>',
+ '/root/user[@id="1"]/name',
+ '/root/user/age'),
+ (14, '<products><item price="10.99">Book</item><item
price="20.99">Pen</item></products>',
+ null,
+ '/products/item[@price="20.99"]',
+ '/products/item[1]'),
+ (15, '<data><![CDATA[<nested>value</nested>]]></data>',
+ '<data><plain>text</plain></data>',
+ '/data',
+ '//plain/text()'),
+ (16,
'<menu><item>Coffee<price>3.99</price></item><item>Tea<price>2.99</price></item></menu>',
+ '<menu><item><price>5.99</price></item></menu>',
+ '//item[price="2.99"]',
+ '/menu/item[1]/price'),
+ (17, '<doc><section id="1">First</section><section
id="2">Second</section></doc>',
+ null,
+ '/doc/section[@id="2"]',
+ '/doc/section[1]'),
+ (18, '<list><elem pos="1">A</elem><elem pos="2">B</elem><elem
pos="3">C</elem></list>',
+ '<list><elem>X</elem></list>',
+ '/list/elem[@pos="2"]',
+ '/list/elem[last()]'),
+ (19, '<nested><a><b><c>Deep</c></b></a></nested>',
+ '<nested><x><y>Shallow</y></x></nested>',
+ '//c',
+ '/nested/x/y'),
+ (20, '<mixed>Text<b>Bold</b>Normal<i>Italic</i>End</mixed>',
+ '<mixed><b>Only Bold</b></mixed>',
+ '/mixed',
+ '//b/text()'),
+ (21, '<empty></empty>',
+ '<empty/>',
+ '/empty',
+ '/empty/text()')
+ """
+
+ order_qt_all_null "select xpath_string(xml_null, xpath_null) from
xpath_string_args"
+ order_qt_all_not_null "select xpath_string(xml_not_null, xpath_not_null)
from xpath_string_args"
+ order_qt_partial_nullable "select xpath_string(xml_null, xpath_not_null),
xpath_string(xml_not_null, xpath_null) from xpath_string_args"
+ order_qt_nullable_no_null "select xpath_string(xml_null,
nullable(xpath_not_null)), xpath_string(nullable(xml_not_null), xpath_null)
from xpath_string_args"
+
+ /// consts. most by BE-UT
+ order_qt_const_nullable "select xpath_string(xml_null, NULL),
xpath_string(NULL, xpath_null) from xpath_string_args"
+ order_qt_const_not_nullable "select xpath_string(xml_not_null, '/a/b'),
xpath_string('<a><b>123</b></a>', xpath_not_null) from xpath_string_args"
+ order_qt_const_partial_nullable "select xpath_string(xml_null,
nullable('/a/b')), xpath_string(xml_not_null, nullable(xpath_null)) from
xpath_string_args"
+ order_qt_const_nullable_no_null "select
xpath_string(nullable(xml_not_null), nullable('/a/b')),
xpath_string(nullable('<a><b>123</b></a>'), nullable(xpath_not_null)) from
xpath_string_args"
+
+ order_qt_1 "select xpath_string('', '')"
+ order_qt_2 "select xpath_string(NULL, NULL)"
+ order_qt_3 "select xpath_string(NULL, '/a/b')"
+ order_qt_4 "select xpath_string('<a><b>123</b></a>', NULL)"
+ order_qt_5 "select xpath_string('<a><b>123</b></a>', '/a/b')"
+ order_qt_6 "select xpath_string('<a>123</a>', '/a')"
+ order_qt_7 "select xpath_string('<a><b>123</b><c>456</c></a>', '/a/c')"
+ order_qt_8 "select xpath_string('<a><b>123</b><b>456</b></a>', '/a/b[1]')"
+ order_qt_9 "select xpath_string('<a><b attr=\"val\">123</b></a>',
'/a/b[@attr]')"
+ order_qt_10 "select xpath_string('<a><!-- comment -->123</a>', '/a')"
+ order_qt_11 "select xpath_string('<a><![CDATA[123]]></a>', '/a')"
+ order_qt_12 "select xpath_string('<book><title>Intro to
Hive</title></book>', '//title/text()')"
+ order_qt_13 "select xpath_string(nullable('<a><b>123</b></a>'),
nullable('/a/b'))"
+ order_qt_14 "select xpath_string('<a><b>123</b></a>', nullable('/a/b'))"
+ order_qt_15 "select xpath_string(nullable('<a><b>123</b></a>'), '/a/b')"
+ order_qt_16 "select xpath_string('<root><user
id=\"1\"><name>Alice</name></user></root>', '/root/user[@id=\"1\"]/name')"
+ order_qt_17 "select xpath_string('<products><item
price=\"10.99\">Book</item></products>', '/products/item[@price=\"10.99\"]')"
+ order_qt_18 "select
xpath_string('<menu><item><price>3.99</price></item></menu>',
'//item/price/text()')"
+ order_qt_19 "select xpath_string('<data><a>1</a><a>2</a><a>3</a></data>',
'/data/a[last()]')"
+ order_qt_20 "select
xpath_string('<nested><a><b><c>Deep</c></b></a></nested>', '//c/text()')"
+ order_qt_21 "select xpath_string('<mixed>Text<b>Bold</b>Normal</mixed>',
'/mixed/text()')"
+ order_qt_22 "select xpath_string('<doc><item
pos=\"1\">First</item></doc>', '/doc/item[@pos=\"1\"]/text()')"
+ order_qt_23 "select xpath_string('<test><a>x</a><b>y</b><c>z</c></test>',
'/test/*[2]')"
+ order_qt_24 "select
xpath_string('<data><![CDATA[<nested>value</nested>]]></data>', '/data')"
+ order_qt_25 "select xpath_string('<root><elem><!-- comment
-->value</elem></root>', '/root/elem')"
+ order_qt_26 "select
xpath_string('<doc><section><title>Test</title><para>Text</para></section></doc>',
'/doc/section[title=\"Test\"]/para')"
+ order_qt_27 "select xpath_string('<list><item val=\"1\"/><item
val=\"2\"/></list>', '/list/item[@val=\"2\"]')"
+ order_qt_28 "select
xpath_string('<data><group><name>A</name><value>1</value></group></data>',
'/data/group[name=\"A\"]/value')"
+ order_qt_29 "select
xpath_string('<root><a><b>1</b></a><a><b>2</b></a></root>', '//a[b=\"2\"]/b')"
+ order_qt_30 "select xpath_string('<doc><p
class=\"main\">Content</p></doc>', '//p[@class=\"main\"]/text()')"
+
+ /// error cases:
+ test {
+ sql """ select xpath_string('wrong xml', '//a/c') """
+ exception "Function xpath_string failed to parse XML string: No
document element found"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]