@Override
    public PlanAndMappings visitSemiJoin(SemiJoinNode node, UnaliasContext context) {
      // it is assumed that symbols are distinct between SemiJoin source and filtering source. Only
      // symbols from outer correlation might be the exception
      PlanAndMappings rewrittenSource = node.getSource().accept(this, context);
      PlanAndMappings rewrittenFilteringSource = node.getFilteringSource().accept(this, context);

      Map<Symbol, Symbol> outputMapping = new HashMap<>();
      outputMapping.putAll(rewrittenSource.getMappings());
      outputMapping.putAll(rewrittenFilteringSource.getMappings());

      SymbolMapper mapper = symbolMapper(outputMapping);

      Symbol newSourceJoinSymbol = mapper.map(node.getSourceJoinSymbol());
      Symbol newFilteringSourceJoinSymbol = mapper.map(node.getFilteringSourceJoinSymbol());
      Symbol newSemiJoinOutput = mapper.map(node.getSemiJoinOutput());

      return new PlanAndMappings(
          new SemiJoinNode(
              node.getPlanNodeId(),
              rewrittenSource.getRoot(),
              rewrittenFilteringSource.getRoot(),
              newSourceJoinSymbol,
              newFilteringSourceJoinSymbol,
              newSemiJoinOutput),
          outputMapping);
    }
Replace all symbols in the plan with new symbols. The returned plan has different output than
the original plan. Also, the order of symbols might change during symbol replacement. Symbols
in the list `fields` are replaced maintaining the order so they might be used to match original
symbols with their replacements. Replacing symbols helps avoid collisions when symbols or parts
of the plan are reused.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java