
io.trino.sql.planner.ConnectorExpressionTranslator Maven / Gradle / Ivy
/*
* 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 io.trino.sql.planner;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.airlift.slice.Slice;
import io.airlift.slice.Slices;
import io.trino.Session;
import io.trino.connector.system.GlobalSystemConnector;
import io.trino.metadata.ResolvedFunction;
import io.trino.operator.scalar.JsonPath;
import io.trino.plugin.base.expression.ConnectorExpressions;
import io.trino.security.AllowAllAccessControl;
import io.trino.spi.connector.CatalogSchemaName;
import io.trino.spi.expression.ConnectorExpression;
import io.trino.spi.expression.FieldDereference;
import io.trino.spi.expression.FunctionName;
import io.trino.spi.expression.StandardFunctions;
import io.trino.spi.expression.Variable;
import io.trino.spi.function.CatalogSchemaFunctionName;
import io.trino.spi.function.OperatorType;
import io.trino.spi.type.ArrayType;
import io.trino.spi.type.RowType;
import io.trino.spi.type.Type;
import io.trino.spi.type.VarcharType;
import io.trino.sql.PlannerContext;
import io.trino.sql.ir.Between;
import io.trino.sql.ir.Call;
import io.trino.sql.ir.Cast;
import io.trino.sql.ir.Comparison;
import io.trino.sql.ir.Constant;
import io.trino.sql.ir.Expression;
import io.trino.sql.ir.FieldReference;
import io.trino.sql.ir.In;
import io.trino.sql.ir.IrVisitor;
import io.trino.sql.ir.IsNull;
import io.trino.sql.ir.Logical;
import io.trino.sql.ir.Not;
import io.trino.sql.ir.NullIf;
import io.trino.sql.ir.Reference;
import io.trino.sql.tree.QualifiedName;
import io.trino.type.JoniRegexp;
import io.trino.type.JsonPathType;
import io.trino.type.LikePattern;
import io.trino.type.Re2JRegexp;
import io.trino.type.Re2JRegexpType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getOnlyElement;
import static io.airlift.slice.SliceUtf8.countCodePoints;
import static io.trino.SystemSessionProperties.isComplexExpressionPushdown;
import static io.trino.metadata.GlobalFunctionCatalog.builtinFunctionName;
import static io.trino.metadata.GlobalFunctionCatalog.isBuiltinFunctionName;
import static io.trino.metadata.LanguageFunctionManager.isInlineFunction;
import static io.trino.spi.expression.StandardFunctions.ADD_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.AND_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.ARRAY_CONSTRUCTOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.CAST_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.DIVIDE_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.EQUAL_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.GREATER_THAN_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.IN_PREDICATE_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.IS_NULL_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.MODULUS_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.MULTIPLY_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.NEGATE_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.NOT_EQUAL_OPERATOR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.NOT_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.NULLIF_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.OR_FUNCTION_NAME;
import static io.trino.spi.expression.StandardFunctions.SUBTRACT_FUNCTION_NAME;
import static io.trino.spi.function.OperatorType.ADD;
import static io.trino.spi.function.OperatorType.DIVIDE;
import static io.trino.spi.function.OperatorType.MODULUS;
import static io.trino.spi.function.OperatorType.MULTIPLY;
import static io.trino.spi.function.OperatorType.NEGATION;
import static io.trino.spi.function.OperatorType.SUBTRACT;
import static io.trino.spi.type.BooleanType.BOOLEAN;
import static io.trino.spi.type.VarcharType.VARCHAR;
import static io.trino.spi.type.VarcharType.createVarcharType;
import static io.trino.sql.DynamicFilters.isDynamicFilterFunction;
import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes;
import static io.trino.sql.ir.IrUtils.combineConjuncts;
import static io.trino.sql.ir.IrUtils.extractConjuncts;
import static io.trino.type.JoniRegexpType.JONI_REGEXP;
import static io.trino.type.LikeFunctions.LIKE_FUNCTION_NAME;
import static io.trino.type.LikeFunctions.LIKE_PATTERN_FUNCTION_NAME;
import static io.trino.type.LikePatternType.LIKE_PATTERN;
import static java.util.Objects.requireNonNull;
public final class ConnectorExpressionTranslator
{
private ConnectorExpressionTranslator() {}
public static Expression translate(Session session, ConnectorExpression expression, PlannerContext plannerContext, Map variableMappings)
{
return new ConnectorToSqlExpressionTranslator(session, plannerContext, variableMappings)
.translate(expression)
.orElseThrow(() -> new UnsupportedOperationException("Expression is not supported: " + expression.toString()));
}
public static Optional translate(Session session, Expression expression)
{
return new SqlToConnectorExpressionTranslator(session)
.process(expression);
}
public static ConnectorExpressionTranslation translateConjuncts(
Session session,
Expression expression)
{
SqlToConnectorExpressionTranslator translator = new SqlToConnectorExpressionTranslator(session);
List conjuncts = extractConjuncts(expression);
List remaining = new ArrayList<>();
List converted = new ArrayList<>(conjuncts.size());
for (Expression conjunct : conjuncts) {
Optional connectorExpression = translator.process(conjunct);
if (connectorExpression.isPresent()) {
converted.add(connectorExpression.get());
}
else {
remaining.add(conjunct);
}
}
return new ConnectorExpressionTranslation(
ConnectorExpressions.and(converted),
combineConjuncts(remaining));
}
@VisibleForTesting
static FunctionName functionNameForComparisonOperator(Comparison.Operator operator)
{
return switch (operator) {
case EQUAL -> EQUAL_OPERATOR_FUNCTION_NAME;
case NOT_EQUAL -> NOT_EQUAL_OPERATOR_FUNCTION_NAME;
case LESS_THAN -> LESS_THAN_OPERATOR_FUNCTION_NAME;
case LESS_THAN_OR_EQUAL -> LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME;
case GREATER_THAN -> GREATER_THAN_OPERATOR_FUNCTION_NAME;
case GREATER_THAN_OR_EQUAL -> GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME;
case IS_DISTINCT_FROM -> IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME;
};
}
public record ConnectorExpressionTranslation(ConnectorExpression connectorExpression, Expression remainingExpression)
{
public ConnectorExpressionTranslation
{
requireNonNull(connectorExpression, "connectorExpression is null");
requireNonNull(remainingExpression, "remainingExpression is null");
}
}
private static class ConnectorToSqlExpressionTranslator
{
private final Session session;
private final PlannerContext plannerContext;
private final Map variableMappings;
public ConnectorToSqlExpressionTranslator(Session session, PlannerContext plannerContext, Map variableMappings)
{
this.session = requireNonNull(session, "session is null");
this.plannerContext = requireNonNull(plannerContext, "plannerContext is null");
this.variableMappings = requireNonNull(variableMappings, "variableMappings is null");
}
public Optional translate(ConnectorExpression expression)
{
if (expression instanceof Variable variable) {
String name = variable.getName();
return Optional.of(variableMappings.get(name).toSymbolReference());
}
if (expression instanceof io.trino.spi.expression.Constant constant) {
return Optional.of(new Constant(constant.getType(), constant.getValue()));
}
if (expression instanceof FieldDereference dereference) {
return translate(dereference.getTarget())
.map(base -> new FieldReference(base, dereference.getField()));
}
if (expression instanceof io.trino.spi.expression.Call call) {
return translateCall(call);
}
return Optional.empty();
}
protected Optional translateCall(io.trino.spi.expression.Call call)
{
if (call.getFunctionName().getCatalogSchema().isPresent()) {
CatalogSchemaName catalogSchemaName = call.getFunctionName().getCatalogSchema().get();
checkArgument(!catalogSchemaName.getCatalogName().equals(GlobalSystemConnector.NAME), "System functions must not be fully qualified");
// this uses allow allow all access control because connector expressions are not allowed access any function
ResolvedFunction resolved = plannerContext.getFunctionResolver().resolveFunction(
session,
QualifiedName.of(catalogSchemaName.getCatalogName(), catalogSchemaName.getSchemaName(), call.getFunctionName().getName()),
fromTypes(call.getArguments().stream().map(ConnectorExpression::getType).collect(toImmutableList())),
new AllowAllAccessControl());
return translateCall(call.getFunctionName().getName(), resolved, call.getArguments());
}
if (AND_FUNCTION_NAME.equals(call.getFunctionName())) {
return translateLogicalExpression(Logical.Operator.AND, call.getArguments());
}
if (OR_FUNCTION_NAME.equals(call.getFunctionName())) {
return translateLogicalExpression(Logical.Operator.OR, call.getArguments());
}
if (NOT_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 1) {
ConnectorExpression expression = getOnlyElement(call.getArguments());
if (expression instanceof io.trino.spi.expression.Call innerCall) {
if (innerCall.getFunctionName().equals(IS_NULL_FUNCTION_NAME) && innerCall.getArguments().size() == 1) {
return translateIsNotNull(innerCall.getArguments().get(0));
}
}
return translateNot(expression);
}
if (IS_NULL_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 1) {
return translateIsNull(call.getArguments().get(0));
}
if (NULLIF_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 2) {
return translateNullIf(call.getArguments().get(0), call.getArguments().get(1));
}
if (CAST_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 1) {
return translateCast(call.getType(), call.getArguments().get(0));
}
// comparisons
if (call.getArguments().size() == 2) {
Optional operator = comparisonOperatorForFunctionName(call.getFunctionName());
if (operator.isPresent()) {
return translateComparison(operator.get(), call.getArguments().get(0), call.getArguments().get(1));
}
}
// arithmetic binary
if (call.getArguments().size() == 2) {
Optional operator = arithmeticBinaryOperatorForFunctionName(call.getFunctionName());
if (operator.isPresent()) {
return translateArithmeticBinary(operator.get(), call.getArguments().get(0), call.getArguments().get(1));
}
}
// arithmetic unary
if (NEGATE_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 1) {
ConnectorExpression argument = getOnlyElement(call.getArguments());
ResolvedFunction function = plannerContext.getMetadata().resolveOperator(NEGATION, ImmutableList.of(argument.getType()));
return translate(argument).map(value -> new Call(function, ImmutableList.of(value)));
}
if (StandardFunctions.LIKE_FUNCTION_NAME.equals(call.getFunctionName())) {
return switch (call.getArguments().size()) {
case 2 -> translateLike(call.getArguments().get(0), call.getArguments().get(1), Optional.empty());
case 3 -> translateLike(call.getArguments().get(0), call.getArguments().get(1), Optional.of(call.getArguments().get(2)));
default -> Optional.empty();
};
}
if (IN_PREDICATE_FUNCTION_NAME.equals(call.getFunctionName()) && call.getArguments().size() == 2) {
return translateInPredicate(call.getArguments().get(0), call.getArguments().get(1));
}
ResolvedFunction resolved = plannerContext.getMetadata().resolveBuiltinFunction(
call.getFunctionName().getName(),
fromTypes(call.getArguments().stream().map(ConnectorExpression::getType).collect(toImmutableList())));
return translateCall(call.getFunctionName().getName(), resolved, call.getArguments());
}
private Optional translateCall(String functionName, ResolvedFunction resolved, List arguments)
{
ResolvedFunctionCallBuilder builder = ResolvedFunctionCallBuilder.builder(resolved);
for (int i = 0; i < arguments.size(); i++) {
ConnectorExpression argument = arguments.get(i);
Type formalType = resolved.signature().getArgumentTypes().get(i);
Type argumentType = argument.getType();
Optional translated = translate(argument);
if (translated.isEmpty()) {
return Optional.empty();
}
Expression expression = translated.get();
if ((formalType == JONI_REGEXP || formalType instanceof Re2JRegexpType || formalType instanceof JsonPathType)
&& argumentType instanceof VarcharType) {
// These types are not used in connector expressions, so require special handling when translating back to expressions.
expression = new Cast(expression, formalType);
}
else if (!argumentType.equals(formalType)) {
// There are no implicit coercions in connector expressions except for engine types that are not exposed in connector expressions.
throw new IllegalArgumentException("Unexpected type %s for argument %s of type %s of %s".formatted(argumentType, formalType, i, functionName));
}
builder.addArgument(expression);
}
return Optional.of(builder.build());
}
private Optional translateIsNotNull(ConnectorExpression argument)
{
Optional translatedArgument = translate(argument);
if (translatedArgument.isPresent()) {
return Optional.of(new Not(new IsNull(translatedArgument.get())));
}
return Optional.empty();
}
private Optional translateIsNull(ConnectorExpression argument)
{
Optional translatedArgument = translate(argument);
if (translatedArgument.isPresent()) {
return Optional.of(new IsNull(translatedArgument.get()));
}
return Optional.empty();
}
private Optional translateNot(ConnectorExpression argument)
{
Optional translatedArgument = translate(argument);
if (argument.getType().equals(BOOLEAN) && translatedArgument.isPresent()) {
return Optional.of(new Not(translatedArgument.get()));
}
return Optional.empty();
}
private Optional translateCast(Type type, ConnectorExpression expression)
{
Optional translatedExpression = translate(expression);
if (translatedExpression.isPresent()) {
return Optional.of(new Cast(translatedExpression.get(), type));
}
return Optional.empty();
}
private Optional translateLogicalExpression(Logical.Operator operator, List arguments)
{
Optional> translatedArguments = translateExpressions(arguments);
return translatedArguments.map(expressions -> new Logical(operator, expressions));
}
private Optional translateComparison(Comparison.Operator operator, ConnectorExpression left, ConnectorExpression right)
{
return translate(left).flatMap(leftTranslated ->
translate(right).map(rightTranslated ->
new Comparison(operator, leftTranslated, rightTranslated)));
}
private Optional translateNullIf(ConnectorExpression first, ConnectorExpression second)
{
Optional firstExpression = translate(first);
Optional secondExpression = translate(second);
if (firstExpression.isPresent() && secondExpression.isPresent()) {
return Optional.of(new NullIf(firstExpression.get(), secondExpression.get()));
}
return Optional.empty();
}
private Optional comparisonOperatorForFunctionName(FunctionName functionName)
{
if (EQUAL_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.EQUAL);
}
if (NOT_EQUAL_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.NOT_EQUAL);
}
if (LESS_THAN_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.LESS_THAN);
}
if (LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.LESS_THAN_OR_EQUAL);
}
if (GREATER_THAN_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.GREATER_THAN);
}
if (GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.GREATER_THAN_OR_EQUAL);
}
if (IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME.equals(functionName)) {
return Optional.of(Comparison.Operator.IS_DISTINCT_FROM);
}
return Optional.empty();
}
private Optional translateArithmeticBinary(OperatorType operator, ConnectorExpression left, ConnectorExpression right)
{
ResolvedFunction function = plannerContext.getMetadata().resolveOperator(operator, ImmutableList.of(left.getType(), right.getType()));
return translate(left).flatMap(leftTranslated ->
translate(right).map(rightTranslated ->
new Call(function, ImmutableList.of(leftTranslated, rightTranslated))));
}
private Optional arithmeticBinaryOperatorForFunctionName(FunctionName functionName)
{
if (ADD_FUNCTION_NAME.equals(functionName)) {
return Optional.of(ADD);
}
if (SUBTRACT_FUNCTION_NAME.equals(functionName)) {
return Optional.of(SUBTRACT);
}
if (MULTIPLY_FUNCTION_NAME.equals(functionName)) {
return Optional.of(MULTIPLY);
}
if (DIVIDE_FUNCTION_NAME.equals(functionName)) {
return Optional.of(DIVIDE);
}
if (MODULUS_FUNCTION_NAME.equals(functionName)) {
return Optional.of(MODULUS);
}
return Optional.empty();
}
protected Optional translateLike(ConnectorExpression value, ConnectorExpression pattern, Optional escape)
{
Optional translatedValue = translate(value);
Optional translatedPattern = translate(pattern);
if (translatedValue.isPresent() && translatedPattern.isPresent()) {
Call patternCall;
if (escape.isPresent()) {
Optional translatedEscape = translate(escape.get());
if (translatedEscape.isEmpty()) {
return Optional.empty();
}
patternCall = BuiltinFunctionCallBuilder.resolve(plannerContext.getMetadata())
.setName(LIKE_PATTERN_FUNCTION_NAME)
.addArgument(VARCHAR, castIfNecessary(translatedPattern.get(), VARCHAR))
.addArgument(VARCHAR, castIfNecessary(translatedEscape.get(), VARCHAR))
.build();
}
else {
patternCall = BuiltinFunctionCallBuilder.resolve(plannerContext.getMetadata())
.setName(LIKE_PATTERN_FUNCTION_NAME)
.addArgument(VARCHAR, castIfNecessary(translatedPattern.get(), VARCHAR))
.build();
}
Call call = BuiltinFunctionCallBuilder.resolve(plannerContext.getMetadata())
.setName(LIKE_FUNCTION_NAME)
.addArgument(value.getType(), translatedValue.get())
.addArgument(LIKE_PATTERN, patternCall)
.build();
return Optional.of(call);
}
return Optional.empty();
}
protected Optional translateInPredicate(ConnectorExpression value, ConnectorExpression values)
{
Optional translatedValue = translate(value);
Optional> translatedValues = extractExpressionsFromArrayCall(values);
if (translatedValue.isPresent() && translatedValues.isPresent()) {
return Optional.of(new In(translatedValue.get(), translatedValues.get()));
}
return Optional.empty();
}
protected Optional> extractExpressionsFromArrayCall(ConnectorExpression expression)
{
if (!(expression instanceof io.trino.spi.expression.Call call)) {
return Optional.empty();
}
if (!call.getFunctionName().equals(ARRAY_CONSTRUCTOR_FUNCTION_NAME)) {
return Optional.empty();
}
return translateExpressions(call.getArguments());
}
protected Optional> translateExpressions(List expressions)
{
ImmutableList.Builder translatedExpressions = ImmutableList.builderWithExpectedSize(expressions.size());
for (ConnectorExpression expression : expressions) {
Optional translated = translate(expression);
if (translated.isEmpty()) {
return Optional.empty();
}
translatedExpressions.add(translated.get());
}
return Optional.of(translatedExpressions.build());
}
private static Expression castIfNecessary(Expression expression, Type type)
{
if (expression.type().equals(type)) {
return expression;
}
return new Cast(expression, type);
}
}
public static class SqlToConnectorExpressionTranslator
extends IrVisitor, Void>
{
private final Session session;
public SqlToConnectorExpressionTranslator(Session session)
{
this.session = requireNonNull(session, "session is null");
}
@Override
protected Optional visitReference(Reference node, Void context)
{
return Optional.of(new Variable(node.name(), ((Expression) node).type()));
}
@Override
protected Optional visitConstant(Constant node, Void context)
{
return Optional.of(constantFor(node.type(), node.value()));
}
@Override
protected Optional visitLogical(Logical node, Void context)
{
if (!isComplexExpressionPushdown(session)) {
return Optional.empty();
}
ImmutableList.Builder arguments = ImmutableList.builderWithExpectedSize(node.terms().size());
for (Expression argument : node.terms()) {
Optional translated = process(argument);
if (translated.isEmpty()) {
return Optional.empty();
}
arguments.add(translated.get());
}
return switch (node.operator()) {
case AND -> Optional.of(new io.trino.spi.expression.Call(BOOLEAN, AND_FUNCTION_NAME, arguments.build()));
case OR -> Optional.of(new io.trino.spi.expression.Call(BOOLEAN, OR_FUNCTION_NAME, arguments.build()));
};
}
@Override
protected Optional visitComparison(Comparison node, Void context)
{
if (!isComplexExpressionPushdown(session)) {
return Optional.empty();
}
return process(node.left()).flatMap(left -> process(node.right()).map(right ->
new io.trino.spi.expression.Call(((Expression) node).type(), functionNameForComparisonOperator(node.operator()), ImmutableList.of(left, right))));
}
@Override
protected Optional visitBetween(Between node, Void context)
{
if (!isComplexExpressionPushdown(session)) {
return Optional.empty();
}
return process(node.value()).flatMap(value ->
process(node.min()).flatMap(min ->
process(node.max()).map(max ->
new io.trino.spi.expression.Call(
BOOLEAN,
AND_FUNCTION_NAME,
ImmutableList.of(
new io.trino.spi.expression.Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(value, min)),
new io.trino.spi.expression.Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(value, max)))))));
}
protected Optional translateNegation(Call node)
{
return process(node.arguments().getFirst())
.map(value -> new io.trino.spi.expression.Call(node.type(), NEGATE_FUNCTION_NAME, ImmutableList.of(value)));
}
@Override
protected Optional visitCast(Cast node, Void context)
{
if (isSpecialType(((Expression) node).type())) {
// We don't want to expose some internal types to connectors.
// These should be constant-folded (if appropriate) separately and
// handled by the regular visitConstant path
return Optional.empty();
}
if (node.safe()) {
// try_cast would need to be modeled separately
return Optional.empty();
}
if (!isComplexExpressionPushdown(session)) {
return Optional.empty();
}
Optional translatedExpression = process(node.expression());
if (translatedExpression.isPresent()) {
return Optional.of(new io.trino.spi.expression.Call(node.type(), CAST_FUNCTION_NAME, List.of(translatedExpression.get())));
}
return Optional.empty();
}
@Override
protected Optional visitCall(Call node, Void context)
{
if (!isComplexExpressionPushdown(session)) {
return Optional.empty();
}
CatalogSchemaFunctionName functionName = node.function().name();
checkArgument(!isDynamicFilterFunction(functionName), "Dynamic filter has no meaning for a connector, it should not be translated into ConnectorExpression");
if (functionName.equals(builtinFunctionName(LIKE_FUNCTION_NAME))) {
return translateLike(node);
}
else if (functionName.equals(builtinFunctionName(NEGATION))) {
return translateNegation(node);
}
else if (functionName.equals(builtinFunctionName(ADD))) {
return process(node.arguments().get(0)).flatMap(left -> process(node.arguments().get(1)).map(right ->
new io.trino.spi.expression.Call(node.type(), ADD_FUNCTION_NAME, ImmutableList.of(left, right))));
}
else if (functionName.equals(builtinFunctionName(SUBTRACT))) {
return process(node.arguments().get(0)).flatMap(left -> process(node.arguments().get(1)).map(right ->
new io.trino.spi.expression.Call(node.type(), SUBTRACT_FUNCTION_NAME, ImmutableList.of(left, right))));
}
else if (functionName.equals(builtinFunctionName(MULTIPLY))) {
return process(node.arguments().get(0)).flatMap(left -> process(node.arguments().get(1)).map(right ->
new io.trino.spi.expression.Call(node.type(), MULTIPLY_FUNCTION_NAME, ImmutableList.of(left, right))));
}
else if (functionName.equals(builtinFunctionName(DIVIDE))) {
return process(node.arguments().get(0)).flatMap(left -> process(node.arguments().get(1)).map(right ->
new io.trino.spi.expression.Call(node.type(), DIVIDE_FUNCTION_NAME, ImmutableList.of(left, right))));
}
else if (functionName.equals(builtinFunctionName(MODULUS))) {
return process(node.arguments().get(0)).flatMap(left -> process(node.arguments().get(1)).map(right ->
new io.trino.spi.expression.Call(node.type(), MODULUS_FUNCTION_NAME, ImmutableList.of(left, right))));
}
ImmutableList.Builder arguments = ImmutableList.builder();
for (Expression argumentExpression : node.arguments()) {
Optional argument = process(argumentExpression);
if (argument.isEmpty()) {
return Optional.empty();
}
arguments.add(argument.get());
}
FunctionName name;
if (isInlineFunction(functionName)) {
return Optional.empty();
}
if (isBuiltinFunctionName(functionName)) {
name = new FunctionName(functionName.getFunctionName());
}
else {
name = new FunctionName(Optional.of(new CatalogSchemaName(functionName.getCatalogName(), functionName.getSchemaName())), functionName.getFunctionName());
}
return Optional.of(new io.trino.spi.expression.Call(((Expression) node).type(), name, arguments.build()));
}
private Optional translateLike(Call node)
{
// we need special handling for LIKE because within the engine IR a LIKE expression
// is modeled as $like(value, $like_pattern(pattern, escape)) and we want
// to expose it to connectors as if if were $like(value, pattern, escape)
ImmutableList.Builder arguments = ImmutableList.builder();
Optional value = process(node.arguments().get(0));
if (value.isEmpty()) {
return Optional.empty();
}
arguments.add(value.get());
Expression patternArgument = node.arguments().get(1);
if (patternArgument instanceof Constant constant) {
LikePattern matcher = (LikePattern) constant.value();
arguments.add(new io.trino.spi.expression.Constant(Slices.utf8Slice(matcher.getPattern()), createVarcharType(matcher.getPattern().length())));
if (matcher.getEscape().isPresent()) {
arguments.add(new io.trino.spi.expression.Constant(Slices.utf8Slice(matcher.getEscape().get().toString()), createVarcharType(1)));
}
}
else if (patternArgument instanceof Call call && call.function().name().equals(builtinFunctionName(LIKE_PATTERN_FUNCTION_NAME))) {
Optional translatedPattern = process(call.arguments().get(0));
if (translatedPattern.isEmpty()) {
return Optional.empty();
}
arguments.add(translatedPattern.get());
if (call.arguments().size() == 2) {
Optional translatedEscape = process(call.arguments().get(1));
if (translatedEscape.isEmpty()) {
return Optional.empty();
}
arguments.add(translatedEscape.get());
}
}
else {
return Optional.empty();
}
return Optional.of(new io.trino.spi.expression.Call(node.type(), StandardFunctions.LIKE_FUNCTION_NAME, arguments.build()));
}
@Override
protected Optional visitIsNull(IsNull node, Void context)
{
Optional translatedValue = process(node.value());
if (translatedValue.isPresent()) {
return Optional.of(new io.trino.spi.expression.Call(BOOLEAN, IS_NULL_FUNCTION_NAME, ImmutableList.of(translatedValue.get())));
}
return Optional.empty();
}
@Override
protected Optional visitNot(Not node, Void context)
{
Optional translatedValue = process(node.value());
if (translatedValue.isPresent()) {
return Optional.of(new io.trino.spi.expression.Call(BOOLEAN, NOT_FUNCTION_NAME, List.of(translatedValue.get())));
}
return Optional.empty();
}
private boolean isSpecialType(Type type)
{
return type.equals(JONI_REGEXP) ||
type instanceof Re2JRegexpType ||
type instanceof JsonPathType;
}
private ConnectorExpression constantFor(Type type, Object value)
{
if (type == JONI_REGEXP) {
Slice pattern = ((JoniRegexp) value).pattern();
return new io.trino.spi.expression.Constant(pattern, createVarcharType(countCodePoints(pattern)));
}
if (type instanceof Re2JRegexpType) {
Slice pattern = Slices.utf8Slice(((Re2JRegexp) value).pattern());
return new io.trino.spi.expression.Constant(pattern, createVarcharType(countCodePoints(pattern)));
}
if (type instanceof JsonPathType) {
Slice pattern = Slices.utf8Slice(((JsonPath) value).pattern());
return new io.trino.spi.expression.Constant(pattern, createVarcharType(countCodePoints(pattern)));
}
return new io.trino.spi.expression.Constant(value, type);
}
@Override
protected Optional visitNullIf(NullIf node, Void context)
{
Optional firstValue = process(node.first());
Optional secondValue = process(node.second());
if (firstValue.isPresent() && secondValue.isPresent()) {
return Optional.of(new io.trino.spi.expression.Call(((Expression) node).type(), NULLIF_FUNCTION_NAME, ImmutableList.of(firstValue.get(), secondValue.get())));
}
return Optional.empty();
}
@Override
protected Optional visitFieldReference(FieldReference node, Void context)
{
if (!(node.base().type() instanceof RowType)) {
return Optional.empty();
}
Optional translatedBase = process(node.base());
if (translatedBase.isEmpty()) {
return Optional.empty();
}
return Optional.of(new FieldDereference(((Expression) node).type(), translatedBase.get(), node.field()));
}
@Override
protected Optional visitIn(In node, Void context)
{
Optional valueExpression = process(node.value());
if (valueExpression.isEmpty()) {
return Optional.empty();
}
ImmutableList.Builder values = ImmutableList.builderWithExpectedSize(node.valueList().size());
for (Expression value : node.valueList()) {
// TODO: NULL should be eliminated on the engine side (within a rule)
if (value == null) {
return Optional.empty();
}
Optional processedValue = process(value);
if (processedValue.isEmpty()) {
return Optional.empty();
}
values.add(processedValue.get());
}
ConnectorExpression arrayExpression = new io.trino.spi.expression.Call(new ArrayType(node.value().type()), ARRAY_CONSTRUCTOR_FUNCTION_NAME, values.build());
return Optional.of(new io.trino.spi.expression.Call(((Expression) node).type(), IN_PREDICATE_FUNCTION_NAME, List.of(valueExpression.get(), arrayExpression)));
}
@Override
protected Optional visitExpression(Expression node, Void context)
{
return Optional.empty();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy