This is an automated email from the ASF dual-hosted git repository.
yiguolei 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 a5aa078fefa [fix](be) Restore null literal return type handling
(#66280)
a5aa078fefa is described below
commit a5aa078fefa4ae2c4cff24d3f38d012b163ac400
Author: Jerry Hu <[email protected]>
AuthorDate: Wed Aug 5 09:07:00 2026 +0800
[fix](be) Restore null literal return type handling (#66280)
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary:
When constant folding is skipped, an untyped `NULL` argument reaches the
backend as a nullable `UInt8` placeholder marked as a null literal.
`FunctionBuilderImpl` stripped the nullable wrapper and invoked
function-specific return type inference, so functions such as
`array_zip` could interpret that placeholder as an array and dereference
a null type pointer during prepare.
Restore the generic `Nullable(Nothing)` short circuit for null literals
before function-specific return type inference. The sentinel lets the
builder use the FE-planned nullable result type, while the existing
default NULL execution path produces the typed NULL result. Add focused
builder unit coverage and SQL regression coverage for folded and
non-folded NULL arguments.
The added argument scan runs only while building a function and
introduces no per-row execution cost. Functions with custom NULL
handling, typed NULL arguments, ordinary nullable columns, and non-NULL
inputs keep their existing paths.
### Release note
Fix a backend crash when `array_zip` receives an untyped `NULL`
argument.
### Check List (For Author)
- Test
- [x] Regression test
- Generated and reran `test_array_zip_array_enumerate_uniq` with an
isolated regression configuration.
- [x] Unit Test
- `./run-be-ut.sh --run
--filter=SimpleFunctionFactoryTest.test_null_literal_skips_return_type_inference`
- [x] Build
- `BUILD_TYPE=ASAN ./build.sh --be --fe`
- [x] Static/style checks
- `./build-support/clang-format.sh`
- `./build-support/check-format.sh`
- `./build-support/run-clang-tidy.sh --build-dir be/ut_build_ASAN`
- `git diff --check`
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason
- Behavior changed:
- [ ] No.
- [x] Yes. `array_zip` with an untyped `NULL` now returns `NULL` instead
of crashing the backend when constant folding is skipped.
- Does this need documentation?
- [x] No.
- [ ] Yes.
---
be/src/exprs/function/function.cpp | 5 +++
.../function/simple_function_factory_test.cpp | 40 +++++++++++++++++++++-
.../test_array_zip_array_enumerate_uniq.out | 15 ++++++++
.../test_array_zip_array_enumerate_uniq.groovy | 20 +++++++++++
4 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/be/src/exprs/function/function.cpp
b/be/src/exprs/function/function.cpp
index 4f8c4e25059..c02ef344f6b 100644
--- a/be/src/exprs/function/function.cpp
+++ b/be/src/exprs/function/function.cpp
@@ -296,6 +296,11 @@ DataTypePtr FunctionBuilderImpl::get_return_type(const
ColumnsWithTypeAndName& a
check_number_of_arguments(arguments.size());
if (!arguments.empty() && use_default_implementation_for_nulls()) {
+ if (std::ranges::any_of(arguments, [](const auto& argument) {
+ return argument.type->is_null_literal();
+ })) {
+ return make_nullable(std::make_shared<DataTypeNothing>());
+ }
if (have_null_column(arguments)) {
ColumnNumbers numbers(arguments.size());
std::iota(numbers.begin(), numbers.end(), 0);
diff --git a/be/test/exprs/function/simple_function_factory_test.cpp
b/be/test/exprs/function/simple_function_factory_test.cpp
index dc8dcdf8583..ab2dbb018fd 100644
--- a/be/test/exprs/function/simple_function_factory_test.cpp
+++ b/be/test/exprs/function/simple_function_factory_test.cpp
@@ -25,6 +25,7 @@
#include <vector>
#include "core/data_type/data_type_bitmap.h"
+#include "core/data_type/data_type_factory.hpp"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
@@ -48,11 +49,32 @@ public:
}
};
+class FunctionNullLiteralBeTestMock : public IFunction {
+public:
+ static constexpr auto name = "null_literal_be_test_mock";
+
+ static FunctionPtr create() { return
std::make_shared<FunctionNullLiteralBeTestMock>(); }
+
+ String get_name() const override { return name; }
+
+ size_t get_number_of_arguments() const override { return 1; }
+
+ DataTypePtr get_return_type_impl(const DataTypes&) const override {
+ return std::make_shared<DataTypeInt64>();
+ }
+
+ Status execute_impl(FunctionContext*, Block&, const ColumnNumbers&,
uint32_t,
+ size_t) const override {
+ return Status::OK();
+ }
+};
+
class SimpleFunctionFactoryTest : public testing::Test {
void SetUp() override {
static std::once_flag oc;
std::call_once(oc, []() {
SimpleFunctionFactory::instance().register_function<FunctionBeTestMock>();
+
SimpleFunctionFactory::instance().register_function<FunctionNullLiteralBeTestMock>();
});
}
@@ -74,6 +96,22 @@ TEST_F(SimpleFunctionFactoryTest, test_return_type_check) {
doris::Exception);
}
+TEST_F(SimpleFunctionFactoryTest,
test_null_literal_skips_return_type_inference) {
+ auto null_literal_type =
+
DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_NULL, true);
+ ASSERT_TRUE(null_literal_type->is_nullable());
+ ASSERT_TRUE(null_literal_type->is_null_literal());
+
+ ColumnsWithTypeAndName arguments = {{nullptr, null_literal_type, "null"}};
+ // The mock infers BIGINT, but a NULL literal should let the FE-provided
nullable type win.
+ auto expected_return_type =
make_nullable(std::make_shared<DataTypeInt32>());
+ FunctionBasePtr function;
+ ASSERT_NO_THROW(function = SimpleFunctionFactory::instance().get_function(
+ FunctionNullLiteralBeTestMock::name, arguments,
expected_return_type));
+ ASSERT_NE(function, nullptr);
+ EXPECT_TRUE(function->get_return_type()->equals(*expected_return_type));
+}
+
TEST_F(SimpleFunctionFactoryTest, test_return_all) {
auto factory = SimpleFunctionFactory::instance();
@@ -130,4 +168,4 @@ TEST_F(SimpleFunctionFactoryTest,
test_bitmap_count_new_version_return_type) {
}
}
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git
a/regression-test/data/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.out
b/regression-test/data/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.out
index 069148cd3f4..0ea4aa18c82 100644
---
a/regression-test/data/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.out
+++
b/regression-test/data/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.out
@@ -1,4 +1,19 @@
-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !array_zip_folded_null --
+\N
+
+-- !array_zip_null_first --
+\N
+
+-- !array_zip_null_middle --
+\N
+
+-- !array_zip_null_last --
+\N
+
+-- !array_zip_typed_null --
+\N
+
-- !sql --
array_enumerate_uniq
diff --git
a/regression-test/suites/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.groovy
b/regression-test/suites/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.groovy
index 82ed0c8a73c..205748653e6 100644
---
a/regression-test/suites/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.groovy
+++
b/regression-test/suites/datatype_p0/nested_types/query/array_functions/test_array_zip_array_enumerate_uniq.groovy
@@ -17,6 +17,26 @@
suite("test_array_zip_array_enumerate_uniq", "p0") {
// ========== array-zip ==========
+ sql "SET debug_skip_fold_constant = false"
+ order_qt_array_zip_folded_null """
+ SELECT array_zip([1.1, 2.2, 3.3], [1, 2, 3], NULL)
+ """
+
+ sql "SET debug_skip_fold_constant = true"
+ order_qt_array_zip_null_first """
+ SELECT array_zip(NULL, [1, 2, 3], ['a', 'b', 'c'])
+ """
+ order_qt_array_zip_null_middle """
+ SELECT array_zip([1.1, 2.2, 3.3], NULL, [1, 2, 3])
+ """
+ order_qt_array_zip_null_last """
+ SELECT array_zip([1.1, 2.2, 3.3], [1, 2, 3], NULL)
+ """
+ order_qt_array_zip_typed_null """
+ SELECT array_zip([1.1, 2.2, 3.3], CAST(NULL AS ARRAY<INT>), [1, 2, 3])
+ """
+ sql "SET debug_skip_fold_constant = false"
+
// wrong case
test {
sql """ SELECT array_zip() """
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]