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

Mryange 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 04b57232a48 [Feat](map) Support function map_from_array/entries 
(#67045)
04b57232a48 is described below

commit 04b57232a48f9587857deaac89c0d6e7baf363ca
Author: linrrarity <[email protected]>
AuthorDate: Mon Aug 24 16:57:18 2026 +0800

    [Feat](map) Support function map_from_array/entries (#67045)
    
    ## Release note
    
    Support function `map_from_array` and `map_from_entries'
    
    doc: https://github.com/apache/doris-website/pull/4083
    
    ```sql
    SELECT map_from_arrays([1, 2], [10, 20]) AS result;
    +--------------+
    | result       |
    +--------------+
    | {1:10, 2:20} |
    +--------------+
    
    
    SELECT map_from_entries(array(struct(1, 10), struct(2, 20))) AS result;
    +--------------+
    | result       |
    +--------------+
    | {1:10, 2:20} |
    +--------------+
    ```
---
 be/src/exprs/function/function_map.cpp             | 196 +++++++++++++
 be/test/exprs/function/function_map_test.cpp       | 313 ++++++++++++++++++++-
 .../doris/catalog/BuiltinScalarFunctions.java      |   4 +
 .../expressions/functions/scalar/MapEntries.java   |  10 +-
 .../functions/scalar/MapFromArrays.java            |  97 +++++++
 .../functions/scalar/MapFromEntries.java           | 108 +++++++
 .../expressions/visitor/ScalarFunctionVisitor.java |  10 +
 .../scalar/MapConstructionFunctionsTest.java       | 214 ++++++++++++++
 .../map_functions/test_map_from_arrays_entries.out |  41 +++
 .../test_map_from_arrays_entries.groovy            | 213 ++++++++++++++
 10 files changed, 1204 insertions(+), 2 deletions(-)

diff --git a/be/src/exprs/function/function_map.cpp 
b/be/src/exprs/function/function_map.cpp
index e0b06e7450d..bd1b8281904 100644
--- a/be/src/exprs/function/function_map.cpp
+++ b/be/src/exprs/function/function_map.cpp
@@ -38,6 +38,7 @@
 #include "core/column/column_const.h"
 #include "core/column/column_map.h"
 #include "core/column/column_nullable.h"
+#include "core/column/column_struct.h"
 #include "core/column/column_vector.h"
 #include "core/data_type/data_type.h"
 #include "core/data_type/data_type_array.h"
@@ -49,6 +50,7 @@
 #include "core/data_type/primitive_type.h"
 #include "core/typeid_cast.h"
 #include "core/types.h"
+#include "exec/common/util.hpp"
 #include "exprs/aggregate/aggregate_function.h"
 #include "exprs/function/array/function_array_index.h"
 #include "exprs/function/function.h"
@@ -61,6 +63,124 @@ class FunctionContext;
 
 namespace doris {
 
+class FunctionMapFromArrays : public IFunction {
+public:
+    static constexpr auto name = "map_from_arrays";
+    static FunctionPtr create() { return 
std::make_shared<FunctionMapFromArrays>(); }
+
+    String get_name() const override { return name; }
+    size_t get_number_of_arguments() const override { return 2; }
+    bool use_default_implementation_for_nulls() const override { return false; 
}
+
+    DataTypePtr get_return_type_impl(const DataTypes& arguments) const 
override {
+        const auto& key_array_type =
+                assert_cast<const 
DataTypeArray&>(*remove_nullable(arguments[0]));
+        const auto& value_array_type =
+                assert_cast<const 
DataTypeArray&>(*remove_nullable(arguments[1]));
+        auto map_type =
+                
std::make_shared<DataTypeMap>(make_nullable(key_array_type.get_nested_type()),
+                                              
make_nullable(value_array_type.get_nested_type()));
+        return have_nullable(arguments) ? make_nullable(std::move(map_type)) : 
map_type;
+    }
+
+    Status execute_impl(FunctionContext* context, Block& block, const 
ColumnNumbers& arguments,
+                        uint32_t result, size_t input_rows_count) const 
override {
+        const auto& [key_column, key_is_const] =
+                unpack_if_const(block.get_by_position(arguments[0]).column);
+        const auto& [value_column, value_is_const] =
+                unpack_if_const(block.get_by_position(arguments[1]).column);
+
+        auto result_null_map = ColumnUInt8::create(input_rows_count, 0);
+        auto& result_null_map_data = result_null_map->get_data();
+        auto merge_null_map = [&](const ColumnPtr& column, bool is_const) -> 
const IColumn& {
+            if (const auto* null_col = 
check_and_get_column<ColumnNullable>(column.get())) {
+                VectorizedUtils::update_null_map(result_null_map_data,
+                                                 
null_col->get_null_map_data(), is_const);
+                return null_col->get_nested_column();
+            }
+            return *column;
+        };
+        const auto& keys =
+                assert_cast<const ColumnArray&>(merge_null_map(key_column, 
key_is_const));
+        const auto& values =
+                assert_cast<const ColumnArray&>(merge_null_map(value_column, 
value_is_const));
+        bool has_mismatched_null_row = false;
+        RETURN_IF_ERROR(check_arguments(keys, key_is_const, values, 
value_is_const,
+                                        result_null_map_data, input_rows_count,
+                                        has_mismatched_null_row));
+
+        ColumnPtr result_keys = keys.get_data_ptr();
+        ColumnPtr result_values = values.get_data_ptr();
+        ColumnPtr result_offsets = keys.get_offsets_ptr();
+        if (has_mismatched_null_row || key_is_const || value_is_const) {
+            auto filtered_keys = keys.get_data().clone_empty();
+            auto filtered_values = values.get_data().clone_empty();
+            auto filtered_offsets = ColumnArray::ColumnOffsets::create();
+            filtered_offsets->reserve(input_rows_count);
+
+            size_t output_offset = 0;
+            for (size_t row = 0; row < input_rows_count; ++row) {
+                if (!result_null_map_data[row]) {
+                    const size_t key_row = index_check_const(row, 
key_is_const);
+                    const size_t value_row = index_check_const(row, 
value_is_const);
+                    const size_t key_begin = key_row == 0 ? 0 : 
keys.get_offsets()[key_row - 1];
+                    const size_t value_begin =
+                            value_row == 0 ? 0 : 
values.get_offsets()[value_row - 1];
+                    const size_t entry_count = keys.size_at(key_row);
+                    filtered_keys->insert_range_from(keys.get_data(), 
key_begin, entry_count);
+                    filtered_values->insert_range_from(values.get_data(), 
value_begin, entry_count);
+                    output_offset += entry_count;
+                }
+                filtered_offsets->insert_value(output_offset);
+            }
+            result_keys = std::move(filtered_keys);
+            result_values = std::move(filtered_values);
+            result_offsets = std::move(filtered_offsets);
+        }
+
+        auto result_map =
+                ColumnMap::create(make_nullable(result_keys), 
make_nullable(result_values),
+                                  std::move(result_offsets));
+        RETURN_IF_ERROR(result_map->deduplicate_keys());
+        if (block.get_by_position(result).type->is_nullable()) {
+            block.replace_by_position(result, 
ColumnNullable::create(std::move(result_map),
+                                                                     
std::move(result_null_map)));
+        } else {
+            block.replace_by_position(result, std::move(result_map));
+        }
+        return Status::OK();
+    }
+
+private:
+    /// Non-const key/value columns must contain input_rows_count rows. A 
const array
+    /// is broadcast from its single nested row. Every non-null result row 
must have
+    /// equally sized key/value arrays; a mismatched NULL row is marked for 
rebuilding
+    /// because its nested payload cannot be shared by the result map.
+    static Status check_arguments(const ColumnArray& keys, bool key_is_const,
+                                  const ColumnArray& values, bool 
value_is_const,
+                                  const NullMap& result_null_map, size_t 
input_rows_count,
+                                  bool& has_mismatched_null_row) {
+        if ((!key_is_const && keys.size() != input_rows_count) ||
+            (!value_is_const && values.size() != input_rows_count)) {
+            return Status::InvalidArgument(
+                    "Key and value arrays of function {} must have the same 
length", name);
+        }
+        for (size_t row = 0; row < input_rows_count; ++row) {
+            const size_t key_size = keys.size_at(index_check_const(row, 
key_is_const));
+            const size_t value_size = values.size_at(index_check_const(row, 
value_is_const));
+            if (key_size == value_size) {
+                continue;
+            }
+            if (!result_null_map[row]) {
+                return Status::InvalidArgument(
+                        "Key and value arrays of function {} must have the 
same length", name);
+            }
+            has_mismatched_null_row = true;
+        }
+        return Status::OK();
+    }
+};
+
 // construct a map
 // map(key1, value2, key2, value2) -> {key1: value2, key2: value2}
 class FunctionMap : public IFunction {
@@ -337,6 +457,80 @@ public:
     }
 };
 
+class FunctionMapFromEntries : public IFunction {
+public:
+    static constexpr auto name = "map_from_entries";
+    static FunctionPtr create() { return 
std::make_shared<FunctionMapFromEntries>(); }
+
+    String get_name() const override { return name; }
+    size_t get_number_of_arguments() const override { return 1; }
+    bool use_default_implementation_for_nulls() const override { return false; 
}
+
+    DataTypePtr get_return_type_impl(const DataTypes& arguments) const 
override {
+        if (arguments[0]->is_null_literal()) {
+            return make_nullable(std::make_shared<DataTypeMap>(arguments[0], 
arguments[0]));
+        }
+        const auto& array_type = assert_cast<const 
DataTypeArray&>(*remove_nullable(arguments[0]));
+        const auto& struct_type =
+                assert_cast<const 
DataTypeStruct&>(*remove_nullable(array_type.get_nested_type()));
+        DCHECK_EQ(struct_type.get_elements().size(), 2);
+        auto map_type = 
std::make_shared<DataTypeMap>(make_nullable(struct_type.get_element(0)),
+                                                      
make_nullable(struct_type.get_element(1)));
+        return arguments[0]->is_nullable() ? 
make_nullable(std::move(map_type)) : map_type;
+    }
+
+    Status execute_impl(FunctionContext* context, Block& block, const 
ColumnNumbers& arguments,
+                        uint32_t result, size_t input_rows_count) const 
override {
+        ColumnPtr entries_column = block.get_by_position(arguments[0]).column;
+        const auto* nullable_array = 
check_and_get_column<ColumnNullable>(entries_column.get());
+        if (nullable_array != nullptr) {
+            entries_column = nullable_array->get_nested_column_ptr();
+        }
+
+        const auto& entries = assert_cast<const ColumnArray&>(*entries_column);
+        const auto& nullable_entries = assert_cast<const 
ColumnNullable&>(entries.get_data());
+        RETURN_IF_ERROR(check_arguments(entries, nullable_entries, 
nullable_array));
+
+        const auto& entry_struct =
+                assert_cast<const 
ColumnStruct&>(nullable_entries.get_nested_column());
+        auto result_map = 
ColumnMap::create(make_nullable(entry_struct.get_column_ptr(0)),
+                                            
make_nullable(entry_struct.get_column_ptr(1)),
+                                            entries.get_offsets_ptr());
+        RETURN_IF_ERROR(result_map->deduplicate_keys());
+        if (nullable_array != nullptr) {
+            block.replace_by_position(
+                    result, ColumnNullable::create(std::move(result_map),
+                                                   
nullable_array->get_null_map_column_ptr()));
+        } else {
+            block.replace_by_position(result, std::move(result_map));
+        }
+        return Status::OK();
+    }
+
+private:
+    /// Every element of a non-null outer array must be a non-null struct 
entry.
+    /// Null outer arrays are skipped, while null key or value fields inside a
+    /// non-null struct are allowed.
+    static Status check_arguments(const ColumnArray& entries,
+                                  const ColumnNullable& nullable_entries,
+                                  const ColumnNullable* nullable_array) {
+        if (!nullable_entries.has_null()) {
+            return Status::OK();
+        }
+        for (size_t row = 0; row < entries.size(); ++row) {
+            if (nullable_array != nullptr && nullable_array->is_null_at(row)) {
+                continue;
+            }
+            const size_t begin = row == 0 ? 0 : entries.get_offsets()[row - 1];
+            const size_t end = entries.get_offsets()[row];
+            if (nullable_entries.has_null(begin, end)) {
+                return Status::InvalidArgument("Map entry of function {} 
cannot be null", name);
+            }
+        }
+        return Status::OK();
+    }
+};
+
 class FunctionStrToMap : public IFunction {
 public:
     static constexpr auto name = "str_to_map";
@@ -790,12 +984,14 @@ private:
 };
 
 void register_function_map(SimpleFunctionFactory& factory) {
+    factory.register_function<FunctionMapFromArrays>();
     factory.register_function<FunctionMap>();
     factory.register_function<FunctionMapContains<true>>();
     factory.register_function<FunctionMapContains<false>>();
     factory.register_function<FunctionMapKeysOrValues<true>>();
     factory.register_function<FunctionMapKeysOrValues<false>>();
     factory.register_function<FunctionMapEntries>();
+    factory.register_function<FunctionMapFromEntries>();
     factory.register_function<FunctionStrToMap>();
     factory.register_function<FunctionMapContainsEntry>();
     factory.register_function<FunctionDeduplicateMap>();
diff --git a/be/test/exprs/function/function_map_test.cpp 
b/be/test/exprs/function/function_map_test.cpp
index c791b2b25cd..22cdd72256f 100644
--- a/be/test/exprs/function/function_map_test.cpp
+++ b/be/test/exprs/function/function_map_test.cpp
@@ -18,16 +18,87 @@
 #include <fmt/core.h>
 #include <gtest/gtest.h>
 
+#include <optional>
 #include <string>
+#include <vector>
 
+#include "core/assert_cast.h"
 #include "core/column/column_array.h"
+#include "core/column/column_const.h"
 #include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_struct.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
 #include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_struct.h"
 #include "core/types.h"
 #include "exprs/function/function_test_util.h"
 
 namespace doris {
 
+namespace {
+
+MutableColumnPtr make_nullable_int_column(const 
std::vector<std::optional<int32_t>>& values) {
+    auto nested = ColumnInt32::create();
+    auto null_map = ColumnUInt8::create();
+    for (const auto& value : values) {
+        nested->insert_value(value.value_or(0));
+        null_map->insert_value(value.has_value() ? 0 : 1);
+    }
+    return ColumnNullable::create(std::move(nested), std::move(null_map));
+}
+
+MutableColumnPtr make_offsets(const std::vector<size_t>& offsets) {
+    auto result = ColumnArray::ColumnOffsets::create();
+    for (size_t offset : offsets) {
+        result->insert_value(offset);
+    }
+    return result;
+}
+
+ColumnPtr make_int_array(const std::vector<std::optional<int32_t>>& values,
+                         const std::vector<size_t>& offsets) {
+    return ColumnArray::create(make_nullable_int_column(values), 
make_offsets(offsets));
+}
+
+ColumnPtr make_int_entry_array(const std::vector<std::optional<int32_t>>& keys,
+                               const std::vector<std::optional<int32_t>>& 
values,
+                               const std::vector<size_t>& offsets,
+                               const std::vector<bool>& null_entries = {}) {
+    auto entries = ColumnStruct::create(
+            Columns {make_nullable_int_column(keys), 
make_nullable_int_column(values)});
+    auto null_map = ColumnUInt8::create(entries->size(), 0);
+    for (size_t i = 0; i < null_entries.size(); ++i) {
+        null_map->get_data()[i] = null_entries[i];
+    }
+    return ColumnArray::create(ColumnNullable::create(std::move(entries), 
std::move(null_map)),
+                               make_offsets(offsets));
+}
+
+int32_t get_nullable_int(const IColumn& column, size_t row) {
+    const auto& nullable = assert_cast<const ColumnNullable&>(column);
+    return assert_cast<const 
ColumnInt32&>(nullable.get_nested_column()).get_element(row);
+}
+
+Status execute_map_function(const std::string& name, Block& block, const 
ColumnNumbers& arguments,
+                            uint32_t result, const DataTypePtr& return_type) {
+    ColumnsWithTypeAndName argument_template;
+    for (uint32_t argument : arguments) {
+        argument_template.push_back(block.get_by_position(argument));
+    }
+    auto function =
+            SimpleFunctionFactory::instance().get_function(name, 
argument_template, return_type);
+    if (function == nullptr) {
+        return Status::InternalError("function {} is not registered", name);
+    }
+    return function->execute(nullptr, block, arguments, result, block.rows());
+}
+
+} // namespace
+
 TEST(FunctionMapTest, deduplicate_map) {
     const std::string func_name = "deduplicate_map";
 
@@ -81,4 +152,244 @@ TEST(FunctionMapTest, deduplicate_map) {
         ASSERT_EQ(map_size, 8) << "deduplicate map failed at row " << i;
     }
 }
-} // namespace doris
\ No newline at end of file
+
+TEST(FunctionMapTest, map_from_arrays) {
+    auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+    auto array_type = std::make_shared<DataTypeArray>(nullable_int);
+    auto map_type = std::make_shared<DataTypeMap>(nullable_int, nullable_int);
+
+    {
+        Block block;
+        block.insert({make_int_array({1, 1, 2}, {2, 3}), array_type, "keys"});
+        block.insert({make_int_array({10, 20, 30}, {2, 3}), array_type, 
"values"});
+        block.insert({nullptr, map_type, "result"});
+
+        ASSERT_TRUE(execute_map_function("map_from_arrays", block, {0, 1}, 2, 
map_type).ok());
+        const auto& result = assert_cast<const 
ColumnMap&>(*block.get_by_position(2).column);
+        ASSERT_EQ(result.get_offsets()[0], 1);
+        ASSERT_EQ(result.get_offsets()[1], 2);
+        EXPECT_EQ(get_nullable_int(result.get_keys(), 0), 1);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 0), 20);
+        EXPECT_EQ(get_nullable_int(result.get_keys(), 1), 2);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 1), 30);
+    }
+
+    {
+        Block block;
+        block.insert({make_int_array({1, 2, 3}, {2, 3}), array_type, "keys"});
+        block.insert({make_int_array({10, 20, 30}, {1, 3}), array_type, 
"values"});
+        block.insert({nullptr, map_type, "result"});
+
+        auto status = execute_map_function("map_from_arrays", block, {0, 1}, 
2, map_type);
+        ASSERT_TRUE(status.is<ErrorCode::INVALID_ARGUMENT>()) << 
status.to_string();
+        EXPECT_NE(status.to_string().find("Key and value arrays of function 
map_from_arrays must "
+                                          "have the same length"),
+                  std::string::npos);
+    }
+
+    {
+        Block block;
+        block.insert({ColumnConst::create(make_int_array({1}, {1}), 2), 
array_type, "keys"});
+        block.insert({make_int_array({10, 20}, {1, 2}), array_type, "values"});
+        block.insert({nullptr, map_type, "result"});
+
+        ASSERT_TRUE(execute_map_function("map_from_arrays", block, {0, 1}, 2, 
map_type).ok());
+        const auto& result = assert_cast<const 
ColumnMap&>(*block.get_by_position(2).column);
+        EXPECT_EQ(result.get_offsets()[0], 1);
+        EXPECT_EQ(result.get_offsets()[1], 2);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 0), 10);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 1), 20);
+    }
+
+    {
+        Block block;
+        block.insert({make_int_array({1, 2}, {1, 2}), array_type, "keys"});
+        block.insert({ColumnConst::create(make_int_array({10}, {1}), 2), 
array_type, "values"});
+        block.insert({nullptr, map_type, "result"});
+
+        ASSERT_TRUE(execute_map_function("map_from_arrays", block, {0, 1}, 2, 
map_type).ok());
+        const auto& result = assert_cast<const 
ColumnMap&>(*block.get_by_position(2).column);
+        EXPECT_EQ(result.get_offsets()[0], 1);
+        EXPECT_EQ(result.get_offsets()[1], 2);
+        EXPECT_EQ(get_nullable_int(result.get_keys(), 0), 1);
+        EXPECT_EQ(get_nullable_int(result.get_keys(), 1), 2);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 0), 10);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 1), 10);
+    }
+}
+
+TEST(FunctionMapTest, map_from_arrays_nullable) {
+    auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+    auto array_type = std::make_shared<DataTypeArray>(nullable_int);
+    auto nullable_array_type = make_nullable(array_type);
+    auto map_type = std::make_shared<DataTypeMap>(nullable_int, nullable_int);
+    auto nullable_map_type = make_nullable(map_type);
+    auto key_null_map = ColumnUInt8::create();
+    key_null_map->insert_value(0);
+    key_null_map->insert_value(0);
+    key_null_map->insert_value(1);
+    key_null_map->insert_value(0);
+    key_null_map->insert_value(0);
+    auto value_null_map = ColumnUInt8::create();
+    value_null_map->insert_value(0);
+    value_null_map->insert_value(0);
+    value_null_map->insert_value(0);
+    value_null_map->insert_value(0);
+    value_null_map->insert_value(0);
+
+    auto keys = make_int_array({1, 2, 99, 3, 4, 5}, {1, 2, 3, 5, 6});
+    auto values = make_int_array({10, 20, 990, 30, 40, 50}, {1, 2, 3, 5, 6});
+    const auto& key_array = assert_cast<const ColumnArray&>(*keys);
+    const auto& value_array = assert_cast<const ColumnArray&>(*values);
+    const auto* key_data = key_array.get_data_ptr().get();
+    const auto* value_data = value_array.get_data_ptr().get();
+    const auto* key_offsets = key_array.get_offsets_ptr().get();
+
+    Block block;
+    block.insert({ColumnNullable::create(std::move(keys), 
std::move(key_null_map)),
+                  nullable_array_type, "keys"});
+    block.insert({ColumnNullable::create(std::move(values), 
std::move(value_null_map)),
+                  nullable_array_type, "values"});
+    block.insert({nullptr, nullable_map_type, "result"});
+
+    ASSERT_TRUE(execute_map_function("map_from_arrays", block, {0, 1}, 2, 
nullable_map_type).ok());
+    const auto& result = assert_cast<const 
ColumnNullable&>(*block.get_by_position(2).column);
+    EXPECT_FALSE(result.is_null_at(0));
+    EXPECT_FALSE(result.is_null_at(1));
+    EXPECT_TRUE(result.is_null_at(2));
+    EXPECT_FALSE(result.is_null_at(3));
+    EXPECT_FALSE(result.is_null_at(4));
+    const auto& nested = assert_cast<const 
ColumnMap&>(result.get_nested_column());
+    EXPECT_EQ(nested.get_keys_ptr().get(), key_data);
+    EXPECT_EQ(nested.get_values_ptr().get(), value_data);
+    EXPECT_EQ(nested.get_offsets_ptr().get(), key_offsets);
+    EXPECT_EQ(nested.get_offsets()[0], 1);
+    EXPECT_EQ(nested.get_offsets()[1], 2);
+    EXPECT_EQ(nested.get_offsets()[2], 3);
+    EXPECT_EQ(nested.get_offsets()[3], 5);
+    EXPECT_EQ(nested.get_offsets()[4], 6);
+    EXPECT_EQ(get_nullable_int(nested.get_keys(), 3), 3);
+    EXPECT_EQ(get_nullable_int(nested.get_values(), 3), 30);
+}
+
+TEST(FunctionMapTest, map_from_arrays_mismatched_nullable_payload) {
+    auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+    auto array_type = std::make_shared<DataTypeArray>(nullable_int);
+    auto nullable_array_type = make_nullable(array_type);
+    auto nullable_map_type =
+            make_nullable(std::make_shared<DataTypeMap>(nullable_int, 
nullable_int));
+    auto key_null_map = ColumnUInt8::create();
+    key_null_map->insert_value(0);
+    key_null_map->insert_value(1);
+    key_null_map->insert_value(0);
+    auto value_null_map = ColumnUInt8::create(3, 0);
+
+    Block block;
+    block.insert({ColumnNullable::create(make_int_array({1, 98, 99, 3}, {1, 3, 
4}),
+                                         std::move(key_null_map)),
+                  nullable_array_type, "keys"});
+    block.insert({ColumnNullable::create(make_int_array({10, 990, 30}, {1, 2, 
3}),
+                                         std::move(value_null_map)),
+                  nullable_array_type, "values"});
+    block.insert({nullptr, nullable_map_type, "result"});
+
+    ASSERT_TRUE(execute_map_function("map_from_arrays", block, {0, 1}, 2, 
nullable_map_type).ok());
+    const auto& result = assert_cast<const 
ColumnNullable&>(*block.get_by_position(2).column);
+    EXPECT_FALSE(result.is_null_at(0));
+    EXPECT_TRUE(result.is_null_at(1));
+    EXPECT_FALSE(result.is_null_at(2));
+    const auto& nested = assert_cast<const 
ColumnMap&>(result.get_nested_column());
+    EXPECT_EQ(nested.get_offsets()[0], 1);
+    EXPECT_EQ(nested.get_offsets()[1], 1);
+    EXPECT_EQ(nested.get_offsets()[2], 2);
+    EXPECT_EQ(get_nullable_int(nested.get_keys(), 1), 3);
+    EXPECT_EQ(get_nullable_int(nested.get_values(), 1), 30);
+}
+
+TEST(FunctionMapTest, map_from_entries) {
+    auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+    auto struct_type = std::make_shared<DataTypeStruct>(DataTypes 
{nullable_int, nullable_int},
+                                                        Strings {"key", 
"value"});
+    auto array_type = 
std::make_shared<DataTypeArray>(make_nullable(struct_type));
+    auto map_type = std::make_shared<DataTypeMap>(nullable_int, nullable_int);
+
+    {
+        Block block;
+        block.insert(
+                {make_int_entry_array({1, 1, 2}, {10, 20, 30}, {2, 3}), 
array_type, "entries"});
+        block.insert({nullptr, map_type, "result"});
+
+        ASSERT_TRUE(execute_map_function("map_from_entries", block, {0}, 1, 
map_type).ok());
+        const auto& result = assert_cast<const 
ColumnMap&>(*block.get_by_position(1).column);
+        ASSERT_EQ(result.get_offsets()[0], 1);
+        ASSERT_EQ(result.get_offsets()[1], 2);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 0), 20);
+        EXPECT_EQ(get_nullable_int(result.get_values(), 1), 30);
+    }
+
+    {
+        Block block;
+        block.insert({ColumnConst::create(make_int_entry_array({1, 2}, {10, 
20}, {2}), 3),
+                      array_type, "entries"});
+        block.insert({nullptr, map_type, "result"});
+
+        ASSERT_TRUE(execute_map_function("map_from_entries", block, {0}, 1, 
map_type).ok());
+        const auto& result = assert_cast<const 
ColumnConst&>(*block.get_by_position(1).column);
+        EXPECT_EQ(result.size(), 3);
+    }
+
+    {
+        auto nullable_array_type = make_nullable(array_type);
+        auto nullable_map_type = make_nullable(map_type);
+        auto null_map = ColumnUInt8::create(1, 0);
+
+        Block block;
+        block.insert({ColumnConst::create(
+                              ColumnNullable::create(
+                                      make_int_entry_array({std::nullopt}, 
{std::nullopt}, {1}),
+                                      std::move(null_map)),
+                              3),
+                      nullable_array_type, "entries"});
+        block.insert({nullptr, nullable_map_type, "result"});
+
+        ASSERT_TRUE(
+                execute_map_function("map_from_entries", block, {0}, 1, 
nullable_map_type).ok());
+        const auto& result = assert_cast<const 
ColumnConst&>(*block.get_by_position(1).column);
+        EXPECT_EQ(result.size(), 3);
+    }
+
+    {
+        Block block;
+        block.insert({make_int_entry_array({1}, {10}, {1}, {true}), 
array_type, "entries"});
+        block.insert({nullptr, map_type, "result"});
+
+        auto status = execute_map_function("map_from_entries", block, {0}, 1, 
map_type);
+        ASSERT_TRUE(status.is<ErrorCode::INVALID_ARGUMENT>()) << 
status.to_string();
+    }
+}
+
+TEST(FunctionMapTest, map_from_entries_nullable) {
+    auto nullable_int = make_nullable(std::make_shared<DataTypeInt32>());
+    auto struct_type = std::make_shared<DataTypeStruct>(DataTypes 
{nullable_int, nullable_int},
+                                                        Strings {"key", 
"value"});
+    auto array_type = 
std::make_shared<DataTypeArray>(make_nullable(struct_type));
+    auto nullable_array_type = make_nullable(array_type);
+    auto map_type = std::make_shared<DataTypeMap>(nullable_int, nullable_int);
+    auto nullable_map_type = make_nullable(map_type);
+    auto null_map = ColumnUInt8::create();
+    null_map->insert_value(0);
+    null_map->insert_value(1);
+
+    Block block;
+    block.insert(
+            {ColumnNullable::create(make_int_entry_array({1, 2}, {10, 20}, {1, 
2}, {false, true}),
+                                    std::move(null_map)),
+             nullable_array_type, "entries"});
+    block.insert({nullptr, nullable_map_type, "result"});
+
+    ASSERT_TRUE(execute_map_function("map_from_entries", block, {0}, 1, 
nullable_map_type).ok());
+    const auto& result = assert_cast<const 
ColumnNullable&>(*block.get_by_position(1).column);
+    EXPECT_FALSE(result.is_null_at(0));
+    EXPECT_TRUE(result.is_null_at(1));
+}
+} // namespace doris
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
index 762f3afc0f6..62369888db9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java
@@ -335,6 +335,8 @@ import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsEn
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsKey;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsValue;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapEntries;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapFromArrays;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapFromEntries;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapKeys;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapSize;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapValues;
@@ -917,6 +919,8 @@ public class BuiltinScalarFunctions implements 
FunctionHelper {
             scalar(MapContainsKey.class, "map_contains_key"),
             scalar(MapContainsValue.class, "map_contains_value"),
             scalar(MapEntries.class, "map_entries"),
+            scalar(MapFromArrays.class, "map_from_arrays"),
+            scalar(MapFromEntries.class, "map_from_entries"),
             scalar(MapKeys.class, "map_keys"),
             scalar(MapSize.class, "map_size"),
             scalar(MapValues.class, "map_values"),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapEntries.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapEntries.java
index 22a14c31df3..8d40f8fbb30 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapEntries.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapEntries.java
@@ -20,6 +20,7 @@ package 
org.apache.doris.nereids.trees.expressions.functions.scalar;
 import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.PreferPushDownProject;
+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.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.functions.SearchSignature;
@@ -42,7 +43,7 @@ import java.util.List;
  * fields 'key' and 'value'.
  */
 public class MapEntries extends ScalarFunction
-        implements UnaryExpression, CustomSignature, PropagateNullable, 
PreferPushDownProject {
+        implements UnaryExpression, ComputePrecision, CustomSignature, 
PropagateNullable, PreferPushDownProject {
 
     /**
      * constructor with 1 argument.
@@ -94,4 +95,11 @@ public class MapEntries extends ScalarFunction
             return null; // unreachable
         }
     }
+
+    // Prevent MAP<DECIMAL(38,0), DECIMAL(38,38)> from being resolved as
+    // MAP<DECIMAL(38,6), DECIMAL(38,6)>, and nested DATETIMEV2(6) as 
DATETIMEV2(0).
+    @Override
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromArrays.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromArrays.java
new file mode 100644
index 00000000000..8626296cd20
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromArrays.java
@@ -0,0 +1,97 @@
+// 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.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.PreferPushDownProject;
+import org.apache.doris.nereids.trees.expressions.functions.ComputePrecision;
+import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.MapType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.TinyIntType;
+import org.apache.doris.nereids.types.coercion.AnyDataType;
+import org.apache.doris.nereids.types.coercion.FollowToAnyDataType;
+import org.apache.doris.nereids.util.TypeCoercionUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** Construct a Map from key and value arrays with identical per-row offsets. 
*/
+public class MapFromArrays extends ScalarFunction
+        implements BinaryExpression, ComputePrecision, 
ExplicitlyCastableSignature, PropagateNullable,
+        PreferPushDownProject {
+
+    public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+            FunctionSignature.ret(MapType.of(new FollowToAnyDataType(0), new 
FollowToAnyDataType(1)))
+                    .args(ArrayType.of(new AnyDataType(0)), ArrayType.of(new 
AnyDataType(1))));
+
+    public MapFromArrays(Expression keys, Expression values) {
+        super("map_from_arrays", keys, values);
+    }
+
+    private MapFromArrays(ScalarFunctionParams functionParams) {
+        super(functionParams);
+    }
+
+    @Override
+    public MapFromArrays withChildren(List<Expression> children) {
+        Preconditions.checkArgument(children.size() == 2);
+        return new MapFromArrays(getFunctionParams(children));
+    }
+
+    @Override
+    public List<FunctionSignature> getSignatures() {
+        return SIGNATURES;
+    }
+
+    /**
+     * Keep the resolved key and value types independent. The default 
precision promotion merges
+     * decimal and time types from all arguments, which can lose precision 
across the two map sides.
+     */
+    @Override
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
+    }
+
+    @Override
+    public FunctionSignature computeSignature(FunctionSignature signature) {
+        FunctionSignature resolvedSignature = 
super.computeSignature(signature);
+        DataType returnType = TypeCoercionUtils.replaceSpecifiedType(
+                resolvedSignature.returnType, NullType.class, 
TinyIntType.INSTANCE);
+        FunctionSignature normalizedSignature = resolvedSignature
+                .withArgumentTypes(getArguments(), (index, argumentType, 
argument) ->
+                        TypeCoercionUtils.replaceSpecifiedType(
+                                argumentType, NullType.class, 
TinyIntType.INSTANCE))
+                .withReturnType(returnType);
+        normalizedSignature.returnType.validateDataType();
+        return normalizedSignature;
+    }
+
+    @Override
+    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+        return visitor.visitMapFromArrays(this, context);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromEntries.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromEntries.java
new file mode 100644
index 00000000000..e2f6d1ec01c
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapFromEntries.java
@@ -0,0 +1,108 @@
+// 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.scalar;
+
+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.PreferPushDownProject;
+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.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.MapType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.types.TinyIntType;
+import org.apache.doris.nereids.util.TypeCoercionUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/** Construct a Map from an Array of two-field Struct entries. */
+public class MapFromEntries extends ScalarFunction
+        implements UnaryExpression, ComputePrecision, CustomSignature, 
PropagateNullable, PreferPushDownProject {
+
+    public MapFromEntries(Expression entries) {
+        super("map_from_entries", entries);
+    }
+
+    private MapFromEntries(ScalarFunctionParams functionParams) {
+        super(functionParams);
+    }
+
+    @Override
+    public MapFromEntries withChildren(List<Expression> children) {
+        Preconditions.checkArgument(children.size() == 1);
+        return new MapFromEntries(getFunctionParams(children));
+    }
+
+    @Override
+    public FunctionSignature customSignature() {
+        DataType inputType = getArgumentType(0);
+        if (inputType.isNullType()) {
+            inputType = ArrayType.of(defaultStructType());
+        }
+        if (!(inputType instanceof ArrayType)) {
+            throw new AnalysisException(
+                    "map_from_entries requires an array of structs with 
exactly two fields");
+        }
+        DataType itemType = ((ArrayType) inputType).getItemType();
+        if (itemType.isNullType()) {
+            inputType = ArrayType.of(defaultStructType());
+        } else if (!(itemType instanceof StructType)) {
+            throw new AnalysisException(
+                    "map_from_entries requires an array of structs with 
exactly two fields");
+        } else {
+            inputType = TypeCoercionUtils.replaceSpecifiedType(
+                    inputType, NullType.class, TinyIntType.INSTANCE);
+        }
+        List<StructField> fields = ((StructType) ((ArrayType) 
inputType).getItemType()).getFields();
+        if (fields.size() != 2) {
+            throw new AnalysisException(
+                    "map_from_entries requires an array of structs with 
exactly two fields");
+        }
+        MapType resultType = MapType.of(fields.get(0).getDataType(), 
fields.get(1).getDataType());
+        resultType.validateDataType();
+        return FunctionSignature.ret(resultType).args(inputType);
+    }
+
+    private static StructType defaultStructType() {
+        return new StructType(ImmutableList.of(
+                new StructField("key", TinyIntType.INSTANCE, true, ""),
+                new StructField("value", TinyIntType.INSTANCE, true, "")));
+    }
+
+    // Prevent STRUCT<DECIMAL(38,0), DECIMAL(38,38)> from being resolved as
+    // STRUCT<DECIMAL(38,6), DECIMAL(38,6)>, and nested DATETIMEV2(6) as 
DATETIMEV2(0).
+    @Override
+    public FunctionSignature computePrecision(FunctionSignature signature) {
+        return signature;
+    }
+
+    @Override
+    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+        return visitor.visitMapFromEntries(this, context);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
index 6b73a00b854..c0f03da1789 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java
@@ -354,6 +354,8 @@ import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsEn
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsKey;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapContainsValue;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapEntries;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapFromArrays;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.MapFromEntries;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapKeys;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapSize;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.MapValues;
@@ -2842,6 +2844,14 @@ public interface ScalarFunctionVisitor<R, C> {
         return visitScalarFunction(mapEntries, context);
     }
 
+    default R visitMapFromArrays(MapFromArrays mapFromArrays, C context) {
+        return visitScalarFunction(mapFromArrays, context);
+    }
+
+    default R visitMapFromEntries(MapFromEntries mapFromEntries, C context) {
+        return visitScalarFunction(mapFromEntries, context);
+    }
+
     default R visitMapKeys(MapKeys mapKeys, C context) {
         return visitScalarFunction(mapKeys, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapConstructionFunctionsTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapConstructionFunctionsTest.java
new file mode 100644
index 00000000000..89219419db1
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/MapConstructionFunctionsTest.java
@@ -0,0 +1,214 @@
+// 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.scalar;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.rules.expression.ExpressionRewriteTestHelper;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.DateTimeV2Type;
+import org.apache.doris.nereids.types.DecimalV3Type;
+import org.apache.doris.nereids.types.MapType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.types.TinyIntType;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class MapConstructionFunctionsTest {
+
+    private static final NereidsParser PARSER = new NereidsParser();
+
+    @Test
+    public void testMapFromArraysCanBeAnalyzed() {
+        Expression map = analyze("map_from_arrays([1, 2], [10, 20])");
+        Assertions.assertTrue(map instanceof MapFromArrays);
+        Assertions.assertEquals(
+                MapType.of(TinyIntType.INSTANCE, TinyIntType.INSTANCE), 
map.getDataType());
+
+        Assertions.assertThrows(RuntimeException.class,
+                () -> analyze("map_from_arrays([1, 2], 10)"));
+
+        AnalysisException complexKeyException = 
Assertions.assertThrows(AnalysisException.class,
+                () -> analyze("map_from_arrays([[1]], [10])"));
+        Assertions.assertTrue(complexKeyException.getMessage().contains(
+                "MAP key type must be a primitive type"), 
complexKeyException::getMessage);
+
+        assertMapFromArraysSignature("map_from_arrays([null], [null])",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromArraysSignature("map_from_arrays(null, null)",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromArraysSignature("map_from_arrays([], [])",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromArraysSignature(
+                "map_from_arrays(map_keys(map(null, null)), 
map_values(map(null, null)))",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromArraysSignature("map_from_arrays([1], [[null]])",
+                TinyIntType.INSTANCE, ArrayType.of(TinyIntType.INSTANCE));
+    }
+
+    @Test
+    public void testMapFromArraysPreservesIndependentPrecision() {
+        DataType decimalKeyType = DecimalV3Type.createDecimalV3Type(38, 0);
+        DataType decimalValueType = DecimalV3Type.createDecimalV3Type(38, 38);
+        assertMapFromArraysSignature(
+                "map_from_arrays(cast([1] as array<decimalv3(38, 0)>),"
+                        + " cast([0.12345678901234567890123456789012345678]"
+                        + " as array<decimalv3(38, 38)>))",
+                decimalKeyType, decimalValueType);
+
+        Expression map = analyze(
+                "map_from_arrays(cast(['2026-01-01 00:00:00'] as 
array<datetimev2(0)>),"
+                        + " array(struct(cast('2026-01-01 00:00:00.123456' as 
datetimev2(6)))))");
+        FunctionSignature signature = ((MapFromArrays) map).getSignature();
+        DataType keyType = ((ArrayType) signature.getArgType(0)).getItemType();
+        DataType valueType = ((ArrayType) 
signature.getArgType(1)).getItemType();
+        Assertions.assertEquals(DateTimeV2Type.of(0), keyType);
+        Assertions.assertTrue(valueType instanceof StructType);
+        Assertions.assertEquals(DateTimeV2Type.of(6),
+                ((StructType) valueType).getFields().get(0).getDataType());
+        Assertions.assertEquals(MapType.of(keyType, valueType), 
signature.returnType);
+    }
+
+    @Test
+    public void testMapFromEntriesCanBeAnalyzed() {
+        Expression map = analyze("map_from_entries(array(struct(1, 10), 
struct(2, 20)))");
+        Assertions.assertTrue(map instanceof MapFromEntries);
+        Assertions.assertEquals(
+                MapType.of(TinyIntType.INSTANCE, TinyIntType.INSTANCE), 
map.getDataType());
+
+        Expression nullMap = analyze("map_from_entries(NULL)");
+        Assertions.assertTrue(nullMap instanceof MapFromEntries);
+        Assertions.assertEquals(
+                MapType.of(TinyIntType.INSTANCE, TinyIntType.INSTANCE), 
nullMap.getDataType());
+
+        Assertions.assertThrows(AnalysisException.class,
+                () -> analyze("map_from_entries(1)"));
+        Assertions.assertThrows(AnalysisException.class,
+                () -> analyze("map_from_entries(array(struct(1)))"));
+        Assertions.assertThrows(AnalysisException.class,
+                () -> analyze("map_from_entries(array(struct(1, 2, 3)))"));
+
+        AnalysisException complexKeyException = 
Assertions.assertThrows(AnalysisException.class,
+                () -> analyze("map_from_entries(array(struct([1], 10)))"));
+        Assertions.assertTrue(complexKeyException.getMessage().contains(
+                "MAP key type must be a primitive type"), 
complexKeyException::getMessage);
+
+        assertMapFromEntriesSignature("map_from_entries([])",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromEntriesSignature("map_from_entries([null])",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromEntriesSignature("map_from_entries(array(struct(1, 
null)))",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromEntriesSignature("map_from_entries(map_entries(map(null, 
null)))",
+                TinyIntType.INSTANCE, TinyIntType.INSTANCE);
+        assertMapFromEntriesSignature("map_from_entries(array(struct(1, 
[null])))",
+                TinyIntType.INSTANCE, ArrayType.of(TinyIntType.INSTANCE));
+    }
+
+    @Test
+    public void testMapFromEntriesPreservesIndependentPrecision() {
+        DataType decimalKeyType = DecimalV3Type.createDecimalV3Type(38, 0);
+        DataType decimalValueType = DecimalV3Type.createDecimalV3Type(38, 38);
+        assertMapFromEntriesSignature(
+                "map_from_entries(map_entries(map(cast(1 as decimalv3(38, 0)),"
+                        + " cast(0.12345678901234567890123456789012345678"
+                        + " as decimalv3(38, 38)))))",
+                decimalKeyType, decimalValueType);
+
+        DataType timeKeyType = DateTimeV2Type.of(0);
+        DataType timeValueType = new StructType(ImmutableList.of(
+                new StructField("col1", DateTimeV2Type.of(6), true, "")));
+        assertMapFromEntriesSignature(
+                "map_from_entries(map_entries(map("
+                        + "cast('2026-01-01 00:00:00' as datetimev2(0)),"
+                        + " struct(cast('2026-01-01 00:00:00.123456' as 
datetimev2(6))))))",
+                timeKeyType, timeValueType);
+    }
+
+    @Test
+    public void testMapEntriesPreservesIndependentPrecision() {
+        DataType decimalKeyType = DecimalV3Type.createDecimalV3Type(38, 0);
+        DataType decimalValueType = DecimalV3Type.createDecimalV3Type(38, 38);
+        assertMapEntriesSignature(
+                "map_entries(map(cast(1 as decimalv3(38, 0)),"
+                        + " cast(0.12345678901234567890123456789012345678"
+                        + " as decimalv3(38, 38))))",
+                decimalKeyType, decimalValueType);
+
+        DataType timeKeyType = DateTimeV2Type.of(0);
+        DataType timeValueType = new StructType(ImmutableList.of(
+                new StructField("col1", DateTimeV2Type.of(6), true, "")));
+        assertMapEntriesSignature(
+                "map_entries(map(cast('2026-01-01 00:00:00' as datetimev2(0)),"
+                        + " struct(cast('2026-01-01 00:00:00.123456' as 
datetimev2(6)))))",
+                timeKeyType, timeValueType);
+    }
+
+    private void assertMapFromArraysSignature(String sql, DataType keyType, 
DataType valueType) {
+        Expression map = analyze(sql);
+        Assertions.assertTrue(map instanceof MapFromArrays);
+        FunctionSignature signature = ((MapFromArrays) map).getSignature();
+        Assertions.assertEquals(MapType.of(keyType, valueType), 
signature.returnType);
+        Assertions.assertEquals(ArrayType.of(keyType), 
signature.getArgType(0));
+        Assertions.assertEquals(ArrayType.of(valueType), 
signature.getArgType(1));
+        assertNoNullType(signature);
+    }
+
+    private void assertMapFromEntriesSignature(String sql, DataType keyType, 
DataType valueType) {
+        Expression map = analyze(sql);
+        Assertions.assertTrue(map instanceof MapFromEntries);
+        FunctionSignature signature = ((MapFromEntries) map).getSignature();
+        Assertions.assertEquals(MapType.of(keyType, valueType), 
signature.returnType);
+        Assertions.assertTrue(signature.getArgType(0) instanceof ArrayType);
+        DataType itemType = ((ArrayType) 
signature.getArgType(0)).getItemType();
+        Assertions.assertTrue(itemType instanceof StructType);
+        Assertions.assertEquals(keyType, ((StructType) 
itemType).getFields().get(0).getDataType());
+        Assertions.assertEquals(valueType, ((StructType) 
itemType).getFields().get(1).getDataType());
+        assertNoNullType(signature);
+    }
+
+    private void assertMapEntriesSignature(String sql, DataType keyType, 
DataType valueType) {
+        Expression entries = analyze(sql);
+        Assertions.assertTrue(entries instanceof MapEntries);
+        FunctionSignature signature = ((MapEntries) entries).getSignature();
+        MapType mapType = MapType.of(keyType, valueType);
+        Assertions.assertEquals(mapType, signature.getArgType(0));
+        Assertions.assertEquals(mapType, entries.child(0).getDataType());
+        DataType itemType = ((ArrayType) signature.returnType).getItemType();
+        Assertions.assertTrue(itemType instanceof StructType);
+        Assertions.assertEquals(keyType, ((StructType) 
itemType).getFields().get(0).getDataType());
+        Assertions.assertEquals(valueType, ((StructType) 
itemType).getFields().get(1).getDataType());
+    }
+
+    private void assertNoNullType(FunctionSignature signature) {
+        signature.returnType.validateDataType();
+        for (DataType argumentType : signature.argumentsTypes) {
+            argumentType.validateDataType();
+        }
+    }
+
+    private Expression analyze(String sql) {
+        return 
ExpressionRewriteTestHelper.typeCoercion(PARSER.parseExpression(sql));
+    }
+}
diff --git 
a/regression-test/data/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.out
 
b/regression-test/data/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.out
new file mode 100644
index 00000000000..474716bc040
--- /dev/null
+++ 
b/regression-test/data/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.out
@@ -0,0 +1,41 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !map_from_arrays_1 --
+2      10      20
+
+-- !map_from_arrays_2 --
+1      20
+
+-- !map_from_arrays_3 --
+1      2       10      20
+2      0       \N      \N
+3      \N      \N      \N
+
+-- !map_from_arrays_4 --
+\N
+
+-- !map_from_arrays_5 --
+{null:10, 2:null}
+
+-- !map_from_arrays_6 --
+\N
+
+-- !map_from_entries_1 --
+2      10      20
+
+-- !map_from_entries_2 --
+1      20
+
+-- !map_from_entries_3 --
+1      2       10      20
+2      0       \N      \N
+3      \N      \N      \N
+
+-- !map_from_entries_4 --
+\N
+
+-- !map_from_entries_5 --
+\N
+
+-- !map_from_entries_6 --
+{null:10, 2:null}
+
diff --git 
a/regression-test/suites/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.groovy
 
b/regression-test/suites/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.groovy
new file mode 100644
index 00000000000..28800340455
--- /dev/null
+++ 
b/regression-test/suites/query_p0/sql_functions/map_functions/test_map_from_arrays_entries.groovy
@@ -0,0 +1,213 @@
+// 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_map_from_arrays_entries", "p0") {
+    sql "set enable_nereids_planner = true"
+    sql "set enable_fallback_to_original_planner = false"
+    sql "set enable_decimal256 = false"
+    sql "drop table if exists test_map_from_arrays_entries"
+    sql """
+        create table test_map_from_arrays_entries (
+            id int,
+            m map<int, int>,
+            decimal_m map<decimalv3(38, 0), decimalv3(38, 38)>,
+            time_m map<datetimev2(0), struct<f:datetimev2(6)>>
+        )
+        duplicate key(id)
+        distributed by hash(id) buckets 1
+        properties("replication_num" = "1")
+    """
+    sql """
+        insert into test_map_from_arrays_entries values
+            (1, map(1, 10, 2, 20),
+                map(cast(1 as decimalv3(38, 0)),
+                    cast(0.12345678901234567890123456789012345678 as 
decimalv3(38, 38))),
+                map(cast('2026-01-01 00:00:00' as datetimev2(0)),
+                    named_struct('f',
+                        cast('2026-01-01 00:00:00.123456' as datetimev2(6))))),
+            (2, cast(map() as map<int, int>), null, null),
+            (3, null, null, null)
+    """
+
+    qt_map_from_arrays_1 """
+        select map_size(r), r[1], r[2]
+        from (select map_from_arrays([1, 2], [10, 20]) r) t
+    """
+
+    qt_map_from_arrays_2 """
+        select map_size(r), r[1]
+        from (select map_from_arrays([1, 1], [10, 20]) r) t
+    """
+
+    order_qt_map_from_arrays_3 """
+        select id, map_size(r), r[1], r[2]
+        from (
+            select id, map_from_arrays(map_keys(m), map_values(m)) r
+            from test_map_from_arrays_entries
+        ) t
+        order by id
+    """
+
+    qt_map_from_arrays_4 """
+        select map_from_arrays(
+            cast(null as array<int>), cast(null as array<int>))
+    """
+
+    qt_map_from_arrays_5 """
+        select map_from_arrays(
+            array(cast(null as int), 2), array(10, cast(null as int)))
+    """
+
+    qt_map_from_arrays_6 """
+        select map_from_arrays(cast(null as array<int>), [10, 20])
+    """
+
+    qt_map_from_entries_1 """
+        select map_size(r), r[1], r[2]
+        from (
+            select map_from_entries(array(struct(1, 10), struct(2, 20))) r
+        ) t
+    """
+
+    qt_map_from_entries_2 """
+        select map_size(r), r[1]
+        from (
+            select map_from_entries(array(struct(1, 10), struct(1, 20))) r
+        ) t
+    """
+
+    order_qt_map_from_entries_3 """
+        select id, map_size(r), r[1], r[2]
+        from (
+            select id, map_from_entries(map_entries(m)) r
+            from test_map_from_arrays_entries
+        ) t
+        order by id
+    """
+
+    qt_map_from_entries_4 """
+        select map_from_entries(cast(null as array<struct<k:int,v:int>>))
+    """
+
+    qt_map_from_entries_5 """
+        select map_from_entries(null)
+    """
+
+    qt_map_from_entries_6 """
+        select map_from_entries(array(
+            struct(cast(null as int), 10),
+            struct(2, cast(null as int))))
+    """
+
+    test {
+        sql """
+            select if(
+                        cast(map_from_entries(map_entries(decimal_m)) as 
string)
+                            like '%0.12345678901234567890123456789012345678%',
+                        1, 0),
+                    if(cast(map_from_entries(map_entries(time_m)) as string)
+                            like '%.123456%', 1, 0)
+            from test_map_from_arrays_entries
+            where id = 1
+        """
+        result([[1, 1]])
+    }
+
+    test {
+        sql """
+            select if(cast(map_from_entries(map_entries(map(
+                            cast(1 as decimalv3(38, 0)),
+                            cast(0.12345678901234567890123456789012345678
+                                as decimalv3(38, 38))))) as string)
+                        like '%0.12345678901234567890123456789012345678%', 1, 
0),
+                    if(cast(map_from_entries(map_entries(map(
+                            cast('2026-01-01 00:00:00' as datetimev2(0)),
+                            struct(cast('2026-01-01 00:00:00.123456'
+                                as datetimev2(6)))))) as string)
+                        like '%.123456%', 1, 0)
+        """
+        result([[1, 1]])
+    }
+
+    testFoldConst("select map_from_arrays([1, 2], ['a', 'b'])")
+    testFoldConst("select map_from_arrays([1, 1], [10, 20])")
+    testFoldConst("select map_from_arrays(array(cast(null as int), 2),"
+            + " array(10, cast(null as int)))")
+    testFoldConst("select map_from_arrays(cast([] as array<int>), cast([] as 
array<int>))")
+    testFoldConst("select map_from_arrays([null], [null])")
+    testFoldConst("select map_from_arrays(null, null)")
+    testFoldConst("select map_from_arrays([], [])")
+    testFoldConst("select map_from_arrays(map_keys(map(null, null)),"
+            + " map_values(map(null, null)))")
+    testFoldConst("select map_from_arrays([1], [[null]])")
+    testFoldConst("select map_values(map_from_arrays("
+            + "cast([1] as array<decimalv3(38, 0)>),"
+            + "cast([0.12345678901234567890123456789012345678]"
+            + " as array<decimalv3(38, 38)>)))[1]")
+    testFoldConst("select 
microsecond(struct_element(map_values(map_from_arrays("
+            + "cast(['2026-01-01 00:00:00'] as array<datetimev2(0)>),"
+            + "array(struct(cast('2026-01-01 00:00:00.123456' as 
datetimev2(6)))))"
+            + ")[1], 1))")
+    testFoldConst("select map_from_entries(array(struct(1, 'a'), struct(2, 
'b')))")
+    testFoldConst("select map_from_entries(array(struct(1, 10), struct(1, 
20)))")
+    testFoldConst("select map_from_entries(array(struct(cast(null as int), 
10),"
+            + " struct(2, cast(null as int))))")
+    testFoldConst("select map_from_entries(cast([] as 
array<struct<k:int,v:int>>))")
+    testFoldConst("select map_from_entries(cast(null as 
array<struct<k:int,v:int>>))")
+    testFoldConst("select map_from_entries(null)")
+    testFoldConst("select map_from_entries([])")
+    testFoldConst("select map_from_entries(array(struct(1, null)))")
+    testFoldConst("select map_from_entries(map_entries(map(null, null)))")
+    testFoldConst("select map_from_entries(array(struct(1, [null])))")
+    testFoldConst("select map_from_entries(map_entries(map("
+            + "cast(1 as decimalv3(38, 0)),"
+            + "cast(0.12345678901234567890123456789012345678"
+            + " as decimalv3(38, 38)))))")
+    testFoldConst("select cast(map_from_entries(map_entries(map("
+            + "cast('2026-01-01 00:00:00' as datetimev2(0)),"
+            + "struct(cast('2026-01-01 00:00:00.123456' as datetimev2(6))))"
+            + ")) as string)")
+
+    test {
+        sql "select map_from_arrays([1, 2], [10])"
+        exception "Key and value arrays of function map_from_arrays must have 
the same length"
+    }
+    test {
+        sql "select map_from_arrays([[1]], [10])"
+        exception "MAP key type must be a primitive type"
+    }
+    test {
+        sql "select map_from_entries(1)"
+        exception "requires an array of structs with exactly two fields"
+    }
+    test {
+        sql "select map_from_entries(array(struct(1)))"
+        exception "requires an array of structs with exactly two fields"
+    }
+    test {
+        sql "select map_from_entries(array(struct(1, 2, 3)))"
+        exception "requires an array of structs with exactly two fields"
+    }
+    test {
+        sql "select map_from_entries(array(cast(null as struct<k:int,v:int>)))"
+        exception "Map entry of function map_from_entries cannot be null"
+    }
+    test {
+        sql "select map_from_entries([null])"
+        exception "Map entry of function map_from_entries cannot be null"
+    }
+}


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

Reply via email to