class Context {
        private final ArrayList<Function> aggregates = new ArrayList<>();
        private final ArrayList<Function> tableFunctions = new ArrayList<>();
        private final ArrayList<Symbol> standalone = new ArrayList<>();
        private final ArrayList<WindowFunction> windowFunctions = new ArrayList<>();
        private final ArrayList<SelectSymbol> correlatedQueries = new ArrayList<>();

        /**
         * OuterColumns found within a relation
         * For example in
         *
         * <pre>
         *  SELECT (SELECT t.mountain) FROM sys.summits t
         * </pre>
         *
         * This would contain `t.mountain` when processing the <b>inner</b> relation.
         */
        private final ArrayList<OuterColumn> outerColumns = new ArrayList<>();
        private boolean insideAggregate = false;

        private int tableFunctionLevel = 0;

        boolean foundAggregateOrTableFunction = false;

        Context() {
        }

        void allocateTableFunction(Function tableFunction) {
            if (tableFunctions.contains(tableFunction) == false) {
                tableFunctions.add(tableFunction);
            }
        }

        void allocateAggregate(Function aggregate) {
            if (aggregates.contains(aggregate) == false) {
                aggregates.add(aggregate);
            }
        }

        void allocateWindowFunction(WindowFunction windowFunction) {
            if (windowFunctions.contains(windowFunction) == false) {
                windowFunctions.add(windowFunction);
            }
        }
    }

    private void process(Collection<Symbol> symbols, Context context) {
        for (Symbol symbol : symbols) {
            process(symbol, context);
        }
    }

    private void process(Symbol symbol, Context context) {
        context.foundAggregateOrTableFunction = false;
        symbol.accept(this, context);
        if (context.foundAggregateOrTableFunction == false) {
            context.standalone.add(symbol);
        }
    }

    public static SplitPoints create(QueriedSelectRelation relation) {<FILL_FUNCTION_BODY>

        Context context = new Context();
        INSTANCE.process(relation.outputs(), context);
        OrderBy orderBy = relation.orderBy();
        if (orderBy != null) {
            INSTANCE.process(orderBy.orderBySymbols(), context);
        }
        Symbol having = relation.having();
        if (having != null) {
            having.accept(INSTANCE, context);
        }
        for (var joinPair : relation.joinPairs()) {
            Symbol condition = joinPair.condition();
            if (condition != null) {
                INSTANCE.process(condition, context);
            }
        }
        Symbol where = relation.where();
        where.accept(INSTANCE, context);
        LinkedHashSet<Symbol> toCollect = new LinkedHashSet<>();
        for (Function tableFunction : context.tableFunctions) {
            toCollect.addAll(extractColumns(tableFunction.arguments()));
        }
        for (Function aggregate : context.aggregates) {
            toCollect.addAll(aggregate.arguments());
            if (aggregate.filter() != null) {
                toCollect.add(aggregate.filter());
            }
        }
        for (WindowFunction windowFunction : context.windowFunctions) {
            toCollect.addAll(extractColumns(windowFunction.arguments()));
            if (windowFunction.filter() != null) {
                toCollect.add(windowFunction.filter());
            }
            INSTANCE.process(windowFunction.windowDefinition().partitions(), context);
            OrderBy windowOrderBy = windowFunction.windowDefinition().orderBy();
            if (windowOrderBy != null) {
                INSTANCE.process(windowOrderBy.orderBySymbols(), context);
            }
        }

        // group-by symbols must be processed on a dedicated context to be able extract table functions which must
        // be processed *below* a grouping operator
        var groupByContext = new Context();
        if (relation.groupBy().isEmpty() == false) {
            INSTANCE.process(relation.groupBy(), groupByContext);
            for (Function tableFunction : groupByContext.tableFunctions) {
                toCollect.addAll(extractColumns(tableFunction.arguments()));
            }
            toCollect.addAll(groupByContext.standalone);
            context.tableFunctions.removeAll(groupByContext.tableFunctions);
        } else if (context.aggregates.isEmpty() && relation.groupBy().isEmpty()) {
            toCollect.addAll(context.standalone);
        }
        var collectOuterColumns = new DefaultTraversalSymbolVisitor<Void, Void>() {

            public Void visitOuterColumn(OuterColumn outerColumn, Void ignored) {
                toCollect.add(outerColumn.symbol());
                return null;
            }
        };
        for (var selectSymbol : context.correlatedQueries) {
            selectSymbol.relation().visitSymbols(symbol -> symbol.accept(collectOuterColumns, null));
        }

        RefVisitor.visitRefs(where, toCollect::add);
        FieldsVisitor.visitFields(where, toCollect::add);
        ArrayList<Symbol> outputs = new ArrayList<>();
        for (var output : toCollect) {
            if (Symbols.containsCorrelatedSubQuery(output)) {
                outputs.addAll(extractColumns(output));
            } else {
                outputs.add(output);
            }
        }
        return new SplitPoints(
            outputs,
            context.outerColumns,
            context.aggregates,
            context.tableFunctions,
            groupByContext.tableFunctions,
            context.windowFunctions
        );
    
<methods>public non-sealed void <init>() ,public java.lang.Void visitAlias(io.crate.expression.symbol.AliasSymbol, io.crate.execution.dsl.projection.builder.SplitPointsBuilder.Context) ,public java.lang.Void visitFetchReference(io.crate.expression.symbol.FetchReference, io.crate.execution.dsl.projection.builder.SplitPointsBuilder.Context) ,public java.lang.Void visitFunction(io.crate.expression.symbol.Function, io.crate.execution.dsl.projection.builder.SplitPointsBuilder.Context) ,public java.lang.Void visitMatchPredicate(io.crate.expression.symbol.MatchPredicate, io.crate.execution.dsl.projection.builder.SplitPointsBuilder.Context) ,public java.lang.Void visitWindowFunction(io.crate.expression.symbol.WindowFunction, io.crate.execution.dsl.projection.builder.SplitPointsBuilder.Context) <variables>