@Override
    public PlanAndMappings visitProject(ProjectNode node, UnaliasContext context) {
      PlanAndMappings rewrittenSource = node.getChild().accept(this, context);

      // Assignment of a form `s -> x` establishes new semantics for symbol s.
      // It is possible though that symbol `s` is present in the source plan, and represents the
      // same or different semantics.
      // As a consequence, any symbol mapping derived from the source plan involving symbol `s`
      // becomes potentially invalid,
      // e.g. `s -> y` or `y -> s` refer to the old semantics of symbol `s`.
      // In such case, the underlying mappings are only used to map projection assignments' values
      // to ensure consistency with the source plan.
      // They aren't used to map projection outputs to avoid:
      // - errors from duplicate assignments
      // - incorrect results from mixed semantics of symbols
      // Also, the underlying mappings aren't passed up the plan, and new mappings aren't derived
      // from projection assignments
      // (with the exception for "deduplicating" mappings for repeated assignments).
      // This can be thought of as a "cut-off" at the point of potentially changed semantics.
      // Note: the issue of ambiguous symbols does not apply to symbols involved in context
      // (correlation) mapping.
      // Those symbols are supposed to represent constant semantics throughout the plan.

      Assignments assignments = node.getAssignments();
      Set<Symbol> newlyAssignedSymbols =
          assignments.filter(output -> !assignments.isIdentity(output)).getSymbols();
      Set<Symbol> symbolsInSourceMapping =
          ImmutableSet.<Symbol>builder()
              .addAll(rewrittenSource.getMappings().keySet())
              .addAll(rewrittenSource.getMappings().values())
              .build();
      Set<Symbol> symbolsInCorrelationMapping =
          ImmutableSet.<Symbol>builder()
              .addAll(context.getCorrelationMapping().keySet())
              .addAll(context.getCorrelationMapping().values())
              .build();
      boolean ambiguousSymbolsPresent =
          !Sets.intersection(
                  newlyAssignedSymbols,
                  Sets.difference(symbolsInSourceMapping, symbolsInCorrelationMapping))
              .isEmpty();

      Map<Symbol, Symbol> mapping = new HashMap<>(rewrittenSource.getMappings());
      SymbolMapper mapper = symbolMapper(mapping);

      // canonicalize ProjectNode assignments
      ImmutableList.Builder<Map.Entry<Symbol, Expression>> rewrittenAssignments =
          ImmutableList.builder();
      for (Map.Entry<Symbol, Expression> assignment : node.getAssignments().entrySet()) {
        rewrittenAssignments.add(
            new SimpleEntry<>(
                ambiguousSymbolsPresent ? assignment.getKey() : mapper.map(assignment.getKey()),
                mapper.map(assignment.getValue())));
      }

      // deduplicate assignments
      Map<Symbol, Expression> deduplicateAssignments =
          rewrittenAssignments.build().stream()
              .distinct()
              .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));

      // derive new mappings for ProjectNode output symbols
      Map<Symbol, Symbol> newMapping =
          mappingFromAssignments(deduplicateAssignments, ambiguousSymbolsPresent);

      Map<Symbol, Symbol> outputMapping = new HashMap<>();
      outputMapping.putAll(ambiguousSymbolsPresent ? context.getCorrelationMapping() : mapping);
      outputMapping.putAll(newMapping);

      mapper = symbolMapper(outputMapping);

      // build new Assignments with canonical outputs
      // duplicate entries will be removed by the Builder
      Assignments.Builder newAssignments = Assignments.builder();
      for (Map.Entry<Symbol, Expression> assignment : deduplicateAssignments.entrySet()) {
        newAssignments.put(mapper.map(assignment.getKey()), assignment.getValue());
      }

      return new PlanAndMappings(
          new ProjectNode(node.getPlanNodeId(), rewrittenSource.getRoot(), newAssignments.build()),
          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