use of io.trino.spi.function.InvocationConvention.InvocationArgumentConvention in project trino by trinodb.
the class FunctionManager method verifyMethodHandleSignature.
private static void verifyMethodHandleSignature(BoundSignature boundSignature, FunctionInvoker functionInvoker, InvocationConvention convention) {
MethodHandle methodHandle = functionInvoker.getMethodHandle();
MethodType methodType = methodHandle.type();
checkArgument(convention.getArgumentConventions().size() == boundSignature.getArgumentTypes().size(), "Expected %s arguments, but got %s", boundSignature.getArgumentTypes().size(), convention.getArgumentConventions().size());
int expectedParameterCount = convention.getArgumentConventions().stream().mapToInt(InvocationArgumentConvention::getParameterCount).sum();
expectedParameterCount += methodType.parameterList().stream().filter(ConnectorSession.class::equals).count();
if (functionInvoker.getInstanceFactory().isPresent()) {
expectedParameterCount++;
}
checkArgument(expectedParameterCount == methodType.parameterCount(), "Expected %s method parameters, but got %s", expectedParameterCount, methodType.parameterCount());
int parameterIndex = 0;
if (functionInvoker.getInstanceFactory().isPresent()) {
verifyFunctionSignature(convention.supportsInstanceFactor(), "Method requires instance factory, but calling convention does not support an instance factory");
MethodHandle factoryMethod = functionInvoker.getInstanceFactory().orElseThrow();
verifyFunctionSignature(methodType.parameterType(parameterIndex).equals(factoryMethod.type().returnType()), "Invalid return type");
parameterIndex++;
}
int lambdaArgumentIndex = 0;
for (int argumentIndex = 0; argumentIndex < boundSignature.getArgumentTypes().size(); argumentIndex++) {
// skip session parameters
while (methodType.parameterType(parameterIndex).equals(ConnectorSession.class)) {
verifyFunctionSignature(convention.supportsSession(), "Method requires session, but calling convention does not support session");
parameterIndex++;
}
Class<?> parameterType = methodType.parameterType(parameterIndex);
Type argumentType = boundSignature.getArgumentTypes().get(argumentIndex);
InvocationArgumentConvention argumentConvention = convention.getArgumentConvention(argumentIndex);
switch(argumentConvention) {
case NEVER_NULL:
verifyFunctionSignature(parameterType.isAssignableFrom(argumentType.getJavaType()), "Expected argument type to be %s, but is %s", argumentType, parameterType);
break;
case NULL_FLAG:
verifyFunctionSignature(parameterType.isAssignableFrom(argumentType.getJavaType()), "Expected argument type to be %s, but is %s", argumentType.getJavaType(), parameterType);
verifyFunctionSignature(methodType.parameterType(parameterIndex + 1).equals(boolean.class), "Expected null flag parameter to be followed by a boolean parameter");
break;
case BOXED_NULLABLE:
verifyFunctionSignature(parameterType.isAssignableFrom(wrap(argumentType.getJavaType())), "Expected argument type to be %s, but is %s", wrap(argumentType.getJavaType()), parameterType);
break;
case BLOCK_POSITION:
verifyFunctionSignature(parameterType.equals(Block.class) && methodType.parameterType(parameterIndex + 1).equals(int.class), "Expected BLOCK_POSITION argument have parameters Block and int");
break;
case FUNCTION:
Class<?> lambdaInterface = functionInvoker.getLambdaInterfaces().get(lambdaArgumentIndex);
verifyFunctionSignature(parameterType.equals(lambdaInterface), "Expected function interface to be %s, but is %s", lambdaInterface, parameterType);
lambdaArgumentIndex++;
break;
default:
throw new UnsupportedOperationException("Unknown argument convention: " + argumentConvention);
}
parameterIndex += argumentConvention.getParameterCount();
}
Type returnType = boundSignature.getReturnType();
switch(convention.getReturnConvention()) {
case FAIL_ON_NULL:
verifyFunctionSignature(methodType.returnType().isAssignableFrom(returnType.getJavaType()), "Expected return type to be %s, but is %s", returnType.getJavaType(), methodType.returnType());
break;
case NULLABLE_RETURN:
verifyFunctionSignature(methodType.returnType().isAssignableFrom(wrap(returnType.getJavaType())), "Expected return type to be %s, but is %s", returnType.getJavaType(), wrap(methodType.returnType()));
break;
default:
throw new UnsupportedOperationException("Unknown return convention: " + convention.getReturnConvention());
}
}
use of io.trino.spi.function.InvocationConvention.InvocationArgumentConvention in project trino by trinodb.
the class BytecodeUtils method generateFullInvocation.
private static BytecodeNode generateFullInvocation(Scope scope, String functionName, FunctionNullability functionNullability, List<Boolean> argumentIsFunctionType, Function<InvocationConvention, FunctionInvoker> functionInvokerProvider, Function<MethodHandle, BytecodeNode> instanceFactory, List<Function<Optional<Class<?>>, BytecodeNode>> argumentCompilers, CallSiteBinder binder) {
verify(argumentIsFunctionType.size() == argumentCompilers.size());
List<InvocationArgumentConvention> argumentConventions = new ArrayList<>();
List<BytecodeNode> arguments = new ArrayList<>();
for (int i = 0; i < argumentIsFunctionType.size(); i++) {
if (argumentIsFunctionType.get(i)) {
argumentConventions.add(FUNCTION);
arguments.add(null);
} else {
BytecodeNode argument = argumentCompilers.get(i).apply(Optional.empty());
argumentConventions.add(getPreferredArgumentConvention(argument, argumentCompilers.size(), functionNullability.isArgumentNullable(i)));
arguments.add(argument);
}
}
InvocationConvention invocationConvention = new InvocationConvention(argumentConventions, functionNullability.isReturnNullable() ? NULLABLE_RETURN : FAIL_ON_NULL, true, true);
FunctionInvoker functionInvoker = functionInvokerProvider.apply(invocationConvention);
Binding binding = binder.bind(functionInvoker.getMethodHandle());
LabelNode end = new LabelNode("end");
BytecodeBlock block = new BytecodeBlock().setDescription("invoke " + functionName);
Optional<BytecodeNode> instance = functionInvoker.getInstanceFactory().map(instanceFactory);
// Index of current parameter in the MethodHandle
int currentParameterIndex = 0;
// Index of parameter (without @IsNull) in Trino function
int realParameterIndex = 0;
// Index of function argument types
int lambdaArgumentIndex = 0;
MethodType methodType = binding.getType();
Class<?> returnType = methodType.returnType();
Class<?> unboxedReturnType = Primitives.unwrap(returnType);
List<Class<?>> stackTypes = new ArrayList<>();
boolean instanceIsBound = false;
while (currentParameterIndex < methodType.parameterArray().length) {
Class<?> type = methodType.parameterArray()[currentParameterIndex];
stackTypes.add(type);
if (instance.isPresent() && !instanceIsBound) {
checkState(type.equals(functionInvoker.getInstanceFactory().get().type().returnType()), "Mismatched type for instance parameter");
block.append(instance.get());
instanceIsBound = true;
} else if (type == ConnectorSession.class) {
block.append(scope.getVariable("session"));
} else {
switch(invocationConvention.getArgumentConvention(realParameterIndex)) {
case NEVER_NULL:
block.append(arguments.get(realParameterIndex));
checkArgument(!Primitives.isWrapperType(type), "Non-nullable argument must not be primitive wrapper type");
block.append(ifWasNullPopAndGoto(scope, end, unboxedReturnType, Lists.reverse(stackTypes)));
break;
case NULL_FLAG:
block.append(arguments.get(realParameterIndex));
block.append(scope.getVariable("wasNull"));
block.append(scope.getVariable("wasNull").set(constantFalse()));
stackTypes.add(boolean.class);
currentParameterIndex++;
break;
case BOXED_NULLABLE:
block.append(arguments.get(realParameterIndex));
block.append(boxPrimitiveIfNecessary(scope, type));
block.append(scope.getVariable("wasNull").set(constantFalse()));
break;
case BLOCK_POSITION:
InputReferenceNode inputReferenceNode = (InputReferenceNode) arguments.get(realParameterIndex);
block.append(inputReferenceNode.produceBlockAndPosition());
stackTypes.add(int.class);
if (!functionNullability.isArgumentNullable(realParameterIndex)) {
block.append(scope.getVariable("wasNull").set(inputReferenceNode.blockAndPositionIsNull()));
block.append(ifWasNullPopAndGoto(scope, end, unboxedReturnType, Lists.reverse(stackTypes)));
}
currentParameterIndex++;
break;
case FUNCTION:
Class<?> lambdaInterface = functionInvoker.getLambdaInterfaces().get(lambdaArgumentIndex);
block.append(argumentCompilers.get(realParameterIndex).apply(Optional.of(lambdaInterface)));
lambdaArgumentIndex++;
break;
default:
throw new UnsupportedOperationException(format("Unsupported argument conventsion type: %s", invocationConvention.getArgumentConvention(realParameterIndex)));
}
realParameterIndex++;
}
currentParameterIndex++;
}
block.append(invoke(binding, functionName));
if (functionNullability.isReturnNullable()) {
block.append(unboxPrimitiveIfNecessary(scope, returnType));
}
block.visitLabel(end);
return block;
}
use of io.trino.spi.function.InvocationConvention.InvocationArgumentConvention in project trino by trinodb.
the class ScalarFunctionAdapter method adaptParameter.
private MethodHandle adaptParameter(MethodHandle methodHandle, int parameterIndex, Type argumentType, InvocationArgumentConvention actualArgumentConvention, InvocationArgumentConvention expectedArgumentConvention, InvocationReturnConvention returnConvention) {
if (actualArgumentConvention == expectedArgumentConvention) {
return methodHandle;
}
if (actualArgumentConvention == BLOCK_POSITION) {
throw new IllegalArgumentException("Block and position argument can not be adapted");
}
if (actualArgumentConvention == FUNCTION) {
throw new IllegalArgumentException("Function argument can not be adapted");
}
// caller will never pass null
if (expectedArgumentConvention == NEVER_NULL) {
if (actualArgumentConvention == BOXED_NULLABLE) {
// if actual argument is boxed primitive, change method handle to accept a primitive and then box to actual method
if (isWrapperType(methodHandle.type().parameterType(parameterIndex))) {
MethodType targetType = methodHandle.type().changeParameterType(parameterIndex, unwrap(methodHandle.type().parameterType(parameterIndex)));
methodHandle = explicitCastArguments(methodHandle, targetType);
}
return methodHandle;
}
if (actualArgumentConvention == NULL_FLAG) {
// actual method takes value and null flag, so change method handle to not have the flag and always pass false to the actual method
return insertArguments(methodHandle, parameterIndex + 1, false);
}
throw new IllegalArgumentException("Unsupported actual argument convention: " + actualArgumentConvention);
}
// caller will pass Java null for SQL null
if (expectedArgumentConvention == BOXED_NULLABLE) {
if (actualArgumentConvention == NEVER_NULL) {
if (nullAdaptationPolicy == UNSUPPORTED) {
throw new IllegalArgumentException("Not null argument can not be adapted to nullable");
}
// box argument
Class<?> boxedType = wrap(methodHandle.type().parameterType(parameterIndex));
MethodType targetType = methodHandle.type().changeParameterType(parameterIndex, boxedType);
methodHandle = explicitCastArguments(methodHandle, targetType);
if (nullAdaptationPolicy == UNDEFINED_VALUE_FOR_NULL) {
// currently, we just perform unboxing, which converts nulls to Java primitive default value
return methodHandle;
}
if (nullAdaptationPolicy == RETURN_NULL_ON_NULL) {
if (returnConvention == FAIL_ON_NULL) {
throw new IllegalArgumentException("RETURN_NULL_ON_NULL adaptation can not be used with FAIL_ON_NULL return convention");
}
return guardWithTest(isNullArgument(methodHandle.type(), parameterIndex), returnNull(methodHandle.type()), methodHandle);
}
if (nullAdaptationPolicy == THROW_ON_NULL) {
MethodType adapterType = methodType(boxedType, boxedType);
MethodHandle adapter = guardWithTest(isNullArgument(adapterType, 0), throwTrinoNullArgumentException(adapterType), identity(boxedType));
return collectArguments(methodHandle, parameterIndex, adapter);
}
}
if (actualArgumentConvention == NULL_FLAG) {
// The conversion is described below in reverse order as this is how method handle adaptation works. The provided example
// signature is based on a boxed Long argument.
// 3. unbox the value (if null the java default is sent)
// long, boolean => Long, boolean
Class<?> parameterType = methodHandle.type().parameterType(parameterIndex);
methodHandle = explicitCastArguments(methodHandle, methodHandle.type().changeParameterType(parameterIndex, wrap(parameterType)));
// 2. replace second argument with the result of isNull
// long, boolean => Long, Long
methodHandle = filterArguments(methodHandle, parameterIndex + 1, explicitCastArguments(IS_NULL_METHOD, methodType(boolean.class, wrap(parameterType))));
// 1. Duplicate the argument, so we have two copies of the value
// Long, Long => Long
int[] reorder = IntStream.range(0, methodHandle.type().parameterCount()).map(i -> i <= parameterIndex ? i : i - 1).toArray();
MethodType newType = methodHandle.type().dropParameterTypes(parameterIndex + 1, parameterIndex + 2);
methodHandle = permuteArguments(methodHandle, newType, reorder);
return methodHandle;
}
throw new IllegalArgumentException("Unsupported actual argument convention: " + actualArgumentConvention);
}
// caller will pass boolean true in the next argument for SQL null
if (expectedArgumentConvention == NULL_FLAG) {
if (actualArgumentConvention == NEVER_NULL) {
if (nullAdaptationPolicy == UNSUPPORTED) {
throw new IllegalArgumentException("Not null argument can not be adapted to nullable");
}
if (nullAdaptationPolicy == UNDEFINED_VALUE_FOR_NULL) {
// add null flag to call
methodHandle = dropArguments(methodHandle, parameterIndex + 1, boolean.class);
return methodHandle;
}
// if caller sets null flag, return null, otherwise invoke target
if (nullAdaptationPolicy == RETURN_NULL_ON_NULL) {
if (returnConvention == FAIL_ON_NULL) {
throw new IllegalArgumentException("RETURN_NULL_ON_NULL adaptation can not be used with FAIL_ON_NULL return convention");
}
// add null flag to call
methodHandle = dropArguments(methodHandle, parameterIndex + 1, boolean.class);
return guardWithTest(isTrueNullFlag(methodHandle.type(), parameterIndex), returnNull(methodHandle.type()), methodHandle);
}
if (nullAdaptationPolicy == THROW_ON_NULL) {
MethodHandle adapter = identity(methodHandle.type().parameterType(parameterIndex));
adapter = dropArguments(adapter, 1, boolean.class);
adapter = guardWithTest(isTrueNullFlag(adapter.type(), 0), throwTrinoNullArgumentException(adapter.type()), adapter);
return collectArguments(methodHandle, parameterIndex, adapter);
}
}
if (actualArgumentConvention == BOXED_NULLABLE) {
return collectArguments(methodHandle, parameterIndex, boxedToNullFlagFilter(methodHandle.type().parameterType(parameterIndex)));
}
throw new IllegalArgumentException("Unsupported actual argument convention: " + actualArgumentConvention);
}
// caller will pass boolean true in the next argument for SQL null
if (expectedArgumentConvention == BLOCK_POSITION) {
MethodHandle getBlockValue = getBlockValue(argumentType, methodHandle.type().parameterType(parameterIndex));
if (actualArgumentConvention == NEVER_NULL) {
if (nullAdaptationPolicy == UNDEFINED_VALUE_FOR_NULL) {
// Current, null is not checked, so whatever type returned is passed through
methodHandle = collectArguments(methodHandle, parameterIndex, getBlockValue);
return methodHandle;
}
if (nullAdaptationPolicy == RETURN_NULL_ON_NULL && returnConvention != FAIL_ON_NULL) {
// if caller sets null flag, return null, otherwise invoke target
methodHandle = collectArguments(methodHandle, parameterIndex, getBlockValue);
return guardWithTest(isBlockPositionNull(methodHandle.type(), parameterIndex), returnNull(methodHandle.type()), methodHandle);
}
if (nullAdaptationPolicy == THROW_ON_NULL || nullAdaptationPolicy == UNSUPPORTED || nullAdaptationPolicy == RETURN_NULL_ON_NULL) {
MethodHandle adapter = guardWithTest(isBlockPositionNull(getBlockValue.type(), 0), throwTrinoNullArgumentException(getBlockValue.type()), getBlockValue);
return collectArguments(methodHandle, parameterIndex, adapter);
}
}
if (actualArgumentConvention == BOXED_NULLABLE) {
getBlockValue = explicitCastArguments(getBlockValue, getBlockValue.type().changeReturnType(wrap(getBlockValue.type().returnType())));
getBlockValue = guardWithTest(isBlockPositionNull(getBlockValue.type(), 0), returnNull(getBlockValue.type()), getBlockValue);
methodHandle = collectArguments(methodHandle, parameterIndex, getBlockValue);
return methodHandle;
}
if (actualArgumentConvention == NULL_FLAG) {
// long, boolean => long, Block, int
MethodHandle isNull = isBlockPositionNull(getBlockValue.type(), 0);
methodHandle = collectArguments(methodHandle, parameterIndex + 1, isNull);
// long, Block, int => Block, int, Block, int
getBlockValue = guardWithTest(isBlockPositionNull(getBlockValue.type(), 0), returnNull(getBlockValue.type()), getBlockValue);
methodHandle = collectArguments(methodHandle, parameterIndex, getBlockValue);
int[] reorder = IntStream.range(0, methodHandle.type().parameterCount()).map(i -> i <= parameterIndex + 1 ? i : i - 2).toArray();
MethodType newType = methodHandle.type().dropParameterTypes(parameterIndex + 2, parameterIndex + 4);
methodHandle = permuteArguments(methodHandle, newType, reorder);
return methodHandle;
}
throw new IllegalArgumentException("Unsupported actual argument convention: " + actualArgumentConvention);
}
throw new IllegalArgumentException("Unsupported expected argument convention: " + expectedArgumentConvention);
}
use of io.trino.spi.function.InvocationConvention.InvocationArgumentConvention in project trino by trinodb.
the class TestScalarFunctionAdapter method verifyAllAdaptations.
private static void verifyAllAdaptations(InvocationConvention actualConvention, MethodHandle methodHandle, NullAdaptationPolicy nullAdaptationPolicy, List<Type> argumentTypes) throws Throwable {
List<List<InvocationArgumentConvention>> allArgumentConventions = allCombinations(ImmutableList.of(NEVER_NULL, BOXED_NULLABLE, NULL_FLAG, BLOCK_POSITION), argumentTypes.size());
for (List<InvocationArgumentConvention> argumentConventions : allArgumentConventions) {
for (InvocationReturnConvention returnConvention : InvocationReturnConvention.values()) {
InvocationConvention expectedConvention = new InvocationConvention(argumentConventions, returnConvention, false, true);
adaptAndVerify(methodHandle, actualConvention, expectedConvention, nullAdaptationPolicy, argumentTypes);
}
}
}
use of io.trino.spi.function.InvocationConvention.InvocationArgumentConvention in project trino by trinodb.
the class TestScalarFunctionAdapter method hasNullableToNoNullableAdaptation.
private static boolean hasNullableToNoNullableAdaptation(InvocationConvention actualConvention, InvocationConvention expectedConvention) {
for (int i = 0; i < actualConvention.getArgumentConventions().size(); i++) {
InvocationArgumentConvention actualArgumentConvention = actualConvention.getArgumentConvention(i);
InvocationArgumentConvention expectedArgumentConvention = expectedConvention.getArgumentConvention(i);
if (actualArgumentConvention == NEVER_NULL && (expectedArgumentConvention == BOXED_NULLABLE || expectedArgumentConvention == NULL_FLAG)) {
// this conversion is not allowed
return true;
}
}
return false;
}
Aggregations