class GraphQL::Query
A combination of query string and {Schema} instance which can be reduced to a {#result}.
Attributes
@return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run.
@api private
The value for root types
@return [String, nil] the triggered event, if this query is a subscription update
@return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined)
Public Class Methods
Prepare query `query_string` on `schema` @param schema [GraphQL::Schema] @param query_string
[String] @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve} @param variables [Hash] values for `$variables` in the query @param operation_name
[String] if the query string contains many operations, this is the one which should be executed @param root_value
[Object] the object used to resolve fields on the root type @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value) @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value) @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
# File lib/graphql/query.rb, line 82 def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} # Use the `.graphql_definition` here which will return legacy types instead of classes if schema.is_a?(Class) && !schema.interpreter? schema = schema.graphql_definition end @schema = schema @interpreter = @schema.interpreter? @filter = schema.default_filter.merge(except: except, only: only) @context = schema.context_class.new(query: self, object: root_value, values: context) @warden = warden @subscription_topic = subscription_topic @root_value = root_value @fragments = nil @operations = nil @validate = validate @tracers = schema.tracers + (context ? context.fetch(:tracers, []) : []) # Support `ctx[:backtrace] = true` for wrapping backtraces if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @analysis_errors = [] if variables.is_a?(String) raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables." else @provided_variables = variables || {} end @query_string = query_string || query @document = document if @query_string && @document raise ArgumentError, "Query should only be provided a query string or a document, not both." end if @query_string && !@query_string.is_a?(String) raise ArgumentError, "Query string argument should be a String, got #{@query_string.class.name} instead." end # A two-layer cache of type resolution: # { abstract_type => { value => resolved_type } } @resolved_types_cache = Hash.new do |h1, k1| h1[k1] = Hash.new do |h2, k2| h2[k2] = @schema.resolve_type(k1, k2, @context) end end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @operation_name = operation_name @prepared_ast = false @validation_pipeline = nil @max_depth = max_depth @max_complexity = max_complexity @result_values = nil @executed = false # TODO add a general way to define schema-level filters if @schema.respond_to?(:visible?) merge_filters(only: @schema.method(:visible?)) end end
Public Instance Methods
# File lib/graphql/query.rb, line 263 def arguments_cache if interpreter? @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self) else @arguments_cache ||= ArgumentsCache.build(self) end end
Node-level cache for calculating arguments. Used during execution and query analysis. @param ast_node [GraphQL::Language::Nodes::AbstractNode] @param definition [GraphQL::Schema::Field] @param parent_object [GraphQL::Schema::Object] @return Hash{Symbol => Object}
# File lib/graphql/query.rb, line 255 def arguments_for(ast_node, definition, parent_object: nil) if interpreter? arguments_cache.fetch(ast_node, definition, parent_object) else arguments_cache[ast_node][definition] end end
@return [GraphQL::Language::Nodes::Document]
# File lib/graphql/query.rb, line 47 def document # It's ok if this hasn't been assigned yet if @query_string || @document with_prepared_ast { @document } else nil end end
# File lib/graphql/query.rb, line 207 def executed? @executed end
This contains a few components:
-
The selected operation name (or `anonymous`)
-
The fingerprint of the query string
-
The number of given variables (for readability)
-
The fingerprint of the given variables
This fingerprint can be used to track runs of the same operation-variables combination over time.
@see operation_fingerprint
@see variables_fingerprint
@return [String] An opaque hash identifying this operation-variables combination
# File lib/graphql/query.rb, line 293 def fingerprint @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}" end
# File lib/graphql/query.rb, line 190 def fragments with_prepared_ast { @fragments } end
# File lib/graphql/query.rb, line 56 def inspect "query ..." end
# File lib/graphql/query.rb, line 156 def interpreter? @interpreter end
# File lib/graphql/query.rb, line 240 def irep_selection @selection ||= begin if selected_operation && internal_representation internal_representation.operation_definitions[selected_operation.name] else nil end end end
A lookahead for the root selections of this query @return [GraphQL::Execution::Lookahead]
# File lib/graphql/query.rb, line 168 def lookahead @lookahead ||= begin ast_node = selected_operation root_type = warden.root_type_for_operation(ast_node.operation_type || "query") root_type = root_type.type_class || raise("Invariant: `lookahead` only works with class-based types") GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node]) end end
@return [void]
# File lib/graphql/query.rb, line 350 def merge_filters(only: nil, except: nil) if @prepared_ast raise "Can't add filters after preparing the query" else @filter = @filter.merge(only: only, except: except) end nil end
# File lib/graphql/query.rb, line 341 def mutation? with_prepared_ast { @mutation } end
@return [String] An opaque hash for identifying this query's given query string and selected operation
# File lib/graphql/query.rb, line 298 def operation_fingerprint @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string)}" end
# File lib/graphql/query.rb, line 194 def operations with_prepared_ast { @operations } end
# File lib/graphql/query.rb, line 345 def query? with_prepared_ast { @query } end
If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document
# File lib/graphql/query.rb, line 152 def query_string @query_string ||= (document ? document.to_query_string : nil) end
@param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType] @param value [Object] Any runtime value @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type} @see {#possible_types} to apply filtering from `only` / `except`
# File lib/graphql/query.rb, line 329 def resolve_type(abstract_type, value = :__undefined__) if value.is_a?(Symbol) && value == :__undefined__ # Old method signature value = abstract_type abstract_type = nil end if value.is_a?(GraphQL::Schema::Object) value = value.object end @resolved_types_cache[abstract_type][value] end
Get the result for this query, executing it once @return [Hash] A GraphQL
response, with `“data”` and/or `“errors”` keys
# File lib/graphql/query.rb, line 200 def result if !@executed Execution::Multiplex.run_queries(@schema, [self], context: @context) end @result ||= Query::Result.new(query: self, values: @result_values) end
@api private
# File lib/graphql/query.rb, line 178 def result_values=(result_hash) if @executed raise "Invariant: Can't reassign result" else @executed = true @result_values = result_hash end end
A version of the given query string, with:
-
Variables
inlined to the query -
Strings replaced with `<REDACTED>`
@return [String, nil] Returns nil if the query is invalid.
# File lib/graphql/query.rb, line 275 def sanitized_query_string(inline_variables: true) with_prepared_ast { schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string } end
This is the operation to run for this query. If more than one operation is present, it must be named at runtime. @return [GraphQL::Language::Nodes::OperationDefinition, nil]
# File lib/graphql/query.rb, line 218 def selected_operation with_prepared_ast { @selected_operation } end
@return [String, nil] The name of the operation to run (may be inferred)
# File lib/graphql/query.rb, line 61 def selected_operation_name return nil unless selected_operation selected_operation.name end
# File lib/graphql/query.rb, line 211 def static_errors validation_errors + analysis_errors + context.errors end
# File lib/graphql/query.rb, line 359 def subscription? with_prepared_ast { @subscription } end
# File lib/graphql/query.rb, line 162 def subscription_update? @subscription_topic && subscription? end
# File lib/graphql/query.rb, line 315 def valid? validation_pipeline.valid? && analysis_errors.empty? end
# File lib/graphql/query.rb, line 307 def validation_pipeline with_prepared_ast { @validation_pipeline } end
Determine the values for variables of this query, using default values if a value isn't provided at runtime.
If some variable is invalid, errors are added to {#validation_errors}.
@return [GraphQL::Query::Variables] Variables
to apply to this query
# File lib/graphql/query.rb, line 228 def variables @variables ||= begin with_prepared_ast { GraphQL::Query::Variables.new( @context, @ast_variables, @provided_variables, ) } end end
@return [String] An opaque hash for identifying this query's given a variable values (not including defaults)
# File lib/graphql/query.rb, line 303 def variables_fingerprint @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}" end
# File lib/graphql/query.rb, line 319 def warden with_prepared_ast { @warden } end
@api private
# File lib/graphql/query.rb, line 364 def with_error_handling schema.error_handler.with_error_handling(context) do yield end end
Private Instance Methods
# File lib/graphql/query.rb, line 372 def find_operation(operations, operation_name) if operation_name.nil? && operations.length == 1 operations.values.first elsif !operations.key?(operation_name) nil else operations.fetch(operation_name) end end
# File lib/graphql/query.rb, line 382 def prepare_ast @prepared_ast = true @warden ||= GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context) parse_error = nil @document ||= begin if query_string GraphQL.parse(query_string, tracer: self) end rescue GraphQL::ParseError => err parse_error = err @schema.parse_error(err, @context) nil end @fragments = {} @operations = {} if @document @document.definitions.each do |part| case part when GraphQL::Language::Nodes::FragmentDefinition @fragments[part.name] = part when GraphQL::Language::Nodes::OperationDefinition @operations[part.name] = part end end elsif parse_error # This will be handled later else parse_error = GraphQL::ExecutionError.new("No query string was present") @context.add_error(parse_error) end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @subscription = false operation_name_error = nil if @operations.any? @selected_operation = find_operation(@operations, @operation_name) if @selected_operation.nil? operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name) else if @operation_name.nil? @operation_name = @selected_operation.name end @ast_variables = @selected_operation.variables @mutation = @selected_operation.operation_type == "mutation" @query = @selected_operation.operation_type == "query" @subscription = @selected_operation.operation_type == "subscription" end end @validation_pipeline = GraphQL::Query::ValidationPipeline.new( query: self, parse_error: parse_error, operation_name_error: operation_name_error, max_depth: @max_depth, max_complexity: @max_complexity ) end
Since the query string is processed at the last possible moment, any internal values which depend on it should be accessed within this wrapper.
# File lib/graphql/query.rb, line 446 def with_prepared_ast if !@prepared_ast prepare_ast end yield end