module SPARQL

A SPARQL for RDF.rb.

@see www.w3.org/TR/sparql11-query

Constants

ERROR_MESSAGE

Public Class Methods

execute(query, queryable, **options, &block) click to toggle source

Parse and execute the given SPARQL `query` string against `queriable`.

Requires a queryable object (such as an RDF::Repository), into which the dataset will be loaded.

Optionally takes a list of URIs to load as default or named graphs into `queryable`.

Note, if default or named graphs are specified as options (protocol elements), or the query references specific default or named graphs the graphs are either presumed to be existant in `queryable` or are loaded into `queryable` depending on the presense and value of the :load_datasets option.

Attempting to load into an immutable `queryable` will result in a TypeError.

@example

repository = RDF::Repository.new
results = SPARQL.execute("SELECT * WHERE { ?s ?p ?o }", repository)

@param [IO, StringIO, String, to_s] query @param [RDF::Queryable] queryable @param [Hash{Symbol => Object}] options @option options [Boolean] :optimize

Optimize query before execution.

@option options [RDF::URI, String, Array<RDF::URI, String>] :default_graph_uri @option options [RDF::URI, String, Array<RDF::URI, String>] :load_datasets

One or more URIs used to initialize a new instance of `queryable` in the default graph. One or more URIs used to initialize a new instance of `queryable` in the default graph.

@option options [RDF::URI, String, Array<RDF::URI, String>] :named_graph_uri

One or more URIs used to initialize the `queryable` as a named graph.

@yield [solution]

each matching solution, statement or boolean

@yieldparam [RDF::Statement, RDF::Query::Solution, Boolean] solution @yieldreturn [void] ignored @return [RDF::Graph, Boolean, RDF::Query::Solutions::Enumerator]

Note, results may be used with {SPARQL.serialize_results} to obtain appropriate output encoding.

@raise [SPARQL::MalformedQuery] on invalid input

# File lib/sparql.rb, line 73
def self.execute(query, queryable, **options, &block)
  query = self.parse(query, **options)
  query = query.optimize(**options) if options[:optimize]
  queryable = queryable || RDF::Repository.new

  if options[:logger]
    options[:logger].debug("SPARQL.execute") {SXP::Generator.string query.to_sxp_bin}
  end

  if options.has_key?(:load_datasets)
    queryable = queryable.class.new
    [options[:default_graph_uri]].flatten.each do |uri|
      queryable.load(uri)
    end
    [options[:named_graph_uri]].flatten.each do |uri|
      queryable.load(uri, graph_name: uri)
    end
  end
  query.execute(queryable, &block)
rescue SPARQL::Grammar::Parser::Error => e
  raise MalformedQuery, e.message
rescue TypeError => e
  raise QueryRequestRefused, e.message
end
first_content_type(acceptable, available) click to toggle source

Find a content_type from a list using an ordered list of acceptable content types using wildcard matching

@param [Array<String>] acceptable @param [Array<String>] available @return [String]

@see www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1

# File lib/sparql/results.rb, line 311
def first_content_type(acceptable, available)
  return acceptable.first if available.empty?
  available.flatten!
  acceptable.each do |pattern|
    type = available.detect { |t| File.fnmatch(pattern, t) }
    return type if type
  end
  nil
end
parse(query, **options) click to toggle source

Parse the given SPARQL `query` string.

@example

query = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }")

@param [IO, StringIO, String, to_s] query @param [Hash{Symbol => Object}] options @option options [Boolean] :update (false)

Parse starting with UpdateUnit production, QueryUnit otherwise.

@return [SPARQL::Query]

The resulting query may be executed against
a `queryable` object such as an RDF::Graph
or RDF::Repository.

@raise [Parser::Error] on invalid input

# File lib/sparql.rb, line 33
def self.parse(query, **options)
  query = Grammar::Parser.new(query, **options).parse(options[:update] ? :UpdateUnit : :QueryUnit)
end
serialize_exception(exception, **options) click to toggle source

Serialize error results

Returns appropriate content based upon an execution exception @param [Exception] exception @param [Hash{Symbol => Object}] options @option options [:format]

Format of results, one of :html, :json or :xml.
May also be an RDF::Writer format to serialize DESCRIBE or CONSTRUCT results

@option options [:content_type]

Format of results, one of 'application/sparql-results+json' or 'application/sparql-results+xml'
May also be an RDF::Writer content_type to serialize DESCRIBE or CONSTRUCT results

@return [String]

String with serialized results and #content_type
# File lib/sparql/results.rb, line 336
def serialize_exception(exception, **options)
  format = options[:format]
  content_type = options[:content_type]
  content_type ||= SPARQL::Results::MIME_TYPES[format]
  serialization = case content_type
  when 'text/html'
    title = exception.respond_to?(:title) ? exception.title : exception.class.to_s
    ERROR_MESSAGE % [title, title, exception.message]
  else
    content_type = "text/plain"
    exception.message
  end
  
  serialization.instance_eval do
    define_singleton_method(:content_type) { content_type }
  end

  serialization
end
serialize_results(solutions, **options) click to toggle source

Serialize solutions using the determined format

@param [RDF::Query::Solutions, RDF::Queryable, Boolean] solutions

Solutions as either a solution set, a Queryable object (such as a graph) or a Boolean value

@param [Hash{Symbol => Object}] options @option options [#to_sym] :format

Format of results, one of :html, :json or :xml.
May also be an RDF::Writer format to serialize DESCRIBE or CONSTRUCT results

@option options [String] :content_type

Format of results, one of 'application/sparql-results+json' or 'application/sparql-results+xml'
May also be an RDF::Writer content_type to serialize DESCRIBE or CONSTRUCT results

@option options [Array<String>] :content_types

Similar to :content_type, but takes an ordered array of appropriate content types,
and serializes using the first appropriate type, including wild-cards.

@return [String]

String with serialized results and `#content_type`

@raise [RDF::WriterError] when inappropriate formatting options are used

# File lib/sparql/results.rb, line 215
def serialize_results(solutions, **options)
  format = options[:format].to_sym if options[:format]
  content_type = options[:content_type].to_s.split(';').first
  content_types = Array(options[:content_types] || '*/*')

  if !format && !content_type
    case solutions
    when RDF::Queryable
      content_type = first_content_type(content_types, RDF::Format.content_types.keys) || 'text/plain'
      format = RDF::Writer.for(content_type: content_type).to_sym
    else
      content_type = first_content_type(content_types, SPARQL::Results::MIME_TYPES.values) || 'application/sparql-results+xml'
      format = SPARQL::Results::MIME_TYPES.invert[content_type]
    end
  end

  serialization = case solutions
  when TrueClass, FalseClass, RDF::Literal::TRUE, RDF::Literal::FALSE
    solutions = solutions.object if solutions.is_a?(RDF::Literal)
    case format
    when :json
      require 'json' unless defined?(::JSON)
      {boolean: solutions}.to_json
    when :xml
      require 'builder' unless defined?(::Builder)
      xml = ::Builder::XmlMarkup.new(indent: 2)
      xml.instruct!
      xml.sparql(xmlns: "http://www.w3.org/2005/sparql-results#") do
        xml.boolean(solutions.to_s)
      end
    when :html
      require 'builder' unless defined?(::Builder)
      content_type = "text/html"
      xml = ::Builder::XmlMarkup.new(indent: 2)
      xml.div(solutions.to_s, class: "sparql")
    else
      raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
    end
  when RDF::Queryable
    begin
      require 'linkeddata'
    rescue LoadError
      require 'rdf/ntriples'
    end
    fmt = RDF::Format.for(format ? format.to_sym : {content_type: content_type})
    unless fmt
      fmt = RDF::Format.for(file_extension: format.to_sym) || RDF::NTriples::Format
      format = fmt.to_sym
    end
    format ||= fmt.to_sym
    content_type ||= fmt.content_type.first
    results = solutions.dump(format, **options)
    raise RDF::WriterError, "Unknown format #{fmt.inspect} for #{solutions.class}" unless results
    results
  when RDF::Query::Solutions
    case format
    when :json  then solutions.to_json
    when :xml   then solutions.to_xml
    when :html  then solutions.to_html
    when :csv   then solutions.to_csv
    when :tsv   then solutions.to_tsv
    else
      raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
    end
  end

  content_type ||= SPARQL::Results::MIME_TYPES[format] if format
  
  serialization.instance_eval do
    define_singleton_method(:content_type) { content_type }
  end
  
  serialization
end

Private Instance Methods

first_content_type(acceptable, available) click to toggle source

Find a content_type from a list using an ordered list of acceptable content types using wildcard matching

@param [Array<String>] acceptable @param [Array<String>] available @return [String]

@see www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1

# File lib/sparql/results.rb, line 311
def first_content_type(acceptable, available)
  return acceptable.first if available.empty?
  available.flatten!
  acceptable.each do |pattern|
    type = available.detect { |t| File.fnmatch(pattern, t) }
    return type if type
  end
  nil
end
serialize_exception(exception, **options) click to toggle source

Serialize error results

Returns appropriate content based upon an execution exception @param [Exception] exception @param [Hash{Symbol => Object}] options @option options [:format]

Format of results, one of :html, :json or :xml.
May also be an RDF::Writer format to serialize DESCRIBE or CONSTRUCT results

@option options [:content_type]

Format of results, one of 'application/sparql-results+json' or 'application/sparql-results+xml'
May also be an RDF::Writer content_type to serialize DESCRIBE or CONSTRUCT results

@return [String]

String with serialized results and #content_type
# File lib/sparql/results.rb, line 336
def serialize_exception(exception, **options)
  format = options[:format]
  content_type = options[:content_type]
  content_type ||= SPARQL::Results::MIME_TYPES[format]
  serialization = case content_type
  when 'text/html'
    title = exception.respond_to?(:title) ? exception.title : exception.class.to_s
    ERROR_MESSAGE % [title, title, exception.message]
  else
    content_type = "text/plain"
    exception.message
  end
  
  serialization.instance_eval do
    define_singleton_method(:content_type) { content_type }
  end

  serialization
end
serialize_results(solutions, **options) click to toggle source

Serialize solutions using the determined format

@param [RDF::Query::Solutions, RDF::Queryable, Boolean] solutions

Solutions as either a solution set, a Queryable object (such as a graph) or a Boolean value

@param [Hash{Symbol => Object}] options @option options [#to_sym] :format

Format of results, one of :html, :json or :xml.
May also be an RDF::Writer format to serialize DESCRIBE or CONSTRUCT results

@option options [String] :content_type

Format of results, one of 'application/sparql-results+json' or 'application/sparql-results+xml'
May also be an RDF::Writer content_type to serialize DESCRIBE or CONSTRUCT results

@option options [Array<String>] :content_types

Similar to :content_type, but takes an ordered array of appropriate content types,
and serializes using the first appropriate type, including wild-cards.

@return [String]

String with serialized results and `#content_type`

@raise [RDF::WriterError] when inappropriate formatting options are used

# File lib/sparql/results.rb, line 215
def serialize_results(solutions, **options)
  format = options[:format].to_sym if options[:format]
  content_type = options[:content_type].to_s.split(';').first
  content_types = Array(options[:content_types] || '*/*')

  if !format && !content_type
    case solutions
    when RDF::Queryable
      content_type = first_content_type(content_types, RDF::Format.content_types.keys) || 'text/plain'
      format = RDF::Writer.for(content_type: content_type).to_sym
    else
      content_type = first_content_type(content_types, SPARQL::Results::MIME_TYPES.values) || 'application/sparql-results+xml'
      format = SPARQL::Results::MIME_TYPES.invert[content_type]
    end
  end

  serialization = case solutions
  when TrueClass, FalseClass, RDF::Literal::TRUE, RDF::Literal::FALSE
    solutions = solutions.object if solutions.is_a?(RDF::Literal)
    case format
    when :json
      require 'json' unless defined?(::JSON)
      {boolean: solutions}.to_json
    when :xml
      require 'builder' unless defined?(::Builder)
      xml = ::Builder::XmlMarkup.new(indent: 2)
      xml.instruct!
      xml.sparql(xmlns: "http://www.w3.org/2005/sparql-results#") do
        xml.boolean(solutions.to_s)
      end
    when :html
      require 'builder' unless defined?(::Builder)
      content_type = "text/html"
      xml = ::Builder::XmlMarkup.new(indent: 2)
      xml.div(solutions.to_s, class: "sparql")
    else
      raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
    end
  when RDF::Queryable
    begin
      require 'linkeddata'
    rescue LoadError
      require 'rdf/ntriples'
    end
    fmt = RDF::Format.for(format ? format.to_sym : {content_type: content_type})
    unless fmt
      fmt = RDF::Format.for(file_extension: format.to_sym) || RDF::NTriples::Format
      format = fmt.to_sym
    end
    format ||= fmt.to_sym
    content_type ||= fmt.content_type.first
    results = solutions.dump(format, **options)
    raise RDF::WriterError, "Unknown format #{fmt.inspect} for #{solutions.class}" unless results
    results
  when RDF::Query::Solutions
    case format
    when :json  then solutions.to_json
    when :xml   then solutions.to_xml
    when :html  then solutions.to_html
    when :csv   then solutions.to_csv
    when :tsv   then solutions.to_tsv
    else
      raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
    end
  end

  content_type ||= SPARQL::Results::MIME_TYPES[format] if format
  
  serialization.instance_eval do
    define_singleton_method(:content_type) { content_type }
  end
  
  serialization
end