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.
@@ -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.inspect}"}
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.inspect}"}
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.inspect}"}
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
  #
@@ -6,7 +6,7 @@ module SPARQL; module Algebra
6
6
  # Used for filters with more than one expression.
7
7
  #
8
8
  # @example
9
- # (ask (filter (notin 2) (bgp)))
9
+ # (ask (filter (notin ?o 1 2) (bgp)))
10
10
  #
11
11
  # @see http://www.w3.org/TR/sparql11-query/#func-notin
12
12
  class NotIn < Operator
@@ -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