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]

Reply via email to