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

xuyang 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 4b743061b4 [feature](function) support type template in SQL function 
(#17344)
4b743061b4 is described below

commit 4b743061b45dbf5a5bd86f368271c55c0a3baaea
Author: Kang <kxiao.ti...@gmail.com>
AuthorDate: Wed Mar 8 10:51:31 2023 +0800

    [feature](function) support type template in SQL function (#17344)
    
    A new way just like c++ template is proposed in this PR. The previous 
functions can be defined much simpler using template function.
    
        # map element extract template function
        [['element_at', '%element_extract%'], 'E', ['ARRAY<E>', 'BIGINT'], 
'ALWAYS_NULLABLE', ['E']],
    
        # map element extract template function
        [['element_at', '%element_extract%'], 'V', ['MAP<K, V>', 'K'], 
'ALWAYS_NULLABLE', ['K', 'V']],
    
    
    BTW, the plain type function is not affected and the legacy ARRAY_X MAP_K_V 
is still supported for compatability.
---
 .../java/org/apache/doris/catalog/ArrayType.java   |  22 ++++
 .../java/org/apache/doris/catalog/MapType.java     |  33 ++++++
 .../org/apache/doris/catalog/PrimitiveType.java    |   1 +
 .../org/apache/doris/catalog/TemplateType.java     | 132 +++++++++++++++++++++
 .../main/java/org/apache/doris/catalog/Type.java   |  17 +++
 .../apache/doris/analysis/FunctionCallExpr.java    |  59 +++++----
 .../java/org/apache/doris/catalog/Function.java    |  10 ++
 .../java/org/apache/doris/catalog/FunctionSet.java |  67 +++++++++++
 gensrc/script/doris_builtins_functions.py          |   8 +-
 gensrc/script/gen_builtins_functions.py            |  41 +++++--
 .../stream_load/test_map_load_and_function.out     |  53 ++++++---
 .../stream_load/test_map_load_and_function.groovy  |  33 +++++-
 12 files changed, 413 insertions(+), 63 deletions(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/ArrayType.java 
b/fe/fe-common/src/main/java/org/apache/doris/catalog/ArrayType.java
index 6cc9162dc6..a28f3c0302 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/ArrayType.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/ArrayType.java
@@ -27,6 +27,7 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -92,6 +93,27 @@ public class ArrayType extends Type {
                 && (((ArrayType) t).containsNull || !containsNull);
     }
 
+    @Override
+    public boolean hasTemplateType() {
+        return itemType.hasTemplateType();
+    }
+
+    @Override
+    public Type specializeTemplateType(Type specificType, Map<String, Type> 
specializedTypeMap,
+                                       boolean useSpecializedType) throws 
TypeException {
+        if (!(specificType instanceof ArrayType)) {
+            throw new TypeException(specificType + " is not ArrayType");
+        }
+
+        ArrayType o = (ArrayType) specificType;
+        Type newItemType = itemType;
+        if (itemType.hasTemplateType()) {
+            newItemType = itemType.specializeTemplateType(o.itemType, 
specializedTypeMap, useSpecializedType);
+        }
+
+        return new ArrayType(newItemType);
+    }
+
     public static ArrayType create() {
         return new ArrayType();
     }
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 6fd8da9c24..e72df77710 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
@@ -27,6 +27,7 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gson.annotations.SerializedName;
 
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -121,6 +122,38 @@ public class MapType extends Type {
             && (valueType.matchesType(((MapType) t).valueType));
     }
 
+    @Override
+    public boolean hasTemplateType() {
+        return keyType.hasTemplateType() || valueType.hasTemplateType();
+    }
+
+    @Override
+    public Type specializeTemplateType(Type specificType, Map<String, Type> 
specializedTypeMap,
+                                       boolean useSpecializedType) throws 
TypeException {
+        if (!(specificType instanceof MapType)) {
+            throw new TypeException(specificType + " is not MapType");
+        }
+
+        MapType specificMapType = (MapType) specificType;
+        Type newKeyType = keyType;
+        if (keyType.hasTemplateType()) {
+            newKeyType = keyType.specializeTemplateType(
+                specificMapType.keyType, specializedTypeMap, 
useSpecializedType);
+        }
+        Type newValueType = valueType;
+        if (valueType.hasTemplateType()) {
+            newValueType = valueType.specializeTemplateType(
+                specificMapType.valueType, specializedTypeMap, 
useSpecializedType);
+        }
+
+        Type newMapType = new MapType(newKeyType, newValueType);
+        if (Type.canCastTo(specificType, newMapType)) {
+            return newMapType;
+        } else {
+            throw new TypeException(specificType + " can not cast to 
specialize type " + newMapType);
+        }
+    }
+
     @Override
     public String toString() {
         return  toSql(0).toUpperCase();
diff --git 
a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java 
b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
index c4d1a4e8d8..fdcbfaedef 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java
@@ -72,6 +72,7 @@ public enum PrimitiveType {
     STRUCT("STRUCT", 16, TPrimitiveType.STRUCT),
     STRING("STRING", 16, TPrimitiveType.STRING),
     VARIANT("VARIANT", 24, TPrimitiveType.VARIANT),
+    TEMPLATE("TEMPLATE", -1, TPrimitiveType.INVALID_TYPE),
     // Unsupported scalar types.
     BINARY("BINARY", -1, TPrimitiveType.BINARY),
     ALL("ALL", -1, TPrimitiveType.INVALID_TYPE);
diff --git 
a/fe/fe-common/src/main/java/org/apache/doris/catalog/TemplateType.java 
b/fe/fe-common/src/main/java/org/apache/doris/catalog/TemplateType.java
new file mode 100644
index 0000000000..58f3fbe952
--- /dev/null
+++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/TemplateType.java
@@ -0,0 +1,132 @@
+// 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.catalog;
+
+import org.apache.doris.thrift.TColumnType;
+import org.apache.doris.thrift.TTypeDesc;
+
+import com.google.common.base.Strings;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Map;
+
+/**
+ * Describes a TemplateType type, used for SQL function argument and return 
type,
+ *  NOT used for table column type.
+ */
+public class TemplateType extends Type {
+
+    @SerializedName(value = "name")
+    private final String name;
+
+    public TemplateType(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public PrimitiveType getPrimitiveType() {
+        return PrimitiveType.TEMPLATE;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof TemplateType)) {
+            return false;
+        }
+        TemplateType o = (TemplateType) other;
+        return o.name.equals(name);
+    }
+
+    @Override
+    public boolean matchesType(Type t) {
+        // not matches any type
+        return false;
+    }
+
+    @Override
+    public boolean hasTemplateType() {
+        return true;
+    }
+
+    @Override
+    public Type specializeTemplateType(Type specificType, Map<String, Type> 
specializedTypeMap,
+                                       boolean useSpecializedType) throws 
TypeException {
+        if (specificType.hasTemplateType() && !specificType.isNull()) {
+            throw new TypeException(specificType + " should not 
hasTemplateType");
+        }
+
+        Type specializedType = specializedTypeMap.get(name);
+        if (useSpecializedType) {
+            if (specializedType == null) {
+                throw new TypeException("template type " + name + " is not 
specialized yet");
+            }
+            return specializedType;
+        }
+
+        if (specializedType != null
+                && !specificType.equals(specializedType)
+                && !specificType.matchesType(specializedType)
+                && !Type.isImplicitlyCastable(specificType, specializedType, 
true)
+                && !Type.canCastTo(specificType, specializedType)) {
+            throw new TypeException(
+                String.format("can not specialize template type %s to %s since 
it's already specialized as %s",
+                    name, specificType, specializedType));
+        }
+
+        if (specializedType == null) {
+            specializedTypeMap.put(name, specificType);
+        }
+        return specializedTypeMap.get(name);
+    }
+
+    @Override
+    public String toSql(int depth) {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return toSql(0).toUpperCase();
+    }
+
+    @Override
+    protected String prettyPrint(int lpad) {
+        String leftPadding = Strings.repeat(" ", lpad);
+        return leftPadding + toSql();
+    }
+
+    @Override
+    public boolean supportSubType(Type subType) {
+        throw new RuntimeException("supportSubType not implementd for 
TemplateType");
+    }
+
+    @Override
+    public void toThrift(TTypeDesc container) {
+        throw new RuntimeException("can not call toThrift on TemplateType");
+    }
+
+    @Override
+    public TColumnType toColumnTypeThrift() {
+        throw new RuntimeException("can not call toColumnTypeThrift on 
TemplateType");
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+}
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 a4bd07d33a..4f7f5cb96e 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
@@ -39,6 +39,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -507,6 +508,21 @@ public abstract class Type {
         return isScalarType(PrimitiveType.DATEV2);
     }
 
+    public boolean hasTemplateType() {
+        return false;
+    }
+
+    // return a new type without template type, by specialize tempalte type in 
this type
+    public Type specializeTemplateType(Type specificType, Map<String, Type> 
specializedTypeMap,
+                                       boolean useSpecializedType) throws 
TypeException {
+        if (hasTemplateType()) {
+            // throw exception by default, sub class should specialize 
tempalte type properly
+            throw new TypeException("specializeTemplateType not implemented");
+        } else {
+            return this;
+        }
+    }
+
     /**
      * Returns true if Impala supports this type in the metdata. It does not 
mean we
      * can manipulate data of this type. For tables that contain columns with 
these
@@ -1560,6 +1576,7 @@ public abstract class Type {
                         || t1 == PrimitiveType.TIMEV2 || t2 == 
PrimitiveType.TIMEV2
                         || t1 == PrimitiveType.MAP || t2 == PrimitiveType.MAP
                         || t1 == PrimitiveType.STRUCT || t2 == 
PrimitiveType.STRUCT
+                        || t1 == PrimitiveType.TEMPLATE || t2 == 
PrimitiveType.TEMPLATE
                         || t1 == PrimitiveType.UNSUPPORTED || t2 == 
PrimitiveType.UNSUPPORTED
                         || t1 == PrimitiveType.VARIANT || t2 == 
PrimitiveType.VARIANT) {
                     continue;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
index 9e101275a0..51b6b75cad 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
@@ -1369,37 +1369,34 @@ public class FunctionCallExpr extends Expr {
             }
         }
 
-        if 
(!fn.getFunctionName().getFunction().equals(ELEMENT_EXTRACT_FN_NAME)) {
-            Type[] args = fn.getArgs();
-            if (args.length > 0) {
-                // Implicitly cast all the children to match the function if 
necessary
-                for (int i = 0; i < argTypes.length - orderByElements.size(); 
++i) {
-                    // For varargs, we must compare with the last type in 
callArgs.argTypes.
-                    int ix = Math.min(args.length - 1, i);
-                    if (fnName.getFunction().equalsIgnoreCase("money_format")
-                            && children.get(0).getType().isDecimalV3() && 
args[ix].isDecimalV3()) {
-                        continue;
-                    } else if (fnName.getFunction().equalsIgnoreCase("array")
-                            && (children.get(0).getType().isDecimalV3() && 
args[ix].isDecimalV3()
-                                    || 
children.get(0).getType().isDatetimeV2() && args[ix].isDatetimeV2())) {
-                        continue;
-                    } else if 
((fnName.getFunction().equalsIgnoreCase("array_min") || fnName.getFunction()
-                            .equalsIgnoreCase("array_max") || 
fnName.getFunction().equalsIgnoreCase("element_at"))
-                            && ((
-                            children.get(0).getType().isDecimalV3() && 
((ArrayType) args[ix]).getItemType()
-                                    .isDecimalV3())
-                                    || 
(children.get(0).getType().isDatetimeV2()
-                                            && ((ArrayType) 
args[ix]).getItemType().isDatetimeV2())
-                                    || (children.get(0).getType().isDecimalV2()
-                                            && ((ArrayType) 
args[ix]).getItemType().isDecimalV2()))) {
-                        continue;
-                    } else if (!argTypes[i].matchesType(args[ix])
-                            && !(argTypes[i].isDateOrDateTime() && 
args[ix].isDateOrDateTime())
-                            && (!fn.getReturnType().isDecimalV3()
-                                    || (argTypes[i].isValid() && 
!argTypes[i].isDecimalV3()
-                                            && args[ix].isDecimalV3()))) {
-                        uncheckedCastChild(args[ix], i);
-                    }
+        Type[] args = fn.getArgs();
+        if (args.length > 0) {
+            // Implicitly cast all the children to match the function if 
necessary
+            for (int i = 0; i < argTypes.length - orderByElements.size(); ++i) 
{
+                // For varargs, we must compare with the last type in 
callArgs.argTypes.
+                int ix = Math.min(args.length - 1, i);
+                if (fnName.getFunction().equalsIgnoreCase("money_format")
+                        && children.get(0).getType().isDecimalV3() && 
args[ix].isDecimalV3()) {
+                    continue;
+                } else if (fnName.getFunction().equalsIgnoreCase("array")
+                        && (children.get(0).getType().isDecimalV3() && 
args[ix].isDecimalV3()
+                        || children.get(0).getType().isDatetimeV2() && 
args[ix].isDatetimeV2())) {
+                    continue;
+                } else if ((fnName.getFunction().equalsIgnoreCase("array_min") 
|| fnName.getFunction()
+                        .equalsIgnoreCase("array_max") || 
fnName.getFunction().equalsIgnoreCase("element_at"))
+                        && ((
+                        children.get(0).getType().isDecimalV3() && 
((ArrayType) args[ix]).getItemType()
+                                .isDecimalV3())
+                        || (children.get(0).getType().isDatetimeV2()
+                        && ((ArrayType) args[ix]).getItemType().isDatetimeV2())
+                        || (children.get(0).getType().isDecimalV2()
+                        && ((ArrayType) 
args[ix]).getItemType().isDecimalV2()))) {
+                    continue;
+                } else if (!argTypes[i].matchesType(args[ix]) && !(
+                        argTypes[i].isDateOrDateTime() && 
args[ix].isDateOrDateTime())
+                        && (!fn.getReturnType().isDecimalV3()
+                        || (argTypes[i].isValid() && 
!argTypes[i].isDecimalV3() && args[ix].isDecimalV3()))) {
+                    uncheckedCastChild(args[ix], i);
                 }
             }
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java
index f1a608a9b8..8997b1bf32 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Function.java
@@ -814,4 +814,14 @@ public class Function implements Writable {
             throw new UserException("failed to serialize function: " + 
functionName(), t);
         }
     }
+
+    public boolean hasTemplateArg() {
+        for (Type t : getArgs()) {
+            if (t.hasTemplateType()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
index 0a4bed2789..cb501817b4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
@@ -1223,6 +1223,34 @@ public class FunctionSet<T> {
             return null;
         }
 
+        List<Function> normalFunctions = Lists.newArrayList();
+        List<Function> templateFunctions = Lists.newArrayList();
+        for (Function fn : fns) {
+            if (fn.hasTemplateArg()) {
+                templateFunctions.add(fn);
+            } else {
+                normalFunctions.add(fn);
+            }
+        }
+
+        // try normal functions first
+        Function fn = getFunction(desc, mode, normalFunctions);
+        if (fn != null) {
+            return fn;
+        }
+
+        // then specialize template functions and try them
+        List<Function> specializedTemplateFunctions = Lists.newArrayList();
+        for (Function f : templateFunctions) {
+            f = FunctionSet.specializeTemplateFunction(f, desc);
+            if (f != null) {
+                specializedTemplateFunctions.add(f);
+            }
+        }
+        return getFunction(desc, mode, specializedTemplateFunctions);
+    }
+
+    private Function getFunction(Function desc, Function.CompareMode mode, 
List<Function> fns) {
         // First check for identical
         for (Function f : fns) {
             if (f.compare(desc, Function.CompareMode.IS_IDENTICAL)) {
@@ -1262,6 +1290,45 @@ public class FunctionSet<T> {
         return null;
     }
 
+    public static Function specializeTemplateFunction(Function 
templateFunction, Function requestFunction) {
+        try {
+            boolean hasTemplateType = false;
+            LOG.debug("templateFunction signature: " + 
templateFunction.signatureString()
+                        + "  return: " + templateFunction.getReturnType());
+            LOG.debug("requestFunction signature: " + 
requestFunction.signatureString()
+                        + "  return: " + requestFunction.getReturnType());
+            Function specializedFunction = templateFunction;
+            if (templateFunction instanceof ScalarFunction) {
+                ScalarFunction f = (ScalarFunction) templateFunction;
+                specializedFunction = new ScalarFunction(f.getFunctionName(), 
Lists.newArrayList(f.getArgs()),
+                                            f.getReturnType(), f.hasVarArgs(), 
f.getSymbolName(), f.getBinaryType(),
+                                            f.isUserVisible(), 
f.isVectorized(), f.getNullableMode());
+            } else {
+                // TODO(xk)
+            }
+            Type[] args = specializedFunction.getArgs();
+            Map<String, Type> specializedTypeMap = Maps.newHashMap();
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].hasTemplateType()) {
+                    hasTemplateType = true;
+                    args[i] = 
args[i].specializeTemplateType(requestFunction.getArgs()[i], 
specializedTypeMap, false);
+                }
+            }
+            if (specializedFunction.getReturnType().hasTemplateType()) {
+                hasTemplateType = true;
+                specializedFunction.setReturnType(
+                        
specializedFunction.getReturnType().specializeTemplateType(
+                        requestFunction.getReturnType(), specializedTypeMap, 
true));
+            }
+            LOG.debug("specializedFunction signature: " + 
specializedFunction.signatureString()
+                        + "  return: " + specializedFunction.getReturnType());
+            return hasTemplateType ? specializedFunction : templateFunction;
+        } catch (TypeException e) {
+            LOG.warn("specializeTemplateFunction exception", e);
+            return null;
+        }
+    }
+
     /**
      * There are essential differences in the implementation of some functions 
for different
      * types params, which should be prohibited.
diff --git a/gensrc/script/doris_builtins_functions.py 
b/gensrc/script/doris_builtins_functions.py
index f650e0a28a..b891b48513 100644
--- a/gensrc/script/doris_builtins_functions.py
+++ b/gensrc/script/doris_builtins_functions.py
@@ -25,7 +25,7 @@
 # It contains all the meta data that describes the function.
 
 # The format is:
-#   [sql aliases], <return_type>, [<args>], <nullable mode>
+#   [sql aliases], <return_type>, [<args>], <nullable mode>, [template_types]
 #
 # 'sql aliases' are the function names that can be used from sql. There must 
be at least
 # one per function.
@@ -33,6 +33,10 @@
 # 'nullable mode' reflects whether the return value of the function is null. 
See @Function.NullableMode
 # for the specific mode and meaning.
 #
+# 'template_types' is for template function just like C++. It is optional list.
+# eg. [['element_at', '%element_extract%'], 'V', ['MAP<K, V>', 'K'], 
'ALWAYS_NULLABLE', ['K', 'V']],
+#     'K' and 'V' is type template and will be specialized at runtime in FE to 
match specific args.
+#
 visible_functions = [
     # Bit and Byte functions
     # For functions corresponding to builtin operators, we can reuse the 
implementations
@@ -100,7 +104,7 @@ visible_functions = [
     [['element_at', '%element_extract%'], 'STRING', ['ARRAY_STRING', 
'BIGINT'], 'ALWAYS_NULLABLE'],
 
     # map element
-    [['element_at', '%element_extract%'], 'INT', ['MAP_STRING_INT', 'STRING'], 
'ALWAYS_NULLABLE'],
+    [['element_at', '%element_extract%'], 'V', ['MAP<K, V>', 'K'], 
'ALWAYS_NULLABLE', ['K', 'V']],
 
     [['arrays_overlap'], 'BOOLEAN', ['ARRAY_BOOLEAN', 'ARRAY_BOOLEAN'], 
'ALWAYS_NULLABLE'],
     [['arrays_overlap'], 'BOOLEAN', ['ARRAY_TINYINT', 'ARRAY_TINYINT'], 
'ALWAYS_NULLABLE'],
diff --git a/gensrc/script/gen_builtins_functions.py 
b/gensrc/script/gen_builtins_functions.py
index 28b8812142..2aa6c74fcd 100755
--- a/gensrc/script/gen_builtins_functions.py
+++ b/gensrc/script/gen_builtins_functions.py
@@ -54,6 +54,7 @@ package org.apache.doris.builtins;\n\
 \n\
 import org.apache.doris.catalog.ArrayType;\n\
 import org.apache.doris.catalog.MapType;\n\
+import org.apache.doris.catalog.TemplateType;\n\
 import org.apache.doris.catalog.Type;\n\
 import org.apache.doris.catalog.Function;\n\
 import org.apache.doris.catalog.FunctionSet;\n\
@@ -73,14 +74,14 @@ print(FE_PATH)
 
 # This contains all the metadata to describe all the builtins.
 # Each meta data entry is itself a map to store all the meta data
-#   - fn_name, ret_type, args, symbol, sql_names
+#   - fn_name, ret_type, args, symbol, sql_names, template_types(optional)
 meta_data_entries = []
 
 # Read in the function and add it to the meta_data_entries map
 def add_function(fn_meta_data, user_visible):
     """add function
     """
-    assert len(fn_meta_data) == 4, \
+    assert len(fn_meta_data) >= 4, \
             "Invalid function entry in doris_builtins_functions.py:\n\t" + 
repr(fn_meta_data)
     entry = {}
     entry["sql_names"] = fn_meta_data[0]
@@ -91,6 +92,12 @@ def add_function(fn_meta_data, user_visible):
     else:
         entry['nullable_mode'] = 'DEPEND_ON_ARGUMENT'
 
+    # process template
+    if len(fn_meta_data) >= 5:
+        entry["template_types"] = fn_meta_data[4]
+    else:
+        entry["template_types"] = []
+
     entry["user_visible"] = user_visible
     meta_data_entries.append(entry)
 
@@ -103,15 +110,35 @@ for example:
     in[ARRAY_INT]   --> out[new ArrayType(Type.INT)]
     in[MAP_STRING_INT]   --> out[new MapType(Type.STRING,Type.INT)]
 """
-def generate_fe_datatype(str_type):
+def generate_fe_datatype(str_type, template_types):
+    # delete whitespace
+    str_type = str_type.replace(' ', '').replace('\t', '')
+
+    # process template
+    if str_type in template_types:
+        return 'new TemplateType("{}")'.format(str_type)
+
+    # process Array, Map, Struct template
+    template_start = str_type.find('<')
+    template_end  = str_type.rfind('>')
+    if template_start >= 0 and template_end > 0:
+        # exclude <>
+        template = str_type[template_start + 1 : template_end]
+        if str_type.startswith("ARRAY<"):
+            return 'new ArrayType({})'.format(generate_fe_datatype(template, 
template_types))
+        elif str_type.startswith("MAP<"):
+            types = template.split(',', 2)
+            return 'new MapType({}, {})'.format(generate_fe_datatype(types[0], 
template_types), generate_fe_datatype(types[1], template_types))
+
+    # lagacy Array, Map syntax
     if str_type.startswith("ARRAY_"):
         vec_type = str_type.split('_', 1);
         if len(vec_type) > 1 and vec_type[0] == "ARRAY":
-            return "new ArrayType(" + generate_fe_datatype(vec_type[1]) + ")"
+            return "new ArrayType(" + generate_fe_datatype(vec_type[1], 
template_types) + ")"
     if str_type.startswith("MAP_"):
         vec_type = str_type.split('_', 2)
         if len(vec_type) > 2 and vec_type[0] == "MAP": 
-            return "new MapType(" + generate_fe_datatype(vec_type[1]) + "," + 
generate_fe_datatype(vec_type[2])+")"
+            return "new MapType(" + generate_fe_datatype(vec_type[1], 
template_types) + "," + generate_fe_datatype(vec_type[2], template_types)+")"
     if str_type == "DECIMALV2":
         return "Type.MAX_DECIMALV2_TYPE"
     if str_type == "DECIMAL32":
@@ -136,7 +163,7 @@ def generate_fe_entry(entry, name):
     else:
         java_output += ", false"
     java_output += ", Function.NullableMode." + entry["nullable_mode"]
-    java_output += ", " + generate_fe_datatype(entry["ret_type"])
+    java_output += ", " + generate_fe_datatype(entry["ret_type"], 
entry["template_types"])
 
     # Check the last entry for varargs indicator.
     if entry["args"] and entry["args"][-1] == "...":
@@ -145,7 +172,7 @@ def generate_fe_entry(entry, name):
     else:
         java_output += ", false"
     for arg in entry["args"]:
-        java_output += ", " + generate_fe_datatype(arg)
+        java_output += ", " + generate_fe_datatype(arg, 
entry["template_types"])
     return java_output
 
 # Generates the FE builtins init file that registers all the builtins.
diff --git 
a/regression-test/data/load_p0/stream_load/test_map_load_and_function.out 
b/regression-test/data/load_p0/stream_load/test_map_load_and_function.out
index 1b7eb1f5a3..8e3b7905a4 100644
--- a/regression-test/data/load_p0/stream_load/test_map_load_and_function.out
+++ b/regression-test/data/load_p0/stream_load/test_map_load_and_function.out
@@ -1,5 +1,5 @@
 -- This file is automatically generated. You should know what you did if you 
want to edit this
--- !select --
+-- !select_all --
 1      \N
 2      {'  11amory  ':23, 'beat':20, ' clever ':66}
 3      {'k1':31, 'k2':300}
@@ -16,22 +16,37 @@
 15     {'':2, 'k2':0}
 16     {null:null}
 
--- !select --
-\N
-\N
-300
-\N
-\N
-400
-\N
-\N
-\N
-\N
-\N
-\N
-\N
-\N
-130
-0
-\N
+-- !select_m --
+1      \N
+2      \N
+3      300
+4      \N
+5      \N
+6      400
+7      \N
+8      \N
+9      \N
+10     \N
+11     \N
+12     \N
+13     \N
+15     0
+16     \N
+17     \N
+18     130
+
+-- !select_m1 --
+1      100     200     \N
+
+-- !select_m2 --
+1      k1      k2      \N
+
+-- !select_m3 --
+1      v1      v2      \N
+
+-- !select_m4 --
+1      10000   20000   \N
+
+-- !select_m5 --
+1      100     200     \N
 
diff --git 
a/regression-test/suites/load_p0/stream_load/test_map_load_and_function.groovy 
b/regression-test/suites/load_p0/stream_load/test_map_load_and_function.groovy
index d796c08b2e..38e4d295ca 100644
--- 
a/regression-test/suites/load_p0/stream_load/test_map_load_and_function.groovy
+++ 
b/regression-test/suites/load_p0/stream_load/test_map_load_and_function.groovy
@@ -63,12 +63,37 @@ suite("test_map_load_and_function", "p0") {
     }
 
     // check result
-    qt_select "SELECT * FROM ${testTable} ORDER BY id"
+    qt_select_all "SELECT * FROM ${testTable} ORDER BY id"
 
     // insert into valid json rows
-    sql """INSERT INTO ${testTable} VALUES(12, NULL)"""
-    sql """INSERT INTO ${testTable} VALUES(13, {"k1":100, "k2": 130})"""
+    sql """INSERT INTO ${testTable} VALUES(17, NULL)"""
+    sql """INSERT INTO ${testTable} VALUES(18, {"k1":100, "k2": 130})"""
 
     // map element_at
-    qt_select "SELECT m['k2'] FROM ${testTable} ORDER BY id"
+    qt_select_m "SELECT id, m['k2'] FROM ${testTable} ORDER BY id"
+
+
+    testTable = "tbl_test_map2"
+    sql "DROP TABLE IF EXISTS ${testTable}"
+    sql """
+        CREATE TABLE IF NOT EXISTS ${testTable} (
+            id INT,
+            `m1` MAP<STRING, INT> NULL,
+            `m2` MAP<INT, STRING> NULL,
+            `m3` MAP<STRING, STRING> NULL,
+            `m4` MAP<INT, BIGINT> NULL,
+            `m5` MAP<BIGINT, INT> NULL
+        )
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 10
+        PROPERTIES("replication_num" = "1");
+        """
+    sql """INSERT INTO ${testTable} VALUES(1, {'k1':100, 'k2':200}, {100:'k1', 
200:'k2'}, {'k1':'v1', 'k2':'v2'}, {100:10000, 200:20000}, {10000:100, 
20000:200})"""
+
+    // map element_at
+    qt_select_m1 "SELECT id, m1['k1'], m1['k2'], m1['nokey'] FROM ${testTable} 
ORDER BY id"
+    qt_select_m2 "SELECT id, m2[100], m2[200], m1[300] FROM ${testTable} ORDER 
BY id"
+    qt_select_m3 "SELECT id, m3['k1'], m3['k2'], m3['nokey'] FROM ${testTable} 
ORDER BY id"
+    qt_select_m4 "SELECT id, m4[100], m4[200], m4[300] FROM ${testTable} ORDER 
BY id"
+    qt_select_m5 "SELECT id, m5[10000], m5[20000], m5[30000] FROM ${testTable} 
ORDER BY id"
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to