sparql-client 2.1.0 → 3.1.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.
@@ -1,4 +1,6 @@
1
- module SPARQL; class Client
1
+ require 'delegate'
2
+
3
+ class SPARQL::Client
2
4
  ##
3
5
  # A SPARQL query builder.
4
6
  #
@@ -10,19 +12,13 @@ module SPARQL; class Client
10
12
  # The form of the query.
11
13
  #
12
14
  # @return [:select, :ask, :construct, :describe]
13
- # @see http://www.w3.org/TR/sparql11-query/#QueryForms
15
+ # @see https://www.w3.org/TR/sparql11-query/#QueryForms
14
16
  attr_reader :form
15
17
 
16
18
  ##
17
19
  # @return [Hash{Symbol => Object}]
18
20
  attr_reader :options
19
21
 
20
- ##
21
- # Values returned from previous query.
22
- #
23
- # @return [Array<[key, RDF::Value]>]
24
- attr_reader :values
25
-
26
22
  ##
27
23
  # Creates a boolean `ASK` query.
28
24
  #
@@ -31,28 +27,33 @@ module SPARQL; class Client
31
27
  #
32
28
  # @param [Hash{Symbol => Object}] options (see {#initialize})
33
29
  # @return [Query]
34
- # @see http://www.w3.org/TR/sparql11-query/#ask
35
- def self.ask(options = {})
36
- self.new(:ask, options)
30
+ # @see https://www.w3.org/TR/sparql11-query/#ask
31
+ def self.ask(**options)
32
+ self.new(:ask, **options)
37
33
  end
38
34
 
39
35
  ##
40
36
  # Creates a tuple `SELECT` query.
41
37
  #
42
- # @example SELECT * WHERE { ?s ?p ?o . }
38
+ # @example `SELECT * WHERE { ?s ?p ?o . }`
43
39
  # Query.select.where([:s, :p, :o])
44
40
  #
41
+ # @example `SELECT ?s WHERE {?s ?p ?o .}`
42
+ # Query.select(:s).where([:s, :p, :o])
43
+ #
44
+ # @example `SELECT COUNT(?uri as ?c) WHERE {?uri a owl:Class}`
45
+ # Query.select(count: {uri: :c}).where([:uri, RDF.type, RDF::OWL.Class])
46
+ #
45
47
  # @param [Array<Symbol>] variables
46
48
  # @return [Query]
47
49
  #
48
- # @overload self.select(*variables, options)
50
+ # @overload self.select(*variables, **options)
49
51
  # @param [Array<Symbol>] variables
50
52
  # @param [Hash{Symbol => Object}] options (see {#initialize})
51
53
  # @return [Query]
52
- # @see http://www.w3.org/TR/sparql11-query/#select
53
- def self.select(*variables)
54
- options = variables.last.is_a?(Hash) ? variables.pop : {}
55
- self.new(:select, options).select(*variables)
54
+ # @see https://www.w3.org/TR/sparql11-query/#select
55
+ def self.select(*variables, **options)
56
+ self.new(:select, **options).select(*variables)
56
57
  end
57
58
 
58
59
  ##
@@ -64,14 +65,13 @@ module SPARQL; class Client
64
65
  # @param [Array<Symbol, RDF::URI>] variables
65
66
  # @return [Query]
66
67
  #
67
- # @overload self.describe(*variables, options)
68
+ # @overload self.describe(*variables, **options)
68
69
  # @param [Array<Symbol, RDF::URI>] variables
69
70
  # @param [Hash{Symbol => Object}] options (see {#initialize})
70
71
  # @return [Query]
71
- # @see http://www.w3.org/TR/sparql11-query/#describe
72
- def self.describe(*variables)
73
- options = variables.last.is_a?(Hash) ? variables.pop : {}
74
- self.new(:describe, options).describe(*variables)
72
+ # @see https://www.w3.org/TR/sparql11-query/#describe
73
+ def self.describe(*variables, **options)
74
+ self.new(:describe, **options).describe(*variables)
75
75
  end
76
76
 
77
77
  ##
@@ -83,27 +83,30 @@ module SPARQL; class Client
83
83
  # @param [Array<RDF::Query::Pattern, Array>] patterns
84
84
  # @return [Query]
85
85
  #
86
- # @overload self.construct(*variables, options)
86
+ # @overload self.construct(*variables, **options)
87
87
  # @param [Array<RDF::Query::Pattern, Array>] patterns
88
88
  # @param [Hash{Symbol => Object}] options (see {#initialize})
89
89
  # @return [Query]
90
- # @see http://www.w3.org/TR/sparql11-query/#construct
91
- def self.construct(*patterns)
92
- options = patterns.last.is_a?(Hash) ? patterns.pop : {}
93
- self.new(:construct, options).construct(*patterns) # FIXME
90
+ # @see https://www.w3.org/TR/sparql11-query/#construct
91
+ def self.construct(*patterns, **options)
92
+ self.new(:construct, **options).construct(*patterns) # FIXME
94
93
  end
95
94
 
96
95
  ##
97
96
  # @param [Symbol, #to_s] form
98
- # @overload self.construct(*variables, options)
97
+ # @overload self.construct(*variables, **options)
99
98
  # @param [Symbol, #to_s] form
100
99
  # @param [Hash{Symbol => Object}] options (see {Client#initialize})
100
+ # @option options [Hash{Symbol => Symbol}] :count
101
+ # Contents are symbols relating a variable described within the query,
102
+ # to the projected variable.
103
+ #
101
104
  # @yield [query]
102
105
  # @yieldparam [Query]
103
- def initialize(form = :ask, options = {}, &block)
106
+ def initialize(form = :ask, **options, &block)
104
107
  @subqueries = []
105
108
  @form = form.respond_to?(:to_sym) ? form.to_sym : form.to_s.to_sym
106
- super([], options, &block)
109
+ super([], **options, &block)
107
110
  end
108
111
 
109
112
  ##
@@ -111,21 +114,31 @@ module SPARQL; class Client
111
114
  # query.ask.where([:s, :p, :o])
112
115
  #
113
116
  # @return [Query]
114
- # @see http://www.w3.org/TR/sparql11-query/#ask
117
+ # @see https://www.w3.org/TR/sparql11-query/#ask
115
118
  def ask
116
119
  @form = :ask
117
120
  self
118
121
  end
119
122
 
120
123
  ##
121
- # @example SELECT * WHERE { ?s ?p ?o . }
124
+ # @example `SELECT * WHERE { ?s ?p ?o . }`
122
125
  # query.select.where([:s, :p, :o])
123
126
  #
124
- # @param [Array<Symbol>] variables
127
+ # @example `SELECT ?s WHERE {?s ?p ?o .}`
128
+ # query.select(:s).where([:s, :p, :o])
129
+ #
130
+ # @example `SELECT COUNT(?uri as ?c) WHERE {?uri a owl:Class}`
131
+ # query.select(count: {uri: :c}).where([:uri, RDF.type, RDF::OWL.Class])
132
+ #
133
+ # @param [Array<Symbol>, Hash{Symbol => RDF::Query::Variable}] variables
125
134
  # @return [Query]
126
- # @see http://www.w3.org/TR/sparql11-query/#select
135
+ # @see https://www.w3.org/TR/sparql11-query/#select
127
136
  def select(*variables)
128
- @values = variables.map { |var| [var, RDF::Query::Variable.new(var)] }
137
+ @values = if variables.length == 1 && variables.first.is_a?(Hash)
138
+ variables.to_a
139
+ else
140
+ variables.map { |var| [var, RDF::Query::Variable.new(var)] }
141
+ end
129
142
  self
130
143
  end
131
144
 
@@ -135,7 +148,7 @@ module SPARQL; class Client
135
148
  #
136
149
  # @param [Array<Symbol>] variables
137
150
  # @return [Query]
138
- # @see http://www.w3.org/TR/sparql11-query/#describe
151
+ # @see https://www.w3.org/TR/sparql11-query/#describe
139
152
  def describe(*variables)
140
153
  @values = variables.map { |var|
141
154
  [var, var.is_a?(RDF::URI) ? var : RDF::Query::Variable.new(var)]
@@ -149,7 +162,7 @@ module SPARQL; class Client
149
162
  #
150
163
  # @param [Array<RDF::Query::Pattern, Array>] patterns
151
164
  # @return [Query]
152
- # @see http://www.w3.org/TR/sparql11-query/#construct
165
+ # @see https://www.w3.org/TR/sparql11-query/#construct
153
166
  def construct(*patterns)
154
167
  options[:template] = build_patterns(patterns)
155
168
  self
@@ -161,7 +174,7 @@ module SPARQL; class Client
161
174
  #
162
175
  # @param [RDF::URI] uri
163
176
  # @return [Query]
164
- # @see http://www.w3.org/TR/sparql11-query/#specifyingDataset
177
+ # @see https://www.w3.org/TR/sparql11-query/#specifyingDataset
165
178
  def from(uri)
166
179
  options[:from] = uri
167
180
  self
@@ -172,19 +185,55 @@ module SPARQL; class Client
172
185
  # query.select.where([:s, :p, :o])
173
186
  # query.select.whether([:s, :p, :o])
174
187
  #
188
+ # @example SELECT * WHERE { { SELECT * WHERE { ?s ?p ?o . } } . ?s ?p ?o . }
189
+ # subquery = query.select.where([:s, :p, :o])
190
+ # query.select.where([:s, :p, :o], subquery)
191
+ #
192
+ # @example SELECT * WHERE { { SELECT * WHERE { ?s ?p ?o . } } . ?s ?p ?o . }
193
+ # query.select.where([:s, :p, :o]) do |q|
194
+ # q.select.where([:s, :p, :o])
195
+ # end
196
+ #
197
+ # Block form can be used for chaining calls in addition to creating sub-select queries.
198
+ #
199
+ # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ?o
200
+ # query.select.where([:s, :p, :o]) do
201
+ # order(:o)
202
+ # end
203
+ #
175
204
  # @param [Array<RDF::Query::Pattern, Array>] patterns_queries
176
205
  # splat of zero or more patterns followed by zero or more queries.
206
+ # @yield [query]
207
+ # Yield form with or without argument; without an argument, evaluates within the query.
208
+ # @yieldparam [SPARQL::Client::Query] query Actually a delegator to query. Methods other than `#select` are evaluated against `self`. For `#select`, a new Query is created, and the result added as a subquery.
177
209
  # @return [Query]
178
- # @see http://www.w3.org/TR/sparql11-query/#GraphPattern
179
- def where(*patterns_queries)
210
+ # @see https://www.w3.org/TR/sparql11-query/#GraphPattern
211
+ def where(*patterns_queries, &block)
180
212
  subqueries, patterns = patterns_queries.partition {|pq| pq.is_a? SPARQL::Client::Query}
181
213
  @patterns += build_patterns(patterns)
182
214
  @subqueries += subqueries
215
+
216
+ if block_given?
217
+ decorated_query = WhereDecorator.new(self)
218
+ case block.arity
219
+ when 1 then block.call(decorated_query)
220
+ else decorated_query.instance_eval(&block)
221
+ end
222
+ end
183
223
  self
184
224
  end
185
225
 
186
226
  alias_method :whether, :where
187
227
 
228
+ # @private
229
+ class WhereDecorator < SimpleDelegator
230
+ def select(*variables)
231
+ query = SPARQL::Client::Query.select(*variables)
232
+ __getobj__.instance_variable_get(:@subqueries) << query
233
+ query
234
+ end
235
+ end
236
+
188
237
  ##
189
238
  # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ?o
190
239
  # query.select.where([:s, :p, :o]).order(:o)
@@ -194,11 +243,11 @@ module SPARQL; class Client
194
243
  # query.select.where([:s, :p, :o]).order_by(:o, :p)
195
244
  #
196
245
  # @example SELECT * WHERE { ?s ?p ?o . } ORDER BY ASC(?o) DESC(?p)
197
- # query.select.where([:s, :p, :o]).order_by(:o => :asc, :p => :desc)
246
+ # query.select.where([:s, :p, :o]).order_by(o: :asc, p: :desc)
198
247
  #
199
248
  # @param [Array<Symbol, String>] variables
200
249
  # @return [Query]
201
- # @see http://www.w3.org/TR/sparql11-query/#modOrderBy
250
+ # @see https://www.w3.org/TR/sparql11-query/#modOrderBy
202
251
  def order(*variables)
203
252
  options[:order_by] = variables
204
253
  self
@@ -213,7 +262,7 @@ module SPARQL; class Client
213
262
  #
214
263
  # @param [Array<Symbol, String>] var
215
264
  # @return [Query]
216
- # @see http://www.w3.org/TR/sparql11-query/#modOrderBy
265
+ # @see https://www.w3.org/TR/sparql11-query/#modOrderBy
217
266
  def asc(var)
218
267
  (options[:order_by] ||= []) << {var => :asc}
219
268
  self
@@ -226,7 +275,7 @@ module SPARQL; class Client
226
275
  #
227
276
  # @param [Array<Symbol, String>] var
228
277
  # @return [Query]
229
- # @see http://www.w3.org/TR/sparql11-query/#modOrderBy
278
+ # @see https://www.w3.org/TR/sparql11-query/#modOrderBy
230
279
  def desc(var)
231
280
  (options[:order_by] ||= []) << {var => :desc}
232
281
  self
@@ -238,7 +287,7 @@ module SPARQL; class Client
238
287
  #
239
288
  # @param [Array<Symbol, String>] variables
240
289
  # @return [Query]
241
- # @see http://www.w3.org/TR/sparql11-query/#groupby
290
+ # @see https://www.w3.org/TR/sparql11-query/#groupby
242
291
  def group(*variables)
243
292
  options[:group_by] = variables
244
293
  self
@@ -251,7 +300,7 @@ module SPARQL; class Client
251
300
  # query.select(:s).distinct.where([:s, :p, :o])
252
301
  #
253
302
  # @return [Query]
254
- # @see http://www.w3.org/TR/sparql11-query/#modDuplicates
303
+ # @see https://www.w3.org/TR/sparql11-query/#modDuplicates
255
304
  def distinct(state = true)
256
305
  options[:distinct] = state
257
306
  self
@@ -262,7 +311,7 @@ module SPARQL; class Client
262
311
  # query.select(:s).reduced.where([:s, :p, :o])
263
312
  #
264
313
  # @return [Query]
265
- # @see http://www.w3.org/TR/sparql11-query/#modDuplicates
314
+ # @see https://www.w3.org/TR/sparql11-query/#modDuplicates
266
315
  def reduced(state = true)
267
316
  options[:reduced] = state
268
317
  self
@@ -273,7 +322,7 @@ module SPARQL; class Client
273
322
  # query.select.graph(:g).where([:s, :p, :o])
274
323
  # @param [RDF::Value] graph_uri_or_var
275
324
  # @return [Query]
276
- # @see http://www.w3.org/TR/sparql11-query/#queryDataset
325
+ # @see https://www.w3.org/TR/sparql11-query/#queryDataset
277
326
  def graph(graph_uri_or_var)
278
327
  options[:graph] = case graph_uri_or_var
279
328
  when Symbol then RDF::Query::Variable.new(graph_uri_or_var)
@@ -290,7 +339,7 @@ module SPARQL; class Client
290
339
  #
291
340
  # @param [Integer, #to_i] start
292
341
  # @return [Query]
293
- # @see http://www.w3.org/TR/sparql11-query/#modOffset
342
+ # @see https://www.w3.org/TR/sparql11-query/#modOffset
294
343
  def offset(start)
295
344
  slice(start, nil)
296
345
  end
@@ -301,7 +350,7 @@ module SPARQL; class Client
301
350
  #
302
351
  # @param [Integer, #to_i] length
303
352
  # @return [Query]
304
- # @see http://www.w3.org/TR/sparql11-query/#modResultLimit
353
+ # @see https://www.w3.org/TR/sparql11-query/#modResultLimit
305
354
  def limit(length)
306
355
  slice(nil, length)
307
356
  end
@@ -320,16 +369,38 @@ module SPARQL; class Client
320
369
  end
321
370
 
322
371
  ##
323
- # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
324
- # query.select.
325
- # prefix(dc: RDF::URI("http://purl.org/dc/elements/1.1/")).
326
- # prefix(foaf: RDF::URI("http://xmlns.com/foaf/0.1/")).
327
- # where([:s, :p, :o])
372
+ # @overload prefix(prefix: uri)
373
+ # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
374
+ # query.select.
375
+ # prefix(dc: RDF::URI("http://purl.org/dc/elements/1.1/")).
376
+ # prefix(foaf: RDF::URI("http://xmlns.com/foaf/0.1/")).
377
+ # where([:s, :p, :o])
328
378
  #
329
- # @return [Query]
330
- # @see http://www.w3.org/TR/sparql11-query/#prefNames
331
- def prefix(string)
332
- (options[:prefixes] ||= []) << string
379
+ # @param [RDF::URI] uri
380
+ # @param [Symbol, String] prefix
381
+ # @return [Query]
382
+ #
383
+ # @overload prefix(string)
384
+ # @example PREFIX dc: <http://purl.org/dc/elements/1.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE \{ ?s ?p ?o . \}
385
+ # query.select.
386
+ # prefix("dc: <http://purl.org/dc/elements/1.1/>").
387
+ # prefix("foaf: <http://xmlns.com/foaf/0.1/>").
388
+ # where([:s, :p, :o])
389
+ #
390
+ # @param [string] string
391
+ # @return [Query]
392
+ # @see https://www.w3.org/TR/sparql11-query/#prefNames
393
+ def prefix(val)
394
+ options[:prefixes] ||= []
395
+ if val.kind_of? String
396
+ options[:prefixes] << val
397
+ elsif val.kind_of? Hash
398
+ val.each do |k, v|
399
+ options[:prefixes] << "#{k}: <#{v}>"
400
+ end
401
+ else
402
+ raise ArgumentError, "prefix must be a kind of String or a Hash"
403
+ end
333
404
  self
334
405
  end
335
406
 
@@ -338,10 +409,206 @@ module SPARQL; class Client
338
409
  # query.select.where([:s, :p, :o]).
339
410
  # optional([:s, RDF.type, :o], [:s, RDF::Vocab::DC.abstract, :o])
340
411
  #
412
+ # The block form can be used for adding filters:
413
+ #
414
+ # @example ASK WHERE { ?s ?p ?o . OPTIONAL { ?s ?p ?o . FILTER(regex(?s, 'Abiline, Texas'))} }
415
+ # query.ask.where([:s, :p, :o]).optional([:s, :p, :o]) do
416
+ # filter("regex(?s, 'Abiline, Texas')")
417
+ # end
418
+ #
419
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
420
+ # splat of zero or more patterns followed by zero or more queries.
421
+ # @yield [query]
422
+ # Yield form with or without argument; without an argument, evaluates within the query.
423
+ # @yieldparam [SPARQL::Client::Query] query used for creating filters on the optional patterns.
341
424
  # @return [Query]
342
- # @see http://www.w3.org/TR/sparql11-query/#optionals
343
- def optional(*patterns)
425
+ # @see https://www.w3.org/TR/sparql11-query/#optionals
426
+ def optional(*patterns, &block)
344
427
  (options[:optionals] ||= []) << build_patterns(patterns)
428
+
429
+ if block_given?
430
+ # Steal options[:filters]
431
+ query_filters = options[:filters]
432
+ options[:filters] = []
433
+ case block.arity
434
+ when 1 then block.call(self)
435
+ else instance_eval(&block)
436
+ end
437
+ options[:optionals].last.concat(options[:filters])
438
+ options[:filters] = query_filters
439
+ end
440
+
441
+ self
442
+ end
443
+
444
+ ##
445
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title \}
446
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).
447
+ # union([:book, RDF::Vocab::DC11.title, :title])
448
+ #
449
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\}
450
+ # query1 = SPARQL::Client::Query.select.
451
+ # where([:book, RDF::Vocab::DC11.title, :title]).
452
+ # filter("langmatches(?title, 'en')")
453
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).union(query1)
454
+ #
455
+ # The block form can be used for more complicated queries, using the `select` form (note, use either block or argument forms, not both):
456
+ #
457
+ # @example SELECT * WHERE \{ ?book dc:title ?title \} UNION \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\}
458
+ # query1 = SPARQL::Client::Query.select.where([:book, RDF::Vocab::DC11.title, :title]).filter("langmatches(?title, 'en')")
459
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).union do |q|
460
+ # q.select.
461
+ # where([:book, RDF::Vocab::DC11.title, :title]).
462
+ # filter("langmatches(?title, 'en')")
463
+ # end
464
+ #
465
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
466
+ # splat of zero or more patterns followed by zero or more queries.
467
+ # @yield [query]
468
+ # Yield form with or without argument; without an argument, evaluates within the query.
469
+ # @yieldparam [SPARQL::Client::Query] query used for adding select clauses.
470
+ # @return [Query]
471
+ # @see https://www.w3.org/TR/sparql11-query/#optionals
472
+ def union(*patterns, &block)
473
+ options[:unions] ||= []
474
+
475
+ if block_given?
476
+ raise ArgumentError, "#union requires either arguments or a block, not both." unless patterns.empty?
477
+ # Evaluate calls in a new query instance
478
+ query = self.class.select
479
+ case block.arity
480
+ when 1 then block.call(query)
481
+ else query.instance_eval(&block)
482
+ end
483
+ options[:unions] << query
484
+ elsif patterns.all? {|p| p.is_a?(SPARQL::Client::Query)}
485
+ # With argument form, all must be patterns or queries
486
+ options[:unions] += patterns
487
+ elsif patterns.all? {|p| p.is_a?(Array)}
488
+ # With argument form, all must be patterns, or queries
489
+ options[:unions] << self.class.select.where(*patterns)
490
+ else
491
+ raise ArgumentError, "#union arguments are triple patters or queries, not both."
492
+ end
493
+
494
+ self
495
+ end
496
+
497
+ ##
498
+ # @example SELECT * WHERE \{ ?book dc:title ?title . MINUS \{ ?book dc11:title ?title \} \}
499
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).
500
+ # minus([:book, RDF::Vocab::DC11.title, :title])
501
+ #
502
+ # @example SELECT * WHERE \{ ?book dc:title ?title MINUS \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN')) \} \}
503
+ # query1 = SPARQL::Client::Query.select.
504
+ # where([:book, RDF::Vocab::DC11.title, :title]).
505
+ # filter("langmatches(?title, 'en')")
506
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).minus(query1)
507
+ #
508
+ # The block form can be used for more complicated queries, using the `select` form (note, use either block or argument forms, not both):
509
+ #
510
+ # @example SELECT * WHERE \{ ?book dc:title ?title MINUS \{ ?book dc11:title ?title . FILTER(langmatches(lang(?title), 'EN'))\} \}
511
+ # query1 = SPARQL::Client::Query.select.where([:book, RDF::Vocab::DC11.title, :title]).filter("langmatches(?title, 'en')")
512
+ # query.select.where([:book, RDF::Vocab::DC.title, :title]).minus do |q|
513
+ # q.select.
514
+ # where([:book, RDF::Vocab::DC11.title, :title]).
515
+ # filter("langmatches(?title, 'en')")
516
+ # end
517
+ #
518
+ # @param [Array<RDF::Query::Pattern, Array>] patterns
519
+ # splat of zero or more patterns followed by zero or more queries.
520
+ # @yield [query]
521
+ # Yield form with or without argument; without an argument, evaluates within the query.
522
+ # @yieldparam [SPARQL::Client::Query] query used for adding select clauses.
523
+ # @return [Query]
524
+ # @see https://www.w3.org/TR/sparql11-query/#optionals
525
+ def minus(*patterns, &block)
526
+ options[:minuses] ||= []
527
+
528
+ if block_given?
529
+ raise ArgumentError, "#minus requires either arguments or a block, not both." unless patterns.empty?
530
+ # Evaluate calls in a new query instance
531
+ query = self.class.select
532
+ case block.arity
533
+ when 1 then block.call(query)
534
+ else query.instance_eval(&block)
535
+ end
536
+ options[:minuses] << query
537
+ elsif patterns.all? {|p| p.is_a?(SPARQL::Client::Query)}
538
+ # With argument form, all must be patterns or queries
539
+ options[:minuses] += patterns
540
+ elsif patterns.all? {|p| p.is_a?(Array)}
541
+ # With argument form, all must be patterns, or queries
542
+ options[:minuses] << self.class.select.where(*patterns)
543
+ else
544
+ raise ArgumentError, "#minus arguments are triple patters or queries, not both."
545
+ end
546
+
547
+ self
548
+ end
549
+
550
+ ##
551
+ # Specify inline data for a query
552
+ #
553
+ # @overload values
554
+ # Values returned from previous query.
555
+ #
556
+ # @return [Array<Array(key, RDF::Value)>]
557
+ #
558
+ # @overload values(vars, *data)
559
+ # @example single variable with multiple values
560
+ # query.select
561
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title])
562
+ # .values(:title, "This title", "Another title")
563
+ #
564
+ # @example multiple variables with multiple values
565
+ # query.select
566
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title],
567
+ # [:s, RDF.type, :type])
568
+ # .values([:type, :title],
569
+ # [RDF::URI('http://pcdm.org/models#Object'), "This title"],
570
+ # [RDF::URI('http://pcdm.org/models#Collection', 'Another title'])
571
+ #
572
+ # @example multiple variables with UNDEF
573
+ # query.select
574
+ # .where([:s, RDF::URI('http://purl.org/dc/terms/title'), :title],
575
+ # [:s, RDF.type, :type])
576
+ # .values([:type, :title],
577
+ # [nil "This title"],
578
+ # [RDF::URI('http://pcdm.org/models#Collection', nil])
579
+ #
580
+ # @param [Symbol, Array<Symbol>] vars
581
+ # @param [Array<RDF::Term, String, nil>] *data
582
+ # @return [Query]
583
+ def values(*args)
584
+ return @values if args.empty?
585
+ vars, *data = *args
586
+ vars = Array(vars).map {|var| RDF::Query::Variable.new(var)}
587
+ if vars.length == 1
588
+ # data may be a in array form or simple form
589
+ if data.any? {|d| d.is_a?(Array)} && !data.all? {|d| d.is_a?(Array)}
590
+ raise ArgumentError, "values data must all be in array form or all simple"
591
+ end
592
+ data = data.map {|d| Array(d)}
593
+ end
594
+
595
+ # Each data value must be an array with the same number of entries as vars
596
+ unless data.all? {|d| d.is_a?(Array) && d.all? {|dd| dd.is_a?(RDF::Value) || dd.is_a?(String) || dd.nil?}}
597
+ raise ArgumentError, "values data must each be an array of terms, strings, or nil"
598
+ end
599
+
600
+ # Turn strings into Literals
601
+ data = data.map do |d|
602
+ d.map do |nil_literal_or_term|
603
+ case nil_literal_or_term
604
+ when nil then nil
605
+ when String then RDF::Literal(nil_literal_or_term)
606
+ when RDF::Value then nil_literal_or_term
607
+ else raise ArgumentError
608
+ end
609
+ end
610
+ end
611
+ options[:values] = [vars, *data]
345
612
  self
346
613
  end
347
614
 
@@ -352,15 +619,17 @@ module SPARQL; class Client
352
619
  end
353
620
 
354
621
  ##
355
- # @private
622
+ # @private
356
623
  def build_patterns(patterns)
357
624
  patterns.map {|pattern| RDF::Query::Pattern.from(pattern)}
358
625
  end
359
626
 
360
627
  ##
361
- # @private
628
+ # @example ASK WHERE { ?s ?p ?o . FILTER(regex(?s, 'Abiline, Texas')) }
629
+ # query.ask.where([:s, :p, :o]).filter("regex(?s, 'Abiline, Texas')")
630
+ # @return [Query]
362
631
  def filter(string)
363
- ((options[:filters] ||= []) << string) if string and not string.empty?
632
+ ((options[:filters] ||= []) << Filter.new(string)) if string and not string.empty?
364
633
  self
365
634
  end
366
635
 
@@ -445,33 +714,11 @@ module SPARQL; class Client
445
714
  buffer << "FROM #{SPARQL::Client.serialize_value(options[:from])}" if options[:from]
446
715
 
447
716
  unless patterns.empty? && form == :describe
448
- buffer << 'WHERE {'
449
-
450
- if options[:graph]
451
- buffer << 'GRAPH ' + SPARQL::Client.serialize_value(options[:graph])
452
- buffer << '{'
453
- end
454
-
455
- @subqueries.each do |sq|
456
- buffer << "{ #{sq.to_s} } ."
457
- end
458
-
459
- buffer += SPARQL::Client.serialize_patterns(patterns)
460
- if options[:optionals]
461
- options[:optionals].each do |patterns|
462
- buffer << 'OPTIONAL {'
463
- buffer += SPARQL::Client.serialize_patterns(patterns)
464
- buffer << '}'
465
- end
466
- end
467
- if options[:filters]
468
- buffer += options[:filters].map { |filter| "FILTER(#{filter})" }
469
- end
470
- if options[:graph]
471
- buffer << '}' # GRAPH
472
- end
717
+ buffer += self.to_s_ggp.unshift('WHERE')
718
+ end
473
719
 
474
- buffer << '}' # WHERE
720
+ options.fetch(:unions, []).each do |query|
721
+ buffer += query.to_s_ggp.unshift('UNION')
475
722
  end
476
723
 
477
724
  if options[:group_by]
@@ -483,7 +730,7 @@ module SPARQL; class Client
483
730
  buffer << 'ORDER BY'
484
731
  options[:order_by].map { |elem|
485
732
  case elem
486
- # .order_by({ :var1 => :asc, :var2 => :desc})
733
+ # .order_by({ var1: :asc, var2: :desc})
487
734
  when Hash
488
735
  elem.each { |key, val|
489
736
  # check provided values
@@ -524,6 +771,55 @@ module SPARQL; class Client
524
771
  buffer.join(' ')
525
772
  end
526
773
 
774
+ # Serialize a Group Graph Pattern
775
+ # @private
776
+ def to_s_ggp
777
+ buffer = ["{"]
778
+
779
+ if options[:graph]
780
+ buffer << 'GRAPH ' + SPARQL::Client.serialize_value(options[:graph])
781
+ buffer << '{'
782
+ end
783
+
784
+ @subqueries.each do |sq|
785
+ buffer << "{ #{sq.to_s} } ."
786
+ end
787
+
788
+ buffer += SPARQL::Client.serialize_patterns(patterns)
789
+ if options[:optionals]
790
+ options[:optionals].each do |patterns|
791
+ buffer << 'OPTIONAL {'
792
+ buffer += SPARQL::Client.serialize_patterns(patterns)
793
+ buffer << '}'
794
+ end
795
+ end
796
+ if options[:filters]
797
+ buffer += options[:filters].map(&:to_s)
798
+ end
799
+ if options[:values]
800
+ vars = options[:values].first.map {|var| SPARQL::Client.serialize_value(var)}
801
+ buffer << "VALUES (#{vars.join(' ')}) {"
802
+ options[:values][1..-1].each do |data_block_value|
803
+ buffer << '('
804
+ buffer << data_block_value.map do |value|
805
+ value.nil? ? 'UNDEF' : SPARQL::Client.serialize_value(value)
806
+ end.join(' ')
807
+ buffer << ')'
808
+ end
809
+ buffer << '}'
810
+ end
811
+ if options[:graph]
812
+ buffer << '}' # GRAPH
813
+ end
814
+
815
+ options.fetch(:minuses, []).each do |query|
816
+ buffer += query.to_s_ggp.unshift('MINUS')
817
+ end
818
+
819
+ buffer << '}'
820
+ buffer
821
+ end
822
+
527
823
  ##
528
824
  # Outputs a developer-friendly representation of this query to `stderr`.
529
825
  #
@@ -540,5 +836,16 @@ module SPARQL; class Client
540
836
  def inspect
541
837
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s)
542
838
  end
839
+
840
+ # Allow Filters to be
841
+ class Filter < SPARQL::Client::QueryElement
842
+ def initialize(*args)
843
+ super
844
+ end
845
+
846
+ def to_s
847
+ "FILTER(#{elements.join(' ')})"
848
+ end
849
+ end
543
850
  end
544
- end; end
851
+ end