code: def project_dataframe(df, targets, table_columns):
      """
      case-insensitive projection
      'select A' and 'select a' return different column case but with the same content
      """
  
      columns = []
      df_cols_idx = {col.lower(): col for col in df.columns}
      df_col_rename = {}
  
      for target in targets:
          if isinstance(target, ast.Star):
              for col in table_columns:
                  col_df = df_cols_idx.get(col.lower())
                  if col_df is not None:
                      df_col_rename[col_df] = col
                  columns.append(col)
  
              break
          elif isinstance(target, ast.Identifier):
              col = target.parts[-1]
              col_df = df_cols_idx.get(col.lower())
              if col_df is not None:
                  if hasattr(target, "alias") and isinstance(
                      target.alias, ast.Identifier
                  ):
                      df_col_rename[col_df] = target.alias.parts[0]
                  else:
                      df_col_rename[col_df] = col
              columns.append(col)
          else:
              raise NotImplementedError
  
      if len(df) == 0:
          df = pd.DataFrame([], columns=columns)
      else:
          # add absent columns
          for col in set(columns) & set(df.columns) ^ set(columns):
              df[col] = None
  
          # filter by columns
          df = df[columns]
  
      # adapt column names to projection
      if len(df_col_rename) > 0:
          df = df.rename(columns=df_col_rename)
      return df
  
file_path: mindsdb/mindsdb/integrations/utilities/sql_utils.py
imports: import pandas
  from mindsdb.api.executor.utilities.sql import query_df
  from mindsdb_sql.parser import ast
  from mindsdb_sql.parser.ast.base import ASTNode
  from mindsdb_sql.planner.utils import query_traversal
label_desc: Use image node_id 4 for calling a global function with example usage: project_dataframe(df, targets, table_columns) and returns: df