sparql 3.1.0 → 3.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +188 -73
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/bin/sparql +37 -17
  6. data/lib/rack/sparql/conneg.rb +2 -2
  7. data/lib/sinatra/sparql.rb +4 -4
  8. data/lib/sparql.rb +13 -12
  9. data/lib/sparql/algebra.rb +11 -19
  10. data/lib/sparql/algebra/aggregate.rb +2 -2
  11. data/lib/sparql/algebra/expression.rb +67 -38
  12. data/lib/sparql/algebra/extensions.rb +182 -23
  13. data/lib/sparql/algebra/operator.rb +55 -22
  14. data/lib/sparql/algebra/operator/abs.rb +2 -2
  15. data/lib/sparql/algebra/operator/add.rb +2 -2
  16. data/lib/sparql/algebra/operator/alt.rb +2 -2
  17. data/lib/sparql/algebra/operator/and.rb +3 -3
  18. data/lib/sparql/algebra/operator/asc.rb +1 -1
  19. data/lib/sparql/algebra/operator/ask.rb +2 -12
  20. data/lib/sparql/algebra/operator/avg.rb +8 -1
  21. data/lib/sparql/algebra/operator/base.rb +8 -8
  22. data/lib/sparql/algebra/operator/bgp.rb +2 -2
  23. data/lib/sparql/algebra/operator/bnode.rb +2 -2
  24. data/lib/sparql/algebra/operator/bound.rb +1 -1
  25. data/lib/sparql/algebra/operator/ceil.rb +2 -2
  26. data/lib/sparql/algebra/operator/clear.rb +2 -2
  27. data/lib/sparql/algebra/operator/coalesce.rb +1 -11
  28. data/lib/sparql/algebra/operator/compare.rb +15 -8
  29. data/lib/sparql/algebra/operator/concat.rb +2 -2
  30. data/lib/sparql/algebra/operator/construct.rb +4 -13
  31. data/lib/sparql/algebra/operator/contains.rb +2 -2
  32. data/lib/sparql/algebra/operator/copy.rb +2 -2
  33. data/lib/sparql/algebra/operator/count.rb +1 -1
  34. data/lib/sparql/algebra/operator/create.rb +2 -2
  35. data/lib/sparql/algebra/operator/dataset.rb +3 -14
  36. data/lib/sparql/algebra/operator/datatype.rb +1 -1
  37. data/lib/sparql/algebra/operator/day.rb +1 -1
  38. data/lib/sparql/algebra/operator/delete.rb +6 -6
  39. data/lib/sparql/algebra/operator/delete_data.rb +2 -2
  40. data/lib/sparql/algebra/operator/delete_where.rb +3 -3
  41. data/lib/sparql/algebra/operator/desc.rb +1 -1
  42. data/lib/sparql/algebra/operator/describe.rb +2 -12
  43. data/lib/sparql/algebra/operator/distinct.rb +2 -12
  44. data/lib/sparql/algebra/operator/divide.rb +1 -1
  45. data/lib/sparql/algebra/operator/drop.rb +2 -2
  46. data/lib/sparql/algebra/operator/encode_for_uri.rb +2 -2
  47. data/lib/sparql/algebra/operator/equal.rb +2 -2
  48. data/lib/sparql/algebra/operator/exists.rb +1 -1
  49. data/lib/sparql/algebra/operator/exprlist.rb +1 -11
  50. data/lib/sparql/algebra/operator/extend.rb +3 -12
  51. data/lib/sparql/algebra/operator/filter.rb +3 -13
  52. data/lib/sparql/algebra/operator/floor.rb +2 -2
  53. data/lib/sparql/algebra/operator/graph.rb +4 -15
  54. data/lib/sparql/algebra/operator/greater_than.rb +5 -5
  55. data/lib/sparql/algebra/operator/greater_than_or_equal.rb +5 -5
  56. data/lib/sparql/algebra/operator/group.rb +14 -14
  57. data/lib/sparql/algebra/operator/group_concat.rb +1 -1
  58. data/lib/sparql/algebra/operator/hours.rb +1 -1
  59. data/lib/sparql/algebra/operator/if.rb +1 -11
  60. data/lib/sparql/algebra/operator/in.rb +1 -11
  61. data/lib/sparql/algebra/operator/insert.rb +4 -4
  62. data/lib/sparql/algebra/operator/insert_data.rb +2 -2
  63. data/lib/sparql/algebra/operator/iri.rb +1 -1
  64. data/lib/sparql/algebra/operator/is_blank.rb +1 -1
  65. data/lib/sparql/algebra/operator/is_iri.rb +1 -1
  66. data/lib/sparql/algebra/operator/is_literal.rb +1 -1
  67. data/lib/sparql/algebra/operator/is_numeric.rb +1 -1
  68. data/lib/sparql/algebra/operator/is_triple.rb +30 -0
  69. data/lib/sparql/algebra/operator/join.rb +9 -7
  70. data/lib/sparql/algebra/operator/lang.rb +1 -1
  71. data/lib/sparql/algebra/operator/lang_matches.rb +3 -3
  72. data/lib/sparql/algebra/operator/lcase.rb +2 -2
  73. data/lib/sparql/algebra/operator/left_join.rb +16 -9
  74. data/lib/sparql/algebra/operator/less_than.rb +5 -5
  75. data/lib/sparql/algebra/operator/less_than_or_equal.rb +5 -5
  76. data/lib/sparql/algebra/operator/load.rb +2 -2
  77. data/lib/sparql/algebra/operator/max.rb +8 -1
  78. data/lib/sparql/algebra/operator/md5.rb +1 -1
  79. data/lib/sparql/algebra/operator/min.rb +8 -1
  80. data/lib/sparql/algebra/operator/minus.rb +9 -8
  81. data/lib/sparql/algebra/operator/minutes.rb +1 -1
  82. data/lib/sparql/algebra/operator/modify.rb +1 -1
  83. data/lib/sparql/algebra/operator/month.rb +1 -1
  84. data/lib/sparql/algebra/operator/move.rb +2 -2
  85. data/lib/sparql/algebra/operator/multiply.rb +1 -1
  86. data/lib/sparql/algebra/operator/negate.rb +1 -1
  87. data/lib/sparql/algebra/operator/not.rb +1 -1
  88. data/lib/sparql/algebra/operator/not_equal.rb +2 -2
  89. data/lib/sparql/algebra/operator/notexists.rb +2 -2
  90. data/lib/sparql/algebra/operator/notin.rb +1 -11
  91. data/lib/sparql/algebra/operator/notoneof.rb +2 -2
  92. data/lib/sparql/algebra/operator/now.rb +1 -1
  93. data/lib/sparql/algebra/operator/object.rb +27 -0
  94. data/lib/sparql/algebra/operator/or.rb +3 -3
  95. data/lib/sparql/algebra/operator/order.rb +2 -12
  96. data/lib/sparql/algebra/operator/path.rb +2 -2
  97. data/lib/sparql/algebra/operator/path_opt.rb +2 -2
  98. data/lib/sparql/algebra/operator/path_plus.rb +2 -2
  99. data/lib/sparql/algebra/operator/path_star.rb +2 -2
  100. data/lib/sparql/algebra/operator/plus.rb +2 -2
  101. data/lib/sparql/algebra/operator/predicate.rb +27 -0
  102. data/lib/sparql/algebra/operator/prefix.rb +8 -8
  103. data/lib/sparql/algebra/operator/project.rb +2 -12
  104. data/lib/sparql/algebra/operator/rand.rb +1 -1
  105. data/lib/sparql/algebra/operator/reduced.rb +2 -12
  106. data/lib/sparql/algebra/operator/regex.rb +5 -5
  107. data/lib/sparql/algebra/operator/replace.rb +3 -3
  108. data/lib/sparql/algebra/operator/reverse.rb +2 -2
  109. data/lib/sparql/algebra/operator/round.rb +2 -2
  110. data/lib/sparql/algebra/operator/same_term.rb +8 -5
  111. data/lib/sparql/algebra/operator/sample.rb +9 -2
  112. data/lib/sparql/algebra/operator/seconds.rb +1 -1
  113. data/lib/sparql/algebra/operator/seq.rb +1 -1
  114. data/lib/sparql/algebra/operator/sequence.rb +1 -1
  115. data/lib/sparql/algebra/operator/sha1.rb +1 -1
  116. data/lib/sparql/algebra/operator/sha256.rb +1 -1
  117. data/lib/sparql/algebra/operator/sha384.rb +1 -1
  118. data/lib/sparql/algebra/operator/sha512.rb +1 -1
  119. data/lib/sparql/algebra/operator/slice.rb +2 -12
  120. data/lib/sparql/algebra/operator/str.rb +1 -1
  121. data/lib/sparql/algebra/operator/strafter.rb +2 -2
  122. data/lib/sparql/algebra/operator/strbefore.rb +2 -2
  123. data/lib/sparql/algebra/operator/strdt.rb +2 -2
  124. data/lib/sparql/algebra/operator/strends.rb +2 -2
  125. data/lib/sparql/algebra/operator/strlang.rb +2 -2
  126. data/lib/sparql/algebra/operator/strlen.rb +2 -2
  127. data/lib/sparql/algebra/operator/strstarts.rb +2 -2
  128. data/lib/sparql/algebra/operator/struuid.rb +1 -1
  129. data/lib/sparql/algebra/operator/subject.rb +29 -0
  130. data/lib/sparql/algebra/operator/substr.rb +3 -3
  131. data/lib/sparql/algebra/operator/subtract.rb +1 -1
  132. data/lib/sparql/algebra/operator/sum.rb +1 -1
  133. data/lib/sparql/algebra/operator/table.rb +2 -2
  134. data/lib/sparql/algebra/operator/timezone.rb +1 -1
  135. data/lib/sparql/algebra/operator/triple.rb +27 -0
  136. data/lib/sparql/algebra/operator/tz.rb +1 -1
  137. data/lib/sparql/algebra/operator/ucase.rb +2 -2
  138. data/lib/sparql/algebra/operator/union.rb +7 -6
  139. data/lib/sparql/algebra/operator/update.rb +2 -2
  140. data/lib/sparql/algebra/operator/using.rb +2 -2
  141. data/lib/sparql/algebra/operator/uuid.rb +1 -1
  142. data/lib/sparql/algebra/operator/with.rb +3 -3
  143. data/lib/sparql/algebra/operator/year.rb +1 -1
  144. data/lib/sparql/algebra/query.rb +1 -1
  145. data/lib/sparql/algebra/update.rb +1 -1
  146. data/lib/sparql/algebra/version.rb +1 -1
  147. data/lib/sparql/extensions.rb +7 -13
  148. data/lib/sparql/grammar.rb +81 -6
  149. data/lib/sparql/grammar/meta.rb +5801 -1584
  150. data/lib/sparql/grammar/parser11.rb +116 -50
  151. data/lib/sparql/grammar/terminals11.rb +4 -0
  152. data/lib/sparql/results.rb +70 -43
  153. data/lib/sparql/version.rb +1 -1
  154. metadata +34 -39
@@ -43,7 +43,7 @@ module Rack; module SPARQL
43
43
  #
44
44
  # @param [Hash{String => String}] env
45
45
  # @return [Array(Integer, Hash, #each)]
46
- # @see http://www.rubydoc.info/github/rack/rack/Rack/Runtime#call-instance_method
46
+ # @see https://www.rubydoc.info/github/rack/rack/Rack/Runtime#call-instance_method
47
47
  def call(env)
48
48
  env['ORDERED_CONTENT_TYPES'] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
49
49
  response = app.call(env)
@@ -89,7 +89,7 @@ module Rack; module SPARQL
89
89
  #
90
90
  # @param [String, #to_s] header
91
91
  # @return [Array<String>]
92
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
92
+ # @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
93
93
  def parse_accept_header(header)
94
94
  entries = header.to_s.split(',')
95
95
  entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
@@ -8,7 +8,7 @@ module Sinatra
8
8
  #
9
9
  # To override negotiation on Content-Type, set :format in `sparql_options` to a RDF Format class, or symbol identifying a format.
10
10
  #
11
- # @see http://www.sinatrarb.com/extensions.html
11
+ # @see https://www.sinatrarb.com/extensions.html
12
12
  module SPARQL
13
13
  ##
14
14
  # Helper methods.
@@ -25,8 +25,8 @@ module Sinatra
25
25
  # URI of the service endpoint, defaults to "/sparql" in the current realm.
26
26
  # @return [RDF::Graph]
27
27
  #
28
- # @see http://www.w3.org/TR/sparql11-service-description
29
- # @see http://www.w3.org/TR/void/
28
+ # @see https://www.w3.org/TR/sparql11-service-description
29
+ # @see https://www.w3.org/TR/void/
30
30
  def service_description(**options)
31
31
  repository = options[:repository]
32
32
 
@@ -103,7 +103,7 @@ module Sinatra
103
103
  app.helpers(Sinatra::SPARQL::Helpers)
104
104
  app.send(:include, ::SPARQL)
105
105
  app.send(:include, ::RDF)
106
- app.send(:include, ::LinkedData)
106
+ app.send(:include, ::LinkedData) if defined?(::LinkedData)
107
107
  end
108
108
  end
109
109
  end
data/lib/sparql.rb CHANGED
@@ -1,16 +1,18 @@
1
+ require 'sxp'
1
2
  require 'sparql/extensions'
3
+ require 'sparql/algebra/sxp_extensions'
2
4
 
3
5
  ##
4
6
  # A SPARQL for RDF.rb.
5
7
  #
6
- # @see http://www.w3.org/TR/sparql11-query
8
+ # @see https://www.w3.org/TR/sparql11-query
7
9
  module SPARQL
8
10
  autoload :Algebra, 'sparql/algebra'
9
11
  autoload :Grammar, 'sparql/grammar'
10
12
  autoload :Results, 'sparql/results'
11
13
  autoload :VERSION, 'sparql/version'
12
14
 
13
- # @see http://rubygems.org/gems/sparql-client
15
+ # @see https://rubygems-client
14
16
  autoload :Client, 'sparql/client'
15
17
 
16
18
  ##
@@ -52,12 +54,13 @@ module SPARQL
52
54
  # results = SPARQL.execute("SELECT * WHERE { ?s ?p ?o }", repository)
53
55
  #
54
56
  # @param [IO, StringIO, String, #to_s] query
57
+ # @param [RDF::Queryable] queryable
55
58
  # @param [Hash{Symbol => Object}] options
56
- # @option options [RDF::Queryable] :queryable
57
- # @option options [RDF::URI, String, Array<RDF::URI, String>] :load_datasets
58
- # One or more URIs used to initialize a new instance of `queryable` in the default graph.
59
+ # @option options [Boolean] :optimize
60
+ # Optimize query before execution.
59
61
  # @option options [RDF::URI, String, Array<RDF::URI, String>] :default_graph_uri
60
- # One or more URIs used to initialize a new instance of `queryable` in the default graph.
62
+ # @option options [RDF::URI, String, Array<RDF::URI, String>] :load_datasets
63
+ # One or more URIs used to initialize a new instance of `queryable` in the default graph. One or more URIs used to initialize a new instance of `queryable` in the default graph.
61
64
  # @option options [RDF::URI, String, Array<RDF::URI, String>] :named_graph_uri
62
65
  # One or more URIs used to initialize the `queryable` as a named graph.
63
66
  # @yield [solution]
@@ -69,13 +72,11 @@ module SPARQL
69
72
  # @raise [SPARQL::MalformedQuery] on invalid input
70
73
  def self.execute(query, queryable, **options, &block)
71
74
  query = self.parse(query, **options)
75
+ query = query.optimize(**options) if options[:optimize]
72
76
  queryable = queryable || RDF::Repository.new
73
-
74
- case options.fetch(:debug, nil)
75
- when TrueClass
76
- puts query.to_sxp
77
- when Array
78
- options[:debug] << query.to_sxp
77
+
78
+ if options[:logger]
79
+ options[:logger].debug("SPARQL.execute") {SXP::Generator.string query.to_sxp_bin}
79
80
  end
80
81
 
81
82
  if options.has_key?(:load_datasets)
@@ -1,4 +1,4 @@
1
- require 'rdf' # @see http://rubygems.org/gems/rdf
1
+ require 'rdf' # @see https://rubygems.org/gems/rdf
2
2
  require 'rdf/xsd'
3
3
 
4
4
  module SPARQL
@@ -141,21 +141,21 @@ module SPARQL
141
141
  # ## Constructing operator expressions manually
142
142
  #
143
143
  # Operator(:isBlank).new(RDF::Node(:foobar)).to_sxp #=> "(isBlank _:foobar)"
144
- # Operator(:isIRI).new(RDF::URI('http://rubygems.org/gems/rdf/')).to_sxp #=> "(isIRI <http://rubygems.org/gems/rdf/>)"
145
- # Operator(:isLiteral).new(RDF::Literal(3.1415)).to_sxp #=> "(isLiteral 3.1415)"
146
- # Operator(:str).new(Operator(:datatype).new(RDF::Literal(3.1415))).to_sxp #=> "(str (datatype 3.1415))"
144
+ # Operator(:isIRI).new(RDF::URI('https://rubygems.org/gems/rdf/')).to_sxp #=> "(isIRI <https://rubygems.org/gems/rdf/>)"
145
+ # Operator(:isLiteral).new(RDF::Literal(3.1415)).to_sxp #=> "(isLiteral 3.1415e0)"
146
+ # Operator(:str).new(Operator(:datatype).new(RDF::Literal(3.1415))).to_sxp #=> "(str (datatype 3.1415e0))"
147
147
  #
148
148
  # ## Constructing operator expressions using SSE forms
149
149
  #
150
150
  # SPARQL::Algebra::Expression[:isBlank, RDF::Node(:foobar)].to_sxp #=> "(isBlank _:foobar)"
151
- # SPARQL::Algebra::Expression[:isIRI, RDF::URI('http://rubygems.org/gems/rdf/')].to_sxp #=> "(isIRI <http://rubygems.org/gems/rdf/>)"
152
- # SPARQL::Algebra::Expression[:isLiteral, RDF::Literal(3.1415)].to_sxp #=> "(isLiteral 3.1415)"
153
- # SPARQL::Algebra::Expression[:str, [:datatype, RDF::Literal(3.1415)]].to_sxp #=> "(str (datatype 3.1415))"
151
+ # SPARQL::Algebra::Expression[:isIRI, RDF::URI('https://rubygems.org/gems/rdf/')].to_sxp #=> "(isIRI <https://rubygems.org/gems/rdf/>)"
152
+ # SPARQL::Algebra::Expression[:isLiteral, RDF::Literal(3.1415)].to_sxp #=> "(isLiteral 3.1415e0)"
153
+ # SPARQL::Algebra::Expression[:str, [:datatype, RDF::Literal(3.1415)]].to_sxp #=> "(str (datatype 3.1415e0))"
154
154
  #
155
155
  # ## Constructing operator expressions using SSE strings
156
156
  #
157
157
  # SPARQL::Algebra::Expression.parse('(isBlank _:foobar)')
158
- # SPARQL::Algebra::Expression.parse('(isIRI <http://rubygems.org/gems/rdf/>)')
158
+ # SPARQL::Algebra::Expression.parse('(isIRI <https://rubygems.org/gems/rdf/>)')
159
159
  # SPARQL::Algebra::Expression.parse('(isLiteral 3.1415)')
160
160
  # SPARQL::Algebra::Expression.parse('(str (datatype 3.1415))')
161
161
  #
@@ -165,11 +165,6 @@ module SPARQL
165
165
  # Operator(:isIRI).evaluate(RDF::Vocab::DC.title) #=> RDF::Literal::TRUE
166
166
  # Operator(:isLiteral).evaluate(RDF::Literal(3.1415)) #=> RDF::Literal::TRUE
167
167
  #
168
- # ## Optimizing expressions containing constant subexpressions
169
- #
170
- # SPARQL::Algebra::Expression.parse('(sameTerm ?var ?var)').optimize #=> RDF::Literal::TRUE
171
- # SPARQL::Algebra::Expression.parse('(* -2 (- (* (+ 1 2) (+ 3 4))))').optimize #=> RDF::Literal(42)
172
- #
173
168
  # ## Evaluating expressions on a solution sequence
174
169
  #
175
170
  # # Find all people and their names & e-mail addresses:
@@ -405,8 +400,7 @@ module SPARQL
405
400
  def Expression(*sse)
406
401
  Expression.for(*sse)
407
402
  end
408
- alias_method :Expr, :Expression
409
- module_function :Expr, :Expression
403
+ module_function :Expression
410
404
 
411
405
  ##
412
406
  # @example
@@ -417,8 +411,7 @@ module SPARQL
417
411
  def Operator(name, arity = nil)
418
412
  Operator.for(name, arity)
419
413
  end
420
- alias_method :Op, :Operator
421
- module_function :Op, :Operator
414
+ module_function :Operator
422
415
 
423
416
  ##
424
417
  # @example
@@ -430,8 +423,7 @@ module SPARQL
430
423
  def Variable(name)
431
424
  Variable.new(name)
432
425
  end
433
- alias_method :Var, :Variable
434
- module_function :Var, :Variable
426
+ module_function :Variable
435
427
 
436
428
  Variable = RDF::Query::Variable
437
429
  end # Algebra
@@ -6,8 +6,8 @@ module SPARQL; module Algebra
6
6
  # or more operands which are `Enumerable` lists of `RDF::Term`
7
7
  # and return a single `RDF::Term` or `TypeError`.
8
8
  #
9
- # @see http://www.w3.org/TR/sparql11-query/#setFunctions
10
- # @see http://www.w3.org/TR/sparql11-query/#aggregates
9
+ # @see https://www.w3.org/TR/sparql11-query/#setFunctions
10
+ # @see https://www.w3.org/TR/sparql11-query/#aggregates
11
11
  #
12
12
  # @abstract
13
13
  module Aggregate
@@ -4,6 +4,8 @@ module SPARQL; module Algebra
4
4
  #
5
5
  # @abstract
6
6
  module Expression
7
+ include RDF::Util::Logger
8
+
7
9
  ##
8
10
  # @example
9
11
  # Expression.parse('(isLiteral 3.1415)')
@@ -20,13 +22,6 @@ module SPARQL; module Algebra
20
22
  # @yieldreturn [void] ignored
21
23
  # @return [Expression]
22
24
  def self.parse(sse, **options, &block)
23
- begin
24
- require 'sxp' # @see http://rubygems.org/gems/sxp
25
- rescue LoadError
26
- abort "SPARQL::Algebra::Expression.parse requires the SXP gem (hint: `gem install sxp')."
27
- end
28
- require 'sparql/algebra/sxp_extensions'
29
-
30
25
  sse = sse.encode(Encoding::UTF_8)
31
26
  sxp = SXP::Reader::SPARQL.new(sse) do |reader|
32
27
  # Set base_uri if we have one
@@ -60,7 +55,7 @@ module SPARQL; module Algebra
60
55
  def self.open(filename, **options, &block)
61
56
  RDF::Util::File.open_file(filename, **options) do |file|
62
57
  options[:base_uri] ||= filename
63
- Expression.parse(file, options, &block)
58
+ Expression.parse(file, **options, &block)
64
59
  end
65
60
  end
66
61
 
@@ -120,9 +115,12 @@ module SPARQL; module Algebra
120
115
  end
121
116
 
122
117
  debug(options) {"#{operator.inspect}(#{operands.map(&:inspect).join(',')})"}
123
- options.delete_if {|k, v| [:debug, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
124
- operands << options unless options.empty?
125
- operator.new(*operands)
118
+ options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
119
+ begin
120
+ operator.new(*operands, **options)
121
+ rescue ArgumentError => e
122
+ error(options) {"Operator=#{operator.inspect}: #{e}"}
123
+ end
126
124
  end
127
125
 
128
126
  ##
@@ -175,8 +173,8 @@ module SPARQL; module Algebra
175
173
  # @param [RDF::URI] function
176
174
  # @param [Array<RDF::Term>] args splat of args to function
177
175
  # @return [RDF::Term]
178
- # @see http://www.w3.org/TR/sparql11-query/#extensionFunctions
179
- # @see http://www.w3.org/TR/sparql11-query/#FunctionMapping
176
+ # @see https://www.w3.org/TR/sparql11-query/#extensionFunctions
177
+ # @see https://www.w3.org/TR/sparql11-query/#FunctionMapping
180
178
  def self.extension(function, *args)
181
179
  if function.to_s.start_with?(RDF::XSD.to_s)
182
180
  self.cast(function, args.first)
@@ -197,7 +195,7 @@ module SPARQL; module Algebra
197
195
  # Value, which should be a typed literal, where the type must be that specified
198
196
  # @raise [TypeError] if datatype is not a URI or value cannot be cast to datatype
199
197
  # @return [RDF::Term]
200
- # @see http://www.w3.org/TR/sparql11-query/#FunctionMapping
198
+ # @see https://www.w3.org/TR/sparql11-query/#FunctionMapping
201
199
  def self.cast(datatype, value)
202
200
  case datatype
203
201
  when RDF::XSD.dateTime
@@ -229,21 +227,45 @@ module SPARQL; module Algebra
229
227
  when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node
230
228
  raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
231
229
  else
232
- RDF::Literal.new(!value.to_s.empty?, datatype: datatype, validate: true)
230
+ RDF::Literal::Boolean.new(value.value, datatype: datatype, validate: true)
233
231
  end
234
232
  when RDF::XSD.decimal, RDF::XSD.integer
235
233
  case value
236
234
  when RDF::Literal::Boolean
237
235
  RDF::Literal.new(value.object ? 1 : 0, datatype: datatype)
238
- when RDF::Literal::Integer, RDF::Literal::Decimal
239
- RDF::Literal.new(value, datatype: datatype)
236
+ when RDF::Literal::Numeric
237
+ RDF::Literal.new(value.object, datatype: datatype)
240
238
  when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node
241
239
  raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
242
240
  else
243
241
  RDF::Literal.new(value.value, datatype: datatype, validate: true)
244
242
  end
245
243
  when RDF::XSD.string
246
- RDF::Literal.new(value, datatype: datatype)
244
+ # Cast to string rules based on https://www.w3.org/TR/xpath-functions/#casting-to-string
245
+ case value
246
+ when RDF::Literal::Integer
247
+ RDF::Literal.new(value.canonicalize.to_s, datatype: datatype)
248
+ when RDF::Literal::Decimal
249
+ if value == value.ceil
250
+ RDF::Literal.new(value.ceil, datatype: datatype)
251
+ else
252
+ RDF::Literal.new(value.canonicalize.to_s, datatype: datatype)
253
+ end
254
+ when RDF::Literal::Float, RDF::Literal::Double
255
+ if value.abs >= 0.000001 && value.abs < 1000000
256
+ # If SV has an absolute value that is greater than or equal to 0.000001 (one millionth) and less than 1000000 (one million), then the value is converted to an xs:decimal and the resulting xs:decimal is converted to an xs:string according to the rules above, as though using an implementation of xs:decimal that imposes no limits on the totalDigits or fractionDigits facets.
257
+ cast(datatype, RDF::Literal::Decimal.new(value.object))
258
+ elsif value.object.zero?
259
+ # If SV has the value positive or negative zero, TV is "0" or "-0" respectively.
260
+ RDF::Literal.new(value.to_s.start_with?('-') ? '-0' : '0', datatype: datatype)
261
+ else
262
+ # If SV is positive or negative infinity, TV is the string "INF" or "-INF" respectively.
263
+ # In other cases, the result consists of a mantissa, which has the lexical form of an xs:decimal, followed by the letter "E", followed by an exponent which has the lexical form of an xs:integer. Leading zeroes and "+" signs are prohibited in the exponent. For the mantissa, there must be a decimal point, and there must be exactly one digit before the decimal point, which must be non-zero. The "+" sign is prohibited. There must be at least one digit after the decimal point. Apart from this mandatory digit, trailing zero digits are prohibited.
264
+ RDF::Literal.new(value.canonicalize.to_s, datatype: datatype)
265
+ end
266
+ else
267
+ RDF::Literal.new(value.canonicalize.to_s, datatype: datatype)
268
+ end
247
269
  else
248
270
  raise TypeError, "Expected datatype (#{datatype}) to be a recognized XPath function"
249
271
  end
@@ -280,12 +302,26 @@ module SPARQL; module Algebra
280
302
  ##
281
303
  # Returns an optimized version of this expression.
282
304
  #
283
- # This is the default implementation, which simply returns `self`.
305
+ # This is the default implementation, which simply returns a copy of `self`.
284
306
  # Subclasses can override this method in order to implement something
285
307
  # more useful.
286
308
  #
287
- # @return [Expression] `self`
288
- def optimize
309
+ # @param [Hash{Symbol => Object}] options
310
+ # any additional options for optimization
311
+ # @return [Expression] a copy of `self`
312
+ # @see RDF::Query#optimize
313
+ def optimize(**options)
314
+ self.deep_dup.optimize!(**options)
315
+ end
316
+
317
+ ##
318
+ # Optimizes this query.
319
+ #
320
+ # @param [Hash{Symbol => Object}] options
321
+ # any additional options for optimization
322
+ # @return [self]
323
+ # @see RDF::Query#optimize!
324
+ def optimize!(**options)
289
325
  self
290
326
  end
291
327
 
@@ -301,7 +337,7 @@ module SPARQL; module Algebra
301
337
  # @param [Hash{Symbol => Object}] options ({})
302
338
  # options passed from query
303
339
  # @return [Expression] `self`
304
- def evaluate(bindings, options = {})
340
+ def evaluate(bindings, **options)
305
341
  self
306
342
  end
307
343
 
@@ -313,7 +349,7 @@ module SPARQL; module Algebra
313
349
  # more useful.
314
350
  #
315
351
  # @return [Array] `self`
316
- # @see http://openjena.org/wiki/SSE
352
+ # @see https://openjena.org/wiki/SSE
317
353
  def to_sxp_bin
318
354
  self
319
355
  end
@@ -349,7 +385,7 @@ module SPARQL; module Algebra
349
385
  # @param [String] node processing node
350
386
  # @param [String] message
351
387
  # @param [Hash{Symbol => Object}] options
352
- # @option options [Boolean] :debug output debug messages to $stderr
388
+ # @option options [Logger] :logger for logging progress
353
389
  # @option options [Integer] :depth (@productions.length)
354
390
  # Processing depth for indenting message output.
355
391
  # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
@@ -357,7 +393,7 @@ module SPARQL; module Algebra
357
393
  # @overload: May be called with node and an option hash
358
394
  # @param [String] node processing node
359
395
  # @param [Hash{Symbol => Object}] options
360
- # @option options [Boolean] :debug output debug messages to $stderr
396
+ # @option options [Logger] :logger for logging progress
361
397
  # @option options [Integer] :depth (@productions.length)
362
398
  # Processing depth for indenting message output.
363
399
  # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
@@ -365,26 +401,19 @@ module SPARQL; module Algebra
365
401
  # @overload: May be called with only options, in which case the block is used to return the output message
366
402
  # @param [String] node processing node
367
403
  # @param [Hash{Symbol => Object}] options
368
- # @option options [Boolean] :debug output debug messages to $stderr
404
+ # @option options [Logger] :logger for logging progress
369
405
  # @option options [Integer] :depth (@productions.length)
370
406
  # Processing depth for indenting message output.
371
407
  # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
372
- def self.debug(*args)
408
+ def self.debug(*args, &block)
373
409
  options = args.last.is_a?(Hash) ? args.pop : {}
374
- return unless options[:debug]
375
- message = args.join(": ")
376
- message = message + yield if block_given?
377
- depth = options[:depth] || 0
378
- case options[:debug]
379
- when Array
380
- options[:debug] << "#{' ' * depth}#{message}"
381
- else
382
- $stderr.puts("#{' ' * depth}#{message}")
383
- end
410
+ return unless options[:logger]
411
+ options[:logger].debug(*args, **options, &block)
384
412
  end
385
413
 
386
414
  def debug(*args, &block)
387
- Expression.debug(*args, &block)
415
+ options = args.last.is_a?(Hash) ? args.pop : {}
416
+ log_debug(*args, **options, &block)
388
417
  end
389
418
  end # Expression
390
419
  end; end # SPARQL::Algebra
@@ -27,6 +27,22 @@ class Object
27
27
  def to_sse
28
28
  SXP::Generator.string(self.to_sxp_bin)
29
29
  end
30
+
31
+ ##
32
+ # A duplicate of this object.
33
+ #
34
+ # @return [Object] a copy of `self`
35
+ # @see SPARQL::Algebra::Expression#optimize
36
+ def optimize(**options)
37
+ self.deep_dup
38
+ end
39
+
40
+ ##
41
+ # Default for deep_dup is shallow dup
42
+ # @return [Object]
43
+ def deep_dup
44
+ dup
45
+ end
30
46
  end
31
47
 
32
48
  ##
@@ -66,11 +82,34 @@ class Array
66
82
  # @param [Hash{Symbol => Object}] options
67
83
  # @raise [NotImplementedError]
68
84
  # If an attempt is made to perform an unsupported operation
69
- # @see http://www.w3.org/TR/sparql11-query/#sparqlAlgebra
85
+ # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
70
86
  def execute(queryable, **options)
71
87
  raise NotImplementedError, "SPARQL::Algebra '#{first}' operator not implemented"
72
88
  end
73
89
 
90
+ ##
91
+ # Return an optimized version of this array.
92
+ #
93
+ # @return [Array] a copy of `self`
94
+ # @see SPARQL::Algebra::Expression#optimize
95
+ def optimize(**options)
96
+ self.map do |op|
97
+ op.optimize(**options) if op.respond_to?(:optimize)
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Binds the pattern to a solution, making it no longer variable if all variables are resolved to bound variables
103
+ #
104
+ # @param [RDF::Query::Solution] solution
105
+ # @return [self]
106
+ def bind(solution)
107
+ map! do |op|
108
+ op.respond_to?(:bind) ? op.bind(solution) : op
109
+ end
110
+ self
111
+ end
112
+
74
113
  ##
75
114
  # Returns `true` if any of the operands are variables, `false`
76
115
  # otherwise.
@@ -78,7 +117,7 @@ class Array
78
117
  # @return [Boolean] `true` or `false`
79
118
  # @see #constant?
80
119
  def variable?
81
- any?(&:variable?)
120
+ any? {|op| op.respond_to?(:variable?) && op.variable?}
82
121
  end
83
122
  def constant?; !(variable?); end
84
123
 
@@ -166,6 +205,12 @@ class Array
166
205
  each {|e| e.validate! if e.respond_to?(:validate!)}
167
206
  self
168
207
  end
208
+
209
+ ##
210
+ # Deep duplicate
211
+ def deep_dup
212
+ map(&:deep_dup)
213
+ end
169
214
  end
170
215
 
171
216
  ##
@@ -179,6 +224,21 @@ class Hash
179
224
  to_a.to_sxp_bin
180
225
  end
181
226
  def to_sxp; to_sxp_bin; end
227
+
228
+ ##
229
+ # A duplicate of this hash.
230
+ #
231
+ # @return [Hash] a copy of `self`
232
+ # @see SPARQL::Algebra::Expression#optimize
233
+ def optimize(**options)
234
+ self.deep_dup
235
+ end
236
+
237
+ ##
238
+ # Deep duplicate
239
+ def deep_dup
240
+ inject({}) {|memo, (k, v)| memo.merge(k => v.deep_dup)}
241
+ end
182
242
  end
183
243
 
184
244
  ##
@@ -210,8 +270,33 @@ module RDF::Term
210
270
  def vars
211
271
  variable? ? [self] : []
212
272
  end
273
+
274
+ ##
275
+ # A duplicate of this term.
276
+ #
277
+ # @return [RDF::Term] a copy of `self`
278
+ # @see SPARQL::Algebra::Expression#optimize
279
+ def optimize(**options)
280
+ optimized = self.deep_dup
281
+ optimized.lexical = nil if optimized.respond_to?(:lexical=)
282
+ optimized
283
+ end
213
284
  end # RDF::Term
214
285
 
286
+ class RDF::Literal::Double
287
+ ##
288
+ # Returns the SXP representation of this object.
289
+ #
290
+ # @return [String]
291
+ def to_sxp
292
+ case
293
+ when nan? then 'nan.0'
294
+ when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0')
295
+ else canonicalize.to_s.downcase
296
+ end
297
+ end
298
+ end
299
+
215
300
  # Override RDF::Queryable to execute against SPARQL::Algebra::Query elements as well as RDF::Query and RDF::Pattern
216
301
  module RDF::Queryable
217
302
  alias_method :query_without_sparql, :query
@@ -223,7 +308,7 @@ module RDF::Queryable
223
308
  #
224
309
  # @example
225
310
  # queryable.query([nil, RDF::DOAP.developer, nil])
226
- # queryable.query(predicate: RDF::DOAP.developer)
311
+ # queryable.query({predicate: RDF::DOAP.developer})
227
312
  #
228
313
  # op = SPARQL::Algebra::Expression.parse(%q((bgp (triple ?a doap:developer ?b))))
229
314
  # queryable.query(op)
@@ -235,7 +320,7 @@ module RDF::Queryable
235
320
  # @yieldreturn [void] ignored
236
321
  # @return [Enumerator]
237
322
  # @see RDF::Queryable#query_pattern
238
- def query(pattern, options = {}, &block)
323
+ def query(pattern, **options, &block)
239
324
  raise TypeError, "#{self} is not queryable" if respond_to?(:queryable?) && !queryable?
240
325
 
241
326
  if pattern.is_a?(SPARQL::Algebra::Operator) && pattern.respond_to?(:execute)
@@ -258,19 +343,39 @@ module RDF::Queryable
258
343
  query_without_sparql(pattern, **options, &block)
259
344
  end
260
345
  end
261
-
262
346
  end
263
347
 
264
348
  class RDF::Statement
265
349
  # Transform Statement Pattern into an SXP
266
350
  # @return [Array]
267
351
  def to_sxp_bin
268
- if has_graph?
269
- [:quad, subject, predicate, object, graph_name]
270
- else
271
- [:triple, subject, predicate, object]
272
- end
352
+ [ (has_graph? ? :quad : :triple),
353
+ (:inferred if inferred?),
354
+ subject,
355
+ predicate,
356
+ object,
357
+ graph_name
358
+ ].compact.map(&:to_sxp_bin)
359
+ end
360
+
361
+ ##
362
+ # Returns an S-Expression (SXP) representation
363
+ #
364
+ # @return [String]
365
+ def to_sxp
366
+ to_sxp_bin.to_sxp
273
367
  end
368
+
369
+ ##
370
+ # A duplicate of this Statement.
371
+ #
372
+ # @return [RDF::Statement] a copy of `self`
373
+ # @see SPARQL::Algebra::Expression#optimize
374
+ def optimize(**options)
375
+ self.dup
376
+ end
377
+
378
+ def executable?; false; end
274
379
  end
275
380
 
276
381
  class RDF::Query
@@ -306,6 +411,16 @@ class RDF::Query
306
411
  end
307
412
  end
308
413
 
414
+ ##
415
+ # Binds the pattern to a solution, making it no longer variable if all variables are resolved to bound variables
416
+ #
417
+ # @param [RDF::Query::Solution] solution
418
+ # @return [self]
419
+ def bind(solution)
420
+ patterns.each {|p| p.bind(solution)}
421
+ self
422
+ end
423
+
309
424
  # Query results in a boolean result (e.g., ASK)
310
425
  # @return [Boolean]
311
426
  def query_yields_boolean?
@@ -338,25 +453,34 @@ class RDF::Query
338
453
  variables.values
339
454
  end
340
455
 
456
+ alias_method :optimize_without_expression!, :optimize!
341
457
  ##
342
- # Returns `true` if this is executable (i.e., contains a graph patterns), `false`
343
- # otherwise.
458
+ # Optimize the query, removing lexical shortcuts in URIs
344
459
  #
345
- # @return [Boolean] `true` or `false`
460
+ # @return [self]
461
+ # @see SPARQL::Algebra::Expression#optimize!
462
+ def optimize!(**options)
463
+ @patterns = @patterns.map do |pattern|
464
+ components = pattern.to_quad.map do |term|
465
+ if term.respond_to?(:lexical=)
466
+ term.dup.instance_eval {@lexical = nil; self}
467
+ else
468
+ term
469
+ end
470
+ end
471
+ RDF::Query::Pattern.from(components, **pattern.options)
472
+ end
473
+ self.optimize_without_expression!(**options)
474
+ end
475
+
476
+ ##
477
+ # Returns `true` as this is executable.
478
+ #
479
+ # @return [Boolean] `true`
346
480
  def executable?; true; end
347
481
  end
348
482
 
349
483
  class RDF::Query::Pattern
350
- # Transform Query Pattern into an SXP
351
- # @return [Array]
352
- def to_sxp_bin
353
- if has_graph?
354
- [:quad, subject, predicate, object, graph_name]
355
- else
356
- [:triple, subject, predicate, object]
357
- end
358
- end
359
-
360
484
  ##
361
485
  # Return the non-destinguished variables contained within this pattern
362
486
  # @return [Array<RDF::Query::Variable>]
@@ -370,6 +494,12 @@ class RDF::Query::Pattern
370
494
  def vars
371
495
  variables.values
372
496
  end
497
+
498
+ ##
499
+ # Returns `true` as this is executable.
500
+ #
501
+ # @return [Boolean] `true`
502
+ def executable?; true; end
373
503
  end
374
504
 
375
505
  ##
@@ -390,6 +520,22 @@ class RDF::Query::Variable
390
520
  raise TypeError if bindings.respond_to?(:bound?) && !bindings.bound?(self)
391
521
  bindings[name.to_sym]
392
522
  end
523
+
524
+ ##
525
+ # Return self
526
+ #
527
+ # @return [RDF::Query::Variable] a copy of `self`
528
+ # @see SPARQL::Algebra::Expression#optimize
529
+ def optimize(**options)
530
+ self
531
+ end
532
+
533
+ # Display variable as SXP
534
+ # @return [Array]
535
+ def to_sxp
536
+ prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??')
537
+ unbound? ? "#{prefix}#{name}".to_sym.to_sxp : ["#{prefix}#{name}".to_sym, value].to_sxp
538
+ end
393
539
  end # RDF::Query::Variable
394
540
 
395
541
  ##
@@ -419,3 +565,16 @@ class RDF::Query::Solutions
419
565
  end
420
566
  alias_method :filter!, :filter
421
567
  end # RDF::Query::Solutions
568
+
569
+ ##
570
+ # Extensions for `RDF::Query::Solution`.
571
+ class RDF::Query::Solution
572
+ ##
573
+ # Returns the SXP representation of this object, defaults to `self`.
574
+ #
575
+ # @return [String]
576
+ def to_sxp_bin
577
+ to_a.to_sxp_bin
578
+ end
579
+ def to_sxp; to_sxp_bin; end
580
+ end # RDF::Query::Solution