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

lihaopeng 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 0f439bb1ca [vectorized](udf) java udf support map type (#22059)
0f439bb1ca is described below

commit 0f439bb1caeff927712a8ed597c624b0bae8a87c
Author: Mryange <[email protected]>
AuthorDate: Tue Jul 25 11:56:20 2023 +0800

    [vectorized](udf) java udf support map type (#22059)
---
 be/src/vec/functions/function_java_udf.cpp         |  53 ++++++-
 be/src/vec/functions/function_java_udf.h           |   1 +
 .../apache/doris/common/jni/utils/UdfUtils.java    |  48 ++++++-
 .../java/org/apache/doris/udf/BaseExecutor.java    | 119 ++++++++++++++++
 .../java/org/apache/doris/udf/UdfExecutor.java     | 152 ++++++++++++++++++++-
 .../java/org/apache/doris/catalog/MapType.java     |   9 ++
 .../main/java/org/apache/doris/catalog/Type.java   |   2 +
 .../apache/doris/analysis/CreateFunctionStmt.java  |   4 +
 .../java/org/apache/doris/catalog/ColumnType.java  |  15 +-
 .../data/javaudf_p0/test_javaudf_map.out           |  10 ++
 .../java/org/apache/doris/udf/MapIntIntTest.java   |  35 +++++
 .../java/org/apache/doris/udf/MapStrStrTest.java   |  36 +++++
 .../suites/javaudf_p0/test_javaudf_map.groovy      |  85 ++++++++++++
 13 files changed, 560 insertions(+), 9 deletions(-)

diff --git a/be/src/vec/functions/function_java_udf.cpp 
b/be/src/vec/functions/function_java_udf.cpp
index cd992b84e1..0df2806026 100644
--- a/be/src/vec/functions/function_java_udf.cpp
+++ b/be/src/vec/functions/function_java_udf.cpp
@@ -32,6 +32,7 @@
 #include "util/jni-util.h"
 #include "vec/columns/column.h"
 #include "vec/columns/column_array.h"
+#include "vec/columns/column_map.h"
 #include "vec/columns/column_nullable.h"
 #include "vec/columns/column_string.h"
 #include "vec/columns/column_vector.h"
@@ -71,7 +72,8 @@ Status JavaFunctionCall::open(FunctionContext* context, 
FunctionContext::Functio
                 jni_env->executor_cl, "convertBasicArguments", 
"(IZIJJJ)[Ljava/lang/Object;");
         jni_env->executor_convert_array_argument_id = env->GetMethodID(
                 jni_env->executor_cl, "convertArrayArguments", 
"(IZIJJJJJ)[Ljava/lang/Object;");
-
+        jni_env->executor_convert_map_argument_id = env->GetMethodID(
+                jni_env->executor_cl, "convertMapArguments", 
"(IZIJJJJJJJJ)[Ljava/lang/Object;");
         jni_env->executor_result_basic_batch_id = env->GetMethodID(
                 jni_env->executor_cl, "copyBatchBasicResult", 
"(ZI[Ljava/lang/Object;JJJ)V");
         jni_env->executor_result_array_batch_id = env->GetMethodID(
@@ -148,6 +150,7 @@ Status JavaFunctionCall::execute(FunctionContext* context, 
Block& block,
     ColumnPtr null_cols[arg_size];
     jclass obj_class = env->FindClass("[Ljava/lang/Object;");
     jclass arraylist_class = env->FindClass("Ljava/util/ArrayList;");
+    // jclass hashmap_class = env->FindClass("Ljava/util/HashMap;");
     jobjectArray arg_objects = env->NewObjectArray(arg_size, obj_class, 
nullptr);
     int64_t nullmap_address = 0;
     for (size_t arg_idx = 0; arg_idx < arg_size; ++arg_idx) {
@@ -218,6 +221,54 @@ Status JavaFunctionCall::execute(FunctionContext* context, 
Block& block,
                     jni_env->executor_convert_array_argument_id, arg_idx, 
arg_column_nullable,
                     num_rows, nullmap_address, offset_address, 
nested_nullmap_address,
                     nested_data_address, nested_offset_address);
+        } else if (data_cols[arg_idx]->is_column_map()) {
+            const ColumnMap* map_col = assert_cast<const 
ColumnMap*>(data_cols[arg_idx].get());
+            auto offset_address =
+                    
reinterpret_cast<int64_t>(map_col->get_offsets_column().get_raw_data().data);
+            const ColumnNullable& map_key_column_nullable =
+                    assert_cast<const ColumnNullable&>(map_col->get_keys());
+            auto key_data_column_null_map = 
map_key_column_nullable.get_null_map_column_ptr();
+            auto key_data_column = 
map_key_column_nullable.get_nested_column_ptr();
+
+            auto key_nested_nullmap_address = reinterpret_cast<int64_t>(
+                    
check_and_get_column<ColumnVector<UInt8>>(key_data_column_null_map)
+                            ->get_data()
+                            .data());
+            int64_t key_nested_data_address = 0, key_nested_offset_address = 0;
+            if (key_data_column->is_column_string()) {
+                const ColumnString* col = assert_cast<const 
ColumnString*>(key_data_column.get());
+                key_nested_data_address = 
reinterpret_cast<int64_t>(col->get_chars().data());
+                key_nested_offset_address = 
reinterpret_cast<int64_t>(col->get_offsets().data());
+            } else {
+                key_nested_data_address =
+                        
reinterpret_cast<int64_t>(key_data_column->get_raw_data().data);
+            }
+
+            const ColumnNullable& map_value_column_nullable =
+                    assert_cast<const ColumnNullable&>(map_col->get_values());
+            auto value_data_column_null_map = 
map_value_column_nullable.get_null_map_column_ptr();
+            auto value_data_column = 
map_value_column_nullable.get_nested_column_ptr();
+            auto value_nested_nullmap_address = reinterpret_cast<int64_t>(
+                    
check_and_get_column<ColumnVector<UInt8>>(value_data_column_null_map)
+                            ->get_data()
+                            .data());
+            int64_t value_nested_data_address = 0, value_nested_offset_address 
= 0;
+            // array type need pass address: [nullmap_address], 
offset_address, nested_nullmap_address, 
nested_data_address/nested_char_address,nested_offset_address
+            if (value_data_column->is_column_string()) {
+                const ColumnString* col = assert_cast<const 
ColumnString*>(value_data_column.get());
+                value_nested_data_address = 
reinterpret_cast<int64_t>(col->get_chars().data());
+                value_nested_offset_address = 
reinterpret_cast<int64_t>(col->get_offsets().data());
+            } else {
+                value_nested_data_address =
+                        
reinterpret_cast<int64_t>(value_data_column->get_raw_data().data);
+            }
+            arr_obj = (jobjectArray)env->CallNonvirtualObjectMethod(
+                    jni_ctx->executor, jni_env->executor_cl,
+                    jni_env->executor_convert_map_argument_id, arg_idx, 
arg_column_nullable,
+                    num_rows, nullmap_address, offset_address, 
key_nested_nullmap_address,
+                    key_nested_data_address, key_nested_offset_address,
+                    value_nested_nullmap_address, value_nested_data_address,
+                    value_nested_offset_address);
         } else {
             return Status::InvalidArgument(
                     strings::Substitute("Java UDF doesn't support type $0 now 
!",
diff --git a/be/src/vec/functions/function_java_udf.h 
b/be/src/vec/functions/function_java_udf.h
index ba17942cce..4398fa038d 100644
--- a/be/src/vec/functions/function_java_udf.h
+++ b/be/src/vec/functions/function_java_udf.h
@@ -99,6 +99,7 @@ private:
         jmethodID executor_evaluate_id;
         jmethodID executor_convert_basic_argument_id;
         jmethodID executor_convert_array_argument_id;
+        jmethodID executor_convert_map_argument_id;
         jmethodID executor_result_basic_batch_id;
         jmethodID executor_result_array_batch_id;
         jmethodID executor_close_id;
diff --git 
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/UdfUtils.java
 
b/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/UdfUtils.java
index 09e0dc3804..c546750bcf 100644
--- 
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/UdfUtils.java
+++ 
b/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/UdfUtils.java
@@ -18,6 +18,7 @@
 package org.apache.doris.common.jni.utils;
 
 import org.apache.doris.catalog.ArrayType;
+import org.apache.doris.catalog.MapType;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.catalog.Type;
@@ -52,7 +53,7 @@ import java.time.LocalDateTime;
 import java.util.Set;
 
 public class UdfUtils {
-    private static final Logger LOG = Logger.getLogger(UdfUtils.class);
+    public static final Logger LOG = Logger.getLogger(UdfUtils.class);
     public static final Unsafe UNSAFE;
     private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
     public static final long BYTE_ARRAY_OFFSET;
@@ -95,15 +96,16 @@ public class UdfUtils {
         DECIMAL32("DECIMAL32", TPrimitiveType.DECIMAL32, 4),
         DECIMAL64("DECIMAL64", TPrimitiveType.DECIMAL64, 8),
         DECIMAL128("DECIMAL128", TPrimitiveType.DECIMAL128I, 16),
-        ARRAY_TYPE("ARRAY_TYPE", TPrimitiveType.ARRAY, 0);
-
+        ARRAY_TYPE("ARRAY_TYPE", TPrimitiveType.ARRAY, 0),
+        MAP_TYPE("MAP_TYPE", TPrimitiveType.MAP, 0);
         private final String description;
         private final TPrimitiveType thriftType;
         private final int len;
         private int precision;
         private int scale;
         private Type itemType;
-
+        private Type keyType;
+        private Type valueType;
         JavaUdfDataType(String description, TPrimitiveType thriftType, int 
len) {
             this.description = description;
             this.thriftType = thriftType;
@@ -153,6 +155,8 @@ public class UdfUtils {
                         JavaUdfDataType.DECIMAL128);
             } else if (c == java.util.ArrayList.class) {
                 return Sets.newHashSet(JavaUdfDataType.ARRAY_TYPE);
+            } else if (c == java.util.HashMap.class) {
+                return Sets.newHashSet(JavaUdfDataType.MAP_TYPE);
             }
             return Sets.newHashSet(JavaUdfDataType.INVALID_TYPE);
         }
@@ -192,6 +196,22 @@ public class UdfUtils {
         public void setItemType(Type type) {
             this.itemType = type;
         }
+
+        public Type getKeyType() {
+            return keyType;
+        }
+
+        public Type getValueType() {
+            return valueType;
+        }
+
+        public void setKeyType(Type type) {
+            this.keyType = type;
+        }
+
+        public void setValueType(Type type) {
+            this.valueType = type;
+        }
     }
 
     public static Pair<Type, Integer> fromThrift(TTypeDesc typeDesc, int 
nodeIdx) throws InternalException {
@@ -232,6 +252,14 @@ public class UdfUtils {
                 nodeIdx = childType.second;
                 break;
             }
+            case MAP: {
+                Preconditions.checkState(nodeIdx + 1 < 
typeDesc.getTypesSize());
+                Pair<Type, Integer> keyType = fromThrift(typeDesc, nodeIdx + 
1);
+                Pair<Type, Integer> valueType = fromThrift(typeDesc, nodeIdx + 
1 + keyType.value());
+                type = new MapType(keyType.key(), valueType.key());
+                nodeIdx = 1 + keyType.value() + valueType.value();
+                break;
+            }
 
             default:
                 throw new InternalException("Return type " + node.getType() + 
" is not supported now!");
@@ -307,6 +335,14 @@ public class UdfUtils {
                 result.setPrecision(arrType.getItemType().getPrecision());
                 result.setScale(((ScalarType) 
arrType.getItemType()).getScalarScale());
             }
+        } else if (retType.isMapType()) {
+            MapType mapType = (MapType) retType;
+            result.setKeyType(mapType.getKeyType());
+            result.setValueType(mapType.getValueType());
+            if (mapType.getKeyType().isDatetimeV2() || 
mapType.getKeyType().isDecimalV3()) {
+                result.setPrecision(mapType.getKeyType().getPrecision());
+                result.setScale(((ScalarType) 
mapType.getKeyType()).getScalarScale());
+            }
         }
         return Pair.of(res.length != 0, result);
     }
@@ -332,6 +368,10 @@ public class UdfUtils {
             } else if (parameterTypes[finalI].isArrayType()) {
                 ArrayType arrType = (ArrayType) parameterTypes[finalI];
                 inputArgTypes[i].setItemType(arrType.getItemType());
+            } else if (parameterTypes[finalI].isMapType()) {
+                MapType mapType = (MapType) parameterTypes[finalI];
+                inputArgTypes[i].setKeyType(mapType.getKeyType());
+                inputArgTypes[i].setValueType(mapType.getValueType());
             }
             if (res.length == 0) {
                 return Pair.of(false, inputArgTypes);
diff --git 
a/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/BaseExecutor.java
 
b/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/BaseExecutor.java
index ef405197d6..abbddab454 100644
--- 
a/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/BaseExecutor.java
+++ 
b/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/BaseExecutor.java
@@ -1202,4 +1202,123 @@ public abstract class BaseExecutor {
         }
         return argument;
     }
+
+    public Object[] convertMapArg(PrimitiveType type, int argIdx, boolean 
isNullable, int rowStart, int rowEnd,
+            long nullMapAddr,
+            long offsetsAddr, long nestedNullMapAddr, long dataAddr, long 
strOffsetAddr) {
+        Object[] argument = (Object[]) Array.newInstance(ArrayList.class, 
rowEnd - rowStart);
+        for (int row = rowStart; row < rowEnd; ++row) {
+            long offsetStart = UdfUtils.UNSAFE.getLong(null, offsetsAddr + 8L 
* (row - 1));
+            long offsetEnd = UdfUtils.UNSAFE.getLong(null, offsetsAddr + 8L * 
(row));
+            int currentRowNum = (int) (offsetEnd - offsetStart);
+            switch (type) {
+                case BOOLEAN: {
+                    argument[row
+                            - rowStart] = UdfConvert
+                                    .convertArrayBooleanArg(row, 
currentRowNum, offsetStart, isNullable, nullMapAddr,
+                                            nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case TINYINT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayTinyIntArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case SMALLINT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArraySmallIntArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case INT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayIntArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case BIGINT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayBigIntArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case LARGEINT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayLargeIntArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case FLOAT: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayFloatArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DOUBLE: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayDoubleArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case CHAR:
+                case VARCHAR:
+                case STRING: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayStringArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr, 
strOffsetAddr);
+                    break;
+                }
+                case DATE: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayDateArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DATETIME: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayDateTimeArg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DATEV2: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayDateV2Arg(row, currentRowNum, 
offsetStart, isNullable, nullMapAddr,
+                                    nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DATETIMEV2: {
+                    argument[row - rowStart] = UdfConvert
+                            .convertArrayDateTimeV2Arg(row, currentRowNum, 
offsetStart, isNullable,
+                                    nullMapAddr, nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DECIMALV2:
+                case DECIMAL128: {
+                    argument[row - rowStart] = UdfConvert
+                            
.convertArrayDecimalArg(argTypes[argIdx].getScale(), 16L, row, currentRowNum,
+                                    offsetStart, isNullable, nullMapAddr, 
nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DECIMAL32: {
+                    argument[row - rowStart] = UdfConvert
+                            
.convertArrayDecimalArg(argTypes[argIdx].getScale(), 4L, row, currentRowNum,
+                                    offsetStart, isNullable, nullMapAddr, 
nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                case DECIMAL64: {
+                    argument[row - rowStart] = UdfConvert
+                            
.convertArrayDecimalArg(argTypes[argIdx].getScale(), 8L, row, currentRowNum,
+                                    offsetStart, isNullable, nullMapAddr, 
nestedNullMapAddr, dataAddr);
+                    break;
+                }
+                default: {
+                    LOG.info("Not support: " + argTypes[argIdx]);
+                    Preconditions.checkState(false, "Not support type " + 
argTypes[argIdx].toString());
+                    break;
+                }
+            }
+        }
+        return argument;
+    }
 }
diff --git 
a/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/UdfExecutor.java
 
b/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/UdfExecutor.java
index 50528d007b..34333ba1eb 100644
--- 
a/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/UdfExecutor.java
+++ 
b/fe/be-java-extensions/java-udf/src/main/java/org/apache/doris/udf/UdfExecutor.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.udf;
 
+import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.Type;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.exception.UdfRuntimeException;
@@ -36,14 +37,20 @@ import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.MalformedURLException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.HashMap;
 
 public class UdfExecutor extends BaseExecutor {
-    private static final Logger LOG = Logger.getLogger(UdfExecutor.class);
+    // private static final java.util.logging.Logger LOG =
+    // Logger.getLogger(UdfExecutor.class);
+    public static final Logger LOG = Logger.getLogger(UdfExecutor.class);
     // setup by init() and cleared by close()
     private Method method;
 
-    // Pre-constructed input objects for the UDF. This minimizes object 
creation overhead
+    // Pre-constructed input objects for the UDF. This minimizes object 
creation
+    // overhead
     // as these objects are reused across calls to evaluate().
     private Object[] inputObjects;
 
@@ -119,13 +126,76 @@ public class UdfExecutor extends BaseExecutor {
         return convertBasicArg(true, argIdx, isNullable, 0, numRows, 
nullMapAddr, columnAddr, strOffsetAddr);
     }
 
-
     public Object[] convertArrayArguments(int argIdx, boolean isNullable, int 
numRows, long nullMapAddr,
             long offsetsAddr, long nestedNullMapAddr, long dataAddr, long 
strOffsetAddr) {
         return convertArrayArg(argIdx, isNullable, 0, numRows, nullMapAddr, 
offsetsAddr, nestedNullMapAddr, dataAddr,
                 strOffsetAddr);
     }
 
+    public Object[] convertMapArguments(int argIdx, boolean isNullable, int 
numRows, long nullMapAddr,
+            long offsetsAddr, long keyNestedNullMapAddr, long keyDataAddr, 
long keyStrOffsetAddr,
+            long valueNestedNullMapAddr, long valueDataAddr, long 
valueStrOffsetAddr) {
+        PrimitiveType keyType = 
argTypes[argIdx].getKeyType().getPrimitiveType();
+        PrimitiveType valueType = 
argTypes[argIdx].getValueType().getPrimitiveType();
+        Object[] keyCol = convertMapArg(keyType, argIdx, isNullable, 0, 
numRows, nullMapAddr, offsetsAddr,
+                keyNestedNullMapAddr, keyDataAddr,
+                keyStrOffsetAddr);
+        Object[] valueCol = convertMapArg(valueType, argIdx, isNullable, 0, 
numRows, nullMapAddr, offsetsAddr,
+                valueNestedNullMapAddr, valueDataAddr,
+                valueStrOffsetAddr);
+        switch (keyType) {
+            case BOOLEAN: {
+                return new HashMapBuilder<Boolean>().get(keyCol, valueCol, 
valueType);
+            }
+            case TINYINT: {
+                return new HashMapBuilder<Byte>().get(keyCol, valueCol, 
valueType);
+            }
+            case SMALLINT: {
+                return new HashMapBuilder<Short>().get(keyCol, valueCol, 
valueType);
+            }
+            case INT: {
+                return new HashMapBuilder<Integer>().get(keyCol, valueCol, 
valueType);
+            }
+            case BIGINT: {
+                return new HashMapBuilder<Long>().get(keyCol, valueCol, 
valueType);
+            }
+            case LARGEINT: {
+                return new HashMapBuilder<BigInteger>().get(keyCol, valueCol, 
valueType);
+            }
+            case FLOAT: {
+                return new HashMapBuilder<Float>().get(keyCol, valueCol, 
valueType);
+            }
+            case DOUBLE: {
+                return new HashMapBuilder<Double>().get(keyCol, valueCol, 
valueType);
+            }
+            case CHAR:
+            case VARCHAR:
+            case STRING: {
+                return new HashMapBuilder<String>().get(keyCol, valueCol, 
valueType);
+            }
+            case DATEV2:
+            case DATE: {
+                return new HashMapBuilder<LocalDate>().get(keyCol, valueCol, 
valueType);
+            }
+            case DATETIMEV2:
+            case DATETIME: {
+                return new HashMapBuilder<LocalDateTime>().get(keyCol, 
valueCol, valueType);
+            }
+            case DECIMAL32:
+            case DECIMAL64:
+            case DECIMALV2:
+            case DECIMAL128: {
+                return new HashMapBuilder<BigDecimal>().get(keyCol, valueCol, 
valueType);
+            }
+            default: {
+                LOG.info("Not support: " + keyType);
+                Preconditions.checkState(false, "Not support type " + 
keyType.toString());
+                break;
+            }
+        }
+        return null;
+    }
+
     /**
      * Evaluates the UDF with 'args' as the input to the UDF.
      */
@@ -503,4 +573,80 @@ public class UdfExecutor extends BaseExecutor {
             throw new UdfRuntimeException("Unable to call create UDF 
instance.", e);
         }
     }
+
+    public static class HashMapBuilder<keyType> {
+        public Object[] get(Object[] keyCol, Object[] valueCol, PrimitiveType 
valueType) {
+            switch (valueType) {
+                case BOOLEAN: {
+                    return new BuildMapFromType<keyType, 
Boolean>().get(keyCol, valueCol);
+                }
+                case TINYINT: {
+                    return new BuildMapFromType<keyType, Byte>().get(keyCol, 
valueCol);
+                }
+                case SMALLINT: {
+                    return new BuildMapFromType<keyType, Short>().get(keyCol, 
valueCol);
+                }
+                case INT: {
+                    return new BuildMapFromType<keyType, 
Integer>().get(keyCol, valueCol);
+                }
+                case BIGINT: {
+                    return new BuildMapFromType<keyType, Long>().get(keyCol, 
valueCol);
+                }
+                case LARGEINT: {
+                    return new BuildMapFromType<keyType, 
BigInteger>().get(keyCol, valueCol);
+                }
+                case FLOAT: {
+                    return new BuildMapFromType<keyType, Float>().get(keyCol, 
valueCol);
+                }
+                case DOUBLE: {
+                    return new BuildMapFromType<keyType, Double>().get(keyCol, 
valueCol);
+                }
+                case CHAR:
+                case VARCHAR:
+                case STRING: {
+                    return new BuildMapFromType<keyType, String>().get(keyCol, 
valueCol);
+                }
+                case DATEV2:
+                case DATE: {
+                    return new BuildMapFromType<keyType, 
LocalDate>().get(keyCol, valueCol);
+                }
+                case DATETIMEV2:
+                case DATETIME: {
+                    return new BuildMapFromType<keyType, 
LocalDateTime>().get(keyCol, valueCol);
+                }
+                case DECIMAL32:
+                case DECIMAL64:
+                case DECIMALV2:
+                case DECIMAL128: {
+                    return new BuildMapFromType<keyType, 
BigDecimal>().get(keyCol, valueCol);
+                }
+                default: {
+                    LOG.info("Not support: " + valueType);
+                    Preconditions.checkState(false, "Not support type " + 
valueType.toString());
+                    break;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static class BuildMapFromType<T1, T2> {
+        public Object[] get(Object[] keyCol, Object[] valueCol) {
+            Object[] retHashMap = new HashMap[keyCol.length];
+            for (int colIdx = 0; colIdx < keyCol.length; colIdx++) {
+                HashMap<T1, T2> hashMap = new HashMap<>();
+                ArrayList<T1> keys = (ArrayList<T1>) (keyCol[colIdx]);
+                ArrayList<T2> values = (ArrayList<T2>) (valueCol[colIdx]);
+                for (int i = 0; i < keys.size(); i++) {
+                    T1 key = keys.get(i);
+                    T2 value = values.get(i);
+                    if (!hashMap.containsKey(key)) {
+                        hashMap.put(key, value);
+                    }
+                }
+                retHashMap[colIdx] = hashMap;
+            }
+            return retHashMap;
+        }
+    }
 }
diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/MapType.java 
b/fe/fe-common/src/main/java/org/apache/doris/catalog/MapType.java
index 8b7a83bc93..8227770636 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/MapType.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/MapType.java
@@ -63,6 +63,15 @@ public class MapType extends Type {
         this.isValueContainsNull = true;
     }
 
+    public MapType(Type keyType, Type valueType, boolean keyContainsNull, 
boolean valueContainsNull) {
+        Preconditions.checkNotNull(keyType);
+        Preconditions.checkNotNull(valueType);
+        this.keyType = keyType;
+        this.isKeyContainsNull = keyContainsNull;
+        this.valueType = valueType;
+        this.isValueContainsNull = valueContainsNull;
+    }
+
     @Override
     public PrimitiveType getPrimitiveType() {
         return PrimitiveType.MAP;
diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java 
b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java
index db3bb398ab..c57ad04958 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java
@@ -38,6 +38,7 @@ import java.math.BigInteger;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -258,6 +259,7 @@ public abstract class Type {
                     .put(PrimitiveType.DECIMAL64, 
Sets.newHashSet(BigDecimal.class))
                     .put(PrimitiveType.DECIMAL128, 
Sets.newHashSet(BigDecimal.class))
                     .put(PrimitiveType.ARRAY, Sets.newHashSet(ArrayList.class))
+                    .put(PrimitiveType.MAP, Sets.newHashSet(HashMap.class))
                     .build();
 
     public static ArrayList<ScalarType> getIntegerTypes() {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java
index 06422e9b35..d00318d0a2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java
@@ -23,6 +23,7 @@ import org.apache.doris.catalog.ArrayType;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.Function;
 import org.apache.doris.catalog.Function.NullableMode;
+import org.apache.doris.catalog.MapType;
 import org.apache.doris.catalog.ScalarFunction;
 import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.catalog.Type;
@@ -577,6 +578,9 @@ public class CreateFunctionStmt extends DdlStmt {
         } else if (expType instanceof ArrayType) {
             ArrayType arrayType = (ArrayType) expType;
             javaTypes = 
Type.PrimitiveTypeToJavaClassType.get(arrayType.getPrimitiveType());
+        } else if (expType instanceof MapType) {
+            MapType mapType = (MapType) expType;
+            javaTypes = 
Type.PrimitiveTypeToJavaClassType.get(mapType.getPrimitiveType());
         } else {
             throw new AnalysisException(
                     String.format("Method '%s' in class '%s' does not support 
type '%s'",
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java
index 10f88eb27f..d4813dbc82 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java
@@ -166,7 +166,7 @@ public abstract class ColumnType {
     }
 
     public static void write(DataOutput out, Type type) throws IOException {
-        Preconditions.checkArgument(type.isScalarType() || type.isArrayType(),
+        Preconditions.checkArgument(type.isScalarType() || type.isArrayType() 
|| type.isMapType(),
                 "only support scalar type and array serialization");
         if (type.isScalarType()) {
             ScalarType scalarType = (ScalarType) type;
@@ -181,6 +181,13 @@ public abstract class ColumnType {
             Text.writeString(out, arrayType.getPrimitiveType().name());
             write(out, arrayType.getItemType());
             out.writeBoolean(arrayType.getContainsNull());
+        } else if (type.isMapType()) {
+            MapType mapType = (MapType) type;
+            Text.writeString(out, mapType.getPrimitiveType().name());
+            write(out, mapType.getKeyType());
+            write(out, mapType.getValueType());
+            out.writeBoolean(mapType.getIsKeyContainsNull());
+            out.writeBoolean(mapType.getIsValueContainsNull());
         }
     }
 
@@ -190,6 +197,12 @@ public abstract class ColumnType {
             Type itermType = read(in);
             boolean containsNull = in.readBoolean();
             return ArrayType.create(itermType, containsNull);
+        } else if (primitiveType == PrimitiveType.MAP) {
+            Type keyType = read(in);
+            Type valueType = read(in);
+            boolean keyContainsNull = in.readBoolean();
+            boolean valueContainsNull = in.readBoolean();
+            return new MapType(keyType, valueType, keyContainsNull, 
valueContainsNull);
         } else {
             int scale = in.readInt();
             int precision = in.readInt();
diff --git a/regression-test/data/javaudf_p0/test_javaudf_map.out 
b/regression-test/data/javaudf_p0/test_javaudf_map.out
new file mode 100644
index 0000000000..7c7cf58b2f
--- /dev/null
+++ b/regression-test/data/javaudf_p0/test_javaudf_map.out
@@ -0,0 +1,10 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !select_1 --
+{1:1, 10:1, 100:1}     111
+{2:1, 20:1, 200:1, 2000:1}     2222
+{3:1}  3
+
+-- !select_2 --
+{"114":"514", "1919":"810"}    1145141919810
+{"a":"bc", "def":"g", "hij":"k"}       abcdefghijk
+
diff --git 
a/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapIntIntTest.java
 
b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapIntIntTest.java
new file mode 100644
index 0000000000..ae1fb9bc77
--- /dev/null
+++ 
b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapIntIntTest.java
@@ -0,0 +1,35 @@
+// 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.udf;
+
+import org.apache.hadoop.hive.ql.exec.UDF;
+
+import java.util.*;
+
+public class MapIntIntTest extends UDF {
+    public Integer evaluate(HashMap<Integer, Integer> hashMap) {
+        Integer mul = 0;
+        for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
+            Integer key = entry.getKey();
+            Integer value = entry.getValue();
+            mul += key * value;
+        }
+        return mul;
+    }
+}
+
diff --git 
a/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapStrStrTest.java
 
b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapStrStrTest.java
new file mode 100644
index 0000000000..fd5ac9c20f
--- /dev/null
+++ 
b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MapStrStrTest.java
@@ -0,0 +1,36 @@
+// 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.udf;
+
+import org.apache.hadoop.hive.ql.exec.UDF;
+
+import java.util.*;
+
+public class MapStrStrTest extends UDF {
+    public String evaluate(HashMap<String, String> hashMap) {
+        StringBuffer sb = new StringBuffer();
+        for (Map.Entry<String, String> entry : hashMap.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            sb.append((key + value));
+        }
+        String ans = sb.toString();
+        return ans;
+    }
+}
+
diff --git a/regression-test/suites/javaudf_p0/test_javaudf_map.groovy 
b/regression-test/suites/javaudf_p0/test_javaudf_map.groovy
new file mode 100644
index 0000000000..4f7b89fd92
--- /dev/null
+++ b/regression-test/suites/javaudf_p0/test_javaudf_map.groovy
@@ -0,0 +1,85 @@
+// 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.
+
+import org.codehaus.groovy.runtime.IOGroovyMethods
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Paths
+
+suite("test_javaudf_map") {
+    def jarPath = 
"""${context.file.parent}/jars/java-udf-case-jar-with-dependencies.jar"""
+    log.info("Jar path: ${jarPath}".toString())
+    try {
+        try_sql("DROP FUNCTION IF EXISTS udfii(Map<INT, INT>);")
+        try_sql("DROP FUNCTION IF EXISTS udfss(Map<String, String>);")
+        try_sql("DROP TABLE IF EXISTS map_ii")
+        try_sql("DROP TABLE IF EXISTS map_ss")
+        sql """
+            CREATE TABLE IF NOT EXISTS map_ii (
+                        `id` INT(11) NULL COMMENT "",
+                        `m` Map<INT, INT> NULL COMMENT ""
+                        ) ENGINE=OLAP
+                        DUPLICATE KEY(`id`)
+                        DISTRIBUTED BY HASH(`id`) BUCKETS 1
+                        PROPERTIES (
+                        "replication_allocation" = "tag.location.default: 1",
+                        "storage_format" = "V2"
+            );
+        """
+                 sql """  """
+        sql """ INSERT INTO map_ii VALUES(1, {1:1,10:1,100:1}); """
+        sql """ INSERT INTO map_ii VALUES(2, {2:1,20:1,200:1,2000:1});   """
+        sql """ INSERT INTO map_ii VALUES(3, {3:1}); """
+        sql """ DROP FUNCTION IF EXISTS udfii(Map<INT, INT>); """
+        sql """ CREATE FUNCTION udfii(Map<INT, INT>) RETURNS INT PROPERTIES (
+            "file"="file://${jarPath}",
+            "symbol"="org.apache.doris.udf.MapIntIntTest",
+            "type"="JAVA_UDF"
+        ); """
+
+
+        qt_select_1 """ select m,udfii(m) from map_ii order by id; """
+
+        sql """ CREATE TABLE IF NOT EXISTS map_ss (
+              `id` INT(11) NULL COMMENT "",
+              `m` Map<String, String> NULL COMMENT ""
+            ) ENGINE=OLAP
+            DUPLICATE KEY(`id`)
+            DISTRIBUTED BY HASH(`id`) BUCKETS 1
+            PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1",
+            "storage_format" = "V2"
+        ); """
+        sql """ INSERT INTO map_ss VALUES(1, {"114":"514","1919":"810"});      
   """
+        sql """ INSERT INTO map_ss VALUES(2, {"a":"bc","def":"g","hij":"k"});  
 """
+        sql """ DROP FUNCTION IF EXISTS udfss(Map<String, String>); """
+
+        sql """ CREATE FUNCTION udfss(Map<String, String>) RETURNS STRING 
PROPERTIES (
+            "file"="file://${jarPath}",
+            "symbol"="org.apache.doris.udf.MapStrStrTest",
+            "type"="JAVA_UDF"
+        ); """
+
+        qt_select_2 """ select m,udfss(m) from map_ss order by id; """
+    } finally {
+        try_sql("DROP FUNCTION IF EXISTS udfii(Map<INT, INT>);")
+        try_sql("DROP FUNCTION IF EXISTS udfss(Map<String, String>);")
+        try_sql("DROP TABLE IF EXISTS map_ii")
+        try_sql("DROP TABLE IF EXISTS map_ss")
+    }
+}


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


Reply via email to