sparql 3.1.8 → 3.2.3

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -60
  3. data/VERSION +1 -1
  4. data/bin/sparql +15 -35
  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 +110 -46
  10. data/lib/sparql/algebra/operator/abs.rb +22 -2
  11. data/lib/sparql/algebra/operator/add.rb +21 -2
  12. data/lib/sparql/algebra/operator/adjust.rb +69 -0
  13. data/lib/sparql/algebra/operator/alt.rb +26 -2
  14. data/lib/sparql/algebra/operator/and.rb +25 -3
  15. data/lib/sparql/algebra/operator/asc.rb +20 -1
  16. data/lib/sparql/algebra/operator/ask.rb +17 -1
  17. data/lib/sparql/algebra/operator/avg.rb +19 -1
  18. data/lib/sparql/algebra/operator/base.rb +18 -1
  19. data/lib/sparql/algebra/operator/bgp.rb +13 -1
  20. data/lib/sparql/algebra/operator/bnode.rb +33 -10
  21. data/lib/sparql/algebra/operator/bound.rb +22 -1
  22. data/lib/sparql/algebra/operator/ceil.rb +25 -2
  23. data/lib/sparql/algebra/operator/clear.rb +26 -2
  24. data/lib/sparql/algebra/operator/coalesce.rb +33 -11
  25. data/lib/sparql/algebra/operator/compare.rb +9 -0
  26. data/lib/sparql/algebra/operator/concat.rb +26 -2
  27. data/lib/sparql/algebra/operator/construct.rb +29 -6
  28. data/lib/sparql/algebra/operator/contains.rb +24 -2
  29. data/lib/sparql/algebra/operator/copy.rb +19 -2
  30. data/lib/sparql/algebra/operator/count.rb +52 -6
  31. data/lib/sparql/algebra/operator/create.rb +20 -2
  32. data/lib/sparql/algebra/operator/dataset.rb +37 -2
  33. data/lib/sparql/algebra/operator/datatype.rb +25 -6
  34. data/lib/sparql/algebra/operator/day.rb +25 -7
  35. data/lib/sparql/algebra/operator/delete.rb +29 -2
  36. data/lib/sparql/algebra/operator/delete_data.rb +23 -2
  37. data/lib/sparql/algebra/operator/delete_where.rb +24 -2
  38. data/lib/sparql/algebra/operator/desc.rb +20 -1
  39. data/lib/sparql/algebra/operator/describe.rb +27 -4
  40. data/lib/sparql/algebra/operator/distinct.rb +20 -3
  41. data/lib/sparql/algebra/operator/divide.rb +26 -2
  42. data/lib/sparql/algebra/operator/drop.rb +27 -3
  43. data/lib/sparql/algebra/operator/encode_for_uri.rb +22 -2
  44. data/lib/sparql/algebra/operator/equal.rb +12 -2
  45. data/lib/sparql/algebra/operator/exists.rb +28 -4
  46. data/lib/sparql/algebra/operator/exprlist.rb +15 -2
  47. data/lib/sparql/algebra/operator/extend.rb +95 -7
  48. data/lib/sparql/algebra/operator/filter.rb +27 -5
  49. data/lib/sparql/algebra/operator/floor.rb +25 -2
  50. data/lib/sparql/algebra/operator/function_call.rb +64 -0
  51. data/lib/sparql/algebra/operator/graph.rb +69 -6
  52. data/lib/sparql/algebra/operator/greater_than.rb +12 -3
  53. data/lib/sparql/algebra/operator/greater_than_or_equal.rb +12 -2
  54. data/lib/sparql/algebra/operator/group.rb +133 -8
  55. data/lib/sparql/algebra/operator/group_concat.rb +43 -7
  56. data/lib/sparql/algebra/operator/hours.rb +25 -7
  57. data/lib/sparql/algebra/operator/if.rb +20 -3
  58. data/lib/sparql/algebra/operator/in.rb +18 -1
  59. data/lib/sparql/algebra/operator/insert.rb +24 -2
  60. data/lib/sparql/algebra/operator/insert_data.rb +23 -2
  61. data/lib/sparql/algebra/operator/iri.rb +21 -4
  62. data/lib/sparql/algebra/operator/is_blank.rb +20 -3
  63. data/lib/sparql/algebra/operator/is_iri.rb +20 -3
  64. data/lib/sparql/algebra/operator/is_literal.rb +20 -3
  65. data/lib/sparql/algebra/operator/is_numeric.rb +22 -5
  66. data/lib/sparql/algebra/operator/is_triple.rb +32 -0
  67. data/lib/sparql/algebra/operator/join.rb +58 -3
  68. data/lib/sparql/algebra/operator/lang.rb +25 -0
  69. data/lib/sparql/algebra/operator/lang_matches.rb +22 -1
  70. data/lib/sparql/algebra/operator/lcase.rb +22 -2
  71. data/lib/sparql/algebra/operator/left_join.rb +44 -3
  72. data/lib/sparql/algebra/operator/less_than.rb +12 -3
  73. data/lib/sparql/algebra/operator/less_than_or_equal.rb +12 -2
  74. data/lib/sparql/algebra/operator/load.rb +25 -2
  75. data/lib/sparql/algebra/operator/max.rb +19 -1
  76. data/lib/sparql/algebra/operator/md5.rb +22 -5
  77. data/lib/sparql/algebra/operator/min.rb +21 -3
  78. data/lib/sparql/algebra/operator/minus.rb +65 -7
  79. data/lib/sparql/algebra/operator/minutes.rb +25 -7
  80. data/lib/sparql/algebra/operator/modify.rb +62 -5
  81. data/lib/sparql/algebra/operator/month.rb +25 -7
  82. data/lib/sparql/algebra/operator/move.rb +20 -2
  83. data/lib/sparql/algebra/operator/multiply.rb +26 -3
  84. data/lib/sparql/algebra/operator/negate.rb +23 -3
  85. data/lib/sparql/algebra/operator/not.rb +24 -3
  86. data/lib/sparql/algebra/operator/not_equal.rb +13 -0
  87. data/lib/sparql/algebra/operator/notexists.rb +30 -6
  88. data/lib/sparql/algebra/operator/notin.rb +20 -3
  89. data/lib/sparql/algebra/operator/notoneof.rb +21 -2
  90. data/lib/sparql/algebra/operator/now.rb +24 -5
  91. data/lib/sparql/algebra/operator/object.rb +32 -0
  92. data/lib/sparql/algebra/operator/or.rb +26 -3
  93. data/lib/sparql/algebra/operator/order.rb +64 -1
  94. data/lib/sparql/algebra/operator/path.rb +29 -2
  95. data/lib/sparql/algebra/operator/path_opt.rb +28 -65
  96. data/lib/sparql/algebra/operator/path_plus.rb +37 -10
  97. data/lib/sparql/algebra/operator/path_range.rb +178 -0
  98. data/lib/sparql/algebra/operator/path_star.rb +25 -4
  99. data/lib/sparql/algebra/operator/path_zero.rb +110 -0
  100. data/lib/sparql/algebra/operator/plus.rb +49 -8
  101. data/lib/sparql/algebra/operator/predicate.rb +32 -0
  102. data/lib/sparql/algebra/operator/prefix.rb +24 -3
  103. data/lib/sparql/algebra/operator/project.rb +111 -6
  104. data/lib/sparql/algebra/operator/rand.rb +30 -2
  105. data/lib/sparql/algebra/operator/reduced.rb +20 -3
  106. data/lib/sparql/algebra/operator/regex.rb +26 -18
  107. data/lib/sparql/algebra/operator/replace.rb +26 -6
  108. data/lib/sparql/algebra/operator/reverse.rb +31 -2
  109. data/lib/sparql/algebra/operator/round.rb +25 -2
  110. data/lib/sparql/algebra/operator/same_term.rb +24 -6
  111. data/lib/sparql/algebra/operator/sample.rb +32 -8
  112. data/lib/sparql/algebra/operator/seconds.rb +25 -7
  113. data/lib/sparql/algebra/operator/seq.rb +23 -5
  114. data/lib/sparql/algebra/operator/sequence.rb +14 -11
  115. data/lib/sparql/algebra/operator/sha1.rb +18 -1
  116. data/lib/sparql/algebra/operator/sha256.rb +18 -1
  117. data/lib/sparql/algebra/operator/sha384.rb +18 -1
  118. data/lib/sparql/algebra/operator/sha512.rb +18 -1
  119. data/lib/sparql/algebra/operator/slice.rb +27 -5
  120. data/lib/sparql/algebra/operator/str.rb +21 -1
  121. data/lib/sparql/algebra/operator/strafter.rb +25 -2
  122. data/lib/sparql/algebra/operator/strbefore.rb +25 -2
  123. data/lib/sparql/algebra/operator/strdt.rb +22 -1
  124. data/lib/sparql/algebra/operator/strends.rb +25 -3
  125. data/lib/sparql/algebra/operator/strlang.rb +24 -6
  126. data/lib/sparql/algebra/operator/strlen.rb +23 -2
  127. data/lib/sparql/algebra/operator/strstarts.rb +25 -2
  128. data/lib/sparql/algebra/operator/struuid.rb +29 -9
  129. data/lib/sparql/algebra/operator/subject.rb +32 -0
  130. data/lib/sparql/algebra/operator/substr.rb +23 -2
  131. data/lib/sparql/algebra/operator/subtract.rb +37 -7
  132. data/lib/sparql/algebra/operator/sum.rb +24 -6
  133. data/lib/sparql/algebra/operator/table.rb +85 -4
  134. data/lib/sparql/algebra/operator/timezone.rb +25 -7
  135. data/lib/sparql/algebra/operator/triple.rb +24 -0
  136. data/lib/sparql/algebra/operator/tz.rb +24 -7
  137. data/lib/sparql/algebra/operator/ucase.rb +23 -2
  138. data/lib/sparql/algebra/operator/union.rb +29 -6
  139. data/lib/sparql/algebra/operator/update.rb +46 -4
  140. data/lib/sparql/algebra/operator/using.rb +49 -2
  141. data/lib/sparql/algebra/operator/uuid.rb +27 -8
  142. data/lib/sparql/algebra/operator/with.rb +38 -4
  143. data/lib/sparql/algebra/operator/year.rb +25 -7
  144. data/lib/sparql/algebra/operator.rb +150 -12
  145. data/lib/sparql/algebra/query.rb +5 -3
  146. data/lib/sparql/algebra/sxp_extensions.rb +3 -3
  147. data/lib/sparql/algebra.rb +42 -6
  148. data/lib/sparql/grammar/meta.rb +1367 -267
  149. data/lib/sparql/grammar/parser11.rb +829 -331
  150. data/lib/sparql/grammar/terminals11.rb +2 -2
  151. data/lib/sparql/grammar.rb +6 -4
  152. data/lib/sparql/results.rb +3 -2
  153. data/lib/sparql/server.rb +93 -0
  154. data/lib/sparql.rb +8 -5
  155. metadata +57 -35
@@ -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,18 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL Property Path `path*` (ZeroOrMorePath) operator.
5
5
  #
6
- # @example
7
- # (path* :p)
6
+ # [91] PathElt ::= PathPrimary PathMod?
7
+ # [93] PathMod ::= '*' | '?' | '+' | '{' INTEGER? (',' INTEGER?)? '}'
8
+
9
+ # @example SPARQL Grammar
10
+ # PREFIX : <http://example/>
11
+ # SELECT * WHERE {
12
+ # :a :p* ?z
13
+ # }
14
+ #
15
+ # @example SSE
16
+ # (prefix ((: <http://example/>))
17
+ # (path :a (path* :p) ?z))
8
18
  #
9
19
  # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrMorePath
10
20
  class PathStar < Operator::Unary
@@ -13,10 +23,13 @@ module SPARQL; module Algebra
13
23
  NAME = :"path*"
14
24
 
15
25
  ##
16
- # Path including zero length:
26
+ # Path including at zero length:
17
27
  #
18
28
  # (path :a (path* :p) :b)
19
- # => (path :a (path? (path+ :p)) :b)
29
+ #
30
+ # into
31
+ #
32
+ # (path :a (path? (path+ :p)) :b)
20
33
  #
21
34
  # @param [RDF::Queryable] queryable
22
35
  # the graph or repository to query
@@ -37,6 +50,14 @@ module SPARQL; module Algebra
37
50
  query = PathOpt.new(PathPlus.new(*operands))
38
51
  query.execute(queryable, depth: options[:depth].to_i + 1, **options, &block)
39
52
  end
53
+ ##
54
+ #
55
+ # Returns a partial SPARQL grammar for this operator.
56
+ #
57
+ # @return [String]
58
+ def to_sparql(**options)
59
+ "(#{operands.first.to_sparql(**options)})*"
60
+ end
40
61
  end # PathStar
41
62
  end # Operator
42
63
  end; end # SPARQL::Algebra
@@ -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
@@ -3,9 +3,39 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL numeric binary/unary `+` operator.
5
5
  #
6
- # @example
7
- # (+ ?x ?y)
8
- # (plus ?x ?y)
6
+ # [118] UnaryExpression ::= ... | '+' PrimaryExpression
7
+ #
8
+ # @example SPARQL Grammar
9
+ # PREFIX : <http://example.org/>
10
+ # SELECT ?s WHERE {
11
+ # ?s :p ?o .
12
+ # FILTER(-?o = +3) .
13
+ # }
14
+ #
15
+ # @example SSE
16
+ # (prefix ((: <http://example.org/>))
17
+ # (project (?s)
18
+ # (filter (= (- ?o) +3)
19
+ # (bgp (triple ?s :p ?o)))))
20
+ #
21
+ # [116] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression )?
22
+ #
23
+ # @example SPARQL Grammar
24
+ # PREFIX : <http://example.org/>
25
+ # SELECT ?s WHERE {
26
+ # ?s :p ?o .
27
+ # ?s2 :p ?o2 .
28
+ # FILTER(?o + ?o2 = 3) .
29
+ # }
30
+ #
31
+ # @example SSE
32
+ # (prefix
33
+ # ((: <http://example.org/>))
34
+ # (project (?s)
35
+ # (filter (= (+ ?o ?o2) 3)
36
+ # (bgp
37
+ # (triple ?s :p ?o)
38
+ # (triple ?s2 :p ?o2)))))
9
39
  #
10
40
  # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-plus
11
41
  # @see https://www.w3.org/TR/xpath-functions/#func-numeric-add
@@ -17,21 +47,32 @@ module SPARQL; module Algebra
17
47
  ##
18
48
  # Returns the arithmetic sum of the operands, unless there is no `right`.
19
49
  #
20
- # @param [RDF::Literal::Numeric] left
50
+ # @param [RDF::Literal::Numeric, RDF::Literal::Temporal] left
21
51
  # a numeric literal
22
- # @param [RDF::Literal::Numeric] right
52
+ # @param [RDF::Literal::Numeric, RDF::Literal::Duration] right
23
53
  # a numeric literal
24
- # @return [RDF::Literal::Numeric]
25
- # @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
26
56
  def apply(left, right = nil, **options)
27
57
  case
28
58
  when left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)
29
59
  left + right
30
60
  when left.is_a?(RDF::Literal::Numeric) && right.nil?
31
61
  left
32
- 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}"
33
65
  end
34
66
  end
67
+
68
+ ##
69
+ #
70
+ # Returns a partial SPARQL grammar for this operator.
71
+ #
72
+ # @return [String]
73
+ def to_sparql(**options)
74
+ "(#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)})"
75
+ end
35
76
  end # Plus
36
77
  end # Operator
37
78
  end; end # SPARQL::Algebra
@@ -5,6 +5,29 @@ module SPARQL; module Algebra
5
5
  #
6
6
  # If triple is an RDF-star triple, the function returns the predicate of this triple. Passing anything other than an RDF-star triple is an error.
7
7
  #
8
+ # [121] BuiltInCall ::= ... | 'PREDICATE' '(' Expression ')'
9
+ #
10
+ # @example SPARQL Grammar
11
+ # PREFIX : <http://example.com/ns#>
12
+ # SELECT * {
13
+ # ?t :source :g
14
+ # FILTER(isTriple(?t))
15
+ # FILTER(SUBJECT(?t) = :s)
16
+ # FILTER(PREDICATE(?t) = :p)
17
+ # FILTER(OBJECT(?t) = :o)
18
+ # }
19
+ #
20
+ # @example SSE
21
+ # (prefix
22
+ # ((: <http://example.com/ns#>))
23
+ # (filter
24
+ # (exprlist
25
+ # (isTRIPLE ?t)
26
+ # (= (subject ?t) :s)
27
+ # (= (predicate ?t) :p)
28
+ # (= (object ?t) :o))
29
+ # (bgp (triple ?t :source :g))) )
30
+ #
8
31
  # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#predicate
9
32
  class Predicate < Operator::Unary
10
33
  include Evaluatable
@@ -22,6 +45,15 @@ module SPARQL; module Algebra
22
45
  raise TypeError, "expected an RDF::Statement, but got #{operand.inspect}" unless operand.is_a?(RDF::Statement)
23
46
  operand.predicate
24
47
  end
48
+
49
+ ##
50
+ #
51
+ # Returns a partial SPARQL grammar for this operator.
52
+ #
53
+ # @return [String]
54
+ def to_sparql(**options)
55
+ "PREDICATE(" + operands.last.to_sparql(**options) + ")"
56
+ end
25
57
  end # Predicate
26
58
  end # Operator
27
59
  end; end # SPARQL::Algebra
@@ -3,10 +3,15 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL GraphPattern `prefix` operator.
5
5
  #
6
- # @example
6
+ # [6] PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
7
+ #
8
+ # @example SPARQL Grammar
9
+ # PREFIX : <http://example/>
10
+ # SELECT * { :s :p :o }
11
+ #
12
+ # @example SSE
7
13
  # (prefix ((: <http://example/>))
8
- # (graph ?g
9
- # (bgp (triple ?s ?p ?o))))
14
+ # (bgp (triple :s :p :o)))
10
15
  #
11
16
  # @see https://www.w3.org/TR/sparql11-query/#QSynIRI
12
17
  class Prefix < Binary
@@ -65,6 +70,22 @@ module SPARQL; module Algebra
65
70
  def query_yields_statements?
66
71
  operands.last.query_yields_statements?
67
72
  end
73
+
74
+ ##
75
+ #
76
+ # Returns a partial SPARQL grammar for this term.
77
+ #
78
+ # @return [String]
79
+ def to_sparql(**options)
80
+ prefixes = {}
81
+ str = operands.first.map do |(pfx, sfx)|
82
+ pfx = pfx.to_s.chomp(':').to_sym
83
+ prefixes[pfx] = sfx
84
+ "PREFIX #{pfx}: #{sfx.to_sparql}\n"
85
+ end.join("")
86
+
87
+ str << operands.last.to_sparql(prefixes: prefixes, **options)
88
+ end
68
89
  end # Prefix
69
90
  end # Operator
70
91
  end; end # SPARQL::Algebra
@@ -3,11 +3,53 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL GraphPattern `project` operator.
5
5
  #
6
- # @example
7
- # (select (?v)
8
- # (project (?v)
9
- # (filter (= ?v 2)
10
- # (bgp (triple ?s <http://example/p> ?v)))))
6
+ # [9] SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' )
7
+ #
8
+ # ## Basic Projection
9
+ #
10
+ # @example SPARQL Grammar
11
+ # PREFIX : <http://example/>
12
+ # SELECT ?v {
13
+ # ?s :p ?v .
14
+ # FILTER (?v = 2)
15
+ # }
16
+ #
17
+ # @example SSE
18
+ # (prefix ((: <http://example/>))
19
+ # (project (?v)
20
+ # (filter (= ?v 2)
21
+ # (bgp (triple ?s :p ?v)))))
22
+ #
23
+ # @example SPARQL Grammar (Sub select)
24
+ # SELECT (1 AS ?X ) {
25
+ # SELECT (2 AS ?Y ) {}
26
+ # }
27
+ #
28
+ # @example SSE (Sub select)
29
+ # (project (?X)
30
+ # (extend ((?X 1))
31
+ # (project (?Y)
32
+ # (extend ((?Y 2))
33
+ # (bgp)))))
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)))))) ))
11
53
  #
12
54
  # @see https://www.w3.org/TR/sparql11-query/#modProjection
13
55
  class Project < Operator::Binary
@@ -15,10 +57,38 @@ module SPARQL; module Algebra
15
57
 
16
58
  NAME = [:project]
17
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
+
18
86
  ##
19
87
  # Executes this query on the given `queryable` graph or repository.
20
88
  # Reduces the result set to the variables listed in the first operand
21
89
  #
90
+ # If the first operand is empty, this indicates a `SPARQL *`, and all in-scope variables are projected.
91
+ #
22
92
  # @param [RDF::Queryable] queryable
23
93
  # the graph or repository to query
24
94
  # @param [Hash{Symbol => Object}] options
@@ -32,10 +102,45 @@ module SPARQL; module Algebra
32
102
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
33
103
  def execute(queryable, **options, &block)
34
104
  @solutions = queryable.query(operands.last, depth: options[:depth].to_i + 1, **options)
35
- @solutions = @solutions.project(*(operands.first))
105
+ @solutions.variable_names = self.variables.keys
106
+ @solutions = @solutions.project(*(operands.first)) unless operands.first.empty?
36
107
  @solutions.each(&block) if block_given?
37
108
  @solutions
38
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
122
+
123
+ ##
124
+ #
125
+ # Returns a partial SPARQL grammar for this operator.
126
+ #
127
+ # Extracts projections
128
+ #
129
+ # If there are already extensions or filters, then this is a sub-select.
130
+ #
131
+ # @return [String]
132
+ def to_sparql(**options)
133
+ vars = operands[0].empty? ? [:*] : operands[0]
134
+ if options[:extensions] || options[:filter_ops] || options[:project]
135
+ # Any of these options indicates we're in a sub-select
136
+ opts = options.dup.delete_if {|k,v| %I{extensions filter_ops project}.include?(k)}
137
+ content = operands.last.to_sparql(project: vars, **opts)
138
+ content = "{#{content}}" unless content.start_with?('{') && content.end_with?('}')
139
+ Operator.to_sparql(content, **options)
140
+ else
141
+ operands.last.to_sparql(project: vars, **options)
142
+ end
143
+ end
39
144
  end # Project
40
145
  end # Operator
41
146
  end; end # SPARQL::Algebra
@@ -5,8 +5,25 @@ module SPARQL; module Algebra
5
5
  #
6
6
  # Returns a pseudo-random number between 0 (inclusive) and 1.0e0 (exclusive). Different numbers can be produced every time this function is invoked. Numbers should be produced with approximately equal probability.
7
7
  #
8
- # @example
9
- # (rand)
8
+ # [121] BuiltInCall ::= ... | 'RAND' NIL
9
+ #
10
+ # @example SPARQL Grammar
11
+ # PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
12
+ # ASK {
13
+ # BIND(RAND() AS ?r)
14
+ # FILTER(DATATYPE(?r) = xsd:double && ?r >= 0.0 && ?r < 1.0)
15
+ # }
16
+ #
17
+ # @example SSE
18
+ # (prefix
19
+ # ((xsd: <http://www.w3.org/2001/XMLSchema#>))
20
+ # (ask
21
+ # (filter
22
+ # (&&
23
+ # (&& (= (datatype ?r) xsd:double) (>= ?r 0.0))
24
+ # (< ?r 1.0))
25
+ # (extend ((?r (rand)))
26
+ # (bgp)))))
10
27
  #
11
28
  # @see https://www.w3.org/TR/sparql11-query/#idp2130040
12
29
  class Rand < Operator::Nullary
@@ -21,6 +38,17 @@ module SPARQL; module Algebra
21
38
  def apply(**options)
22
39
  RDF::Literal::Double.new(Random.rand)
23
40
  end
41
+
42
+ ##
43
+ #
44
+ # Returns a partial SPARQL grammar for this operator.
45
+ #
46
+ # Extracts projections
47
+ #
48
+ # @return [String]
49
+ def to_sparql(**options)
50
+ "RAND()"
51
+ end
24
52
  end # Rand
25
53
  end # Operator
26
54
  end; end # SPARQL::Algebra