class Optimizer {

    private final List<Rule<?>> rules;
    private final Supplier<Version> minNodeVersionInCluster;
    private final NodeContext nodeCtx;

    public Optimizer(NodeContext nodeCtx,
                     Supplier<Version> minNodeVersionInCluster,
                     List<Rule<?>> rules) {
        this.rules = rules;
        this.minNodeVersionInCluster = minNodeVersionInCluster;
        this.nodeCtx = nodeCtx;
    }

    public LogicalPlan optimize(LogicalPlan plan,
                                PlanStats planStats,
                                CoordinatorTxnCtx txnCtx,
                                OptimizerTracer tracer) {
        var applicableRules = removeExcludedRules(rules, txnCtx.sessionSettings().excludedOptimizerRules());
        LogicalPlan optimizedRoot = tryApplyRules(applicableRules, plan, planStats, txnCtx, tracer);
        var optimizedSources = Lists.mapIfChange(optimizedRoot.sources(), x -> optimize(x, planStats, txnCtx, tracer));
        return tryApplyRules(
            applicableRules,
            optimizedSources == optimizedRoot.sources() ? optimizedRoot : optimizedRoot.replaceSources(optimizedSources),
            planStats,
            txnCtx,
            tracer
        );
    }

    public static List<Rule<?>> removeExcludedRules(List<Rule<?>> rules, Set<Class<? extends Rule<?>>> excludedRules) {
        if (excludedRules.isEmpty()) {
            return rules;
        }
        var result = new ArrayList<Rule<?>>(rules.size());
        for (var rule : rules) {
            if (rule.mandatory() == false &&
                excludedRules.contains(rule.getClass())) {
                continue;
            }
            result.add(rule);
        }
        return result;
    }

    private LogicalPlan tryApplyRules(List<Rule<?>> rules,
                                      LogicalPlan plan,
                                      PlanStats planStats,
                                      TransactionContext txnCtx,
                                      OptimizerTracer tracer) {<FILL_FUNCTION_BODY>}

    @Nullable
    public static <T> LogicalPlan tryMatchAndApply(Rule<T> rule,
                                                   LogicalPlan node,
                                                   PlanStats planStats,
                                                   NodeContext nodeCtx,
                                                   TransactionContext txnCtx,
                                                   UnaryOperator<LogicalPlan> resolvePlan,
                                                   OptimizerTracer tracer) {
        Match<T> match = rule.pattern().accept(node, Captures.empty(), resolvePlan);
        if (match.isPresent()) {
            tracer.ruleMatched(rule);
            return rule.apply(match.value(), match.captures(), planStats, txnCtx, nodeCtx, resolvePlan);
        }
        return null;
    }
}

        LogicalPlan node = plan;
        // Some rules may only become applicable after another rule triggered, so we keep
        // trying to re-apply the rules as long as at least one plan was transformed.
        boolean done = false;
        int numIterations = 0;
        UnaryOperator<LogicalPlan> resolvePlan = UnaryOperator.identity();
        Version minVersion = minNodeVersionInCluster.get();
        while (!done && numIterations < 10_000) {
            done = true;
            for (Rule<?> rule : rules) {
                if (minVersion.before(rule.requiredVersion())) {
                    continue;
                }
                LogicalPlan transformedPlan = tryMatchAndApply(rule, node, planStats, nodeCtx, txnCtx, resolvePlan, tracer);
                if (transformedPlan != null) {
                    tracer.ruleApplied(rule, transformedPlan, planStats);
                    node = transformedPlan;
                    done = false;
                }
            }
            numIterations++;
        }
        assert numIterations < 10_000
            : "Optimizer reached 10_000 iterations safety guard. This is an indication of a broken rule that matches again and again";
        return node;
    
<no_super_class>