Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions lib/graphql/analysis/query_complexity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ def initialize(query)
super
@skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
@complexities_on_type_by_query = {}
@intersect_cache = Hash.new { |h, k| h[k] = {}.compare_by_identity }.compare_by_identity
@possible_types_cache = {}.compare_by_identity
end

# Override this method to use the complexity result
Expand Down Expand Up @@ -159,8 +161,22 @@ def merged_max_complexity_for_scopes(query, scopes, mode)

def types_intersect?(query, a, b)
return true if a == b
a_types = query.types.possible_types(a)
query.types.possible_types(b).any? { |t| a_types.include?(t) }

if a.object_id < b.object_id
first_cache = @intersect_cache[a]
second_key = b
else
first_cache = @intersect_cache[b]
second_key = a
end

if first_cache.key?(second_key)
first_cache[second_key]
else
a_types = @possible_types_cache[a] ||= query.types.possible_types(a).to_set
b_types = @possible_types_cache[b] ||= query.types.possible_types(b).to_set
first_cache[second_key] = a_types.intersect?(b_types)
end
end

# A hook which is called whenever a field's max complexity is calculated.
Expand All @@ -175,18 +191,16 @@ def field_complexity(scoped_type_complexity, max_complexity:, child_complexity:
# @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
# @return [Integer] Total complexity value for all these selections in the parent scope
def merged_max_complexity(query, inner_selections)
# Aggregate a set of all unique field selection keys across all scopes.
# Use a hash, but ignore the values; it's just a fast way to work with the keys.
unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
memo.merge!(inner_selection)
child_scopes_by_key = {}
inner_selections.each do |inner_selection|
inner_selection.each do |k, v|
scopes = child_scopes_by_key[k] ||= []
scopes << v
end
end

# Add up the total cost for each unique field name's coalesced selections
unique_field_keys.each_key.reduce(0) do |total, field_key|
# Collect all child scopes for this field key;
# all keys come with at least one scope.
child_scopes = inner_selections.filter_map { _1[field_key] }

total = 0
child_scopes_by_key.each do |field_key, child_scopes|
# Compute maximum possible cost of child selections;
# composites merge their maximums, while leaf scopes are always zero.
# FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
Expand Down Expand Up @@ -214,8 +228,10 @@ def merged_max_complexity(query, inner_selections)
child_complexity: maximum_children_cost,
)

total + maximum_cost
total += maximum_cost
end

total
end

def legacy_merged_max_complexity(query, inner_selections)
Expand Down