class Rename extends ForwardingLogicalPlan implements FieldResolver {

    private final List<Symbol> outputs;
    private final FieldResolver fieldResolver;
    final RelationName name;

    public Rename(List<Symbol> outputs, RelationName name, FieldResolver fieldResolver, LogicalPlan source) {
        super(source);
        this.outputs = outputs;
        this.name = name;
        this.fieldResolver = fieldResolver;
        assert this.outputs.size() == source.outputs().size()
            : "Rename operator must have the same number of outputs as the source. Got " + outputs + " and " + source.outputs();
    }

    @Override
    public boolean preferShardProjections() {
        return source.preferShardProjections();
    }

    public RelationName name() {
        return name;
    }

    @Override
    public List<Symbol> outputs() {
        return outputs;
    }

    @Override
    public LogicalPlan pruneOutputsExcept(SequencedCollection<Symbol> outputsToKeep) {<FILL_FUNCTION_BODY>}

    @Nullable
    @Override
    public FetchRewrite rewriteToFetch(Collection<Symbol> usedColumns) {
        IdentityHashMap<Symbol, Symbol> parentToChildMap = new IdentityHashMap<>(outputs.size());
        IdentityHashMap<Symbol, Symbol> childToParentMap = new IdentityHashMap<>(outputs.size());
        for (int i = 0; i < outputs.size(); i++) {
            parentToChildMap.put(outputs.get(i), source.outputs().get(i));
            childToParentMap.put(source.outputs().get(i), outputs.get(i));
        }
        ArrayList<Symbol> mappedUsedColumns = new ArrayList<>();
        for (Symbol usedColumn : usedColumns) {
            SymbolVisitors.intersection(usedColumn, outputs, s -> {
                Symbol childSymbol = parentToChildMap.get(s);
                assert childSymbol != null : "There must be a mapping available for symbol " + s;
                mappedUsedColumns.add(childSymbol);
            });
        }
        FetchRewrite fetchRewrite = source.rewriteToFetch(mappedUsedColumns);
        if (fetchRewrite == null) {
            return null;
        }
        LogicalPlan newSource = fetchRewrite.newPlan();
        ArrayList<Symbol> newOutputs = new ArrayList<>();
        for (Symbol output : newSource.outputs()) {
            if (output instanceof FetchMarker) {
                FetchMarker marker = (FetchMarker) output;
                FetchMarker newMarker = new FetchMarker(name, marker.fetchRefs(), marker.fetchId());
                newOutputs.add(newMarker);
                childToParentMap.put(marker, newMarker);
            } else {
                Symbol mappedOutput = requireNonNull(
                    childToParentMap.get(output),
                    () -> "Mapping must exist for output from source. `" + output + "` is missing in " + childToParentMap
                );
                newOutputs.add(mappedOutput);
            }
        }
        LinkedHashMap<Symbol, Symbol> replacedOutputs = new LinkedHashMap<>();
        UnaryOperator<Symbol> convertChildrenToScopedSymbols = s -> MapBackedSymbolReplacer.convert(s, childToParentMap);
        for (var entry : fetchRewrite.replacedOutputs().entrySet()) {
            Symbol key = entry.getKey();
            Symbol value = entry.getValue();
            Symbol parentSymbolForKey = requireNonNull(
                childToParentMap.get(key),
                () -> "Mapping must exist for output from source. `" + key + "` is missing in " + childToParentMap
            );
            replacedOutputs.put(parentSymbolForKey, convertChildrenToScopedSymbols.apply(value));
        }
        Rename newRename = new Rename(newOutputs, name, fieldResolver, newSource);
        return new FetchRewrite(replacedOutputs, newRename);
    }

    @Override
    public ExecutionPlan build(DependencyCarrier executor,
                               PlannerContext plannerContext,
                               Set<PlanHint> hints,
                               ProjectionBuilder projectionBuilder,
                               int limit,
                               int offset,
                               @Nullable OrderBy order,
                               @Nullable Integer pageSizeHint,
                               Row params,
                               SubQueryResults subQueryResults) {
        return source.build(
            executor, plannerContext, hints, projectionBuilder, limit, offset, order, pageSizeHint, params, subQueryResults);
    }

    @Override
    public LogicalPlan replaceSources(List<LogicalPlan> sources) {
        return new Rename(outputs, name, fieldResolver, Lists.getOnlyElement(sources));
    }

    @Override
    public <C, R> R accept(LogicalPlanVisitor<C, R> visitor, C context) {
        return visitor.visitRename(this, context);
    }

    @Nullable
    @Override
    public Symbol resolveField(ScopedSymbol field) {
        return fieldResolver.resolveField(field);
    }

    @Override
    public List<RelationName> relationNames() {
        return List.of(name);
    }

    @Override
    public void print(PrintContext printContext) {
        printContext
            .text("Rename[")
            .text(Lists.joinOn(", ", outputs, Symbol::toString))
            .text("] AS ")
            .text(name.toString());
        printStats(printContext);
        printContext.nest(source::print);
    }

    @Override
    public String toString() {
        return "Rename{name=" + name + ", outputs=" + outputs + ", src=" + source + "}";
    }
}

        /* In `SELECT * FROM (SELECT t1.*, t2.* FROM tbl AS t1, tbl AS t2) AS tjoin`
         * The `ScopedSymbol`s are ambiguous; To map them correctly this uses a IdentityHashMap
         */
        IdentityHashMap<Symbol, Symbol> parentToChildMap = new IdentityHashMap<>(outputs.size());
        IdentityHashMap<Symbol, Symbol> childToParentMap = new IdentityHashMap<>(outputs.size());
        for (int i = 0; i < outputs.size(); i++) {
            parentToChildMap.put(outputs.get(i), source.outputs().get(i));
            childToParentMap.put(source.outputs().get(i), outputs.get(i));
        }
        ArrayList<Symbol> mappedToKeep = new ArrayList<>();
        for (Symbol outputToKeep : outputsToKeep) {
            SymbolVisitors.intersection(outputToKeep, outputs, s -> {
                Symbol childSymbol = parentToChildMap.get(s);
                assert childSymbol != null : "There must be a mapping available for symbol " + s;
                mappedToKeep.add(childSymbol);
            });
        }
        LogicalPlan newSource = source.pruneOutputsExcept(mappedToKeep);
        if (newSource == source) {
            return this;
        }
        ArrayList<Symbol> newOutputs = new ArrayList<>(newSource.outputs().size());
        for (Symbol sourceOutput : newSource.outputs()) {
            newOutputs.add(childToParentMap.get(sourceOutput));
        }
        return new Rename(
            newOutputs,
            name,
            fieldResolver,
            newSource
        );
    
<methods>public void <init>(io.crate.planner.operators.LogicalPlan) ,public Map<io.crate.planner.operators.LogicalPlan,io.crate.expression.symbol.SelectSymbol> dependencies() ,public List<io.crate.expression.symbol.Symbol> outputs() ,public io.crate.planner.operators.LogicalPlan pruneOutputsExcept(SequencedCollection<io.crate.expression.symbol.Symbol>) ,public List<io.crate.metadata.RelationName> relationNames() ,public io.crate.planner.operators.LogicalPlan source() ,public List<io.crate.planner.operators.LogicalPlan> sources() ,public boolean supportsDistributedReads() <variables>final non-sealed io.crate.planner.operators.LogicalPlan source,private final non-sealed List<io.crate.planner.operators.LogicalPlan> sources