Class: RDF::Query

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/rdf/query.rb,
lib/rdf/query/pattern.rb,
lib/rdf/query/solution.rb,
lib/rdf/query/variable.rb,
lib/rdf/query/solutions.rb,
lib/rdf/query/hash_pattern_normalizer.rb

Overview

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.

Examples:

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

Constructing a basic graph pattern query (2)

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

Executing a basic graph pattern query

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

Constructing and executing a query in one go (1)

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

Constructing and executing a query in one go (2)

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

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 Also:

Since:

  • 0.3.0

Defined Under Namespace

Classes: HashPatternNormalizer, Pattern, Solution, Solutions, Variable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#dump, #each_graph, #each_object, #each_predicate, #each_quad, #each_subject, #each_term, #each_triple, #enum_graph, #enum_object, #enum_predicate, #enum_quad, #enum_statement, #enum_subject, #enum_term, #enum_triple, #graph_names, #has_graph?, #has_object?, #has_predicate?, #has_quad?, #has_statement?, #has_subject?, #has_term?, #has_triple?, #invalid?, #method_missing, #objects, #predicates, #project_graph, #quads, #respond_to_missing?, #statements, #subjects, #supports?, #terms, #to_a, #to_hash, #to_set, #triples

Methods included from Util::Aliasing::LateBound

#alias_method

Methods included from Countable

#count

Constructor Details

#initialize(patterns = [], options = {}) {|query| ... } ⇒ Query #initialize(patterns, options = {}) {|query| ... } ⇒ Query

Initializes a new basic graph pattern query.

Overloads:

  • #initialize(patterns = [], options = {}) {|query| ... } ⇒ Query

    Options Hash (options):

    • :solutions (RDF::Query::Solutions) — default: Solutions.new
    • :graph_name (RDF::Resource, RDF::Query::Variable, false) — default: nil

      Default graph name for matching against queryable. Named queries either match against a specifically named graphs if the name is an Resource or bound Variable. Names that are against unbound variables match either default or named graphs. The name of false will only match against the default graph.

    • :name (RDF::Resource, RDF::Query::Variable, false) — default: nil

      Alias for :graph_name.

    Yields:

    • (query)

    Yield Parameters:

    Yield Returns:

    • (void)

      ignored

  • #initialize(patterns, options = {}) {|query| ... } ⇒ Query

    Options Hash (options):

    • :solutions (RDF::Query::Solutions) — default: Solutions.new
    • :graph_name (RDF::Resource, RDF::Query::Variable, false) — default: nil

      Default graph name for matching against queryable. Named queries either match against a specifically named graphs if the name is an Resource or bound Variable. Names that are against unbound variables match either default or named graphs. The name of false will only match against the default graph.

    • :name (RDF::Resource, RDF::Query::Variable, false) — default: nil

      Alias for :graph_name.

    Yields:

    • (query)

    Yield Parameters:

    Yield Returns:

    • (void)

      ignored

Since:

  • 0.3.0



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class RDF::Enumerable

Instance Attribute Details

#optionsHash (readonly)

Any additional options for this query.

Since:

  • 0.3.0



141
142
143
# File 'lib/rdf/query.rb', line 141

def options
  @options
end

#patternsArray<RDF::Query::Pattern> (readonly)

The patterns that constitute this query.

Since:

  • 0.3.0



129
130
131
# File 'lib/rdf/query.rb', line 129

def patterns
  @patterns
end

#solutionsRDF::Query::Solutions (readonly)

The solution sequence for this query.

Since:

  • 0.3.0



135
136
137
# File 'lib/rdf/query.rb', line 135

def solutions
  @solutions
end

#variablesHash{Symbol => RDF::Query::Variable} (readonly)

The variables used in this query.

Since:

  • 0.3.0



123
124
125
# File 'lib/rdf/query.rb', line 123

def variables
  @variables
end

Class Method Details

.execute(queryable, patterns = {}, options = {}) {|query| ... } ⇒ RDF::Query::Solutions

Executes a query on the given queryable graph or repository.

Yields:

  • (query)

Yield Parameters:

Yield Returns:

  • (void)

    ignored

See Also:

Since:

  • 0.3.0



92
93
94
# File 'lib/rdf/query.rb', line 92

def self.execute(queryable, patterns = {}, options = {}, &block)
  self.new(patterns, options, &block).execute(queryable, options)
end

.SolutionsSolutions .Solutions(solutions) ⇒ Solutions .Solutions(array) ⇒ Solutions .Solutions(*args) ⇒ Solutions

Cast values as Solutions

Since:

  • 0.3.0



111
112
113
114
115
116
117
# 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

Instance Method Details

#+(other) ⇒ RDF::Query

Add patterns from another query to form a new Query

Since:

  • 0.3.0



403
404
405
# File 'lib/rdf/query.rb', line 403

def +(other)
  Query.new(self.patterns + other.patterns)
end

#<<(pattern)

This method returns an undefined value.

Appends the given query pattern to this query.

Since:

  • 0.3.0



214
215
216
217
# File 'lib/rdf/query.rb', line 214

def <<(pattern)
  @patterns << Pattern.from(pattern)
  self
end

#apply_graph_name(graph_name = options[:graph_name]) ⇒ Object

Apply the graph name specified (or configured) to all patterns that have no graph name

Since:

  • 0.3.0



441
442
443
# 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?Boolean

Is this query scoped to the default graph?

Since:

  • 0.3.0



415
416
417
# File 'lib/rdf/query.rb', line 415

def default?
  options[:graph_name] == false
end

#dupRDF::Query

Duplicate query, including patterns and solutions

Since:

  • 0.3.0



494
495
496
497
498
# 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_solution {|solution| ... } ⇒ Enumerator Also known as: each

Enumerates over each matching query solution.

Yields:

  • (solution)

Yield Parameters:

Since:

  • 0.3.0



475
476
477
# File 'lib/rdf/query.rb', line 475

def each_solution(&block)
  @solutions.each(&block)
end

#each_statement {|RDF::Query::Pattern| ... } ⇒ Enumerator

Enumerates over each statement (pattern).

Yields:

Yield Parameters:

Since:

  • 0.3.0



486
487
488
489
# File 'lib/rdf/query.rb', line 486

def each_statement(&block)
  apply_graph_name
  patterns.each(&block)
end

#empty?Boolean

Query has no patterns

Since:

  • 0.3.0



464
465
466
# File 'lib/rdf/query.rb', line 464

def empty?
  patterns.empty?
end

#execute(queryable, options = {}) {|solution| ... } ⇒ RDF::Query::Solutions

Note:

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

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.

Options Hash (options):

Yields:

  • (solution)

    each matching solution

Yield Parameters:

Yield Returns:

  • (void)

    ignored

See Also:

Since:

  • 0.3.0



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# 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?Boolean

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.

See Also:

Since:

  • 0.3.0



384
385
386
# File 'lib/rdf/query.rb', line 384

def failed?
  @solutions.empty?
end

#graph_nameRDF::IRI, RDF::Query::Variable

Scope of this query, if any

Since:

  • 0.3.0



435
436
437
# File 'lib/rdf/query.rb', line 435

def graph_name
  options[:graph_name]
end

#graph_name=(value) ⇒ RDF::IRI, RDF::Query::Variable

Scope the query to named graphs matching value

Since:

  • 0.3.0



429
430
431
# File 'lib/rdf/query.rb', line 429

def graph_name=(value)
  options[:graph_name] = value
end

#matched?Boolean

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.

See Also:

Since:

  • 0.3.0



396
397
398
# File 'lib/rdf/query.rb', line 396

def matched?
  !failed?
end

#named?Boolean

Is this query scoped to a named graph?

Since:

  • 0.3.0



409
410
411
# File 'lib/rdf/query.rb', line 409

def named?
  !!options[:graph_name]
end

#node?Boolean Also known as: has_blank_nodes?

Returns true if any pattern contains a blank node.

Since:

  • 2.0



458
459
460
# File 'lib/rdf/query.rb', line 458

def node?
  patterns.any?(&:node?) || graph_name && graph_name.node?
end

#optimize(options = {}) ⇒ RDF::Query

Returns an optimized copy of this query.

Since:

  • 0.3.0



241
242
243
# File 'lib/rdf/query.rb', line 241

def optimize(options = {})
  self.dup.optimize!(options)
end

#optimize!(options = {}) ⇒ self

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

See Also:

Since:

  • 0.3.0



254
255
256
257
258
259
# 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 = {})

This method returns an undefined value.

Appends the given query pattern to this query.

Options Hash (options):

  • :optional (Boolean) — default: false

    whether this is an optional pattern

Since:

  • 0.3.0



229
230
231
232
# File 'lib/rdf/query.rb', line 229

def pattern(pattern, options = {})
  @patterns << Pattern.from(pattern, options)
  self
end

#unnamed?Boolean

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

Since:

  • 0.3.0



422
423
424
# File 'lib/rdf/query.rb', line 422

def unnamed?
  options[:graph_name].nil?
end

#valid?Boolean

Determine if the URI is a valid according to RFC3987

Since:

  • 0.3.9



505
506
507
508
509
# File 'lib/rdf/query.rb', line 505

def valid?
  !!validate!
rescue
  false
end

#validate!RDF::Query

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.

Raises:

  • (ArgumentError)

    This query cannot be executed.

Since:

  • 0.3.0



518
519
520
521
522
523
524
525
526
527
528
529
530
# 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?Boolean

Returns true if any pattern contains a variable.

Since:

  • 0.3.0



449
450
451
# File 'lib/rdf/query.rb', line 449

def variable?
  patterns.any?(&:variable?) || graph_name && graph_name.variable?
end