class RDF::Query

An RDF basic graph pattern (BGP) query.

Named queries either match against a specifically named graph if the name is an RDF::Resource or bound RDF::Query::Variable. Names that are against unbound variables match either default or named graphs. The name of `false` will only match against the default graph.

Variable names cause the variable to be added to the solution set elements.

@example Constructing a basic graph pattern query (1)

query = RDF::Query.new do
  pattern [:person, RDF.type,  FOAF.Person]
  pattern [:person, FOAF.name, :name]
  pattern [:person, FOAF.mbox, :email]
end

@example Constructing a basic graph pattern query (2)

query = RDF::Query.new({
  person: {
    RDF.type  => FOAF.Person,
    FOAF.name => :name,
    FOAF.mbox => :email,
  }
})

@example Executing a basic graph pattern query

graph = RDF::Graph.load('etc/doap.nt')
query.execute(graph).each do |solution|
  puts solution.inspect
end

@example Constructing and executing a query in one go (1)

solutions = RDF::Query.execute(graph) do
  pattern [:person, RDF.type, FOAF.Person]
end

@example Constructing and executing a query in one go (2)

solutions = RDF::Query.execute(graph, {
  person: {
    RDF.type => FOAF.Person,
  }
})

@example In this example, the default graph contains the names of the publishers of two named graphs. The triples in the named graphs are not visible in the default graph in this example.

# default graph
@prefix dc: <http://purl.org/dc/elements/1.1/

<http://example.org/bob>    dc:publisher  "Bob" .
<http://example.org/alice>  dc:publisher  "Alice" .

# Named graph: http://example.org/bob
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

_:a foaf:name "Bob" .
_:a foaf:mbox <mailto:bob@oldcorp.example.org> .

# Named graph: http://example.org/alice
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

_:a foaf:name "Alice" .
_:a foaf:mbox <mailto:alice@work.example.org> .

@see www.w3.org/TR/rdf-sparql-query/#rdfDataset @since 0.3.0

Attributes

options[R]

Any additional options for this query.

@return [Hash]

patterns[R]

The patterns that constitute this query.

@return [Array<RDF::Query::Pattern>]

solutions[R]

The solution sequence for this query.

@return [RDF::Query::Solutions]

variables[R]

The variables used in this query.

@return [Hash{Symbol => RDF::Query::Variable}]

Public Class Methods

Solutions(*args) click to toggle source

Cast values as Solutions @overload Solutions()

@return [Solutions] returns Solutions.new()

@overload Solutions(solutions)

@return [Solutions] returns the argument

@overload Solutions(array)

@param [Array] array
@return [Solutions] returns the array extended with solutions

@overload Solutions(*args)

@param [Array<Solution>] args
@return [Solutions] returns new solutions including the arguments, which must each be a {Solution}
# File lib/rdf/query.rb, line 111
def self.Solutions(*args)
   if args.length == 1
    return args[0] if args[0].is_a?(Solutions)
    args = args[0] if args[0].is_a?(Array)
  end
  return Solutions.new(args)
end
execute(queryable, patterns = {}, options = {}, &block) click to toggle source

Executes a query on the given `queryable` graph or repository.

@param [RDF::Queryable] queryable

the graph or repository to query

@param [Hash{Object => Object}] patterns

optional hash patterns to initialize the query with

@param [Hash{Symbol => Object}] options

any additional keyword options (see {RDF::Query#initialize})

@yield [query] @yieldparam [RDF::Query] query @yieldreturn [void] ignored @return [RDF::Query::Solutions]

the resulting solution sequence

@see #execute

# File lib/rdf/query.rb, line 92
def self.execute(queryable, patterns = {}, options = {}, &block)
  self.new(patterns, options, &block).execute(queryable, options)
end
new(*patterns, &block) click to toggle source

Initializes a new basic graph pattern query.

@overload initialize(patterns = [], options = {})

@param  [Array<RDF::Query::Pattern>] patterns
  ...
@param  [Hash{Symbol => Object}] options
  any additional keyword options
@option options [RDF::Query::Solutions] :solutions (Solutions.new)
@option options [RDF::Resource, RDF::Query::Variable, false] :graph_name (nil)
  Default graph name for matching against queryable.
  Named queries either match against a specifically named
  graphs if the name is an {RDF::Resource} or bound {RDF::Query::Variable}.
  Names that are against unbound variables match either default
  or named graphs.
  The name of `false` will only match against the default graph.
@option options [RDF::Resource, RDF::Query::Variable, false] :name (nil)
  Alias for `:graph_name`.
@yield  [query]
@yieldparam  [RDF::Query] query
@yieldreturn [void] ignored

@overload initialize(patterns, options = {})

@param  [Hash{Object => Object}] patterns
  ...
@param  [Hash{Symbol => Object}] options
  any additional keyword options
@option options [RDF::Query::Solutions] :solutions (Solutions.new)
@option options [RDF::Resource, RDF::Query::Variable, false] :graph_name (nil)
  Default graph name for matching against queryable.
  Named queries either match against a specifically named
  graphs if the name is an {RDF::Resource} or bound {RDF::Query::Variable}.
  Names that are against unbound variables match either default
  or named graphs.
  The name of `false` will only match against the default graph.
@option options [RDF::Resource, RDF::Query::Variable, false] :name (nil)
  Alias for `:graph_name`.
@yield  [query]
@yieldparam  [RDF::Query] query
@yieldreturn [void] ignored
# File lib/rdf/query.rb, line 183
def initialize(*patterns, &block)
  @options  = patterns.last.is_a?(Hash) ? patterns.pop.dup : {}
  patterns << @options if patterns.empty?
  @variables = {}
  @solutions = Query::Solutions(@options.delete(:solutions))
  graph_name = @options.fetch(:graph_name, @options.fetch(:name, nil))
  @options.delete(:graph_name)
  @options.delete(:name)

  @patterns  = case patterns.first
    when Hash  then compile_hash_patterns(HashPatternNormalizer.normalize!(patterns.first.dup, @options))
    when Array then patterns.first
    else patterns
  end

  self.graph_name = graph_name

  if block_given?
    case block.arity
      when 1 then block.call(self)
      else instance_eval(&block)
    end
  end
end

Public Instance Methods

+(other) click to toggle source

Add patterns from another query to form a new Query @param [RDF::Query] other @return [RDF::Query]

# File lib/rdf/query.rb, line 403
def +(other)
  Query.new(self.patterns + other.patterns)
end
<<(pattern) click to toggle source

Appends the given query `pattern` to this query.

@param [RDF::Query::Pattern] pattern

a triple query pattern

@return [void] self

# File lib/rdf/query.rb, line 214
def <<(pattern)
  @patterns << Pattern.from(pattern)
  self
end
apply_graph_name(graph_name = options[:graph_name]) click to toggle source

Apply the graph name specified (or configured) to all patterns that have no graph name @param [RDF::IRI, RDF::Query::Variable] #graph_name (self.graph_name)

# File lib/rdf/query.rb, line 441
def apply_graph_name(graph_name = options[:graph_name])
  patterns.each {|pattern| pattern.graph_name = graph_name if pattern.graph_name.nil?} unless graph_name.nil?
end
default?() click to toggle source

Is this query scoped to the default graph? @return [Boolean]

# File lib/rdf/query.rb, line 415
def default?
  options[:graph_name] == false
end
dup() click to toggle source

Duplicate query, including patterns and solutions @return [RDF::Query]

# File lib/rdf/query.rb, line 494
def dup
  patterns = @patterns.map {|p| p.dup}
  patterns << @options.merge(solutions: @solutions.dup)
  Query.new(*patterns)
end
each(&block)
Alias for: each_solution
each_solution(&block) click to toggle source

Enumerates over each matching query solution.

@yield [solution] @yieldparam [RDF::Query::Solution] solution @return [Enumerator]

# File lib/rdf/query.rb, line 475
def each_solution(&block)
  @solutions.each(&block)
end
Also aliased as: each
each_statement(&block) click to toggle source

Enumerates over each statement (pattern).

@yield [RDF::Query::Pattern] @yieldparam [::Query::Pattern] pattern @return [Enumerator]

# File lib/rdf/query.rb, line 486
def each_statement(&block)
  apply_graph_name
  patterns.each(&block)
end
empty?() click to toggle source

Query has no patterns @return [Boolean]

# File lib/rdf/query.rb, line 464
def empty?
  patterns.empty?
end
execute(queryable, options = {}, &block) click to toggle source

Executes this query on the given `queryable` graph or repository.

Named queries either match against a specifically named graphs if the name is an RDF::Resource or bound RDF::Query::Variable. Names that are against unbound variables match either detault or named graphs. The name of `false` will only match against the default graph.

If the query nas no patterns, it returns a single empty solution as per SPARQL 1.1 _Empty Group Pattern_.

@note solutions could be an Iterator, but this algorithm cycles over solutions, which requires them to be an array internally.

@param [RDF::Queryable] queryable

the graph or repository to query

@param [Hash{Symbol => Object}] options

any additional keyword options

@option options [Hash{Symbol => RDF::Term}] bindings

optional variable bindings to use

@option options [RDF::Resource, RDF::Query::Variable, false] #graph_name (nil)

Specific graph name for matching against queryable;
overrides default graph defined on query.

@option options [RDF::Resource, RDF::Query::Variable, false] name (nil)

Alias for `:graph_name`.

@option options [RDF::Query::Solutions] solutions

optional initial solutions for chained queries

@yield [solution]

each matching solution

@yieldparam [RDF::Query::Solution] solution @yieldreturn [void] ignored @return [RDF::Query::Solutions]

the resulting solution sequence

@see www.holygoat.co.uk/blog/entry/2005-10-25-1 @see www.w3.org/TR/sparql11-query/#emptyGroupPattern

# File lib/rdf/query.rb, line 296
def execute(queryable, options = {}, &block)
  validate!
  options = options.dup

  # just so we can call #keys below without worrying
  options[:bindings] ||= {}

  # Use provided solutions to allow for query chaining
  # Otherwise, a quick empty solution simplifies the logic below; no special case for
  # the first pattern
  @solutions = Query::Solutions(options[:solutions] || Solution.new)

  # If there are no patterns, just return the empty solution
  if empty?
    @solutions.each(&block) if block_given?
    return @solutions
  end

  patterns = @patterns
  graph_name = options.fetch(:graph_name, options.fetch(:name, self.graph_name))

  # Add graph_name to pattern, if necessary
  unless graph_name.nil?
    if patterns.empty?
      patterns = [Pattern.new(nil, nil, nil, graph_name: graph_name)]
    else
      apply_graph_name(graph_name)
    end
  end

  patterns.each do |pattern|

    old_solutions, @solutions = @solutions, Query::Solutions()

    options[:bindings].each_key do |variable|
      if pattern.variables.include?(variable)
        unbound_solutions, old_solutions = old_solutions, Query::Solutions()
        options[:bindings][variable].each do |binding|
          unbound_solutions.each do |solution|
            old_solutions << solution.merge(variable => binding)
          end
        end
        options[:bindings].delete(variable)
      end
    end

    old_solutions.each do |solution|
      found_match = false
      pattern.execute(queryable, solution) do |statement|
        found_match = true
        @solutions << solution.merge(pattern.solution(statement))
      end
      # If this pattern was optional, and we didn't find any matches,
      # just copy it over as-is.
      if !found_match && pattern.optional?
        @solutions << solution
      end
    end

    #puts "solutions after #{pattern} are #{@solutions.to_a.inspect}"

    # It's important to abort failed queries quickly because later patterns
    # that can have constraints are often broad without them.
    # We have no solutions at all:
    return @solutions if @solutions.empty?

    if !pattern.optional?
      # We have no solutions for variables we should have solutions for:
      need_vars = pattern.variables.keys
      @solutions.each do |solution|
        break if need_vars.empty?
        need_vars -= solution.bindings.keys
      end
      return Query::Solutions() unless need_vars.empty?
    end
  end
  @solutions.each(&block) if block_given?
  @solutions
end
failed?() click to toggle source

Returns `true` if this query did not match when last executed.

When the solution sequence is empty, this method can be used to determine whether the query failed to match or not.

@return [Boolean] @see matched?

# File lib/rdf/query.rb, line 384
def failed?
  @solutions.empty?
end
graph_name() click to toggle source

Scope of this query, if any @return [RDF::IRI, RDF::Query::Variable]

# File lib/rdf/query.rb, line 435
def graph_name
  options[:graph_name]
end
graph_name=(value) click to toggle source

Scope the query to named graphs matching value @param [RDF::IRI, RDF::Query::Variable] value @return [RDF::IRI, RDF::Query::Variable]

# File lib/rdf/query.rb, line 429
def graph_name=(value)
  options[:graph_name] = value
end
has_blank_nodes?()
Alias for: node?
matched?() click to toggle source

Returns `true` if this query matched when last executed.

When the solution sequence is empty, this method can be used to determine whether the query matched successfully or not.

@return [Boolean] @see failed?

# File lib/rdf/query.rb, line 396
def matched?
  !failed?
end
named?() click to toggle source

Is this query scoped to a named graph? @return [Boolean]

# File lib/rdf/query.rb, line 409
def named?
  !!options[:graph_name]
end
node?() click to toggle source

Returns `true` if any pattern contains a blank node.

@return [Boolean] @since 2.0

# File lib/rdf/query.rb, line 458
def node?
  patterns.any?(&:node?) || graph_name && graph_name.node?
end
Also aliased as: has_blank_nodes?
optimize(options = {}) click to toggle source

Returns an optimized copy of this query.

@param [Hash{Symbol => Object}] options

any additional options for optimization

@return [RDF::Query] a copy of `self` @since 0.3.0

# File lib/rdf/query.rb, line 241
def optimize(options = {})
  self.dup.optimize!(options)
end
optimize!(options = {}) click to toggle source

Optimizes this query by reordering its constituent triple patterns according to their cost estimates.

@param [Hash{Symbol => Object}] options

any additional options for optimization

@return [self] @see RDF::Query::Pattern#cost @since 0.3.0

# File lib/rdf/query.rb, line 254
def optimize!(options = {})
  @patterns.sort! do |a, b|
    (a.cost || 0) <=> (b.cost || 0)
  end
  self
end
pattern(pattern, options = {}) click to toggle source

Appends the given query `pattern` to this query.

@param [RDF::Query::Pattern] pattern

a triple query pattern

@param [Hash{Symbol => Object}] options

any additional keyword options

@option options [Boolean] :optional (false)

whether this is an optional pattern

@return [void] self

# File lib/rdf/query.rb, line 229
def pattern(pattern, options = {})
  @patterns << Pattern.from(pattern, options)
  self
end
unnamed?() click to toggle source

Is this query unscoped? This indicates that it can return results from either a named graph or the default graph. @return [Boolean]

# File lib/rdf/query.rb, line 422
def unnamed?
  options[:graph_name].nil?
end
valid?() click to toggle source

Determine if the URI is a valid according to RFC3987

@return [Boolean] `true` or `false` @since 0.3.9

# File lib/rdf/query.rb, line 505
def valid?
  !!validate!
rescue
  false
end
validate!() click to toggle source

Validate this query, making sure it can be executed by our query engine. This method is public so that it may be called by implementations of RDF::Queryable#query_execute that bypass our built-in query engine.

@return [RDF::Query] `self` @raise [ArgumentError] This query cannot be executed.

# File lib/rdf/query.rb, line 518
def validate!
  # All patterns must be valid
  @patterns.each(&:validate!)

  # All optional patterns must appear after the regular patterns.
  if i = @patterns.find_index(&:optional?)
    unless @patterns[i..-1].all?(&:optional?)
      raise ArgumentError.new("Optional patterns must appear at end of query")
    end
  end

  self
end
variable?() click to toggle source

Returns `true` if any pattern contains a variable.

@return [Boolean]

# File lib/rdf/query.rb, line 449
def variable?
  patterns.any?(&:variable?) || graph_name && graph_name.variable?
end

Protected Instance Methods

compile_hash_patterns(hash_patterns) click to toggle source

@private

# File lib/rdf/query.rb, line 536
def compile_hash_patterns(hash_patterns)
  patterns = []
  hash_patterns.each do |s, pos|
    raise ArgumentError, "invalid hash pattern: #{hash_patterns.inspect}" unless pos.is_a?(Hash)
    pos.each do |p, os|
      case os
        when Hash
          patterns += os.keys.map { |o| [s, p, o] }
          patterns += compile_hash_patterns(os)
        when Array
          patterns += os.map { |o| [s, p, o] }
        else
          patterns << [s, p, os]
      end
    end
  end
  patterns.map { |pattern| Pattern.from(pattern) }
end