rdf-blazegraph 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8e2e77f00c85d5d33fa1138638238040525b0311
4
+ data.tar.gz: d3cc73a2b6bd389a3206999ff1a8105d8764f065
5
+ SHA512:
6
+ metadata.gz: 34f1525cf7ded366446347c4b6721649943f6674d2e0f8bd03b494386cd8876c7671e891a664db3b6a3e282aebbf88eb851bc20a9d4a953aeee817fce236f014
7
+ data.tar.gz: 612f391cff097c9631f03f08b14d4350690a3c7db1bc10703043e3d72f8975d888eb4d5c60bca8df26e7362d15bde483846c19d908be48906e072d51f6a8f102
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ * Tom Johnson <tom@dp.la>
2
+
@@ -0,0 +1,33 @@
1
+ RDF::Blazegraph
2
+ ================
3
+
4
+ ## Running the Tests
5
+
6
+ ```bash
7
+ $ bundle install
8
+ $ bundle exec rspec
9
+ ```
10
+
11
+ ## Contributing
12
+
13
+ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.
14
+
15
+ * Do your best to adhere to the existing coding conventions and idioms.
16
+ * Don't use hard tabs, and don't leave trailing whitespace on any line.
17
+ Before committing, run `git diff --check` to make sure of this.
18
+ * Do document every method you add using YARD annotations. Read the
19
+ [tutorial][YARD-GS] or look at the existing code for examples.
20
+ * Don't touch the `.gemspec` or `VERSION` files. If you need to change them,
21
+ do so on your private branch only.
22
+ * Do feel free to add yourself to the `CREDITS` file and the
23
+ corresponding list in the the `README`. Alphabetical order applies.
24
+ * Don't touch the `AUTHORS` file. If your contributions are significant
25
+ enough, be assured we will eventually add you in there.
26
+ * Do note that in order for us to merge any non-trivial changes (as a rule
27
+ of thumb, additions larger than about 15 lines of code), we need an
28
+ explicit [public domain dedication][PDD] on record from you.
29
+
30
+ ## License
31
+
32
+ This is free and unencumbered public domain software. For more information,
33
+ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,19 @@
1
+ require 'rdf'
2
+ require 'sparql/client'
3
+ require 'rexml/document'
4
+ require 'net/http/persistent'
5
+
6
+ module RDF
7
+ ##
8
+ # This module bundles tools for working with Blazegraph using RDF.rb.
9
+ #
10
+ # @see http://ruby-rdf.github.io
11
+ # @see http://wiki.blazegraph.com
12
+ module Blazegraph
13
+ VOCAB_BD = RDF::Vocabulary.new('http://www.bigdata.com/rdf#')
14
+ NULL_GRAPH_URI = Blazegraph::VOCAB_BD.nullGraph
15
+
16
+ autoload :Repository, 'rdf/blazegraph/repository'
17
+ autoload :RestClient, 'rdf/blazegraph/rest_client'
18
+ end
19
+ end
@@ -0,0 +1,147 @@
1
+ module RDF::Blazegraph
2
+ ##
3
+ # An RDF::Repository implementaton for Blazegraph (formerly BigData).
4
+ #
5
+ # @see RDF::Repository
6
+ class Repository < SPARQL::Client::Repository
7
+ ##
8
+ # @return [RDF::Blazegraph::RestClient]
9
+ def rest_client
10
+ @rest_client = RestClient.new(@client.url)
11
+ end
12
+
13
+ ##
14
+ # @see RDF::Enumerable#count
15
+ def count
16
+ rest_client.fast_range_count
17
+ end
18
+
19
+ ##
20
+ # @see RDF::Repository#each
21
+ def each(&block)
22
+ rest_client.get_statements.each_statement(&block)
23
+ end
24
+
25
+ ##
26
+ # @see RDF::Repository#empty?
27
+ def empty?
28
+ !rest_client.has_statement?
29
+ end
30
+
31
+ ##
32
+ # @see RDF::Repository#has_predicate?
33
+ def has_context?(context)
34
+ rest_client.has_statement?(context: context)
35
+ end
36
+
37
+ ##
38
+ # @see RDF::Repository#has_predicate?
39
+ def has_predicate?(predicate)
40
+ rest_client.has_statement?(predicate: predicate)
41
+ end
42
+
43
+ ##
44
+ # @see RDF::Repository#has_object?
45
+ def has_object?(object)
46
+ return super if object.node?
47
+ rest_client.has_statement?(object: object)
48
+ end
49
+
50
+ ##
51
+ # @see RDF::Repository#has_triple?
52
+ def has_triple?(triple)
53
+ return super unless triple.find(&:node?).nil?
54
+ rest_client.has_statement?(subject: triple[0],
55
+ predicate: triple[1],
56
+ object: triple[2])
57
+ end
58
+
59
+ ##
60
+ # Calls the Blazegraph API unless a blank node is present, in which case we
61
+ # fall back on SPARQL ASK.
62
+ # @see RDF::Repostiory#has_statement?
63
+ def has_statement?(statement)
64
+ has_quad?(statement)
65
+ end
66
+
67
+ ##
68
+ # @see RDF::Repository#has_subject?
69
+ def has_subject?(subject)
70
+ return super if subject.node?
71
+ rest_client.has_statement?(subject: subject)
72
+ end
73
+
74
+ ##
75
+ # @see RDF::Repository#has_subject?
76
+ def has_quad?(statement)
77
+ statement = RDF::Statement.from(statement)
78
+ rest_client.has_statement?(subject: statement.subject,
79
+ predicate: statement.predicate,
80
+ object: statement.object,
81
+ context: statement.context)
82
+ end
83
+
84
+ ##
85
+ # @see SPARQL::Client::Repository#supports?
86
+ def supports?(feature)
87
+ return true if feature.to_sym == :context
88
+ super
89
+ end
90
+
91
+ protected
92
+
93
+ ##
94
+ # Deletes the given RDF statements from the underlying storage.
95
+ #
96
+ # Overridden here to use SPARQL/UPDATE
97
+ #
98
+ # @param [RDF::Enumerable] statements
99
+ # @return [void]
100
+ def delete_statements(statements)
101
+ @rest_client.delete(statements)
102
+ end
103
+
104
+ ##
105
+ # Queries `self` for RDF statements matching the given `pattern`.
106
+ #
107
+ # @see SPARQL::Client::Repository#query_pattern
108
+ def query_pattern(pattern, &block)
109
+ pattern = pattern.dup
110
+ pattern.subject ||= RDF::Query::Variable.new
111
+ pattern.predicate ||= RDF::Query::Variable.new
112
+ pattern.object ||= RDF::Query::Variable.new
113
+ pattern.initialize!
114
+
115
+ # Blazegraph objects to bnodes shared across the CONSTRUCT & WHERE scopes
116
+ # so we dup the pattern with fresh bnodes
117
+ where_pattern = pattern.dup
118
+ where_pattern.subject = RDF::Node.new if where_pattern.subject.node?
119
+ where_pattern.predicate = RDF::Node.new if where_pattern.predicate.node?
120
+ where_pattern.object = RDF::Node.new if where_pattern.object.node?
121
+ where_pattern.initialize!
122
+
123
+ query = client.construct(pattern).where(where_pattern)
124
+
125
+ unless where_pattern.context.nil?
126
+ where_pattern.context ||= NULL_GRAPH_URI
127
+ query.graph(where_pattern.context)
128
+ query.filter("#{where_pattern.context} != #{NULL_GRAPH_URI.to_base}") if
129
+ where_pattern.context.variable?
130
+ end
131
+
132
+ if block_given?
133
+ query.each_statement(&block)
134
+ else
135
+ query.solutions.to_a.extend(RDF::Enumerable, RDF::Queryable)
136
+ end
137
+ end
138
+
139
+ def insert_statements(statements)
140
+ rest_client.insert(statements)
141
+ end
142
+
143
+ def insert_statement(statement)
144
+ rest_client.insert([statement])
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,219 @@
1
+ module RDF::Blazegraph
2
+ ##
3
+ # A wrapper for the NanoSparqlServer REST API. This implements portions of
4
+ # the API that are not parts of SPARQL; such as optimized queries like
5
+ # FastRangeCount.
6
+ #
7
+ # @see https://wiki.blazegraph.com/wiki/index.php/REST_API NanoSparqlServer
8
+ # REST documentation
9
+ class RestClient
10
+ attr_reader :url
11
+
12
+ ##
13
+ # @param [#to_s] uri a uri identifying the Blazegraph API endpoint; e.g.
14
+ # `http://localhost:9999/bigdata/sparql`
15
+ def initialize(url)
16
+ @http = Net::HTTP::Persistent.new(self.class)
17
+ @url = URI(url.to_s)
18
+ @sparql_client = SPARQL::Client.new(@url)
19
+ end
20
+
21
+ ##
22
+ # Send a request to the server
23
+ #
24
+ # @param [String] query
25
+ #
26
+ # @return [Net::HTTP::Response] the server's response
27
+ # @raise [RequestError] if the request returns a non-success response code
28
+ def execute(query)
29
+ response = @http.request(url + ::URI::encode(query))
30
+
31
+ return response if response.is_a? Net::HTTPSuccess
32
+ raise RequestError.new("#{response.code}: #{response.body}\n" \
33
+ "Processing query #{query}")
34
+ end
35
+
36
+ ##
37
+ # Returns a count of the number of triples in the datastore.
38
+ #
39
+ # We use the `exact` option, to ensure that counts are exact regardless
40
+ # of sharding and other configuration conditions.
41
+ #
42
+ # Errors are thrown if a bnode is given for any of the terms or if a
43
+ # literal is given in the subject or predicate place.
44
+ #
45
+ # @param [Boolean] exact whether to insist on an exact range count, or
46
+ # allow approximations e.g. across multiple shards; default: `true`
47
+ #
48
+ # @return [Integer] the count of triples matching the query
49
+ #
50
+ # @raise [RequestError] if the request is invalid or the server throws an
51
+ # error
52
+ def fast_range_count(subject: nil, predicate: nil, object: nil,
53
+ context: nil, exact: true)
54
+ st_query = access_path_query(subject, predicate, object, context)
55
+ resp = execute("?ESTCARD#{st_query}&exact=#{exact}")
56
+ read_xml_response(resp, :rangeCount).to_i
57
+ end
58
+
59
+ ##
60
+ # Returns statements matching the given pattern.
61
+ #
62
+ # Errors are thrown if a bnode is given for any of the terms or if a
63
+ # literal is given in the subject or predicate place.
64
+ #
65
+ # @param [Boolean] include_inferred includes inferred triples if `true',
66
+ # default: `false`
67
+ #
68
+ # @return [RDF::Enumerable] statements parsed from the server response
69
+ #
70
+ # @raise [RequestError] if the request is invalid or the server throws an
71
+ # error
72
+ def get_statements(subject: nil, predicate: nil, object: nil,
73
+ context: nil, include_inferred: false)
74
+ st_query = access_path_query(subject, predicate, object, context)
75
+ query = "?GETSTMTS#{st_query}&include_inferred=#{include_inferred}"
76
+ read_rdf_response(execute(query))
77
+ end
78
+
79
+ ##
80
+ # Checks for existence of a statement matching the pattern.
81
+ #
82
+ # Errors are thrown if a bnode is given for any of the terms or if a
83
+ # literal is given in the subject or predicate place.
84
+ #
85
+ # @param [Boolean] include_inferred includes inferred triples if `true',
86
+ # default: `false`
87
+ #
88
+ # @return [Boolean] true if statement matching the pattern exists
89
+ #
90
+ # @raise [RequestError] if the request is invalid or the server throws an
91
+ # error
92
+ def has_statement?(subject: nil, predicate: nil, object: nil,
93
+ context: nil, include_inferred: false)
94
+ if [subject, predicate, object].compact.find(&:node?)
95
+ sparql_ask_statement([subject, predicate, object], context)
96
+ else
97
+ st_query = access_path_query(subject, predicate, object, context)
98
+ query = "?HASSTMT#{st_query}&include_inferred=#{include_inferred}"
99
+
100
+ read_boolean_response(execute(query))
101
+ end
102
+ end
103
+
104
+ ##
105
+ # @param [RDF::Enumerable] statements
106
+ #
107
+ # @todo handle blank nodes
108
+ def insert(statements)
109
+ send_post_request(url, statements)
110
+ return self
111
+ end
112
+
113
+ ##
114
+ # @param [RDF::Enumerable] statements
115
+ #
116
+ # @todo handle blank nodes
117
+ def delete(statements)
118
+ return self if statements.empty?
119
+
120
+ statements.map! do |s|
121
+ statement = RDF::Statement.from(s)
122
+ statement.context ||= NULL_GRAPH_URI
123
+ statement
124
+ end
125
+
126
+ constant = statements.all? do |statement|
127
+ !statement.respond_to?(:each_statement) && statement.constant? &&
128
+ !statement.has_blank_nodes?
129
+ end
130
+
131
+ if constant
132
+ send_post_request(url + '?delete', statements)
133
+ else
134
+ # delete with blank nodes
135
+ end
136
+
137
+ return self
138
+ end
139
+
140
+ private
141
+
142
+ def sparql_ask_statement(triple, context)
143
+ triple.map! do |term|
144
+ unless term.nil?
145
+ term.node? ? RDF::Query::Variable.new(term.id) : term
146
+ end
147
+ end
148
+
149
+ query = @sparql_client.ask.where(triple)
150
+ query.graph(context) if context
151
+ triple.each do |term|
152
+ query.filter("isBlank(#{term})") if !term.nil? && term.variable?
153
+ end
154
+
155
+ query.true?
156
+ end
157
+
158
+ def send_post_request(request_url, statements)
159
+ io = StringIO.new
160
+ writer = RDF::Writer.for(:nquads)
161
+
162
+ io.rewind
163
+
164
+ request = Net::HTTP::Post.new(request_url)
165
+ request['Content-Type'] =
166
+ RDF::Writer.for(:nquads).format.content_type.last # use text/x-nquads
167
+ request.body = writer.dump(statements)
168
+
169
+ @http.request(url, request)
170
+ end
171
+
172
+ ##
173
+ # @param [Net::HTTPResponse] response
174
+ # @return [RDF::Enumerable]
175
+ def read_rdf_response(response)
176
+ RDF::Reader.for(content_type: response.content_type).new(response.body)
177
+ end
178
+
179
+ ##
180
+ # @param [Net::HTTPResponse] response
181
+ # @return [RDF::Enumerable]
182
+ def read_boolean_response(response)
183
+ read_xml_response(response, :result) == 'true' ? true : false
184
+ end
185
+
186
+ ##
187
+ # @param [Net::HTTPResponse] response
188
+ # @param [Net::HTTPResponse] attr
189
+ # @return [RDF::Enumerable]
190
+ def read_xml_response(response, attr)
191
+ REXML::Document.new(response.body).root.attribute(attr).value
192
+ end
193
+
194
+ ##
195
+ # @param [RDF::URI, RDF::Literal] subject
196
+ # @param [RDF::URI, RDF::Literal] predicate
197
+ # @param [RDF::URI, RDF::Literal] object
198
+ # @param [RDF::URI, RDF::Literal] context
199
+ #
200
+ # @return [String] a query string for "Access Path Operations"
201
+ #
202
+ # @todo: fail fast when given a literal predicate or a bnode? Currently we
203
+ # try the request on Blazegraph and handle the Net::HTTP response.
204
+ #
205
+ # @see https://wiki.blazegraph.com/wiki/index.php/REST_API#Access_Path_Operations
206
+ def access_path_query(subject, predicate, object, context)
207
+ str = {s: subject, p: predicate, o: object, c: context}.map do |k, v|
208
+ v ? "#{k}=#{v.to_base}" : nil
209
+ end.compact.join('&')
210
+ str.empty? ? str : "&#{str}"
211
+ end
212
+
213
+ public
214
+
215
+ ##
216
+ # An error class to capture non-succesful RestClient responses
217
+ class RequestError < RuntimeError; end
218
+ end
219
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdf-blazegraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom Johnson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sparql-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdf-spec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.1.13
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '1.1'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.1.13
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec-its
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: yard
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.8'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.8'
103
+ description: A Blazegraph Repository adapter for RDF.rb
104
+ email: public-rdf-ruby@w3.org
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - AUTHORS
110
+ - README.md
111
+ - UNLICENSE
112
+ - VERSION
113
+ - lib/rdf/blazegraph.rb
114
+ - lib/rdf/blazegraph/repository.rb
115
+ - lib/rdf/blazegraph/rest_client.rb
116
+ homepage: http://ruby-rdf.github.com/
117
+ licenses:
118
+ - Public Domain
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ - app
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 1.9.2
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.2.1
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: A Blazegraph Repository adapter for RDF.rb
141
+ test_files: []
142
+ has_rdoc: false