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
@@ -44,8 +44,8 @@ module SPARQL; module Algebra
44
44
  # @return [RDF::Query::Solutions]
45
45
  # the resulting solution sequence
46
46
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
47
- # @see https://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#merge-instance_method
48
- # @see https://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#compatible%3F-instance_method
47
+ # @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#merge-instance_method
48
+ # @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#compatible%3F-instance_method
49
49
  def execute(queryable, **options, &block)
50
50
  filter = operand(2)
51
51
 
@@ -66,6 +66,11 @@ module SPARQL; module Algebra
66
66
  load_left = true
67
67
  right.each do |s2|
68
68
  s = s2.merge(s1)
69
+ # Re-bind to bindings, if defined, as they might not be found in solution
70
+ options[:bindings].each_binding do |name, value|
71
+ s[name] = value if filter.variables.include?(name)
72
+ end if options[:bindings] && filter.respond_to?(:variables)
73
+
69
74
  expr = filter ? boolean(filter.evaluate(s)).true? : true rescue false
70
75
  debug(options) {"===>(evaluate) #{s.inspect}"} if filter
71
76
 
@@ -131,14 +136,27 @@ module SPARQL; module Algebra
131
136
  #
132
137
  # @param [Boolean] top_level (true)
133
138
  # Treat this as a top-level, generating SELECT ... WHERE {}
139
+ # @param [Hash{String => Operator}] extensions
140
+ # Variable bindings
141
+ # @param [Array<Operator>] filter_ops ([])
142
+ # Filter Operations
134
143
  # @return [String]
135
- def to_sparql(top_level: true, **options)
136
- str = operands[0].to_sparql(top_level: false, **options) +
137
- "\nOPTIONAL { \n" +
138
- operands[1].to_sparql(top_level: false, **options) + "\n"
139
- str << 'FILTER (' + operands[2].to_sparql(**options) + ") \n" if operands[2]
140
- str << '}'
141
- top_level ? Operator.to_sparql(str, **options) : str
144
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
145
+ str = "{\n" + operands[0].to_sparql(top_level: false, extensions: {}, **options)
146
+ str <<
147
+ "\nOPTIONAL {\n" +
148
+ operands[1].to_sparql(top_level: false, extensions: {}, **options)
149
+ case operands[2]
150
+ when SPARQL::Algebra::Operator::Exprlist
151
+ operands[2].operands.each do |op|
152
+ str << "\nFILTER (" + op.to_sparql(**options) + ")"
153
+ end
154
+ when nil
155
+ else
156
+ str << "\nFILTER (" + operands[2].to_sparql(**options) + ")"
157
+ end
158
+ str << "\n}}"
159
+ top_level ? Operator.to_sparql(str, filter_ops: filter_ops, extensions: extensions, **options) : str
142
160
  end
143
161
  end # LeftJoin
144
162
  end # Operator
@@ -56,7 +56,9 @@ module SPARQL; module Algebra
56
56
  #
57
57
  # @return [String]
58
58
  def to_sparql(**options)
59
- "MAX(" + operands.to_sparql(**options) + ")"
59
+ distinct = operands.first == :distinct
60
+ args = distinct ? operands[1..-1] : operands
61
+ "MAX(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
60
62
  end
61
63
  end # Max
62
64
  end # Operator
@@ -15,7 +15,7 @@ module SPARQL; module Algebra
15
15
  # (project (?min)
16
16
  # (extend ((?min ??.0))
17
17
  # (group () ((??.0 (min ?o)))
18
- # (bgp (triple ?s ?p ?o))))))
18
+ # (bgp (triple ?s :dec ?o))))))
19
19
  #
20
20
  # @see https://www.w3.org/TR/sparql11-query/#defn_aggMin
21
21
  class Min < Operator
@@ -56,7 +56,9 @@ module SPARQL; module Algebra
56
56
  #
57
57
  # @return [String]
58
58
  def to_sparql(**options)
59
- "MIN(" + operands.to_sparql(**options) + ")"
59
+ distinct = operands.first == :distinct
60
+ args = distinct ? operands[1..-1] : operands
61
+ "MIN(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
60
62
  end
61
63
  end # Min
62
64
  end # Operator
@@ -14,6 +14,32 @@ module SPARQL; module Algebra
14
14
  # (triple ?s ?p ?o))
15
15
  # (bgp (triple ?s ?q ?v)))
16
16
  #
17
+ # @example SPARQL Grammar (inline filter)
18
+ # PREFIX : <http://example/>
19
+ # SELECT (?s1 AS ?subset) (?s2 AS ?superset)
20
+ # WHERE {
21
+ # ?s2 a :Set .
22
+ # ?s1 a :Set .
23
+ # FILTER(?s1 != ?s2)
24
+ # MINUS {
25
+ # ?s1 a :Set .
26
+ # ?s2 a :Set .
27
+ # FILTER(?s1 != ?s2)
28
+ # }
29
+ # }
30
+ #
31
+ # @example SSE (inline filter)
32
+ # (prefix ((: <http://example/>))
33
+ # (project (?subset ?superset)
34
+ # (extend ((?subset ?s1) (?superset ?s2))
35
+ # (filter (!= ?s1 ?s2)
36
+ # (minus
37
+ # (bgp (triple ?s2 a :Set) (triple ?s1 a :Set))
38
+ # (filter (!= ?s1 ?s2)
39
+ # (bgp
40
+ # (triple ?s1 a :Set)
41
+ # (triple ?s2 a :Set))))))))
42
+ #
17
43
  # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus
18
44
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
19
45
  class Minus < Operator::Binary
@@ -74,15 +100,29 @@ module SPARQL; module Algebra
74
100
  #
75
101
  # Returns a partial SPARQL grammar for this operator.
76
102
  #
103
+ # @param [Hash{String => Operator}] extensions
104
+ # Variable bindings
105
+ # @param [Array<Operator>] filter_ops ([])
106
+ # Filter Operations
77
107
  # @param [Boolean] top_level (true)
78
108
  # Treat this as a top-level, generating SELECT ... WHERE {}
79
109
  # @return [String]
80
- def to_sparql(top_level: true, **options)
81
- str = operands.first.to_sparql(top_level: false, **options) + "\n"
82
- str << "MINUS {\n"
83
- str << operands.last.to_sparql(top_level: false, **options)
84
- str << "\n}"
85
- top_level ? Operator.to_sparql(str, **options) : str
110
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
111
+ lhs, *rhs = operands
112
+ str = "{\n" + lhs.to_sparql(top_level: false, extensions: {}, **options)
113
+
114
+ # Any accrued filters go here.
115
+ filter_ops.each do |op|
116
+ str << "\nFILTER (#{op.to_sparql(**options)}) ."
117
+ end
118
+
119
+ rhs.each do |minus|
120
+ str << "\nMINUS {\n"
121
+ str << minus.to_sparql(top_level: false, extensions: {}, **options)
122
+ str << "\n}"
123
+ end
124
+ str << "}"
125
+ top_level ? Operator.to_sparql(str, extensions: extensions, **options) : str
86
126
  end
87
127
  end # Minus
88
128
  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.minute)
39
39
  end
40
40
 
@@ -6,6 +6,11 @@ module SPARQL; module Algebra
6
6
  #
7
7
  # Wraps delete/insert
8
8
  #
9
+ # If `options` contains any of the Protocol attributes, it is treated as if there is a USING or USING NAMED clause inserted.
10
+ #
11
+ # * `using-graph-uri`
12
+ # * `using-named-graph-uri`
13
+ #
9
14
  # [41] Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern
10
15
  #
11
16
  # @example SPARQL Grammar
@@ -35,6 +40,10 @@ module SPARQL; module Algebra
35
40
  #
36
41
  # Execute the first operand to get solutions, and apply those solutions to the subsequent operators.
37
42
  #
43
+ # If `options` contains any of the Protocol attributes, any `using` clause is removed and a new `using` clause is added with entries taken from the `using-graph-uri` and `using-named-graph-uri`.
44
+ #
45
+ # It is an error to supply the using-graph-uri or using-named-graph-uri parameters when using this protocol to convey a SPARQL 1.1 Update request that contains an operation that uses the USING, USING NAMED, or WITH clause.
46
+ #
38
47
  # @param [RDF::Queryable] queryable
39
48
  # the graph or repository to write
40
49
  # @param [Hash{Symbol => Object}] options
@@ -50,6 +59,18 @@ module SPARQL; module Algebra
50
59
  debug(options) {"Modify"}
51
60
  query = operands.shift
52
61
 
62
+ if %i(using-graph-uri using-named-graph-uri).any? {|k| options.key?(k)}
63
+ raise ArgumentError,
64
+ "query contains USING/WITH clause, which is incompatible with using-graph-uri or using-named-graph-uri query parameters" if
65
+ query.is_a?(Operator::Using) || query.is_a?(Operator::With)
66
+
67
+ debug("=> Insert USING clause", options)
68
+ defaults = Array(options.delete(:'using-graph-uri')).map {|uri| RDF::URI(uri)}
69
+ named = Array(options.delete(:'using-named-graph-uri')).map {|uri| [:named, RDF::URI(uri)]}
70
+
71
+ query = Operator::Using.new((defaults + named), query, **options)
72
+ end
73
+
53
74
  queryable.query(query, depth: options[:depth].to_i + 1, **options) do |solution|
54
75
  debug(options) {"(solution)=>#{solution.inspect}"}
55
76
 
@@ -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.month)
39
39
  end
40
40
 
@@ -50,7 +50,7 @@ module SPARQL; module Algebra
50
50
  #
51
51
  # @return [String]
52
52
  def to_sparql(**options)
53
- "#{operands.first.to_sparql(**options)} * #{operands.last.to_sparql(**options)}"
53
+ "(#{operands.first.to_sparql(**options)} * #{operands.last.to_sparql(**options)})"
54
54
  end
55
55
  end # Multiply
56
56
  end # Operator
@@ -8,13 +8,13 @@ module SPARQL; module Algebra
8
8
  # @example SPARQL Grammar
9
9
  # PREFIX ex: <http://www.example.org/schema#>
10
10
  # PREFIX in: <http://www.example.org/instance#>
11
- # ASK { in:b ^ex:p in:a }
11
+ # ASK { in:a !(ex:p1|ex:p2) ?x }
12
12
  #
13
13
  # @example SSE
14
14
  # (prefix ((ex: <http://www.example.org/schema#>)
15
- # (in: <http://www.example.org/instance#>))
15
+ # (in: <http://www.example.org/instance#>))
16
16
  # (ask
17
- # (path in:b (reverse ex:p) in:a)))
17
+ # (path in:a (notoneof ex:p1 ex:p2) ?x)))
18
18
  #
19
19
  # @see https://www.w3.org/TR/sparql11-query/#eval_negatedPropertySet
20
20
  class NotOneOf < Operator
@@ -56,6 +56,15 @@ module SPARQL; module Algebra
56
56
  block.call(solution)
57
57
  end
58
58
  end
59
+
60
+ ##
61
+ #
62
+ # Returns a partial SPARQL grammar for this operator.
63
+ #
64
+ # @return [String]
65
+ def to_sparql(**options)
66
+ "!(" + operands.to_sparql(delimiter: ' | ', **options) + ')'
67
+ end
59
68
  end # NotOneOf
60
69
  end # Operator
61
70
  end; end # SPARQL::Algebra
@@ -17,6 +17,50 @@ module SPARQL; module Algebra
17
17
  # (order ((asc ?name))
18
18
  # (bgp (triple ?x foaf:name ?name)))))
19
19
  #
20
+ # @example SPARQL Grammar (with builtin)
21
+ # PREFIX : <http://example.org/>
22
+ # SELECT ?s WHERE {
23
+ # ?s :p ?o .
24
+ # }
25
+ # ORDER BY str(?o)
26
+ #
27
+ # @example SSE (with builtin)
28
+ # (prefix ((: <http://example.org/>))
29
+ # (project (?s)
30
+ # (order ((str ?o))
31
+ # (bgp (triple ?s :p ?o)))))
32
+ #
33
+ # @example SPARQL Grammar (with bracketed expression)
34
+ # PREFIX : <http://example.org/>
35
+ # SELECT ?s WHERE {
36
+ # ?s :p ?o1 ; :q ?o2 .
37
+ # } ORDER BY (?o1 + ?o2)
38
+ #
39
+ # @example SSE (with bracketed expression)
40
+ # (prefix
41
+ # ((: <http://example.org/>))
42
+ # (project (?s)
43
+ # (order ((+ ?o1 ?o2))
44
+ # (bgp
45
+ # (triple ?s :p ?o1)
46
+ # (triple ?s :q ?o2)))))
47
+ #
48
+ # @example SPARQL Grammar (with function call)
49
+ # PREFIX : <http://example.org/ns#>
50
+ # PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
51
+ # SELECT *
52
+ # { ?s ?p ?o }
53
+ # ORDER BY
54
+ # DESC(?o+57) xsd:string(?o) ASC(?s)
55
+ #
56
+ # @example SSE (with function call)
57
+ # (prefix ((: <http://example.org/ns#>)
58
+ # (xsd: <http://www.w3.org/2001/XMLSchema#>))
59
+ # (order ((desc (+ ?o 57))
60
+ # (xsd:string ?o)
61
+ # (asc ?s))
62
+ # (bgp (triple ?s ?p ?o))))
63
+ #
20
64
  # @see https://www.w3.org/TR/sparql11-query/#modOrderBy
21
65
  class Order < Operator::Binary
22
66
  include Query
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL Property Path `path?` (ZeroOrOnePath) 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,11 +23,10 @@ module SPARQL; module Algebra
23
23
  NAME = :path?
24
24
 
25
25
  ##
26
- # Equivalent to:
26
+ # Optional path:
27
27
  #
28
28
  # (path x (path? :p) y)
29
- # => (union (bgp ((x :p y))) (filter (x = x) (solution x y)))
30
- #
29
+ # => (union (bgp ((x :p y))) (filter (x = y) (solution x y)))
31
30
  #
32
31
  # @param [RDF::Queryable] queryable
33
32
  # the graph or repository to query
@@ -44,76 +43,21 @@ module SPARQL; module Algebra
44
43
  subject, object = options[:subject], options[:object]
45
44
  debug(options) {"Path? #{[subject, operands, object].to_sse}"}
46
45
 
47
- solutions = RDF::Query::Solutions.new
48
- # Solutions where subject == object with no predicate
49
- case
50
- when subject.variable? && object.variable?
51
- # Nodes is the set of all subjects and objects in queryable
52
- # FIXME: should this be Queryable#enum_nodes?
53
- # All subjects which are `object`
54
- query = RDF::Query.new {|q| q.pattern({subject: subject})}
55
- queryable.query(query, **options) do |solution|
56
- solution.merge!(object.to_sym => solution[subject])
57
- debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
58
- solutions << solution
59
- end if query.valid?
60
-
61
- # All objects which are `object`
62
- query = RDF::Query.new {|q| q.pattern({object: object})}
63
- queryable.query(query, **options) do |solution|
64
- solution.merge!(subject.to_sym => solution[object])
65
- debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
66
- solutions << solution
67
- end if query.valid?
68
- when subject.variable?
69
- # All subjects which are `object`
70
- query = RDF::Query.new {|q| q.pattern({subject: object})}
71
- queryable.query(query, **options) do |solution|
72
- solution.merge!(subject.to_sym => object)
73
- debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
74
- solutions << solution
75
- end if query.valid?
76
-
77
- # All objects which are `object`
78
- query = RDF::Query.new {|q| q.pattern({object: object})}
79
- queryable.query(query, **options) do |solution|
80
- solution.merge!(subject.to_sym => object)
81
- debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
82
- solutions << solution
83
- end if query.valid?
84
- when object.variable?
85
- # All subjects which are `subject`
86
- query = RDF::Query.new {|q| q.pattern({subject: subject})}
87
- queryable.query(query, **options) do |solution|
88
- solution.merge!(object.to_sym => subject)
89
- debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
90
- solutions << solution
91
- end if query.valid?
92
-
93
- # All objects which are `subject
94
- query = RDF::Query.new {|q| q.pattern({object: subject})}
95
- queryable.query(query, **options) do |solution|
96
- solution.merge!(object.to_sym => subject)
97
- debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
98
- solutions << solution
99
- end if query.valid?
100
- else
101
- # Otherwise, if subject == object, an empty solution
102
- solutions << RDF::Query::Solution.new if subject == object
103
- end
46
+ query = PathZero.new(operand)
47
+ solutions = query.execute(queryable, **options.merge(depth: options[:depth].to_i + 1))
104
48
 
105
49
  # Solutions where predicate exists
106
50
  query = if operand.is_a?(RDF::Term)
107
51
  RDF::Query.new do |q|
108
52
  q.pattern [subject, operand, object]
109
53
  end
110
- else
54
+ else # path
111
55
  operand
112
56
  end
113
57
 
114
58
  # Recurse into query
115
- solutions +=
116
- queryable.query(query, depth: options[:depth].to_i + 1, **options)
59
+ solutions += query.execute(queryable, **options.merge(depth: options[:depth].to_i + 1))
60
+ debug(options) {"(path?)=> #{solutions.to_sxp}"}
117
61
  solutions.each(&block) if block_given?
118
62
  solutions
119
63
  end
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
3
3
  ##
4
4
  # The SPARQL Property Path `path+` (OneOrMorePath) 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/>
@@ -25,6 +25,16 @@ module SPARQL; module Algebra
25
25
  ##
26
26
  # Match on simple relation of subject to object, and then recurse on solutions
27
27
  #
28
+ # Path including at least one:
29
+ #
30
+ # (path :a (path+ :p) :b)
31
+ #
32
+ # into
33
+ #
34
+ # (union
35
+ # (bgp (triple :a :p :b))
36
+ # (path :a (path* :p) :b))
37
+ #
28
38
  # @param [RDF::Queryable] queryable
29
39
  # the graph or repository to query
30
40
  # @param [Hash{Symbol => Object}] options
@@ -62,10 +72,8 @@ module SPARQL; module Algebra
62
72
 
63
73
  # Keep track of solutions
64
74
  # Recurse into query
65
- immediate_solutions = []
66
- queryable.query(query, depth: options[:depth].to_i + 1, **options) do |solution|
67
- immediate_solutions << solution
68
- end
75
+ immediate_solutions =
76
+ query.execute(queryable, depth: options[:depth].to_i + 1, **options)
69
77
 
70
78
  # For all solutions, if they are not in the accumulator, add them and recurse, otherwise skip
71
79
  recursive_solutions = RDF::Query::Solutions.new
@@ -76,23 +84,23 @@ module SPARQL; module Algebra
76
84
  case
77
85
  when subject.variable? && object.variable?
78
86
  # Query starting with bound object as subject, but replace result with subject
79
- rs = queryable.query(self, **options.merge(
87
+ rs = self.execute(queryable, **options.merge(
80
88
  subject: solution[object],
81
89
  accumulator: (cumulative_solutions + immediate_solutions),
82
90
  depth: options[:depth].to_i + 1)).map {|s| s.merge(subject.to_sym => solution[subject])}
83
91
  # Query starting with bound subject as object, but replace result with subject
84
- ro = queryable.query(self, **options.merge(
92
+ ro = self.execute(queryable, **options.merge(
85
93
  object: solution[subject],
86
94
  accumulator: (cumulative_solutions + immediate_solutions),
87
95
  depth: options[:depth].to_i + 1)).map {|s| s.merge(object.to_sym => solution[object])}
88
96
  recursive_solutions += (rs + ro).uniq
89
97
  when subject.variable?
90
- recursive_solutions += queryable.query(self, **options.merge(
98
+ recursive_solutions += self.execute(queryable, **options.merge(
91
99
  object: solution[subject],
92
100
  accumulator: (cumulative_solutions + immediate_solutions),
93
101
  depth: options[:depth].to_i + 1)).uniq
94
102
  when object.variable?
95
- recursive_solutions += queryable.query(self, **options.merge(
103
+ recursive_solutions += self.execute(queryable, **options.merge(
96
104
  subject: solution[object],
97
105
  accumulator: (cumulative_solutions + immediate_solutions),
98
106
  depth: options[:depth].to_i + 1)).uniq