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

panxiaolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 1b8120880f2 [Fix](function) fix skip null value for some date 
functions (#59616)
1b8120880f2 is described below

commit 1b8120880f274016b74da7600019f7ef767c41da
Author: zclllyybb <[email protected]>
AuthorDate: Thu Jan 8 22:07:43 2026 +0800

    [Fix](function) fix skip null value for some date functions (#59616)
    
    fix skip null value for some date functions
---
 .../function_date_or_datetime_computation.cpp      |   8 +-
 .../function_date_or_datetime_computation.h        | 148 +++++++--------------
 .../function_date_or_datetime_to_string.cpp        |  49 +++++--
 .../vec/functions/function_needs_to_handle_null.h  |   6 +
 .../{Map.out => nereids_scalar_fn_map.out}         |   8 ++
 .../data/nereids_p0/join/test_outer_join.out       |   5 +
 .../{Map.groovy => nereids_scalar_fn_map.groovy}   |  21 +++
 .../suites/nereids_p0/join/test_outer_join.groovy  |  29 +++-
 .../test_date_function_v2.groovy                   |   4 +-
 9 files changed, 159 insertions(+), 119 deletions(-)

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 266324f28d6..3495c16d1bb 100644
--- a/be/src/vec/functions/function_date_or_datetime_computation.cpp
+++ b/be/src/vec/functions/function_date_or_datetime_computation.cpp
@@ -119,10 +119,10 @@ using FunctionDatetimeSubQuarters =
 using FunctionDatetimeSubYears =
         FunctionDateOrDateTimeComputation<SubtractYearsImpl<TYPE_DATETIMEV2>>;
 
-using FunctionAddTimeDatetime = FunctionAddTime<TYPE_DATETIMEV2, AddTimeImpl>;
-using FunctionAddTimeTime = FunctionAddTime<TYPE_TIMEV2, AddTimeImpl>;
-using FunctionSubTimeDatetime = FunctionAddTime<TYPE_DATETIMEV2, SubTimeImpl>;
-using FunctionSubTimeTime = FunctionAddTime<TYPE_TIMEV2, SubTimeImpl>;
+using FunctionAddTimeDatetime = FunctionNeedsToHandleNull<AddTimeDatetimeImpl, 
TYPE_DATETIMEV2>;
+using FunctionAddTimeTime = FunctionNeedsToHandleNull<AddTimeTimeImpl, 
TYPE_TIMEV2>;
+using FunctionSubTimeDatetime = FunctionNeedsToHandleNull<SubTimeDatetimeImpl, 
TYPE_DATETIMEV2>;
+using FunctionSubTimeTime = FunctionNeedsToHandleNull<SubTimeTimeImpl, 
TYPE_TIMEV2>;
 
 #define FUNCTION_TIME_DIFF(NAME, IMPL, TYPE) using NAME##_##TYPE = 
FunctionTimeDiff<IMPL<TYPE>>;
 
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 e2953c9ab9e..2e31144b453 100644
--- a/be/src/vec/functions/function_date_or_datetime_computation.h
+++ b/be/src/vec/functions/function_date_or_datetime_computation.h
@@ -23,6 +23,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <memory>
+#include <string>
 #include <string_view>
 #include <type_traits>
 #include <utility>
@@ -1819,118 +1820,71 @@ public:
     }
 };
 
-struct AddTimeImpl {
-    static constexpr auto name = "add_time";
-    static bool is_negative() { return false; }
-};
-
-struct SubTimeImpl {
-    static constexpr auto name = "sub_time";
-    static bool is_negative() { return true; }
-};
-
-template <PrimitiveType PType, typename Impl>
-class FunctionAddTime : public IFunction {
+template <PrimitiveType PType, bool IsNegative>
+class AddTimeImplBase {
 public:
-    static constexpr auto name = Impl::name;
-    static constexpr PrimitiveType ReturnType = PType;
-    static constexpr PrimitiveType ArgType1 = PType;
-    static constexpr PrimitiveType ArgType2 = TYPE_TIMEV2;
-    using ColumnType1 = typename PrimitiveTypeTraits<PType>::ColumnType;
-    using ColumnType2 = typename PrimitiveTypeTraits<TYPE_TIMEV2>::ColumnType;
+    static constexpr auto name = IsNegative ? "sub_time" : "add_time";
     using InputType1 = typename 
PrimitiveTypeTraits<PType>::DataType::FieldType;
     using InputType2 = typename 
PrimitiveTypeTraits<TYPE_TIMEV2>::DataType::FieldType;
-    using ReturnNativeType = InputType1;
-    using ReturnDataType = typename PrimitiveTypeTraits<PType>::DataType;
+    using ResultColumnType = typename PrimitiveTypeTraits<PType>::ColumnType;
 
-    String get_name() const override { return name; }
-    size_t get_number_of_arguments() const override { return 2; }
-    DataTypes get_variadic_argument_types_impl() const override {
+    static size_t get_number_of_arguments() { return 2; }
+    static bool is_variadic() { return true; }
+    static DataTypes get_variadic_argument_types_impl() {
         return {std::make_shared<typename 
PrimitiveTypeTraits<PType>::DataType>(),
                 std::make_shared<typename 
PrimitiveTypeTraits<TYPE_TIMEV2>::DataType>()};
     }
-    DataTypePtr get_return_type_impl(const ColumnsWithTypeAndName& arguments) 
const override {
-        return std::make_shared<ReturnDataType>();
-    }
-
-    ReturnNativeType compute(const InputType1& arg1, const InputType2& arg2) 
const {
-        if constexpr (PType == TYPE_DATETIMEV2) {
-            DateV2Value<DateTimeV2ValueType> dtv1 =
-                    binary_cast<InputType1, 
DateV2Value<DateTimeV2ValueType>>(arg1);
-            auto tv2 = static_cast<TimeValue::TimeType>(arg2);
-            TimeInterval interval(TimeUnit::MICROSECOND, tv2, 
Impl::is_negative());
-            bool out_range = dtv1.template 
date_add_interval<TimeUnit::MICROSECOND>(interval);
-            if (UNLIKELY(!out_range)) {
-                throw Exception(ErrorCode::INVALID_ARGUMENT,
-                                "datetime value is out of range in function 
{}", name);
-            }
-            return binary_cast<DateV2Value<DateTimeV2ValueType>, 
ReturnNativeType>(dtv1);
-        } else if constexpr (PType == TYPE_TIMEV2) {
-            auto tv1 = static_cast<TimeValue::TimeType>(arg1);
-            auto tv2 = static_cast<TimeValue::TimeType>(arg2);
-            double res = TimeValue::limit_with_bound(Impl::is_negative() ? tv1 
- tv2 : tv1 + tv2);
-            return res;
-        } else {
-            throw Exception(ErrorCode::FATAL_ERROR, "not support type for 
function {}", name);
+    static DataTypePtr get_return_type_impl(const DataTypes& arguments) {
+        if (arguments[0]->is_nullable() || arguments[1]->is_nullable()) {
+            return make_nullable(std::make_shared<typename 
PrimitiveTypeTraits<PType>::DataType>());
         }
+        return std::make_shared<typename 
PrimitiveTypeTraits<PType>::DataType>();
     }
 
-    static FunctionPtr create() { return std::make_shared<FunctionAddTime>(); }
-
-    Status execute_impl(FunctionContext* context, Block& block, const 
ColumnNumbers& arguments,
-                        uint32_t result, size_t input_rows_count) const 
override {
-        DCHECK_EQ(arguments.size(), 2);
-        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);
-        ColumnPtr nest_col1 = remove_nullable(left_col);
-        ColumnPtr nest_col2 = remove_nullable(right_col);
-        auto res = ColumnVector<ReturnType>::create(input_rows_count, 0);
-
-        if (left_const) {
-            execute_constant_vector(assert_cast<const 
ColumnType1&>(*nest_col1).get_element(0),
-                                    assert_cast<const 
ColumnType2&>(*nest_col2).get_data(),
-                                    res->get_data(), input_rows_count);
-        } else if (right_const) {
-            execute_vector_constant(assert_cast<const 
ColumnType1&>(*nest_col1).get_data(),
-                                    assert_cast<const 
ColumnType2&>(*nest_col2).get_element(0),
-                                    res->get_data(), input_rows_count);
-        } else {
-            execute_vector_vector(assert_cast<const 
ColumnType1&>(*nest_col1).get_data(),
-                                  assert_cast<const 
ColumnType2&>(*nest_col2).get_data(),
-                                  res->get_data(), input_rows_count);
-        }
-
-        block.replace_by_position(result, std::move(res));
-        return Status::OK();
-    }
-    void execute_vector_vector(const PaddedPODArray<InputType1>& left_col,
-                               const PaddedPODArray<InputType2>& right_col,
-                               PaddedPODArray<ReturnNativeType>& res_data,
-                               size_t input_rows_count) const {
-        for (size_t i = 0; i < input_rows_count; ++i) {
-            res_data[i] = compute(left_col[i], right_col[i]);
-        }
-    }
+    static void execute(const std::vector<ColumnWithConstAndNullMap>& 
cols_info,
+                        typename ResultColumnType::MutablePtr& res_col,
+                        PaddedPODArray<UInt8>& res_null_map_data, size_t 
input_rows_count) {
+        const auto& left_data =
+                assert_cast<const 
ResultColumnType*>(cols_info[0].nested_col)->get_data();
+        const auto& right_data =
+                assert_cast<const 
ColumnVector<TYPE_TIMEV2>*>(cols_info[1].nested_col)->get_data();
 
-    void execute_vector_constant(const PaddedPODArray<InputType1>& left_col,
-                                 const InputType2 right_value,
-                                 PaddedPODArray<ReturnNativeType>& res_data,
-                                 size_t input_rows_count) const {
         for (size_t i = 0; i < input_rows_count; ++i) {
-            res_data[i] = compute(left_col[i], right_value);
-        }
-    }
+            if (cols_info[0].is_null_at(i) || cols_info[1].is_null_at(i)) {
+                res_col->insert_default();
+                res_null_map_data[i] = 1;
+                continue;
+            }
 
-    void execute_constant_vector(const InputType1 left_value,
-                                 const PaddedPODArray<InputType2>& right_col,
-                                 PaddedPODArray<ReturnNativeType>& res_data,
-                                 size_t input_rows_count) const {
-        for (size_t i = 0; i < input_rows_count; ++i) {
-            res_data[i] = compute(left_value, right_col[i]);
+            const auto& arg1 = left_data[index_check_const(i, 
cols_info[0].is_const)];
+            const auto& arg2 = right_data[index_check_const(i, 
cols_info[1].is_const)];
+
+            if constexpr (PType == TYPE_DATETIMEV2) {
+                DateV2Value<DateTimeV2ValueType> dtv1 =
+                        binary_cast<InputType1, 
DateV2Value<DateTimeV2ValueType>>(arg1);
+                auto tv2 = static_cast<TimeValue::TimeType>(arg2);
+                TimeInterval interval(TimeUnit::MICROSECOND, tv2, IsNegative);
+                bool out_range = dtv1.template 
date_add_interval<TimeUnit::MICROSECOND>(interval);
+                if (!out_range) [[unlikely]] {
+                    throw_invalid_strings(name, dtv1.to_string(), 
std::to_string(arg2));
+                }
+                res_col->insert_value(
+                        binary_cast<DateV2Value<DateTimeV2ValueType>, 
InputType1>(dtv1));
+            } else if constexpr (PType == TYPE_TIMEV2) {
+                auto tv1 = static_cast<TimeValue::TimeType>(arg1);
+                auto tv2 = static_cast<TimeValue::TimeType>(arg2);
+                double res = TimeValue::limit_with_bound(IsNegative ? tv1 - 
tv2 : tv1 + tv2);
+                res_col->insert_value(res);
+            } else {
+                throw Exception(ErrorCode::FATAL_ERROR, "not support type for 
function {}", name);
+            }
         }
     }
 };
+
+using AddTimeDatetimeImpl = AddTimeImplBase<TYPE_DATETIMEV2, false>;
+using AddTimeTimeImpl = AddTimeImplBase<TYPE_TIMEV2, false>;
+using SubTimeDatetimeImpl = AddTimeImplBase<TYPE_DATETIMEV2, true>;
+using SubTimeTimeImpl = AddTimeImplBase<TYPE_TIMEV2, true>;
 #include "common/compile_check_avoid_end.h"
 } // namespace doris::vectorized
diff --git a/be/src/vec/functions/function_date_or_datetime_to_string.cpp 
b/be/src/vec/functions/function_date_or_datetime_to_string.cpp
index 6b068ccf159..8e38aaf0b7f 100644
--- a/be/src/vec/functions/function_date_or_datetime_to_string.cpp
+++ b/be/src/vec/functions/function_date_or_datetime_to_string.cpp
@@ -77,8 +77,6 @@ public:
         return {};
     }
 
-    ColumnNumbers get_arguments_that_are_always_constant() const override { 
return {1}; }
-
     // In ICU, Week_array: {"", "Sunday", "Monday", ..., "Saturday"}, size = 8
     // Month_array: {"January", "February", ..., "December"}, size = 12
     static constexpr size_t DAY_NUM_IN_ICU = 8;
@@ -140,28 +138,48 @@ public:
         return IFunction::open(context, scope);
     }
 
+    bool use_default_implementation_for_nulls() const override { return false; 
}
+
     Status execute_impl(FunctionContext* context, Block& block, const 
ColumnNumbers& arguments,
                         uint32_t result, size_t input_rows_count) const 
override {
         const ColumnPtr source_col = 
block.get_by_position(arguments[0]).column;
+        const NullMap* null_map = nullptr;
+        ColumnPtr actual_col = source_col;
+
+        if (is_column_nullable(*source_col)) {
+            const auto* nullable_col = 
check_and_get_column<ColumnNullable>(source_col.get());
+            actual_col = nullable_col->get_nested_column_ptr();
+            null_map = &nullable_col->get_null_map_data();
+        }
+
         const auto* sources =
-                
check_and_get_column<ColumnVector<Transform::OpArgType>>(source_col.get());
-        auto col_res = ColumnString::create();
+                
check_and_get_column<ColumnVector<Transform::OpArgType>>(actual_col.get());
+        if (!sources) [[unlikely]] {
+            return Status::FatalError("Illegal column {} of first argument of 
function {}",
+                                      
block.get_by_position(arguments[0]).column->get_name(), name);
+        }
 
-        // Support all input of datetime is valind to make sure not null return
-        if (sources) {
-            vector(context, sources->get_data(), col_res->get_chars(), 
col_res->get_offsets());
-            block.replace_by_position(result, std::move(col_res));
+        auto col_res = ColumnString::create();
+        vector(context, sources->get_data(), col_res->get_chars(), 
col_res->get_offsets(),
+               null_map);
+
+        if (null_map) {
+            const auto* nullable_col = 
check_and_get_column<ColumnNullable>(source_col.get());
+            block.replace_by_position(
+                    result,
+                    ColumnNullable::create(std::move(col_res),
+                                           
nullable_col->get_null_map_column_ptr()->clone_resized(
+                                                   input_rows_count)));
         } else {
-            return Status::InternalError("Illegal column {} of first argument 
of function {}",
-                                         
block.get_by_position(arguments[0]).column->get_name(),
-                                         name);
+            block.replace_by_position(result, std::move(col_res));
         }
         return Status::OK();
     }
 
 private:
     static void vector(FunctionContext* context, const 
PaddedPODArray<NativeType>& ts,
-                       ColumnString::Chars& res_data, ColumnString::Offsets& 
res_offsets) {
+                       ColumnString::Chars& res_data, ColumnString::Offsets& 
res_offsets,
+                       const NullMap* null_map = nullptr) {
         const auto len = ts.size();
         res_data.resize(len * Transform::max_size);
         res_offsets.resize(len);
@@ -176,7 +194,12 @@ private:
             names_ptr = state->month_names;
         }
 
-        for (int i = 0; i < len; ++i) {
+        for (size_t i = 0; i < len; ++i) {
+            if (null_map && (*null_map)[i]) {
+                res_offsets[i] = cast_set<UInt32>(offset);
+                continue;
+            }
+
             const auto& t = ts[i];
             const auto date_time_value = binary_cast<NativeType, DateType>(t);
             res_offsets[i] = cast_set<UInt32>(
diff --git a/be/src/vec/functions/function_needs_to_handle_null.h 
b/be/src/vec/functions/function_needs_to_handle_null.h
index 0e60da92e68..5ac6584924d 100644
--- a/be/src/vec/functions/function_needs_to_handle_null.h
+++ b/be/src/vec/functions/function_needs_to_handle_null.h
@@ -53,6 +53,12 @@ public:
         }
         return false;
     }
+    DataTypes get_variadic_argument_types_impl() const override {
+        if constexpr (requires { Impl::get_variadic_argument_types_impl(); }) {
+            return Impl::get_variadic_argument_types_impl();
+        }
+        return {};
+    }
 
     bool use_default_implementation_for_nulls() const override { return false; 
}
 
diff --git a/regression-test/data/nereids_function_p0/scalar_function/Map.out 
b/regression-test/data/nereids_function_p0/scalar_function/nereids_scalar_fn_map.out
similarity index 99%
rename from regression-test/data/nereids_function_p0/scalar_function/Map.out
rename to 
regression-test/data/nereids_function_p0/scalar_function/nereids_scalar_fn_map.out
index c3eb0f6c071..b5e55783718 100644
--- a/regression-test/data/nereids_function_p0/scalar_function/Map.out
+++ 
b/regression-test/data/nereids_function_p0/scalar_function/nereids_scalar_fn_map.out
@@ -3722,3 +3722,11 @@ false
 -- !sql --
 {"{"zip":10001}":"{"city":"NY"}", "{"code":10002}":"{"state":"NY"}"}
 
+-- !element_at_tint_date_dayname --
+\N
+Sunday
+
+-- !element_at_tint_date_dayname_notnull --
+\N
+Saturday
+
diff --git a/regression-test/data/nereids_p0/join/test_outer_join.out 
b/regression-test/data/nereids_p0/join/test_outer_join.out
index 9ae6731b347..60c7db8f729 100644
--- a/regression-test/data/nereids_p0/join/test_outer_join.out
+++ b/regression-test/data/nereids_p0/join/test_outer_join.out
@@ -10,3 +10,8 @@
 
 -- !join --
 
+-- !join --
+\N     \N
+1      2023-12-18T23:30:01
+2      2023-12-15T23:30:01
+
diff --git 
a/regression-test/suites/nereids_function_p0/scalar_function/Map.groovy 
b/regression-test/suites/nereids_function_p0/scalar_function/nereids_scalar_fn_map.groovy
similarity index 97%
rename from 
regression-test/suites/nereids_function_p0/scalar_function/Map.groovy
rename to 
regression-test/suites/nereids_function_p0/scalar_function/nereids_scalar_fn_map.groovy
index 7c686a08baa..343b298a872 100644
--- a/regression-test/suites/nereids_function_p0/scalar_function/Map.groovy
+++ 
b/regression-test/suites/nereids_function_p0/scalar_function/nereids_scalar_fn_map.groovy
@@ -35,6 +35,7 @@ suite("nereids_scalar_fn_map") {
     order_qt_element_at_str_tint    """ select km_str_tint[kstr] from fn_test 
"""
     order_qt_element_at_date_tint   """ select km_date_tint[kdt] from fn_test 
"""
     order_qt_element_at_dtm_tint    """ select km_dtm_tint[kdtm] from fn_test 
"""
+
     order_qt_element_at_bool_tint_notnull   """ select km_bool_tint[kbool] 
from fn_test_not_nullable """
     order_qt_element_at_tint_tint_notnull   """ select km_tint_tint[ktint] 
from fn_test_not_nullable """
     order_qt_element_at_sint_tint_notnull   """ select km_sint_tint[ksint] 
from fn_test_not_nullable """
@@ -314,4 +315,24 @@ suite("nereids_scalar_fn_map") {
     qt_sql "select map('postal_code', 10001, 'area_code', 10002, 'zip_plus_4', 
10003)"
     qt_sql "select map('{\"zip\":10001}', '{\"city\":\"NY\"}', 
'{\"code\":10002}', '{\"state\":\"NY\"}')"
 
+    sql """ drop table if exists fn_test_element_at_map_date """
+    sql """
+        create table fn_test_element_at_map_date (
+            id int null,
+            m map<tinyint, date> null
+        ) engine=olap
+        distributed by hash(id) buckets 1
+        properties('replication_num' = '1')
+    """
+    sql """
+        insert into fn_test_element_at_map_date values
+        (1, map(1,null, 2,'2023-12-16')),
+        (2, map(1,'2023-01-01', 2,null));
+    """
+    order_qt_element_at_tint_date_dayname         """
+    select dayname(element_at(m, 1)) from fn_test_element_at_map_date
+    """
+    order_qt_element_at_tint_date_dayname_notnull """
+    select dayname(element_at(m, 2)) from fn_test_element_at_map_date
+    """
 }
\ No newline at end of file
diff --git a/regression-test/suites/nereids_p0/join/test_outer_join.groovy 
b/regression-test/suites/nereids_p0/join/test_outer_join.groovy
index 9264f3fc579..f97c580d84c 100644
--- a/regression-test/suites/nereids_p0/join/test_outer_join.groovy
+++ b/regression-test/suites/nereids_p0/join/test_outer_join.groovy
@@ -84,7 +84,30 @@ suite("test_outer_join", "nereids_p0") {
         contains "FULL OUTER JOIN(PARTITIONED)"
     }
 
-    sql "DROP TABLE IF EXISTS ${tbl1}"
-    sql "DROP TABLE IF EXISTS ${tbl2}"
-    sql "DROP TABLE IF EXISTS ${tbl3}"
+    def tbl4 = "test_outer_join4"
+    def tbl5 = "test_outer_join5"
+
+    sql "DROP TABLE IF EXISTS test_outer_join4"
+    sql """
+        CREATE TABLE test_outer_join4 (
+            k INT,
+            d DATEV2
+        )
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1 PROPERTIES ("replication_num" = "1");
+    """
+    sql "DROP TABLE IF EXISTS test_outer_join5"
+    sql """
+        CREATE TABLE test_outer_join5 (
+            k INT
+        )
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1 PROPERTIES ("replication_num" = "1");
+    """
+    sql """INSERT INTO test_outer_join4 VALUES (1, '2023-12-18'), (2, 
'2023-12-15');"""
+    sql """INSERT INTO test_outer_join5 VALUES (1), (2), (3);"""
+
+    order_qt_join """
+        SELECT k, add_time(d, '23:30:01') FROM test_outer_join4 RIGHT JOIN 
test_outer_join5 USING (k) ORDER BY k;
+    """
 }
diff --git 
a/regression-test/suites/nereids_p0/sql_functions/datetime_functions/test_date_function_v2.groovy
 
b/regression-test/suites/nereids_p0/sql_functions/datetime_functions/test_date_function_v2.groovy
index d96a6f7c770..977acfc36a2 100644
--- 
a/regression-test/suites/nereids_p0/sql_functions/datetime_functions/test_date_function_v2.groovy
+++ 
b/regression-test/suites/nereids_p0/sql_functions/datetime_functions/test_date_function_v2.groovy
@@ -175,7 +175,7 @@ suite("test_date_function_v2") {
 
     test{
         sql("select add_time('9999-12-29 00:00:00', '122:35:22.123456');")
-        exception "datetime value is out of range in function add_time"
+        exception "Operation add_time of"
     }
 
     qt_sql_subtime1("select sub_time('2023-10-14 00:00:00', '22:35:22');")
@@ -193,7 +193,7 @@ suite("test_date_function_v2") {
 
     test{
         sql("select sub_time('0000-01-01 00:00:00', '122:35:22.123456');")
-        exception "datetime value is out of range in function sub_time"
+        exception "Operation sub_time of"
     }
 
     //test computetimearithmetic regular


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

Reply via email to