dpla-sparql-client 1.1.3.2.pre.dpla.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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