Class: SPARQL::Client
- Inherits:
-
Object
- Object
- SPARQL::Client
- Defined in:
- lib/sparql/client.rb,
lib/sparql/client/query.rb,
lib/sparql/client/update.rb,
lib/sparql/client/version.rb,
lib/sparql/client/repository.rb
Overview
A SPARQL 1.0/1.1 client for RDF.rb.
Defined Under Namespace
Modules: Update, VERSION Classes: ClientError, MalformedQuery, Query, QueryElement, Repository, ServerError
Constant Summary collapse
- RESULT_JSON =
'application/sparql-results+json'.freeze
- RESULT_XML =
'application/sparql-results+xml'.freeze
- RESULT_CSV =
'text/csv'.freeze
- RESULT_TSV =
'text/tab-separated-values'.freeze
- RESULT_BOOL =
Sesame-specific
'text/boolean'.freeze
- RESULT_BRTR =
Sesame-specific
'application/x-binary-rdf-results-table'.freeze
- RESULT_ALL =
[ RESULT_JSON, RESULT_XML, RESULT_BOOL, "#{RESULT_TSV};q=0.8", "#{RESULT_CSV};q=0.2", '*/*;q=0.1' ].join(', ').freeze
- GRAPH_ALL =
( RDF::Format.content_types.keys + ['*/*;q=0.1'] ).join(', ').freeze
- ACCEPT_JSON =
{'Accept' => RESULT_JSON}.freeze
- ACCEPT_XML =
{'Accept' => RESULT_XML}.freeze
- ACCEPT_CSV =
{'Accept' => RESULT_CSV}.freeze
- ACCEPT_TSV =
{'Accept' => RESULT_TSV}.freeze
- ACCEPT_BRTR =
{'Accept' => RESULT_BRTR}.freeze
- ACCEPT_RESULTS =
{'Accept' => RESULT_ALL}.freeze
- ACCEPT_GRAPH =
{'Accept' => GRAPH_ALL}.freeze
- DEFAULT_PROTOCOL =
1.0
- DEFAULT_METHOD =
:post
- XMLNS =
{'sparql' => 'http://www.w3.org/2005/sparql-results#'}.freeze
Instance Attribute Summary collapse
-
#headers ⇒ Hash{String => String}
readonly
The HTTP headers that will be sent in requests to the endpoint.
-
#options ⇒ Hash{Symbol => Object}
readonly
Any miscellaneous configuration.
-
#url ⇒ RDF::URI, RDF::Queryable
readonly
The SPARQL endpoint URL, or an RDF::Queryable instance, to use the native SPARQL engine.
Class Method Summary collapse
-
.finalize(klass) ⇒ Object
Close the http connection when object is deallocated.
- .parse_csv_bindings(csv, nodes = {}) ⇒ <RDF::Query::Solutions>
- .parse_json_bindings(json, nodes = {}) ⇒ <RDF::Query::Solutions>
- .parse_json_value(value, nodes = {}) ⇒ RDF::Value
- .parse_tsv_bindings(tsv, nodes = {}) ⇒ <RDF::Query::Solutions>
- .parse_xml_bindings(xml, nodes = {}, library: :nokogiri) ⇒ <RDF::Query::Solutions>
- .parse_xml_value(value, nodes = {}) ⇒ RDF::Value
-
.serialize_patterns(patterns, use_vars = false) ⇒ String
Serializes a SPARQL graph.
-
.serialize_predicate(value, rdepth = 0) ⇒ String
Serializes a SPARQL predicate.
-
.serialize_uri(uri) ⇒ String
Serializes a URI or URI string into SPARQL syntax.
-
.serialize_value(value, use_vars = false) ⇒ String
Serializes an ‘RDF::Value` into SPARQL syntax.
Instance Method Summary collapse
-
#ask(*args, **options) ⇒ Query
Executes a boolean ‘ASK` query.
- #call_query_method(meth, *args, **options) ⇒ Object
-
#clear(what, *arguments) ⇒ Object
Executes a ‘CLEAR` operation.
-
#clear_graph(graph_uri, **options) ⇒ void
Executes a ‘CLEAR GRAPH` operation.
-
#close ⇒ void
Closes a client instance by finishing the connection.
-
#construct(*args, **options) ⇒ Query
Executes a graph ‘CONSTRUCT` query.
-
#delete_data(data, **options) ⇒ void
Executes a ‘DELETE DATA` operation.
-
#delete_insert(delete_graph, insert_graph = nil, where_graph = nil, **options) ⇒ void
Executes a ‘DELETE/INSERT` operation.
-
#describe(*args, **options) ⇒ Query
Executes a ‘DESCRIBE` query.
-
#initialize(url, **options, &block) ⇒ Client
constructor
Initialize a new sparql client, either using the URL of a SPARQL endpoint or an ‘RDF::Queryable` instance to use the native SPARQL gem.
-
#insert_data(data, **options) ⇒ void
Executes an ‘INSERT DATA` operation.
-
#inspect ⇒ String
Returns a developer-friendly representation of this object.
-
#inspect! ⇒ void
Outputs a developer-friendly representation of this object to ‘stderr`.
-
#nodes ⇒ Object
Returns a mapping of blank node results for this client.
- #parse_rdf_serialization(response, **options) ⇒ RDF::Enumerable
- #parse_response(response, **options) ⇒ Object
-
#query(query, **options) ⇒ Array<RDF::Query::Solution>
Executes a SPARQL query and returns the parsed results.
-
#response(query, **options) ⇒ String
Executes a SPARQL query and returns the Net::HTTP::Response of the result.
-
#select(*args, **options) ⇒ Query
Executes a tuple ‘SELECT` query.
-
#update(query, **options) ⇒ void
Executes a SPARQL update operation.
Constructor Details
#initialize(url, **options, &block) ⇒ Client
Initialize a new sparql client, either using the URL of a SPARQL endpoint or an ‘RDF::Queryable` instance to use the native SPARQL gem.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/sparql/client.rb', line 95 def initialize(url, **, &block) case url when RDF::Queryable @url, @options = url, .dup else @url, @options = RDF::URI.new(url.to_s), .dup @headers = @options.delete(:headers) || {} @http = http_klass(@url.scheme) # Close the http connection when object is deallocated ObjectSpace.define_finalizer(self, self.class.finalize(@http)) end if block_given? case block.arity when 1 then block.call(self) else instance_eval(&block) end end end |
Instance Attribute Details
#headers ⇒ Hash{String => String} (readonly)
The HTTP headers that will be sent in requests to the endpoint.
70 71 72 |
# File 'lib/sparql/client.rb', line 70 def headers @headers end |
#options ⇒ Hash{Symbol => Object} (readonly)
Any miscellaneous configuration.
76 77 78 |
# File 'lib/sparql/client.rb', line 76 def @options end |
#url ⇒ RDF::URI, RDF::Queryable (readonly)
The SPARQL endpoint URL, or an RDF::Queryable instance, to use the native SPARQL engine.
64 65 66 |
# File 'lib/sparql/client.rb', line 64 def url @url end |
Class Method Details
.finalize(klass) ⇒ Object
Close the http connection when object is deallocated
117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/sparql/client.rb', line 117 def self.finalize(klass) proc do if klass.respond_to?(:shutdown) begin # Attempt asynchronous shutdown Thread.new {klass.shutdown} rescue ThreadError klass.shutdown end end end end |
.parse_csv_bindings(csv, nodes = {}) ⇒ <RDF::Query::Solutions>
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'lib/sparql/client.rb', line 471 def self.parse_csv_bindings(csv, nodes = {}) require 'csv' unless defined?(::CSV) csv = CSV.parse(csv.to_s) unless csv.is_a?(Array) vars = csv.shift solutions = RDF::Query::Solutions.new csv.each do |row| solution = RDF::Query::Solution.new row.each_with_index do |v, i| term = case v when /^_:(.*)$/ then nodes[$1] ||= RDF::Node($1) when /^\w+:.*$/ then RDF::URI(v) else RDF::Literal(v) end solution[vars[i].to_sym] = term end solutions << solution end solutions end |
.parse_json_bindings(json, nodes = {}) ⇒ <RDF::Query::Solutions>
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/sparql/client.rb', line 420 def self.parse_json_bindings(json, nodes = {}) require 'json' unless defined?(::JSON) json = JSON.parse(json.to_s) unless json.is_a?(Hash) case when json.has_key?('boolean') json['boolean'] when json.has_key?('results') solutions = json['results']['bindings'].map do |row| row = row.inject({}) do |cols, (name, value)| cols.merge(name.to_sym => parse_json_value(value, nodes)) end RDF::Query::Solution.new(row) end solns = RDF::Query::Solutions.new(solutions) # Set variable names explicitly if json.fetch('head', {}).has_key?('vars') solns.variable_names = json['head']['vars'].map(&:to_sym) end solns end end |
.parse_json_value(value, nodes = {}) ⇒ RDF::Value
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 |
# File 'lib/sparql/client.rb', line 448 def self.parse_json_value(value, nodes = {}) case value['type'].to_sym when :bnode nodes[id = value['value']] ||= RDF::Node.new(id) when :uri RDF::URI.new(value['value']) when :literal RDF::Literal.new(value['value'], datatype: value['datatype'], language: value['xml:lang']) when :'typed-literal' RDF::Literal.new(value['value'], datatype: value['datatype']) when :triple s = parse_json_value(value['value']['subject'], nodes) p = parse_json_value(value['value']['predicate'], nodes) o = parse_json_value(value['value']['object'], nodes) RDF::Statement(s, p, o) else nil end end |
.parse_tsv_bindings(tsv, nodes = {}) ⇒ <RDF::Query::Solutions>
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 |
# File 'lib/sparql/client.rb', line 495 def self.parse_tsv_bindings(tsv, nodes = {}) tsv = tsv.lines.map {|l| l.chomp.split("\t")} unless tsv.is_a?(Array) vars = tsv.shift.map {|h| h.sub(/^\?/, '')} solutions = RDF::Query::Solutions.new tsv.each do |row| # Flesh out columns which may be missing vars.each_with_index do |_, i| row[i] ||= "" end solution = RDF::Query::Solution.new row.each_with_index do |v, i| term = case v when "" then RDF::Literal("") when /^\d+\.\d*[eE][+-]?[0-9]+$/ then RDF::Literal::Double.new(v) when /^\d*\.\d+[eE][+-]?[0-9]+$/ then RDF::Literal::Double.new(v) when /^\d*\.\d+$/ then RDF::Literal::Decimal.new(v) when /^\d+$/ then RDF::Literal::Integer.new(v) else RDF::NTriples.unserialize(v) || RDF::Literal(v) end nodes[term.id] = term if term.is_a? RDF::Node solution[vars[i].to_sym] = term end solutions << solution end solutions end |
.parse_xml_bindings(xml, nodes = {}, library: :nokogiri) ⇒ <RDF::Query::Solutions>
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/sparql/client.rb', line 529 def self.parse_xml_bindings(xml, nodes = {}, library: :nokogiri) xml.force_encoding(::Encoding::UTF_8) if xml.respond_to?(:force_encoding) if defined?(::Nokogiri) && library == :nokogiri xml = Nokogiri::XML(xml).root unless xml.is_a?(Nokogiri::XML::Document) case when boolean = xml.xpath("//sparql:boolean", XMLNS)[0] boolean.text == 'true' when results = xml.xpath("//sparql:results", XMLNS)[0] solutions = results.elements.map do |result| row = {} result.elements.each do |binding| name = binding.attr('name').to_sym value = binding.elements.first row[name] = parse_xml_value(value, nodes) if value end RDF::Query::Solution.new(row) end solns = RDF::Query::Solutions.new(solutions) # Set variable names explicitly var_names = xml.xpath("//sparql:head/sparql:variable/@name", XMLNS) solns.variable_names = var_names.map(&:to_s) solns end else # REXML xml = REXML::Document.new(xml).root unless xml.is_a?(REXML::Element) case when boolean = xml.elements['boolean'] boolean.text == 'true' when results = xml.elements['results'] solutions = results.elements.map do |result| row = {} result.elements.each do |binding| name = binding.attributes['name'].to_sym value = binding.select { |node| node.kind_of?(::REXML::Element) }.first row[name] = parse_xml_value(value, nodes) if value end RDF::Query::Solution.new(row) end solns = RDF::Query::Solutions.new(solutions) # Set variable names explicitly var_names = xml.elements['head'].elements.map {|e| e.attributes['name']} solns.variable_names = var_names.map(&:to_sym) solns end end end |
.parse_xml_value(value, nodes = {}) ⇒ RDF::Value
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 |
# File 'lib/sparql/client.rb', line 584 def self.parse_xml_value(value, nodes = {}) case value.name.to_sym when :bnode nodes[id = value.text] ||= RDF::Node.new(id) when :uri RDF::URI.new(value.text) when :literal lang = value.respond_to?(:attr) ? value.attr('xml:lang') : value.attributes['xml:lang'] datatype = value.respond_to?(:attr) ? value.attr('datatype') : value.attributes['datatype'] RDF::Literal.new(value.text, language: lang, datatype: datatype) when :triple # Note, this is order dependent res = value.elements.map {|e| e.elements.to_a}. flatten.map {|e| parse_xml_value(e, nodes)} RDF::Statement(*res) else nil end end |
.serialize_patterns(patterns, use_vars = false) ⇒ String
Serializes a SPARQL graph
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 |
# File 'lib/sparql/client.rb', line 676 def self.serialize_patterns(patterns, use_vars = false) patterns.map do |pattern| serialized_pattern = case pattern when SPARQL::Client::QueryElement then [pattern.to_s] else RDF::Statement.from(pattern).to_triple.each_with_index.map do |v, i| if i == 1 SPARQL::Client.serialize_predicate(v) else SPARQL::Client.serialize_value(v, use_vars) end end end serialized_pattern.join(' ') + ' .' end end |
.serialize_predicate(value, rdepth = 0) ⇒ String
Serializes a SPARQL predicate
655 656 657 658 659 660 661 662 663 664 665 666 667 |
# File 'lib/sparql/client.rb', line 655 def self.serialize_predicate(value,rdepth=0) case value when nil RDF::Query::Variable.new.to_s when String then value when Array s = value.map{|v|serialize_predicate(v,rdepth+1)}.join rdepth > 0 ? "(#{s})" : s when RDF::Value # abbreviate RDF.type in the predicate position per SPARQL grammar value.equal?(RDF.type) ? 'a' : serialize_value(value) end end |
.serialize_uri(uri) ⇒ String
Serializes a URI or URI string into SPARQL syntax.
622 623 624 625 626 627 628 |
# File 'lib/sparql/client.rb', line 622 def self.serialize_uri(uri) case uri when String then RDF::NTriples.serialize(RDF::URI(uri)) when RDF::URI then RDF::NTriples.serialize(uri) else raise ArgumentError, "expected the graph URI to be a String or RDF::URI, but got #{uri.inspect}" end end |
.serialize_value(value, use_vars = false) ⇒ String
Serializes an ‘RDF::Value` into SPARQL syntax.
637 638 639 640 641 642 643 644 645 646 |
# File 'lib/sparql/client.rb', line 637 def self.serialize_value(value, use_vars = false) # SPARQL queries are UTF-8, but support ASCII-style Unicode escapes, so # the N-Triples serializer is fine unless it's a variable: case when value.nil? then RDF::Query::Variable.new.to_s when value.variable? then value.to_s when value.node? then (use_vars ? RDF::Query::Variable.new(value.id) : value) else RDF::NTriples.serialize(value) end end |
Instance Method Details
#ask(*args, **options) ⇒ Query
Executes a boolean ‘ASK` query.
145 146 147 |
# File 'lib/sparql/client.rb', line 145 def ask(*args, **) call_query_method(:ask, *args, **) end |
#call_query_method(meth, *args, **options) ⇒ Object
298 299 300 301 302 303 304 305 |
# File 'lib/sparql/client.rb', line 298 def call_query_method(meth, *args, **) client = self result = Query.send(meth, *args, **) (class << result; self; end).send(:define_method, :execute) do client.query(self) end result end |
#clear(what, *arguments) ⇒ void #clear(what, *arguments, **options) ⇒ void
Executes a ‘CLEAR` operation.
This requires that the endpoint support SPARQL 1.1 Update.
292 293 294 |
# File 'lib/sparql/client.rb', line 292 def clear(what, *arguments) self.update(Update::Clear.new(what, *arguments)) end |
#clear_graph(graph_uri, **options) ⇒ void
This method returns an undefined value.
Executes a ‘CLEAR GRAPH` operation.
This is a convenience wrapper for the #clear method.
257 258 259 |
# File 'lib/sparql/client.rb', line 257 def clear_graph(graph_uri, **) self.clear(:graph, graph_uri, **) end |
#close ⇒ void
This method returns an undefined value.
Closes a client instance by finishing the connection. The client is unavailable for any further data operations; an IOError is raised if such an attempt is made. I/O streams are automatically closed when they are claimed by the garbage collector.
134 135 136 137 138 |
# File 'lib/sparql/client.rb', line 134 def close @http.shutdown if @http @http = nil self end |
#construct(*args, **options) ⇒ Query
Executes a graph ‘CONSTRUCT` query.
172 173 174 |
# File 'lib/sparql/client.rb', line 172 def construct(*args, **) call_query_method(:construct, *args, **) end |
#delete_data(data, **options) ⇒ void
This method returns an undefined value.
Executes a ‘DELETE DATA` operation.
This requires that the endpoint support SPARQL 1.1 Update.
224 225 226 |
# File 'lib/sparql/client.rb', line 224 def delete_data(data, **) self.update(Update::DeleteData.new(data, **)) end |
#delete_insert(delete_graph, insert_graph = nil, where_graph = nil, **options) ⇒ void
This method returns an undefined value.
Executes a ‘DELETE/INSERT` operation.
This requires that the endpoint support SPARQL 1.1 Update.
240 241 242 |
# File 'lib/sparql/client.rb', line 240 def delete_insert(delete_graph, insert_graph = nil, where_graph = nil, **) self.update(Update::DeleteInsert.new(delete_graph, insert_graph, where_graph, **)) end |
#describe(*args, **options) ⇒ Query
Executes a ‘DESCRIBE` query.
163 164 165 |
# File 'lib/sparql/client.rb', line 163 def describe(*args, **) call_query_method(:describe, *args, **) end |
#insert_data(data, **options) ⇒ void
This method returns an undefined value.
Executes an ‘INSERT DATA` operation.
This requires that the endpoint support SPARQL 1.1 Update.
Note that for inserting non-trivial amounts of data, you probably ought to consider using the RDF store’s native bulk-loading facilities or APIs, as ‘INSERT DATA` operations entail comparably higher parsing overhead.
203 204 205 |
# File 'lib/sparql/client.rb', line 203 def insert_data(data, **) self.update(Update::InsertData.new(data, **)) end |
#inspect ⇒ String
Returns a developer-friendly representation of this object.
705 706 707 |
# File 'lib/sparql/client.rb', line 705 def inspect sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, url.to_s) end |
#inspect! ⇒ void
This method returns an undefined value.
Outputs a developer-friendly representation of this object to ‘stderr`.
697 698 699 |
# File 'lib/sparql/client.rb', line 697 def inspect! warn(inspect) end |
#nodes ⇒ Object
Returns a mapping of blank node results for this client.
311 312 313 |
# File 'lib/sparql/client.rb', line 311 def nodes @nodes ||= {} end |
#parse_rdf_serialization(response, **options) ⇒ RDF::Enumerable
607 608 609 610 611 612 613 614 |
# File 'lib/sparql/client.rb', line 607 def parse_rdf_serialization(response, **) = {content_type: response.content_type} unless [:content_type] if reader = RDF::Reader.for(**) reader.new(response.body) else raise RDF::ReaderError, "no RDF reader was found for #{}." end end |
#parse_response(response, **options) ⇒ Object
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/sparql/client.rb', line 397 def parse_response(response, **) case [:content_type] || response.content_type when NilClass response.body when RESULT_BOOL # Sesame-specific response.body == 'true' when RESULT_JSON self.class.parse_json_bindings(response.body, nodes) when RESULT_XML self.class.parse_xml_bindings(response.body, nodes) when RESULT_CSV self.class.parse_csv_bindings(response.body, nodes) when RESULT_TSV self.class.parse_tsv_bindings(response.body, nodes) else parse_rdf_serialization(response, **) end end |
#query(query, **options) ⇒ Array<RDF::Query::Solution>
Executes a SPARQL query and returns the parsed results.
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/sparql/client.rb', line 325 def query(query, **) @op = :query @alt_endpoint = [:endpoint] case @url when RDF::Queryable require 'sparql' unless defined?(::SPARQL::Grammar) begin SPARQL.execute(query, @url, optimize: true, **) rescue SPARQL::MalformedQuery $stderr.puts "error running #{query}: #{$!}" raise end else parse_response(response(query, **), **) end end |
#response(query, **options) ⇒ String
Executes a SPARQL query and returns the Net::HTTP::Response of the result.
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/sparql/client.rb', line 376 def response(query, **) headers = [:headers] || @headers headers['Accept'] = [:content_type] if [:content_type] request(query, headers) do |response| case response when Net::HTTPBadRequest # 400 Bad Request raise MalformedQuery.new(response.body + " Processing query #{query}") when Net::HTTPClientError # 4xx raise ClientError.new(response.body + " Processing query #{query}") when Net::HTTPServerError # 5xx raise ServerError.new(response.body + " Processing query #{query}") when Net::HTTPSuccess # 2xx response end end end |
#select(*args, **options) ⇒ Query
Executes a tuple ‘SELECT` query.
154 155 156 |
# File 'lib/sparql/client.rb', line 154 def select(*args, **) call_query_method(:select, *args, **) end |
#update(query, **options) ⇒ void
This method returns an undefined value.
Executes a SPARQL update operation.
353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/sparql/client.rb', line 353 def update(query, **) @op = :update @alt_endpoint = [:endpoint] case @url when RDF::Queryable require 'sparql' unless defined?(::SPARQL::Grammar) SPARQL.execute(query, @url, update: true, optimize: true, **) else response(query, **) end self end |