class_docstr: Removes unnecessary columns from SELECT statements in the SQL query plan.
  
  As the visitor traverses up to the parents, it pushes the list of required columns and rewrites the parent nodes.
class_docstr_tok: ["Removes","unnecessary","columns","from","SELECT","statements","in","the","SQL","query","plan",".","As","the","visitor","traverses","up","to","the","parents",",","it","pushes","the","list","of","required","columns","and","rewrites","the","parent","nodes","."]
docstr: Assume that you need all columns from the parent and prune the grandparents.
docstr_tok: ["Assume","that","you","need","all","columns","from","the","parent","and","prune","the","grandparents","."]
code: def _prune_columns_from_grandparents(
      self,
      node: SqlSelectStatementNode,
      pruned_select_columns: Tuple[SqlSelectColumn, ...],
  ) -> SqlSelectStatementNode:
      """Assume that you need all columns from the parent and prune the grandparents."""
      pruned_from_source: SqlQueryPlanNode
      if node.from_source.as_select_node:
          from_visitor = SqlColumnPrunerVisitor(
              required_column_aliases={
                  x.column_alias
                  for x in node.from_source.as_select_node.select_columns
              }
          )
          pruned_from_source = node.from_source.as_select_node.accept(
              from_visitor
          )
      else:
          pruned_from_source = node.from_source
      pruned_join_descriptions: List[SqlJoinDescription] = []
      for join_description in node.join_descs:
          right_source_as_select_node = (
              join_description.right_source.as_select_node
          )
          if right_source_as_select_node:
              right_source_visitor = SqlColumnPrunerVisitor(
                  required_column_aliases={
                      x.column_alias
                      for x in right_source_as_select_node.select_columns
                  }
              )
              pruned_join_descriptions.append(
                  SqlJoinDescription(
                      right_source=join_description.right_source.accept(
                          right_source_visitor
                      ),
                      right_source_alias=join_description.right_source_alias,
                      on_condition=join_description.on_condition,
                      join_type=join_description.join_type,
                  )
              )
          else:
              pruned_join_descriptions.append(join_description)
  
      return SqlSelectStatementNode(
          description=node.description,
          select_columns=pruned_select_columns,
          from_source=pruned_from_source,
          from_source_alias=node.from_source_alias,
          joins_descs=tuple(pruned_join_descriptions),
          group_bys=node.group_bys,
          order_bys=node.order_bys,
          where=node.where,
          limit=node.limit,
          distinct=node.distinct,
      )
  
file_path: metricflow/metricflow/sql/optimizer/column_pruner.py
imports: from __future__ import annotations
  import logging
  from collections import defaultdict
  from typing import Dict, List, Set, Tuple
  from metricflow.sql.optimizer.sql_query_plan_optimizer import SqlQueryPlanOptimizer
  from metricflow.sql.sql_exprs import SqlExpressionTreeLineage
  from metricflow.sql.sql_plan import SqlJoinDescription, SqlQueryPlanNode, SqlQueryPlanNodeVisitor, SqlSelectColumn, SqlSelectQueryFromClauseNode, SqlSelectStatementNode, SqlTableFromClauseNode
label_desc: Use image node_id 3 for calling the SqlColumnPrunerVisitor obj's underlying member method code with example usage: obj._prune_columns_from_grandparents(node, pruned_select_columns) and returns: SqlSelectStatementNode