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

dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.0 by this push:
     new 39542191c23 [Improve](explode) explode function support multi param 
(#50793)
39542191c23 is described below

commit 39542191c23918587c74fa7e70f3d406c6cb099e
Author: Benjaminwei <[email protected]>
AuthorDate: Wed May 14 10:01:04 2025 +0800

    [Improve](explode) explode function support multi param (#50793)
    
    ### What problem does this PR solve?
    backport:https://github.com/apache/doris/pull/48537
---
 be/src/pipeline/exec/table_function_operator.cpp   |   7 +-
 .../table_function/table_function_factory.cpp      |  19 +-
 .../exprs/table_function/table_function_factory.h  |  17 +-
 be/src/vec/exprs/table_function/vexplode_v2.cpp    | 246 +++++++++++++++++++++
 be/src/vec/exprs/table_function/vexplode_v2.h      |  58 +++++
 be/src/vec/functions/function_fake.cpp             |  68 +++++-
 be/src/vec/functions/simple_function_factory.h     |  17 +-
 be/test/vec/function/function_test_util.cpp        | 125 +++++++++--
 be/test/vec/function/function_test_util.h          |   3 +-
 be/test/vec/function/table_function_test.cpp       | 163 +++++++++++++-
 .../catalog/BuiltinTableGeneratingFunctions.java   |  11 +
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  10 +-
 .../expressions/functions/generator/Explode.java   |  61 +++--
 .../functions/generator/ExplodeOuter.java          |  61 +++--
 .../functions/generator/ExplodeVariantArray.java   |  57 +++--
 .../functions/generator/ExplodeOuterTest.java      |  95 ++++++++
 .../functions/generator/ExplodeTest.java           |  95 ++++++++
 .../generator/ExplodeVariantArrayTest.java         |  68 ++++++
 .../sql_functions/table_function/explode.out       | Bin 25904 -> 27843 bytes
 .../hive/test_parquet_nested_types.groovy          |   8 +-
 .../sql_functions/table_function/explode.groovy    |  51 +++++
 21 files changed, 1146 insertions(+), 94 deletions(-)

diff --git a/be/src/pipeline/exec/table_function_operator.cpp 
b/be/src/pipeline/exec/table_function_operator.cpp
index c1621470f43..3701520907a 100644
--- a/be/src/pipeline/exec/table_function_operator.cpp
+++ b/be/src/pipeline/exec/table_function_operator.cpp
@@ -54,8 +54,8 @@ Status TableFunctionLocalState::open(RuntimeState* state) {
         RETURN_IF_ERROR(p._vfn_ctxs[i]->clone(state, _vfn_ctxs[i]));
 
         vectorized::TableFunction* fn = nullptr;
-        
RETURN_IF_ERROR(vectorized::TableFunctionFactory::get_fn(_vfn_ctxs[i]->root()->fn(),
-                                                                 
state->obj_pool(), &fn));
+        RETURN_IF_ERROR(vectorized::TableFunctionFactory::get_fn(
+                _vfn_ctxs[i]->root()->fn(), state->obj_pool(), &fn, 
state->be_exec_version()));
         fn->set_expr_context(_vfn_ctxs[i]);
         _fns.push_back(fn);
     }
@@ -272,7 +272,8 @@ Status TableFunctionOperatorX::init(const TPlanNode& tnode, 
RuntimeState* state)
 
         auto root = ctx->root();
         vectorized::TableFunction* fn = nullptr;
-        RETURN_IF_ERROR(vectorized::TableFunctionFactory::get_fn(root->fn(), 
_pool, &fn));
+        RETURN_IF_ERROR(vectorized::TableFunctionFactory::get_fn(root->fn(), 
_pool, &fn,
+                                                                 
state->be_exec_version()));
         fn->set_expr_context(ctx);
         _fns.push_back(fn);
     }
diff --git a/be/src/vec/exprs/table_function/table_function_factory.cpp 
b/be/src/vec/exprs/table_function/table_function_factory.cpp
index 332eaed37d4..23918f4fae5 100644
--- a/be/src/vec/exprs/table_function/table_function_factory.cpp
+++ b/be/src/vec/exprs/table_function/table_function_factory.cpp
@@ -23,6 +23,7 @@
 #include <string_view>
 #include <utility>
 
+#include "agent/be_exec_version_manager.h"
 #include "common/object_pool.h"
 #include "vec/exprs/table_function/table_function.h"
 #include "vec/exprs/table_function/udf_table_function.h"
@@ -33,6 +34,7 @@
 #include "vec/exprs/table_function/vexplode_map.h"
 #include "vec/exprs/table_function/vexplode_numbers.h"
 #include "vec/exprs/table_function/vexplode_split.h"
+#include "vec/exprs/table_function/vexplode_v2.h"
 #include "vec/exprs/table_function/vposexplode.h"
 #include "vec/utils/util.hpp"
 
@@ -52,7 +54,7 @@ struct VExplodeJsonArrayCreator {
 
 const std::unordered_map<std::string, 
std::function<std::unique_ptr<TableFunction>()>>
         TableFunctionFactory::_function_map {
-                {"explode_variant_array", 
TableFunctionCreator<VExplodeTableFunction>()},
+                {"explode_variant_array", 
TableFunctionCreator<VExplodeV2TableFunction>()},
                 {"explode_split", 
TableFunctionCreator<VExplodeSplitTableFunction>()},
                 {"explode_numbers", 
TableFunctionCreator<VExplodeNumbersTableFunction>()},
                 {"explode_json_array_int", 
VExplodeJsonArrayCreator<ParsedDataInt>()},
@@ -63,9 +65,15 @@ const std::unordered_map<std::string, 
std::function<std::unique_ptr<TableFunctio
                 {"explode_map", TableFunctionCreator<VExplodeMapTableFunction> 
{}},
                 {"explode_json_object", 
TableFunctionCreator<VExplodeJsonObjectTableFunction> {}},
                 {"posexplode", TableFunctionCreator<VPosExplodeTableFunction> 
{}},
-                {"explode", TableFunctionCreator<VExplodeTableFunction> {}}};
+                {"explode", TableFunctionCreator<VExplodeV2TableFunction> {}},
+                {"explode_variant_array_old", 
TableFunctionCreator<VExplodeTableFunction>()},
+                {"explode_old", TableFunctionCreator<VExplodeTableFunction> 
{}}};
 
-Status TableFunctionFactory::get_fn(const TFunction& t_fn, ObjectPool* pool, 
TableFunction** fn) {
+const std::unordered_map<std::string, std::string> 
TableFunctionFactory::_function_to_replace = {
+        {"explode", "explode_old"}, {"explode_variant_array", 
"explode_variant_array_old"}};
+
+Status TableFunctionFactory::get_fn(const TFunction& t_fn, ObjectPool* pool, 
TableFunction** fn,
+                                    int be_version = 
BeExecVersionManager::get_newest_version()) {
     bool is_outer = match_suffix(t_fn.name.function_name, 
COMBINATOR_SUFFIX_OUTER);
     if (t_fn.binary_type == TFunctionBinaryType::JAVA_UDF) {
         *fn = pool->add(UDFTableFunction::create_unique(t_fn).release());
@@ -78,7 +86,10 @@ Status TableFunctionFactory::get_fn(const TFunction& t_fn, 
ObjectPool* pool, Tab
         const std::string& fn_name_real =
                 is_outer ? remove_suffix(fn_name_raw, COMBINATOR_SUFFIX_OUTER) 
: fn_name_raw;
 
-        auto fn_iterator = _function_map.find(fn_name_real);
+        std::string fn_name_real_temp = fn_name_real;
+        temporary_function_update(be_version, fn_name_real_temp);
+
+        auto fn_iterator = _function_map.find(fn_name_real_temp);
         if (fn_iterator != _function_map.end()) {
             *fn = pool->add(fn_iterator->second().release());
             if (is_outer) {
diff --git a/be/src/vec/exprs/table_function/table_function_factory.h 
b/be/src/vec/exprs/table_function/table_function_factory.h
index cd06c202f37..46d1912c8c6 100644
--- a/be/src/vec/exprs/table_function/table_function_factory.h
+++ b/be/src/vec/exprs/table_function/table_function_factory.h
@@ -35,10 +35,25 @@ class TableFunction;
 class TableFunctionFactory {
 public:
     TableFunctionFactory() = delete;
-    static Status get_fn(const TFunction& t_fn, ObjectPool* pool, 
TableFunction** fn);
+    static Status get_fn(const TFunction& t_fn, ObjectPool* pool, 
TableFunction** fn,
+                         int be_version);
 
     const static std::unordered_map<std::string, 
std::function<std::unique_ptr<TableFunction>()>>
             _function_map;
+
+    /// @TEMPORARY: for be_exec_version=8. replace function to old version.
+    const static std::unordered_map<std::string, std::string> 
_function_to_replace;
+    const static int NEWEST_VERSION_EXPLODE_MULTI_PARAM = 8;
+
+private:
+    /// @TEMPORARY: for be_exec_version=8
+    static void temporary_function_update(int fe_version_now, std::string& 
name) {
+        // replace if fe is old version.
+        if (fe_version_now < NEWEST_VERSION_EXPLODE_MULTI_PARAM &&
+            _function_to_replace.find(name) != _function_to_replace.end()) {
+            name = _function_to_replace.at(name);
+        }
+    }
 };
 } // namespace vectorized
 } // namespace doris
diff --git a/be/src/vec/exprs/table_function/vexplode_v2.cpp 
b/be/src/vec/exprs/table_function/vexplode_v2.cpp
new file mode 100644
index 00000000000..ccebae91f34
--- /dev/null
+++ b/be/src/vec/exprs/table_function/vexplode_v2.cpp
@@ -0,0 +1,246 @@
+// 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.
+
+#include "vec/exprs/table_function/vexplode_v2.h"
+
+#include <glog/logging.h>
+
+#include <ostream>
+
+#include "common/status.h"
+#include "vec/columns/column.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_nothing.h"
+#include "vec/columns/column_object.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_nothing.h"
+#include "vec/exprs/vexpr.h"
+#include "vec/exprs/vexpr_context.h"
+#include "vec/functions/function_helpers.h"
+
+namespace doris::vectorized {
+
+#include "common/compile_check_begin.h"
+#include "vec/columns/column_struct.h"
+
+VExplodeV2TableFunction::VExplodeV2TableFunction() {
+    _fn_name = "vexplode";
+}
+
+Status VExplodeV2TableFunction::_process_init_variant(Block* block, int 
value_column_idx,
+                                                      int children_column_idx) 
{
+    // explode variant array
+    auto column_without_nullable = 
remove_nullable(block->get_by_position(value_column_idx).column);
+    auto column = column_without_nullable->convert_to_full_column_if_const();
+    const auto& variant_column = assert_cast<const ColumnObject&>(*column);
+    _multi_detail[children_column_idx].output_as_variant = true;
+    if (!variant_column.is_null_root()) {
+        _array_columns[children_column_idx] = variant_column.get_root();
+        // We need to wrap the output nested column within a variant column.
+        // Otherwise the type is missmatched
+        const auto* array_type = check_and_get_data_type<DataTypeArray>(
+                remove_nullable(variant_column.get_root_type()).get());
+        if (array_type == nullptr) {
+            return Status::NotSupported("explode not support none array type 
{}",
+                                        
variant_column.get_root_type()->get_name());
+        }
+        _multi_detail[children_column_idx].nested_type = 
array_type->get_nested_type();
+    } else {
+        // null root, use nothing type
+        _array_columns[children_column_idx] = ColumnNullable::create(
+                ColumnArray::create(ColumnNothing::create(0)), 
ColumnUInt8::create(0));
+        
_array_columns[children_column_idx]->assume_mutable()->insert_many_defaults(
+                variant_column.size());
+        _multi_detail[children_column_idx].nested_type = 
std::make_shared<DataTypeNothing>();
+    }
+    return Status::OK();
+}
+
+Status VExplodeV2TableFunction::process_init(Block* block, RuntimeState* 
state) {
+    CHECK(_expr_context->root()->children().size() >= 1)
+            << "VExplodeV2TableFunction support one or more child but has "
+            << _expr_context->root()->children().size();
+
+    int value_column_idx = -1;
+    _multi_detail.resize(_expr_context->root()->children().size());
+    _array_offsets.resize(_expr_context->root()->children().size());
+    _array_columns.resize(_expr_context->root()->children().size());
+
+    for (int i = 0; i < _expr_context->root()->children().size(); i++) {
+        
RETURN_IF_ERROR(_expr_context->root()->children()[i]->execute(_expr_context.get(),
 block,
+                                                                      
&value_column_idx));
+        if 
(WhichDataType(remove_nullable(block->get_by_position(value_column_idx).type))
+                    .is_variant_type()) {
+            RETURN_IF_ERROR(_process_init_variant(block, value_column_idx, i));
+        } else {
+            _array_columns[i] = block->get_by_position(value_column_idx)
+                                        
.column->convert_to_full_column_if_const();
+        }
+        if (!extract_column_array_info(*_array_columns[i], _multi_detail[i])) {
+            return Status::NotSupported(
+                    "column type {} not supported now",
+                    
block->get_by_position(value_column_idx).column->get_name());
+        }
+    }
+
+    return Status::OK();
+}
+
+void VExplodeV2TableFunction::process_row(size_t row_idx) {
+    TableFunction::process_row(row_idx);
+
+    for (int i = 0; i < _multi_detail.size(); i++) {
+        auto& detail = _multi_detail[i];
+        if (!detail.array_nullmap_data || !detail.array_nullmap_data[row_idx]) 
{
+            _array_offsets[i] = (*detail.offsets_ptr)[row_idx - 1];
+            // find max size in array
+            auto cur_size = (*detail.offsets_ptr)[row_idx] - _array_offsets[i];
+            if (_cur_size < cur_size) {
+                _cur_size = cur_size;
+            }
+        }
+    }
+    _row_idx = row_idx;
+}
+
+void VExplodeV2TableFunction::process_close() {
+    _multi_detail.clear();
+    _array_offsets.clear();
+    _array_columns.clear();
+    _row_idx = 0;
+}
+
+void VExplodeV2TableFunction::get_same_many_values(MutableColumnPtr& column, 
int length) {
+    if (current_empty()) {
+        column->insert_many_defaults(length);
+        return;
+    }
+    ColumnStruct* struct_column = nullptr;
+    if (_is_nullable) {
+        auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
+        struct_column = 
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
+        auto* nullmap_column =
+                
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+        nullmap_column->insert_many_defaults(length);
+    } else {
+        struct_column = assert_cast<ColumnStruct*>(column.get());
+    }
+    if (!struct_column) {
+        throw Exception(ErrorCode::INTERNAL_ERROR,
+                        "Only multiple columns can be returned within a 
struct.");
+    }
+    for (int i = 0; i < _multi_detail.size(); i++) {
+        auto& detail = _multi_detail[i];
+        size_t pos = _array_offsets[i] + _cur_offset;
+        size_t element_size = _multi_detail[i].array_col->size_at(_row_idx);
+        auto& struct_field = struct_column->get_column(i);
+        if ((detail.array_nullmap_data && 
detail.array_nullmap_data[_row_idx])) {
+            struct_field.insert_many_defaults(length);
+        } else {
+            auto* nullable_column = 
assert_cast<ColumnNullable*>(struct_field.get_ptr().get());
+            auto* nullmap_column =
+                    
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+            // only need to check if the value at position pos is null
+            if (element_size < _cur_offset ||
+                (detail.nested_nullmap_data && 
detail.nested_nullmap_data[pos])) {
+                nullable_column->insert_many_defaults(length);
+            } else {
+                
nullable_column->get_nested_column_ptr()->insert_many_from(*detail.nested_col, 
pos,
+                                                                           
length);
+                nullmap_column->insert_many_defaults(length);
+            }
+        }
+    }
+}
+
+int VExplodeV2TableFunction::get_value(MutableColumnPtr& column, int max_step) 
{
+    max_step = std::min(max_step, (int)(_cur_size - _cur_offset));
+    if (current_empty()) {
+        column->insert_default();
+        max_step = 1;
+    } else {
+        ColumnStruct* struct_column = nullptr;
+        if (_is_nullable) {
+            auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
+            struct_column =
+                    
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
+            auto* nullmap_column =
+                    
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+            nullmap_column->insert_many_defaults(max_step);
+
+        } else {
+            struct_column = assert_cast<ColumnStruct*>(column.get());
+        }
+        if (!struct_column) {
+            throw Exception(ErrorCode::INTERNAL_ERROR,
+                            "Only multiple columns can be returned within a 
struct.");
+        }
+        for (int i = 0; i < _multi_detail.size(); i++) {
+            auto& detail = _multi_detail[i];
+            size_t pos = _array_offsets[i] + _cur_offset;
+            size_t element_size = 
_multi_detail[i].array_col->size_at(_row_idx);
+            auto& struct_field = struct_column->get_column(i);
+            if (detail.array_nullmap_data && 
detail.array_nullmap_data[_row_idx]) {
+                struct_field.insert_many_defaults(max_step);
+            } else {
+                auto* nullable_column = 
assert_cast<ColumnNullable*>(struct_field.get_ptr().get());
+                auto* nullmap_column =
+                        
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+                if (element_size >= _cur_offset + max_step) {
+                    
nullable_column->get_nested_column_ptr()->insert_range_from(*detail.nested_col,
+                                                                               
 pos, max_step);
+                    if (detail.nested_nullmap_data) {
+                        for (int j = 0; j < max_step; j++) {
+                            if (detail.nested_nullmap_data[pos + j]) {
+                                nullmap_column->insert_value(1);
+                            } else {
+                                nullmap_column->insert_value(0);
+                            }
+                        }
+                    } else {
+                        nullmap_column->insert_many_defaults(max_step);
+                    }
+                } else {
+                    
nullable_column->get_nested_column_ptr()->insert_range_from(
+                            *detail.nested_col, pos, element_size - 
_cur_offset);
+                    if (detail.nested_nullmap_data) {
+                        for (int j = 0; j < element_size - _cur_offset; j++) {
+                            if (detail.nested_nullmap_data[pos + j]) {
+                                nullmap_column->insert_value(1);
+                            } else {
+                                nullmap_column->insert_value(0);
+                            }
+                        }
+                    } else {
+                        nullmap_column->insert_many_defaults(element_size - 
_cur_offset);
+                    }
+                    nullable_column->insert_many_defaults(max_step - 
(element_size - _cur_offset));
+                }
+            }
+        }
+    }
+
+    forward(max_step);
+    return max_step;
+}
+
+#include "common/compile_check_end.h"
+
+} // namespace doris::vectorized
diff --git a/be/src/vec/exprs/table_function/vexplode_v2.h 
b/be/src/vec/exprs/table_function/vexplode_v2.h
new file mode 100644
index 00000000000..20853f1ad18
--- /dev/null
+++ b/be/src/vec/exprs/table_function/vexplode_v2.h
@@ -0,0 +1,58 @@
+// 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.
+
+#pragma once
+
+#include <cstddef>
+
+#include "common/status.h"
+#include "vec/columns/column_struct.h"
+#include "vec/data_types/data_type.h"
+#include "vec/exprs/table_function/table_function.h"
+#include "vec/functions/array/function_array_utils.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+class Block;
+} // namespace doris::vectorized
+
+namespace doris::vectorized {
+
+class VExplodeV2TableFunction : public TableFunction {
+    ENABLE_FACTORY_CREATOR(VExplodeV2TableFunction);
+
+public:
+    VExplodeV2TableFunction();
+
+    ~VExplodeV2TableFunction() override = default;
+
+    Status process_init(Block* block, RuntimeState* state) override;
+    void process_row(size_t row_idx) override;
+    void process_close() override;
+    void get_same_many_values(MutableColumnPtr& column, int length) override;
+    int get_value(MutableColumnPtr& column, int max_step) override;
+
+private:
+    Status _process_init_variant(Block* block, int value_column_idx, int 
children_column_idx);
+    std::vector<ColumnPtr> _array_columns;
+    size_t _row_idx;
+    ColumnArrayExecutionDatas _multi_detail;
+    std::vector<size_t> _array_offsets;
+};
+
+#include "common/compile_check_end.h"
+} // namespace doris::vectorized
diff --git a/be/src/vec/functions/function_fake.cpp 
b/be/src/vec/functions/function_fake.cpp
index 8ba400e7944..70828fa4a68 100644
--- a/be/src/vec/functions/function_fake.cpp
+++ b/be/src/vec/functions/function_fake.cpp
@@ -68,6 +68,33 @@ struct FunctionExplode {
     static std::string get_error_msg() { return "Fake function do not support 
execute"; }
 };
 
+struct FunctionExplodeV2 {
+    static DataTypePtr get_return_type_impl(const DataTypes& arguments) {
+        DataTypes fieldTypes(arguments.size());
+        for (int i = 0; i < arguments.size(); i++) {
+            if (arguments[i]->get_type_id() == TypeIndex::VARIANT) {
+                if (arguments[i]->is_nullable()) {
+                    fieldTypes[i] = arguments[i];
+                } else {
+                    fieldTypes[i] = make_nullable(arguments[i]);
+                }
+            } else {
+                auto nestedType = 
check_and_get_data_type<DataTypeArray>(arguments[i].get())
+                                          ->get_nested_type();
+                if (nestedType->is_nullable()) {
+                    fieldTypes[i] = nestedType;
+                } else {
+                    fieldTypes[i] = make_nullable(nestedType);
+                }
+            }
+        }
+
+        return 
make_nullable(std::make_shared<vectorized::DataTypeStruct>(fieldTypes));
+    }
+    static DataTypes get_variadic_argument_types() { return {}; }
+    static std::string get_error_msg() { return "Fake function do not support 
execute"; }
+};
+
 // explode map: make map k,v as struct field
 struct FunctionExplodeMap {
     static DataTypePtr get_return_type_impl(const DataTypes& arguments) {
@@ -134,6 +161,14 @@ void register_table_function_expand(SimpleFunctionFactory& 
factory, const std::s
     factory.register_function<FunctionFake<FunctionImpl>>(name + suffix);
 };
 
+template <typename FunctionImpl>
+void register_table_alternative_function_expand(SimpleFunctionFactory& factory,
+                                                const std::string& name,
+                                                const std::string& suffix) {
+    factory.register_alternative_function<FunctionFake<FunctionImpl>>(name);
+    factory.register_alternative_function<FunctionFake<FunctionImpl>>(name + 
suffix);
+};
+
 template <typename ReturnType, bool VARIADIC>
 void register_table_function_expand_default(SimpleFunctionFactory& factory, 
const std::string& name,
                                             const std::string& suffix) {
@@ -143,11 +178,28 @@ void 
register_table_function_expand_default(SimpleFunctionFactory& factory, cons
             name + suffix);
 };
 
+template <typename ReturnType, bool VARIADIC>
+void register_table_alternative_function_expand_default(SimpleFunctionFactory& 
factory,
+                                                        const std::string& 
name,
+                                                        const std::string& 
suffix) {
+    factory.register_alternative_function<
+            FunctionFake<FunctionFakeBaseImpl<ReturnType, false, 
VARIADIC>>>(name);
+    factory.register_alternative_function<
+            FunctionFake<FunctionFakeBaseImpl<ReturnType, true, 
VARIADIC>>>(name + suffix);
+};
+
 template <typename FunctionImpl>
 void register_table_function_expand_outer(SimpleFunctionFactory& factory, 
const std::string& name) {
     register_table_function_expand<FunctionImpl>(factory, name, 
COMBINATOR_SUFFIX_OUTER);
 };
 
+template <typename FunctionImpl>
+void register_table_alternative_function_expand_outer(SimpleFunctionFactory& 
factory,
+                                                      const std::string& name) 
{
+    register_table_alternative_function_expand<FunctionImpl>(factory, name,
+                                                             
COMBINATOR_SUFFIX_OUTER);
+};
+
 template <typename ReturnType, bool VARIADIC>
 void register_table_function_expand_outer_default(SimpleFunctionFactory& 
factory,
                                                   const std::string& name) {
@@ -155,6 +207,13 @@ void 
register_table_function_expand_outer_default(SimpleFunctionFactory& factory
                                                                  
COMBINATOR_SUFFIX_OUTER);
 };
 
+template <typename ReturnType, bool VARIADIC>
+void 
register_table_alternative_function_expand_outer_default(SimpleFunctionFactory& 
factory,
+                                                              const 
std::string& name) {
+    register_table_alternative_function_expand_default<ReturnType, VARIADIC>(
+            factory, name, COMBINATOR_SUFFIX_OUTER);
+};
+
 template <typename FunctionImpl>
 void register_table_function_with_impl(SimpleFunctionFactory& factory, const 
std::string& name,
                                        const std::string& suffix = "") {
@@ -164,7 +223,9 @@ void 
register_table_function_with_impl(SimpleFunctionFactory& factory, const std
 void register_function_fake(SimpleFunctionFactory& factory) {
     register_function<FunctionEsquery>(factory, "esquery");
 
-    register_table_function_expand_outer<FunctionExplode>(factory, "explode");
+    register_table_function_expand_outer<FunctionExplodeV2>(factory, 
"explode");
+    register_table_alternative_function_expand_outer<FunctionExplode>(factory, 
"explode");
+
     register_table_function_expand_outer<FunctionExplodeMap>(factory, 
"explode_map");
 
     register_table_function_expand_outer<FunctionExplodeJsonObject>(factory, 
"explode_json_object");
@@ -184,8 +245,9 @@ void register_function_fake(SimpleFunctionFactory& factory) 
{
     register_table_function_with_impl<FunctionPoseExplode<false>>(factory, 
"posexplode");
     register_table_function_with_impl<FunctionPoseExplode<true>>(factory, 
"posexplode",
                                                                  
COMBINATOR_SUFFIX_OUTER);
-    register_table_function_expand_outer_default<DataTypeObject, 
false>(factory,
-                                                                        
"explode_variant_array");
+    register_table_alternative_function_expand_outer_default<DataTypeObject, 
false>(
+            factory, "explode_variant_array");
+    register_table_function_with_impl<FunctionExplodeV2>(factory, 
"explode_variant_array");
 }
 
 } // namespace doris::vectorized
diff --git a/be/src/vec/functions/simple_function_factory.h 
b/be/src/vec/functions/simple_function_factory.h
index 5a1718a0283..58dcfb57214 100644
--- a/be/src/vec/functions/simple_function_factory.h
+++ b/be/src/vec/functions/simple_function_factory.h
@@ -118,6 +118,9 @@ class SimpleFunctionFactory {
     /// which actually should be done by now-2 version.
     constexpr static int NEWEST_VERSION_FUNCTION_SUBSTITUTE = 5;
 
+    /// @TEMPORARY: for be_exec_version=8.
+    constexpr static int NEWEST_VERSION_EXPLODE_MULTI_PARAM = 8;
+
 public:
     void register_function(const std::string& name, const Creator& ptr) {
         DataTypes types = ptr()->get_variadic_argument_types();
@@ -148,6 +151,14 @@ public:
         register_function(name, &createDefaultFunction<Function>);
     }
 
+    /// @TEMPORARY: for be_exec_version=8
+    template <class Function>
+    void register_alternative_function(std::string name) {
+        static std::string suffix {"_old"};
+        function_to_replace[name] = name + suffix;
+        register_function(name + suffix, &createDefaultFunction<Function>);
+    }
+
     void register_alias(const std::string& name, const std::string& alias) {
         function_alias[alias] = name;
     }
@@ -203,7 +214,7 @@ private:
     FunctionCreators function_creators;
     FunctionIsVariadic function_variadic_set;
     std::unordered_map<std::string, std::string> function_alias;
-    /// @TEMPORARY: for be_exec_version=4. replace function to old version.
+    /// @TEMPORARY: for be_exec_version=8. replace function to old version.
     std::unordered_map<std::string, std::string> function_to_replace;
 
     template <typename Function>
@@ -211,10 +222,10 @@ private:
         return std::make_shared<DefaultFunctionBuilder>(Function::create());
     }
 
-    /// @TEMPORARY: for be_exec_version=4
+    /// @TEMPORARY: for be_exec_version=8
     void temporary_function_update(int fe_version_now, std::string& name) {
         // replace if fe is old version.
-        if (fe_version_now < NEWEST_VERSION_FUNCTION_SUBSTITUTE &&
+        if (fe_version_now < NEWEST_VERSION_EXPLODE_MULTI_PARAM &&
             function_to_replace.find(name) != function_to_replace.end()) {
             name = function_to_replace[name];
         }
diff --git a/be/test/vec/function/function_test_util.cpp 
b/be/test/vec/function/function_test_util.cpp
index 7cc381c260a..5d7cc6b1731 100644
--- a/be/test/vec/function/function_test_util.cpp
+++ b/be/test/vec/function/function_test_util.cpp
@@ -34,7 +34,9 @@
 #include "vec/data_types/data_type_ipv4.h"
 #include "vec/data_types/data_type_ipv6.h"
 #include "vec/data_types/data_type_jsonb.h"
+#include "vec/data_types/data_type_map.h"
 #include "vec/data_types/data_type_string.h"
+#include "vec/data_types/data_type_struct.h"
 #include "vec/data_types/data_type_time_v2.h"
 #include "vec/exprs/table_function/table_function.h"
 #include "vec/runtime/vdatetime_value.h"
@@ -195,6 +197,56 @@ size_t type_index_to_data_type(const std::vector<AnyType>& 
input_types, size_t i
         type = std::make_shared<DataTypeArray>(sub_type);
         return ret + 1;
     }
+    case TypeIndex::Map: {
+        desc.type = doris::PrimitiveType::TYPE_MAP;
+        ut_type::UTDataTypeDesc key_desc;
+        DataTypePtr key_type = nullptr;
+        ut_type::UTDataTypeDesc value_desc;
+        DataTypePtr value_type = nullptr;
+        ++index;
+        size_t ret = type_index_to_data_type(input_types, index, key_desc, 
key_type);
+        if (ret <= 0) {
+            return ret;
+        }
+        ++index;
+        ret = type_index_to_data_type(input_types, index, value_desc, 
value_type);
+        if (ret <= 0) {
+            return ret;
+        }
+        desc.children.push_back(key_desc.type_desc);
+        desc.children.push_back(value_desc.type_desc);
+        if (key_desc.is_nullable) {
+            key_type = make_nullable(key_type);
+        }
+        if (value_desc.is_nullable) {
+            value_type = make_nullable(value_type);
+        }
+        type = std::make_shared<DataTypeMap>(key_type, value_type);
+        return ret + 1;
+    }
+    case TypeIndex::Struct: {
+        desc.type = doris::PrimitiveType::TYPE_STRUCT;
+        ++index;
+        size_t ret = 0;
+        DataTypes sub_types;
+        while (index < input_types.size()) {
+            ut_type::UTDataTypeDesc sub_desc;
+            DataTypePtr sub_type = nullptr;
+            size_t inner_ret = type_index_to_data_type(input_types, index, 
sub_desc, sub_type);
+            if (inner_ret <= 0) {
+                return inner_ret;
+            }
+            ret += inner_ret;
+            desc.children.push_back(sub_desc.type_desc);
+            if (sub_desc.is_nullable) {
+                sub_type = make_nullable(sub_type);
+                sub_types.push_back(sub_type);
+            }
+            ++index;
+        }
+        type = std::make_shared<DataTypeStruct>(sub_types);
+        return ret + 1;
+    }
     case TypeIndex::Nullable: {
         ++index;
         size_t ret = type_index_to_data_type(input_types, index, ut_desc, 
type);
@@ -341,6 +393,28 @@ bool insert_cell(MutableColumnPtr& column, DataTypePtr 
type_ptr, const AnyType&
     } else if (type.is_array()) {
         auto v = any_cast<Array>(cell);
         column->insert(v);
+    } else if (type.is_struct()) {
+        auto v = any_cast<CellSet>(cell);
+        auto struct_type = assert_cast<const DataTypeStruct*>(type_ptr.get());
+        auto nullable_column = assert_cast<ColumnNullable*>(column.get());
+        auto* struct_column =
+                
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
+        auto* nullmap_column =
+                
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+        nullmap_column->insert_default();
+        for (size_t i = 0; i < v.size(); ++i) {
+            auto& field = v[i];
+            auto col = struct_column->get_column(i).get_ptr();
+            RETURN_IF_FALSE(insert_cell(col, struct_type->get_element(i), 
field));
+        }
+    } else if (type.is_nullable()) {
+        auto nullable_column = assert_cast<ColumnNullable*>(column.get());
+        auto col_type = remove_nullable(type_ptr);
+        auto col = nullable_column->get_nested_column_ptr();
+        auto* nullmap_column =
+                
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+        nullmap_column->insert_default();
+        RETURN_IF_FALSE(insert_cell(col, col_type, cell));
     } else {
         LOG(WARNING) << "dataset not supported for TypeIndex:" << 
(int)type.idx;
         return false;
@@ -359,32 +433,39 @@ Block* create_block_from_inputset(const InputTypeSet& 
input_types, const InputDa
     // 1.1 insert data and create block
     auto row_size = input_set.size();
     std::unique_ptr<Block> block = Block::create_unique();
-    for (size_t i = 0; i < descs.size(); ++i) {
-        auto& desc = descs[i];
-        auto column = desc.data_type->create_column();
-        column->reserve(row_size);
 
-        auto type_ptr = desc.data_type->is_nullable()
-                                ? 
((DataTypeNullable*)(desc.data_type.get()))->get_nested_type()
-                                : desc.data_type;
-        WhichDataType type(type_ptr);
+    auto input_set_size = input_set[0].size();
+    // 1.2 calculate the input column size
+    auto input_col_size = input_set_size / descs.size();
 
-        for (int j = 0; j < row_size; j++) {
-            if (!insert_cell(column, type_ptr, input_set[j][i])) {
-                return nullptr;
+    for (size_t i = 0; i < descs.size(); ++i) {
+        auto& desc = descs[i];
+        for (size_t j = 0; j < input_col_size; ++j) {
+            auto column = desc.data_type->create_column();
+            column->reserve(row_size);
+
+            auto type_ptr = desc.data_type->is_nullable()
+                                    ? 
((DataTypeNullable*)(desc.data_type.get()))->get_nested_type()
+                                    : desc.data_type;
+            WhichDataType type(type_ptr);
+
+            for (int r = 0; r < row_size; r++) {
+                if (!insert_cell(column, type_ptr, input_set[r][i * 
input_col_size + j])) {
+                    return nullptr;
+                }
             }
-        }
 
-        if (desc.is_const) {
-            column = ColumnConst::create(std::move(column), row_size);
+            if (desc.is_const) {
+                column = ColumnConst::create(std::move(column), row_size);
+            }
+            block->insert({std::move(column), desc.data_type, desc.col_name});
         }
-        block->insert({std::move(column), desc.data_type, desc.col_name});
     }
     return block.release();
 }
 
 Block* process_table_function(TableFunction* fn, Block* input_block,
-                              const InputTypeSet& output_types) {
+                              const InputTypeSet& output_types, bool 
test_get_value_func) {
     // pasrse output data types
     ut_type::UTDataTypeDescs descs;
     if (!parse_ut_data_type(output_types, descs)) {
@@ -418,8 +499,12 @@ Block* process_table_function(TableFunction* fn, Block* 
input_block,
         }
 
         do {
-            fn->get_same_many_values(column, 1);
-            fn->forward();
+            if (test_get_value_func) {
+                fn->get_value(column, 10);
+            } else {
+                fn->get_same_many_values(column, 1);
+                fn->forward();
+            }
         } while (!fn->eos());
     }
 
@@ -430,7 +515,7 @@ Block* process_table_function(TableFunction* fn, Block* 
input_block,
 
 void check_vec_table_function(TableFunction* fn, const InputTypeSet& 
input_types,
                               const InputDataSet& input_set, const 
InputTypeSet& output_types,
-                              const InputDataSet& output_set) {
+                              const InputDataSet& output_set, const bool 
test_get_value_func) {
     std::unique_ptr<Block> input_block(create_block_from_inputset(input_types, 
input_set));
     EXPECT_TRUE(input_block != nullptr);
 
@@ -439,7 +524,7 @@ void check_vec_table_function(TableFunction* fn, const 
InputTypeSet& input_types
     EXPECT_TRUE(expect_output_block != nullptr);
 
     std::unique_ptr<Block> real_output_block(
-            process_table_function(fn, input_block.get(), output_types));
+            process_table_function(fn, input_block.get(), output_types, 
test_get_value_func));
     EXPECT_TRUE(real_output_block != nullptr);
 
     // compare real_output_block with expect_output_block
diff --git a/be/test/vec/function/function_test_util.h 
b/be/test/vec/function/function_test_util.h
index c33a1d64f83..6cb6ef226fb 100644
--- a/be/test/vec/function/function_test_util.h
+++ b/be/test/vec/function/function_test_util.h
@@ -41,6 +41,7 @@
 #include "vec/columns/column_complex.h"
 #include "vec/columns/column_const.h"
 #include "vec/columns/column_nullable.h"
+#include "vec/columns/column_struct.h"
 #include "vec/common/string_ref.h"
 #include "vec/core/block.h"
 #include "vec/core/column_numbers.h"
@@ -207,7 +208,7 @@ Block* process_table_function(TableFunction* fn, Block* 
input_block,
                               const InputTypeSet& output_types);
 void check_vec_table_function(TableFunction* fn, const InputTypeSet& 
input_types,
                               const InputDataSet& input_set, const 
InputTypeSet& output_types,
-                              const InputDataSet& output_set);
+                              const InputDataSet& output_set, bool 
test_get_value_func = false);
 
 // Null values are represented by Null()
 // The type of the constant column is represented as follows: Consted 
{TypeIndex::String}
diff --git a/be/test/vec/function/table_function_test.cpp 
b/be/test/vec/function/table_function_test.cpp
index 43d37f6bf73..8703a8feb02 100644
--- a/be/test/vec/function/table_function_test.cpp
+++ b/be/test/vec/function/table_function_test.cpp
@@ -32,6 +32,7 @@
 #include "vec/exprs/table_function/vexplode.h"
 #include "vec/exprs/table_function/vexplode_numbers.h"
 #include "vec/exprs/table_function/vexplode_split.h"
+#include "vec/exprs/table_function/vexplode_v2.h"
 #include "vec/exprs/vexpr_context.h"
 #include "vec/function/function_test_util.h"
 
@@ -123,6 +124,61 @@ TEST_F(TableFunctionTest, vexplode_outer) {
     }
 }
 
+TEST_F(TableFunctionTest, vexplode_outer_v2) {
+    init_expr_context(1);
+    VExplodeV2TableFunction explode_outer;
+    explode_outer.set_outer();
+    explode_outer.set_expr_context(_ctx);
+
+    // explode_outer(Array<Int32>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::Int32};
+        Array vec = {Int32(1), Null(), Int32(2), Int32(3)};
+        InputDataSet input_set = {{vec}, {Null()}, {Array()}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::Int32};
+
+        InputDataSet output_set = {{{CellSet {Int32(1)}}}, {{CellSet 
{Null()}}},
+                                   {{CellSet {Int32(2)}}}, {{CellSet 
{Int32(3)}}},
+                                   {{CellSet {Null()}}},   {{CellSet 
{Null()}}}};
+
+        check_vec_table_function(&explode_outer, input_types, input_set, 
output_types, output_set);
+    }
+
+    // explode_outer(Array<String>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::String};
+        Array vec = {Field(std::string("abc")), Field(std::string("")), 
Field(std::string("def"))};
+        InputDataSet input_set = {{Null()}, {Array()}, {vec}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::String};
+
+        InputDataSet output_set = {{{CellSet {Null()}}},
+                                   {{CellSet {Null()}}},
+                                   {{CellSet {std::string("abc")}}},
+                                   {{CellSet {std::string("")}}},
+                                   {{CellSet {std::string("def")}}}};
+
+        check_vec_table_function(&explode_outer, input_types, input_set, 
output_types, output_set);
+    }
+
+    // explode_outer(Array<Decimal>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::Decimal128V2};
+        Array vec = {ut_type::DECIMALFIELD(17014116.67), 
ut_type::DECIMALFIELD(-17014116.67)};
+        InputDataSet input_set = {{Null()}, {Array()}, {vec}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, 
TypeIndex::Decimal128V2};
+
+        InputDataSet output_set = {{{CellSet {Null()}}},
+                                   {{CellSet {Null()}}},
+                                   {{CellSet 
{ut_type::DECIMALV2(17014116.67)}}},
+                                   {{CellSet 
{ut_type::DECIMALV2(-17014116.67)}}}};
+
+        check_vec_table_function(&explode_outer, input_types, input_set, 
output_types, output_set);
+    }
+}
+
 TEST_F(TableFunctionTest, vexplode) {
     init_expr_context(1);
     VExplodeTableFunction explode;
@@ -166,6 +222,111 @@ TEST_F(TableFunctionTest, vexplode) {
     }
 }
 
+TEST_F(TableFunctionTest, vexplode_v2) {
+    init_expr_context(1);
+    VExplodeV2TableFunction explode;
+    explode.set_expr_context(_ctx);
+
+    // explode(Array<Int32>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::Int32};
+
+        Array vec = {Int32(1), Null(), Int32(2), Int32(3)};
+        InputDataSet input_set = {{vec}, {Null()}, {Array()}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::Int32};
+        InputDataSet output_set = {{{CellSet {Int32(1)}}},
+                                   {{CellSet {Null()}}},
+                                   {{CellSet {Int32(2)}}},
+                                   {{CellSet {Int32(3)}}}};
+
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set);
+    }
+
+    // explode(Array<String>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::String};
+        Array vec = {Field(std::string("abc")), Field(std::string("")), 
Field(std::string("def"))};
+        InputDataSet input_set = {{Null()}, {Array()}, {vec}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::String};
+
+        InputDataSet output_set = {{{CellSet {std::string("abc")}}},
+                                   {{CellSet {std::string("")}}},
+                                   {{CellSet {std::string("def")}}}};
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set);
+    }
+
+    // explode(Array<Date>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::Date};
+        Array vec = {Null(), str_to_date_time("2022-01-02", false)};
+        InputDataSet input_set = {{Null()}, {Array()}, {vec}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::Date};
+
+        InputDataSet output_set = {{{CellSet {Null()}}}, {{CellSet 
{std::string("2022-01-02")}}}};
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set);
+    }
+}
+
+TEST_F(TableFunctionTest, vexplode_v2_two_param) {
+    init_expr_context(2);
+    VExplodeV2TableFunction explode;
+    explode.set_expr_context(_ctx);
+    // explode(Array<String>, Array<String>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::String};
+        Array vec = {Field(std::string("one")), Field(std::string("two")),
+                     Field(std::string("three"))};
+        Array vec1 = {Field(std::string("1")), Field(std::string("2")), 
Field(std::string("3"))};
+        InputDataSet input_set = {{vec, vec1}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::String, 
TypeIndex::String};
+
+        InputDataSet output_set = {{{CellSet {std::string("one"), 
std::string("1")}}},
+                                   {{CellSet {std::string("two"), 
std::string("2")}}},
+                                   {{CellSet {std::string("three"), 
std::string("3")}}}};
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, false);
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, true);
+    }
+
+    // explode(null, Array<String>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::String};
+        Array vec = {Field(std::string("one")), Field(std::string("two")),
+                     Field(std::string("three"))};
+        InputDataSet input_set = {{Null(), vec}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::String, 
TypeIndex::String};
+
+        InputDataSet output_set = {{{CellSet {Null(), std::string("one")}}},
+                                   {{CellSet {Null(), std::string("two")}}},
+                                   {{CellSet {Null(), std::string("three")}}}};
+
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, false);
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, true);
+    }
+
+    // explode(Array<Null>, Array<String>)
+    {
+        InputTypeSet input_types = {TypeIndex::Array, TypeIndex::String};
+        Array vec = {Field(std::string("one")), Field(std::string("two")),
+                     Field(std::string("three"))};
+        Array vec1 = {Field(std::string("1")), Field(Null()), 
Field(std::string("3"))};
+        InputDataSet input_set = {{vec, vec1}};
+
+        InputTypeSet output_types = {TypeIndex::Struct, TypeIndex::String, 
TypeIndex::String};
+
+        InputDataSet output_set = {{{CellSet {std::string("one"), 
std::string("1")}}},
+                                   {{CellSet {std::string("two"), Null()}}},
+                                   {{CellSet {std::string("three"), 
std::string("3")}}}};
+
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, false);
+        check_vec_table_function(&explode, input_types, input_set, 
output_types, output_set, true);
+    }
+}
+
 TEST_F(TableFunctionTest, vexplode_numbers) {
     init_expr_context(1);
     VExplodeNumbersTableFunction tfn;
@@ -213,4 +374,4 @@ TEST_F(TableFunctionTest, vexplode_split) {
     }
 }
 
-} // namespace doris::vectorized
+} // namespace doris::vectorized
\ No newline at end of file
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
index d342b01ff7e..19773704b1e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
@@ -42,8 +42,11 @@ import 
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplode
 import 
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplodeOuter;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Builtin table generating functions.
@@ -78,6 +81,14 @@ public class BuiltinTableGeneratingFunctions implements 
FunctionHelper {
             tableGenerating(PosExplodeOuter.class, "posexplode_outer")
     );
 
+    public static final ImmutableSet<String> RETURN_MULTI_COLUMNS_FUNCTIONS = 
new ImmutableSortedSet.Builder<String>(
+            
String.CASE_INSENSITIVE_ORDER).add("explode").add("explode_outer").add("explode_variant_array")
+            .add("explode_variant_array_outer").build();
+
+    public Set<String> getReturnManyColumnFunctions() {
+        return RETURN_MULTI_COLUMNS_FUNCTIONS;
+    }
+
     public static final BuiltinTableGeneratingFunctions INSTANCE = new 
BuiltinTableGeneratingFunctions();
 
     // Note: Do not add any code here!
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index da7aa27cb19..d29eb2b2433 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -27,6 +27,7 @@ import org.apache.doris.analysis.TableSnapshot;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.BuiltinAggregateFunctions;
+import org.apache.doris.catalog.BuiltinTableGeneratingFunctions;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.ScalarType;
@@ -1254,8 +1255,13 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         String generateName = ctx.tableName.getText();
         // if later view explode map type, we need to add a project to convert 
map to struct
         String columnName = ctx.columnNames.get(0).getText();
-        List<String> expandColumnNames = Lists.newArrayList();
-        if (ctx.columnNames.size() > 1) {
+        List<String> expandColumnNames = ImmutableList.of();
+
+        // explode can pass multiple columns
+        // then use struct to return the result of the expansion of multiple 
columns.
+        if (ctx.columnNames.size() > 1
+                || 
BuiltinTableGeneratingFunctions.INSTANCE.getReturnManyColumnFunctions()
+                    .contains(ctx.functionName.getText())) {
             columnName = ConnectContext.get() != null
                     ? 
ConnectContext.get().getStatementContext().generateColumnName() : "expand_cols";
             expandColumnNames = ctx.columnNames.stream()
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
index 7fb87b055f8..22b80c7f6d4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
@@ -20,31 +20,34 @@ package 
org.apache.doris.nereids.trees.expressions.functions.generator;
 import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
-import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.functions.ComputePrecision;
+import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
+import org.apache.doris.nereids.trees.expressions.functions.SearchSignature;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.ArrayType;
-import org.apache.doris.nereids.types.coercion.AnyDataType;
-import org.apache.doris.nereids.types.coercion.FollowToAnyDataType;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
- * explode([1, 2, 3]), generate 3 lines include 1, 2 and 3.
+ * explode([1, 2, 3]), generate three rows include 1, 2 and 3.
+ * explode([1, 2, 3], [4, 5, 6]) generates two columns and three rows
+ * where the first column contains 1, 2, 3, and the second column contains 4, 
5, 6.
  */
-public class Explode extends TableGeneratingFunction implements 
BinaryExpression, AlwaysNullable {
-
-    public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
-            FunctionSignature.ret(new 
FollowToAnyDataType(0)).args(ArrayType.of(new AnyDataType(0)))
-    );
+public class Explode extends TableGeneratingFunction implements 
CustomSignature, ComputePrecision, AlwaysNullable {
 
     /**
-     * constructor with 1 argument.
+     * constructor with one or more argument.
      */
-    public Explode(Expression arg) {
-        super("explode", arg);
+    public Explode(Expression[] args) {
+        super("explode", args);
     }
 
     /**
@@ -52,17 +55,43 @@ public class Explode extends TableGeneratingFunction 
implements BinaryExpression
      */
     @Override
     public Explode withChildren(List<Expression> children) {
-        Preconditions.checkArgument(children.size() == 1);
-        return new Explode(children.get(0));
+        Preconditions.checkArgument(!children.isEmpty());
+        return new Explode(children.toArray(new Expression[0]));
+    }
+
+    @Override
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
     }
 
     @Override
-    public List<FunctionSignature> getSignatures() {
-        return SIGNATURES;
+    public FunctionSignature customSignature() {
+        List<DataType> arguments = new ArrayList<>();
+        ImmutableList.Builder<StructField> structFields = 
ImmutableList.builder();
+        for (int i = 0; i < children.size(); i++) {
+            if (children.get(i).getDataType().isNullType()) {
+                arguments.add(ArrayType.of(NullType.INSTANCE));
+                structFields.add(
+                    new StructField("col" + (i + 1), NullType.INSTANCE, true, 
""));
+            } else if (children.get(i).getDataType().isArrayType()) {
+                structFields.add(
+                    new StructField("col" + (i + 1),
+                        ((ArrayType) 
(children.get(i)).getDataType()).getItemType(), true, ""));
+                arguments.add(children.get(i).getDataType());
+            } else {
+                
SearchSignature.throwCanNotFoundFunctionException(this.getName(), 
getArguments());
+            }
+        }
+        return FunctionSignature.of(new StructType(structFields.build()), 
arguments);
     }
 
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitExplode(this, context);
     }
+
+    @Override
+    public FunctionSignature searchSignature(List<FunctionSignature> 
signatures) {
+        return super.searchSignature(signatures);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
index 5d90546ebe7..cbcc2eaa1c1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
@@ -20,31 +20,34 @@ package 
org.apache.doris.nereids.trees.expressions.functions.generator;
 import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
-import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.functions.ComputePrecision;
+import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
+import org.apache.doris.nereids.trees.expressions.functions.SearchSignature;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.ArrayType;
-import org.apache.doris.nereids.types.coercion.AnyDataType;
-import org.apache.doris.nereids.types.coercion.FollowToAnyDataType;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
- * explode([1, 2, 3]), generate 3 lines include 1, 2 and 3.
+ * explode_outer([1, 2, 3]), generate three rows include 1, 2 and 3.
+ * explode_outer([1, 2, 3], [4, 5, 6]) generates two columns and three rows
+ * where the first column contains 1, 2, 3, and the second column contains 4, 
5, 6.
  */
-public class ExplodeOuter extends TableGeneratingFunction implements 
BinaryExpression, AlwaysNullable {
-
-    public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
-            FunctionSignature.ret(new 
FollowToAnyDataType(0)).args(ArrayType.of(new AnyDataType(0)))
-    );
+public class ExplodeOuter extends TableGeneratingFunction implements 
CustomSignature, ComputePrecision, AlwaysNullable {
 
     /**
-     * constructor with 1 argument.
+     * constructor with one or more argument.
      */
-    public ExplodeOuter(Expression arg) {
-        super("explode_outer", arg);
+    public ExplodeOuter(Expression[] args) {
+        super("explode_outer", args);
     }
 
     /**
@@ -52,17 +55,43 @@ public class ExplodeOuter extends TableGeneratingFunction 
implements BinaryExpre
      */
     @Override
     public ExplodeOuter withChildren(List<Expression> children) {
-        Preconditions.checkArgument(children.size() == 1);
-        return new ExplodeOuter(children.get(0));
+        Preconditions.checkArgument(!children.isEmpty());
+        return new ExplodeOuter(children.toArray(new Expression[0]));
+    }
+
+    @Override
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
     }
 
     @Override
-    public List<FunctionSignature> getSignatures() {
-        return SIGNATURES;
+    public FunctionSignature customSignature() {
+        List<DataType> arguments = new ArrayList<>();
+        ImmutableList.Builder<StructField> structFields = 
ImmutableList.builder();
+        for (int i = 0; i < children.size(); i++) {
+            if (children.get(i).getDataType().isNullType()) {
+                arguments.add(ArrayType.of(NullType.INSTANCE));
+                structFields.add(
+                    new StructField("col" + (i + 1), NullType.INSTANCE, true, 
""));
+            } else if (children.get(i).getDataType().isArrayType()) {
+                structFields.add(
+                    new StructField("col" + (i + 1),
+                        ((ArrayType) 
(children.get(i)).getDataType()).getItemType(), true, ""));
+                arguments.add(children.get(i).getDataType());
+            } else {
+                
SearchSignature.throwCanNotFoundFunctionException(this.getName(), 
getArguments());
+            }
+        }
+        return FunctionSignature.of(new StructType(structFields.build()), 
arguments);
     }
 
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitExplodeOuter(this, context);
     }
+
+    @Override
+    public FunctionSignature searchSignature(List<FunctionSignature> 
signatures) {
+        return super.searchSignature(signatures);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
index 62d7eb72e20..6f03f93923a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
@@ -18,32 +18,36 @@
 package org.apache.doris.nereids.trees.expressions.functions.generator;
 
 import org.apache.doris.catalog.FunctionSignature;
-import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
-import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
+import org.apache.doris.nereids.trees.expressions.functions.ComputePrecision;
+import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
+import org.apache.doris.nereids.trees.expressions.functions.SearchSignature;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
 import org.apache.doris.nereids.types.VariantType;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
- * explode_variant_array(variant([1, 2, 3])), generate 3 lines include 1, 2 
and 3.
+ * explode_variant_array(variant([1, 2, 3])), generate three rows include 1, 2 
and 3.
+ * explode_variant_array(variant([1, 2, 3]), variant([4, 5, 6])) generates two 
columns and three rows
+ * where the first column contains 1, 2, 3, and the second column contains 4, 
5, 6.
  */
-public class ExplodeVariantArray extends TableGeneratingFunction implements 
UnaryExpression, AlwaysNullable {
-
-    public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
-            FunctionSignature.ret(new VariantType()).args(new VariantType())
-    );
+public class ExplodeVariantArray extends TableGeneratingFunction implements
+        CustomSignature, ComputePrecision, AlwaysNullable {
 
     /**
-     * constructor with 1 argument.
+     * constructor with one or more argument.
      */
-    public ExplodeVariantArray(Expression arg) {
-        super("explode_variant_array", arg);
+    public ExplodeVariantArray(Expression[] args) {
+        super("explode_variant_array", args);
     }
 
     /**
@@ -51,25 +55,38 @@ public class ExplodeVariantArray extends 
TableGeneratingFunction implements Unar
      */
     @Override
     public ExplodeVariantArray withChildren(List<Expression> children) {
-        Preconditions.checkArgument(children.size() == 1);
-        return new ExplodeVariantArray(children.get(0));
+        Preconditions.checkArgument(!children.isEmpty());
+        return new ExplodeVariantArray(children.toArray(new Expression[0]));
     }
 
     @Override
-    public void checkLegalityBeforeTypeCoercion() {
-        if (!(child().getDataType() instanceof VariantType)) {
-            throw new AnalysisException("only support variant type for 
explode_variant_array function but got "
-                    + child().getDataType());
-        }
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
     }
 
     @Override
-    public List<FunctionSignature> getSignatures() {
-        return SIGNATURES;
+    public FunctionSignature customSignature() {
+        List<DataType> arguments = new ArrayList<>();
+        ImmutableList.Builder<StructField> structFields = 
ImmutableList.builder();
+        for (int i = 0; i < children.size(); i++) {
+            if (children.get(i).getDataType() instanceof VariantType) {
+                structFields.add(
+                    new StructField("col" + (i + 1), VariantType.INSTANCE, 
true, ""));
+                arguments.add(VariantType.INSTANCE);
+            } else {
+                
SearchSignature.throwCanNotFoundFunctionException(this.getName(), 
getArguments());
+            }
+        }
+        return FunctionSignature.of(new StructType(structFields.build()), 
arguments);
     }
 
     @Override
     public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
         return visitor.visitExplodeVariant(this, context);
     }
+
+    @Override
+    public FunctionSignature searchSignature(List<FunctionSignature> 
signatures) {
+        return super.searchSignature(signatures);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuterTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuterTest.java
new file mode 100644
index 00000000000..fd381a4e7d4
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuterTest.java
@@ -0,0 +1,95 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.generator;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.StructType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ExplodeOuterTest {
+
+    /////////////////////////////////////////
+    // GetSignatures
+    /////////////////////////////////////////
+
+    @Test
+    public void testGetSignatures() {
+        // build explode_outer(array<int>, array<str>) expression
+        Expression[] args = {SlotReference.of("int", 
ArrayType.of(IntegerType.INSTANCE)),
+            SlotReference.of("str", ArrayType.of(StringType.INSTANCE))};
+        ExplodeOuter explode = new ExplodeOuter(args);
+
+        // check signature
+        List<FunctionSignature> signatures = explode.getSignatures();
+        Assertions.assertEquals(1, signatures.size());
+        FunctionSignature signature = signatures.get(0);
+        Assertions.assertEquals(2, signature.argumentsTypes.size());
+        Assertions.assertTrue(signature.argumentsTypes.get(0).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(0)).getItemType().isIntegerType());
+        Assertions.assertTrue(signature.argumentsTypes.get(1).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(1)).getItemType().isStringType());
+        Assertions.assertTrue(signature.returnType.isStructType());
+        StructType returnType = (StructType) signature.returnType;
+        Assertions.assertEquals(2, returnType.getFields().size());
+        Assertions.assertEquals(IntegerType.INSTANCE, 
returnType.getFields().get(0).getDataType());
+        Assertions.assertEquals(StringType.INSTANCE, 
returnType.getFields().get(1).getDataType());
+    }
+
+    @Test
+    public void testGetSignaturesWithNull() {
+        // build explode(null, array<int>) expression
+        Expression[] args = { SlotReference.of("null", NullType.INSTANCE), 
SlotReference.of("int", ArrayType.of(IntegerType.INSTANCE))};
+        ExplodeOuter explode = new ExplodeOuter(args);
+
+        // check signature
+        List<FunctionSignature> signatures = explode.getSignatures();
+        Assertions.assertEquals(1, signatures.size());
+        FunctionSignature signature = signatures.get(0);
+        Assertions.assertEquals(2, signature.argumentsTypes.size());
+        Assertions.assertTrue(signature.argumentsTypes.get(0).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(0)).getItemType().isNullType());
+        Assertions.assertTrue(signature.argumentsTypes.get(1).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(1)).getItemType().isIntegerType());
+        Assertions.assertTrue(signature.returnType.isStructType());
+        StructType returnType = (StructType) signature.returnType;
+        Assertions.assertEquals(2, returnType.getFields().size());
+        Assertions.assertEquals(NullType.INSTANCE, 
returnType.getFields().get(0).getDataType());
+        Assertions.assertEquals(IntegerType.INSTANCE, 
returnType.getFields().get(1).getDataType());
+    }
+
+    @Test
+    public void testGetSignaturesWithInvalidArgument() {
+        // build explode_outer(int)
+        Expression[] args = { SlotReference.of("int", IntegerType.INSTANCE) };
+        ExplodeOuter explode = new ExplodeOuter(args);
+
+        Assertions.assertThrows(AnalysisException.class, 
explode::getSignatures);
+    }
+
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeTest.java
new file mode 100644
index 00000000000..0cb61d6171a
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeTest.java
@@ -0,0 +1,95 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.generator;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.StructType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ExplodeTest {
+
+    /////////////////////////////////////////
+    // GetSignatures
+    /////////////////////////////////////////
+
+    @Test
+    public void testGetSignatures() {
+        // build explode(array<int>, array<str>) expression
+        Expression[] args = {SlotReference.of("int", 
ArrayType.of(IntegerType.INSTANCE)),
+            SlotReference.of("str", ArrayType.of(StringType.INSTANCE))};
+        Explode explode = new Explode(args);
+
+        // check signature
+        List<FunctionSignature> signatures = explode.getSignatures();
+        Assertions.assertEquals(1, signatures.size());
+        FunctionSignature signature = signatures.get(0);
+        Assertions.assertEquals(2, signature.argumentsTypes.size());
+        Assertions.assertTrue(signature.argumentsTypes.get(0).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(0)).getItemType().isIntegerType());
+        Assertions.assertTrue(signature.argumentsTypes.get(1).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(1)).getItemType().isStringType());
+        Assertions.assertTrue(signature.returnType.isStructType());
+        StructType returnType = (StructType) signature.returnType;
+        Assertions.assertEquals(2, returnType.getFields().size());
+        Assertions.assertEquals(IntegerType.INSTANCE, 
returnType.getFields().get(0).getDataType());
+        Assertions.assertEquals(StringType.INSTANCE, 
returnType.getFields().get(1).getDataType());
+    }
+
+    @Test
+    public void testGetSignaturesWithNull() {
+        // build explode(null, array<int>) expression
+        Expression[] args = { SlotReference.of("null", NullType.INSTANCE), 
SlotReference.of("int", ArrayType.of(IntegerType.INSTANCE))};
+        Explode explode = new Explode(args);
+
+        // check signature
+        List<FunctionSignature> signatures = explode.getSignatures();
+        Assertions.assertEquals(1, signatures.size());
+        FunctionSignature signature = signatures.get(0);
+        Assertions.assertEquals(2, signature.argumentsTypes.size());
+        Assertions.assertTrue(signature.argumentsTypes.get(0).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(0)).getItemType().isNullType());
+        Assertions.assertTrue(signature.argumentsTypes.get(1).isArrayType());
+        Assertions.assertTrue(((ArrayType) 
signature.argumentsTypes.get(1)).getItemType().isIntegerType());
+        Assertions.assertTrue(signature.returnType.isStructType());
+        StructType returnType = (StructType) signature.returnType;
+        Assertions.assertEquals(2, returnType.getFields().size());
+        Assertions.assertEquals(NullType.INSTANCE, 
returnType.getFields().get(0).getDataType());
+        Assertions.assertEquals(IntegerType.INSTANCE, 
returnType.getFields().get(1).getDataType());
+    }
+
+    @Test
+    public void testGetSignaturesWithInvalidArgument() {
+        // build explode(int)
+        Expression[] args = { SlotReference.of("int", IntegerType.INSTANCE) };
+        Explode explode = new Explode(args);
+
+        Assertions.assertThrows(AnalysisException.class, 
explode::getSignatures);
+    }
+
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArrayTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArrayTest.java
new file mode 100644
index 00000000000..d9f089ae4fd
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArrayTest.java
@@ -0,0 +1,68 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.generator;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.types.VariantType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ExplodeVariantArrayTest {
+
+    /////////////////////////////////////////
+    // GetSignatures
+    /////////////////////////////////////////
+
+    @Test
+    public void testGetSignatures() {
+        // build explode_variant_array(variant, variant) expression
+        Expression[] args = { SlotReference.of("int", VariantType.INSTANCE), 
SlotReference.of("int", VariantType.INSTANCE) };
+        ExplodeVariantArray explode = new ExplodeVariantArray(args);
+
+        // check signature
+        List<FunctionSignature> signatures = explode.getSignatures();
+        Assertions.assertEquals(1, signatures.size());
+        FunctionSignature signature = signatures.get(0);
+        Assertions.assertEquals(2, signature.argumentsTypes.size());
+        Assertions.assertTrue(signature.argumentsTypes.get(0).isVariantType());
+        Assertions.assertTrue(signature.argumentsTypes.get(1).isVariantType());
+        Assertions.assertTrue(signature.returnType.isStructType());
+        StructType returnType = (StructType) signature.returnType;
+        Assertions.assertEquals(2, returnType.getFields().size());
+        Assertions.assertEquals(VariantType.INSTANCE, 
returnType.getFields().get(0).getDataType());
+        Assertions.assertEquals(VariantType.INSTANCE, 
returnType.getFields().get(1).getDataType());
+    }
+
+    @Test
+    public void testGetSignaturesWithInvalidArgument() {
+        // build explode_variant_array(int)
+        Expression[] args = { SlotReference.of("int", IntegerType.INSTANCE) };
+        ExplodeVariantArray explode = new ExplodeVariantArray(args);
+
+        Assertions.assertThrows(AnalysisException.class, 
explode::getSignatures);
+    }
+
+}
diff --git 
a/regression-test/data/query_p0/sql_functions/table_function/explode.out 
b/regression-test/data/query_p0/sql_functions/table_function/explode.out
index 415abdf8237..234119bea63 100644
Binary files 
a/regression-test/data/query_p0/sql_functions/table_function/explode.out and 
b/regression-test/data/query_p0/sql_functions/table_function/explode.out differ
diff --git 
a/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
 
b/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
index d94f909142e..034559aaa78 100644
--- 
a/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
+++ 
b/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
@@ -132,11 +132,11 @@ suite("test_parquet_nested_types", 
"p0,external,hive,external_docker,external_do
         order_qt_nested_cross_page2_parquet_q5 """
             SELECT
                 id,
-                item_x as x_value,
-                item_y as y_value
+                STRUCT_ELEMENT(item, 'x'),
+                STRUCT_ELEMENT(item, 'y')
             FROM nested_cross_page2_parquet
-            LATERAL VIEW EXPLODE(array_struct_col) tmp AS item_x, item_y
-            WHERE id = 1 AND item_x > 100
+            LATERAL VIEW EXPLODE(array_struct_col) tmp AS item
+            WHERE id = 1 AND STRUCT_ELEMENT(item, 'x') > 100
         """
 
         order_qt_nested_cross_page2_parquet_q6 """
diff --git 
a/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy 
b/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
index 982e27a9667..3ec71810eaa 100644
--- 
a/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
+++ 
b/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
@@ -137,4 +137,55 @@ suite("explode") {
     for (int r = 0; r < res_origin_size_aa.size(); ++ r) {
         assertEquals(res_origin_size_aa[r][0], res_explode_aa[r][0])
     }
+
+    sql "DROP TABLE IF EXISTS array_test;"
+    sql """
+    CREATE TABLE `array_test` (
+      `id` INT NULL,
+      `array_int` ARRAY<INT> NOT NULL,
+      `array_string` ARRAY<String> NULL,
+      `v_int` VARIANT NULL,
+      `v_string` VARIANT NULL
+    ) ENGINE=OLAP
+    DUPLICATE KEY(`id`)
+    COMMENT 'OLAP'
+    DISTRIBUTED BY HASH(`id`) BUCKETS 1
+    PROPERTIES (
+    "replication_allocation" = "tag.location.default: 1",
+    "min_load_replica_num" = "-1",
+    "is_being_synced" = "false",
+    "storage_medium" = "hdd",
+    "storage_format" = "V2",
+    "light_schema_change" = "true",
+    "disable_auto_compaction" = "false",
+    "enable_single_replica_compaction" = "false",
+    "group_commit_interval_ms" = "10000",
+    "group_commit_data_bytes" = "134217728"
+    );
+    """
+
+    sql """insert into array_test values( 1, [4,5,6], ["2","3"], '{"a": 
[4,5,6]}', '{"a": ["2","3"]}'),( 2, [14,15], ["2",null], '{"a": [14,15]}', 
'{"a": ["2",null]}'),( 3, [114,115,116], null, '{"a": [114,115,116]}','{"a": 
null}');"""
+
+
+    qt_test6 "select id,e1 from array_test as a lateral view 
explode(a.array_string) tmp1 as e1;"
+    qt_test7 "select id,e1 from array_test as a lateral view 
explode(a.array_int) tmp1 as e1;"
+    qt_test8 "select id,e1,e2 from array_test as a lateral view 
explode(a.array_int,a.array_string) tmp1 as e1,e2;"
+    qt_test9 "select id,e1,e2 from array_test as a lateral view 
explode(a.array_string,a.array_int) tmp1 as e1,e2;"
+    qt_test10 "select id,e1,e2,e3 from array_test as a lateral view 
explode(a.array_string,a.array_int,a.array_int) tmp1 as e1,e2,e3;"
+    qt_test11 "select id,e1,e2,e11,e12 from array_test as a lateral view 
explode(a.array_int,a.array_string) tmp1 as e1,e2 lateral view 
explode(a.array_int,a.array_string) tmp2 as e11,e12;"
+
+    qt_test12 "select id,e1 from array_test as a lateral view 
explode_outer(a.array_string) tmp1 as e1;"
+    qt_test13 "select id,e1 from array_test as a lateral view 
explode_outer(a.array_int) tmp1 as e1;"
+    qt_test14 "select id,e1,e2 from array_test as a lateral view 
explode_outer(a.array_int,a.array_string) tmp1 as e1,e2;"
+    qt_test15 "select id,e1,e2 from array_test as a lateral view 
explode_outer(a.array_string,a.array_int) tmp1 as e1,e2;"
+    qt_test16 "select id,e1,e2,e3 from array_test as a lateral view 
explode_outer(a.array_string,a.array_int,a.array_int) tmp1 as e1,e2,e3;"
+    qt_test17 "select id,e1,e2,e11,e12 from array_test as a lateral view 
explode_outer(a.array_int,a.array_string) tmp1 as e1,e2 lateral view 
explode_outer(a.array_int,a.array_string) tmp2 as e11,e12;"
+
+    qt_test18 "select id,e1 from array_test as a lateral view 
explode_variant_array(a.v_string['a']) tmp1 as e1;"
+    qt_test19 "select id,e1 from array_test as a lateral view 
explode_variant_array(a.v_int['a']) tmp1 as e1;"
+    qt_test20 "select id,e1,e2 from array_test as a lateral view 
explode_variant_array(a.v_int['a'],a.v_string['a']) tmp1 as e1,e2;"
+    qt_test21 "select id,e1,e2 from array_test as a lateral view 
explode_variant_array(a.v_string['a'],a.v_int['a']) tmp1 as e1,e2;"
+    qt_test22 "select id,e1,e2,e3 from array_test as a lateral view 
explode_variant_array(a.v_string['a'],a.v_int['a'],a.v_int['a']) tmp1 as 
e1,e2,e3;"
+    qt_test23 "select id,e1,e2,e11,e12 from array_test as a lateral view 
explode_variant_array(a.v_int['a'],a.v_string['a']) tmp1 as e1,e2 lateral view 
explode_variant_array(a.v_int['a'],a.v_string['a']) tmp2 as e11,e12;"
+
 }


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

Reply via email to