@Override
  public Result apply(FilterNode filterNode, Captures captures, Context context) {
    SemiJoinNode semiJoin = captures.get(SEMI_JOIN);

    Symbol semiJoinSymbol = semiJoin.getSemiJoinOutput();
    Predicate<Expression> isSemiJoinSymbol =
        expression -> expression.equals(semiJoinSymbol.toSymbolReference());

    List<Expression> conjuncts = extractConjuncts(filterNode.getPredicate());
    if (conjuncts.stream().noneMatch(isSemiJoinSymbol)) {
      return Result.empty();
    }
    Expression filteredPredicate =
        and(conjuncts.stream().filter(isSemiJoinSymbol.negate()).collect(toImmutableList()));

    Expression simplifiedPredicate =
        inlineSymbols(
            symbol -> {
              if (symbol.equals(semiJoinSymbol)) {
                return TRUE_LITERAL;
              }
              return symbol.toSymbolReference();
            },
            filteredPredicate);

    Optional<Expression> joinFilter =
        simplifiedPredicate.equals(TRUE_LITERAL)
            ? Optional.empty()
            : Optional.of(simplifiedPredicate);

    PlanNode filteringSourceDistinct =
        singleAggregation(
            context.getIdAllocator().genPlanNodeId(),
            semiJoin.getFilteringSource(),
            ImmutableMap.of(),
            singleGroupingSet(ImmutableList.of(semiJoin.getFilteringSourceJoinSymbol())));

    JoinNode innerJoin =
        new JoinNode(
            semiJoin.getPlanNodeId(),
            INNER,
            semiJoin.getSource(),
            filteringSourceDistinct,
            ImmutableList.of(
                new JoinNode.EquiJoinClause(
                    semiJoin.getSourceJoinSymbol(), semiJoin.getFilteringSourceJoinSymbol())),
            Optional.empty(),
            semiJoin.getSource().getOutputSymbols(),
            ImmutableList.of(),
            joinFilter,
            Optional.empty());

    ProjectNode project =
        new ProjectNode(
            context.getIdAllocator().genPlanNodeId(),
            innerJoin,
            Assignments.builder()
                .putIdentities(innerJoin.getOutputSymbols())
                .put(semiJoinSymbol, TRUE_LITERAL)
                .build());

    return Result.ofPlanNode(project);
  }
Rewrite filtering semi-join to inner join.

<p>Transforms:

<pre>
- Filter (semiJoinSymbol AND predicate)
   - SemiJoin (semiJoinSymbol <- (a IN b))
       source: plan A producing symbol a
       filtering source: plan B producing symbol b
</pre>

<p>Into:

<pre>
- Project (semiJoinSymbol <- TRUE)
   - Join INNER on (a = b), joinFilter (predicate with semiJoinSymbol replaced with TRUE)
      - source
      - Aggregation distinct(b)
         - filtering source
</pre>
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/TransformFilteringSemiJoinToInnerJoin.java