private PlanNode planProject(PlanNode resultNode, RewriterContext context) {
      FilterNode pushDownFilterNode = context.getPushDownFilterNode();
      if (resultNode instanceof TransformNode) {
        // transform contains project
        return resultNode;
      }

      if (context.isBuildPlanUseTemplate()) {
        return new ProjectNode(context.genPlanNodeId(), resultNode, null);
      }

      if (context.isAlignByDevice()
          || (pushDownFilterNode.getOutputColumnNames().size()
              != pushDownFilterNode.getChild().getOutputColumnNames().size())) {
        return new ProjectNode(
            context.genPlanNodeId(), resultNode, pushDownFilterNode.getOutputColumnNames());
      }
      return resultNode;
    }
ProjectNode is used to project the output columns of the child node.

<p>There are two cases where ProjectNode is used:
<li>Because of the removal of the FilterNode (FilterAndProjectOperator), we need a
    ProjectNode to do the projection.
<li>For ALIGN_BY_DEVICE query, the ProjectNode is used to ensure the order of the output
    columns is consistent with before optimization (required by TreeMergeSortOperator).
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/optimization/PredicatePushDown.java