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 ecf7732adbd [fix](function) Support cross-dimensional casts for null 
arrays (#65456)
ecf7732adbd is described below

commit ecf7732adbdb2cdbfb926ab2ba6bc83e86c9f80e
Author: Mryange <[email protected]>
AuthorDate: Fri Jul 17 08:49:26 2026 +0800

    [fix](function) Support cross-dimensional casts for null arrays (#65456)
    
    ### What problem does this PR solve?
    
    
    When constant folding was disabled, BE rejected casts from null-only
    arrays to arrays with a different number of dimensions, although FE
    constant folding already supported them. Root cause: the BE array cast
    checked dimensionality before recognizing that the source element type
    represented a NULL literal.
    
    This change allows cross-dimensional array casts when the source nested
    type is a NULL literal, while ordinary arrays still require matching
    dimensions. For example, `CAST([NULL, NULL] AS ARRAY<ARRAY<INT>>)` now
    returns `[NULL, NULL]`: BE casts the two nested NULL values to
    `Nullable<Array<Int>>` and rebuilds the outer array with its original
    offsets.
    
    ### Release note
    
    Support casting null-only arrays to array types with different
    dimensions in BE.
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] 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 <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 be/src/exprs/function/cast/cast_to_array.h         |  17 +--
 be/test/exprs/function/cast/cast_to_array_test.cpp |  91 ++++++++++++++++
 .../cast/test_cast_null_array_to_nested_array.out  |  37 +++++++
 .../test_cast_null_array_to_nested_array.groovy    | 120 +++++++++++++++++++++
 4 files changed, 259 insertions(+), 6 deletions(-)

diff --git a/be/src/exprs/function/cast/cast_to_array.h 
b/be/src/exprs/function/cast/cast_to_array.h
index c1d96c25f35..d4eb6886530 100644
--- a/be/src/exprs/function/cast/cast_to_array.h
+++ b/be/src/exprs/function/cast/cast_to_array.h
@@ -16,11 +16,19 @@
 // under the License.
 
 #include "core/column/column_array.h"
-#include "core/column/column_nullable.h"
 #include "core/data_type/data_type_array.h"
 #include "exprs/function/cast/cast_base.h"
 
 namespace doris::CastWrapper {
+inline bool has_null_literal_leaf(const DataTypePtr& type) {
+    const auto type_without_nullable = remove_nullable(type);
+    if (const auto* array_type =
+                
check_and_get_data_type<DataTypeArray>(type_without_nullable.get())) {
+        return has_null_literal_leaf(array_type->get_nested_type());
+    }
+    return type_without_nullable->is_null_literal();
+}
+
 WrapperType create_array_wrapper(FunctionContext* context, const DataTypePtr& 
from_type_untyped,
                                  const DataTypeArray& to_type) {
     /// Conversion from String through parsing.
@@ -42,11 +50,8 @@ WrapperType create_array_wrapper(FunctionContext* context, 
const DataTypePtr& fr
 
     DataTypePtr from_nested_type = from_type->get_nested_type();
 
-    /// In query SELECT CAST([] AS Array(Array(String))) from type is 
Array(Nothing)
-    bool from_empty_array = from_nested_type->get_primitive_type() == 
INVALID_TYPE;
-
     if (from_type->get_number_of_dimensions() != 
to_type.get_number_of_dimensions() &&
-        !from_empty_array) {
+        !has_null_literal_leaf(from_nested_type)) {
         return CastWrapper::create_unsupport_wrapper(
                 "CAST AS Array can only be performed between same-dimensional 
array types");
     }
@@ -90,4 +95,4 @@ WrapperType create_array_wrapper(FunctionContext* context, 
const DataTypePtr& fr
         return Status::OK();
     };
 }
-} // namespace doris::CastWrapper
\ No newline at end of file
+} // namespace doris::CastWrapper
diff --git a/be/test/exprs/function/cast/cast_to_array_test.cpp 
b/be/test/exprs/function/cast/cast_to_array_test.cpp
index 3ac5ac4476d..bf3b8a95b16 100644
--- a/be/test/exprs/function/cast/cast_to_array_test.cpp
+++ b/be/test/exprs/function/cast/cast_to_array_test.cpp
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "core/column/column_array.h"
+#include "core/data_type/data_type_array.h"
 #include "core/data_type/data_type_nullable.h"
 #include "core/data_type/data_type_number.h"
 #include "core/data_type/data_type_string.h"
@@ -196,4 +197,94 @@ TEST_F(FunctionCastTest, test_from_string_to_array_bool) {
                builder.build(), false);
 }
 
+TEST_F(FunctionCastTest, test_from_null_literal_array_to_nested_array) {
+    auto null_literal_type = std::make_shared<DataTypeBool>();
+    null_literal_type->set_null_literal(true);
+    auto from_nested_type = 
std::make_shared<DataTypeNullable>(null_literal_type);
+    auto from_type = std::make_shared<DataTypeArray>(from_nested_type);
+
+    auto from_nested_column =
+            ColumnHelper::create_nullable_column<DataTypeBool>({0, 0, 0}, 
{true, true, true});
+    auto from_offsets = ColumnHelper::create_column_offsets<TYPE_UINT64>({0, 
1, 3});
+    auto from_column = ColumnArray::create(std::move(from_nested_column), 
std::move(from_offsets));
+
+    auto to_type = std::make_shared<DataTypeArray>(
+            
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+
+    auto ctx = create_context(false);
+    auto fn = get_cast_wrapper(ctx.get(), from_type, to_type);
+    ASSERT_TRUE(fn != nullptr);
+
+    Block block = {
+            {std::move(from_column), from_type, "from"},
+            {nullptr, to_type, "to"},
+    };
+    ASSERT_TRUE(fn(ctx.get(), block, {0}, 1, block.rows(), nullptr));
+
+    const auto* result_array =
+            
check_and_get_column<ColumnArray>(block.get_by_position(1).column.get());
+    ASSERT_NE(result_array, nullptr);
+    EXPECT_EQ(result_array->get_offsets(), ColumnArray::Offsets64({0, 1, 3}));
+
+    const auto* result_nested_nullable =
+            check_and_get_column<ColumnNullable>(&result_array->get_data());
+    ASSERT_NE(result_nested_nullable, nullptr);
+    EXPECT_EQ(result_nested_nullable->get_null_map_data(), NullMap({1, 1, 1}));
+
+    const auto* result_nested_array =
+            
check_and_get_column<ColumnArray>(&result_nested_nullable->get_nested_column());
+    ASSERT_NE(result_nested_array, nullptr);
+    EXPECT_EQ(result_nested_array->size(), 3);
+    EXPECT_EQ(result_nested_array->get_offsets(), ColumnArray::Offsets64({0, 
0, 0}));
+}
+
+TEST_F(FunctionCastTest, test_from_nested_null_literal_array_to_deeper_array) {
+    auto null_literal_type = std::make_shared<DataTypeBool>();
+    null_literal_type->set_null_literal(true);
+    auto from_inner_type = std::make_shared<DataTypeArray>(null_literal_type);
+    auto from_type = std::make_shared<DataTypeArray>(from_inner_type);
+
+    auto leaf_column = ColumnHelper::create_nullable_column<DataTypeBool>({0}, 
{true});
+    auto inner_offsets = ColumnHelper::create_column_offsets<TYPE_UINT64>({1});
+    auto inner_column = ColumnArray::create(std::move(leaf_column), 
std::move(inner_offsets));
+    auto inner_null_map = ColumnHelper::create_column<DataTypeUInt8>({false});
+    auto nullable_inner_column =
+            ColumnNullable::create(std::move(inner_column), 
std::move(inner_null_map));
+    auto outer_offsets = ColumnHelper::create_column_offsets<TYPE_UINT64>({1});
+    auto from_column =
+            ColumnArray::create(std::move(nullable_inner_column), 
std::move(outer_offsets));
+
+    auto to_type = 
std::make_shared<DataTypeArray>(std::make_shared<DataTypeArray>(
+            
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>())));
+
+    auto ctx = create_context(false);
+    auto fn = get_cast_wrapper(ctx.get(), from_type, to_type);
+    ASSERT_TRUE(fn != nullptr);
+
+    Block block = {
+            {std::move(from_column), from_type, "from"},
+            {nullptr, to_type, "to"},
+    };
+    ASSERT_TRUE(fn(ctx.get(), block, {0}, 1, block.rows(), nullptr));
+    EXPECT_EQ(to_type->to_string(*block.get_by_position(1).column, 0), 
"[[null]]");
+}
+
+TEST_F(FunctionCastTest, test_from_non_null_array_to_nested_array_is_rejected) 
{
+    ColumnArrayBuilder<DataTypeInt32> builder;
+    builder.add({1});
+    auto from = builder.build();
+    auto to_type = std::make_shared<DataTypeArray>(
+            
std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+
+    auto ctx = create_context(false);
+    auto fn = get_cast_wrapper(ctx.get(), from.type, to_type);
+    ASSERT_TRUE(fn != nullptr);
+
+    Block block = {
+            from,
+            {nullptr, to_type, "to"},
+    };
+    EXPECT_FALSE(fn(ctx.get(), block, {0}, 1, block.rows(), nullptr));
+}
+
 } // namespace doris
diff --git 
a/regression-test/data/function_p0/cast/test_cast_null_array_to_nested_array.out
 
b/regression-test/data/function_p0/cast/test_cast_null_array_to_nested_array.out
new file mode 100644
index 00000000000..4a8dd0c8b72
--- /dev/null
+++ 
b/regression-test/data/function_p0/cast/test_cast_null_array_to_nested_array.out
@@ -0,0 +1,37 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !cast_empty_array_to_nested_array_fold --
+[]
+
+-- !cast_null_array_to_nested_array_fold --
+[null]
+
+-- !cast_nulls_array_to_nested_array_fold --
+[null, null]
+
+-- !cast_nested_null_array_to_deeper_array_fold --
+[[null]]
+
+-- !cast_deeply_nested_null_array_to_deeper_array_fold --
+[[[null]]]
+
+-- !cast_nested_null_array_to_same_dimension_fold --
+[[null]]
+
+-- !cast_empty_array_to_nested_array_be --
+[]
+
+-- !cast_null_array_to_nested_array_be --
+[null]
+
+-- !cast_nulls_array_to_nested_array_be --
+[null, null]
+
+-- !cast_nested_null_array_to_deeper_array_be --
+[[null]]
+
+-- !cast_deeply_nested_null_array_to_deeper_array_be --
+[[[null]]]
+
+-- !cast_nested_null_array_to_same_dimension_be --
+[[null]]
+
diff --git 
a/regression-test/suites/function_p0/cast/test_cast_null_array_to_nested_array.groovy
 
b/regression-test/suites/function_p0/cast/test_cast_null_array_to_nested_array.groovy
new file mode 100644
index 00000000000..a777a90eff7
--- /dev/null
+++ 
b/regression-test/suites/function_p0/cast/test_cast_null_array_to_nested_array.groovy
@@ -0,0 +1,120 @@
+// 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_cast_null_array_to_nested_array") {
+    sql "set debug_skip_fold_constant = false;"
+
+    qt_cast_empty_array_to_nested_array_fold """
+        select cast([] as array<array<int>>);
+    """
+
+    qt_cast_null_array_to_nested_array_fold """
+        select cast([null] as array<array<int>>);
+    """
+
+    qt_cast_nulls_array_to_nested_array_fold """
+        select cast([null, null] as array<array<int>>);
+    """
+
+    qt_cast_nested_null_array_to_deeper_array_fold """
+        select cast([[null]] as array<array<array<int>>>);
+    """
+
+    qt_cast_deeply_nested_null_array_to_deeper_array_fold """
+        select cast([[[null]]] as array<array<array<array<int>>>>);
+    """
+
+    qt_cast_nested_null_array_to_same_dimension_fold """
+        select cast([[null]] as array<array<int>>);
+    """
+
+    test {
+        sql "select cast([[null]] as array<int>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([[[null]]] as array<array<int>>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([[1]] as array<array<array<int>>>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([1] as array<array<int>>);"
+        exception "can not cast from origin type ARRAY<TINYINT> to target 
type=ARRAY<ARRAY<INT>>"
+    }
+
+    test {
+        sql "select cast([1, null] as array<array<int>>);"
+        exception "can not cast from origin type ARRAY<TINYINT> to target 
type=ARRAY<ARRAY<INT>>"
+    }
+
+    sql "set debug_skip_fold_constant = true;"
+
+    qt_cast_empty_array_to_nested_array_be """
+        select cast([] as array<array<int>>);
+    """
+
+    qt_cast_null_array_to_nested_array_be """
+        select cast([null] as array<array<int>>);
+    """
+
+    qt_cast_nulls_array_to_nested_array_be """
+        select cast([null, null] as array<array<int>>);
+    """
+
+    qt_cast_nested_null_array_to_deeper_array_be """
+        select cast([[null]] as array<array<array<int>>>);
+    """
+
+    qt_cast_deeply_nested_null_array_to_deeper_array_be """
+        select cast([[[null]]] as array<array<array<array<int>>>>);
+    """
+
+    qt_cast_nested_null_array_to_same_dimension_be """
+        select cast([[null]] as array<array<int>>);
+    """
+
+    test {
+        sql "select cast([[null]] as array<int>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([[[null]]] as array<array<int>>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([[1]] as array<array<array<int>>>);"
+        exception "can not cast from origin type"
+    }
+
+    test {
+        sql "select cast([1] as array<array<int>>);"
+        exception "can not cast from origin type ARRAY<TINYINT> to target 
type=ARRAY<ARRAY<INT>>"
+    }
+
+    test {
+        sql "select cast([1, null] as array<array<int>>);"
+        exception "can not cast from origin type ARRAY<TINYINT> to target 
type=ARRAY<ARRAY<INT>>"
+    }
+}


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

Reply via email to