@Override
  public Result apply(ApplyNode applyNode, Captures captures, Context context) {
    if (applyNode.getSubqueryAssignments().size() != 1) {
      return Result.empty();
    }

    ApplyNode.SetExpression expression =
        getOnlyElement(applyNode.getSubqueryAssignments().values());
    if (!(expression instanceof ApplyNode.In)) {
      return Result.empty();
    }

    ApplyNode.In inPredicate = (ApplyNode.In) expression;

    Symbol semiJoinSymbol = getOnlyElement(applyNode.getSubqueryAssignments().keySet());

    SemiJoinNode replacement =
        new SemiJoinNode(
            context.getIdAllocator().genPlanNodeId(),
            applyNode.getInput(),
            applyNode.getSubquery(),
            inPredicate.getValue(),
            inPredicate.getReference(),
            semiJoinSymbol);

    return Result.ofPlanNode(replacement);
  }
This optimizers looks for InPredicate expressions in ApplyNodes and replaces the nodes with
SemiJoin nodes.

<p>Plan before optimizer:

<pre>
Filter(a IN b):
  Apply
    - correlation: []  // empty
    - input: some plan A producing symbol a
    - subquery: some plan B producing symbol b
</pre>

<p>Plan after optimizer:

<pre>
Filter(semijoinresult):
  SemiJoin
    - source: plan A
    - filteringSource: B
    - sourceJoinSymbol: symbol a
    - filteringSourceJoinSymbol: symbol b
    - semiJoinOutput: semijoinresult
</pre>
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/TransformUncorrelatedInPredicateSubqueryToSemiJoin.java