dpla-sparql-client 1.1.3.2.pre.dpla.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,412 @@
1
+ module SPARQL; class Client
2
+ ##
3
+ # A SPARQL query builder.
4
+ #
5
+ # @example Iterating over all found solutions
6
+ # query.each_solution { |solution| puts solution.inspect }
7
+ #
8
+ class Query < RDF::Query
9
+ ##
10
+ # @return [Symbol]
11
+ # @see http://www.w3.org/TR/sparql11-query/#QueryForms
12
+ attr_reader :form
13
+
14
+ ##
15
+ # @return [Hash{Symbol => Object}]
16
+ attr_reader :options
17
+
18
+ ##
19
+ # @return [Array<[key, RDF::Value]>]
20
+ attr_reader :values
21
+
22
+ ##
23
+ # Creates a boolean `ASK` query.
24
+ #
25
+ # @param [Hash{Symbol => Object}] options
26
+ # @return [Query]
27
+ # @see http://www.w3.org/TR/sparql11-query/#ask
28
+ def self.ask(options = {})
29
+ self.new(:ask, options)
30
+ end
31
+
32
+ ##
33
+ # Creates a tuple `SELECT` query.
34
+ #
35
+ # @param [Array<Symbol>] variables
36
+ # @return [Query]
37
+ #
38
+ # @overload self.select(*variables, options)
39
+ # @param [Array<Symbol>] variables
40
+ # @return [Query]
41
+ # @see http://www.w3.org/TR/sparql11-query/#select
42
+ def self.select(*variables)
43
+ options = variables.last.is_a?(Hash) ? variables.pop : {}
44
+ self.new(:select, options).select(*variables)
45
+ end
46
+
47
+ ##
48
+ # Creates a `DESCRIBE` query.
49
+ #
50
+ # @param [Array<Symbol, RDF::URI>] variables
51
+ # @return [Query]
52
+ #
53
+ # @overload self.describe(*variables, options)
54
+ # @param [Array<Symbol, RDF::URI>] variables
55
+ # @return [Query]
56
+ # @see http://www.w3.org/TR/sparql11-query/#describe
57
+ def self.describe(*variables)
58
+ options = variables.last.is_a?(Hash) ? variables.pop : {}
59
+ self.new(:describe, options).describe(*variables)
60
+ end
61
+
62
+ ##
63
+ # Creates a graph `CONSTRUCT` query.
64
+ #
65
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
66
+ # @return [Query]
67
+ #
68
+ # @overload self.construct(*variables, options)
69
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
70
+ # @param [Hash{Symbol => Object}] options
71
+ # @return [Query]
72
+ # @see http://www.w3.org/TR/sparql11-query/#construct
73
+ def self.construct(*patterns)
74
+ options = patterns.last.is_a?(Hash) ? patterns.pop : {}
75
+ self.new(:construct, options).construct(*patterns) # FIXME
76
+ end
77
+
78
+ ##
79
+ # @param [Symbol, #to_s] form
80
+ # @overload self.construct(*variables, options)
81
+ # @param [Symbol, #to_s] form
82
+ # @param [Hash{Symbol => Object}] options
83
+ # @yield [query]
84
+ # @yieldparam [Query]
85
+ def initialize(form = :ask, options = {}, &block)
86
+ @subqueries = []
87
+ @form = form.respond_to?(:to_sym) ? form.to_sym : form.to_s.to_sym
88
+ super([], options, &block)
89
+ end
90
+
91
+ ##
92
+ # @return [Query]
93
+ # @see http://www.w3.org/TR/sparql11-query/#ask
94
+ def ask
95
+ @form = :ask
96
+ self
97
+ end
98
+
99
+ ##
100
+ # @param [Array<Symbol>] variables
101
+ # @return [Query]
102
+ # @see http://www.w3.org/TR/sparql11-query/#select
103
+ def select(*variables)
104
+ @values = variables.map { |var| [var, RDF::Query::Variable.new(var)] }
105
+ self
106
+ end
107
+
108
+ ##
109
+ # @param [Array<Symbol>] variables
110
+ # @return [Query]
111
+ # @see http://www.w3.org/TR/sparql11-query/#describe
112
+ def describe(*variables)
113
+ @values = variables.map { |var|
114
+ [var, var.is_a?(RDF::URI) ? var : RDF::Query::Variable.new(var)]
115
+ }
116
+ self
117
+ end
118
+
119
+ ##
120
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
121
+ # @return [Query]
122
+ # @see http://www.w3.org/TR/sparql11-query/#construct
123
+ def construct(*patterns)
124
+ options[:template] = build_patterns(patterns)
125
+ self
126
+ end
127
+
128
+ # @param [RDF::URI] uri
129
+ # @return [Query]
130
+ # @see http://www.w3.org/TR/sparql11-query/#specifyingDataset
131
+ def from(uri)
132
+ options[:from] = uri
133
+ self
134
+ end
135
+
136
+ ##
137
+ # @param [Array<RDF::Query::Pattern, Array>] patterns_queries
138
+ # splat of zero or more patterns followed by zero or more queries.
139
+ # @return [Query]
140
+ # @see http://www.w3.org/TR/sparql11-query/#GraphPattern
141
+ def where(*patterns_queries)
142
+ subqueries, patterns = patterns_queries.partition {|pq| pq.is_a? SPARQL::Client::Query}
143
+ @patterns += build_patterns(patterns)
144
+ @subqueries += subqueries
145
+ self
146
+ end
147
+
148
+ alias_method :whether, :where
149
+
150
+ ##
151
+ # @param [Array<Symbol, String>] variables
152
+ # @return [Query]
153
+ # @see http://www.w3.org/TR/sparql11-query/#modOrderBy
154
+ def order(*variables)
155
+ options[:order_by] = variables
156
+ self
157
+ end
158
+
159
+ alias_method :order_by, :order
160
+
161
+ ##
162
+ # @param [Array<Symbol, String>] variables
163
+ # @return [Query]
164
+ # @see http://www.w3.org/TR/sparql11-query/#groupby
165
+ def group(*variables)
166
+ options[:group_by] = variables
167
+ self
168
+ end
169
+
170
+ alias_method :group_by, :group
171
+
172
+ ##
173
+ # @return [Query]
174
+ # @see http://www.w3.org/TR/sparql11-query/#modDuplicates
175
+ def distinct(state = true)
176
+ options[:distinct] = state
177
+ self
178
+ end
179
+
180
+ ##
181
+ # @return [Query]
182
+ # @see http://www.w3.org/TR/sparql11-query/#modDuplicates
183
+ def reduced(state = true)
184
+ options[:reduced] = state
185
+ self
186
+ end
187
+
188
+ ##
189
+ # @param [RDF::Value] graph_uri_or_var
190
+ # @return [Query]
191
+ # @see http://www.w3.org/TR/sparql11-query/#queryDataset
192
+ def graph(graph_uri_or_var)
193
+ options[:graph] = case graph_uri_or_var
194
+ when Symbol then RDF::Query::Variable.new(graph_uri_or_var)
195
+ when String then RDF::URI(graph_uri_or_var)
196
+ when RDF::Value then graph_uri_or_var
197
+ else raise ArgumentError
198
+ end
199
+ self
200
+ end
201
+
202
+ ##
203
+ # @param [Integer, #to_i] start
204
+ # @return [Query]
205
+ # @see http://www.w3.org/TR/sparql11-query/#modOffset
206
+ def offset(start)
207
+ slice(start, nil)
208
+ end
209
+
210
+ ##
211
+ # @param [Integer, #to_i] length
212
+ # @return [Query]
213
+ # @see http://www.w3.org/TR/sparql11-query/#modResultLimit
214
+ def limit(length)
215
+ slice(nil, length)
216
+ end
217
+
218
+ ##
219
+ # @param [Integer, #to_i] start
220
+ # @param [Integer, #to_i] length
221
+ # @return [Query]
222
+ def slice(start, length)
223
+ options[:offset] = start.to_i if start
224
+ options[:limit] = length.to_i if length
225
+ self
226
+ end
227
+
228
+ ##
229
+ # @return [Query]
230
+ # @see http://www.w3.org/TR/sparql11-query/#prefNames
231
+ def prefix(string)
232
+ (options[:prefixes] ||= []) << string
233
+ self
234
+ end
235
+
236
+ ##
237
+ # @return [Query]
238
+ # @see http://www.w3.org/TR/sparql11-query/#optionals
239
+ def optional(*patterns)
240
+ (options[:optionals] ||= []) << build_patterns(patterns)
241
+ self
242
+ end
243
+
244
+ ##
245
+ # @return expects_statements?
246
+ def expects_statements?
247
+ [:construct, :describe].include?(form)
248
+ end
249
+
250
+ ##
251
+ # @private
252
+ def build_patterns(patterns)
253
+ patterns.map do |pattern|
254
+ case pattern
255
+ when RDF::Query::Pattern then pattern
256
+ else RDF::Query::Pattern.new(*pattern.to_a)
257
+ end
258
+ end
259
+ end
260
+
261
+ ##
262
+ # @private
263
+ def filter(string)
264
+ ((options[:filters] ||= []) << string) if string and not string.empty?
265
+ self
266
+ end
267
+
268
+ ##
269
+ # @return [Boolean]
270
+ def true?
271
+ case result
272
+ when TrueClass, FalseClass then result
273
+ when RDF::Literal::Boolean then result.true?
274
+ when Enumerable then !result.empty?
275
+ else false
276
+ end
277
+ end
278
+
279
+ ##
280
+ # @return [Boolean]
281
+ def false?
282
+ !true?
283
+ end
284
+
285
+ ##
286
+ # @return [Enumerable<RDF::Query::Solution>]
287
+ def solutions
288
+ result
289
+ end
290
+
291
+ ##
292
+ # @yield [statement]
293
+ # @yieldparam [RDF::Statement]
294
+ # @return [Enumerator]
295
+ def each_statement(&block)
296
+ result.each_statement(&block)
297
+ end
298
+
299
+ # Enumerates over each matching query solution.
300
+ #
301
+ # @yield [solution]
302
+ # @yieldparam [RDF::Query::Solution] solution
303
+ # @return [Enumerator]
304
+ def each_solution(&block)
305
+ @solutions = result
306
+ super
307
+ end
308
+
309
+ ##
310
+ # @return [Object]
311
+ def result
312
+ @result ||= execute
313
+ end
314
+
315
+ ##
316
+ # @return [Object]
317
+ def execute
318
+ raise NotImplementedError
319
+ end
320
+
321
+ ##
322
+ # Returns the string representation of this query.
323
+ #
324
+ # @return [String]
325
+ def to_s
326
+ buffer = [form.to_s.upcase]
327
+
328
+ case form
329
+ when :select, :describe
330
+ only_count = values.empty? && options[:count]
331
+ buffer << 'DISTINCT' if options[:distinct] and not only_count
332
+ buffer << 'REDUCED' if options[:reduced]
333
+ buffer << ((values.empty? and not options[:count]) ? '*' : values.map { |v| SPARQL::Client.serialize_value(v[1]) }.join(' '))
334
+ if options[:count]
335
+ options[:count].each do |var, count|
336
+ buffer << '( COUNT(' + (options[:distinct] ? 'DISTINCT ' : '') +
337
+ (var.is_a?(String) ? var : "?#{var}") + ') AS ' + (count.is_a?(String) ? count : "?#{count}") + ' )'
338
+ end
339
+ end
340
+ when :construct
341
+ buffer << '{'
342
+ buffer += SPARQL::Client.serialize_patterns(options[:template])
343
+ buffer << '}'
344
+ end
345
+
346
+ buffer << "FROM #{SPARQL::Client.serialize_value(options[:from])}" if options[:from]
347
+
348
+ unless patterns.empty? && form == :describe
349
+ buffer << 'WHERE {'
350
+
351
+ if options[:graph]
352
+ buffer << 'GRAPH ' + SPARQL::Client.serialize_value(options[:graph])
353
+ buffer << '{'
354
+ end
355
+
356
+ @subqueries.each do |sq|
357
+ buffer << "{ #{sq.to_s} } ."
358
+ end
359
+
360
+ buffer += SPARQL::Client.serialize_patterns(patterns)
361
+ if options[:optionals]
362
+ options[:optionals].each do |patterns|
363
+ buffer << 'OPTIONAL {'
364
+ buffer += SPARQL::Client.serialize_patterns(patterns)
365
+ buffer << '}'
366
+ end
367
+ end
368
+ if options[:filters]
369
+ buffer += options[:filters].map { |filter| "FILTER(#{filter})" }
370
+ end
371
+ if options[:graph]
372
+ buffer << '}' # GRAPH
373
+ end
374
+
375
+ buffer << '}' # WHERE
376
+ end
377
+
378
+ if options[:group_by]
379
+ buffer << 'GROUP BY'
380
+ buffer += options[:group_by].map { |var| var.is_a?(String) ? var : "?#{var}" }
381
+ end
382
+
383
+ if options[:order_by]
384
+ buffer << 'ORDER BY'
385
+ buffer += options[:order_by].map { |var| var.is_a?(String) ? var : "?#{var}" }
386
+ end
387
+
388
+ buffer << "OFFSET #{options[:offset]}" if options[:offset]
389
+ buffer << "LIMIT #{options[:limit]}" if options[:limit]
390
+ options[:prefixes].reverse.each { |e| buffer.unshift("PREFIX #{e}") } if options[:prefixes]
391
+
392
+ buffer.join(' ')
393
+ end
394
+
395
+ ##
396
+ # Outputs a developer-friendly representation of this query to `stderr`.
397
+ #
398
+ # @return [void]
399
+ def inspect!
400
+ warn(inspect)
401
+ self
402
+ end
403
+
404
+ ##
405
+ # Returns a developer-friendly representation of this query.
406
+ #
407
+ # @return [String]
408
+ def inspect
409
+ sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s)
410
+ end
411
+ end
412
+ end; end
@@ -0,0 +1,302 @@
1
+ module SPARQL; class Client
2
+ ##
3
+ # A read-only repository view of a SPARQL endpoint.
4
+ #
5
+ # @see RDF::Repository
6
+ class Repository < RDF::Repository
7
+ # @return [SPARQL::Client]
8
+ attr_reader :client
9
+
10
+ ##
11
+ # @param [String, #to_s] endpoint
12
+ # @param [Hash{Symbol => Object}] options
13
+ def initialize(endpoint, options = {})
14
+ @options = options.dup
15
+ @update_client = SPARQL::Client.new(options.delete(:update_endpoint), options) if options[:update_endpoint]
16
+ @client = SPARQL::Client.new(endpoint, options)
17
+ end
18
+
19
+ ##
20
+ # Returns the client for the update_endpoint if specified, otherwise the
21
+ # {client}.
22
+ #
23
+ # @return [SPARQL::Client]
24
+ def update_client
25
+ @update_client || @client
26
+ end
27
+
28
+ ##
29
+ # Queries `self` using the given basic graph pattern (BGP) query,
30
+ # yielding each matched solution to the given block.
31
+ #
32
+ # Overrides Queryable::query_execute to use SPARQL::Client::query
33
+ #
34
+ # @param [RDF::Query] query
35
+ # the query to execute
36
+ # @param [Hash{Symbol => Object}] options ({})
37
+ # Any other options passed to `query.execute`
38
+ # @yield [solution]
39
+ # @yieldparam [RDF::Query::Solution] solution
40
+ # @yieldreturn [void] ignored
41
+ # @return [void] ignored
42
+ # @see RDF::Queryable#query
43
+ # @see RDF::Query#execute
44
+ def query_execute(query, options = {}, &block)
45
+ return nil unless block_given?
46
+ q = SPARQL::Client::Query.select(query.variables).where(*query.patterns)
47
+ client.query(q, options).each do |solution|
48
+ yield solution
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Enumerates each RDF statement in this repository.
54
+ #
55
+ # @yield [statement]
56
+ # @yieldparam [RDF::Statement] statement
57
+ # @see RDF::Repository#each
58
+ def each(&block)
59
+ client.construct([:s, :p, :o]).where([:s, :p, :o]).each_statement(&block)
60
+ end
61
+
62
+ ##
63
+ # @private
64
+ # @see RDF::Enumerable#supports?
65
+ def supports?(feature)
66
+ case feature.to_sym
67
+ # statement contexts / named graphs
68
+ when :context then false
69
+ when :inference then false # forward-chaining inference
70
+ when :validity then false
71
+ else false
72
+ end
73
+ end
74
+
75
+ ##
76
+ # Returns `true` if this repository contains the given subject.
77
+ #
78
+ # @param [RDF::Resource] subject
79
+ # @return [Boolean]
80
+ # @see RDF::Repository#has_subject?
81
+ def has_subject?(subject)
82
+ client.ask.whether([subject, :p, :o]).true?
83
+ end
84
+
85
+ ##
86
+ # Returns `true` if this repository contains the given predicate.
87
+ #
88
+ # @param [RDF::URI] predicate
89
+ # @return [Boolean]
90
+ # @see RDF::Repository#has_predicate?
91
+ def has_predicate?(predicate)
92
+ client.ask.whether([:s, predicate, :o]).true?
93
+ end
94
+
95
+ ##
96
+ # Returns `true` if this repository contains the given object.
97
+ #
98
+ # @param [RDF::Value] object
99
+ # @return [Boolean]
100
+ # @see RDF::Repository#has_object?
101
+ def has_object?(object)
102
+ client.ask.whether([:s, :p, object]).true?
103
+ end
104
+
105
+ ##
106
+ # Iterates over each subject in this repository.
107
+ #
108
+ # @yield [subject]
109
+ # @yieldparam [RDF::Resource] subject
110
+ # @return [Enumerator]
111
+ # @see RDF::Repository#each_subject?
112
+ def each_subject(&block)
113
+ if block_given?
114
+ client.select(:s, :distinct => true).where([:s, :p, :o]).each_solution { |solution| block.call(solution[:s]) }
115
+ end
116
+ enum_subject
117
+ end
118
+
119
+ ##
120
+ # Iterates over each predicate in this repository.
121
+ #
122
+ # @yield [predicate]
123
+ # @yieldparam [RDF::URI] predicate
124
+ # @return [Enumerator]
125
+ # @see RDF::Repository#each_predicate?
126
+ def each_predicate(&block)
127
+ if block_given?
128
+ client.select(:p, :distinct => true).where([:s, :p, :o]).each_solution { |solution| block.call(solution[:p]) }
129
+ end
130
+ enum_predicate
131
+ end
132
+
133
+ ##
134
+ # Iterates over each object in this repository.
135
+ #
136
+ # @yield [object]
137
+ # @yieldparam [RDF::Value] object
138
+ # @return [Enumerator]
139
+ # @see RDF::Repository#each_object?
140
+ def each_object(&block)
141
+ if block_given?
142
+ client.select(:o, :distinct => true).where([:s, :p, :o]).each_solution { |solution| block.call(solution[:o]) }
143
+ end
144
+ enum_object
145
+ end
146
+
147
+ ##
148
+ # Returns `true` if this repository contains the given `triple`.
149
+ #
150
+ # @param [Array<RDF::Resource, RDF::URI, RDF::Value>] triple
151
+ # @return [Boolean]
152
+ # @see RDF::Repository#has_triple?
153
+ def has_triple?(triple)
154
+ client.ask.whether(triple.to_a[0...3]).true?
155
+ end
156
+
157
+ ##
158
+ # Returns `true` if this repository contains the given `statement`.
159
+ #
160
+ # @param [RDF::Statement] statement
161
+ # @return [Boolean]
162
+ # @see RDF::Repository#has_statement?
163
+ def has_statement?(statement)
164
+ has_triple?(statement.to_triple)
165
+ end
166
+
167
+ ##
168
+ # Returns the number of statements in this repository.
169
+ #
170
+ # @return [Integer]
171
+ # @see RDF::Repository#count?
172
+ def count
173
+ begin
174
+ binding = client.query("SELECT COUNT(*) WHERE { ?s ?p ?o }").first.to_hash
175
+ binding[binding.keys.first].value.to_i
176
+ rescue SPARQL::Client::MalformedQuery => e
177
+ # SPARQL 1.0 does not include support for aggregate functions:
178
+ count = 0
179
+ each_statement { count += 1 } # TODO: optimize this
180
+ count
181
+ end
182
+ end
183
+
184
+ alias_method :size, :count
185
+ alias_method :length, :count
186
+
187
+ ##
188
+ # Returns `true` if this repository contains no statements.
189
+ #
190
+ # @return [Boolean]
191
+ # @see RDF::Repository#empty?
192
+ def empty?
193
+ client.ask.whether([:s, :p, :o]).false?
194
+ end
195
+
196
+ ##
197
+ # Queries `self` for RDF statements matching the given `pattern`.
198
+ #
199
+ # @example
200
+ # repository.query([nil, RDF::DOAP.developer, nil])
201
+ # repository.query(:predicate => RDF::DOAP.developer)
202
+ #
203
+ # @fixme This should use basic SPARQL query mechanism.
204
+ #
205
+ # @param [Pattern] pattern
206
+ # @see RDF::Queryable#query_pattern
207
+ # @yield [statement]
208
+ # @yieldparam [Statement]
209
+ # @return [Enumerable<Statement>]
210
+ def query_pattern(pattern, &block)
211
+ pattern = pattern.dup
212
+ pattern.subject ||= RDF::Query::Variable.new
213
+ pattern.predicate ||= RDF::Query::Variable.new
214
+ pattern.object ||= RDF::Query::Variable.new
215
+ pattern.initialize!
216
+ query = client.construct(pattern).where(pattern)
217
+
218
+ if block_given?
219
+ query.each_statement(&block)
220
+ else
221
+ query.solutions.to_a.extend(RDF::Enumerable, RDF::Queryable)
222
+ end
223
+ end
224
+
225
+ ##
226
+ # Returns `false` to indicate that this is a read-only repository.
227
+ #
228
+ # @return [Boolean]
229
+ # @see RDF::Mutable#mutable?
230
+ def writable?
231
+ true
232
+ end
233
+
234
+ ##
235
+ # @private
236
+ # @see RDF::Mutable#clear
237
+ def clear_statements
238
+ update_client.clear(:all)
239
+ end
240
+
241
+ ##
242
+ # Deletes RDF statements from `self`.
243
+ # If any statement contains a {Query::Variable}, it is
244
+ # considered to be a pattern, and used to query
245
+ # self to find matching statements to delete.
246
+ #
247
+ # @param [Enumerable<RDF::Statement>] statements
248
+ # @raise [TypeError] if `self` is immutable
249
+ # @return [Mutable]
250
+ def delete(*statements)
251
+ delete_statements(statements) unless statements.empty?
252
+ return self
253
+ end
254
+
255
+ protected
256
+
257
+ ##
258
+ # Deletes the given RDF statements from the underlying storage.
259
+ #
260
+ # Overridden here to use SPARQL/UPDATE
261
+ #
262
+ # @param [RDF::Enumerable] statements
263
+ # @return [void]
264
+ def delete_statements(statements)
265
+
266
+ constant = statements.all? do |value|
267
+ # needs to be flattened... urgh
268
+ !value.respond_to?(:each_statement) && begin
269
+ statement = RDF::Statement.from(value)
270
+ statement.constant? && !statement.has_blank_nodes?
271
+ end
272
+ end
273
+
274
+ if constant
275
+ update_client.delete_data(statements)
276
+ else
277
+ update_client.delete_insert(statements)
278
+ end
279
+ end
280
+
281
+ ##
282
+ # Inserts the given RDF statements into the underlying storage or output
283
+ # stream.
284
+ #
285
+ # Overridden here to use SPARQL/UPDATE
286
+ #
287
+ # @param [RDF::Enumerable] statements
288
+ # @return [void]
289
+ # @since 0.1.6
290
+ def insert_statements(statements)
291
+ update_client.insert_data(statements)
292
+ end
293
+
294
+ ##
295
+ # @private
296
+ # @see RDF::Mutable#insert
297
+ def insert_statement(statement)
298
+ update_client.insert_data([statement])
299
+ end
300
+
301
+ end
302
+ end; end