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","."]
code: def visit_select_statement_node(
      self, node: SqlSelectStatementNode
  ) -> SqlQueryPlanNode:  # noqa: D
      # Remove columns that are not needed from this SELECT statement because the parent SELECT statement doesn't
      # need them. However, keep columns that are in group bys because that changes the meaning of the query.
      # Similarly, if this node is a distinct select node, keep all columns as it may return a different result set.
      pruned_select_columns = tuple(
          select_column
          for select_column in node.select_columns
          if select_column.column_alias in self._required_column_aliases
          or select_column in node.group_bys
          or node.distinct
      )
  
      if len(pruned_select_columns) == 0:
          raise RuntimeError(
              "All columns have been pruned - this indicates an bug in the pruner or in the inputs."
          )
  
      # Based on the expressions in this select statement, figure out what column aliases are needed in the sources of
      # this query (i.e. tables or sub-queries in the FROM or JOIN clauses).
      exprs_used_in_this_node = self._search_for_expressions(
          node, pruned_select_columns
      )
  
      # If any of the string expressions don't have context on what columns are used in the expression, then it's
      # impossible to know what columns can be pruned from the parent sources. So return a SELECT statement that
      # leaves the parent sources untouched. Columns from the grandparents can be pruned based on the parent node
      # though.
      if any(
          [
              string_expr.used_columns is None
              for string_expr in exprs_used_in_this_node.string_exprs
          ]
      ):
          return self._prune_columns_from_grandparents(
              node, pruned_select_columns
          )
  
      # Create a mapping from the source alias to the column aliases needed from the corresponding source.
      source_alias_to_required_column_alias: Dict[
          str, Set[str]
      ] = defaultdict(set)
      for (
          column_reference_expr
      ) in exprs_used_in_this_node.column_reference_exprs:
          column_reference = column_reference_expr.col_ref
          source_alias_to_required_column_alias[
              column_reference.table_alias
          ].add(column_reference.column_name)
  
      # For all string columns, assume that they are needed from all sources since we don't have a table alias
      # in SqlStringExpression.used_columns
      for string_expr in exprs_used_in_this_node.string_exprs:
          if string_expr.used_columns:
              for column_alias in string_expr.used_columns:
                  source_alias_to_required_column_alias[
                      node.from_source_alias
                  ].add(column_alias)
                  for join_description in node.join_descs:
                      source_alias_to_required_column_alias[
                          join_description.right_source_alias
                      ].add(column_alias)
      # Same with unqualified column references.
      for (
          unqualified_column_reference_expr
      ) in exprs_used_in_this_node.column_alias_reference_exprs:
          column_alias = unqualified_column_reference_expr.column_alias
          source_alias_to_required_column_alias[
              node.from_source_alias
          ].add(column_alias)
          for join_description in node.join_descs:
              source_alias_to_required_column_alias[
                  join_description.right_source_alias
              ].add(column_alias)
  
      # Once we know which column aliases are required from which source aliases, replace the sources with new SELECT
      # statements.
      from_source_pruner = SqlColumnPrunerVisitor(
          required_column_aliases=source_alias_to_required_column_alias[
              node.from_source_alias
          ]
      )
      pruned_from_source = node.from_source.accept(from_source_pruner)
      pruned_join_descriptions: List[SqlJoinDescription] = []
      for join_description in node.join_descs:
          join_source_pruner = SqlColumnPrunerVisitor(
              required_column_aliases=source_alias_to_required_column_alias[
                  join_description.right_source_alias
              ]
          )
          pruned_join_descriptions.append(
              SqlJoinDescription(
                  right_source=join_description.right_source.accept(
                      join_source_pruner
                  ),
                  right_source_alias=join_description.right_source_alias,
                  on_condition=join_description.on_condition,
                  join_type=join_description.join_type,
              )
          )
  
      return SqlSelectStatementNode(
          description=node.description,
          select_columns=tuple(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 4 for calling the SqlColumnPrunerVisitor obj's underlying member method code with example usage: obj.visit_select_statement_node(node) and returns: SqlSelectStatementNode, self