/* * 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.sql.planner; import com.facebook.presto.Session; import com.facebook.presto.metadata.Metadata; import com.facebook.presto.metadata.TableHandle; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.predicate.TupleDomain; import com.facebook.presto.spi.type.Type; import com.facebook.presto.sql.ExpressionUtils; import com.facebook.presto.sql.analyzer.Analysis; import com.facebook.presto.sql.analyzer.Field; import com.facebook.presto.sql.analyzer.RelationId; import com.facebook.presto.sql.analyzer.RelationType; import com.facebook.presto.sql.analyzer.Scope; import com.facebook.presto.sql.planner.plan.AggregationNode; import com.facebook.presto.sql.planner.plan.Assignments; import com.facebook.presto.sql.planner.plan.ExceptNode; import com.facebook.presto.sql.planner.plan.FilterNode; import com.facebook.presto.sql.planner.plan.IntersectNode; import com.facebook.presto.sql.planner.plan.JoinNode; import com.facebook.presto.sql.planner.plan.PlanNode; import com.facebook.presto.sql.planner.plan.ProjectNode; import com.facebook.presto.sql.planner.plan.SampleNode; import com.facebook.presto.sql.planner.plan.TableScanNode; import com.facebook.presto.sql.planner.plan.UnionNode; import com.facebook.presto.sql.planner.plan.UnnestNode; import com.facebook.presto.sql.planner.plan.ValuesNode; import com.facebook.presto.sql.tree.AliasedRelation; import com.facebook.presto.sql.tree.Cast; import com.facebook.presto.sql.tree.CoalesceExpression; import com.facebook.presto.sql.tree.ComparisonExpression; import com.facebook.presto.sql.tree.ComparisonExpressionType; import com.facebook.presto.sql.tree.DefaultTraversalVisitor; import com.facebook.presto.sql.tree.Except; import com.facebook.presto.sql.tree.Expression; import com.facebook.presto.sql.tree.ExpressionTreeRewriter; import com.facebook.presto.sql.tree.InPredicate; import com.facebook.presto.sql.tree.Intersect; import com.facebook.presto.sql.tree.Join; import com.facebook.presto.sql.tree.JoinUsing; import com.facebook.presto.sql.tree.LambdaArgumentDeclaration; import com.facebook.presto.sql.tree.LongLiteral; import com.facebook.presto.sql.tree.QualifiedName; import com.facebook.presto.sql.tree.Query; import com.facebook.presto.sql.tree.QuerySpecification; import com.facebook.presto.sql.tree.Relation; import com.facebook.presto.sql.tree.Row; import com.facebook.presto.sql.tree.SampledRelation; import com.facebook.presto.sql.tree.SetOperation; import com.facebook.presto.sql.tree.SymbolReference; import com.facebook.presto.sql.tree.Table; import com.facebook.presto.sql.tree.TableSubquery; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; import com.facebook.presto.sql.tree.Values; import com.facebook.presto.type.ArrayType; import com.facebook.presto.type.MapType; import com.facebook.presto.util.maps.IdentityLinkedHashMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.UnmodifiableIterator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import static com.facebook.presto.sql.analyzer.SemanticExceptions.notSupportedException; import static com.facebook.presto.sql.planner.ExpressionInterpreter.evaluateConstantExpression; import static com.facebook.presto.sql.tree.Join.Type.INNER; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.Objects.requireNonNull; class RelationPlanner extends DefaultTraversalVisitor<RelationPlan, Void> { private final Analysis analysis; private final SymbolAllocator symbolAllocator; private final PlanNodeIdAllocator idAllocator; private final IdentityLinkedHashMap<LambdaArgumentDeclaration, Symbol> lambdaDeclarationToSymbolMap; private final Metadata metadata; private final Session session; private final SubqueryPlanner subqueryPlanner; RelationPlanner( Analysis analysis, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator, IdentityLinkedHashMap<LambdaArgumentDeclaration, Symbol> lambdaDeclarationToSymbolMap, Metadata metadata, Session session) { requireNonNull(analysis, "analysis is null"); requireNonNull(symbolAllocator, "symbolAllocator is null"); requireNonNull(idAllocator, "idAllocator is null"); requireNonNull(lambdaDeclarationToSymbolMap, "lambdaDeclarationToSymbolMap is null"); requireNonNull(metadata, "metadata is null"); requireNonNull(session, "session is null"); this.analysis = analysis; this.symbolAllocator = symbolAllocator; this.idAllocator = idAllocator; this.lambdaDeclarationToSymbolMap = lambdaDeclarationToSymbolMap; this.metadata = metadata; this.session = session; this.subqueryPlanner = new SubqueryPlanner(analysis, symbolAllocator, idAllocator, lambdaDeclarationToSymbolMap, metadata, session, analysis.getParameters()); } @Override protected RelationPlan visitTable(Table node, Void context) { Query namedQuery = analysis.getNamedQuery(node); Scope scope = analysis.getScope(node); if (namedQuery != null) { RelationPlan subPlan = process(namedQuery, null); // Add implicit coercions if view query produces types that don't match the declared output types // of the view (e.g., if the underlying tables referenced by the view changed) Type[] types = scope.getRelationType().getAllFields().stream().map(Field::getType).toArray(Type[]::new); RelationPlan withCoercions = addCoercions(subPlan, types); return new RelationPlan(withCoercions.getRoot(), scope, withCoercions.getFieldMappings()); } TableHandle handle = analysis.getTableHandle(node); ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder(); ImmutableMap.Builder<Symbol, ColumnHandle> columns = ImmutableMap.builder(); for (Field field : scope.getRelationType().getAllFields()) { Symbol symbol = symbolAllocator.newSymbol(field.getName().get(), field.getType()); outputSymbolsBuilder.add(symbol); columns.put(symbol, analysis.getColumn(field)); } List<Symbol> outputSymbols = outputSymbolsBuilder.build(); PlanNode root = new TableScanNode(idAllocator.getNextId(), handle, outputSymbols, columns.build(), Optional.empty(), TupleDomain.all(), null); return new RelationPlan(root, scope, outputSymbols); } @Override protected RelationPlan visitAliasedRelation(AliasedRelation node, Void context) { RelationPlan subPlan = process(node.getRelation(), context); return new RelationPlan(subPlan.getRoot(), analysis.getScope(node), subPlan.getFieldMappings()); } @Override protected RelationPlan visitSampledRelation(SampledRelation node, Void context) { RelationPlan subPlan = process(node.getRelation(), context); double ratio = analysis.getSampleRatio(node); PlanNode planNode = new SampleNode(idAllocator.getNextId(), subPlan.getRoot(), ratio, SampleNode.Type.fromType(node.getType())); return new RelationPlan(planNode, analysis.getScope(node), subPlan.getFieldMappings()); } @Override protected RelationPlan visitJoin(Join node, Void context) { // TODO: translate the RIGHT join into a mirrored LEFT join when we refactor (@martint) RelationPlan leftPlan = process(node.getLeft(), context); // Convert CROSS JOIN UNNEST to an UnnestNode if (node.getRight() instanceof Unnest || (node.getRight() instanceof AliasedRelation && ((AliasedRelation) node.getRight()).getRelation() instanceof Unnest)) { Unnest unnest; if (node.getRight() instanceof AliasedRelation) { unnest = (Unnest) ((AliasedRelation) node.getRight()).getRelation(); } else { unnest = (Unnest) node.getRight(); } if (node.getType() != Join.Type.CROSS && node.getType() != Join.Type.IMPLICIT) { throw notSupportedException(unnest, "UNNEST on other than the right side of CROSS JOIN"); } return planCrossJoinUnnest(leftPlan, node, unnest); } RelationPlan rightPlan = process(node.getRight(), context); PlanBuilder leftPlanBuilder = initializePlanBuilder(leftPlan); PlanBuilder rightPlanBuilder = initializePlanBuilder(rightPlan); // NOTE: symbols must be in the same order as the outputDescriptor List<Symbol> outputSymbols = ImmutableList.<Symbol>builder() .addAll(leftPlan.getFieldMappings()) .addAll(rightPlan.getFieldMappings()) .build(); ImmutableList.Builder<JoinNode.EquiJoinClause> equiClauses = ImmutableList.builder(); List<Expression> complexJoinExpressions = new ArrayList<>(); List<Expression> postInnerJoinConditions = new ArrayList<>(); if (node.getType() != Join.Type.CROSS && node.getType() != Join.Type.IMPLICIT) { Expression criteria = analysis.getJoinCriteria(node); RelationType left = analysis.getOutputDescriptor(node.getLeft()); RelationType right = analysis.getOutputDescriptor(node.getRight()); List<Expression> leftComparisonExpressions = new ArrayList<>(); List<Expression> rightComparisonExpressions = new ArrayList<>(); List<ComparisonExpressionType> joinConditionComparisonTypes = new ArrayList<>(); for (Expression conjunct : ExpressionUtils.extractConjuncts(criteria)) { conjunct = ExpressionUtils.normalize(conjunct); if (!isEqualComparisonExpression(conjunct) && node.getType() != INNER) { complexJoinExpressions.add(conjunct); continue; } Set<QualifiedName> dependencies = DependencyExtractor.extractNames(conjunct, analysis.getColumnReferences()); boolean isJoinUsing = node.getCriteria().filter(JoinUsing.class::isInstance).isPresent(); if (!isJoinUsing && (dependencies.stream().allMatch(left::canResolve) || dependencies.stream().allMatch(right::canResolve))) { // If the conjunct can be evaluated entirely with the inputs on either side of the join, add // it to the list complex expressions and let the optimizers figure out how to push it down later. // Due to legacy reasons, the expression for "join using" looks like "x = x", which (incorrectly) // appears to fit the condition we're after. So we skip them. complexJoinExpressions.add(conjunct); } else if (conjunct instanceof ComparisonExpression) { Expression firstExpression = ((ComparisonExpression) conjunct).getLeft(); Expression secondExpression = ((ComparisonExpression) conjunct).getRight(); ComparisonExpressionType comparisonType = ((ComparisonExpression) conjunct).getType(); Set<QualifiedName> firstDependencies = DependencyExtractor.extractNames(firstExpression, analysis.getColumnReferences()); Set<QualifiedName> secondDependencies = DependencyExtractor.extractNames(secondExpression, analysis.getColumnReferences()); if (firstDependencies.stream().allMatch(left::canResolve) && secondDependencies.stream().allMatch(right::canResolve)) { leftComparisonExpressions.add(firstExpression); rightComparisonExpressions.add(secondExpression); joinConditionComparisonTypes.add(comparisonType); } else if (firstDependencies.stream().allMatch(right::canResolve) && secondDependencies.stream().allMatch(left::canResolve)) { leftComparisonExpressions.add(secondExpression); rightComparisonExpressions.add(firstExpression); joinConditionComparisonTypes.add(comparisonType.flip()); } else { // the case when we mix symbols from both left and right join side on either side of condition. complexJoinExpressions.add(conjunct); } } else { complexJoinExpressions.add(conjunct); } } leftPlanBuilder = subqueryPlanner.handleSubqueries(leftPlanBuilder, leftComparisonExpressions, node); rightPlanBuilder = subqueryPlanner.handleSubqueries(rightPlanBuilder, rightComparisonExpressions, node); // Add projections for join criteria leftPlanBuilder = leftPlanBuilder.appendProjections(leftComparisonExpressions, symbolAllocator, idAllocator); rightPlanBuilder = rightPlanBuilder.appendProjections(rightComparisonExpressions, symbolAllocator, idAllocator); for (int i = 0; i < leftComparisonExpressions.size(); i++) { if (joinConditionComparisonTypes.get(i) == ComparisonExpressionType.EQUAL) { Symbol leftSymbol = leftPlanBuilder.translate(leftComparisonExpressions.get(i)); Symbol rightSymbol = rightPlanBuilder.translate(rightComparisonExpressions.get(i)); equiClauses.add(new JoinNode.EquiJoinClause(leftSymbol, rightSymbol)); } else { Expression leftExpression = leftPlanBuilder.rewrite(leftComparisonExpressions.get(i)); Expression rightExpression = rightPlanBuilder.rewrite(rightComparisonExpressions.get(i)); postInnerJoinConditions.add(new ComparisonExpression(joinConditionComparisonTypes.get(i), leftExpression, rightExpression)); } } } PlanNode root = new JoinNode(idAllocator.getNextId(), JoinNode.Type.typeConvert(node.getType()), leftPlanBuilder.getRoot(), rightPlanBuilder.getRoot(), equiClauses.build(), ImmutableList.<Symbol>builder() .addAll(leftPlanBuilder.getRoot().getOutputSymbols()) .addAll(rightPlanBuilder.getRoot().getOutputSymbols()) .build(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); if (node.getType() != INNER) { for (Expression complexExpression : complexJoinExpressions) { Set<InPredicate> inPredicates = subqueryPlanner.collectInPredicateSubqueries(complexExpression, node); if (!inPredicates.isEmpty()) { InPredicate inPredicate = Iterables.getLast(inPredicates); throw notSupportedException(inPredicate, "IN with subquery predicate in join condition"); } } // subqueries can be applied only to one side of join - left side is selected in arbitrary way leftPlanBuilder = subqueryPlanner.handleUncorrelatedSubqueries(leftPlanBuilder, complexJoinExpressions, node); } RelationPlan intermediateRootRelationPlan = new RelationPlan(root, analysis.getScope(node), outputSymbols); TranslationMap translationMap = new TranslationMap(intermediateRootRelationPlan, analysis, lambdaDeclarationToSymbolMap); translationMap.setFieldMappings(outputSymbols); translationMap.putExpressionMappingsFrom(leftPlanBuilder.getTranslations()); translationMap.putExpressionMappingsFrom(rightPlanBuilder.getTranslations()); if (node.getType() != INNER && !complexJoinExpressions.isEmpty()) { Expression joinedFilterCondition = ExpressionUtils.and(complexJoinExpressions); joinedFilterCondition = ExpressionTreeRewriter.rewriteWith(new ParameterRewriter(analysis.getParameters(), analysis), joinedFilterCondition); Expression rewrittenFilterCondition = translationMap.rewrite(joinedFilterCondition); root = new JoinNode(idAllocator.getNextId(), JoinNode.Type.typeConvert(node.getType()), leftPlanBuilder.getRoot(), rightPlanBuilder.getRoot(), equiClauses.build(), ImmutableList.<Symbol>builder() .addAll(leftPlanBuilder.getRoot().getOutputSymbols()) .addAll(rightPlanBuilder.getRoot().getOutputSymbols()) .build(), Optional.of(rewrittenFilterCondition), Optional.empty(), Optional.empty(), Optional.empty()); } if (node.getType() == INNER) { // rewrite all the other conditions using output symbols from left + right plan node. PlanBuilder rootPlanBuilder = new PlanBuilder(translationMap, root, analysis.getParameters()); rootPlanBuilder = subqueryPlanner.handleSubqueries(rootPlanBuilder, complexJoinExpressions, node); for (Expression expression : complexJoinExpressions) { expression = ExpressionTreeRewriter.rewriteWith(new ParameterRewriter(analysis.getParameters(), analysis), expression); postInnerJoinConditions.add(rootPlanBuilder.rewrite(expression)); } root = rootPlanBuilder.getRoot(); Expression postInnerJoinCriteria; if (!postInnerJoinConditions.isEmpty()) { postInnerJoinCriteria = ExpressionUtils.and(postInnerJoinConditions); root = new FilterNode(idAllocator.getNextId(), root, postInnerJoinCriteria); } } return new RelationPlan(root, analysis.getScope(node), outputSymbols); } private static boolean isEqualComparisonExpression(Expression conjunct) { return conjunct instanceof ComparisonExpression && ((ComparisonExpression) conjunct).getType() == ComparisonExpressionType.EQUAL; } private RelationPlan planCrossJoinUnnest(RelationPlan leftPlan, Join joinNode, Unnest node) { RelationType unnestOutputDescriptor = analysis.getOutputDescriptor(node); // Create symbols for the result of unnesting ImmutableList.Builder<Symbol> unnestedSymbolsBuilder = ImmutableList.builder(); for (Field field : unnestOutputDescriptor.getVisibleFields()) { Symbol symbol = symbolAllocator.newSymbol(field); unnestedSymbolsBuilder.add(symbol); } ImmutableList<Symbol> unnestedSymbols = unnestedSymbolsBuilder.build(); // Add a projection for all the unnest arguments PlanBuilder planBuilder = initializePlanBuilder(leftPlan); planBuilder = planBuilder.appendProjections(node.getExpressions(), symbolAllocator, idAllocator); TranslationMap translations = planBuilder.getTranslations(); ProjectNode projectNode = (ProjectNode) planBuilder.getRoot(); ImmutableMap.Builder<Symbol, List<Symbol>> unnestSymbols = ImmutableMap.builder(); UnmodifiableIterator<Symbol> unnestedSymbolsIterator = unnestedSymbols.iterator(); for (Expression expression : node.getExpressions()) { Type type = analysis.getType(expression); Symbol inputSymbol = translations.get(expression); if (type instanceof ArrayType) { unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next())); } else if (type instanceof MapType) { unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next(), unnestedSymbolsIterator.next())); } else { throw new IllegalArgumentException("Unsupported type for UNNEST: " + type); } } Optional<Symbol> ordinalitySymbol = node.isWithOrdinality() ? Optional.of(unnestedSymbolsIterator.next()) : Optional.empty(); checkState(!unnestedSymbolsIterator.hasNext(), "Not all output symbols were matched with input symbols"); UnnestNode unnestNode = new UnnestNode(idAllocator.getNextId(), projectNode, leftPlan.getFieldMappings(), unnestSymbols.build(), ordinalitySymbol); return new RelationPlan(unnestNode, analysis.getScope(joinNode), unnestNode.getOutputSymbols()); } private static Expression oneIfNull(Optional<Symbol> symbol) { if (symbol.isPresent()) { return new CoalesceExpression(symbol.get().toSymbolReference(), new LongLiteral("1")); } else { return new LongLiteral("1"); } } @Override protected RelationPlan visitTableSubquery(TableSubquery node, Void context) { return process(node.getQuery(), context); } @Override protected RelationPlan visitQuery(Query node, Void context) { return new QueryPlanner(analysis, symbolAllocator, idAllocator, lambdaDeclarationToSymbolMap, metadata, session) .plan(node); } @Override protected RelationPlan visitQuerySpecification(QuerySpecification node, Void context) { return new QueryPlanner(analysis, symbolAllocator, idAllocator, lambdaDeclarationToSymbolMap, metadata, session) .plan(node); } @Override protected RelationPlan visitValues(Values node, Void context) { Scope scope = analysis.getScope(node); ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder(); for (Field field : scope.getRelationType().getVisibleFields()) { Symbol symbol = symbolAllocator.newSymbol(field); outputSymbolsBuilder.add(symbol); } ImmutableList.Builder<List<Expression>> rows = ImmutableList.builder(); for (Expression row : node.getRows()) { ImmutableList.Builder<Expression> values = ImmutableList.builder(); if (row instanceof Row) { List<Expression> items = ((Row) row).getItems(); for (int i = 0; i < items.size(); i++) { Expression expression = items.get(i); expression = ExpressionTreeRewriter.rewriteWith(new ParameterRewriter(analysis.getParameters(), analysis), expression); Object constantValue = evaluateConstantExpression(expression, analysis.getCoercions(), metadata, session, analysis.getColumnReferences(), analysis.getParameters()); values.add(LiteralInterpreter.toExpression(constantValue, scope.getRelationType().getFieldByIndex(i).getType())); } } else { row = ExpressionTreeRewriter.rewriteWith(new ParameterRewriter(analysis.getParameters(), analysis), row); Object constantValue = evaluateConstantExpression(row, analysis.getCoercions(), metadata, session, analysis.getColumnReferences(), analysis.getParameters()); values.add(LiteralInterpreter.toExpression(constantValue, scope.getRelationType().getFieldByIndex(0).getType())); } rows.add(values.build()); } ValuesNode valuesNode = new ValuesNode(idAllocator.getNextId(), outputSymbolsBuilder.build(), rows.build()); return new RelationPlan(valuesNode, scope, outputSymbolsBuilder.build()); } @Override protected RelationPlan visitUnnest(Unnest node, Void context) { Scope scope = analysis.getScope(node); ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder(); for (Field field : scope.getRelationType().getVisibleFields()) { Symbol symbol = symbolAllocator.newSymbol(field); outputSymbolsBuilder.add(symbol); } List<Symbol> unnestedSymbols = outputSymbolsBuilder.build(); // If we got here, then we must be unnesting a constant, and not be in a join (where there could be column references) ImmutableList.Builder<Symbol> argumentSymbols = ImmutableList.builder(); ImmutableList.Builder<Expression> values = ImmutableList.builder(); ImmutableMap.Builder<Symbol, List<Symbol>> unnestSymbols = ImmutableMap.builder(); Iterator<Symbol> unnestedSymbolsIterator = unnestedSymbols.iterator(); for (Expression expression : node.getExpressions()) { expression = ExpressionTreeRewriter.rewriteWith(new ParameterRewriter(analysis.getParameters(), analysis), expression); Object constantValue = evaluateConstantExpression(expression, analysis.getCoercions(), metadata, session, analysis.getColumnReferences(), analysis.getParameters()); Type type = analysis.getType(expression); values.add(LiteralInterpreter.toExpression(constantValue, type)); Symbol inputSymbol = symbolAllocator.newSymbol(expression, type); argumentSymbols.add(inputSymbol); if (type instanceof ArrayType) { unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next())); } else if (type instanceof MapType) { unnestSymbols.put(inputSymbol, ImmutableList.of(unnestedSymbolsIterator.next(), unnestedSymbolsIterator.next())); } else { throw new IllegalArgumentException("Unsupported type for UNNEST: " + type); } } Optional<Symbol> ordinalitySymbol = node.isWithOrdinality() ? Optional.of(unnestedSymbolsIterator.next()) : Optional.empty(); checkState(!unnestedSymbolsIterator.hasNext(), "Not all output symbols were matched with input symbols"); ValuesNode valuesNode = new ValuesNode(idAllocator.getNextId(), argumentSymbols.build(), ImmutableList.of(values.build())); UnnestNode unnestNode = new UnnestNode(idAllocator.getNextId(), valuesNode, ImmutableList.of(), unnestSymbols.build(), ordinalitySymbol); return new RelationPlan(unnestNode, scope, unnestedSymbols); } private RelationPlan processAndCoerceIfNecessary(Relation node, Void context) { Type[] coerceToTypes = analysis.getRelationCoercion(node); RelationPlan plan = this.process(node, context); if (coerceToTypes == null) { return plan; } return addCoercions(plan, coerceToTypes); } private RelationPlan addCoercions(RelationPlan plan, Type[] targetColumnTypes) { List<Symbol> oldSymbols = plan.getFieldMappings(); RelationType oldDescriptor = plan.getDescriptor().withOnlyVisibleFields(); verify(targetColumnTypes.length == oldSymbols.size()); ImmutableList.Builder<Symbol> newSymbols = new ImmutableList.Builder<>(); Field[] newFields = new Field[targetColumnTypes.length]; Assignments.Builder assignments = Assignments.builder(); for (int i = 0; i < targetColumnTypes.length; i++) { Symbol inputSymbol = oldSymbols.get(i); Type inputType = symbolAllocator.getTypes().get(inputSymbol); Type outputType = targetColumnTypes[i]; if (outputType != inputType) { Expression cast = new Cast(inputSymbol.toSymbolReference(), outputType.getTypeSignature().toString()); Symbol outputSymbol = symbolAllocator.newSymbol(cast, outputType); assignments.put(outputSymbol, cast); newSymbols.add(outputSymbol); } else { SymbolReference symbolReference = inputSymbol.toSymbolReference(); Symbol outputSymbol = symbolAllocator.newSymbol(symbolReference, outputType); assignments.put(outputSymbol, symbolReference); newSymbols.add(outputSymbol); } Field oldField = oldDescriptor.getFieldByIndex(i); newFields[i] = new Field( oldField.getRelationAlias(), oldField.getName(), targetColumnTypes[i], oldField.isHidden(), oldField.getOriginTable(), oldField.isAliased()); } ProjectNode projectNode = new ProjectNode(idAllocator.getNextId(), plan.getRoot(), assignments.build()); return new RelationPlan(projectNode, Scope.builder().withRelationType(RelationId.anonymous(), new RelationType(newFields)).build(), newSymbols.build()); } @Override protected RelationPlan visitUnion(Union node, Void context) { checkArgument(!node.getRelations().isEmpty(), "No relations specified for UNION"); SetOperationPlan setOperationPlan = process(node); PlanNode planNode = new UnionNode(idAllocator.getNextId(), setOperationPlan.getSources(), setOperationPlan.getSymbolMapping(), ImmutableList.copyOf(setOperationPlan.getSymbolMapping().keySet())); if (node.isDistinct()) { planNode = distinct(planNode); } return new RelationPlan(planNode, analysis.getScope(node), planNode.getOutputSymbols()); } @Override protected RelationPlan visitIntersect(Intersect node, Void context) { checkArgument(!node.getRelations().isEmpty(), "No relations specified for INTERSECT"); SetOperationPlan setOperationPlan = process(node); PlanNode planNode = new IntersectNode(idAllocator.getNextId(), setOperationPlan.getSources(), setOperationPlan.getSymbolMapping(), ImmutableList.copyOf(setOperationPlan.getSymbolMapping().keySet())); return new RelationPlan(planNode, analysis.getScope(node), planNode.getOutputSymbols()); } @Override protected RelationPlan visitExcept(Except node, Void context) { checkArgument(!node.getRelations().isEmpty(), "No relations specified for EXCEPT"); SetOperationPlan setOperationPlan = process(node); PlanNode planNode = new ExceptNode(idAllocator.getNextId(), setOperationPlan.getSources(), setOperationPlan.getSymbolMapping(), ImmutableList.copyOf(setOperationPlan.getSymbolMapping().keySet())); return new RelationPlan(planNode, analysis.getScope(node), planNode.getOutputSymbols()); } private SetOperationPlan process(SetOperation node) { List<Symbol> outputs = null; ImmutableList.Builder<PlanNode> sources = ImmutableList.builder(); ImmutableListMultimap.Builder<Symbol, Symbol> symbolMapping = ImmutableListMultimap.builder(); List<RelationPlan> subPlans = node.getRelations().stream() .map(relation -> processAndCoerceIfNecessary(relation, null)) .collect(toImmutableList()); for (RelationPlan relationPlan : subPlans) { List<Symbol> childOutputSymbols = relationPlan.getFieldMappings(); if (outputs == null) { // Use the first Relation to derive output symbol names RelationType descriptor = relationPlan.getDescriptor(); ImmutableList.Builder<Symbol> outputSymbolBuilder = ImmutableList.builder(); for (Field field : descriptor.getVisibleFields()) { int fieldIndex = descriptor.indexOf(field); Symbol symbol = childOutputSymbols.get(fieldIndex); outputSymbolBuilder.add(symbolAllocator.newSymbol(symbol.getName(), symbolAllocator.getTypes().get(symbol))); } outputs = outputSymbolBuilder.build(); } RelationType descriptor = relationPlan.getDescriptor(); checkArgument(descriptor.getVisibleFieldCount() == outputs.size(), "Expected relation to have %s symbols but has %s symbols", descriptor.getVisibleFieldCount(), outputs.size()); int fieldId = 0; for (Field field : descriptor.getVisibleFields()) { int fieldIndex = descriptor.indexOf(field); symbolMapping.put(outputs.get(fieldId), childOutputSymbols.get(fieldIndex)); fieldId++; } sources.add(relationPlan.getRoot()); } return new SetOperationPlan(sources.build(), symbolMapping.build()); } private PlanBuilder initializePlanBuilder(RelationPlan relationPlan) { TranslationMap translations = new TranslationMap(relationPlan, analysis, lambdaDeclarationToSymbolMap); // Make field->symbol mapping from underlying relation plan available for translations // This makes it possible to rewrite FieldOrExpressions that reference fields from the underlying tuple directly translations.setFieldMappings(relationPlan.getFieldMappings()); return new PlanBuilder(translations, relationPlan.getRoot(), analysis.getParameters()); } private PlanNode distinct(PlanNode node) { return new AggregationNode(idAllocator.getNextId(), node, ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(node.getOutputSymbols()), AggregationNode.Step.SINGLE, Optional.empty(), Optional.empty()); } private static class SetOperationPlan { private final List<PlanNode> sources; private final ListMultimap<Symbol, Symbol> symbolMapping; private SetOperationPlan(List<PlanNode> sources, ListMultimap<Symbol, Symbol> symbolMapping) { this.sources = sources; this.symbolMapping = symbolMapping; } public List<PlanNode> getSources() { return sources; } public ListMultimap<Symbol, Symbol> getSymbolMapping() { return symbolMapping; } } }