/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer.rule; import com.foundationdb.sql.optimizer.plan.Sort.OrderByExpression; import com.foundationdb.sql.optimizer.plan.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** {@link Sort} takes a list of expressions, unlike {@link * AggregateSource}, which takes value rows from {@link Project}. If * the Sort would be pushed into a {@link MapJoin}, it needs to get * {@link Project} so that can go on the inside and it can remain * outside. */ public class SortSplitter extends BaseRule { private static final Logger logger = LoggerFactory.getLogger(SortSplitter.class); @Override protected Logger getLogger() { return logger; } @Override public void apply(PlanContext plan) { List<Sort> sorts = new MapSortFinder().find(plan.getPlan()); for (Sort sort : sorts) { split(sort); } } protected void split(Sort sort) { PlanNode input = sort.getInput(); if (input instanceof Project) return; // Already all set. Project outputProject = null; List<ExpressionNode> projects = new ArrayList<>(); Project inputProject = new Project(input, projects); boolean projectsAdded = false; if (sort.getOutput() instanceof Project) { outputProject = (Project)sort.getOutput(); projects.addAll(outputProject.getFields()); } for (OrderByExpression orderBy : sort.getOrderBy()) { ExpressionNode expr = orderBy.getExpression(); int idx; if (outputProject == null) idx = -1; else idx = projects.indexOf(expr); if (idx < 0) { idx = projects.size(); projects.add(expr); projectsAdded = true; } ExpressionNode cexpr = new ColumnExpression(inputProject, idx, expr.getSQLtype(), expr.getSQLsource(), expr.getType()); orderBy.setExpression(cexpr); } sort.replaceInput(input, inputProject); if (outputProject != null) { if (!projectsAdded) { // If we sorted by a subset of the output projects (in any // order), since we arranged for the input projects to // match, we don't need that second project any more. outputProject.getOutput().replaceInput(outputProject, sort); } else { // If we sorted by something we didn't output, we // still need the project, so it has to be reformed in // terms of the superset one. List<ExpressionNode> oprojects = outputProject.getFields(); for (int i = 0; i < oprojects.size(); i++) { ExpressionNode oexpr = oprojects.get(i); ExpressionNode cexpr = new ColumnExpression(inputProject, i, oexpr.getSQLtype(), oexpr.getSQLsource(), oexpr.getType()); oprojects.set(i, cexpr); } } } } static class MapSortFinder implements PlanVisitor, ExpressionVisitor { List<Sort> result = new ArrayList<>(); public List<Sort> find(PlanNode root) { root.accept(this); return result; } @Override public boolean visitEnter(PlanNode n) { return visit(n); } @Override public boolean visitLeave(PlanNode n) { return true; } @Override public boolean visit(PlanNode n) { if (n instanceof MapJoin) { PlanNode output = n.getOutput(); if (output instanceof UsingHashTable) output = output.getOutput(); if (output instanceof Select) output = output.getOutput(); if (output instanceof Sort) result.add((Sort)output); } return true; } @Override public boolean visitEnter(ExpressionNode n) { return visit(n); } @Override public boolean visitLeave(ExpressionNode n) { return true; } @Override public boolean visit(ExpressionNode n) { return true; } } }