sparql-client 2.0.2 → 3.1.0

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