/*
* Licensed 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 com.facebook.presto.operator.scalar.annotations;
import com.facebook.presto.metadata.BoundVariables;
import com.facebook.presto.metadata.FunctionRegistry;
import com.facebook.presto.metadata.LongVariableConstraint;
import com.facebook.presto.metadata.Signature;
import com.facebook.presto.metadata.TypeVariableConstraint;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.function.FunctionDependency;
import com.facebook.presto.spi.function.IsNull;
import com.facebook.presto.spi.function.LiteralParameters;
import com.facebook.presto.spi.function.OperatorDependency;
import com.facebook.presto.spi.function.OperatorType;
import com.facebook.presto.spi.function.SqlNullable;
import com.facebook.presto.spi.function.SqlType;
import com.facebook.presto.spi.function.TypeParameter;
import com.facebook.presto.spi.function.TypeParameterSpecialization;
import com.facebook.presto.spi.type.Type;
import com.facebook.presto.spi.type.TypeManager;
import com.facebook.presto.spi.type.TypeSignature;
import com.facebook.presto.spi.type.TypeSignatureParameter;
import com.facebook.presto.type.Constraint;
import com.facebook.presto.type.LiteralParameter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import static com.facebook.presto.metadata.FunctionKind.SCALAR;
import static com.facebook.presto.metadata.Signature.comparableTypeParameter;
import static com.facebook.presto.metadata.Signature.internalOperator;
import static com.facebook.presto.metadata.Signature.internalScalarFunction;
import static com.facebook.presto.metadata.Signature.orderableTypeParameter;
import static com.facebook.presto.metadata.Signature.typeVariable;
import static com.facebook.presto.metadata.SignatureBinder.applyBoundVariables;
import static com.facebook.presto.spi.StandardErrorCode.FUNCTION_IMPLEMENTATION_ERROR;
import static com.facebook.presto.spi.function.OperatorType.BETWEEN;
import static com.facebook.presto.spi.function.OperatorType.CAST;
import static com.facebook.presto.spi.function.OperatorType.EQUAL;
import static com.facebook.presto.spi.function.OperatorType.GREATER_THAN;
import static com.facebook.presto.spi.function.OperatorType.GREATER_THAN_OR_EQUAL;
import static com.facebook.presto.spi.function.OperatorType.HASH_CODE;
import static com.facebook.presto.spi.function.OperatorType.LESS_THAN;
import static com.facebook.presto.spi.function.OperatorType.LESS_THAN_OR_EQUAL;
import static com.facebook.presto.spi.function.OperatorType.NOT_EQUAL;
import static com.facebook.presto.spi.type.TypeSignature.parseTypeSignature;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.lang.String.format;
import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
public class ScalarImplementation
{
private final Signature signature;
private final boolean nullable;
private final List<Boolean> nullableArguments;
private final List<Boolean> nullFlags;
private final MethodHandle methodHandle;
private final List<ImplementationDependency> dependencies;
private final Optional<MethodHandle> constructor;
private final List<ImplementationDependency> constructorDependencies;
private final List<Class<?>> argumentNativeContainerTypes;
private final Map<String, Class<?>> specializedTypeParameters;
public ScalarImplementation(
Signature signature,
boolean nullable,
List<Boolean> nullableArguments,
List<Boolean> nullFlags,
MethodHandle methodHandle,
List<ImplementationDependency> dependencies,
Optional<MethodHandle> constructor,
List<ImplementationDependency> constructorDependencies,
List<Class<?>> argumentNativeContainerTypes,
Map<String, Class<?>> specializedTypeParameters)
{
this.signature = requireNonNull(signature, "signature is null");
this.nullable = nullable;
this.nullableArguments = ImmutableList.copyOf(requireNonNull(nullableArguments, "nullableArguments is null"));
this.nullFlags = ImmutableList.copyOf(requireNonNull(nullFlags, "nullFlags is null"));
this.methodHandle = requireNonNull(methodHandle, "methodHandle is null");
this.dependencies = ImmutableList.copyOf(requireNonNull(dependencies, "dependencies is null"));
this.constructor = requireNonNull(constructor, "constructor is null");
this.constructorDependencies = ImmutableList.copyOf(requireNonNull(constructorDependencies, "constructorDependencies is null"));
this.argumentNativeContainerTypes = ImmutableList.copyOf(requireNonNull(argumentNativeContainerTypes, "argumentNativeContainerTypes is null"));
this.specializedTypeParameters = ImmutableMap.copyOf(requireNonNull(specializedTypeParameters, "specializedTypeParameters is null"));
}
public Optional<MethodHandleAndConstructor> specialize(Signature boundSignature, BoundVariables boundVariables, TypeManager typeManager, FunctionRegistry functionRegistry)
{
for (Map.Entry<String, Class<?>> entry : specializedTypeParameters.entrySet()) {
if (!entry.getValue().isAssignableFrom(boundVariables.getTypeVariable(entry.getKey()).getJavaType())) {
return Optional.empty();
}
}
Class<?> returnContainerType = getNullAwareReturnType(typeManager.getType(boundSignature.getReturnType()).getJavaType(), nullable);
if (!returnContainerType.equals(methodHandle.type().returnType())) {
return Optional.empty();
}
for (int i = 0; i < boundSignature.getArgumentTypes().size(); i++) {
Class<?> argumentType = typeManager.getType(boundSignature.getArgumentTypes().get(i)).getJavaType();
boolean nullableParameter = isParameterNullable(argumentType, nullableArguments.get(i), nullFlags.get(i));
Class<?> argumentContainerType = getNullAwareContainerType(argumentType, nullableParameter);
if (!argumentNativeContainerTypes.get(i).isAssignableFrom(argumentContainerType)) {
return Optional.empty();
}
}
MethodHandle methodHandle = this.methodHandle;
for (ImplementationDependency dependency : dependencies) {
methodHandle = MethodHandles.insertArguments(methodHandle, 0, dependency.resolve(boundVariables, typeManager, functionRegistry));
}
MethodHandle constructor = null;
if (this.constructor.isPresent()) {
constructor = this.constructor.get();
for (ImplementationDependency dependency : constructorDependencies) {
constructor = MethodHandles.insertArguments(constructor, 0, dependency.resolve(boundVariables, typeManager, functionRegistry));
}
}
return Optional.of(new MethodHandleAndConstructor(methodHandle, Optional.ofNullable(constructor)));
}
private static Class<?> getNullAwareReturnType(Class<?> clazz, boolean nullable)
{
if (nullable) {
return Primitives.wrap(clazz);
}
return clazz;
}
private static Class<?> getNullAwareContainerType(Class<?> clazz, boolean nullable)
{
if (clazz == void.class) {
return Primitives.wrap(clazz);
}
if (nullable) {
return Primitives.wrap(clazz);
}
return clazz;
}
private static boolean isParameterNullable(Class<?> type, boolean nullableArgument, boolean nullFlag)
{
if (!nullableArgument) {
return false;
}
// void must be nullable even if the null flag is present
if (type == void.class) {
return true;
}
if (nullFlag) {
return !type.isPrimitive();
}
return true;
}
public boolean hasSpecializedTypeParameters()
{
return !specializedTypeParameters.isEmpty();
}
public Signature getSignature()
{
return signature;
}
public boolean isNullable()
{
return nullable;
}
public List<Boolean> getNullableArguments()
{
return nullableArguments;
}
public List<Boolean> getNullFlags()
{
return nullFlags;
}
public MethodHandle getMethodHandle()
{
return methodHandle;
}
public List<ImplementationDependency> getDependencies()
{
return dependencies;
}
public static final class MethodHandleAndConstructor
{
private final MethodHandle methodHandle;
private final Optional<MethodHandle> constructor;
public MethodHandleAndConstructor(MethodHandle methodHandle, Optional<MethodHandle> constructor)
{
this.methodHandle = requireNonNull(methodHandle, "methodHandle is null");
this.constructor = requireNonNull(constructor, "constructor is null");
}
public MethodHandle getMethodHandle()
{
return methodHandle;
}
public Optional<MethodHandle> getConstructor()
{
return constructor;
}
}
private interface ImplementationDependency
{
Object resolve(BoundVariables boundVariables, TypeManager typeManager, FunctionRegistry functionRegistry);
}
private static final class FunctionImplementationDependency
extends ScalarImplementationDependency
{
private FunctionImplementationDependency(String name, TypeSignature returnType, List<TypeSignature> argumentTypes)
{
super(internalScalarFunction(name, returnType, argumentTypes));
}
}
private static final class OperatorImplementationDependency
extends ScalarImplementationDependency
{
private final OperatorType operator;
private OperatorImplementationDependency(OperatorType operator, TypeSignature returnType, List<TypeSignature> argumentTypes)
{
super(internalOperator(operator, returnType, argumentTypes));
this.operator = requireNonNull(operator, "operator is null");
}
public OperatorType getOperator()
{
return operator;
}
}
private abstract static class ScalarImplementationDependency
implements ImplementationDependency
{
private final Signature signature;
private ScalarImplementationDependency(Signature signature)
{
this.signature = requireNonNull(signature, "signature is null");
}
public Signature getSignature()
{
return signature;
}
@Override
public MethodHandle resolve(BoundVariables boundVariables, TypeManager typeManager, FunctionRegistry functionRegistry)
{
Signature signature = applyBoundVariables(this.signature, boundVariables, this.signature.getArgumentTypes().size());
return functionRegistry.getScalarFunctionImplementation(signature).getMethodHandle();
}
}
private static final class TypeImplementationDependency
implements ImplementationDependency
{
private final TypeSignature signature;
private TypeImplementationDependency(String signature)
{
this.signature = parseTypeSignature(requireNonNull(signature, "signature is null"));
}
@Override
public Type resolve(BoundVariables boundVariables, TypeManager typeManager, FunctionRegistry functionRegistry)
{
return typeManager.getType(applyBoundVariables(signature, boundVariables));
}
}
private static final class LiteralImplementationDependency
implements ImplementationDependency
{
private final String literalName;
private LiteralImplementationDependency(String literalName)
{
this.literalName = requireNonNull(literalName, "literalName is null");
}
@Override
public Long resolve(BoundVariables boundVariables, TypeManager typeManager, FunctionRegistry functionRegistry)
{
return boundVariables.getLongVariable(literalName);
}
}
public static final class Parser
{
private static final Set<OperatorType> COMPARABLE_TYPE_OPERATORS = ImmutableSet.of(EQUAL, NOT_EQUAL, HASH_CODE);
private static final Set<OperatorType> ORDERABLE_TYPE_OPERATORS = ImmutableSet.of(LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, BETWEEN);
private final String functionName;
private final boolean nullable;
private final List<Boolean> nullableArguments = new ArrayList<>();
private final List<Boolean> nullFlags = new ArrayList<>();
private final TypeSignature returnType;
private final List<TypeSignature> argumentTypes = new ArrayList<>();
private final List<Class<?>> argumentNativeContainerTypes = new ArrayList<>();
private final MethodHandle methodHandle;
private final List<ImplementationDependency> dependencies = new ArrayList<>();
private final LinkedHashSet<TypeParameter> typeParameters = new LinkedHashSet<>();
private final ImmutableSet<String> typeParameterNames;
private final Set<String> literalParameters = new HashSet<>();
private final Map<String, Class<?>> specializedTypeParameters;
private final Optional<MethodHandle> constructorMethodHandle;
private final List<ImplementationDependency> constructorDependencies = new ArrayList<>();
private final List<LongVariableConstraint> longVariableConstraints = new ArrayList<>();
private Parser(String functionName, Method method, Map<Set<TypeParameter>, Constructor<?>> constructors)
{
this.functionName = requireNonNull(functionName, "functionName is null");
this.nullable = method.getAnnotation(SqlNullable.class) != null;
checkArgument(nullable || !containsLegacyNullable(method.getAnnotations()), "Method [%s] is annotated with @Nullable but not @SqlNullable", method);
Stream.of(method.getAnnotationsByType(TypeParameter.class))
.forEach(typeParameters::add);
typeParameterNames = typeParameters.stream()
.map(TypeParameter::value)
.collect(toImmutableSet());
LiteralParameters literalParametersAnnotation = method.getAnnotation(LiteralParameters.class);
if (literalParametersAnnotation != null) {
literalParameters.addAll(asList(literalParametersAnnotation.value()));
}
SqlType returnType = method.getAnnotation(SqlType.class);
checkArgument(returnType != null, format("Method [%s] is missing @SqlType annotation", method));
this.returnType = parseTypeSignature(returnType.value(), literalParameters);
Class<?> actualReturnType = method.getReturnType();
if (Primitives.isWrapperType(actualReturnType)) {
checkArgument(nullable, "Method [%s] has wrapper return type %s but is missing @SqlNullable", method, actualReturnType.getSimpleName());
}
else if (actualReturnType.isPrimitive()) {
checkArgument(!nullable, "Method [%s] annotated with @SqlNullable has primitive return type %s", method, actualReturnType.getSimpleName());
}
Stream.of(method.getAnnotationsByType(Constraint.class))
.map(annotation -> new LongVariableConstraint(annotation.variable(), annotation.expression()))
.forEach(longVariableConstraints::add);
this.specializedTypeParameters = getDeclaredSpecializedTypeParameters(method);
for (TypeParameter typeParameter : typeParameters) {
checkArgument(
typeParameter.value().matches("[A-Z][A-Z0-9]*"),
"Expected type parameter to only contain A-Z and 0-9 (starting with A-Z), but got %s on method [%s]", typeParameter.value(), method);
}
parseArguments(method);
this.constructorMethodHandle = getConstructor(method, constructors);
this.methodHandle = getMethodHandle(method);
}
private void parseArguments(Method method)
{
for (int i = 0; i < method.getParameterCount(); i++) {
Annotation[] annotations = method.getParameterAnnotations()[i];
Class<?> parameterType = method.getParameterTypes()[i];
// Skip injected parameters
if (parameterType == ConnectorSession.class) {
continue;
}
if (containsMetaParameter(annotations)) {
checkArgument(annotations.length == 1, "Meta parameters may only have a single annotation [%s]", method);
checkArgument(argumentTypes.isEmpty(), "Meta parameter must come before parameters [%s]", method);
Annotation annotation = annotations[0];
if (annotation instanceof TypeParameter) {
checkTypeParameters(parseTypeSignature(((TypeParameter) annotation).value()), method, typeParameterNames);
}
if (annotation instanceof LiteralParameter) {
checkArgument(literalParameters.contains(((LiteralParameter) annotation).value()), "Parameter injected by @LiteralParameter must be declared with @LiteralParameters on the method [%s]", method);
}
dependencies.add(parseDependency(annotation));
}
else {
checkArgument(!Stream.of(annotations).anyMatch(IsNull.class::isInstance), "Method [%s] has @IsNull parameter that does not follow a @SqlType parameter", method);
SqlType type = Stream.of(annotations)
.filter(SqlType.class::isInstance)
.map(SqlType.class::cast)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(format("Method [%s] is missing @SqlType annotation for parameter", method)));
boolean nullableArgument = Stream.of(annotations).anyMatch(SqlNullable.class::isInstance);
checkArgument(nullableArgument || !containsLegacyNullable(annotations), "Method [%s] has parameter annotated with @Nullable but not @SqlNullable", method);
boolean hasNullFlag = false;
if (method.getParameterCount() > (i + 1)) {
Annotation[] parameterAnnotations = method.getParameterAnnotations()[i + 1];
if (Stream.of(parameterAnnotations).anyMatch(IsNull.class::isInstance)) {
Class<?> isNullType = method.getParameterTypes()[i + 1];
checkArgument(Stream.of(parameterAnnotations).filter(Parser::isPrestoAnnotation).allMatch(IsNull.class::isInstance), "Method [%s] has @IsNull parameter that has other annotations", method);
checkArgument(isNullType == boolean.class, "Method [%s] has non-boolean parameter with @IsNull", method);
checkArgument((parameterType == Void.class) || !Primitives.isWrapperType(parameterType), "Method [%s] uses @IsNull following a parameter with boxed primitive type: %s", method, parameterType.getSimpleName());
nullableArgument = true;
hasNullFlag = true;
}
}
if (Primitives.isWrapperType(parameterType)) {
checkArgument(nullableArgument, "Method [%s] has parameter with wrapper type %s that is missing @SqlNullable", method, parameterType.getSimpleName());
}
else if (parameterType.isPrimitive() && !hasNullFlag) {
checkArgument(!nullableArgument, "Method [%s] has parameter with primitive type %s annotated with @SqlNullable", method, parameterType.getSimpleName());
}
if (typeParameterNames.contains(type.value()) && !(parameterType == Object.class && nullableArgument)) {
// Infer specialization on this type parameter. We don't do this for @SqlNullable Object because it could match a type like BIGINT
Class<?> specialization = specializedTypeParameters.get(type.value());
Class<?> nativeParameterType = Primitives.unwrap(parameterType);
checkArgument(specialization == null || specialization.equals(nativeParameterType), "Method [%s] type %s has conflicting specializations %s and %s", method, type.value(), specialization, nativeParameterType);
specializedTypeParameters.put(type.value(), nativeParameterType);
}
argumentNativeContainerTypes.add(parameterType);
argumentTypes.add(parseTypeSignature(type.value(), literalParameters));
if (hasNullFlag) {
// skip @IsNull parameter
i++;
}
nullableArguments.add(nullableArgument);
nullFlags.add(hasNullFlag);
}
}
}
private void checkTypeParameters(TypeSignature typeSignature, Method method, Set<String> typeParameterNames)
{
// Check recursively if `typeSignature` contains something like `T<bigint>`
if (typeParameterNames.contains(typeSignature.getBase())) {
checkArgument(typeSignature.getParameters().isEmpty(), "Expected type parameter not to take parameters, but got %s on method [%s]", typeSignature.getBase(), method);
return;
}
for (TypeSignatureParameter parameter : typeSignature.getParameters()) {
Optional<TypeSignature> childTypeSignature = parameter.getTypeSignatureOrNamedTypeSignature();
if (childTypeSignature.isPresent()) {
checkTypeParameters(childTypeSignature.get(), method, typeParameterNames);
}
}
}
// Find matching constructor, if this is an instance method, and populate constructorDependencies
private Optional<MethodHandle> getConstructor(Method method, Map<Set<TypeParameter>, Constructor<?>> constructors)
{
if (isStatic(method.getModifiers())) {
return Optional.empty();
}
Constructor<?> constructor = constructors.get(typeParameters);
checkArgument(constructor != null, "Method [%s] is an instance method and requires a public constructor to be declared with %s type parameters", method, typeParameters);
for (int i = 0; i < constructor.getParameterCount(); i++) {
Annotation[] annotations = constructor.getParameterAnnotations()[i];
checkArgument(containsMetaParameter(annotations), "Constructors may only have meta parameters [%s]", constructor);
checkArgument(annotations.length == 1, "Meta parameters may only have a single annotation [%s]", constructor);
Annotation annotation = annotations[0];
if (annotation instanceof TypeParameter) {
checkTypeParameters(parseTypeSignature(((TypeParameter) annotation).value()), method, typeParameterNames);
}
constructorDependencies.add(parseDependency(annotation));
}
try {
return Optional.of(lookup().unreflectConstructor(constructor));
}
catch (IllegalAccessException e) {
throw new PrestoException(FUNCTION_IMPLEMENTATION_ERROR, e);
}
}
private Map<String, Class<?>> getDeclaredSpecializedTypeParameters(Method method)
{
Map<String, Class<?>> specializedTypeParameters = new HashMap<>();
TypeParameterSpecialization[] typeParameterSpecializations = method.getAnnotationsByType(TypeParameterSpecialization.class);
ImmutableSet<String> typeParameterNames = typeParameters.stream()
.map(TypeParameter::value)
.collect(toImmutableSet());
for (TypeParameterSpecialization specialization : typeParameterSpecializations) {
checkArgument(typeParameterNames.contains(specialization.name()), "%s does not match any declared type parameters (%s) [%s]", specialization.name(), typeParameters, method);
Class<?> existingSpecialization = specializedTypeParameters.get(specialization.name());
checkArgument(existingSpecialization == null || existingSpecialization.equals(specialization.nativeContainerType()),
"%s has conflicting specializations %s and %s [%s]", specialization.name(), existingSpecialization, specialization.nativeContainerType(), method);
specializedTypeParameters.put(specialization.name(), specialization.nativeContainerType());
}
return specializedTypeParameters;
}
private MethodHandle getMethodHandle(Method method)
{
MethodHandle methodHandle;
try {
methodHandle = lookup().unreflect(method);
}
catch (IllegalAccessException e) {
throw new PrestoException(FUNCTION_IMPLEMENTATION_ERROR, e);
}
if (!isStatic(method.getModifiers())) {
// Re-arrange the parameters, so that the "this" parameter is after the meta parameters
int[] permutedIndices = new int[methodHandle.type().parameterCount()];
permutedIndices[0] = dependencies.size();
MethodType newType = methodHandle.type().changeParameterType(dependencies.size(), methodHandle.type().parameterType(0));
for (int i = 0; i < dependencies.size(); i++) {
permutedIndices[i + 1] = i;
newType = newType.changeParameterType(i, methodHandle.type().parameterType(i + 1));
}
for (int i = dependencies.size() + 1; i < permutedIndices.length; i++) {
permutedIndices[i] = i;
}
methodHandle = permuteArguments(methodHandle, newType, permutedIndices);
}
return methodHandle;
}
private static List<TypeVariableConstraint> createTypeVariableConstraints(Iterable<TypeParameter> typeParameters, List<ImplementationDependency> dependencies)
{
Set<String> orderableRequired = new HashSet<>();
Set<String> comparableRequired = new HashSet<>();
for (ImplementationDependency dependency : dependencies) {
if (dependency instanceof OperatorImplementationDependency) {
OperatorType operator = ((OperatorImplementationDependency) dependency).getOperator();
if (operator == CAST) {
continue;
}
Set<String> argumentTypes = ((OperatorImplementationDependency) dependency).getSignature().getArgumentTypes().stream()
.map(TypeSignature::getBase)
.collect(toImmutableSet());
checkArgument(argumentTypes.size() == 1, "Operator dependency must only have arguments of a single type");
String argumentType = Iterables.getOnlyElement(argumentTypes);
if (COMPARABLE_TYPE_OPERATORS.contains(operator)) {
comparableRequired.add(argumentType);
}
if (ORDERABLE_TYPE_OPERATORS.contains(operator)) {
orderableRequired.add(argumentType);
}
}
}
ImmutableList.Builder<TypeVariableConstraint> typeVariableConstraints = ImmutableList.builder();
for (TypeParameter typeParameter : typeParameters) {
String name = typeParameter.value();
if (orderableRequired.contains(name)) {
typeVariableConstraints.add(orderableTypeParameter(name));
}
else if (comparableRequired.contains(name)) {
typeVariableConstraints.add(comparableTypeParameter(name));
}
else {
typeVariableConstraints.add(typeVariable(name));
}
}
return typeVariableConstraints.build();
}
private ImplementationDependency parseDependency(Annotation annotation)
{
if (annotation instanceof TypeParameter) {
return new TypeImplementationDependency(((TypeParameter) annotation).value());
}
if (annotation instanceof LiteralParameter) {
return new LiteralImplementationDependency(((LiteralParameter) annotation).value());
}
if (annotation instanceof FunctionDependency) {
FunctionDependency function = (FunctionDependency) annotation;
return new FunctionImplementationDependency(
function.name(),
parseTypeSignature(function.returnType(), literalParameters),
Arrays.stream(function.argumentTypes())
.map(signature -> parseTypeSignature(signature, literalParameters))
.collect(toImmutableList()));
}
if (annotation instanceof OperatorDependency) {
OperatorDependency operator = (OperatorDependency) annotation;
return new OperatorImplementationDependency(
operator.operator(),
parseTypeSignature(operator.returnType(), literalParameters),
Arrays.stream(operator.argumentTypes())
.map(signature -> parseTypeSignature(signature, literalParameters))
.collect(toImmutableList()));
}
throw new IllegalArgumentException("Unsupported annotation " + annotation.getClass().getSimpleName());
}
private static boolean containsMetaParameter(Annotation[] annotations)
{
for (Annotation annotation : annotations) {
if (isMetaParameter(annotation)) {
return true;
}
}
return false;
}
private static boolean isMetaParameter(Annotation annotation)
{
return annotation instanceof TypeParameter ||
annotation instanceof LiteralParameter ||
annotation instanceof FunctionDependency ||
annotation instanceof OperatorDependency;
}
public ScalarImplementation get()
{
Signature signature = new Signature(
functionName,
SCALAR,
createTypeVariableConstraints(typeParameters, dependencies),
longVariableConstraints,
returnType,
argumentTypes,
false);
return new ScalarImplementation(
signature,
nullable,
nullableArguments,
nullFlags,
methodHandle,
dependencies,
constructorMethodHandle,
constructorDependencies,
argumentNativeContainerTypes,
specializedTypeParameters);
}
public static ScalarImplementation parseImplementation(String functionName, Method method, Map<Set<TypeParameter>, Constructor<?>> constructors)
{
return new Parser(functionName, method, constructors).get();
}
private static boolean containsLegacyNullable(Annotation[] annotations)
{
return Arrays.stream(annotations)
.map(Annotation::annotationType)
.map(Class::getName)
.anyMatch(name -> name.equals(Nullable.class.getName()));
}
private static boolean isPrestoAnnotation(Annotation annotation)
{
return isMetaParameter(annotation) ||
annotation instanceof SqlType ||
annotation instanceof SqlNullable ||
annotation instanceof IsNull;
}
}
}