sparql 1.1.6 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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