Class: Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Node

Inherits:
Object
  • Object
show all
Extended by:
Utils::StrongMemoize
Defined in:
lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb

Overview

The Node class allows us to traverse PgQuery nodes with tree like semantics.

This class balances convenience and performance. The PgQuery nodes are Google::Protobuf::MessageExts which contain a dynamic set of attributes known as fields. Accessing these fields can cause performance problems due to the large volume of iterable fields.

When possible use #dig over the descendant methods.

The filter available to each method reduces the traversed attributes. The default filter only traverses nodes required to parse for set operator mismatches.

Constant Summary collapse

DEFAULT_NODES =

The default nodes help speed up traversal. Traversal of other nodes can greatly affect performance.

i[
  a_star
  alias
  args
  column_ref
  fields
  func_call
  join_expr
  larg
  range_subselect
  range_var
  rarg
  res_target
  subquery
  val
].freeze
DEFAULT_FIELD_FILTER =
->(field) { field.is_a?(Integer) || DEFAULT_NODES.include?(field) }.freeze

Class Method Summary collapse

Class Method Details

.descendants(node, filter: DEFAULT_FIELD_FILTER, &blk) ⇒ Object

Recurse through children. The block will yield the child node and the name of that node. Calling without a block will return an Enumerator.



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb', line 43

def descendants(node, filter: DEFAULT_FIELD_FILTER, &blk)
  if blk
    children(node, filter: filter) do |child_node, child_field|
      yield(child_node, child_field)

      descendants(child_node, filter: filter, &blk)
    end
    nil
  else
    enum_for(:descendants, node, filter: filter, &blk)
  end
end

.dig(node, *attrs) ⇒ Object

Like Hash#dig, traverse attributes in sequential order and return the final value. Return nil if any of the fields are not available.



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb', line 68

def dig(node, *attrs)
  obj = node
  attrs.each do |attr|
    if obj.respond_to?(attr)
      obj = obj.public_send(attr) # rubocop:disable GitlabSecurity/PublicSend
    else
      obj = nil
      break
    end
  end
  obj
end

.locate_descendant(node, field, filter: DEFAULT_FIELD_FILTER) ⇒ Object

Return the first node that matches the field.



57
58
59
# File 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb', line 57

def locate_descendant(node, field, filter: DEFAULT_FIELD_FILTER)
  descendants(node, filter: filter).find { |_, child_field| child_field == field }&.first
end

.locate_descendants(node, field, filter: DEFAULT_FIELD_FILTER) ⇒ Object

Return all nodes that match the field.



62
63
64
# File 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node.rb', line 62

def locate_descendants(node, field, filter: DEFAULT_FIELD_FILTER)
  descendants(node, filter: filter).select { |_, child_field| child_field == field }.map(&:first)
end