sparql 3.1.0 → 3.1.6

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.
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