QueryComplexity analyzer may have IO side-effect
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 42/100
Hướng nghiên cứu
Start with lib/graphql/schema/field.rb around the connection complexity calculation and the GraphQL::Analysis::QueryComplexity entry point. Run the reproduction script with ActiveRecord logging enabled, then trace how argument_cache is accessed during analysis. Done means complexity analysis reads needed arguments statically without triggering the loads: lookup or another database call.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
Discussed in https://github.com/rmosolgo/graphql-ruby/pull/4800#issuecomment-2684939962
The GraphQL::Analysis::QueryComplexity may have IO side-effects (eg making a DB query via active record), while it would be expected to be fully static (as mentioned in the doc "None of GraphQL-Ruby’s validators make IO calls").
This happens because the field complexity calculation for connections (in https://github.com/rmosolgo/graphql-ruby/blob/v2.4.10/lib/graphql/schema/field.rb#L504) calls the argument_cache to read the arguments (to read static arguments like :first), which fully resolves the arguments. This is a problem for example when an argument uses loads: to get an ActiveRecord object from id. This makes using the validate_timeout feature for analysis unsafe.
Versions
graphql version: 2.4.10
Script to reproduce
(took inspiration from the script in https://github.com/rmosolgo/graphql-ruby/issues/5036#issuecomment-2298473769)
require 'bundler/inline'
gemfile do
gem 'graphql'
gem 'sqlite3'
gem 'activerecord', require: 'active_record'
end
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Schema.define do
create_table :mentions do |t|
t.string(:content)
t.belongs_to :ticket
end
create_table :tickets do |t|
t.string(:subject)
end
end
class Mention < ActiveRecord::Base; end
class Ticket < ActiveRecord::Base
has_many :mentions
end
ticket = Ticket.create!(subject: "Ticket 1")
Mention.create!(content: "Mention 1", ticket_id: ticket.id)
class MySchema < GraphQL::Schema
class Mention < GraphQL::Schema::Object
field :content, String
end
class Ticket < GraphQL::Schema::Object
field :subject, String
field :mentions, Mention.connection_type
def mentions(obj)
obj.mentions
end
end
class Query < GraphQL::Schema::Object
field :mentions, Mention.connection_type do
argument :ticket_id, ID, loads: Ticket
end
def mentions(ticket:)
ticket.mentions
end
end
default_page_size 50
max_complexity 1000
query(Query)
def self.object_from_id(id, ctx)
::Ticket.find(id)
end
def self.resolve_type(abs_t, obj, ctx)
Ticket
end
end
query_str = <<~GRAPHQL
query queryOne($ticketId: ID!, $first: Int!) {
mentions(ticketId: $ticketId, first: $first) {
nodes { content }
}
}
GRAPHQL
ActiveRecord::Base.logger = Logger.new(STDOUT) # Logs ActiveRecord SQL queries
query = GraphQL::Query.new(MySchema, query_str, variables: { ticketId: '1', first: 10 })
pp GraphQL::Analysis.analyze_query(query, [GraphQL::Analysis::QueryComplexity])
# D, [2025-02-28T16:55:50.009374 #66030] DEBUG -- : Ticket Load (0.2ms) SELECT "tickets".* FROM "tickets" WHERE "tickets"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
# [12]
pp query.arguments_cache.instance_variable_get(:@storage).values[0].values[0].values[0]
# #<GraphQL::Execution::Interpreter::Arguments @keyword_arguments={:first=>10, :ticket=>#<Ticket id: 1, subject: "Ticket 1">}>
Expected behavior
No DB call should be made, analyzer should be fully static. Arguments that are needed for the complexity calculation should be read statically.
Actual behavior
We can see a DB call made through ActiveRecord
D, [2025-02-28T16:55:50.009374 #66030] DEBUG -- : Ticket Load (0.2ms) SELECT "tickets".* FROM "tickets" WHERE "tickets"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
And can see the argument resolved in the arguments_cache
#<GraphQL::Execution::Interpreter::Arguments @keyword_arguments={:first=>10, :ticket=>#<Ticket id: 1, subject: "Ticket 1">}>
- Ngôn ngữ chính
- Ruby
- Star
- 5.4k
- Fork
- 1.4k
- Merge trung bình
- 1 ngày 8 giờ
- Pull request đã merge (30 ngày)
- 74
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của rmosolgo/graphql-ruby
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
rmosolgo/graphql-ruby#5843 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
rmosolgo/graphql-ruby#5763 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
rmosolgo/graphql-ruby#5761 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 52/100
rmosolgo/graphql-ruby#5707 · 10 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 30/100
rmosolgo/graphql-ruby#5655 · 4 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của rmosolgo/graphql-ruby
Issue tương tự
-
data error
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
riscv/riscv-unified-db#2794 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
heartcombo/simple_form#1874 ·
-
Bug: Reports Monthly breakdown shows the whole first month when a custom range starts mid-monthĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
we-promise/sure#4603 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Missing zh-CN translation: components/contributing:contributorsList.categorySwitcher.authoringĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày