sparql 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/README.md +11 -4
  2. data/VERSION +1 -1
  3. data/lib/sparql/algebra/extensions.rb +36 -0
  4. data/lib/sparql/algebra/operator.rb +197 -87
  5. data/lib/sparql/algebra/operator/abs.rb +31 -0
  6. data/lib/sparql/algebra/operator/base.rb +1 -0
  7. data/lib/sparql/algebra/operator/bnode.rb +88 -0
  8. data/lib/sparql/algebra/operator/bound.rb +2 -1
  9. data/lib/sparql/algebra/operator/ceil.rb +31 -0
  10. data/lib/sparql/algebra/operator/coalesce.rb +65 -0
  11. data/lib/sparql/algebra/operator/concat.rb +49 -0
  12. data/lib/sparql/algebra/operator/contains.rb +44 -0
  13. data/lib/sparql/algebra/operator/dataset.rb +11 -48
  14. data/lib/sparql/algebra/operator/datatype.rb +4 -2
  15. data/lib/sparql/algebra/operator/day.rb +31 -0
  16. data/lib/sparql/algebra/operator/encode_for_uri.rb +38 -0
  17. data/lib/sparql/algebra/operator/extend.rb +31 -2
  18. data/lib/sparql/algebra/operator/floor.rb +33 -0
  19. data/lib/sparql/algebra/operator/hours.rb +31 -0
  20. data/lib/sparql/algebra/operator/if.rb +55 -0
  21. data/lib/sparql/algebra/operator/in.rb +68 -0
  22. data/lib/sparql/algebra/operator/iri.rb +40 -0
  23. data/lib/sparql/algebra/operator/is_numeric.rb +41 -0
  24. data/lib/sparql/algebra/operator/lang_matches.rb +2 -2
  25. data/lib/sparql/algebra/operator/lcase.rb +31 -0
  26. data/lib/sparql/algebra/operator/md5.rb +34 -0
  27. data/lib/sparql/algebra/operator/minutes.rb +31 -0
  28. data/lib/sparql/algebra/operator/month.rb +31 -0
  29. data/lib/sparql/algebra/operator/not.rb +2 -2
  30. data/lib/sparql/algebra/operator/notin.rb +70 -0
  31. data/lib/sparql/algebra/operator/now.rb +29 -0
  32. data/lib/sparql/algebra/operator/order.rb +9 -13
  33. data/lib/sparql/algebra/operator/rand.rb +24 -0
  34. data/lib/sparql/algebra/operator/replace.rb +81 -0
  35. data/lib/sparql/algebra/operator/round.rb +31 -0
  36. data/lib/sparql/algebra/operator/seconds.rb +31 -0
  37. data/lib/sparql/algebra/operator/sha1.rb +34 -0
  38. data/lib/sparql/algebra/operator/sha256.rb +34 -0
  39. data/lib/sparql/algebra/operator/sha384.rb +34 -0
  40. data/lib/sparql/algebra/operator/sha512.rb +34 -0
  41. data/lib/sparql/algebra/operator/strafter.rb +57 -0
  42. data/lib/sparql/algebra/operator/strbefore.rb +59 -0
  43. data/lib/sparql/algebra/operator/strdt.rb +33 -0
  44. data/lib/sparql/algebra/operator/strends.rb +46 -0
  45. data/lib/sparql/algebra/operator/strlang.rb +34 -0
  46. data/lib/sparql/algebra/operator/strlen.rb +34 -0
  47. data/lib/sparql/algebra/operator/strstarts.rb +46 -0
  48. data/lib/sparql/algebra/operator/struuid.rb +32 -0
  49. data/lib/sparql/algebra/operator/substr.rb +80 -0
  50. data/lib/sparql/algebra/operator/timezone.rb +34 -0
  51. data/lib/sparql/algebra/operator/tz.rb +31 -0
  52. data/lib/sparql/algebra/operator/ucase.rb +31 -0
  53. data/lib/sparql/algebra/operator/uuid.rb +32 -0
  54. data/lib/sparql/algebra/operator/year.rb +31 -0
  55. data/lib/sparql/grammar/parser11.rb +128 -70
  56. data/lib/sparql/grammar/terminals11.rb +4 -5
  57. metadata +62 -7
@@ -0,0 +1,34 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `timezone` operator.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://example.org/>))
8
+ # (project (?s ?x)
9
+ # (extend ((?x (timezone ?date)))
10
+ # (bgp (triple ?s :date ?date)))))
11
+ #
12
+ # @see http://www.w3.org/TR/sparql11-query/#func-timezone
13
+ class Timezone < Operator::Unary
14
+ include Evaluatable
15
+
16
+ NAME = :timezone
17
+
18
+ ##
19
+ # Returns the timezone part of arg as an xsd:dayTimeDuration. Raises an error if there is no timezone.
20
+ #
21
+ # This function corresponds to fn:timezone-from-dateTime except for the treatment of literals with no timezone.
22
+ #
23
+ # @param [RDF::Literal] operand
24
+ # the operand
25
+ # @return [RDF::Literal]
26
+ # @raise [TypeError] if the operand is not a simple literal
27
+ def apply(operand)
28
+ raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
29
+ raise TypeError, "literal has no timezone" unless res = operand.timezone
30
+ res
31
+ end
32
+ end # Timezone
33
+ end # Operator
34
+ end; end # SPARQL::Algebra
@@ -0,0 +1,31 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `tz` operator.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://example.org/>))
8
+ # (project (?s ?x)
9
+ # (extend ((?x (tz ?date)))
10
+ # (bgp (triple ?s :date ?date)))))
11
+ #
12
+ # @see http://www.w3.org/TR/sparql11-query/#func-tz
13
+ class TZ < Operator::Unary
14
+ include Evaluatable
15
+
16
+ NAME = :tz
17
+
18
+ ##
19
+ # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
20
+ #
21
+ # @param [RDF::Literal] operand
22
+ # the operand
23
+ # @return [RDF::Literal]
24
+ # @raise [TypeError] if the operand is not a simple literal
25
+ def apply(operand)
26
+ raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
27
+ operand.tz
28
+ end
29
+ end # TZ
30
+ end # Operator
31
+ end; end # SPARQL::Algebra
@@ -0,0 +1,31 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `ucase` operator.
5
+ #
6
+ # @example
7
+ # (ucase ?x)
8
+ #
9
+ # @see http://www.w3.org/TR/sparql11-query/#func-ucase
10
+ # @see http://www.w3.org/TR/xpath-functions/#func-ucase
11
+ class UCase < Operator::Unary
12
+ include Evaluatable
13
+
14
+ NAME = :ucase
15
+
16
+ ##
17
+ # The LCASE function corresponds to the XPath fn:lower-case function. It returns a string literal whose lexical form is the lower case of the lexcial form of the argument.
18
+ #
19
+ # @param [RDF::Literal] operand
20
+ # the operand
21
+ # @return [RDF::Literal] literal of same type
22
+ # @raise [TypeError] if the operand is not a literal value
23
+ def apply(operand)
24
+ case operand
25
+ when RDF::Literal then RDF::Literal(operand.to_s.upcase, :datatype => operand.datatype, :language => operand.language)
26
+ else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}"
27
+ end
28
+ end
29
+ end # UCase
30
+ end # Operator
31
+ end; end # SPARQL::Algebra
@@ -0,0 +1,32 @@
1
+ require 'securerandom'
2
+
3
+ module SPARQL; module Algebra
4
+ class Operator
5
+ ##
6
+ # The SPARQL `uuid` function.
7
+ #
8
+ # @example
9
+ # (prefix ((: <http://example.org/>)
10
+ # (xsd: <http://www.w3.org/2001/XMLSchema#>))
11
+ # (project (?length)
12
+ # (extend ((?length (strlen (str ?uuid))))
13
+ # (filter (&& (isIRI ?uuid) (regex (str ?uuid) "^urn:uuid:[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$" "i"))
14
+ # (extend ((?uuid (uuid)))
15
+ # (bgp))))))
16
+ #
17
+ # @see http://www.w3.org/TR/sparql11-query/#func-uuid
18
+ class UUID < Operator::Nullary
19
+ include Evaluatable
20
+
21
+ NAME = :uuid
22
+
23
+ ##
24
+ # Return a fresh IRI from the UUID URN scheme. Each call of UUID() returns a different UUID. It must not be the "nil" UUID (all zeroes). The variant and version of the UUID is implementation dependent.
25
+ #
26
+ # @return [RDF::URI]
27
+ def apply
28
+ RDF::URI("urn:uuid:#{SecureRandom.uuid}")
29
+ end
30
+ end # UUID
31
+ end # Operator
32
+ end; end # SPARQL::Algebra
@@ -0,0 +1,31 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `year` operator.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://example.org/>))
8
+ # (project (?s ?x)
9
+ # (extend ((?x (year ?date)))
10
+ # (bgp (triple ?s :date ?date)))))
11
+ #
12
+ # @see http://www.w3.org/TR/sparql11-query/#func-year
13
+ class Year < Operator::Unary
14
+ include Evaluatable
15
+
16
+ NAME = :year
17
+
18
+ ##
19
+ # Returns the year part of arg as an integer.
20
+ #
21
+ # @param [RDF::Literal] operand
22
+ # the operand
23
+ # @return [RDF::Literal]
24
+ # @raise [TypeError] if the operand is not a simple literal
25
+ def apply(operand)
26
+ raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
27
+ RDF::Literal(operand.object.year)
28
+ end
29
+ end # Year
30
+ end # Operator
31
+ end; end # SPARQL::Algebra
@@ -16,7 +16,7 @@ module SPARQL::Grammar
16
16
  # Builtin functions
17
17
  BUILTINS = %w{
18
18
  ABS BNODE CEIL COALESCE CONCAT
19
- CONTAINS DATATYPE DAY ENCODE_FOR_URI EXISTS
19
+ CONTAINS DATATYPE DAY ENCODE_FOR_URI
20
20
  FLOOR HOURS IF IRI LANGMATCHES LANG LCASE
21
21
  MD5 MINUTES MONTH NOW RAND ROUND SECONDS
22
22
  SHA1 SHA224 SHA256 SHA384 SHA512
@@ -25,8 +25,9 @@ module SPARQL::Grammar
25
25
  isBLANK isIRI isURI isLITERAL isNUMERIC sameTerm
26
26
  }.map {|s| s.downcase.to_sym}.freeze
27
27
 
28
- BUILTIN_RULES = [:regex, :substr, :replace, :exists, :not_exists].freeze
28
+ BUILTIN_RULES = [:aggregate, :regex, :substr, :replace, :exists, :notexists].freeze
29
29
 
30
+ AGGREGATE_RULES = [:count, :sum, :min, :max, :avg, :sample, :group_concat]
30
31
  ##
31
32
  # Any additional options for the parser.
32
33
  #
@@ -173,12 +174,12 @@ module SPARQL::Grammar
173
174
  when /ASC|DESC/ then add_prod_datum(:OrderDirection, token.value.downcase.to_sym)
174
175
  when /DISTINCT|REDUCED/ then add_prod_datum(:DISTINCT_REDUCED, token.value.downcase.to_sym)
175
176
  when %r{
176
- ABS|BNODE|BOUND|CEIL|COALESCE|CONCAT
177
- |CONTAINS|DATATYPE|DAY|ENCODE_FOR_URI|EXISTS
178
- |FLOOR|HOURS|IF|IRI|LANGMATCHES|LANG|LCASE
179
- |MD5|MINUTES|MONTH|NOW|RAND|REPLACE|ROUND|SECONDS
177
+ ABS|AVG|BNODE|BOUND|CEIL|COALESCE|CONCAT
178
+ |CONTAINS|COUNT|DATATYPE|DAY|ENCODE_FOR_URI|EXISTS
179
+ |FLOOR|HOURS|IF|GROUP_CONCAT|IRI|LANGMATCHES|LANG|LCASE
180
+ |MAX|MD5|MINUTES|MIN|MONTH|NOW|RAND|REPLACE|ROUND|SAMPLE|SECONDS|SEPARATOR
180
181
  |SHA1|SHA224|SHA256|SHA384|SHA512
181
- |STRAFTER|STRBEFORE|STRDT|STRENDS|STRLANG|STRLEN|STRSTARTS|STRUUID|SUBSTR|STR
182
+ |STRAFTER|STRBEFORE|STRDT|STRENDS|STRLANG|STRLEN|STRSTARTS|STRUUID|SUBSTR|STR|SUM
182
183
  |TIMEZONE|TZ|UCASE|URI|UUID|YEAR
183
184
  |isBLANK|isIRI|isURI|isLITERAL|isNUMERIC|sameTerm
184
185
  }x
@@ -245,7 +246,6 @@ module SPARQL::Grammar
245
246
  # [9.8] _SelectClause_8 ::= ( '(' Expression 'AS' Var ')' )
246
247
  production(:_SelectClause_8) do |input, data, callback|
247
248
  add_prod_datum :extend, [data[:Expression].unshift(data[:Var].first)]
248
- add_prod_datum :Var, data[:Var]
249
249
  end
250
250
 
251
251
  # [10] ConstructQuery ::= 'CONSTRUCT'
@@ -290,13 +290,25 @@ module SPARQL::Grammar
290
290
  # [18] SolutionModifier ::= GroupClause? HavingClause? OrderClause? LimitOffsetClauses?
291
291
 
292
292
  # [19] GroupClause ::= 'GROUP' 'BY' GroupCondition+
293
- #production(:GroupClause) do |input, data, callback|
294
- #end
293
+ production(:GroupClause) do |input, data, callback|
294
+ add_prod_data :group, data[:GroupCondition]
295
+ end
295
296
 
296
297
  # [20] GroupCondition ::= BuiltInCall | FunctionCall
297
298
  # | '(' Expression ( 'AS' Var )? ')' | Var
298
- #production(:GroupClause) do |input, data, callback|
299
- #end
299
+ production(:GroupCondition) do |input, data, callback|
300
+ add_prod_datum :GroupCondition, data.values.first
301
+ end
302
+
303
+ # _GroupCondition_1 ::= '(' Expression ( 'AS' Var )? ')'
304
+ production(:_GroupCondition_1) do |input, data, callback|
305
+ cond = if data[:Var]
306
+ [data[:Expression].unshift(data[:Var].first)]
307
+ else
308
+ data[:Expression]
309
+ end
310
+ add_prod_datum(:GroupCondition, cond)
311
+ end
300
312
 
301
313
  # [21] HavingClause ::= 'HAVING' HavingCondition+
302
314
  #production(:GroupClause) do |input, data, callback|
@@ -797,7 +809,7 @@ module SPARQL::Grammar
797
809
  # [119] PrimaryExpression ::= BrackettedExpression | BuiltInCall
798
810
  # | iriOrFunction | RDFLiteral
799
811
  # | NumericLiteral | BooleanLiteral
800
- # | Var | Aggregate
812
+ # | Var
801
813
  production(:PrimaryExpression) do |input, data, callback|
802
814
  if data[:Expression]
803
815
  add_prod_datum(:Expression, data[:Expression])
@@ -817,56 +829,62 @@ module SPARQL::Grammar
817
829
  add_prod_datum(:UnaryExpression, data[:UnaryExpression])
818
830
  end
819
831
 
820
- # [122] BuiltInCall ::= 'STR' '(' Expression ')'
821
- # | 'LANG' '(' Expression ')'
822
- # | 'LANGMATCHES' '(' Expression ',' Expression ')'
823
- # | 'DATATYPE' '(' Expression ')'
824
- # | 'BOUND' '(' Var ')'
825
- # | 'IRI' '(' Expression ')'
826
- # | 'URI' '(' Expression ')'
827
- # | 'BNODE' ( '(' Expression ')' | NIL )
828
- # | 'RAND' NIL
829
- # | 'ABS' '(' Expression ')'
830
- # | 'CEIL' '(' Expression ')'
831
- # | 'FLOOR' '(' Expression ')'
832
- # | 'ROUND' '(' Expression ')'
833
- # | 'CONCAT' ExpressionList
834
- # | SubstringExpression
835
- # | 'STRLEN' '(' Expression ')'
836
- # | 'UCASE' '(' Expression ')'
837
- # | 'LCASE' '(' Expression ')'
838
- # | 'ENCODE_FOR_URI' '(' Expression ')'
839
- # | 'CONTAINS' '(' Expression ',' Expression ')'
840
- # | 'STRSTARTS' '(' Expression ',' Expression ')'
841
- # | 'STRENDS' '(' Expression ',' Expression ')'
842
- # | 'YEAR' '(' Expression ')'
843
- # | 'MONTH' '(' Expression ')'
844
- # | 'DAY' '(' Expression ')'
845
- # | 'HOURS' '(' Expression ')'
846
- # | 'MINUTES' '(' Expression ')'
847
- # | 'SECONDS' '(' Expression ')'
848
- # | 'TIMEZONE' '(' Expression ')'
849
- # | 'TZ' '(' Expression ')'
850
- # | 'NOW' NIL
851
- # | 'MD5' '(' Expression ')'
852
- # | 'SHA1' '(' Expression ')'
853
- # | 'SHA224' '(' Expression ')'
854
- # | 'SHA256' '(' Expression ')'
855
- # | 'SHA384' '(' Expression ')'
856
- # | 'SHA512' '(' Expression ')'
857
- # | 'COALESCE' ExpressionList
858
- # | 'IF' '(' Expression ',' Expression ',' Expression ')'
859
- # | 'STRLANG' '(' Expression ',' Expression ')'
860
- # | 'STRDT' '(' Expression ',' Expression ')'
861
- # | 'sameTerm' '(' Expression ',' Expression ')'
862
- # | 'isIRI' '(' Expression ')'
863
- # | 'isURI' '(' Expression ')'
864
- # | 'isBLANK' '(' Expression ')'
865
- # | 'isLITERAL' '(' Expression ')'
866
- # | 'isNUMERIC' '(' Expression ')'
867
- # | RegexExpression
868
- # | ExistsFunc
869
- # | NotExistsFunc
832
+ # [121] BuiltInCall ::= Aggregate
833
+ # | 'STR' '(' Expression ')'
834
+ # | 'LANG' '(' Expression ')'
835
+ # | 'LANGMATCHES' '(' Expression ',' Expression ')'
836
+ # | 'DATATYPE' '(' Expression ')'
837
+ # | 'BOUND' '(' Var ')'
838
+ # | 'IRI' '(' Expression ')'
839
+ # | 'URI' '(' Expression ')'
840
+ # | 'BNODE' ( '(' Expression ')' | NIL )
841
+ # | 'RAND' NIL
842
+ # | 'ABS' '(' Expression ')'
843
+ # | 'CEIL' '(' Expression ')'
844
+ # | 'FLOOR' '(' Expression ')'
845
+ # | 'ROUND' '(' Expression ')'
846
+ # | 'CONCAT' ExpressionList
847
+ # | SubstringExpression
848
+ # | 'STRLEN' '(' Expression ')'
849
+ # | StrReplaceExpression
850
+ # | 'UCASE' '(' Expression ')'
851
+ # | 'LCASE' '(' Expression ')'
852
+ # | 'ENCODE_FOR_URI' '(' Expression ')'
853
+ # | 'CONTAINS' '(' Expression ',' Expression ')'
854
+ # | 'STRSTARTS' '(' Expression ',' Expression ')'
855
+ # | 'STRENDS' '(' Expression ',' Expression ')'
856
+ # | 'STRBEFORE' '(' Expression ',' Expression ')'
857
+ # | 'STRAFTER' '(' Expression ',' Expression ')'
858
+ # | 'YEAR' '(' Expression ')'
859
+ # | 'MONTH' '(' Expression ')'
860
+ # | 'DAY' '(' Expression ')'
861
+ # | 'HOURS' '(' Expression ')'
862
+ # | 'MINUTES' '(' Expression ')'
863
+ # | 'SECONDS' '(' Expression ')'
864
+ # | 'TIMEZONE' '(' Expression ')'
865
+ # | 'TZ' '(' Expression ')'
866
+ # | 'NOW' NIL
867
+ # | 'UUID' NIL
868
+ # | 'STRUUID' NIL
869
+ # | 'MD5' '(' Expression ')'
870
+ # | 'SHA1' '(' Expression ')'
871
+ # | 'SHA224' '(' Expression ')'
872
+ # | 'SHA256' '(' Expression ')'
873
+ # | 'SHA384' '(' Expression ')'
874
+ # | 'SHA512' '(' Expression ')'
875
+ # | 'COALESCE' ExpressionList
876
+ # | 'IF' '(' Expression ',' Expression ',' Expression ')'
877
+ # | 'STRLANG' '(' Expression ',' Expression ')'
878
+ # | 'STRDT' '(' Expression ',' Expression ')'
879
+ # | 'sameTerm' '(' Expression ',' Expression ')'
880
+ # | 'isIRI' '(' Expression ')'
881
+ # | 'isURI' '(' Expression ')'
882
+ # | 'isBLANK' '(' Expression ')'
883
+ # | 'isLITERAL' '(' Expression ')'
884
+ # | 'isNUMERIC' '(' Expression ')'
885
+ # | RegexExpression
886
+ # | ExistsFunc
887
+ # | NotExistsFunc
870
888
  production(:BuiltInCall) do |input, data, callback|
871
889
  if builtin = data.keys.detect {|k| BUILTINS.include?(k)}
872
890
  add_prod_datum(:BuiltInCall,
@@ -875,6 +893,8 @@ module SPARQL::Grammar
875
893
  unshift(builtin)))
876
894
  elsif builtin_rule = data.keys.detect {|k| BUILTIN_RULES.include?(k)}
877
895
  add_prod_datum(:BuiltInCall, SPARQL::Algebra::Expression.for(data[builtin_rule].unshift(builtin_rule)))
896
+ elsif aggregate_rule = data.keys.detect {|k| AGGREGATE_RULES.include?(k)}
897
+ add_prod_datum(:BuiltInCall, data[aggregate_rule].first)
878
898
  elsif data[:bound]
879
899
  add_prod_datum(:BuiltInCall, SPARQL::Algebra::Expression.for(data[:Var].unshift(:bound)))
880
900
  elsif data[:BuiltInCall]
@@ -910,7 +930,25 @@ module SPARQL::Grammar
910
930
 
911
931
  # [126] NotExistsFunc ::= 'NOT' 'EXISTS' GroupGraphPattern
912
932
  production(:NotExistsFunc) do |input, data, callback|
913
- add_prod_datum(:not_exists, data[:query])
933
+ add_prod_datum(:notexists, data[:query])
934
+ end
935
+
936
+ # [127] Aggregate ::= 'COUNT' '(' 'DISTINCT'? ( '*' | Expression ) ')'
937
+ # | 'SUM' '(' 'DISTINCT'? Expression ')'
938
+ # | 'MIN' '(' 'DISTINCT'? Expression ')'
939
+ # | 'MAX' '(' 'DISTINCT'? Expression ')'
940
+ # | 'AVG' '(' 'DISTINCT'? Expression ')'
941
+ # | 'SAMPLE' '(' 'DISTINCT'? Expression ')'
942
+ # | 'GROUP_CONCAT' '(' 'DISTINCT'? Expression
943
+ # ( ';' 'SEPARATOR' '=' String )? ')'
944
+ production(:Aggregate) do |input, data, callback|
945
+ if aggregate_rule = data.keys.detect {|k| AGGREGATE_RULES.include?(k)}
946
+ parts = [aggregate_rule]
947
+ parts << [:separator, data[:string].first] if data[:separator] && data[:string]
948
+ parts << :distinct if data[:DISTINCT_REDUCED]
949
+ parts << data[:Expression].first if data[:Expression]
950
+ add_prod_data(aggregate_rule, SPARQL::Algebra::Expression.for(parts))
951
+ end
914
952
  end
915
953
 
916
954
  # [128] iriOrFunction ::= iri ArgList?
@@ -988,8 +1026,8 @@ module SPARQL::Grammar
988
1026
  @input.force_encoding(Encoding::UTF_8)
989
1027
  @options = {:anon_base => "b0", :validate => false}.merge(options)
990
1028
  @options[:debug] ||= case
991
- when @options[:progress] then 2
992
- when @options[:validate] then 1
1029
+ when options[:progress] then 2
1030
+ when options[:validate] then 1
993
1031
  end
994
1032
 
995
1033
  debug("base IRI") {base_uri.inspect}
@@ -1311,15 +1349,35 @@ module SPARQL::Grammar
1311
1349
  end
1312
1350
 
1313
1351
  # Merge query modifiers, datasets, and projections
1352
+ #
1353
+ # This includes tranforming aggregates if also used with a GROUP BY
1354
+ #
1355
+ # @see http://www.w3.org/TR/sparql11-query/#convertGroupAggSelectExpressions
1314
1356
  def merge_modifiers(data)
1315
1357
  query = data[:query] ? data[:query].first : SPARQL::Algebra::Operator::BGP.new
1316
-
1358
+
1359
+ vars = data[:Var] || []
1360
+ order = data[:order] ? data[:order].first : []
1361
+
1317
1362
  # Add datasets and modifiers in order
1318
- query = SPARQL::Algebra::Expression[:extend, data[:extend], query] if data[:extend]
1363
+ if data[:group]
1364
+ query = SPARQL::Algebra::Expression[:group, data[:group].first, query]
1365
+ end
1366
+
1367
+ if data[:extend]
1368
+ # extension variables must not appear in projected variables.
1369
+ # Add them to the projection otherwise
1370
+ data[:extend].each do |(var, expr)|
1371
+ raise Error, "Extension variable #{var} also in SELECT" if vars.map(&:to_s).include?(var.to_s)
1372
+ vars << var
1373
+ end
1374
+
1375
+ query = SPARQL::Algebra::Expression[:extend, data[:extend], query]
1376
+ end
1319
1377
 
1320
- query = SPARQL::Algebra::Expression[:order, data[:order].first, query] if data[:order]
1378
+ query = SPARQL::Algebra::Expression[:order, data[:order].first, query] unless order.empty?
1321
1379
 
1322
- query = SPARQL::Algebra::Expression[:project, data[:Var], query] if data[:Var]
1380
+ query = SPARQL::Algebra::Expression[:project, vars, query] unless vars.empty?
1323
1381
 
1324
1382
  query = SPARQL::Algebra::Expression[data[:DISTINCT_REDUCED].first, query] if data[:DISTINCT_REDUCED]
1325
1383
 
@@ -91,7 +91,7 @@ module SPARQL::Grammar
91
91
  ANON = /\[#{WS}*\]/m
92
92
 
93
93
  # String terminals, case insensitive
94
- STR_EXPR = %r(ABS|ADD|ALL|ASC|ASK|AS|BASE|BINDINGS|BIND
94
+ STR_EXPR = %r(ABS|ADD|ALL|ASC|ASK|AS|AVG|BASE|BINDINGS|BIND
95
95
  |BNODE|BOUND|BY|CEIL|CLEAR|COALESCE|CONCAT
96
96
  |CONSTRUCT|CONTAINS|COPY|COUNT|CREATE|DATATYPE|DAY
97
97
  |DEFAULT|DELETE\sDATA|DELETE\sWHERE|DELETE
@@ -114,7 +114,7 @@ module SPARQL::Grammar
114
114
  )xi
115
115
 
116
116
  # Map terminals to canonical form
117
- STR_MAP = (%w{ABS ADD ALL ASC ASK AS BASE BINDINGS BIND
117
+ STR_MAP = (%w{ABS ADD ALL ASC ASK AS AVG BASE BINDINGS BIND
118
118
  BNODE BOUND BY CEIL CLEAR COALESCE CONCAT
119
119
  CONSTRUCT CONTAINS COPY COUNT CREATE DATATYPE DAY
120
120
  DEFAULT DELETE
@@ -131,12 +131,11 @@ module SPARQL::Grammar
131
131
  TIMEZONE TO TZ UCASE UNDEF UNION URI USING UUID
132
132
  WHERE WITH YEAR
133
133
  isBLANK isIRI isURI isLITERAL isNUMERIC sameTerm
134
- true
135
- false
134
+ true false
136
135
  } + [
137
136
  "DELETE DATA",
138
137
  "DELETE WHERE",
139
138
  "INSERT DATA",
140
- ]).inject({}) {|memo, t| memo[t.downcase] = t; memo}.freeze
139
+ ]).inject({}) {|memo, t| memo[t.sub(' ', '_').downcase] = t; memo}.freeze
141
140
  end
142
141
  end