sparql 3.2.1 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -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 +35 -7
  9. data/lib/sparql/algebra/extensions.rb +18 -18
  10. data/lib/sparql/algebra/operator/adjust.rb +69 -0
  11. data/lib/sparql/algebra/operator/bgp.rb +1 -1
  12. data/lib/sparql/algebra/operator/construct.rb +1 -1
  13. data/lib/sparql/algebra/operator/dataset.rb +10 -0
  14. data/lib/sparql/algebra/operator/day.rb +2 -2
  15. data/lib/sparql/algebra/operator/delete.rb +1 -1
  16. data/lib/sparql/algebra/operator/delete_data.rb +1 -1
  17. data/lib/sparql/algebra/operator/delete_where.rb +1 -1
  18. data/lib/sparql/algebra/operator/extend.rb +32 -2
  19. data/lib/sparql/algebra/operator/group.rb +34 -6
  20. data/lib/sparql/algebra/operator/hours.rb +2 -2
  21. data/lib/sparql/algebra/operator/insert.rb +1 -1
  22. data/lib/sparql/algebra/operator/insert_data.rb +1 -1
  23. data/lib/sparql/algebra/operator/join.rb +3 -3
  24. data/lib/sparql/algebra/operator/left_join.rb +3 -3
  25. data/lib/sparql/algebra/operator/minus.rb +1 -1
  26. data/lib/sparql/algebra/operator/minutes.rb +2 -2
  27. data/lib/sparql/algebra/operator/modify.rb +21 -0
  28. data/lib/sparql/algebra/operator/month.rb +2 -2
  29. data/lib/sparql/algebra/operator/path_opt.rb +9 -65
  30. data/lib/sparql/algebra/operator/path_plus.rb +18 -10
  31. data/lib/sparql/algebra/operator/path_range.rb +178 -0
  32. data/lib/sparql/algebra/operator/path_star.rb +7 -4
  33. data/lib/sparql/algebra/operator/path_zero.rb +110 -0
  34. data/lib/sparql/algebra/operator/plus.rb +7 -5
  35. data/lib/sparql/algebra/operator/project.rb +42 -1
  36. data/lib/sparql/algebra/operator/seconds.rb +2 -2
  37. data/lib/sparql/algebra/operator/seq.rb +3 -3
  38. data/lib/sparql/algebra/operator/sequence.rb +10 -0
  39. data/lib/sparql/algebra/operator/subtract.rb +9 -5
  40. data/lib/sparql/algebra/operator/table.rb +11 -2
  41. data/lib/sparql/algebra/operator/timezone.rb +2 -2
  42. data/lib/sparql/algebra/operator/triple.rb +51 -0
  43. data/lib/sparql/algebra/operator/tz.rb +2 -2
  44. data/lib/sparql/algebra/operator/using.rb +2 -2
  45. data/lib/sparql/algebra/operator/year.rb +2 -2
  46. data/lib/sparql/algebra/operator.rb +27 -10
  47. data/lib/sparql/algebra/query.rb +5 -3
  48. data/lib/sparql/algebra.rb +22 -3
  49. data/lib/sparql/grammar/meta.rb +1367 -267
  50. data/lib/sparql/grammar/parser11.rb +826 -328
  51. data/lib/sparql/grammar/terminals11.rb +2 -2
  52. data/lib/sparql/grammar.rb +6 -4
  53. data/lib/sparql/results.rb +3 -2
  54. data/lib/sparql/server.rb +93 -0
  55. data/lib/sparql.rb +8 -5
  56. metadata +39 -17
@@ -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
 
@@ -57,10 +57,38 @@ module SPARQL; module Algebra
57
57
 
58
58
  NAME = [:project]
59
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
+
60
86
  ##
61
87
  # Executes this query on the given `queryable` graph or repository.
62
88
  # Reduces the result set to the variables listed in the first operand
63
89
  #
90
+ # If the first operand is empty, this indicates a `SPARQL *`, and all in-scope variables are projected.
91
+ #
64
92
  # @param [RDF::Queryable] queryable
65
93
  # the graph or repository to query
66
94
  # @param [Hash{Symbol => Object}] options
@@ -74,10 +102,23 @@ module SPARQL; module Algebra
74
102
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
75
103
  def execute(queryable, **options, &block)
76
104
  @solutions = queryable.query(operands.last, depth: options[:depth].to_i + 1, **options)
77
- @solutions = @solutions.project(*(operands.first))
105
+ @solutions.variable_names = self.variables.keys
106
+ @solutions = @solutions.project(*(operands.first)) unless operands.first.empty?
78
107
  @solutions.each(&block) if block_given?
79
108
  @solutions
80
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
81
122
 
82
123
  ##
83
124
  #
@@ -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
@@ -51,6 +51,16 @@ module SPARQL; module Algebra
51
51
  @solutions.each(&block) if block_given?
52
52
  @solutions
53
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
54
64
  end # Sequence
55
65
  end # Operator
56
66
  end; end # SPARQL::Algebra
@@ -31,17 +31,21 @@ module SPARQL; module Algebra
31
31
  ##
32
32
  # Returns the arithmetic difference of the operands.
33
33
  #
34
- # @param [RDF::Literal::Numeric] left
34
+ # @param [RDF::Literal::Numeric, RDF::Literal::Temporal] left
35
35
  # a numeric literal
36
- # @param [RDF::Literal::Numeric] right
36
+ # @param [RDF::Literal::Numeric, RDF::Literal::Temporal, RDF::Literal::Duration] right
37
37
  # a numeric literal
38
- # @return [RDF::Literal::Numeric]
39
- # @raise [TypeError] if either operand is not a numeric literal
38
+ # @return [RDF::Literal::Numeric, RDF::Literal::Temporal, RDF::Literal::Duration]
39
+ # @raise [TypeError] if either operand is neither a numeric nor a temporal literal
40
40
  def apply(left, right, **options)
41
41
  case
42
42
  when left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)
43
43
  left - right
44
- else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}"
44
+ when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Temporal)
45
+ left - right
46
+ when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Duration)
47
+ left - right
48
+ else raise TypeError, "expected two RDF::Literal::Numeric, Temporal operands, or a Temporal and a Duration, but got #{left.inspect} and #{right.inspect}"
45
49
  end
46
50
  end
47
51
 
@@ -89,9 +89,19 @@ module SPARQL; module Algebra
89
89
  end
90
90
  @solutions << RDF::Query::Solution.new(bindings)
91
91
  end
92
+ @solutions.variable_names = self.variables.keys
92
93
  @solutions.each(&block) if block_given?
93
94
  @solutions
94
95
  end
96
+
97
+ ##
98
+ # In-scope variables for a table are the variables operand
99
+ #
100
+ # @return [Hash{Symbol => RDF::Query::Variable}]
101
+ def variables
102
+ in_scope = operands.first.is_a?(Array) ? operands.first[1..-1] : []
103
+ in_scope.inject({}) {|memo, v| memo.merge(v.variables)}
104
+ end
95
105
 
96
106
  ##
97
107
  #
@@ -105,8 +115,7 @@ module SPARQL; module Algebra
105
115
  operands[1..-1].each do |row|
106
116
  line = '('
107
117
  row[1..-1].each do |col|
108
- v = col[1].to_sparql(as_statement: true, **options)
109
- v = "<< #{v} >>" if col[1].is_a?(RDF::Statement)
118
+ v = col[1].to_sparql(**options)
110
119
  line << v + ' '
111
120
  end
112
121
  line = line.chomp(' ')
@@ -33,10 +33,10 @@ module SPARQL; module Algebra
33
33
  #
34
34
  # @param [RDF::Literal] operand
35
35
  # the operand
36
- # @return [RDF::Literal]
36
+ # @return [RDF::Literal::Temporal]
37
37
  # @raise [TypeError] if the operand is not a simple literal
38
38
  def apply(operand, **options)
39
- raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
39
+ raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
40
40
  raise TypeError, "literal has no timezone" unless res = operand.timezone
41
41
  res
42
42
  end
@@ -0,0 +1,51 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `triple` operator.
5
+ #
6
+ # If the 3-tuple (term1, term2, term3) is an RDF-star triple, the function returns this triple. If the 3-tuple is not an RDF-star triple, then the function raises an error.
7
+ #
8
+ # [121] BuiltInCall ::= ... | 'TRIPLE' '(' Expression ',' Expression ',' Expression ')'
9
+ #
10
+ # @example SPARQL Grammar
11
+ # PREFIX : <http://example.com/ns#>
12
+ # SELECT * {
13
+ # ?s ?p ?o .
14
+ # BIND(TRIPLE(?s, ?p, ?o) AS ?t1)
15
+ # }
16
+ #
17
+ # @example SSE
18
+ # (prefix ((: <http://example.com/ns#>))
19
+ # (extend ((?t1 (triple ?s ?p ?o)))
20
+ # (bgp (triple ?s ?p ?o))))
21
+ #
22
+ # @note This operator overlaps with RDF::Query::Pattern as used as an operand to a BGP.
23
+ # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#triple
24
+ class Triple < Operator::Ternary
25
+ include Evaluatable
26
+
27
+ NAME = :triple
28
+
29
+ ##
30
+ # @param [RDF::Term] subject
31
+ # @param [RDF::Term] predicate
32
+ # @param [RDF::Term] object
33
+ # @return [RDF::URI]
34
+ # @raise [TypeError] if the operand is not a simple literal
35
+ def apply(subject, predicate, object, **options)
36
+ triple = RDF::Statement(subject, predicate, object)
37
+ raise TypeError, "valid components, but got #{triple.inspect}" unless triple.valid?
38
+ triple
39
+ end
40
+
41
+ ##
42
+ #
43
+ # Returns a partial SPARQL grammar for this operator.
44
+ #
45
+ # @return [String]
46
+ def to_sparql(**options)
47
+ "TRIPLE(#{operands.to_sparql(delimiter: ', ', **options)})"
48
+ end
49
+ end # Triple
50
+ end # Operator
51
+ end; end # SPARQL::Algebra
@@ -29,12 +29,12 @@ module SPARQL; module Algebra
29
29
  ##
30
30
  # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
31
31
  #
32
- # @param [RDF::Literal] operand
32
+ # @param [RDF::Literal::Temporal] operand
33
33
  # the operand
34
34
  # @return [RDF::Literal]
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
  operand.tz
39
39
  end
40
40
  ##
@@ -39,8 +39,8 @@ module SPARQL; module Algebra
39
39
  # @example SSE (multiple clauses)
40
40
  # (prefix ((: <http://example.org/>))
41
41
  # (update
42
- # (modify (using (:g1 :g2)
43
- # (bgp (triple ?s ?p ?o)))
42
+ # (modify
43
+ # (using (:g1 :g2) (bgp (triple ?s ?p ?o)))
44
44
  # (insert ((triple ?s ?p "q"))))))
45
45
  #
46
46
  # @see https://www.w3.org/TR/sparql11-update/#add
@@ -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.year)
39
39
  end
40
40