class RDF::AllegroGraph::AbstractRepository
Features shared by regular AllegroGraph
repositories and by persistent backend sessions.
Note that this class does not interoperate well with the Unix ‘fork` command if you’re using blank nodes. See README.md for details.
Attributes
This code is based on blog.datagraph.org/2010/04/rdf-repository-howto
For comparison purposes, here’s a list of other RDF::Repository implementations:
github.com/fumi/rdf-4store/blob/master/lib/rdf/four_store/repository.rb github.com/bendiken/rdf-bert/blob/master/lib/rdf/bert/client.rb github.com/bendiken/rdf-cassandra/blob/master/lib/rdf/cassandra/repository.rb (more complete than many) github.com/bhuga/rdf-do/blob/master/lib/rdf/do.rb github.com/pius/rdf-mongo/blob/master/lib/rdf/mongo.rb github.com/njh/rdf-redstore/blob/master/lib/rdf/redstore/repository.rb github.com/bendiken/rdf-sesame/blob/master/lib/rdf/sesame/repository.rb github.com/bhuga/rdf-talis/blob/master/lib/rdf/talis/repository.rb github.com/bendiken/sparql-client/blob/master/lib/sparql/client/repository.rb
We actually stack up pretty well against this list.
This code is based on blog.datagraph.org/2010/04/rdf-repository-howto
For comparison purposes, here’s a list of other RDF::Repository implementations:
github.com/fumi/rdf-4store/blob/master/lib/rdf/four_store/repository.rb github.com/bendiken/rdf-bert/blob/master/lib/rdf/bert/client.rb github.com/bendiken/rdf-cassandra/blob/master/lib/rdf/cassandra/repository.rb (more complete than many) github.com/bhuga/rdf-do/blob/master/lib/rdf/do.rb github.com/pius/rdf-mongo/blob/master/lib/rdf/mongo.rb github.com/njh/rdf-redstore/blob/master/lib/rdf/redstore/repository.rb github.com/bendiken/rdf-sesame/blob/master/lib/rdf/sesame/repository.rb github.com/bhuga/rdf-talis/blob/master/lib/rdf/talis/repository.rb github.com/bendiken/sparql-client/blob/master/lib/sparql/client/repository.rb
We actually stack up pretty well against this list.
Public Class Methods
Create a new AllegroGraph
repository adapter.
@param [AllegroGraph::Resource] resource
The underlying 'agraph'-based implementation to wrap.
@private
# File lib/rdf/allegro_graph/abstract_repository.rb, line 37 def initialize(resource, options={}) @resource = resource @resource_writable = options[:writable_repository] || resource @blank_nodes = [] @blank_nodes_to_generate = 8 @blank_nodes_local_to_server = {} @blank_nodes_server_to_local = {} self.global_query_options = options[:query] end
Public Instance Methods
Construct an AllegroGraph-specific query.
@param [Hash{Symbol => Object}] query_options @option query_options [true,false,String] :infer
The AllegroGraph inference mode to use. Defaults to `false`. The value `true` is equivalent to `'rdfs++'`.
@yield query @yieldparam [Query] The query to build. Use the Query
API to add
patterns and functors.
@yieldreturn [void] @return [Query]
@see Query
@see RDF::Query
# File lib/rdf/allegro_graph/abstract_repository.rb, line 300 def build_query(query_options={}, &block) Query.new(self, query_options, &block) end
Clear all statements from the repository.
@param [Hash] options @option options [String] :subject Match a specific subject @option options [String] :predicate Match a specific predicate @option options [String] :object Match a specific object @option options [String] :context Match a specific graph name. @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 372 def clear(options = {}) @resource_writable.statements.delete(options) end
Iterate over all statements in the repository. This is used by RDF::Enumerable as a fallback for handling any unimplemented methods.
@yield [statement] @yieldparam [RDF::Statement] statement @yieldreturn [void] @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 99 def each(&block) query_pattern(RDF::Query::Pattern.new, &block) end
Set the global query options that will be used at each request. Current supported options are :offset, :limit and :infer.
@param [Hash] options the options to set
www.franz.com/agraph/support/documentation/current/http-protocol.html#get-post-repo
# File lib/rdf/allegro_graph/abstract_repository.rb, line 64 def global_query_options=(options) @global_query_options = filter_query_options(options) end
Does the repository contain the specified statement?
@param [RDF::Statement] statement @return [Boolean]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 107 def has_statement?(statement) found = @resource.statements.find(statement_to_dict(statement)) !found.empty? end
Run a raw Prolog query.
@overload prolog_query
(query) {|solution| … }
@yield solution @yieldparam [RDF::Query::Solution] solution @yieldreturn [void] @return [void]
@overload prolog_query
(pattern)
@return [Enumerator<RDF::Query::Solution>]
@param [String] query The query to run. @param [Hash{Symbol => Object}] query_options
The query options (see build_query).
@return [void] @note This function returns a single-use Enumerator! If you want to
to treat the results as an array, call `to_a` on it, or you will re-run the query against the server repeatedly. This curious decision is made for consistency with RDF.rb.
@see build_query
# File lib/rdf/allegro_graph/abstract_repository.rb, line 255 def prolog_query(query, query_options={}, &block) raw_query(:prolog, query, query_options, &block) end
Serialize an RDF::Value for transmission to the server. This is exported for low-level libraries that need to access our serialization and deserialization machinery, which has special-case support for RDF
nodes.
@param [RDF::Value,RDF::Query::Variable] value @return [String] @see serialize_prolog
# File lib/rdf/allegro_graph/abstract_repository.rb, line 388 def serialize(value) case value when RDF::Query::Variable then value.to_s when false then nil else RDF::NTriples::Writer.serialize(map_to_server(value)) end end
Serialize an RDF::Value for use in a Prolog expression that will be transmitted to the server.
@param [RDF::Value,RDF::Query::Variable] value @return [String] @see serialize
# File lib/rdf/allegro_graph/abstract_repository.rb, line 402 def serialize_prolog(value) case value when RDF::AllegroGraph::Query::PrologLiteral then value.to_s when RDF::Query::Variable then value.to_s else "!#{serialize(value)}" end end
Returns the amount of statements in the repository, as an integer
@return [Integer] the number of statements
# File lib/rdf/allegro_graph/abstract_repository.rb, line 71 def size @resource.size end
Run a raw SPARQL query.
@overload sparql_query
(query) {|solution| … }
@yield solution @yieldparam [RDF::Query::Solution] solution @yieldreturn [void] @return [void]
@overload sparql_query
(pattern)
@return [Enumerator<RDF::Query::Solution>]
@param [String] query The query to run. @param [Hash{Symbol => Object}] query_options
The query options (see build_query).
@return [void] @note This function returns a single-use Enumerator! If you want to
to treat the results as an array, call `to_a` on it, or you will re-run the query against the server repeatedly. This curious decision is made for consistency with RDF.rb.
@see build_query
# File lib/rdf/allegro_graph/abstract_repository.rb, line 229 def sparql_query(query, query_options={}, &block) query_options[:type] = query.split(' ').first.downcase.to_sym unless query.empty? raw_query(:sparql, query, query_options, &block) end
Returns true if ‘feature` is supported.
@param [Symbol] feature @return [Boolean]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 51 def supports?(feature) case feature.to_sym when :context then true else super end end
Protected Instance Methods
Allocate an “official” AllegroGraph
blank node, which should maintain its identity across requests.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 524 def allocate_blank_node if @blank_nodes.empty? @blank_nodes = generate_blank_nodes(@blank_nodes_to_generate).reverse @blank_nodes_to_generate *= 2 end @blank_nodes.pop end
Return true if this a blank RDF
node.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 510 def blank_node?(value) !value.nil? && value.anonymous? end
Delete a single statement from the repository.
@param [RDF::Statement] statement @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 343 def delete_statement(statement) # TODO: Do we need to handle invalid statements here by turning them # into queries and deleting all matching statements? delete_statements([statement]) end
Delete multiple statements from the repository.
@param [Array<RDF::Statement>] statements @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 354 def delete_statements(statements) json = statements_to_json(statements) @resource_writable.request_json(:post, path_writable('statements/delete'), :body => json, :expected_status_code => 204) end
@private
# File lib/rdf/allegro_graph/abstract_repository.rb, line 571 def filter_query_options(options) options ||= {} filtered_options = {} [ :limit, :infer, :offset ].each do |key| filtered_options.merge! key => options[key] if options.has_key?(key) end filtered_options end
Ask AllegroGraph
to generate a series of blank node IDs.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 515 def generate_blank_nodes(amount) response = @resource.request_http(:post, path(:blankNodes), :parameters => { :amount => amount }, :expected_status_code => 200) response.chomp.split("\n").map {|i| i.gsub(/^_:/, '') } end
Insert a single statement into the repository.
@param [RDF::Statement] statement @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 312 def insert_statement(statement) insert_statements([statement]) end
Insert multiple statements at once.
@param [Array<RDF::Statement>] statements @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 321 def insert_statements(statements) # FIXME: RDF.rb expects duplicate statements to be ignored if # inserted into a mutable store, but AllegoGraph allows duplicate # statements. We work around this in our other methods, but we # need to either use transactions, find appropriate AllegroGraph # documentation, or talk to the RDF.rb folks. # # A discussion of duplicate RDF statements: # http://lists.w3.org/Archives/Public/www-rdf-interest/2004Oct/0091.html # # Note that specifying deleteDuplicates on repository creation doesn't # seem to affect this. json = statements_to_json(statements) @resource_writable.request_json(:post, path_writable(:statements), :body => json, :expected_status_code => 204) end
Convert a JSON triples list to a RDF::Graph object.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 502 def json_to_graph(json) statements = json.map {|t| RDF::Statement.new(unserialize(t[0]), unserialize(t[1]), unserialize(t[2]))} graph = RDF::Graph.new graph.insert_statements(statements) graph end
Convert a JSON query solution to a list of RDF::Query::Solution objects.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 487 def json_to_query_solutions(json) names = json['names'].map {|n| n.to_sym } json['values'].map do |match| hash = {} names.each_with_index do |name, i| # TODO: I'd like to include nil values, too, but # RDF::Query#execute does not yet do so, so we'll filter them for # now. hash[name] = unserialize(match[i]) unless match[i].nil? end RDF::Query::Solution.new(hash) end end
Create a mapping between a local blank node ID and a server-side blank node ID.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 534 def map_blank_node(local_id, server_id) #puts "Mapping #{local_id} -> #{server_id}" @blank_nodes_local_to_server[local_id] = server_id @blank_nodes_server_to_local[server_id] = local_id end
Translate this value to a client-specific representation, taking care to handle blank nodes correctly.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 553 def map_from_server(value) return value unless blank_node?(value) if @blank_nodes_server_to_local.has_key?(value.id) RDF::Node.new(@blank_nodes_server_to_local[value.id]) else # We didn't generate this node ID, so we want to pass it back to # the server unchanged. map_blank_node(value.id, value.id) value end end
Translate this value to a server-specific representation, taking care to handle blank nodes correctly.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 542 def map_to_server(value) return value unless blank_node?(value) unless @blank_nodes_local_to_server.has_key?(value.id) new_id = allocate_blank_node map_blank_node(value.id, new_id) end RDF::Node.new(@blank_nodes_local_to_server[value.id]) end
Build a repository-relative path.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 414 def path(relative_path=nil) if relative_path "#{@resource.path}/#{relative_path}" else @resource.path end end
Build a repository-relative path for the writable mirror
# File lib/rdf/allegro_graph/abstract_repository.rb, line 423 def path_writable(relative_path=nil) if relative_path "#{@resource_writable.path}/#{relative_path}" else @resource_writable.path end end
Run an RDF::Query on the server.
@param [RDF::Query] query The query to execute. @yield solution @yieldparam [RDF::Query::Solution] solution @yieldreturn [void]
@see RDF::Queryable#query @see RDF::Query#execute
# File lib/rdf/allegro_graph/abstract_repository.rb, line 189 def query_execute(query, &block) query_options = if query.respond_to?(:query_options) query.query_options else {} end if query.respond_to?(:requires_prolog?) && query.requires_prolog? prolog_query(query.to_prolog(self), query_options, &block) else sparql_query(query_to_sparql(query), query_options, &block) end end
Find all RDF
statements matching a pattern.
@overload query_pattern
(pattern) {|statement| … }
@yield statement @yieldparam [RDF::Statement] statement @yieldreturn [void] @return [void]
@overload query_pattern
(pattern)
@return [Enumerator]
@param [RDF::Query::Pattern] pattern A simple pattern to match. @return [void]
# File lib/rdf/allegro_graph/abstract_repository.rb, line 155 def query_pattern(pattern) if block_given? seen = {} dict = statement_to_dict(pattern) dict.delete(:context) if dict[:context] == 'null' @resource.statements.find(dict).each do |statement| unless seen.has_key?(statement) seen[statement] = true s,p,o,c = statement.map {|v| unserialize(v) } if c.nil? yield RDF::Statement.new(s,p,o) else yield RDF::Statement.new(s,p,o, :context => c) end end end else enum_for(:query_pattern, pattern) end end
Convert a query to SPARQL.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 469 def query_to_sparql(query) variables = [] patterns = [] query.patterns.each do |p| variables.concat(p.variables.values) triple = [p.subject, p.predicate, p.object] str = triple.map {|v| serialize(v) }.join(" ") # TODO: Wrap in graph block for context! if p.optional? str = "OPTIONAL { #{str} }" end patterns << "#{str} ." end "SELECT #{variables.uniq.join(" ")}\nWHERE {\n #{patterns.join("\n ")} }" end
Run a raw query in the specified language.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 260 def raw_query(language, query, query_options={}, &block) # Build our query parameters. params = { :query => query, :queryLn => language.to_s }.merge!(@global_query_options).merge!(filter_query_options(query_options)) # Run the query and process the results. json = @resource.request_json(:get, path, :parameters => params, :expected_status_code => 200) # Parse the result (depends on the type of the query) if language == :sparql and query_options[:type] == :construct results = json_to_graph(json) else results = json_to_query_solutions(json) results = enum_for(:raw_query, language, query) unless block_given? end if block_given? results.each {|s| yield s } else results end end
Translate a RDF::Statement into a dictionary the we can pass directly to the ‘agraph’ gem.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 456 def statement_to_dict(statement) { :subject => serialize(statement.subject), :predicate => serialize(statement.predicate), :object => serialize(statement.object), # We have to pass the null context explicitly if we only want # to operate a single statement. Otherwise, we will operate # on all matching s,p,o triples regardless of context. :context => serialize(statement.context) || 'null' }.merge!(@global_query_options) end
Convert a list of statements to a JSON-compatible array.
# File lib/rdf/allegro_graph/abstract_repository.rb, line 446 def statements_to_json(statements) statements.map do |s| tuple = [s.subject, s.predicate, s.object] tuple << s.context if s.context tuple.map {|v| serialize(v) } end end
Deserialize an RDF::Value received from the server, or an array of such values when working with Prolog queries.
@param [String,Array] str_or_array
A string, or a possibly-nested array of strings.
@return [RDF::Value] @see serialize
# File lib/rdf/allegro_graph/abstract_repository.rb, line 438 def unserialize(str_or_array) case str_or_array when Array then str_or_array.map {|v| unserialize(v) } else map_from_server(RDF::NTriples::Reader.unserialize(str_or_array)) end end