presto-main/src/main/java/io/prestosql/sql/planner/QueryPlanner.java/QueryPlanner/plan
class QueryPlanner:    public RelationPlan plan(QuerySpecification node)
    {
        PlanBuilder builder = planFrom(node);
        RelationPlan fromRelationPlan = builder.getRelationPlan();

        builder = filter(builder, analysis.getWhere(node), node);
        builder = aggregate(builder, node);
        builder = filter(builder, analysis.getHaving(node), node);

        builder = window(builder, node);

        List<SelectExpression> selectExpressions = analysis.getSelectExpressions(node);
        List<Expression> expressions = selectExpressions.stream()
                .map(SelectExpression::getExpression)
                .collect(toImmutableList());
        List<Expression> outputs = expressions;
        builder = handleSubqueries(builder, node, expressions);
        List<Expression> outputExpressions = outputExpressions(selectExpressions);

        if (node.getOrderBy().isPresent()) {
            if (!analysis.isAggregation(node)) {
                // ORDER BY requires both output and source fields to be visible if there are no aggregations
                builder = project(builder, Iterables.concat(expressions, toSymbolReferences(fromRelationPlan.getFieldMappings())));
                outputs = toSymbolReferences(computeOutputs(builder, expressions));

                if (hasExpressionsToUnfold(selectExpressions)) {
                    builder = project(builder, Iterables.concat(outputExpressions, toSymbolReferences(fromRelationPlan.getFieldMappings())));
                    outputs = toSymbolReferences(computeOutputs(builder, outputExpressions));
                }

                builder = planBuilderFor(builder, analysis.getScope(node.getOrderBy().get()));
            }
            else {
                // ORDER BY requires output fields, groups and translated aggregations to be visible for queries with aggregation
                List<Expression> orderByAggregates = analysis.getOrderByAggregates(node.getOrderBy().get());
                builder = project(builder, Iterables.concat(expressions, orderByAggregates));
                outputs = toSymbolReferences(computeOutputs(builder, expressions));

                if (hasExpressionsToUnfold(selectExpressions)) {
                    builder = project(builder, Iterables.concat(outputExpressions, orderByAggregates));
                    outputs = toSymbolReferences(computeOutputs(builder, outputExpressions));
                }

                List<Expression> complexOrderByAggregatesToRemap = orderByAggregates.stream()
                        .filter(expression -> !analysis.isColumnReference(expression))
                        .collect(toImmutableList());
                builder = planBuilderFor(builder, analysis.getScope(node.getOrderBy().get()), complexOrderByAggregatesToRemap);
            }
            builder = window(builder, node.getOrderBy().get());
        }
        else {
            if (hasExpressionsToUnfold(selectExpressions)) {
                builder = project(builder, Iterables.concat(expressions, toSymbolReferences(builder.getRoot().getOutputSymbols())));
                builder = project(builder, Iterables.concat(outputExpressions, toSymbolReferences(builder.getRoot().getOutputSymbols())));
                outputs = toSymbolReferences(computeOutputs(builder, outputExpressions));
            }
        }

        List<Expression> orderBy = analysis.getOrderByExpressions(node);
        builder = handleSubqueries(builder, node, orderBy);
        builder = project(builder, Iterables.concat(orderBy, outputs));

        builder = distinct(builder, node);
        Optional<OrderingScheme> orderingScheme = orderingScheme(builder, node.getOrderBy(), analysis.getOrderByExpressions(node));
        builder = sort(builder, orderingScheme);
        builder = offset(builder, node.getOffset());
        builder = limit(builder, node.getLimit(), orderingScheme);
        builder = project(builder, outputs);

        return new RelationPlan(
                builder.getRoot(),
                analysis.getScope(node),
                computeOutputs(builder, outputs));
    }