Module: GraphQL::Analysis::AST::QueryComplexity::ComplexityMergeFunctions

Defined in:
lib/graphql/analysis/ast/query_complexity.rb

Overview

These functions use ScopedTypeComplexity objects, especially their scoped_children, to traverse down the tree and find the max complexity for any possible runtime type. Yowza.

Class Method Summary collapse

Class Method Details

.applies_to?(query, left_scope, right_scope) ⇒ Boolean

When looking at two selection scopes, figure out whether the selections on right_scope should be applied when analyzing left_scope. This is like the Typecast.subtype?, except it’s using query-specific type filtering.

Returns:

  • (Boolean)


111
112
113
114
115
116
117
118
119
120
121
# File 'lib/graphql/analysis/ast/query_complexity.rb', line 111

def applies_to?(query, left_scope, right_scope)
  if left_scope == right_scope
    # This can happen when several branches are being analyzed together
    true
  else
    # Check if these two scopes have _any_ types in common.
    possible_right_types = query.possible_types(right_scope)
    possible_left_types = query.possible_types(left_scope)
    !(possible_right_types & possible_left_types).empty?
  end
end

.merged_max_complexity(query, children_for_scope) ⇒ Integer

Returns Complexity value for all these selections in the current scope.

Parameters:

  • children_for_scope (Array<Hash>)

    An array of scoped_children[scope] hashes ({field_key => complexity})

Returns:

  • (Integer)

    Complexity value for all these selections in the current scope



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/graphql/analysis/ast/query_complexity.rb', line 181

def merged_max_complexity(query, children_for_scope)
  all_keys = []
  children_for_scope.each do |c|
    all_keys.concat(c.keys)
  end
  all_keys.uniq!
  complexity_for_keys = {}
  all_keys.each do |child_key|

    scoped_children_for_key = nil
    complexity_for_key = nil
    children_for_scope.each do |children_hash|
      if children_hash.key?(child_key)
        complexity_for_key = children_hash[child_key]
        if complexity_for_key.terminal?
          # Assume that all terminals would return the same complexity
          # Since it's a terminal, its child complexity is zero.
          complexity_for_key = complexity_for_key.own_complexity(0)
          complexity_for_keys[child_key] = complexity_for_key
        else
          scoped_children_for_key ||= []
          scoped_children_for_key << complexity_for_key.scoped_children
        end
      end
    end

    if scoped_children_for_key
      child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
      # This is the _last_ one we visited; assume it's representative.
      max_complexity = complexity_for_key.own_complexity(child_complexity)
      complexity_for_keys[child_key] = max_complexity
    end
  end

  # Calculate the child complexity by summing the complexity of all selections
  complexity_for_keys.each_value.inject(0, &:+)
end

.merged_max_complexity_for_scopes(query, scoped_children_hashes) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/graphql/analysis/ast/query_complexity.rb', line 123

def merged_max_complexity_for_scopes(query, scoped_children_hashes)
  # Figure out what scopes are possible here.
  # Use a hash, but ignore the values; it's just a fast way to work with the keys.
  all_scopes = {}
  scoped_children_hashes.each do |h|
    all_scopes.merge!(h)
  end

  # If an abstract scope is present, but _all_ of its concrete types
  # are also in the list, remove it from the list of scopes to check,
  # because every possible type is covered by a concrete type.
  # (That is, there are no remainder types to check.)
  #
  # TODO redocument
  prev_keys = all_scopes.keys
  prev_keys.each do |scope|
    if scope.kind.abstract?
      missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
      # This concrete type is possible _only_ as a member of the abstract type.
      # So, attribute to it the complexity which belongs to the abstract type.
      missing_concrete_types.each do |concrete_scope|
        all_scopes[concrete_scope] = all_scopes[scope]
      end
      all_scopes.delete(scope)
    end
  end

  # This will hold `{ type => int }` pairs, one for each possible branch
  complexity_by_scope = {}

  # For each scope,
  # find the lexical selections that might apply to it,
  # and gather them together into an array.
  # Then, treat the set of selection hashes
  # as a set and calculate the complexity for them as a unit
  all_scopes.each do |scope, _|
    # These will be the selections on `scope`
    children_for_scope = []
    scoped_children_hashes.each do |sc_h|
      sc_h.each do |inner_scope, children_hash|
        if applies_to?(query, scope, inner_scope)
          children_for_scope << children_hash
        end
      end
    end

    # Calculate the complexity for `scope`, merging all
    # possible lexical branches.
    complexity_value = merged_max_complexity(query, children_for_scope)
    complexity_by_scope[scope] = complexity_value
  end

  # Return the max complexity among all scopes
  complexity_by_scope.each_value.max
end