Module: RDF::Queryable

Includes:
Enumerable
Included in:
Dataset, Enumerable::Enumerator, Graph, MergeGraph, Enumerator, Transaction
Defined in:
vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb,
vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/extensions.rb,
vendor/bundler/ruby/3.3.0/bundler/gems/sparql-36baa432eb7f/lib/sparql/extensions.rb,
vendor/bundler/ruby/3.3.0/bundler/gems/sparql-36baa432eb7f/lib/sparql/algebra/extensions.rb

Overview

Override RDF::Queryable to execute against SPARQL::Algebra::Query elements as well as RDF::Query and RDF::Pattern

Defined Under Namespace

Classes: Enumerator

Instance Method Summary collapse

Instance Method Details

#concise_bounded_description(*terms, **options, &block) ⇒ RDF::Graph

Concise Bounded Description

Given a particular node (the starting node) in a particular RDF graph (the source graph), a subgraph of that particular graph, taken to comprise a concise bounded description of the resource denoted by the starting node, can be identified as follows:

  1. Include in the subgraph all statements in the source graph where the subject of the statement is the starting node;
  2. Recursively, for all statements identified in the subgraph thus far having a blank node object, include in the subgraph all statements in the source graph where the subject of the statement is the blank node in question and which are not already included in the subgraph.
  3. Recursively, for all statements included in the subgraph thus far, for all reifications of each statement in the source graph, include the concise bounded description beginning from the rdf:Statement node of each reification. (we skip this step)

This results in a subgraph where the object nodes are either URI references, literals, or blank nodes not serving as the subject of any statement in the graph.

Used to implement the SPARQL DESCRIBE operator.

Parameters:

Options Hash (**options):

  • :non_subjects (Boolean) — default: false

    If term is not a subject within self then add all subjects referencing the term as a predicate or object.

  • graph (RDF::Graph)

    Graph containing statements already considered.

Yields:

  • (statement)

Yield Parameters:

Yield Returns:

  • (void)

    ignored

Returns:

See Also:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/sparql-36baa432eb7f/lib/sparql/extensions.rb', line 43

def concise_bounded_description(*terms, **options, &block)
  graph = options[:graph] || RDF::Graph.new

  if options[:non_subjects]
    query_terms = terms.dup

    # Find terms not in self as a subject and recurse with their subjects
    terms.reject {|term| self.first({subject: term})}.each do |term|
      self.query({predicate: term}) do |statement|
        query_terms << statement.subject
      end

      self.query({object: term}) do |statement|
        query_terms << statement.subject
      end
    end
    
    terms = query_terms.uniq
  end

  # Don't consider term if already in graph
  terms.reject {|term| graph.first({subject: term})}.each do |term|
    # Find statements from queryiable with term as a subject
    self.query({subject: term}) do |statement|
      yield(statement) if block_given?
      graph << statement
      
      # Include reifications of this statement
      RDF::Query.new({
        s: {
          RDF.type => RDF["Statement"],
          RDF.subject => statement.subject,
          RDF.predicate => statement.predicate,
          RDF.object => statement.object,
        }
      }, **{}).execute(self).each do |solution|
        # Recurse to include this subject
        recurse_opts = options.merge(non_subjects: false, graph: graph)
        self.concise_bounded_description(solution[:s], **recurse_opts, &block)
      end

      # Recurse if object is a BNode and it is not already in subjects
      if statement.object.node?
        recurse_opts = options.merge(non_subjects: false, graph: graph)
        self.concise_bounded_description(statement.object, **recurse_opts, &block)
      end
    end
  end
  
  graph
end

#firstRDF::Statement #first(pattern) ⇒ RDF::Statement

Queries self for an RDF statement matching the given pattern and returns that statement if found.

Returns nil if no statements match pattern.

Overloads:

Returns:

Since:

  • 0.1.9



188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 188

def first(pattern = nil)
  if pattern
    query(pattern) do |statement|
      return statement
    end
  elsif respond_to?(:each_statement)
    each_statement do |statement|
      return statement
    end
  else
    return super()
  end
  nil
end

#first_literalRDF::Literal #first_literal(pattern) ⇒ RDF::Literal

Queries self for RDF statements matching the given pattern and returns the first found object literal.

Returns nil if no statements match pattern or if none of the found statements have a literal as their object term.

Overloads:

Returns:

Since:

  • 0.1.9



279
280
281
282
283
284
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 279

def first_literal(pattern = nil)
  __send__(*(pattern ? [:query, pattern] : [:each])) do |statement|
    return statement.object if statement.object.is_a?(RDF::Literal)
  end
  return nil
end

#first_objectRDF::Term #first_object(pattern) ⇒ RDF::Term

Queries self for an RDF statement matching the given pattern and returns the statement's object term.

Returns nil if no statements match pattern.

Overloads:

Since:

  • 0.1.9



256
257
258
259
260
261
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 256

def first_object(pattern = nil)
  __send__(*(pattern ? [:query, pattern] : [:each])) do |statement|
    return statement.object
  end
  return nil
end

#first_predicateRDF::URI #first_predicate(pattern) ⇒ RDF::URI

Queries self for an RDF statement matching the given pattern and returns the statement's predicate term.

Returns nil if no statements match pattern.

Overloads:

Since:

  • 0.1.9



236
237
238
239
240
241
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 236

def first_predicate(pattern = nil)
  __send__(*(pattern ? [:query, pattern] : [:each])) do |statement|
    return statement.predicate
  end
  return nil
end

#first_subjectRDF::Resource #first_subject(pattern) ⇒ RDF::Resource

Queries self for an RDF statement matching the given pattern and returns the statement's subject term.

Returns nil if no statements match pattern.

Overloads:

Since:

  • 0.1.9



216
217
218
219
220
221
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 216

def first_subject(pattern = nil)
  __send__(*(pattern ? [:query, pattern] : [:each])) do |statement|
    return statement.subject
  end
  return nil
end

#first_valueObject #first_value(pattern) ⇒ Object

Queries self for RDF statements matching the given pattern and returns the value of the first found object literal.

Returns nil if no statements match pattern or if none of the found statements have a literal as their object term.

Overloads:

Since:

  • 0.1.9



300
301
302
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 300

def first_value(pattern = nil)
  (literal = first_literal(pattern)) ? literal.value : nil
end

#lintHash{Symbol => Hash{Symbol => Array<String>}}

Lint a queryable, presuming that it has already had RDFS entailment expansion.

Returns:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-reasoner-e77a80426b61/lib/rdf/reasoner/extensions.rb', line 232

def lint
  messages = {}

  # Check for defined classes in known vocabularies
  self.query({predicate: RDF.type}) do |stmt|
    vocab = RDF::Vocabulary.find(stmt.object)
    term = (RDF::Vocabulary.find_term(stmt.object) rescue nil) if vocab
    pname = term ? term.pname : stmt.object.pname
    
    # Must be a defined term, not in RDF or RDFS vocabularies
    if term && term.class?
      # Warn against using a deprecated term
      superseded = term.properties[:'http://schema.org/supersededBy']
      superseded = superseded.pname if superseded.respond_to?(:pname)
      (messages[:class] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
    else
      (messages[:class] ||= {})[pname] = ["No class definition found"] unless vocab.nil? || [RDF::RDFV, RDF::RDFS].include?(vocab)
    end
  end

  # Check for defined predicates in known vocabularies and domain/range
  resource_types = {}
  self.each_statement do |stmt|
    vocab = RDF::Vocabulary.find(stmt.predicate)
    term = (RDF::Vocabulary.find_term(stmt.predicate) rescue nil) if vocab
    pname = term ? term.pname : stmt.predicate.pname

    # Must be a valid statement
    begin
      stmt.validate!
    rescue
      ((messages[:statement] ||= {})[pname] ||= []) << "Triple #{stmt.to_ntriples} is invalid"
    end

    # Must be a defined property
    if term.respond_to?(:property?) && term.property?
      # Warn against using a deprecated term
      superseded = term.properties[:'http://schema.org/supersededBy']
      superseded = superseded.pname if superseded.respond_to?(:pname)
      (messages[:property] ||= {})[pname] = ["Term is superseded by #{superseded}"] if superseded
    else
      ((messages[:property] ||= {})[pname] ||= []) << "No property definition found" unless vocab.nil?
      next
    end

    # See if type of the subject is in the domain of this predicate
    resource_types[stmt.subject] ||= self.query({subject: stmt.subject, predicate: RDF.type}).
    map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
      flatten.
      uniq.
      compact

    unless term.domain_compatible?(stmt.subject, self, types: resource_types[stmt.subject])
      ((messages[:property] ||= {})[pname] ||= []) << if !term.domain.empty?
       "Subject #{show_resource(stmt.subject)} not compatible with domain (#{Array(term.domain).map {|d| d.pname|| d}.join(',')})"
      else
        domains = Array(term.domainIncludes) +
                  Array(term.properties[:'https://schema.org/domainIncludes'])
        "Subject #{show_resource(stmt.subject)} not compatible with domainIncludes (#{domains.map {|d| d.pname|| d}.join(',')})"
      end
    end

    # Make sure that if ranges are defined, the object has an appropriate type
    resource_types[stmt.object] ||= self.query({subject: stmt.object, predicate: RDF.type}).
      map {|s| (t = (RDF::Vocabulary.find_term(s.object) rescue nil)) && t.entail(:subClassOf)}.
      flatten.
      uniq.
      compact if stmt.object.resource?

    unless term.range_compatible?(stmt.object, self, types: resource_types[stmt.object])
      ((messages[:property] ||= {})[pname] ||= []) << if !term.range.empty?
       "Object #{show_resource(stmt.object)} not compatible with range (#{Array(term.range).map {|d| d.pname|| d}.join(',')})"
      else
        ranges = Array(term.rangeIncludes) +
                  Array(term.properties[:'https://schema.org/rangeIncludes'])
        "Object #{show_resource(stmt.object)} not compatible with rangeIncludes (#{ranges.map {|d| d.pname|| d}.join(',')})"
      end
    end
  end

  messages[:class].each {|k, v| messages[:class][k] = v.uniq} if messages[:class]
  messages[:property].each {|k, v| messages[:property][k] = v.uniq} if messages[:property]
  messages
end

#query(pattern, **options) {|statement| ... } ⇒ Enumerator

Queries self for RDF statements matching the given pattern.

Monkey patch to RDF::Queryable#query to execute a SPARQL::Algebra::Operator in addition to an RDF::Query object.

Examples:

queryable.query([nil, RDF::DOAP.developer, nil])
queryable.query({predicate: RDF::DOAP.developer})

op = SPARQL::Algebra::Expression.parse(%q((bgp (triple ?a doap:developer ?b))))
queryable.query(op)

Parameters:

Yields:

  • (statement)

    each matching statement

Yield Parameters:

Yield Returns:

  • (void)

    ignored

Returns:

Raises:

  • (TypeError)

See Also:



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 56

def query(pattern, **options, &block)
  raise TypeError, "#{self} is not readable" if respond_to?(:readable?) && !readable?

  case pattern
    # A basic graph pattern (BGP) query:
    when Query
      solutions = RDF::Query::Solutions.new
      block = lambda {|solution| solutions << solution} unless block_given?
      before_query(pattern) if respond_to?(:before_query)
      query_execute(pattern, **options, &block)
      after_query(pattern) if respond_to?(:after_query)
      # Returns the solutions, not an enumerator
      solutions
 
    # A simple triple/quad pattern query:
    else
      pattern = Query::Pattern.from(pattern)
      before_query(pattern) if respond_to?(:before_query)
      enum = case
        # Blank triple/quad patterns are equivalent to iterating over
        # every statement, so as a minor optimization we'll just do that
        # directly instead of bothering with `#query_pattern`:
        when pattern.blank?
          if block_given?
            each(&block)
          else
            to_a.extend(Queryable)
          end

        # Constant triple/quad patterns are equivalent to looking up a
        # particular statement, so as a minor optimization we'll just do
        # that directly instead of bothering with `#query_pattern`:
        when pattern.constant?
          statement = Statement.from(pattern)
          if include?(statement)
            if block_given?
              yield statement
            else
              [statement]
            end
          end

        # Otherwise, we delegate to `#query_pattern`:
        else # pattern.variable?
          query_pattern(pattern, **options, &block)
      end
      after_query(pattern) if respond_to?(:after_query)
      enum
  end
end

#query_execute(query, **options) {|solution| ... } (protected)

This method returns an undefined value.

Queries self using the given basic graph pattern (BGP) query, yielding each matched solution to the given block.

Since RDF.rb 0.3.0, repository implementations can override this method in order to provide for storage-specific optimized graph pattern query execution.

Parameters:

Yields:

  • (solution)

Yield Parameters:

Yield Returns:

  • (void)

    ignored

See Also:

Since:

  • 0.3.0



126
127
128
129
130
131
132
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 126

def query_execute(query, **options, &block)
  # By default, we let RDF.rb's built-in `RDF::Query#execute` handle BGP
  # query execution by breaking down the query into its constituent
  # triple patterns and invoking `RDF::Query::Pattern#execute` on each
  # pattern.
  query.execute(self, **options, &block)
end

#query_pattern(pattern, **options) {|statement| ... } (protected)

This method returns an undefined value.

Queries self for RDF statements matching the given pattern, yielding each matched statement to the given block.

Since RDF.rb 0.2.0, repository implementations should override this method in order to provide for storage-specific optimized triple pattern matching.

RDF-star

Statements may have embedded statements as either a subject or object, recursively.

Patterns may also have embedded patterns as either a subject or object, recursively.

Patterns with a variable graph_name do not match the default graph.

When matching, match an embedded pattern against embedded statements, recursively. (see RDF::Query::Pattern#eql?)

Parameters:

Yields:

  • (statement)

Yield Parameters:

Yield Returns:

  • (void)

    ignored

See Also:

Since:

  • 0.2.0



164
165
166
167
168
169
170
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/rdf-196b73b4a78a/lib/rdf/mixin/queryable.rb', line 164

def query_pattern(pattern, **options, &block)
  # By default, we let Ruby's built-in `Enumerable#grep` handle the
  # matching of statements by iterating over all statements and calling
  # `RDF::Query::Pattern#===` on each statement.
  # @see http://ruby-doc.org/core/classes/Enumerable.html#M003121
  grep(pattern, &block)
end

#query_without_sparql {|statement| ... } ⇒ Enumerator<RDF::Statement>, ...

Note:

Since 2.0, this may return an Enumerable or an Enumerator in addition to Solutions

Queries self for RDF statements matching the given pattern.

This method delegates to the protected #query_pattern method for the actual lower-level query pattern matching implementation.

Examples:

Querying for statements having a given predicate

queryable.query([nil, RDF::Vocab::DOAP.developer, nil])
queryable.query({predicate: RDF::Vocab::DOAP.developer}) do |statement|
  puts statement.inspect
end

Querying for solutions from a BGP

query = RDF::Query.new {pattern [:s, :p, :o]}
queryable.query(query) do |solution|
  puts solution.inspect
end

Parameters:

Yields:

  • (statement)

    each matching statement

Yield Parameters:

Yield Returns:

  • (void)

    ignored

Returns:

Raises:

  • (TypeError)

See Also:



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
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/sparql-36baa432eb7f/lib/sparql/algebra/extensions.rb', line 299

def query(pattern, **options, &block)
  raise TypeError, "#{self} is not readable" if respond_to?(:readable?) && !readable?

  case pattern
    # A basic graph pattern (BGP) query:
    when Query
      solutions = RDF::Query::Solutions.new
      block = lambda {|solution| solutions << solution} unless block_given?
      before_query(pattern) if respond_to?(:before_query)
      query_execute(pattern, **options, &block)
      after_query(pattern) if respond_to?(:after_query)
      # Returns the solutions, not an enumerator
      solutions
 
    # A simple triple/quad pattern query:
    else
      pattern = Query::Pattern.from(pattern)
      before_query(pattern) if respond_to?(:before_query)
      enum = case
        # Blank triple/quad patterns are equivalent to iterating over
        # every statement, so as a minor optimization we'll just do that
        # directly instead of bothering with `#query_pattern`:
        when pattern.blank?
          if block_given?
            each(&block)
          else
            to_a.extend(Queryable)
          end

        # Constant triple/quad patterns are equivalent to looking up a
        # particular statement, so as a minor optimization we'll just do
        # that directly instead of bothering with `#query_pattern`:
        when pattern.constant?
          statement = Statement.from(pattern)
          if include?(statement)
            if block_given?
              yield statement
            else
              [statement]
            end
          end

        # Otherwise, we delegate to `#query_pattern`:
        else # pattern.variable?
          query_pattern(pattern, **options, &block)
      end
      after_query(pattern) if respond_to?(:after_query)
      enum
  end
end

#to_sparql(**options) ⇒ String

Returns a partial SPARQL grammar for this term.

Returns:

Raises:

  • (NotImplementedError)


349
350
351
# File 'vendor/bundler/ruby/3.3.0/bundler/gems/sparql-36baa432eb7f/lib/sparql/algebra/extensions.rb', line 349

def to_sparql(**options)
  raise NotImplementedError, "SPARQL::Algebra '#{first}' operator not implemented"
end