/*
* 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.metadata.FunctionHandle;
import com.facebook.presto.metadata.Metadata;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.TableHandle;
import com.facebook.presto.sql.analyzer.Analysis;
import com.facebook.presto.sql.analyzer.EquiJoinClause;
import com.facebook.presto.sql.analyzer.Field;
import com.facebook.presto.sql.analyzer.FieldOrExpression;
import com.facebook.presto.sql.analyzer.Session;
import com.facebook.presto.sql.analyzer.TupleDescriptor;
import com.facebook.presto.sql.analyzer.Type;
import com.facebook.presto.sql.planner.plan.AggregationNode;
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.SemiJoinNode;
import com.facebook.presto.sql.planner.plan.TableScanNode;
import com.facebook.presto.sql.planner.plan.UnionNode;
import com.facebook.presto.sql.tree.AliasedRelation;
import com.facebook.presto.sql.tree.DefaultTraversalVisitor;
import com.facebook.presto.sql.tree.Expression;
import com.facebook.presto.sql.tree.FunctionCall;
import com.facebook.presto.sql.tree.InPredicate;
import com.facebook.presto.sql.tree.Join;
import com.facebook.presto.sql.tree.QualifiedNameReference;
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.SampledRelation;
import com.facebook.presto.sql.tree.SubqueryExpression;
import com.facebook.presto.sql.tree.Table;
import com.facebook.presto.sql.tree.TableSubquery;
import com.facebook.presto.sql.tree.Union;
import com.google.common.base.Preconditions;
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 java.util.List;
import java.util.Map;
import java.util.Set;
import static com.facebook.presto.sql.analyzer.EquiJoinClause.leftGetter;
import static com.facebook.presto.sql.analyzer.EquiJoinClause.rightGetter;
import static com.facebook.presto.sql.tree.BooleanLiteral.TRUE_LITERAL;
import static com.google.common.base.Preconditions.checkArgument;
class RelationPlanner
extends DefaultTraversalVisitor<RelationPlan, Void>
{
private final Analysis analysis;
private final SymbolAllocator symbolAllocator;
private final PlanNodeIdAllocator idAllocator;
private final Metadata metadata;
private final Session session;
RelationPlanner(Analysis analysis, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator, Metadata metadata, Session session)
{
Preconditions.checkNotNull(analysis, "analysis is null");
Preconditions.checkNotNull(symbolAllocator, "symbolAllocator is null");
Preconditions.checkNotNull(idAllocator, "idAllocator is null");
Preconditions.checkNotNull(metadata, "metadata is null");
Preconditions.checkNotNull(session, "session is null");
this.analysis = analysis;
this.symbolAllocator = symbolAllocator;
this.idAllocator = idAllocator;
this.metadata = metadata;
this.session = session;
}
@Override
protected RelationPlan visitTable(Table node, Void context)
{
if (!node.getName().getPrefix().isPresent()) {
Query namedQuery = analysis.getNamedQuery(node);
if (namedQuery != null) {
RelationPlan subPlan = process(namedQuery, null);
return new RelationPlan(subPlan.getRoot(), analysis.getOutputDescriptor(node), subPlan.getOutputSymbols());
}
}
TupleDescriptor descriptor = analysis.getOutputDescriptor(node);
TableHandle handle = analysis.getTableHandle(node);
ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
ImmutableMap.Builder<Symbol, ColumnHandle> columns = ImmutableMap.builder();
for (int i = 0; i < descriptor.getFields().size(); i++) {
Field field = descriptor.getFields().get(i);
Symbol symbol = symbolAllocator.newSymbol(field.getName().get(), field.getType());
outputSymbolsBuilder.add(symbol);
columns.put(symbol, analysis.getColumn(field));
}
ImmutableList<Symbol> outputSymbols = outputSymbolsBuilder.build();
return new RelationPlan(new TableScanNode(idAllocator.getNextId(), handle, outputSymbols, columns.build(), TRUE_LITERAL, TRUE_LITERAL), descriptor, outputSymbols);
}
@Override
protected RelationPlan visitAliasedRelation(AliasedRelation node, Void context)
{
RelationPlan subPlan = process(node.getRelation(), context);
TupleDescriptor outputDescriptor = analysis.getOutputDescriptor(node);
return new RelationPlan(subPlan.getRoot(), outputDescriptor, subPlan.getOutputSymbols());
}
@Override
protected RelationPlan visitSampledRelation(SampledRelation node, Void context)
{
RelationPlan subPlan = process(node.getRelation(), context);
TupleDescriptor outputDescriptor = analysis.getOutputDescriptor(node);
double ratio = analysis.getSampleRatio(node);
return new RelationPlan(
new SampleNode(idAllocator.getNextId(), subPlan.getRoot(), ratio, SampleNode.Type.fromType(node.getType())),
outputDescriptor,
subPlan.getOutputSymbols());
}
@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);
RelationPlan rightPlan = process(node.getRight(), context);
PlanBuilder leftPlanBuilder = initializePlanBuilder(leftPlan);
PlanBuilder rightPlanBuilder = initializePlanBuilder(rightPlan);
List<EquiJoinClause> criteria = analysis.getJoinCriteria(node);
Analysis.JoinInPredicates joinInPredicates = analysis.getJoinInPredicates(node);
// Add semi joins if necessary
if (joinInPredicates != null) {
leftPlanBuilder = appendSemiJoins(leftPlanBuilder, joinInPredicates.getLeftInPredicates());
rightPlanBuilder = appendSemiJoins(rightPlanBuilder, joinInPredicates.getRightInPredicates());
}
// Add projections for join criteria
leftPlanBuilder = appendProjections(leftPlanBuilder, Iterables.transform(criteria, leftGetter()));
rightPlanBuilder = appendProjections(rightPlanBuilder, Iterables.transform(criteria, rightGetter()));
ImmutableList.Builder<JoinNode.EquiJoinClause> clauses = ImmutableList.builder();
for (EquiJoinClause clause : criteria) {
Symbol leftSymbol = leftPlanBuilder.translate(clause.getLeft());
Symbol rightSymbol = rightPlanBuilder.translate(clause.getRight());
clauses.add(new JoinNode.EquiJoinClause(leftSymbol, rightSymbol));
}
List<Symbol> outputSymbols = ImmutableList.<Symbol>builder()
.addAll(leftPlan.getOutputSymbols())
.addAll(rightPlan.getOutputSymbols())
.build();
return new RelationPlan(new JoinNode(idAllocator.getNextId(), JoinNode.Type.typeConvert(node.getType()), leftPlanBuilder.getRoot(), rightPlanBuilder.getRoot(), clauses.build()), analysis.getOutputDescriptor(node), outputSymbols);
}
@Override
protected RelationPlan visitTableSubquery(TableSubquery node, Void context)
{
return process(node.getQuery(), context);
}
@Override
protected RelationPlan visitQuery(Query node, Void context)
{
PlanBuilder subPlan = new QueryPlanner(analysis, symbolAllocator, idAllocator, metadata, session).process(node, null);
ImmutableList.Builder<Symbol> outputSymbols = ImmutableList.builder();
for (FieldOrExpression fieldOrExpression : analysis.getOutputExpressions(node)) {
outputSymbols.add(subPlan.translate(fieldOrExpression));
}
return new RelationPlan(subPlan.getRoot(), analysis.getOutputDescriptor(node), outputSymbols.build());
}
@Override
protected RelationPlan visitQuerySpecification(QuerySpecification node, Void context)
{
PlanBuilder subPlan = new QueryPlanner(analysis, symbolAllocator, idAllocator, metadata, session).process(node, null);
ImmutableList.Builder<Symbol> outputSymbols = ImmutableList.builder();
for (FieldOrExpression fieldOrExpression : analysis.getOutputExpressions(node)) {
outputSymbols.add(subPlan.translate(fieldOrExpression));
}
return new RelationPlan(subPlan.getRoot(), analysis.getOutputDescriptor(node), outputSymbols.build());
}
@Override
protected RelationPlan visitUnion(Union node, Void context)
{
checkArgument(!node.getRelations().isEmpty(), "No relations specified for UNION");
List<Symbol> outputSymbols = null;
ImmutableList.Builder<PlanNode> sources = ImmutableList.builder();
ImmutableListMultimap.Builder<Symbol, Symbol> symbolMapping = ImmutableListMultimap.builder();
for (Relation relation : node.getRelations()) {
RelationPlan relationPlan = process(relation, context);
if (outputSymbols == null) {
// Use the first Relation to derive output symbol names
ImmutableList.Builder<Symbol> outputSymbolBuilder = ImmutableList.builder();
for (Symbol symbol : relationPlan.getOutputSymbols()) {
outputSymbolBuilder.add(symbolAllocator.newSymbol(symbol.getName(), symbolAllocator.getTypes().get(symbol)));
}
outputSymbols = outputSymbolBuilder.build();
}
for (int i = 0; i < outputSymbols.size(); i++) {
symbolMapping.put(outputSymbols.get(i), relationPlan.getOutputSymbols().get(i));
}
sources.add(relationPlan.getRoot());
}
PlanNode planNode = new UnionNode(idAllocator.getNextId(), sources.build(), symbolMapping.build());
if (node.isDistinct()) {
planNode = distinct(planNode);
}
return new RelationPlan(planNode, analysis.getOutputDescriptor(node), planNode.getOutputSymbols());
}
private PlanBuilder initializePlanBuilder(RelationPlan relationPlan)
{
TranslationMap translations = new TranslationMap(relationPlan, analysis);
// 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.getOutputSymbols());
return new PlanBuilder(translations, relationPlan.getRoot());
}
private PlanBuilder appendProjections(PlanBuilder subPlan, Iterable<Expression> expressions)
{
TranslationMap translations = new TranslationMap(subPlan.getRelationPlan(), analysis);
// Carry over the translations from the source because we are appending projections
translations.copyMappingsFrom(subPlan.getTranslations());
ImmutableMap.Builder<Symbol, Expression> projections = ImmutableMap.builder();
// add an identity projection for underlying plan
for (Symbol symbol : subPlan.getRoot().getOutputSymbols()) {
Expression expression = new QualifiedNameReference(symbol.toQualifiedName());
projections.put(symbol, expression);
}
ImmutableMap.Builder<Symbol, Expression> newTranslations = ImmutableMap.builder();
for (Expression expression : expressions) {
Symbol symbol = symbolAllocator.newSymbol(expression, analysis.getType(expression));
// TODO: CHECK IF THE REWRITE OF A SEMI JOINED EXPRESSION WILL WORK!!!!!!!
projections.put(symbol, translations.rewrite(expression));
newTranslations.put(symbol, expression);
}
// Now append the new translations into the TranslationMap
for (Map.Entry<Symbol, Expression> entry : newTranslations.build().entrySet()) {
translations.put(entry.getValue(), entry.getKey());
}
return new PlanBuilder(translations, new ProjectNode(idAllocator.getNextId(), subPlan.getRoot(), projections.build()));
}
private PlanBuilder appendSemiJoins(PlanBuilder subPlan, Set<InPredicate> inPredicates)
{
for (InPredicate inPredicate : inPredicates) {
subPlan = appendSemiJoin(subPlan, inPredicate);
}
return subPlan;
}
private PlanBuilder appendSemiJoin(PlanBuilder subPlan, InPredicate inPredicate)
{
TranslationMap translations = new TranslationMap(subPlan.getRelationPlan(), analysis);
translations.copyMappingsFrom(subPlan.getTranslations());
subPlan = appendProjections(subPlan, ImmutableList.of(inPredicate.getValue()));
Symbol sourceJoinSymbol = subPlan.translate(inPredicate.getValue());
Preconditions.checkState(inPredicate.getValueList() instanceof SubqueryExpression);
SubqueryExpression subqueryExpression = (SubqueryExpression) inPredicate.getValueList();
RelationPlanner relationPlanner = new RelationPlanner(analysis, symbolAllocator, idAllocator, metadata, session);
RelationPlan valueListRelation = relationPlanner.process(subqueryExpression.getQuery(), null);
Symbol filteringSourceJoinSymbol = Iterables.getOnlyElement(valueListRelation.getRoot().getOutputSymbols());
Symbol semiJoinOutputSymbol = symbolAllocator.newSymbol("semijoinresult", Type.BOOLEAN);
translations.put(inPredicate, semiJoinOutputSymbol);
return new PlanBuilder(translations,
new SemiJoinNode(idAllocator.getNextId(),
subPlan.getRoot(),
valueListRelation.getRoot(),
sourceJoinSymbol,
filteringSourceJoinSymbol,
semiJoinOutputSymbol));
}
private PlanNode distinct(PlanNode node)
{
return new AggregationNode(idAllocator.getNextId(),
node,
node.getOutputSymbols(),
ImmutableMap.<Symbol, FunctionCall>of(),
ImmutableMap.<Symbol, FunctionHandle>of());
}
}