[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.
This commit is contained in:
Kang
2023-03-08 10:51:31 +08:00
committed by GitHub
parent 9213dd906a
commit 4b743061b4
12 changed files with 413 additions and 63 deletions

View File

@ -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();
}

View File

@ -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();

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;