sparql 3.2.0 → 3.2.4

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -38
  3. data/VERSION +1 -1
  4. data/bin/sparql +2 -31
  5. data/lib/rack/sparql/conneg.rb +22 -1
  6. data/lib/sinatra/sparql/extensions.rb +1 -1
  7. data/lib/sinatra/sparql.rb +57 -12
  8. data/lib/sparql/algebra/expression.rb +63 -10
  9. data/lib/sparql/algebra/extensions.rb +39 -35
  10. data/lib/sparql/algebra/operator/abs.rb +1 -1
  11. data/lib/sparql/algebra/operator/adjust.rb +69 -0
  12. data/lib/sparql/algebra/operator/alt.rb +1 -1
  13. data/lib/sparql/algebra/operator/avg.rb +3 -1
  14. data/lib/sparql/algebra/operator/bgp.rb +9 -1
  15. data/lib/sparql/algebra/operator/clear.rb +13 -3
  16. data/lib/sparql/algebra/operator/construct.rb +1 -1
  17. data/lib/sparql/algebra/operator/count.rb +36 -6
  18. data/lib/sparql/algebra/operator/create.rb +5 -4
  19. data/lib/sparql/algebra/operator/dataset.rb +29 -11
  20. data/lib/sparql/algebra/operator/day.rb +2 -2
  21. data/lib/sparql/algebra/operator/delete.rb +3 -1
  22. data/lib/sparql/algebra/operator/delete_data.rb +1 -1
  23. data/lib/sparql/algebra/operator/delete_where.rb +1 -1
  24. data/lib/sparql/algebra/operator/distinct.rb +2 -2
  25. data/lib/sparql/algebra/operator/divide.rb +1 -1
  26. data/lib/sparql/algebra/operator/drop.rb +15 -6
  27. data/lib/sparql/algebra/operator/encode_for_uri.rb +2 -4
  28. data/lib/sparql/algebra/operator/exprlist.rb +3 -1
  29. data/lib/sparql/algebra/operator/extend.rb +73 -5
  30. data/lib/sparql/algebra/operator/filter.rb +6 -1
  31. data/lib/sparql/algebra/operator/function_call.rb +64 -0
  32. data/lib/sparql/algebra/operator/graph.rb +57 -7
  33. data/lib/sparql/algebra/operator/group.rb +105 -6
  34. data/lib/sparql/algebra/operator/group_concat.rb +25 -1
  35. data/lib/sparql/algebra/operator/hours.rb +2 -2
  36. data/lib/sparql/algebra/operator/if.rb +10 -10
  37. data/lib/sparql/algebra/operator/insert.rb +3 -1
  38. data/lib/sparql/algebra/operator/insert_data.rb +1 -1
  39. data/lib/sparql/algebra/operator/is_blank.rb +1 -2
  40. data/lib/sparql/algebra/operator/is_iri.rb +1 -2
  41. data/lib/sparql/algebra/operator/is_literal.rb +1 -2
  42. data/lib/sparql/algebra/operator/is_numeric.rb +1 -2
  43. data/lib/sparql/algebra/operator/join.rb +39 -5
  44. data/lib/sparql/algebra/operator/lcase.rb +2 -3
  45. data/lib/sparql/algebra/operator/left_join.rb +27 -9
  46. data/lib/sparql/algebra/operator/max.rb +3 -1
  47. data/lib/sparql/algebra/operator/min.rb +4 -2
  48. data/lib/sparql/algebra/operator/minus.rb +46 -6
  49. data/lib/sparql/algebra/operator/minutes.rb +2 -2
  50. data/lib/sparql/algebra/operator/modify.rb +21 -0
  51. data/lib/sparql/algebra/operator/month.rb +2 -2
  52. data/lib/sparql/algebra/operator/multiply.rb +1 -1
  53. data/lib/sparql/algebra/operator/notoneof.rb +12 -3
  54. data/lib/sparql/algebra/operator/order.rb +44 -0
  55. data/lib/sparql/algebra/operator/path_opt.rb +9 -65
  56. data/lib/sparql/algebra/operator/path_plus.rb +18 -10
  57. data/lib/sparql/algebra/operator/path_range.rb +178 -0
  58. data/lib/sparql/algebra/operator/path_star.rb +7 -4
  59. data/lib/sparql/algebra/operator/path_zero.rb +110 -0
  60. data/lib/sparql/algebra/operator/plus.rb +8 -6
  61. data/lib/sparql/algebra/operator/project.rb +64 -5
  62. data/lib/sparql/algebra/operator/reduced.rb +3 -3
  63. data/lib/sparql/algebra/operator/regex.rb +1 -1
  64. data/lib/sparql/algebra/operator/reverse.rb +12 -1
  65. data/lib/sparql/algebra/operator/sample.rb +3 -1
  66. data/lib/sparql/algebra/operator/seconds.rb +2 -2
  67. data/lib/sparql/algebra/operator/seq.rb +4 -4
  68. data/lib/sparql/algebra/operator/sequence.rb +14 -1
  69. data/lib/sparql/algebra/operator/service.rb +86 -0
  70. data/lib/sparql/algebra/operator/strlang.rb +1 -2
  71. data/lib/sparql/algebra/operator/subtract.rb +10 -6
  72. data/lib/sparql/algebra/operator/sum.rb +9 -7
  73. data/lib/sparql/algebra/operator/table.rb +50 -7
  74. data/lib/sparql/algebra/operator/timezone.rb +2 -2
  75. data/lib/sparql/algebra/operator/triple.rb +51 -0
  76. data/lib/sparql/algebra/operator/tz.rb +2 -2
  77. data/lib/sparql/algebra/operator/ucase.rb +1 -1
  78. data/lib/sparql/algebra/operator/update.rb +22 -1
  79. data/lib/sparql/algebra/operator/using.rb +18 -1
  80. data/lib/sparql/algebra/operator/with.rb +1 -1
  81. data/lib/sparql/algebra/operator/year.rb +2 -2
  82. data/lib/sparql/algebra/operator.rb +69 -22
  83. data/lib/sparql/algebra/query.rb +5 -3
  84. data/lib/sparql/algebra.rb +42 -6
  85. data/lib/sparql/grammar/meta.rb +1367 -267
  86. data/lib/sparql/grammar/parser11.rb +842 -331
  87. data/lib/sparql/grammar/terminals11.rb +2 -2
  88. data/lib/sparql/grammar.rb +6 -4
  89. data/lib/sparql/results.rb +3 -2
  90. data/lib/sparql/server.rb +93 -0
  91. data/lib/sparql.rb +8 -5
  92. metadata +49 -13
@@ -0,0 +1,178 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL Property Path `pathRange` (NonCountingPath) operator.
5
+ #
6
+ # Property path ranges allow specific path lenghts to be queried. The minimum range describes the minimum path length that will yield solutions, and the maximum range the maximum path that will be returned. A minumum of zero is similar to the `path?` operator. The maximum range of `*` is similar to the `path*` or `path+` operators.
7
+ #
8
+ # For example, the two queries are functionally equivalent:
9
+ #
10
+ # SELECT * WHERE {:a :p{1,2} :b}
11
+ #
12
+ # SELECT * WHERE {:a (:p/:p?) :b}
13
+ #
14
+ # [91] PathElt ::= PathPrimary PathMod?
15
+ # [93] PathMod ::= '*' | '?' | '+' | '{' INTEGER? (',' INTEGER?)? '}'
16
+ #
17
+ # @example SPARQL Grammar range with fixed length
18
+ # PREFIX : <http://example/>
19
+ # SELECT * WHERE {
20
+ # :a :p{2} ?z
21
+ # }
22
+ #
23
+ # @example SSE range with fixed length only
24
+ # (prefix ((: <http://example/>))
25
+ # (path :a (pathRange 2 2 :p) ?z))
26
+ #
27
+ # @example SPARQL Grammar range with min only
28
+ # PREFIX : <http://example/>
29
+ # SELECT * WHERE {
30
+ # :a :p{1,} ?z
31
+ # }
32
+ #
33
+ # @example SSE range with min only
34
+ # (prefix ((: <http://example/>))
35
+ # (path :a (pathRange 1 * :p) ?z))
36
+ #
37
+ # @example SPARQL Grammar range with max only
38
+ # PREFIX : <http://example/>
39
+ # SELECT * WHERE {
40
+ # :a :p{,2} ?z
41
+ # }
42
+ #
43
+ # @example SSE range with max only
44
+ # (prefix ((: <http://example/>))
45
+ # (path :a (pathRange 0 2 :p) ?z))
46
+ #
47
+ # @example SPARQL Grammar range with min and max
48
+ # PREFIX : <http://example/>
49
+ # SELECT * WHERE {
50
+ # :a :p{1,2} ?z
51
+ # }
52
+ #
53
+ # @example SSE range with min and max
54
+ # (prefix ((: <http://example/>))
55
+ # (path :a (pathRange 1 2 :p) ?z))
56
+ #
57
+ class PathRange < Operator::Ternary
58
+ include Query
59
+
60
+ NAME = :"pathRange"
61
+
62
+ ##
63
+ # Initializes a new operator instance.
64
+ #
65
+ # @param [RDF::Literal::Integer] max
66
+ # the range minimum
67
+ # @param [RDF::Literal::Integer, Symbol] min
68
+ # the range maximum (may be `*`)
69
+ # @param [SPARQL::Operator] path
70
+ # the query
71
+ # @param [Hash{Symbol => Object}] options
72
+ # any additional options (see {Operator#initialize})
73
+ # @raise [TypeError] if any operand is invalid
74
+ # @raise [ArgumentError] range element is invalid
75
+ def initialize(min, max, path, **options)
76
+ raise ArgumentError, "expect min <= max {#{min},#{max}}" if
77
+ max.is_a?(RDF::Literal::Integer) && max < min
78
+ super
79
+ end
80
+
81
+ ##
82
+ # Path with lower and upper bounds on lenghts:
83
+ #
84
+ # (path :a (pathRange 1 2 :p) :b)
85
+ # => (path)
86
+ #
87
+ # @param [RDF::Queryable] queryable
88
+ # the graph or repository to query
89
+ # @param [RDF::Query::Solutions] accumulator (RDF::Query::Solutions.new)
90
+ # For previous solutions to avoid duplicates.
91
+ # @param [Hash{Symbol => Object}] options
92
+ # any additional keyword options
93
+ # @option options [RDF::Term, RDF::Variable] :subject
94
+ # @option options [RDF::Term, RDF::Variable] :object
95
+ # @yield [solution]
96
+ # each matching solution
97
+ # @yieldparam [RDF::Query::Solution] solution
98
+ # @yieldreturn [void] ignored
99
+ # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
100
+ def execute(queryable,
101
+ accumulator: RDF::Query::Solutions.new,
102
+ index: RDF::Literal(0),
103
+ **options,
104
+ &block)
105
+
106
+ min, max, op = *operands
107
+ subject, object = options[:subject], options[:object]
108
+ debug(options) {"Path{#{min},#{max}} #{[subject, op, object].to_sse}"}
109
+
110
+ path_mm = if min.zero? && max.is_a?(RDF::Literal::Integer) && max.zero?
111
+ PathZero.new(op, **options)
112
+ else
113
+ # Build up a sequence
114
+ path_opt = nil
115
+ seq_len = min.to_i
116
+ if max.is_a?(RDF::Literal::Integer)
117
+ # min to max is a sequence of optional sequences
118
+ opt_len = (max - min).to_i
119
+ while opt_len > 0 do
120
+ path_opt = PathOpt.new(path_opt ? Seq.new(op, path_opt, **options) : op, **options)
121
+ opt_len -= 1
122
+ end
123
+ elsif seq_len > 0
124
+ path_opt = PathPlus.new(op, **options)
125
+ seq_len -= 1
126
+ else
127
+ path_opt = PathStar.new(op, **options)
128
+ end
129
+
130
+ # sequence ending in op, op+, op*, or path_opt
131
+ path_seq = nil
132
+ while seq_len > 0 do
133
+ path_seq = if path_opt
134
+ opt, path_opt = path_opt, nil
135
+ Seq.new(op, opt, **options)
136
+ elsif path_seq
137
+ Seq.new(op, path_seq, **options)
138
+ else
139
+ op
140
+ end
141
+ seq_len -= 1
142
+ end
143
+ path_seq || path_opt || op
144
+ end
145
+ debug(options) {"=> #{path_mm.to_sse}"}
146
+
147
+ # After this, path_mm may just be the original op, which can be a term, not an operator.
148
+ if path_mm.is_a?(RDF::Term)
149
+ path_mm = RDF::Query.new do |q|
150
+ q.pattern [subject, path_mm, object]
151
+ end
152
+ end
153
+
154
+ solutions = path_mm.execute(queryable, **options.merge(depth: options[:depth].to_i + 1)).uniq
155
+ debug(options) {"(path{#{min},#{max}})=> #{solutions.to_sxp}"}
156
+ solutions.each(&block) if block_given?
157
+ solutions
158
+ end
159
+
160
+ ##
161
+ #
162
+ # Returns a partial SPARQL grammar for this operator.
163
+ #
164
+ # @return [String]
165
+ def to_sparql(**options)
166
+ min, max, path = operands
167
+ "(#{path.to_sparql(**options)})" +
168
+ if max == :*
169
+ "{#{min},}"
170
+ elsif min == max
171
+ "{#{min}}"
172
+ else
173
+ "{#{min},#{max}}"
174
+ end
175
+ end
176
+ end # PathStar
177
+ end # Operator
178
+ end; end # SPARQL::Algebra
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL Property Path `path*` (ZeroOrMorePath) operator.
5
5
  #
6
- # [91] PathElt ::= PathPrimary PathMod?
7
- # [93] PathMod ::= '*' | '?' | '+'
6
+ # [91] PathElt ::= PathPrimary PathMod?
7
+ # [93] PathMod ::= '*' | '?' | '+' | '{' INTEGER? (',' INTEGER?)? '}'
8
8
 
9
9
  # @example SPARQL Grammar
10
10
  # PREFIX : <http://example/>
@@ -23,10 +23,13 @@ module SPARQL; module Algebra
23
23
  NAME = :"path*"
24
24
 
25
25
  ##
26
- # Path including zero length:
26
+ # Path including at zero length:
27
27
  #
28
28
  # (path :a (path* :p) :b)
29
- # => (path :a (path? (path+ :p)) :b)
29
+ #
30
+ # into
31
+ #
32
+ # (path :a (path? (path+ :p)) :b)
30
33
  #
31
34
  # @param [RDF::Queryable] queryable
32
35
  # the graph or repository to query
@@ -0,0 +1,110 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL Property Path `path0` (ZeroLengthPath) operator.
5
+ #
6
+ # A zero length path matches all subjects and objects in the graph, and also any RDF terms explicitly given as endpoints of the path pattern.
7
+ #
8
+ # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrOnePath
9
+ class PathZero < Operator::Unary
10
+ include Query
11
+
12
+ NAME = :path0
13
+
14
+ ##
15
+ # Zero length path:
16
+ #
17
+ # (path x (path0 :p) y)
18
+ # => (filter (x = y) (solution x y))
19
+ #
20
+ # @param [RDF::Queryable] queryable
21
+ # the graph or repository to query
22
+ # @param [Hash{Symbol => Object}] options
23
+ # any additional keyword options
24
+ # @option options [RDF::Term, RDF::Variable] :subject
25
+ # @option options [RDF::Term, RDF::Variable] :object
26
+ # @yield [solution]
27
+ # each matching solution
28
+ # @yieldparam [RDF::Query::Solution] solution
29
+ # @yieldreturn [void] ignored
30
+ # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
31
+ def execute(queryable, **options, &block)
32
+ subject, object = options[:subject], options[:object]
33
+ debug(options) {"PathZero #{[subject, operands, object].to_sse}"}
34
+
35
+ solutions = RDF::Query::Solutions.new
36
+ # The zero-length case implies subject == object.
37
+ case
38
+ when subject.variable? && object.variable?
39
+ # Nodes is the set of all subjects and objects in queryable
40
+ query = RDF::Query.new {|q| q.pattern({subject: subject})}
41
+ query.execute(queryable, **options) do |solution|
42
+ solution.merge!(object.to_sym => solution[subject])
43
+ unless solutions.include?(solution)
44
+ #debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
45
+ solutions << solution
46
+ end
47
+ end if query.valid?
48
+
49
+ # All objects which are `object`
50
+ query = RDF::Query.new {|q| q.pattern({object: object})}
51
+ query.execute(queryable, **options) do |solution|
52
+ solution.merge!(subject.to_sym => solution[object])
53
+ unless solutions.include?(solution)
54
+ #debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
55
+ solutions << solution
56
+ end
57
+ end if query.valid?
58
+ when subject.variable?
59
+ # All subjects which are `object`
60
+ query = RDF::Query.new {|q| q.pattern({subject: object})}
61
+ query.execute(queryable, **options) do |solution|
62
+ solution.merge!(subject.to_sym => object)
63
+ unless solutions.include?(solution)
64
+ #debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
65
+ solutions << solution
66
+ end
67
+ end if query.valid?
68
+
69
+ # All objects which are `object`
70
+ query = RDF::Query.new {|q| q.pattern({object: object})}
71
+ query.execute(queryable, **options) do |solution|
72
+ solution.merge!(subject.to_sym => object)
73
+ unless solutions.include?(solution)
74
+ #debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
75
+ solutions << solution
76
+ end
77
+ end if query.valid?
78
+ when object.variable?
79
+ # All subjects which are `subject`
80
+ query = RDF::Query.new {|q| q.pattern({subject: subject})}
81
+ query.execute(queryable, **options) do |solution|
82
+ solution.merge!(object.to_sym => subject)
83
+ unless solutions.include?(solution)
84
+ #debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
85
+ solutions << solution
86
+ end
87
+ end if query.valid?
88
+
89
+ # All objects which are `subject`
90
+ query = RDF::Query.new {|q| q.pattern({object: subject})}
91
+ query.execute(queryable, **options) do |solution|
92
+ solution.merge!(object.to_sym => subject)
93
+ unless solutions.include?(solution)
94
+ #debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
95
+ solutions << solution
96
+ end
97
+ end if query.valid?
98
+ else
99
+ # Otherwise, if subject == object, an empty solution
100
+ solutions << RDF::Query::Solution.new if subject == object
101
+ end
102
+
103
+ solutions.uniq!
104
+ debug(options) {"(path0)=> #{solutions.to_sxp}"}
105
+ solutions.each(&block) if block_given?
106
+ solutions
107
+ end
108
+ end # PathZero
109
+ end # Operator
110
+ end; end # SPARQL::Algebra
@@ -47,19 +47,21 @@ module SPARQL; module Algebra
47
47
  ##
48
48
  # Returns the arithmetic sum of the operands, unless there is no `right`.
49
49
  #
50
- # @param [RDF::Literal::Numeric] left
50
+ # @param [RDF::Literal::Numeric, RDF::Literal::Temporal] left
51
51
  # a numeric literal
52
- # @param [RDF::Literal::Numeric] right
52
+ # @param [RDF::Literal::Numeric, RDF::Literal::Duration] right
53
53
  # a numeric literal
54
- # @return [RDF::Literal::Numeric]
55
- # @raise [TypeError] if either operand is not a numeric literal
54
+ # @return [RDF::Literal::Numeric, RDF::Literal::Temporal]
55
+ # @raise [TypeError] if either operand is neither a numeric nor a temporal literal
56
56
  def apply(left, right = nil, **options)
57
57
  case
58
58
  when left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)
59
59
  left + right
60
60
  when left.is_a?(RDF::Literal::Numeric) && right.nil?
61
61
  left
62
- else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}"
62
+ when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Duration)
63
+ left + right
64
+ else raise TypeError, "expected two RDF::Literal::Numeric operands or a Temporal and a Duration, but got #{left.inspect} and #{right.inspect}"
63
65
  end
64
66
  end
65
67
 
@@ -69,7 +71,7 @@ module SPARQL; module Algebra
69
71
  #
70
72
  # @return [String]
71
73
  def to_sparql(**options)
72
- "#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)}"
74
+ "(#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)})"
73
75
  end
74
76
  end # Plus
75
77
  end # Operator
@@ -20,30 +20,75 @@ module SPARQL; module Algebra
20
20
  # (filter (= ?v 2)
21
21
  # (bgp (triple ?s :p ?v)))))
22
22
  #
23
- # ## Sub select
24
- #
25
- # @example SPARQL Grammar
23
+ # @example SPARQL Grammar (Sub select)
26
24
  # SELECT (1 AS ?X ) {
27
25
  # SELECT (2 AS ?Y ) {}
28
26
  # }
29
27
  #
30
- # @example SSE
28
+ # @example SSE (Sub select)
31
29
  # (project (?X)
32
30
  # (extend ((?X 1))
33
31
  # (project (?Y)
34
32
  # (extend ((?Y 2))
35
33
  # (bgp)))))
36
34
  #
35
+ # @example SPARQL Grammar (filter projection)
36
+ # PREFIX : <http://www.example.org/>
37
+ # ASK {
38
+ # {SELECT (GROUP_CONCAT(?o) AS ?g) WHERE {
39
+ # :a :p1 ?o
40
+ # }}
41
+ # FILTER(?g = "1 22" || ?g = "22 1")
42
+ # }
43
+ #
44
+ # @example SSE (filter projection)
45
+ # (prefix ((: <http://www.example.org/>))
46
+ # (ask
47
+ # (filter
48
+ # (|| (= ?g "1 22") (= ?g "22 1"))
49
+ # (project (?g)
50
+ # (extend ((?g ??.0))
51
+ # (group () ((??.0 (group_concat ?o)))
52
+ # (bgp (triple :a :p1 ?o)))))) ))
53
+ #
37
54
  # @see https://www.w3.org/TR/sparql11-query/#modProjection
38
55
  class Project < Operator::Binary
39
56
  include Query
40
57
 
41
58
  NAME = [:project]
42
59
 
60
+ ##
61
+ # Can only project in-scope variables.
62
+ #
63
+ # @return (see Algebra::Operator#initialize)
64
+ def validate!
65
+ if (group = descendants.detect {|o| o.is_a?(Group)})
66
+ raise ArgumentError, "project * on group is illegal" if operands.first.empty?
67
+ query_vars = operands.last.variables
68
+ variables.keys.each do |v|
69
+ raise ArgumentError,
70
+ "projecting #{v.to_sse} not projected from group" unless
71
+ query_vars.key?(v.to_sym)
72
+ end
73
+ end
74
+
75
+ super
76
+ end
77
+
78
+ ##
79
+ # The projected variables.
80
+ #
81
+ # @return [Hash{Symbol => RDF::Query::Variable}]
82
+ def variables
83
+ operands(1).inject({}) {|hash, o| hash.merge(o.variables)}
84
+ end
85
+
43
86
  ##
44
87
  # Executes this query on the given `queryable` graph or repository.
45
88
  # Reduces the result set to the variables listed in the first operand
46
89
  #
90
+ # If the first operand is empty, this indicates a `SPARQL *`, and all in-scope variables are projected.
91
+ #
47
92
  # @param [RDF::Queryable] queryable
48
93
  # the graph or repository to query
49
94
  # @param [Hash{Symbol => Object}] options
@@ -57,10 +102,23 @@ module SPARQL; module Algebra
57
102
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
58
103
  def execute(queryable, **options, &block)
59
104
  @solutions = queryable.query(operands.last, depth: options[:depth].to_i + 1, **options)
60
- @solutions = @solutions.project(*(operands.first))
105
+ @solutions.variable_names = self.variables.keys
106
+ @solutions = @solutions.project(*(operands.first)) unless operands.first.empty?
61
107
  @solutions.each(&block) if block_given?
62
108
  @solutions
63
109
  end
110
+
111
+ ##
112
+ # In-scope variables for a select are limited to those projected.
113
+ #
114
+ # @return [Hash{Symbol => RDF::Query::Variable}]
115
+ def variables
116
+ in_scope = operands.first.empty? ?
117
+ operands.last.variables.values :
118
+ operands.first
119
+
120
+ in_scope.inject({}) {|memo, v| memo.merge(v.variables)}
121
+ end
64
122
 
65
123
  ##
66
124
  #
@@ -77,6 +135,7 @@ module SPARQL; module Algebra
77
135
  # Any of these options indicates we're in a sub-select
78
136
  opts = options.dup.delete_if {|k,v| %I{extensions filter_ops project}.include?(k)}
79
137
  content = operands.last.to_sparql(project: vars, **opts)
138
+ content = "{#{content}}" unless content.start_with?('{') && content.end_with?('}')
80
139
  Operator.to_sparql(content, **options)
81
140
  else
82
141
  operands.last.to_sparql(project: vars, **options)
@@ -8,12 +8,12 @@ module SPARQL; module Algebra
8
8
  # @example SPARQL Grammar
9
9
  # PREFIX : <http://example.org/>
10
10
  # PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
11
- # SELECT DISTINCT ?v
11
+ # SELECT REDUCED ?v
12
12
  # WHERE { ?x ?p ?v }
13
13
  #
14
14
  # @example SSE
15
- # (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
16
- # (: <http://example/>))
15
+ # (prefix ((: <http://example.org/>)
16
+ # (xsd: <http://www.w3.org/2001/XMLSchema#>))
17
17
  # (reduced
18
18
  # (project (?v)
19
19
  # (bgp (triple ?x ?p ?v)))))
@@ -6,8 +6,8 @@ module SPARQL; module Algebra
6
6
  # [122] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
7
7
  #
8
8
  # @example SPARQL Grammar
9
- # PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
10
9
  # PREFIX ex: <http://example.com/#>
10
+ # PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
11
11
  # SELECT ?val
12
12
  # WHERE {
13
13
  # ex:foo rdf:value ?val .
@@ -15,6 +15,17 @@ module SPARQL; module Algebra
15
15
  # (in: <http://www.example.org/instance#>))
16
16
  # (ask (path in:b (reverse ex:p) in:a)))
17
17
  #
18
+ # @example SPARQL Grammar
19
+ # prefix ex: <http://www.example.org/schema#>
20
+ # prefix in: <http://www.example.org/instance#>
21
+ #
22
+ # select * where { in:c ^(ex:p1/ex:p2) ?x }
23
+ #
24
+ # @example SSE
25
+ # (prefix ((ex: <http://www.example.org/schema#>)
26
+ # (in: <http://www.example.org/instance#>))
27
+ # (path in:c (reverse (seq ex:p1 ex:p2)) ?x))
28
+ #
18
29
  # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_inverse
19
30
  class Reverse < Operator::Unary
20
31
  include Query
@@ -65,7 +76,7 @@ module SPARQL; module Algebra
65
76
  #
66
77
  # @return [String]
67
78
  def to_sparql(**options)
68
- "^" + operands.first.to_sparql(**options)
79
+ "^(" + operands.first.to_sparql(**options) + ')'
69
80
  end
70
81
  end # Reverse
71
82
  end # Operator
@@ -54,7 +54,9 @@ module SPARQL; module Algebra
54
54
  #
55
55
  # @return [String]
56
56
  def to_sparql(**options)
57
- "SAMPLE(#{operands.to_sparql(**options)})"
57
+ distinct = operands.first == :distinct
58
+ args = distinct ? operands[1..-1] : operands
59
+ "SAMPLE(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
58
60
  end
59
61
  end # Sample
60
62
  end # Operator
@@ -31,10 +31,10 @@ module SPARQL; module Algebra
31
31
  #
32
32
  # @param [RDF::Literal] operand
33
33
  # the operand
34
- # @return [RDF::Literal]
34
+ # @return [RDF::Literal::Temporal]
35
35
  # @raise [TypeError] if the operand is not a simple literal
36
36
  def apply(operand, **options)
37
- raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
37
+ raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
38
38
  RDF::Literal(operand.object.second)
39
39
  end
40
40
 
@@ -59,10 +59,10 @@ module SPARQL; module Algebra
59
59
  end
60
60
 
61
61
  left = queryable.query(q1, **options.merge(object: v, depth: options[:depth].to_i + 1))
62
- debug(options) {"(seq)=>(left) #{left.map(&:to_h).to_sse}"}
62
+ #debug(options) {"(seq)=>(left) #{left.map(&:to_h).to_sse}"}
63
63
 
64
64
  right = queryable.query(q2, **options.merge(subject: v, depth: options[:depth].to_i + 1))
65
- debug(options) {"(seq)=>(right) #{right.map(&:to_h).to_sse}"}
65
+ #debug(options) {"(seq)=>(right) #{right.map(&:to_h).to_sse}"}
66
66
 
67
67
  @solutions = RDF::Query::Solutions(left.map do |s1|
68
68
  right.map do |s2|
@@ -72,7 +72,7 @@ module SPARQL; module Algebra
72
72
  solution.bindings.delete(v.to_sym)
73
73
  solution
74
74
  end
75
- debug(options) {"(seq)=> #{@solutions.map(&:to_h).to_sse}"}
75
+ debug(options) {"(seq)=> #{@solutions.to_sxp}"}
76
76
  @solutions.each(&block) if block_given?
77
77
  @solutions
78
78
  end
@@ -83,7 +83,7 @@ module SPARQL; module Algebra
83
83
  #
84
84
  # @return [String]
85
85
  def to_sparql(**options)
86
- operands.to_sparql(delimiter: '/', **options)
86
+ '(' + operands.to_sparql(delimiter: '/', **options) + ')'
87
87
  end
88
88
  end # Seq
89
89
  end # Operator
@@ -4,8 +4,11 @@ module SPARQL; module Algebra
4
4
  ##
5
5
  # The SPARQL UPDATE `sequence` operator.
6
6
  #
7
- # Sequences through each operand
7
+ # Sequences through each operand.
8
8
  #
9
+ # [103] CollectionPath ::= '(' GraphNodePath+ ')'
10
+ #
11
+ # @see https://www.w3.org/TR/sparql11-query/#collections
9
12
  class Sequence < Operator
10
13
  include SPARQL::Algebra::Update
11
14
 
@@ -48,6 +51,16 @@ module SPARQL; module Algebra
48
51
  @solutions.each(&block) if block_given?
49
52
  @solutions
50
53
  end
54
+
55
+ ##
56
+ #
57
+ # Returns a partial SPARQL grammar for this operator.
58
+ #
59
+ # @return [String]
60
+ def to_sparql(**options)
61
+ str = "{\n" + operands.to_sparql(top_level: false, **options) + "\n}"
62
+ Operator.to_sparql(str, **options)
63
+ end
51
64
  end # Sequence
52
65
  end # Operator
53
66
  end; end # SPARQL::Algebra