This is an automated email from the ASF dual-hosted git repository.
zzcclp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 207a8ff1b8 [CH] Implement map_from_entries function (#12349)
207a8ff1b8 is described below
commit 207a8ff1b8f291e4662c7ae2f24b365df52d303f
Author: exmy <[email protected]>
AuthorDate: Mon Jun 29 14:49:46 2026 +0800
[CH] Implement map_from_entries function (#12349)
* [CH] Implement map_from_entries function
Add ClickHouse backend support for map_from_entries and its LAST_WIN
variant, including Substrait function mapping and tests for null and
duplicate-key behavior.
---
.../clickhouse/CHSparkPlanExecApi.scala | 16 +
.../execution/GlutenFunctionValidateSuite.scala | 65 +++-
.../Functions/SparkFunctionMapFromEntries.cpp | 407 +++++++++++++++++++++
.../CommonScalarFunctionParser.cpp | 2 +
.../gluten/expression/ExpressionConverter.scala | 3 +-
.../apache/gluten/expression/ExpressionNames.scala | 1 +
6 files changed, 492 insertions(+), 2 deletions(-)
diff --git
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
index 832932f0a4..36c9813fbb 100644
---
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
+++
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
@@ -56,6 +56,7 @@ import
org.apache.spark.sql.execution.joins.{BuildSideRelation, ClickHouseBuildS
import org.apache.spark.sql.execution.metric.SQLMetric
import org.apache.spark.sql.execution.utils.{CHExecUtil, PushDownUtil}
import org.apache.spark.sql.execution.window._
+import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.vectorized.ColumnarBatch
import org.apache.spark.util.SparkVersionUtil
@@ -403,6 +404,21 @@ class CHSparkPlanExecApi extends SparkPlanExecApi with
Logging {
original: GetMapValue): ExpressionTransformer =
GetMapValueTransformer(substraitExprName, left, right, failOnError =
false, original)
+ /** Transform map_from_entries to Substrait. */
+ override def genMapFromEntriesTransformer(
+ substraitExprName: String,
+ child: ExpressionTransformer,
+ expr: Expression): ExpressionTransformer = {
+ val mapKeyDedupPolicy = SQLConf.get.getConf(SQLConf.MAP_KEY_DEDUP_POLICY)
+ val chExprName =
+ if (mapKeyDedupPolicy.toString ==
SQLConf.MapKeyDedupPolicy.LAST_WIN.toString) {
+ ExpressionNames.MAP_FROM_ENTRIES_LAST_WIN
+ } else {
+ substraitExprName
+ }
+ GenericExpressionTransformer(chExprName, Seq(child), expr)
+ }
+
/**
* Generate ShuffleDependency for ColumnarShuffleExchangeExec.
*
diff --git
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/GlutenFunctionValidateSuite.scala
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/GlutenFunctionValidateSuite.scala
index 07d44cc309..602d57d4a8 100644
---
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/GlutenFunctionValidateSuite.scala
+++
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/GlutenFunctionValidateSuite.scala
@@ -20,7 +20,7 @@ import org.apache.gluten.backendsapi.clickhouse.CHConfig
import org.apache.gluten.config.GlutenConfig
import org.apache.gluten.expression.{FlattenedAnd, FlattenedOr}
-import org.apache.spark.SparkConf
+import org.apache.spark.{SparkConf, SparkException}
import org.apache.spark.sql.{DataFrame, GlutenTestUtils, Row}
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.expressions.aggregate._
@@ -1072,6 +1072,69 @@ class GlutenFunctionValidateSuite extends
GlutenClickHouseWholeStageTransformerS
}
}
+ test("Test map_from_entries") {
+ withSQLConf(
+ SQLConf.OPTIMIZER_EXCLUDED_RULES.key ->
+ (ConstantFolding.ruleName + "," + NullPropagation.ruleName)) {
+ val query =
+ """
+ |select id, map_from_entries(entries) from (
+ | select id,
+ | case
+ | when id = 0 then array(
+ | named_struct('key', cast(1 as int), 'value', 'a'),
+ | named_struct('key', cast(2 as int), 'value', cast(null as
string)))
+ | when id = 1 then cast(array() as
array<struct<key:int,value:string>>)
+ | when id = 2 then cast(null as
array<struct<key:int,value:string>>)
+ | else array(
+ | cast(null as struct<key:int,value:string>),
+ | named_struct('key', cast(4 as int), 'value', 'd'))
+ | end as entries
+ | from range(4)
+ |) order by id
+ |""".stripMargin
+ runQueryAndCompare(query)(checkGlutenPlan[ProjectExecTransformer])
+ runQueryAndCompare(
+ "select map_from_entries(cast(array() as
array<struct<key:int,value:string>>)) " +
+ "from range(1)")(checkGlutenPlan[ProjectExecTransformer])
+
+ intercept[SparkException] {
+ sql(
+ """
+ |select map_from_entries(array(
+ | named_struct('key', cast(null as int), 'value', 'a')))
+ |from range(1)
+ |""".stripMargin).collect()
+ }
+
+ intercept[SparkException] {
+ sql(
+ """
+ |select map_from_entries(array(
+ | named_struct('key', cast(1 as int), 'value', 'a'),
+ | named_struct('key', cast(1 as int), 'value', 'b')))
+ |from range(1)
+ |""".stripMargin).collect()
+ }
+ }
+ }
+
+ test("Test map_from_entries with LAST_WIN map key policy") {
+ withSQLConf(
+ SQLConf.OPTIMIZER_EXCLUDED_RULES.key ->
+ (ConstantFolding.ruleName + "," + NullPropagation.ruleName),
+ SQLConf.MAP_KEY_DEDUP_POLICY.key ->
SQLConf.MapKeyDedupPolicy.LAST_WIN.toString
+ ) {
+ runQueryAndCompare(
+ """
+ |select map_from_entries(array(
+ | named_struct('key', cast(1 as int), 'value', 'a'),
+ | named_struct('key', cast(1 as int), 'value', 'b')))
+ |from range(1)
+ |""".stripMargin)(checkGlutenPlan[ProjectExecTransformer])
+ }
+ }
+
test("Test transform_keys/transform_values") {
val sql =
"""
diff --git a/cpp-ch/local-engine/Functions/SparkFunctionMapFromEntries.cpp
b/cpp-ch/local-engine/Functions/SparkFunctionMapFromEntries.cpp
new file mode 100644
index 0000000000..eafc1fa3b3
--- /dev/null
+++ b/cpp-ch/local-engine/Functions/SparkFunctionMapFromEntries.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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 <Columns/ColumnArray.h>
+#include <Columns/ColumnLowCardinality.h>
+#include <Columns/ColumnMap.h>
+#include <Columns/ColumnNullable.h>
+#include <Columns/ColumnTuple.h>
+#include <Columns/ColumnsNumber.h>
+#include <DataTypes/DataTypeArray.h>
+#include <DataTypes/DataTypeMap.h>
+#include <DataTypes/DataTypeNothing.h>
+#include <DataTypes/DataTypeNullable.h>
+#include <DataTypes/DataTypeTuple.h>
+#include <Functions/FunctionFactory.h>
+#include <Functions/FunctionHelpers.h>
+#include <Functions/IFunction.h>
+#include <Common/SipHash.h>
+#include <Common/assert_cast.h>
+#include <utility>
+#include <unordered_map>
+#include <vector>
+
+namespace DB
+{
+namespace ErrorCodes
+{
+ extern const int BAD_ARGUMENTS;
+ extern const int ILLEGAL_COLUMN;
+ extern const int ILLEGAL_TYPE_OF_ARGUMENT;
+}
+
+template <bool last_win>
+class SparkFunctionMapFromEntries : public IFunction
+{
+public:
+ static constexpr auto name = last_win ? "sparkMapFromEntriesLastWin" :
"sparkMapFromEntries";
+
+ static FunctionPtr create(ContextPtr) { return
std::make_shared<SparkFunctionMapFromEntries>(); }
+
+ String getName() const override { return name; }
+
+ size_t getNumberOfArguments() const override { return 1; }
+
+ bool isSuitableForShortCircuitArgumentsExecution(const
DataTypesWithConstInfo &) const override { return true; }
+ bool useDefaultImplementationForConstants() const override { return true; }
+ bool useDefaultImplementationForNulls() const override { return false; }
+ bool useDefaultImplementationForLowCardinalityColumns() const override {
return false; }
+
+ DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
+ {
+ const auto * array_type =
checkAndGetDataType<DataTypeArray>(removeNullable(arguments[0]).get());
+ if (!array_type)
+ throw Exception(
+ ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
+ "Argument for function {} must be Array, but it has type {}",
+ getName(),
+ arguments[0]->getName());
+
+ const auto & entry_type = array_type->getNestedType();
+ const auto entry_type_without_nullable = removeNullable(entry_type);
+ if (isNothing(entry_type_without_nullable))
+ {
+ auto map_type = std::make_shared<DataTypeMap>(
+ std::make_shared<DataTypeNothing>(),
+ std::make_shared<DataTypeNothing>());
+ if (arguments[0]->isNullable() || entry_type->isNullable())
+ return makeNullable(map_type);
+ return map_type;
+ }
+
+ const auto * tuple_type =
checkAndGetDataType<DataTypeTuple>(entry_type_without_nullable.get());
+ if (!tuple_type || tuple_type->getElements().size() != 2)
+ throw Exception(
+ ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
+ "Argument for function {} must be Array of pair Tuple, but it
has nested type {}",
+ getName(),
+ entry_type->getName());
+
+ const auto & elements = tuple_type->getElements();
+ auto map_type =
std::make_shared<DataTypeMap>(removeNullableOrLowCardinalityNullable(elements[0]),
elements[1]);
+ if (arguments[0]->isNullable() || entry_type->isNullable())
+ return makeNullable(map_type);
+ return map_type;
+ }
+
+ ColumnPtr executeImpl(
+ const ColumnsWithTypeAndName & arguments,
+ const DataTypePtr & result_type,
+ size_t input_rows_count) const override
+ {
+ ColumnPtr holder = arguments[0].column->convertToFullColumnIfConst();
+
+ const PaddedPODArray<UInt8> * input_null_map = nullptr;
+ if (const auto * nullable =
checkAndGetColumn<ColumnNullable>(holder.get()))
+ {
+ input_null_map = &nullable->getNullMapData();
+ holder = nullable->getNestedColumnPtr();
+ }
+
+ const auto * entries_array =
checkAndGetColumn<ColumnArray>(holder.get());
+ if (!entries_array)
+ throw Exception(
+ ErrorCodes::ILLEGAL_COLUMN,
+ "Argument column for function {} must be Array, but it is {}",
+ getName(),
+ holder->getName());
+
+ const auto & entries_offsets = entries_array->getOffsets();
+ const IColumn * entries_data = &entries_array->getData();
+ const PaddedPODArray<UInt8> * entry_null_map = nullptr;
+ if (const auto * nullable_entries =
checkAndGetColumn<ColumnNullable>(entries_data))
+ {
+ entry_null_map = &nullable_entries->getNullMapData();
+ entries_data = &nullable_entries->getNestedColumn();
+ }
+
+ const auto & result_map_type = assert_cast<const DataTypeMap
&>(*removeNullable(result_type));
+ if (isNothing(entries_data->getDataType()))
+ {
+ auto result_key_column =
result_map_type.getKeyType()->createColumn();
+ auto result_value_column =
result_map_type.getValueType()->createColumn();
+ auto result_offsets_column =
ColumnArray::ColumnOffsets::create(input_rows_count, 0);
+
+ ColumnUInt8::MutablePtr result_null_map;
+ PaddedPODArray<UInt8> * result_null_map_data = nullptr;
+ if (result_type->isNullable())
+ {
+ result_null_map = ColumnUInt8::create(input_rows_count, 0);
+ result_null_map_data = &result_null_map->getData();
+ }
+
+ size_t previous_entry_offset = 0;
+ for (size_t row = 0; row < input_rows_count; ++row)
+ {
+ const auto current_entry_offset = entries_offsets[row];
+ const bool input_map_null = input_null_map &&
(*input_null_map)[row];
+ bool entry_map_null = false;
+ if (!input_map_null && entry_null_map)
+ {
+ for (size_t entry = previous_entry_offset; entry <
current_entry_offset; ++entry)
+ {
+ if ((*entry_null_map)[entry])
+ {
+ entry_map_null = true;
+ break;
+ }
+ }
+ }
+ if (result_null_map_data)
+ (*result_null_map_data)[row] = input_map_null ||
entry_map_null;
+ previous_entry_offset = current_entry_offset;
+ }
+
+ auto nested_column = ColumnArray::create(
+ ColumnTuple::create(
+ Columns{std::move(result_key_column),
std::move(result_value_column)}),
+ std::move(result_offsets_column));
+ auto result_column = ColumnMap::create(std::move(nested_column));
+ if (result_type->isNullable())
+ return ColumnNullable::create(std::move(result_column),
std::move(result_null_map));
+ return result_column;
+ }
+
+ const auto * entries_tuple =
checkAndGetColumn<ColumnTuple>(entries_data);
+ if (!entries_tuple || entries_tuple->tupleSize() != 2)
+ throw Exception(
+ ErrorCodes::ILLEGAL_COLUMN,
+ "Nested column for function {} must be Tuple with 2 elements,
but it is {}",
+ getName(),
+ entries_data->getName());
+
+ const auto & key_column = entries_tuple->getColumn(0);
+ const auto & value_column = entries_tuple->getColumn(1);
+ ColumnPtr key_insert_holder;
+ const IColumn * key_insert_column = &key_column;
+ if (const auto * nullable_key_column =
checkAndGetColumn<ColumnNullable>(&key_column))
+ key_insert_column = &nullable_key_column->getNestedColumn();
+ else if (const auto * low_cardinality_key_column =
checkAndGetColumn<ColumnLowCardinality>(&key_column);
+ low_cardinality_key_column &&
low_cardinality_key_column->nestedIsNullable())
+ {
+ key_insert_holder =
low_cardinality_key_column->cloneWithDefaultOnNull();
+ key_insert_column = key_insert_holder.get();
+ }
+
+ auto result_key_column = result_map_type.getKeyType()->createColumn();
+ auto result_value_column =
result_map_type.getValueType()->createColumn();
+ auto result_offsets_column = ColumnArray::ColumnOffsets::create();
+ auto & result_offsets = result_offsets_column->getData();
+ result_offsets.reserve(input_rows_count);
+
+ ColumnUInt8::MutablePtr result_null_map;
+ PaddedPODArray<UInt8> * result_null_map_data = nullptr;
+ if (result_type->isNullable())
+ {
+ result_null_map = ColumnUInt8::create(input_rows_count, 0);
+ result_null_map_data = &result_null_map->getData();
+ }
+
+ size_t previous_entry_offset = 0;
+ size_t result_offset = 0;
+ for (size_t row = 0; row < input_rows_count; ++row)
+ {
+ const auto current_entry_offset = entries_offsets[row];
+
+ if (input_null_map && (*input_null_map)[row])
+ {
+ appendNullMap(result_null_map_data, row, result_offsets,
result_offset);
+ previous_entry_offset = current_entry_offset;
+ continue;
+ }
+
+ if (hasNullEntry(entry_null_map, previous_entry_offset,
current_entry_offset))
+ {
+ appendNullMap(result_null_map_data, row, result_offsets,
result_offset);
+ previous_entry_offset = current_entry_offset;
+ continue;
+ }
+
+ auto selected_entries =
+ selectEntriesForRow(key_column, previous_entry_offset,
current_entry_offset);
+ appendSelectedEntries(
+ selected_entries,
+ *key_insert_column,
+ value_column,
+ *result_key_column,
+ *result_value_column,
+ result_offset);
+
+ result_offsets.push_back(result_offset);
+ previous_entry_offset = current_entry_offset;
+ }
+
+ auto nested_column = ColumnArray::create(
+ ColumnTuple::create(Columns{std::move(result_key_column),
std::move(result_value_column)}),
+ std::move(result_offsets_column));
+ auto result_column = ColumnMap::create(std::move(nested_column));
+ if (result_type->isNullable())
+ return ColumnNullable::create(std::move(result_column),
std::move(result_null_map));
+ return result_column;
+ }
+
+private:
+ struct UInt128Hash
+ {
+ size_t operator()(const UInt128 & value) const
+ {
+ return std::hash<UInt64>{}(value.items[0])
+ ^ (std::hash<UInt64>{}(value.items[1]) << 1);
+ }
+ };
+
+ using SelectedEntry = std::pair<size_t, size_t>;
+ using SelectedEntries = std::vector<SelectedEntry>;
+
+ static bool hasNullEntry(
+ const PaddedPODArray<UInt8> * entry_null_map,
+ size_t previous_entry_offset,
+ size_t current_entry_offset)
+ {
+ if (!entry_null_map)
+ return false;
+
+ for (size_t entry = previous_entry_offset; entry <
current_entry_offset; ++entry)
+ {
+ if ((*entry_null_map)[entry])
+ return true;
+ }
+ return false;
+ }
+
+ static void appendNullMap(
+ PaddedPODArray<UInt8> * result_null_map_data,
+ size_t row,
+ ColumnArray::Offsets & result_offsets,
+ size_t result_offset)
+ {
+ if (result_null_map_data)
+ (*result_null_map_data)[row] = 1;
+ result_offsets.push_back(result_offset);
+ }
+
+ static SelectedEntries selectEntriesForRow(
+ const IColumn & key_column,
+ size_t previous_entry_offset,
+ size_t current_entry_offset)
+ {
+ SelectedEntries selected_entries;
+ selected_entries.reserve(current_entry_offset - previous_entry_offset);
+
+ std::unordered_map<UInt128, size_t, UInt128Hash>
first_selected_index_by_hash;
+ first_selected_index_by_hash.reserve(current_entry_offset -
previous_entry_offset);
+ std::unordered_map<UInt128, std::vector<size_t>, UInt128Hash>
+ collision_selected_indices_by_hash;
+
+ for (size_t entry = previous_entry_offset; entry <
current_entry_offset; ++entry)
+ {
+ if (key_column.isNullAt(entry))
+ throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot use NULL as
map key in function {}", name);
+
+ SipHash hash_function;
+ key_column.updateHashWithValue(entry, hash_function);
+ const UInt128 hash = hash_function.get128();
+
+ bool has_duplicate_key = false;
+ size_t duplicate_selected_index = 0;
+ const auto first_selected_index_it =
first_selected_index_by_hash.find(hash);
+ if (first_selected_index_it != first_selected_index_by_hash.end())
+ {
+ const auto first_selected_index =
first_selected_index_it->second;
+ if (key_column.compareAt(
+ entry,
+ selected_entries[first_selected_index].first,
+ key_column,
+ 1) == 0)
+ {
+ has_duplicate_key = true;
+ duplicate_selected_index = first_selected_index;
+ }
+ else
+ {
+ const auto collision_selected_indices_it =
+ collision_selected_indices_by_hash.find(hash);
+ if (collision_selected_indices_it !=
collision_selected_indices_by_hash.end())
+ {
+ for (const auto selected_index :
collision_selected_indices_it->second)
+ {
+ if (key_column.compareAt(
+ entry,
+ selected_entries[selected_index].first,
+ key_column,
+ 1) == 0)
+ {
+ has_duplicate_key = true;
+ duplicate_selected_index = selected_index;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (has_duplicate_key)
+ {
+ if constexpr (last_win)
+ {
+ selected_entries[duplicate_selected_index].second = entry;
+ continue;
+ }
+ throw Exception(
+ ErrorCodes::BAD_ARGUMENTS,
+ "Duplicate map key is found in function {}",
+ name);
+ }
+
+ if (first_selected_index_it == first_selected_index_by_hash.end())
+ {
+ first_selected_index_by_hash.emplace(hash,
selected_entries.size());
+ }
+ else
+ {
+
collision_selected_indices_by_hash[hash].push_back(selected_entries.size());
+ }
+ selected_entries.emplace_back(entry, entry);
+ }
+
+ return selected_entries;
+ }
+
+ static void appendSelectedEntries(
+ const SelectedEntries & selected_entries,
+ const IColumn & key_insert_column,
+ const IColumn & value_column,
+ IColumn & result_key_column,
+ IColumn & result_value_column,
+ size_t & result_offset)
+ {
+ for (const auto & selected_entry : selected_entries)
+ {
+ result_key_column.insertFrom(key_insert_column,
selected_entry.first);
+ result_value_column.insertFrom(value_column,
selected_entry.second);
+ ++result_offset;
+ }
+ }
+};
+
+REGISTER_FUNCTION(SparkMapFromEntries)
+{
+ factory.registerFunction<SparkFunctionMapFromEntries<false>>();
+ factory.registerFunction<SparkFunctionMapFromEntries<true>>();
+}
+
+}
diff --git
a/cpp-ch/local-engine/Parser/scalar_function_parser/CommonScalarFunctionParser.cpp
b/cpp-ch/local-engine/Parser/scalar_function_parser/CommonScalarFunctionParser.cpp
index c1b7dcdb2e..ae760543bb 100644
---
a/cpp-ch/local-engine/Parser/scalar_function_parser/CommonScalarFunctionParser.cpp
+++
b/cpp-ch/local-engine/Parser/scalar_function_parser/CommonScalarFunctionParser.cpp
@@ -173,6 +173,8 @@ REGISTER_COMMON_SCALAR_FUNCTION_PARSER(GetMapValue,
get_map_value, arrayElementO
REGISTER_COMMON_SCALAR_FUNCTION_PARSER(MapKeys, map_keys, mapKeys);
REGISTER_COMMON_SCALAR_FUNCTION_PARSER(MapValues, map_values, mapValues);
REGISTER_COMMON_SCALAR_FUNCTION_PARSER(MapFromArrays, map_from_arrays,
mapFromArrays);
+REGISTER_COMMON_SCALAR_FUNCTION_PARSER(MapFromEntries, map_from_entries,
sparkMapFromEntries);
+REGISTER_COMMON_SCALAR_FUNCTION_PARSER(MapFromEntriesLastWin,
map_from_entries_last_win, sparkMapFromEntriesLastWin);
// json functions
REGISTER_COMMON_SCALAR_FUNCTION_PARSER(FlattenJsonStringOnRequired,
flattenJSONStringOnRequired, flattenJSONStringOnRequired);
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala
index 7969d30502..4f322a66d8 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala
@@ -339,7 +339,8 @@ object ExpressionConverter extends SQLConfHelper with
Logging {
BackendsApiManager.getSparkPlanExecApiInstance.genMapFromEntriesTransformer(
substraitExprName,
replaceWithExpressionTransformer0(m.child, attributeSeq,
expressionsMap),
- m)
+ m
+ )
case e: Explode =>
ExplodeTransformer(
substraitExprName,
diff --git
a/shims/common/src/main/scala/org/apache/gluten/expression/ExpressionNames.scala
b/shims/common/src/main/scala/org/apache/gluten/expression/ExpressionNames.scala
index ca05f0ade1..dd5c3a188b 100644
---
a/shims/common/src/main/scala/org/apache/gluten/expression/ExpressionNames.scala
+++
b/shims/common/src/main/scala/org/apache/gluten/expression/ExpressionNames.scala
@@ -308,6 +308,7 @@ object ExpressionNames {
final val TRANSFORM_KEYS = "transform_keys"
final val TRANSFORM_VALUES = "transform_values"
final val MAP_FROM_ENTRIES = "map_from_entries"
+ final val MAP_FROM_ENTRIES_LAST_WIN = "map_from_entries_last_win"
final val STR_TO_MAP = "str_to_map"
final val MAP_FILTER = "map_filter"
final val MAP_CONTAINS_KEY = "map_contains_key"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]