This is an automated email from the ASF dual-hosted git repository.
mrhhsg 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 74ae7d20b20 [fix](function) Evaluate element_at over a bare NULL
container to NULL (#68114)
74ae7d20b20 is described below
commit 74ae7d20b202afca27dc035f910d5fba6fefdea6
Author: Jerry Hu <[email protected]>
AuthorDate: Fri Sep 18 16:29:56 2026 +0800
[fix](function) Evaluate element_at over a bare NULL container to NULL
(#68114)
### What problem does this PR solve?
Issue Number: None
Problem Summary:
When constant folding is skipped (`debug_skip_fold_constant = true`),
`element_at(NULL, 1)` is not folded on the FE and reaches the BE with
the container typed as a null literal (`TYPE_NULL`, mocked as `UInt8` in
the BE). `element_at` opts out of the default null handling, so its
return type resolution treated the mock type as a real `BOOL` container
and failed with:
```
[INVALID_ARGUMENT][E33] element_at only support array and map so far, but
got BOOL
```
In debug builds the preceding `DCHECK` fires before that error is
raised.
This change makes `element_at` recognize a null-literal container the
same way the default null implementation does: the return type resolves
to `Nullable(Nothing)` and execution produces a NULL for every row,
which matches the folded result.
```sql
SET debug_skip_fold_constant = true;
SELECT element_at(NULL, 1); -- NULL (previously: error "got BOOL")
SELECT element_at(NULL, 'k'); -- NULL
```
### Release note
None
### Check List (For Author)
- Test:
- Unit Test:
`function_array_element_test.element_at_null_literal_container`
- Regression test:
`query_p0/sql_functions/array_functions/test_element_at_null_container`
- Behavior changed: No
- Does this need documentation: No
### Check List (For Reviewer who merge this PR)
- Confirm the release note
- Confirm test cases
- Confirm document
- Add branch pick label
https://claude.ai/code/session_01HXcF45DSFT6fMDvtdNz2w8
---
.../exprs/function/array/function_array_element.h | 13 ++++++++
.../exprs/function/function_array_element_test.cpp | 38 ++++++++++++++++++++++
.../test_element_at_null_container.out | 18 ++++++++++
.../test_element_at_null_container.groovy | 31 ++++++++++++++++++
4 files changed, 100 insertions(+)
diff --git a/be/src/exprs/function/array/function_array_element.h
b/be/src/exprs/function/array/function_array_element.h
index 01e10f145f7..253853c9f0f 100644
--- a/be/src/exprs/function/array/function_array_element.h
+++ b/be/src/exprs/function/array/function_array_element.h
@@ -49,6 +49,7 @@
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_nothing.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_struct.h"
@@ -105,6 +106,12 @@ public:
}
DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
+ // A bare NULL container (element_at(NULL, x)) reaches the BE as a
null-literal type when
+ // the FE skips constant folding. It carries no array/map type, so
resolve it the way the
+ // default null implementation does (Nullable(Nothing)) instead of
reading it as BOOL.
+ if (arguments[0]->is_null_literal()) {
+ return make_nullable(std::make_shared<DataTypeNothing>());
+ }
DataTypePtr arg_0 = remove_nullable(arguments[0]);
DCHECK(arg_0->get_primitive_type() == TYPE_ARRAY ||
arg_0->get_primitive_type() == TYPE_MAP)
<< "first argument for function: " << name
@@ -128,6 +135,12 @@ public:
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
+ if (block.get_by_position(arguments[0]).type->is_null_literal()) {
+ // Null-literal container: every row is NULL, matching the
Nullable(Nothing) return type.
+ auto& res = block.get_by_position(result);
+ res.column =
res.type->create_column_const_with_default_value(input_rows_count);
+ return Status::OK();
+ }
if
(remove_nullable(block.get_by_position(arguments[0]).type)->get_primitive_type()
==
TYPE_STRUCT) {
return _execute_struct(block, arguments, result, input_rows_count);
diff --git a/be/test/exprs/function/function_array_element_test.cpp
b/be/test/exprs/function/function_array_element_test.cpp
index 644e6aec1b6..756e7bf29f5 100644
--- a/be/test/exprs/function/function_array_element_test.cpp
+++ b/be/test/exprs/function/function_array_element_test.cpp
@@ -27,6 +27,7 @@
#include "core/data_type/data_type_date.h"
#include "core/data_type/data_type_date_time.h"
#include "core/data_type/data_type_decimal.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"
#include "core/data_type/data_type_string.h"
@@ -319,6 +320,43 @@ TEST(function_array_element_test,
element_at_const_array_null_index) {
}
}
+// element_at(NULL, idx): with constant folding skipped the FE hands the BE a
bare NULL container
+// typed as a null literal (TYPE_NULL). It carries no array/map type and must
evaluate to NULL
+// rather than being rejected as BOOL.
+TEST(function_array_element_test, element_at_null_literal_container) {
+ constexpr size_t N = 3;
+
+ // Mirror the FE plan: both the container argument and the result are
typed TYPE_NULL.
+ auto null_literal_type =
+
DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_NULL, true);
+ ASSERT_TRUE(null_literal_type->is_null_literal());
+ ColumnPtr const_null_container =
null_literal_type->create_column_const_with_default_value(N);
+
+ auto run_with_index = [&](ColumnPtr idx_col, DataTypePtr idx_type) {
+ auto result = run_element_at(const_null_container, null_literal_type,
std::move(idx_col),
+ idx_type, null_literal_type, N);
+ ASSERT_EQ(result->size(), N);
+ auto full = result->convert_to_full_column_if_const();
+ for (size_t i = 0; i < N; ++i) {
+ EXPECT_TRUE(full->is_null_at(i)) << "row " << i;
+ }
+ };
+
+ // Array-style integer index.
+ auto idx_data = ColumnInt32::create();
+ for (Int32 v : {1, 2, 3}) {
+ idx_data->insert_value(v);
+ }
+ run_with_index(std::move(idx_data), std::make_shared<DataTypeInt32>());
+
+ // Map-style string key.
+ auto key_data = ColumnString::create();
+ for (const auto& v : {std::string("a"), std::string("b"),
std::string("c")}) {
+ key_data->insert_data(v.data(), v.size());
+ }
+ run_with_index(std::move(key_data), std::make_shared<DataTypeString>());
+}
+
// Const Array(String) – exercises _execute_string code path.
TEST(function_array_element_test, element_at_const_string_array_varying_index)
{
constexpr size_t N = 5;
diff --git
a/regression-test/data/query_p0/sql_functions/array_functions/test_element_at_null_container.out
b/regression-test/data/query_p0/sql_functions/array_functions/test_element_at_null_container.out
new file mode 100644
index 00000000000..ea0e9d872fc
--- /dev/null
+++
b/regression-test/data/query_p0/sql_functions/array_functions/test_element_at_null_container.out
@@ -0,0 +1,18 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !null_container_int_index --
+\N
+
+-- !null_container_string_key --
+\N
+
+-- !null_container_null_index --
+\N
+
+-- !null_container_from_rows --
+\N
+\N
+\N
+
+-- !null_container_folded --
+\N
+
diff --git
a/regression-test/suites/query_p0/sql_functions/array_functions/test_element_at_null_container.groovy
b/regression-test/suites/query_p0/sql_functions/array_functions/test_element_at_null_container.groovy
new file mode 100644
index 00000000000..337fb08e75b
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/array_functions/test_element_at_null_container.groovy
@@ -0,0 +1,31 @@
+// 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_element_at_null_container") {
+ // A bare NULL container carries no array/map type. When constant folding
is skipped the
+ // expression reaches the BE as a null-literal argument and must still
evaluate to NULL
+ // instead of being rejected as an unsupported BOOL container.
+ sql "set debug_skip_fold_constant = true"
+ qt_null_container_int_index "select element_at(NULL, 1)"
+ qt_null_container_string_key "select element_at(NULL, 'k')"
+ qt_null_container_null_index "select element_at(NULL, NULL)"
+ qt_null_container_from_rows "select element_at(NULL, number) from
numbers('number' = '3') order by number"
+
+ // The folded path must agree with the BE evaluation above.
+ sql "set debug_skip_fold_constant = false"
+ qt_null_container_folded "select element_at(NULL, 1)"
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]