Method: PgQuery::ParserResult#filter_columns

Defined in:
lib/pg_query/filter_columns.rb

#filter_columnsObject

Returns a list of columns that the query filters by - this excludes the target list, but includes things like JOIN condition and WHERE clause.

Note: This also traverses into sub-selects.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/pg_query/filter_columns.rb', line 7

def filter_columns # rubocop:disable Metrics/CyclomaticComplexity
  load_objects! if @aliases.nil?

  # Get condition items from the parsetree
  statements = @tree.stmts.dup.to_a.map(&:stmt)
  condition_items = []
  filter_columns = []
  loop do
    statement = statements.shift
    if statement
      case statement.node
      when :list
        statements += statement.list.items
      when :raw_stmt
        statements << statement.raw_stmt.stmt
      when :select_stmt
        case statement.select_stmt.op
        when :SETOP_NONE
          if statement.select_stmt.from_clause
            # FROM subselects
            statement.select_stmt.from_clause.each do |item|
              next unless item['RangeSubselect']
              statements << item['RangeSubselect']['subquery']
            end

            # JOIN ON conditions
            condition_items += conditions_from_join_clauses(statement.select_stmt.from_clause)
          end

          # WHERE clause
          condition_items << statement.select_stmt.where_clause if statement.select_stmt.where_clause

          # CTEs
          if statement.select_stmt.with_clause
            statement.select_stmt.with_clause.ctes.each do |item|
              statements << item.common_table_expr.ctequery if item.node == :common_table_expr
            end
          end
        when :SETOP_UNION, :SETOP_EXCEPT, :SETOP_INTERSECT
          statements << PgQuery::Node.new(select_stmt: statement.select_stmt.larg) if statement.select_stmt.larg
          statements << PgQuery::Node.new(select_stmt: statement.select_stmt.rarg) if statement.select_stmt.rarg
        end
      when :update_stmt
        condition_items << statement.update_stmt.where_clause if statement.update_stmt.where_clause
      when :delete_stmt
        condition_items << statement.delete_stmt.where_clause if statement.delete_stmt.where_clause
      when :index_stmt
        condition_items << statement.index_stmt.where_clause if statement.index_stmt.where_clause
      end
    end

    # Process both JOIN and WHERE conditions here
    next_item = condition_items.shift
    if next_item
      case next_item.node
      when :a_expr
        condition_items << next_item.a_expr.lexpr if next_item.a_expr.lexpr
        condition_items << next_item.a_expr.rexpr if next_item.a_expr.rexpr
      when :bool_expr
        condition_items += next_item.bool_expr.args
      when :coalesce_expr
        condition_items += next_item.coalesce_expr.args
      when :row_expr
        condition_items += next_item.row_expr.args
      when :column_ref
        column, table = next_item.column_ref.fields.map { |f| f.string.sval }.reverse
        filter_columns << [@aliases[table] || table, column]
      when :null_test
        condition_items << next_item.null_test.arg
      when :boolean_test
        condition_items << next_item.boolean_test.arg
      when :func_call
        # FIXME: This should actually be extracted as a funccall and be compared with those indices
        condition_items += next_item.func_call.args if next_item.func_call.args
      when :sub_link
        condition_items << next_item.sub_link.testexpr
        statements << next_item.sub_link.subselect
      end
    end

    break if statements.empty? && condition_items.empty?
  end

  filter_columns.uniq
end