class GraphQL::Query

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

Attributes

analysis_errors[RW]
context[R]
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[RW]
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: nil, max_complexity: nil, except: nil, only: 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 76
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: nil, max_complexity: nil, except: nil, only: 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)
  @subscription_topic = subscription_topic
  @root_value = root_value
  @fragments = nil
  @operations = nil
  @validate = validate
  # TODO: remove support for global tracers
  @tracers = schema.tracers + GraphQL::Tracing.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

  # 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

  @arguments_cache = ArgumentsCache.build(self)

  # 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 || schema.max_depth
  @max_complexity = max_complexity || schema.max_complexity

  @result_values = nil
  @executed = false

  # TODO add a general way to define schema-level filters
  # TODO also add this to schema dumps
  if @schema.respond_to?(:visible?)
    merge_filters(only: @schema.method(:visible?))
  end
end

Public Instance Methods

arguments_for(irep_or_ast_node, definition) click to toggle source

Node-level cache for calculating arguments. Used during execution and query analysis. @api private @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables

# File lib/graphql/query.rb, line 216
def arguments_for(irep_or_ast_node, definition)
  @arguments_cache[irep_or_ast_node][definition]
end
document() click to toggle source

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

# File lib/graphql/query.rb, line 46
def document
  with_prepared_ast { @document }
end
fragments() click to toggle source
# File lib/graphql/query.rb, line 155
def fragments
  with_prepared_ast { @fragments }
end
inspect() click to toggle source
# File lib/graphql/query.rb, line 50
def inspect
  "query ..."
end
irep_selection() click to toggle source
# File lib/graphql/query.rb, line 203
def irep_selection
  @selection ||= begin
    if selected_operation && internal_representation
      internal_representation.operation_definitions[selected_operation.name]
    else
      nil
    end
  end
end
merge_filters(only: nil, except: nil) click to toggle source

@return [void]

# File lib/graphql/query.rb, line 262
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 253
def mutation?
  with_prepared_ast { @mutation }
end
operations() click to toggle source
# File lib/graphql/query.rb, line 159
def operations
  with_prepared_ast { @operations }
end
query?() click to toggle source
# File lib/graphql/query.rb, line 257
def query?
  with_prepared_ast { @query }
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 241
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 165
def result
  if !@executed
    with_prepared_ast {
      Execution::Multiplex.run_queries(@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 143
def result_values=(result_hash)
  if @executed
    raise "Invariant: Can't reassign result"
  else
    @executed = true
    @result_values = result_hash
  end
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 181
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 55
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 174
def static_errors
  validation_errors + analysis_errors + context.errors
end
subscription?() click to toggle source
# File lib/graphql/query.rb, line 271
def subscription?
  with_prepared_ast { @subscription }
end
subscription_update?() click to toggle source
# File lib/graphql/query.rb, line 138
def subscription_update?
  @subscription_topic && subscription?
end
valid?() click to toggle source
# File lib/graphql/query.rb, line 227
def valid?
  validation_pipeline.valid? && analysis_errors.none?
end
validation_pipeline() click to toggle source
# File lib/graphql/query.rb, line 220
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 191
def variables
  @variables ||= begin
    with_prepared_ast {
      GraphQL::Query::Variables.new(
        @context,
        @ast_variables,
        @provided_variables,
      )
    }
  end
end
warden() click to toggle source
# File lib/graphql/query.rb, line 231
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 277
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 287
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,
    validate: @validate,
    parse_error: parse_error,
    operation_name_error: operation_name_error,
    max_depth: @max_depth,
    max_complexity: @max_complexity || schema.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 353
def with_prepared_ast
  if !@prepared_ast
    prepare_ast
  end
  yield
end