diff --git a/presto-main/src/main/java/io/prestosql/spi/expression/ConnectorExpressionTranslator.java b/presto-main/src/main/java/io/prestosql/spi/expression/ConnectorExpressionTranslator.java index b6ef55c099f4..b4f9b51aec03 100644 --- a/presto-main/src/main/java/io/prestosql/spi/expression/ConnectorExpressionTranslator.java +++ b/presto-main/src/main/java/io/prestosql/spi/expression/ConnectorExpressionTranslator.java @@ -38,6 +38,7 @@ 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.base.Preconditions.checkState; @@ -52,7 +53,7 @@ public static Expression translate(ConnectorExpression expression, Map translate(Session session, Expression expression, TypeAnalyzer types, TypeProvider inputTypes) { return new SqlToConnectorExpressionTranslator(types.getTypes(session, inputTypes, expression)) .process(expression); @@ -82,7 +83,7 @@ public Expression translate(ConnectorExpression expression) if (expression instanceof FieldDereference) { FieldDereference dereference = (FieldDereference) expression; - RowType type = (RowType) expression.getType(); + RowType type = (RowType) dereference.getTarget().getType(); String name = type.getFields().get(dereference.getField()).getName().get(); return new DereferenceExpression(translate(dereference.getTarget()), new Identifier(name)); } @@ -91,73 +92,78 @@ public Expression translate(ConnectorExpression expression) } } - private static class SqlToConnectorExpressionTranslator - extends AstVisitor + static class SqlToConnectorExpressionTranslator + extends AstVisitor, Void> { private final Map, Type> types; - private SqlToConnectorExpressionTranslator(Map, Type> types) + public SqlToConnectorExpressionTranslator(Map, Type> types) { this.types = requireNonNull(types, "types is null"); } @Override - protected ConnectorExpression visitSymbolReference(SymbolReference node, Void context) + protected Optional visitSymbolReference(SymbolReference node, Void context) { - return new Variable(node.getName(), typeOf(node)); + return Optional.of(new Variable(node.getName(), typeOf(node))); } @Override - protected ConnectorExpression visitBooleanLiteral(BooleanLiteral node, Void context) + protected Optional visitBooleanLiteral(BooleanLiteral node, Void context) { - return new Constant(node.getValue(), typeOf(node)); + return Optional.of(new Constant(node.getValue(), typeOf(node))); } @Override - protected ConnectorExpression visitStringLiteral(StringLiteral node, Void context) + protected Optional visitStringLiteral(StringLiteral node, Void context) { - return new Constant(node.getSlice(), typeOf(node)); + return Optional.of(new Constant(node.getSlice(), typeOf(node))); } @Override - protected ConnectorExpression visitDoubleLiteral(DoubleLiteral node, Void context) + protected Optional visitDoubleLiteral(DoubleLiteral node, Void context) { - return new Constant(node.getValue(), typeOf(node)); + return Optional.of(new Constant(node.getValue(), typeOf(node))); } @Override - protected ConnectorExpression visitDecimalLiteral(DecimalLiteral node, Void context) + protected Optional visitDecimalLiteral(DecimalLiteral node, Void context) { - return new Constant(Decimals.parse(node.getValue()).getObject(), typeOf(node)); + return Optional.of(new Constant(Decimals.parse(node.getValue()).getObject(), typeOf(node))); } @Override - protected ConnectorExpression visitCharLiteral(CharLiteral node, Void context) + protected Optional visitCharLiteral(CharLiteral node, Void context) { - return new Constant(node.getSlice(), typeOf(node)); + return Optional.of(new Constant(node.getSlice(), typeOf(node))); } @Override - protected ConnectorExpression visitBinaryLiteral(BinaryLiteral node, Void context) + protected Optional visitBinaryLiteral(BinaryLiteral node, Void context) { - return new Constant(node.getValue(), typeOf(node)); + return Optional.of(new Constant(node.getValue(), typeOf(node))); } @Override - protected ConnectorExpression visitLongLiteral(LongLiteral node, Void context) + protected Optional visitLongLiteral(LongLiteral node, Void context) { - return new Constant(node.getValue(), typeOf(node)); + return Optional.of(new Constant(node.getValue(), typeOf(node))); } @Override - protected ConnectorExpression visitNullLiteral(NullLiteral node, Void context) + protected Optional visitNullLiteral(NullLiteral node, Void context) { - return new Constant(null, typeOf(node)); + return Optional.of(new Constant(null, typeOf(node))); } @Override - protected ConnectorExpression visitDereferenceExpression(DereferenceExpression node, Void context) + protected Optional visitDereferenceExpression(DereferenceExpression node, Void context) { + Optional translatedBase = process(node.getBase()); + if (!translatedBase.isPresent()) { + return Optional.empty(); + } + RowType rowType = (RowType) typeOf(node.getBase()); String fieldName = node.getField().getValue(); List fields = rowType.getFields(); @@ -172,13 +178,13 @@ protected ConnectorExpression visitDereferenceExpression(DereferenceExpression n checkState(index >= 0, "could not find field name: %s", node.getField()); - return new FieldDereference(typeOf(node), process(node.getBase()), index); + return Optional.of(new FieldDereference(typeOf(node), translatedBase.get(), index)); } @Override - protected ConnectorExpression visitExpression(Expression node, Void context) + protected Optional visitExpression(Expression node, Void context) { - throw new UnsupportedOperationException("not yet implemented: expression translator for " + node.getClass().getName()); + return Optional.empty(); } private Type typeOf(Expression node) diff --git a/presto-main/src/main/java/io/prestosql/spi/expression/PartialTranslator.java b/presto-main/src/main/java/io/prestosql/spi/expression/PartialTranslator.java new file mode 100644 index 000000000000..c9e8c7f513e9 --- /dev/null +++ b/presto-main/src/main/java/io/prestosql/spi/expression/PartialTranslator.java @@ -0,0 +1,91 @@ +/* + * 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.prestosql.spi.expression; + +import com.google.common.collect.ImmutableMap; +import io.prestosql.Session; +import io.prestosql.spi.type.Type; +import io.prestosql.sql.planner.TypeAnalyzer; +import io.prestosql.sql.planner.TypeProvider; +import io.prestosql.sql.tree.AstVisitor; +import io.prestosql.sql.tree.Expression; +import io.prestosql.sql.tree.LambdaExpression; +import io.prestosql.sql.tree.NodeRef; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class PartialTranslator +{ + private PartialTranslator() {} + + /** + * Produces {@link ConnectorExpression} translations for disjoint components in the {@param inputExpression} in a + * top-down manner. i.e. if an expression node is translatable, we do not consider its children. + */ + public static Map, ConnectorExpression> extractPartialTranslations( + Expression inputExpression, + Session session, + TypeAnalyzer typeAnalyzer, + TypeProvider typeProvider) + { + requireNonNull(inputExpression, "expressions is null"); + requireNonNull(session, "session is null"); + requireNonNull(typeAnalyzer, "typeAnalyzer is null"); + requireNonNull(typeProvider, "typeProvider is null"); + + Map, ConnectorExpression> partialTranslations = new HashMap<>(); + new Visitor(typeAnalyzer.getTypes(session, typeProvider, inputExpression), partialTranslations).process(inputExpression); + return ImmutableMap.copyOf(partialTranslations); + } + + private static class Visitor + extends AstVisitor + { + private final Map, ConnectorExpression> translatedSubExpressions; + private final ConnectorExpressionTranslator.SqlToConnectorExpressionTranslator translator; + + Visitor(Map, Type> types, Map, ConnectorExpression> translatedSubExpressions) + { + requireNonNull(types, "types is null"); + this.translatedSubExpressions = requireNonNull(translatedSubExpressions, "translatedSubExpressions is null"); + this.translator = new ConnectorExpressionTranslator.SqlToConnectorExpressionTranslator(types); + } + + @Override + public Void visitExpression(Expression node, Void context) + { + Optional result = translator.process(node); + + if (result.isPresent()) { + translatedSubExpressions.put(NodeRef.of(node), result.get()); + } + else { + node.getChildren().forEach(this::process); + } + + return null; + } + + // TODO support lambda expressions for partial projection + @Override + public Void visitLambdaExpression(LambdaExpression functionCall, Void context) + { + return null; + } + } +} diff --git a/presto-main/src/main/java/io/prestosql/sql/planner/ReferenceAwareExpressionNodeInliner.java b/presto-main/src/main/java/io/prestosql/sql/planner/ReferenceAwareExpressionNodeInliner.java index 556a1997b82c..61713872a674 100644 --- a/presto-main/src/main/java/io/prestosql/sql/planner/ReferenceAwareExpressionNodeInliner.java +++ b/presto-main/src/main/java/io/prestosql/sql/planner/ReferenceAwareExpressionNodeInliner.java @@ -33,7 +33,7 @@ public static Expression replaceExpression(Expression expression, Map, Expression> mappings; - public ReferenceAwareExpressionNodeInliner(Map, Expression> mappings) + private ReferenceAwareExpressionNodeInliner(Map, Expression> mappings) { this.mappings = ImmutableMap.copyOf(requireNonNull(mappings, "mappings is null")); } diff --git a/presto-main/src/main/java/io/prestosql/sql/planner/iterative/rule/PushProjectionIntoTableScan.java b/presto-main/src/main/java/io/prestosql/sql/planner/iterative/rule/PushProjectionIntoTableScan.java index 6b3974dded85..32bfcb9a79d5 100644 --- a/presto-main/src/main/java/io/prestosql/sql/planner/iterative/rule/PushProjectionIntoTableScan.java +++ b/presto-main/src/main/java/io/prestosql/sql/planner/iterative/rule/PushProjectionIntoTableScan.java @@ -13,6 +13,8 @@ */ package io.prestosql.sql.planner.iterative.rule; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.prestosql.matching.Capture; import io.prestosql.matching.Captures; import io.prestosql.matching.Pattern; @@ -30,6 +32,7 @@ import io.prestosql.sql.planner.plan.ProjectNode; import io.prestosql.sql.planner.plan.TableScanNode; import io.prestosql.sql.tree.Expression; +import io.prestosql.sql.tree.NodeRef; import java.util.ArrayList; import java.util.HashMap; @@ -37,9 +40,12 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.prestosql.matching.Capture.newCapture; +import static io.prestosql.spi.expression.PartialTranslator.extractPartialTranslations; +import static io.prestosql.sql.planner.ReferenceAwareExpressionNodeInliner.replaceExpression; import static io.prestosql.sql.planner.plan.Patterns.project; import static io.prestosql.sql.planner.plan.Patterns.source; import static io.prestosql.sql.planner.plan.Patterns.tableScan; @@ -71,37 +77,45 @@ public Result apply(ProjectNode project, Captures captures, Context context) { TableScanNode tableScan = captures.get(TABLE_SCAN); - List projections; - try { - projections = project.getAssignments() - .getExpressions().stream() - .map(expression -> ConnectorExpressionTranslator.translate( - context.getSession(), - expression, - typeAnalyzer, - context.getSymbolAllocator().getTypes())) - .collect(toImmutableList()); - } - catch (UnsupportedOperationException e) { - // some expression not supported by translator, skip - // TODO: Support pushing down the expressions that could be translated - // TODO: A possible approach might be: - // 1. For expressions that could not be translated, extract column references - // 2. Provide those column references as part of the call to applyProjection - // 3. Re-assemble a projection based on the new projections + un-translateble projections - // rewritten in terms of the new assignments for the columns passed in #2 - return Result.empty(); + Map inputExpressions = project.getAssignments().getMap(); + + ImmutableList.Builder> nodeReferencesBuilder = ImmutableList.builder(); + ImmutableList.Builder partialProjectionsBuilder = ImmutableList.builder(); + + // Extract translatable components from projection expressions. Prepare a mapping from these internal + // expression nodes to corresponding ConnectorExpression translations. + for (Map.Entry expression : inputExpressions.entrySet()) { + Map, ConnectorExpression> partialTranslations = extractPartialTranslations( + expression.getValue(), + context.getSession(), + typeAnalyzer, + context.getSymbolAllocator().getTypes()); + + partialTranslations.forEach((nodeRef, expr) -> { + nodeReferencesBuilder.add(nodeRef); + partialProjectionsBuilder.add(expr); + }); } + List> nodesForPartialProjections = nodeReferencesBuilder.build(); + List connectorPartialProjections = partialProjectionsBuilder.build(); + Map assignments = tableScan.getAssignments() .entrySet().stream() .collect(toImmutableMap(entry -> entry.getKey().getName(), Map.Entry::getValue)); - Optional> result = metadata.applyProjection(context.getSession(), tableScan.getTable(), projections, assignments); + Optional> result = metadata.applyProjection(context.getSession(), tableScan.getTable(), connectorPartialProjections, assignments); + if (!result.isPresent()) { return Result.empty(); } + List newConnectorPartialProjections = result.get().getProjections(); + checkState(newConnectorPartialProjections.size() == connectorPartialProjections.size(), + "Mismatch between input and output projections from the connector: expected %s but got %s", + connectorPartialProjections.size(), + newConnectorPartialProjections.size()); + List newScanOutputs = new ArrayList<>(); Map newScanAssignments = new HashMap<>(); Map variableMappings = new HashMap<>(); @@ -113,16 +127,23 @@ public Result apply(ProjectNode project, Captures captures, Context context) variableMappings.put(assignment.getVariable(), symbol); } - // TODO: ensure newProjections.size == original projections.size - - List newProjections = result.get().getProjections().stream() + // Translate partial connector projections back to new partial projections + List newPartialProjections = newConnectorPartialProjections.stream() .map(expression -> ConnectorExpressionTranslator.translate(expression, variableMappings, new LiteralEncoder(metadata))) .collect(toImmutableList()); - Assignments.Builder newProjectionAssignments = Assignments.builder(); - for (int i = 0; i < project.getOutputSymbols().size(); i++) { - newProjectionAssignments.put(project.getOutputSymbols().get(i), newProjections.get(i)); + // Map internal node references to new partial projections + ImmutableMap.Builder, Expression> nodesToNewPartialProjectionsBuilder = ImmutableMap.builder(); + for (int i = 0; i < nodesForPartialProjections.size(); i++) { + nodesToNewPartialProjectionsBuilder.put(nodesForPartialProjections.get(i), newPartialProjections.get(i)); } + Map, Expression> nodesToNewPartialProjections = nodesToNewPartialProjectionsBuilder.build(); + + // Stitch partial translations to form new complete projections + Assignments.Builder newProjectionAssignments = Assignments.builder(); + project.getAssignments().entrySet().forEach(entry -> { + newProjectionAssignments.put(entry.getKey(), replaceExpression(entry.getValue(), nodesToNewPartialProjections)); + }); return Result.ofPlanNode( new ProjectNode( diff --git a/presto-main/src/test/java/io/prestosql/spi/expression/TestConnectorExpressionTranslator.java b/presto-main/src/test/java/io/prestosql/spi/expression/TestConnectorExpressionTranslator.java new file mode 100644 index 000000000000..fe22a5fd1456 --- /dev/null +++ b/presto-main/src/test/java/io/prestosql/spi/expression/TestConnectorExpressionTranslator.java @@ -0,0 +1,108 @@ +/* + * 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.prestosql.spi.expression; + +import com.google.common.collect.ImmutableMap; +import io.prestosql.Session; +import io.prestosql.metadata.Metadata; +import io.prestosql.spi.type.Type; +import io.prestosql.sql.parser.SqlParser; +import io.prestosql.sql.planner.LiteralEncoder; +import io.prestosql.sql.planner.Symbol; +import io.prestosql.sql.planner.TypeAnalyzer; +import io.prestosql.sql.planner.TypeProvider; +import io.prestosql.sql.tree.DereferenceExpression; +import io.prestosql.sql.tree.Expression; +import io.prestosql.sql.tree.Identifier; +import io.prestosql.sql.tree.SymbolReference; +import io.prestosql.testing.TestingSession; +import org.testng.annotations.Test; + +import java.util.Map; +import java.util.Optional; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.prestosql.metadata.MetadataManager.createTestMetadataManager; +import static io.prestosql.spi.expression.ConnectorExpressionTranslator.translate; +import static io.prestosql.spi.type.DoubleType.DOUBLE; +import static io.prestosql.spi.type.IntegerType.INTEGER; +import static io.prestosql.spi.type.RowType.field; +import static io.prestosql.spi.type.RowType.rowType; +import static io.prestosql.spi.type.VarcharType.createVarcharType; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class TestConnectorExpressionTranslator +{ + private static final Session TEST_SESSION = TestingSession.testSessionBuilder().build(); + private static final Metadata METADATA = createTestMetadataManager(); + private static final TypeAnalyzer TYPE_ANALYZER = new TypeAnalyzer(new SqlParser(), METADATA); + private static final Type ROW_TYPE = rowType(field("int_symbol_1", INTEGER), field("varchar_symbol_1", createVarcharType(5))); + private static final LiteralEncoder LITERAL_ENCODER = new LiteralEncoder(METADATA); + + private static final Map symbols = ImmutableMap.builder() + .put(new Symbol("double_symbol_1"), DOUBLE) + .put(new Symbol("row_symbol_1"), ROW_TYPE) + .build(); + + private static final TypeProvider TYPE_PROVIDER = TypeProvider.copyOf(symbols); + private static final Map variableMappings = symbols.entrySet().stream() + .collect(toImmutableMap(entry -> entry.getKey().getName(), entry -> entry.getKey())); + + @Test + public void testTranslationToConnectorExpression() + { + assertTranslationToConnectorExpression(new SymbolReference("double_symbol_1"), Optional.of(new Variable("double_symbol_1", DOUBLE))); + + assertTranslationToConnectorExpression( + new DereferenceExpression( + new SymbolReference("row_symbol_1"), + new Identifier("int_symbol_1")), + Optional.of( + new FieldDereference( + INTEGER, + new Variable("row_symbol_1", ROW_TYPE), + 0))); + } + + @Test + public void testTranslationFromConnectorExpression() + { + assertTranslationFromConnectorExpression(new Variable("double_symbol_1", DOUBLE), new SymbolReference("double_symbol_1")); + + assertTranslationFromConnectorExpression( + new FieldDereference( + INTEGER, + new Variable("row_symbol_1", ROW_TYPE), + 0), + new DereferenceExpression( + new SymbolReference("row_symbol_1"), + new Identifier("int_symbol_1"))); + } + + private void assertTranslationToConnectorExpression(Expression expression, Optional connectorExpression) + { + Optional translation = translate(TEST_SESSION, expression, TYPE_ANALYZER, TYPE_PROVIDER); + assertTrue(translation.isPresent() == connectorExpression.isPresent()); + if (translation.isPresent()) { + assertEquals(translation.get(), connectorExpression.get()); + } + } + + private void assertTranslationFromConnectorExpression(ConnectorExpression connectorExpression, Expression expected) + { + Expression translation = translate(connectorExpression, variableMappings, LITERAL_ENCODER); + assertEquals(translation, expected); + } +} diff --git a/presto-main/src/test/java/io/prestosql/spi/expression/TestPartialTranslator.java b/presto-main/src/test/java/io/prestosql/spi/expression/TestPartialTranslator.java new file mode 100644 index 000000000000..16448b2b6846 --- /dev/null +++ b/presto-main/src/test/java/io/prestosql/spi/expression/TestPartialTranslator.java @@ -0,0 +1,102 @@ +/* + * 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.prestosql.spi.expression; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.prestosql.Session; +import io.prestosql.metadata.Metadata; +import io.prestosql.spi.type.Type; +import io.prestosql.sql.parser.SqlParser; +import io.prestosql.sql.planner.Symbol; +import io.prestosql.sql.planner.TypeAnalyzer; +import io.prestosql.sql.planner.TypeProvider; +import io.prestosql.sql.tree.ArithmeticBinaryExpression; +import io.prestosql.sql.tree.DereferenceExpression; +import io.prestosql.sql.tree.Expression; +import io.prestosql.sql.tree.FunctionCall; +import io.prestosql.sql.tree.Identifier; +import io.prestosql.sql.tree.NodeRef; +import io.prestosql.sql.tree.QualifiedName; +import io.prestosql.sql.tree.StringLiteral; +import io.prestosql.sql.tree.SymbolReference; +import io.prestosql.testing.TestingSession; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static io.prestosql.metadata.MetadataManager.createTestMetadataManager; +import static io.prestosql.spi.expression.ConnectorExpressionTranslator.translate; +import static io.prestosql.spi.expression.PartialTranslator.extractPartialTranslations; +import static io.prestosql.spi.type.BigintType.BIGINT; +import static io.prestosql.spi.type.DoubleType.DOUBLE; +import static io.prestosql.spi.type.IntegerType.INTEGER; +import static io.prestosql.spi.type.RowType.field; +import static io.prestosql.spi.type.RowType.rowType; +import static io.prestosql.spi.type.VarcharType.createVarcharType; +import static io.prestosql.sql.tree.ArithmeticBinaryExpression.Operator.ADD; +import static org.testng.Assert.assertEquals; + +@Test +public class TestPartialTranslator +{ + private static final Session TEST_SESSION = TestingSession.testSessionBuilder().build(); + private static final Metadata METADATA = createTestMetadataManager(); + private static final TypeAnalyzer TYPE_ANALYZER = new TypeAnalyzer(new SqlParser(), METADATA); + private static final TypeProvider TYPE_PROVIDER = TypeProvider.copyOf(ImmutableMap.builder() + .put(new Symbol("double_symbol_1"), DOUBLE) + .put(new Symbol("double_symbol_2"), DOUBLE) + .put(new Symbol("bigint_symbol_1"), BIGINT) + .put(new Symbol("row_symbol_1"), rowType(field("int_symbol_1", INTEGER), field("varchar_symbol_1", createVarcharType(5)))) + .build()); + + @Test + public void testPartialTranslator() + { + Expression rowSymbolReference = new SymbolReference("row_symbol_1"); + Expression dereferenceExpression1 = new DereferenceExpression(rowSymbolReference, new Identifier("int_symbol_1")); + Expression dereferenceExpression2 = new DereferenceExpression(rowSymbolReference, new Identifier("varchar_symbol_1")); + Expression stringLiteral = new StringLiteral("abcd"); + Expression symbolReference1 = new SymbolReference("double_symbol_1"); + + assertFullTranslation(symbolReference1); + assertFullTranslation(dereferenceExpression1); + assertFullTranslation(stringLiteral); + + Expression binaryExpression = new ArithmeticBinaryExpression(ADD, symbolReference1, dereferenceExpression1); + assertPartialTranslation(binaryExpression, ImmutableList.of(symbolReference1, dereferenceExpression1)); + + List functionArguments = ImmutableList.of(stringLiteral, dereferenceExpression2); + Expression functionCallExpression = new FunctionCall(QualifiedName.of("concat"), functionArguments); + assertPartialTranslation(functionCallExpression, functionArguments); + } + + private void assertPartialTranslation(Expression expression, List subexpressions) + { + Map, ConnectorExpression> translation = extractPartialTranslations(expression, TEST_SESSION, TYPE_ANALYZER, TYPE_PROVIDER); + assertEquals(subexpressions.size(), translation.size()); + for (Expression subexpression : subexpressions) { + assertEquals(translation.get(NodeRef.of(subexpression)), translate(TEST_SESSION, subexpression, TYPE_ANALYZER, TYPE_PROVIDER).get()); + } + } + + private void assertFullTranslation(Expression expression) + { + Map, ConnectorExpression> translation = extractPartialTranslations(expression, TEST_SESSION, TYPE_ANALYZER, TYPE_PROVIDER); + assertEquals(getOnlyElement(translation.keySet()), NodeRef.of(expression)); + assertEquals(getOnlyElement(translation.values()), translate(TEST_SESSION, expression, TYPE_ANALYZER, TYPE_PROVIDER).get()); + } +}