class GraphQL::Query

A combination of query string and {Schema} instance which can be reduced to a {#result}.

Attributes

analysis_errors[RW]
context[R]
multiplex[RW]
operation_name[RW]

@return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run.

provided_variables[R]
query_string[W]
result_values[R]

@api private

root_value[RW]

The value for root types

schema[R]
subscription_topic[R]

@return [String, nil] the triggered event, if this query is a subscription update

tracers[R]
validate[RW]

@return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined)

Public Class Methods

new(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) click to toggle source

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 77
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 ||= {}
  @schema = schema
  @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

arguments_cache() click to toggle source
# File lib/graphql/query.rb, line 237
def arguments_cache
  @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
end
arguments_for(ast_node, definition, parent_object: nil) click to toggle source

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 233
def arguments_for(ast_node, definition, parent_object: nil)
  arguments_cache.fetch(ast_node, definition, parent_object)
end
document() click to toggle source

@return [GraphQL::Language::Nodes::Document]

# File lib/graphql/query.rb, line 42
def document
  # It's ok if this hasn't been assigned yet
  if @query_string || @document
    with_prepared_ast { @document }
  else
    nil
  end
end
executed?() click to toggle source
# File lib/graphql/query.rb, line 195
def executed?
  @executed
end
fingerprint() click to toggle source

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 263
def fingerprint
  @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}"
end
fragments() click to toggle source
# File lib/graphql/query.rb, line 178
def fragments
  with_prepared_ast { @fragments }
end
handle_or_reraise(err) click to toggle source

@api private

# File lib/graphql/query.rb, line 334
def handle_or_reraise(err)
  schema.handle_or_reraise(context, err)
end
inspect() click to toggle source
# File lib/graphql/query.rb, line 51
def inspect
  "query ..."
end
interpreter?() click to toggle source
# File lib/graphql/query.rb, line 145
def interpreter?
  true
end
lookahead() click to toggle source

A lookahead for the root selections of this query @return [GraphQL::Execution::Lookahead]

# File lib/graphql/query.rb, line 157
def lookahead
  @lookahead ||= begin
    ast_node = selected_operation
    root_type = warden.root_type_for_operation(ast_node.operation_type || "query")
    GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
  end
end
merge_filters(only: nil, except: nil) click to toggle source

@return [void]

# File lib/graphql/query.rb, line 320
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
mutation?() click to toggle source
# File lib/graphql/query.rb, line 311
def mutation?
  with_prepared_ast { @mutation }
end
operation_fingerprint() click to toggle source

@return [String] An opaque hash for identifying this query’s given query string and selected operation

# File lib/graphql/query.rb, line 268
def operation_fingerprint
  @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string)}"
end
operations() click to toggle source
# File lib/graphql/query.rb, line 182
def operations
  with_prepared_ast { @operations }
end
query?() click to toggle source
# File lib/graphql/query.rb, line 315
def query?
  with_prepared_ast { @query }
end
query_string() click to toggle source

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 141
def query_string
  @query_string ||= (document ? document.to_query_string : nil)
end
resolve_type(abstract_type, value = :__undefined__) click to toggle source

@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 299
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
result() click to toggle source

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 188
def result
  if !@executed
    Execution::Multiplex.run_all(@schema, [self], context: @context)
  end
  @result ||= Query::Result.new(query: self, values: @result_values)
end
result_values=(result_hash) click to toggle source

@api private

# File lib/graphql/query.rb, line 166
def result_values=(result_hash)
  if @executed
    raise "Invariant: Can't reassign result"
  else
    @executed = true
    @result_values = result_hash
  end
end
sanitized_query_string(inline_variables: true) click to toggle source

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 245
def sanitized_query_string(inline_variables: true)
  with_prepared_ast {
    schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string
  }
end
selected_operation() click to toggle source

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 206
def selected_operation
  with_prepared_ast { @selected_operation }
end
selected_operation_name() click to toggle source

@return [String, nil] The name of the operation to run (may be inferred)

# File lib/graphql/query.rb, line 56
def selected_operation_name
  return nil unless selected_operation
  selected_operation.name
end
static_errors() click to toggle source
# File lib/graphql/query.rb, line 199
def static_errors
  validation_errors + analysis_errors + context.errors
end
subscription?() click to toggle source
# File lib/graphql/query.rb, line 329
def subscription?
  with_prepared_ast { @subscription }
end
subscription_update?() click to toggle source
# File lib/graphql/query.rb, line 151
def subscription_update?
  @subscription_topic && subscription?
end
valid?() click to toggle source
# File lib/graphql/query.rb, line 285
def valid?
  validation_pipeline.valid? && analysis_errors.empty?
end
validation_pipeline() click to toggle source
# File lib/graphql/query.rb, line 277
def validation_pipeline
  with_prepared_ast { @validation_pipeline }
end
variables() click to toggle source

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 216
def variables
  @variables ||= begin
    with_prepared_ast {
      GraphQL::Query::Variables.new(
        @context,
        @ast_variables,
        @provided_variables,
      )
    }
  end
end
variables_fingerprint() click to toggle source

@return [String] An opaque hash for identifying this query’s given a variable values (not including defaults)

# File lib/graphql/query.rb, line 273
def variables_fingerprint
  @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}"
end
warden() click to toggle source
# File lib/graphql/query.rb, line 289
def warden
  with_prepared_ast { @warden }
end

Private Instance Methods

find_operation(operations, operation_name) click to toggle source
# File lib/graphql/query.rb, line 340
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
prepare_ast() click to toggle source
# File lib/graphql/query.rb, line 350
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
with_prepared_ast() { || ... } click to toggle source

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 414
def with_prepared_ast
  if !@prepared_ast
    prepare_ast
  end
  yield
end