sparql 1.1.6 → 1.1.7
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.
- checksums.yaml +4 -4
- data/README.md +4 -6
- data/VERSION +1 -1
- data/lib/sparql/algebra/expression.rb +28 -2
- data/lib/sparql/algebra/extensions.rb +73 -0
- data/lib/sparql/algebra/operator.rb +73 -9
- data/lib/sparql/algebra/operator/alt.rb +58 -0
- data/lib/sparql/algebra/operator/extend.rb +15 -3
- data/lib/sparql/algebra/operator/filter.rb +18 -1
- data/lib/sparql/algebra/operator/group.rb +22 -1
- data/lib/sparql/algebra/operator/join.rb +16 -5
- data/lib/sparql/algebra/operator/left_join.rb +12 -1
- data/lib/sparql/algebra/operator/notin.rb +1 -1
- data/lib/sparql/algebra/operator/notoneof.rb +52 -0
- data/lib/sparql/algebra/operator/path.rb +49 -0
- data/lib/sparql/algebra/operator/path_opt.rb +112 -0
- data/lib/sparql/algebra/operator/path_plus.rb +99 -0
- data/lib/sparql/algebra/operator/path_star.rb +42 -0
- data/lib/sparql/algebra/operator/reverse.rb +54 -0
- data/lib/sparql/algebra/operator/seq.rb +73 -0
- data/lib/sparql/algebra/operator/sequence.rb +63 -0
- data/lib/sparql/algebra/operator/union.rb +12 -1
- data/lib/sparql/algebra/query.rb +1 -0
- data/lib/sparql/grammar/meta.rb +4593 -4582
- data/lib/sparql/grammar/parser11.rb +301 -107
- metadata +13 -4
@@ -91,7 +91,28 @@ module SPARQL; module Algebra
|
|
91
91
|
@solutions.each(&block) if block_given?
|
92
92
|
@solutions
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
|
+
# It is an error for aggregates to project variables with a name already used in other aggregate projections, or in the WHERE clause.
|
96
|
+
#
|
97
|
+
# It is also an error to project ungrouped variables
|
98
|
+
def validate!
|
99
|
+
group_vars = operand(0).map {|v| Array(v).first}
|
100
|
+
ext = first_ancestor(Extend)
|
101
|
+
extend_vars = ext ? ext.operand(0).map(&:first).select {|v| v.is_a?(RDF::Query::Variable)} : []
|
102
|
+
project = first_ancestor(Project)
|
103
|
+
# If not projecting, were are effectively projecting all variables in the query
|
104
|
+
project_vars = project ? project.operand(0) : operands.last.vars
|
105
|
+
|
106
|
+
available_vars = (extend_vars + group_vars).compact
|
107
|
+
|
108
|
+
# All variables must either be grouped or extended
|
109
|
+
unless (project_vars - available_vars).empty?
|
110
|
+
raise ArgumentError,
|
111
|
+
"projecting ungrouped/extended variables: #{(project_vars.compact - available_vars.compact).to_sse}"
|
112
|
+
end
|
113
|
+
super
|
114
|
+
end
|
115
|
+
|
95
116
|
##
|
96
117
|
# Returns an optimized version of this query.
|
97
118
|
#
|
@@ -39,22 +39,33 @@ module SPARQL; module Algebra
|
|
39
39
|
# eval(D(G), Join(P1, P2)) = Join(eval(D(G), P1), eval(D(G), P2))
|
40
40
|
#
|
41
41
|
# Generate solutions independently, merge based on solution compatibility
|
42
|
-
debug(options) {"Join"}
|
42
|
+
debug(options) {"Join #{operands.to_sse}"}
|
43
43
|
|
44
44
|
left = queryable.query(operand(0), options.merge(depth: options[:depth].to_i + 1))
|
45
|
-
debug(options) {"(join)=>(left) #{left.
|
45
|
+
debug(options) {"(join)=>(left) #{left.map(&:to_hash).to_sse}"}
|
46
46
|
|
47
47
|
right = queryable.query(operand(1), options.merge(depth: options[:depth].to_i + 1))
|
48
|
-
debug(options) {"(join)=>(right) #{right.
|
48
|
+
debug(options) {"(join)=>(right) #{right.map(&:to_hash).to_sse}"}
|
49
49
|
|
50
50
|
@solutions = RDF::Query::Solutions(left.map do |s1|
|
51
51
|
right.map { |s2| s2.merge(s1) if s2.compatible?(s1) }
|
52
52
|
end.flatten.compact)
|
53
|
-
debug(options) {"=> #{@solutions.
|
53
|
+
debug(options) {"(join)=> #{@solutions.map(&:to_hash).to_sse}"}
|
54
54
|
@solutions.each(&block) if block_given?
|
55
55
|
@solutions
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
|
+
# The same blank node label cannot be used in two different basic graph patterns in the same query
|
59
|
+
def validate!
|
60
|
+
left_nodes, right_nodes = operand(0).ndvars, operand(1).ndvars
|
61
|
+
|
62
|
+
unless (left_nodes.compact & right_nodes.compact).empty?
|
63
|
+
raise ArgumentError,
|
64
|
+
"sub-operands share non-distinguished variables: #{(left_nodes.compact & right_nodes.compact).to_sse}"
|
65
|
+
end
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
58
69
|
##
|
59
70
|
# Returns an optimized version of this query.
|
60
71
|
#
|
@@ -70,7 +70,18 @@ module SPARQL; module Algebra
|
|
70
70
|
@solutions.each(&block) if block_given?
|
71
71
|
@solutions
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
|
+
# The same blank node label cannot be used in two different basic graph patterns in the same query
|
75
|
+
def validate!
|
76
|
+
left_nodes, right_nodes = operand(0).ndvars, operand(1).ndvars
|
77
|
+
|
78
|
+
unless (left_nodes.compact & right_nodes.compact).empty?
|
79
|
+
raise ArgumentError,
|
80
|
+
"sub-operands share non-distinguished variables: #{(left_nodes.compact & right_nodes.compact).to_sse}"
|
81
|
+
end
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
74
85
|
##
|
75
86
|
# Returns an optimized version of this query.
|
76
87
|
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `notoneof` (NegatedPropertySet) operator.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# (notoneof ex:p1 ex:p2)
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/sparql11-query/#eval_negatedPropertySet
|
10
|
+
class NotOneOf < Operator
|
11
|
+
include Query
|
12
|
+
|
13
|
+
NAME = :notoneof
|
14
|
+
|
15
|
+
##
|
16
|
+
# Equivalant to:
|
17
|
+
#
|
18
|
+
# (path (:x (noteoneof :p :q) :y))
|
19
|
+
# => (filter (notin ??p :p :q) (bgp (:x ??p :y)))
|
20
|
+
#
|
21
|
+
# @note all operands are terms, and not operators, so this can be done by filtering results usin
|
22
|
+
#
|
23
|
+
# @param [RDF::Queryable] queryable
|
24
|
+
# the graph or repository to query
|
25
|
+
# @param [Hash{Symbol => Object}] options
|
26
|
+
# any additional keyword options
|
27
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
28
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
29
|
+
# @yield [solution]
|
30
|
+
# each matching solution
|
31
|
+
# @yieldparam [RDF::Query::Solution] solution
|
32
|
+
# @yieldreturn [void] ignored
|
33
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
34
|
+
def execute(queryable, options = {}, &block)
|
35
|
+
debug(options) {"NotOneOf #{operands.to_sse}"}
|
36
|
+
subject, object = options[:subject], options[:object]
|
37
|
+
|
38
|
+
v = RDF::Query::Variable.new
|
39
|
+
v.distinguished = false
|
40
|
+
bgp = RDF::Query.new do |q|
|
41
|
+
q.pattern [subject, v, object]
|
42
|
+
end
|
43
|
+
query = Filter.new(NotIn.new(v, *operands), bgp)
|
44
|
+
queryable.query(query, options.merge(depth: options[:depth].to_i + 1)) do |solution|
|
45
|
+
solution.bindings.delete(v.to_sym)
|
46
|
+
debug(options) {"(solution)-> #{solution.to_hash.to_sse}"}
|
47
|
+
block.call(solution)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end # NotOneOf
|
51
|
+
end # Operator
|
52
|
+
end; end # SPARQL::Algebra
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `path` operator.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# (path :a (path+ :p) ?z)
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/sparql11-query/#sparqlTranslatePathExpressions
|
10
|
+
class Path < Operator::Ternary
|
11
|
+
include Query
|
12
|
+
|
13
|
+
NAME = :path
|
14
|
+
|
15
|
+
##
|
16
|
+
# Finds solutions from `queryable` matching the path.
|
17
|
+
#
|
18
|
+
# @param [RDF::Queryable] queryable
|
19
|
+
# the graph or repository to query
|
20
|
+
# @param [Hash{Symbol => Object}] options
|
21
|
+
# any additional keyword options
|
22
|
+
# @yield [solution]
|
23
|
+
# each matching solution
|
24
|
+
# @yieldparam [RDF::Query::Solution] solution
|
25
|
+
# @yieldreturn [void] ignored
|
26
|
+
# @return [RDF::Query::Solutions]
|
27
|
+
# the resulting solution sequence
|
28
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
29
|
+
def execute(queryable, options = {}, &block)
|
30
|
+
debug(options) {"Path #{operands.to_sse}"}
|
31
|
+
subject, path_op, object = operands
|
32
|
+
|
33
|
+
@solutions = RDF::Query::Solutions.new
|
34
|
+
path_op.execute(queryable, options.merge(
|
35
|
+
subject: subject,
|
36
|
+
object: object,
|
37
|
+
context: options.fetch(:context, false),
|
38
|
+
depth: options[:depth].to_i + 1)
|
39
|
+
) do |solution|
|
40
|
+
@solutions << solution
|
41
|
+
end
|
42
|
+
debug(options) {"=> #{@solutions.inspect}"}
|
43
|
+
@solutions.uniq!
|
44
|
+
@solutions.each(&block) if block_given?
|
45
|
+
@solutions
|
46
|
+
end
|
47
|
+
end # Path
|
48
|
+
end # Operator
|
49
|
+
end; end # SPARQL::Algebra
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `path?` (ZeroOrOnePath) operator.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# (path? :p)
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrOnePath
|
10
|
+
class PathOpt < Operator::Unary
|
11
|
+
include Query
|
12
|
+
|
13
|
+
NAME = :path?
|
14
|
+
|
15
|
+
##
|
16
|
+
# Equivalent to:
|
17
|
+
#
|
18
|
+
# (path x (path? :p) y)
|
19
|
+
# => (union (bgp ((x :p y))) (filter (x = x) (solution x y)))
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# @param [RDF::Queryable] queryable
|
23
|
+
# the graph or repository to query
|
24
|
+
# @param [Hash{Symbol => Object}] options
|
25
|
+
# any additional keyword options
|
26
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
27
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
28
|
+
# @yield [solution]
|
29
|
+
# each matching solution
|
30
|
+
# @yieldparam [RDF::Query::Solution] solution
|
31
|
+
# @yieldreturn [void] ignored
|
32
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
33
|
+
def execute(queryable, options = {}, &block)
|
34
|
+
subject, object = options[:subject], options[:object]
|
35
|
+
debug(options) {"Path? #{[subject, operands, object].to_sse}"}
|
36
|
+
|
37
|
+
solutions = RDF::Query::Solutions.new
|
38
|
+
# Solutions where subject == object with no predicate
|
39
|
+
case
|
40
|
+
when subject.variable? && object.variable?
|
41
|
+
# Nodes is the set of all subjects and objects in queryable
|
42
|
+
# FIXME: should this be Queryable#enum_nodes?
|
43
|
+
# All subjects which are `object`
|
44
|
+
query = RDF::Query.new {|q| q.pattern(subject: subject)}
|
45
|
+
queryable.query(query, options) do |solution|
|
46
|
+
solution.merge!(object.to_sym => solution[subject])
|
47
|
+
debug(options) {"(solution-s0)-> #{solution.to_hash.to_sse}"}
|
48
|
+
solutions << solution
|
49
|
+
end if query.valid?
|
50
|
+
|
51
|
+
# All objects which are `object`
|
52
|
+
query = RDF::Query.new {|q| q.pattern(object: object)}
|
53
|
+
queryable.query(query, options) do |solution|
|
54
|
+
solution.merge!(subject.to_sym => solution[object])
|
55
|
+
debug(options) {"(solution-o0)-> #{solution.to_hash.to_sse}"}
|
56
|
+
solutions << solution
|
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
|
+
queryable.query(query, options) do |solution|
|
62
|
+
solution.merge!(subject.to_sym => object)
|
63
|
+
debug(options) {"(solution-s0)-> #{solution.to_hash.to_sse}"}
|
64
|
+
solutions << solution
|
65
|
+
end if query.valid?
|
66
|
+
|
67
|
+
# All objects which are `object`
|
68
|
+
query = RDF::Query.new {|q| q.pattern(object: object)}
|
69
|
+
queryable.query(query, options) do |solution|
|
70
|
+
solution.merge!(subject.to_sym => object)
|
71
|
+
debug(options) {"(solution-o0)-> #{solution.to_hash.to_sse}"}
|
72
|
+
solutions << solution
|
73
|
+
end if query.valid?
|
74
|
+
when object.variable?
|
75
|
+
# All subjects which are `subject`
|
76
|
+
query = RDF::Query.new {|q| q.pattern(subject: subject)}
|
77
|
+
queryable.query(query, options) do |solution|
|
78
|
+
solution.merge!(object.to_sym => subject)
|
79
|
+
debug(options) {"(solution-s0)-> #{solution.to_hash.to_sse}"}
|
80
|
+
solutions << solution
|
81
|
+
end if query.valid?
|
82
|
+
|
83
|
+
# All objects which are `subject
|
84
|
+
query = RDF::Query.new {|q| q.pattern(object: subject)}
|
85
|
+
queryable.query(query, options) do |solution|
|
86
|
+
solution.merge!(object.to_sym => subject)
|
87
|
+
debug(options) {"(solution-o0)-> #{solution.to_hash.to_sse}"}
|
88
|
+
solutions << solution
|
89
|
+
end if query.valid?
|
90
|
+
else
|
91
|
+
# Otherwise, if subject == object, an empty solution
|
92
|
+
solutions << RDF::Query::Solution.new if subject == object
|
93
|
+
end
|
94
|
+
|
95
|
+
# Solutions where predicate exists
|
96
|
+
query = if operand.is_a?(RDF::Term)
|
97
|
+
RDF::Query.new do |q|
|
98
|
+
q.pattern [subject, operand, object]
|
99
|
+
end
|
100
|
+
else
|
101
|
+
operand
|
102
|
+
end
|
103
|
+
|
104
|
+
# Recurse into query
|
105
|
+
solutions +=
|
106
|
+
queryable.query(query, options.merge(depth: options[:depth].to_i + 1))
|
107
|
+
solutions.each(&block) if block_given?
|
108
|
+
solutions
|
109
|
+
end
|
110
|
+
end # PathOpt
|
111
|
+
end # Operator
|
112
|
+
end; end # SPARQL::Algebra
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `path+` (OneOrMorePath) operator.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# (path+ :p)
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_OneOrMorePath
|
10
|
+
class PathPlus < Operator::Unary
|
11
|
+
include Query
|
12
|
+
|
13
|
+
NAME = :"path+"
|
14
|
+
|
15
|
+
##
|
16
|
+
# Match on simple relation of subject to object, and then recurse on solutions
|
17
|
+
#
|
18
|
+
# @param [RDF::Queryable] queryable
|
19
|
+
# the graph or repository to query
|
20
|
+
# @param [Hash{Symbol => Object}] options
|
21
|
+
# any additional keyword options
|
22
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
23
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
24
|
+
# @yield [solution]
|
25
|
+
# each matching solution
|
26
|
+
# @yieldparam [RDF::Query::Solution] solution
|
27
|
+
# @yieldreturn [void] ignored
|
28
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
29
|
+
def execute(queryable, options = {}, &block)
|
30
|
+
subject, object = options[:subject], options[:object]
|
31
|
+
debug(options) {"Path+ #{[subject, operands, object].to_sse}"}
|
32
|
+
|
33
|
+
# This turns from
|
34
|
+
# (:x :p+ ?y)
|
35
|
+
# into
|
36
|
+
# (:x :p ?y) UNION solutions(:x :p ?y) do |soln|
|
37
|
+
# if :x.variable?
|
38
|
+
# (soln[:x] :p+ soln[:y])
|
39
|
+
# end
|
40
|
+
|
41
|
+
# Solutions where predicate exists
|
42
|
+
query = if operand.is_a?(RDF::Term)
|
43
|
+
RDF::Query.new do |q|
|
44
|
+
q.pattern [subject, operand, object]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
operand
|
48
|
+
end
|
49
|
+
|
50
|
+
# Block is given on the first invocation, otherwise, results are returned. This is necessary to stop when an existing solution has already been found
|
51
|
+
cumulative_solutions = options.fetch(:accumulator, RDF::Query::Solutions.new)
|
52
|
+
|
53
|
+
# Keep track of solutions
|
54
|
+
# Recurse into query
|
55
|
+
immediate_solutions = []
|
56
|
+
queryable.query(query, options.merge(depth: options[:depth].to_i + 1)) do |solution|
|
57
|
+
immediate_solutions << solution
|
58
|
+
end
|
59
|
+
|
60
|
+
# For all solutions, if they are not in the accumulator, add them and recurse, otherwise skip
|
61
|
+
recursive_solutions = RDF::Query::Solutions.new
|
62
|
+
immediate_solutions.reject {|s| cumulative_solutions.include?(s)}.each do |solution|
|
63
|
+
debug(options) {"(immediate solution)-> #{solution.to_hash.to_sse}"}
|
64
|
+
|
65
|
+
# Recurse on subject, if is a variable
|
66
|
+
case
|
67
|
+
when subject.variable? && object.variable?
|
68
|
+
# Query starting with bound object as subject, but replace result with subject
|
69
|
+
rs = queryable.query(self, options.merge(
|
70
|
+
subject: solution[object],
|
71
|
+
accumulator: (cumulative_solutions + immediate_solutions),
|
72
|
+
depth: options[:depth].to_i + 1)).map {|s| s.merge(subject.to_sym => solution[subject])}
|
73
|
+
# Query starting with bound subject as object, but replace result with subject
|
74
|
+
ro = queryable.query(self, options.merge(
|
75
|
+
object: solution[subject],
|
76
|
+
accumulator: (cumulative_solutions + immediate_solutions),
|
77
|
+
depth: options[:depth].to_i + 1)).map {|s| s.merge(object.to_sym => solution[object])}
|
78
|
+
recursive_solutions += (rs + ro).uniq
|
79
|
+
when subject.variable?
|
80
|
+
recursive_solutions += queryable.query(self, options.merge(
|
81
|
+
object: solution[subject],
|
82
|
+
accumulator: (cumulative_solutions + immediate_solutions),
|
83
|
+
depth: options[:depth].to_i + 1)).uniq
|
84
|
+
when object.variable?
|
85
|
+
recursive_solutions += queryable.query(self, options.merge(
|
86
|
+
subject: solution[object],
|
87
|
+
accumulator: (cumulative_solutions + immediate_solutions),
|
88
|
+
depth: options[:depth].to_i + 1)).uniq
|
89
|
+
end
|
90
|
+
end
|
91
|
+
debug(options) {"(recursive solutions)-> #{recursive_solutions.map(&:to_hash).to_sse}"} unless recursive_solutions.empty?
|
92
|
+
|
93
|
+
solutions = (immediate_solutions + recursive_solutions).uniq
|
94
|
+
solutions.each(&block) if block_given? # Only at top-level
|
95
|
+
solutions
|
96
|
+
end
|
97
|
+
end # PathPlus
|
98
|
+
end # Operator
|
99
|
+
end; end # SPARQL::Algebra
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `path*` (ZeroOrMorePath) operator.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# (path* :p)
|
8
|
+
#
|
9
|
+
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrMorePath
|
10
|
+
class PathStar < Operator::Unary
|
11
|
+
include Query
|
12
|
+
|
13
|
+
NAME = :"path*"
|
14
|
+
|
15
|
+
##
|
16
|
+
# Path including zero length:
|
17
|
+
#
|
18
|
+
# (path :a (path* :p) :b)
|
19
|
+
# => (path :a (path? (path+ :p)) :b)
|
20
|
+
#
|
21
|
+
# @param [RDF::Queryable] queryable
|
22
|
+
# the graph or repository to query
|
23
|
+
# @param [Hash{Symbol => Object}] options
|
24
|
+
# any additional keyword options
|
25
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
26
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
27
|
+
# @yield [solution]
|
28
|
+
# each matching solution
|
29
|
+
# @yieldparam [RDF::Query::Solution] solution
|
30
|
+
# @yieldreturn [void] ignored
|
31
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
32
|
+
def execute(queryable, options = {}, &block)
|
33
|
+
subject, object = options[:subject], options[:object]
|
34
|
+
debug(options) {"Path* #{[subject, operands, object].to_sse}"}
|
35
|
+
|
36
|
+
# (:x :p* :y) => (:x (:p+)? :y)
|
37
|
+
query = PathOpt.new(PathPlus.new(*operands))
|
38
|
+
query.execute(queryable, options.merge(depth: options[:depth].to_i + 1), &block)
|
39
|
+
end
|
40
|
+
end # PathStar
|
41
|
+
end # Operator
|
42
|
+
end; end # SPARQL::Algebra
|