rdf 0.2.3 → 0.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/AUTHORS +1 -0
  2. data/{CONTRIBUTORS → CREDITS} +3 -1
  3. data/README +17 -8
  4. data/VERSION +1 -1
  5. data/etc/doap.nt +28 -10
  6. data/lib/rdf/format.rb +55 -47
  7. data/lib/rdf/mixin/countable.rb +3 -6
  8. data/lib/rdf/mixin/enumerable.rb +69 -69
  9. data/lib/rdf/mixin/indexable.rb +26 -0
  10. data/lib/rdf/mixin/mutable.rb +2 -2
  11. data/lib/rdf/mixin/queryable.rb +50 -12
  12. data/lib/rdf/mixin/writable.rb +8 -19
  13. data/lib/rdf/model/literal/boolean.rb +42 -6
  14. data/lib/rdf/model/literal/date.rb +17 -5
  15. data/lib/rdf/model/literal/datetime.rb +18 -6
  16. data/lib/rdf/model/literal/decimal.rb +32 -5
  17. data/lib/rdf/model/literal/double.rb +32 -5
  18. data/lib/rdf/model/literal/integer.rb +16 -5
  19. data/lib/rdf/model/literal/time.rb +6 -6
  20. data/lib/rdf/model/literal/token.rb +5 -5
  21. data/lib/rdf/model/literal/xml.rb +5 -5
  22. data/lib/rdf/model/literal.rb +24 -11
  23. data/lib/rdf/model/node.rb +14 -13
  24. data/lib/rdf/model/uri.rb +315 -42
  25. data/lib/rdf/model/value.rb +1 -1
  26. data/lib/rdf/ntriples/reader.rb +23 -15
  27. data/lib/rdf/ntriples/writer.rb +1 -1
  28. data/lib/rdf/query/pattern.rb +131 -15
  29. data/lib/rdf/query/solution.rb +94 -29
  30. data/lib/rdf/query/solutions.rb +202 -0
  31. data/lib/rdf/query/variable.rb +42 -18
  32. data/lib/rdf/query.rb +210 -160
  33. data/lib/rdf/reader.rb +300 -112
  34. data/lib/rdf/repository.rb +88 -6
  35. data/lib/rdf/transaction.rb +161 -0
  36. data/lib/rdf/util/cache.rb +5 -0
  37. data/lib/rdf/util/file.rb +31 -0
  38. data/lib/rdf/util/uuid.rb +36 -0
  39. data/lib/rdf/util.rb +2 -0
  40. data/lib/rdf/version.rb +3 -3
  41. data/lib/rdf/vocab.rb +43 -35
  42. data/lib/rdf/writer.rb +105 -50
  43. data/lib/rdf.rb +29 -27
  44. metadata +26 -17
@@ -5,12 +5,12 @@ module RDF; class Query
5
5
  ##
6
6
  # @private
7
7
  # @since 0.2.2
8
- def self.from(pattern)
8
+ def self.from(pattern, options = {})
9
9
  case pattern
10
10
  when Pattern then pattern
11
- when Statement then self.new(pattern.to_hash)
12
- when Hash then self.new(pattern)
13
- when Array then self.new(*pattern)
11
+ when Statement then self.new(options.merge(pattern.to_hash))
12
+ when Hash then self.new(options.merge(pattern))
13
+ when Array then self.new(pattern[0], pattern[1], pattern[2], options.merge(:context => pattern[3]))
14
14
  else raise ArgumentError.new("expected RDF::Query::Pattern, RDF::Statement, Hash, or Array, but got #{pattern.inspect}")
15
15
  end
16
16
  end
@@ -49,36 +49,151 @@ module RDF; class Query
49
49
  end
50
50
 
51
51
  ##
52
- # @param [Graph, Repository] graph
52
+ # Returns `true` if this is an optional pattern.
53
+ #
54
+ # @example
55
+ # Pattern.new(:s, :p, :o).optional? #=> false
56
+ # Pattern.new(:s, :p, :o, :optional => true).optional? #=> true
57
+ #
58
+ # @return [Boolean]
59
+ # @since 0.3.0
60
+ def optional?
61
+ !!options[:optional]
62
+ end
63
+
64
+ ##
65
+ # Executes this query pattern on the given `queryable` object.
66
+ #
67
+ # By default any variable terms in this pattern will be treated as `nil`
68
+ # wildcards when executing the query. If the optional `bindings` are
69
+ # given, variables will be substituted with their values when executing
70
+ # the query.
71
+ #
72
+ # @example
73
+ # Pattern.new(:s, :p, :o).execute(RDF::Repository.load('data.nt'))
74
+ #
75
+ # @param [RDF::Queryable] queryable
76
+ # the graph or repository to query
77
+ # @param [Hash{Symbol => RDF::Value}] bindings
78
+ # optional variable bindings to use
79
+ # @yield [statement]
80
+ # each matching statement
81
+ # @yieldparam [RDF::Statement] statement
82
+ # an RDF statement matching this pattern
53
83
  # @return [Enumerator]
54
- def execute(graph, &block)
55
- graph.query(self) # FIXME
84
+ # an enumerator yielding matching statements
85
+ # @see RDF::Queryable#query
86
+ def execute(queryable, bindings = {}, &block)
87
+ variables = self.variables
88
+
89
+ # Does this pattern contain any variables?
90
+ if variables.empty?
91
+ # With no variables to worry about, we will let the repository
92
+ # implementation yield matching statements directly:
93
+ queryable.query(self, &block)
94
+
95
+ # Yes, this pattern uses at least one variable...
96
+ else
97
+ query = {
98
+ :subject => subject && subject.variable? ? bindings[subject.to_sym] : subject,
99
+ :predicate => predicate && predicate.variable? ? bindings[predicate.to_sym] : predicate,
100
+ :object => object && object.variable? ? bindings[object.to_sym] : object,
101
+ # TODO: context handling?
102
+ }
103
+
104
+ # Do all the variable terms refer to distinct variables?
105
+ if variable_count == variables.size
106
+ # If so, we can just let the repository implementation handle
107
+ # everything and yield matching statements directly:
108
+ queryable.query(query, &block)
109
+
110
+ # No, some terms actually refer to the same variable...
111
+ else
112
+ # Figure out which terms refer to the same variable:
113
+ terms = variables.each_key.find do |name|
114
+ terms = variable_terms(name)
115
+ break terms if terms.size > 1
116
+ end
117
+ queryable.query(query) do |statement|
118
+ # Only yield those matching statements where the variable
119
+ # constraint is also satisfied:
120
+ # FIXME: `Array#uniq` uses `#eql?` and `#hash`, not `#==`
121
+ if matches = terms.map { |term| statement.send(term) }.uniq.size.equal?(1)
122
+ block.call(statement)
123
+ end
124
+ end
125
+ end
126
+ end
56
127
  end
57
128
 
58
129
  ##
59
- # Returns `true` if this pattern contains variables.
130
+ # Returns a query solution constructed by binding any variables in this
131
+ # pattern with the corresponding terms in the given `statement`.
60
132
  #
61
- # @return [Boolean]
133
+ # @example
134
+ # pattern.solution(statement)
135
+ #
136
+ # @param [RDF::Statement] statement
137
+ # an RDF statement to bind terms from
138
+ # @return [RDF::Query::Solution]
139
+ def solution(statement)
140
+ RDF::Query::Solution.new do |solution|
141
+ solution[subject.to_sym] = statement.subject if subject.variable?
142
+ solution[predicate.to_sym] = statement.predicate if predicate.variable?
143
+ solution[object.to_sym] = statement.object if object.variable?
144
+ end
145
+ end
146
+
147
+ ##
148
+ # Returns `true` if this pattern contains any variables.
149
+ #
150
+ # @return [Boolean] `true` or `false`
62
151
  def variables?
63
152
  subject.is_a?(Variable) ||
64
153
  predicate.is_a?(Variable) ||
65
154
  object.is_a?(Variable)
66
155
  end
67
156
 
157
+ ##
158
+ # Returns the variable terms in this pattern.
159
+ #
160
+ # @example
161
+ # Pattern.new(RDF::Node.new, :p, 123).variable_terms #=> [:predicate]
162
+ #
163
+ # @param [Symbol, #to_sym] name
164
+ # an optional variable name
165
+ # @return [Array<Symbol>]
166
+ # @since 0.3.0
167
+ def variable_terms(name = nil)
168
+ terms = []
169
+ terms << :subject if subject.is_a?(Variable) && (!name || name.eql?(subject.name))
170
+ terms << :predicate if predicate.is_a?(Variable) && (!name || name.eql?(predicate.name))
171
+ terms << :object if object.is_a?(Variable) && (!name || name.eql?(object.name))
172
+ terms
173
+ end
174
+
68
175
  ##
69
176
  # Returns the number of variables in this pattern.
70
177
  #
178
+ # Note: this does not count distinct variables, and will therefore e.g.
179
+ # return 3 even if two terms are actually the same variable.
180
+ #
71
181
  # @return [Integer] (0..3)
72
182
  def variable_count
73
- variables.size
183
+ count = 0
184
+ count += 1 if subject.is_a?(Variable)
185
+ count += 1 if predicate.is_a?(Variable)
186
+ count += 1 if object.is_a?(Variable)
187
+ count
74
188
  end
75
-
76
189
  alias_method :cardinality, :variable_count
77
190
  alias_method :arity, :variable_count
78
191
 
79
192
  ##
80
193
  # Returns all variables in this pattern.
81
194
  #
195
+ # Note: this returns a hash containing distinct variables only.
196
+ #
82
197
  # @return [Hash{Symbol => Variable}]
83
198
  def variables
84
199
  variables = {}
@@ -91,7 +206,7 @@ module RDF; class Query
91
206
  ##
92
207
  # Returns `true` if this pattern contains bindings.
93
208
  #
94
- # @return [Boolean]
209
+ # @return [Boolean] `true` or `false`
95
210
  def bindings?
96
211
  !bindings.empty?
97
212
  end
@@ -149,16 +264,17 @@ module RDF; class Query
149
264
  end
150
265
 
151
266
  ##
152
- # Returns the string representation of this pattern.
267
+ # Returns a string representation of this pattern.
153
268
  #
154
269
  # @return [String]
155
270
  def to_s
156
271
  StringIO.open do |buffer| # FIXME in RDF::Statement
272
+ buffer << 'OPTIONAL ' if optional?
157
273
  buffer << (subject.is_a?(Variable) ? subject.to_s : "<#{subject}>") << ' '
158
274
  buffer << (predicate.is_a?(Variable) ? predicate.to_s : "<#{predicate}>") << ' '
159
275
  buffer << (object.is_a?(Variable) ? object.to_s : "<#{object}>") << ' .'
160
276
  buffer.string
161
277
  end
162
278
  end
163
- end # class Pattern
164
- end; end # module RDF class Query
279
+ end # Pattern
280
+ end; end # RDF::Query
@@ -22,50 +22,76 @@ class RDF::Query
22
22
  #
23
23
  class Solution
24
24
  # Undefine all superfluous instance methods:
25
- undef_method(*(instance_methods.map(&:to_sym) - [:__id__, :__send__, :__class__, :__eval__, :object_id, :instance_eval, :inspect, :class, :is_a?]))
25
+ undef_method(*(instance_methods.map(&:to_sym) - [:__id__, :__send__, :__class__, :__eval__,
26
+ :object_id, :dup, :instance_eval, :inspect, :to_s,
27
+ :class, :is_a?, :respond_to?, :respond_to_missing?]))
26
28
 
27
29
  include Enumerable
28
30
 
29
31
  ##
30
- # @param [Hash{Symbol => Value}] bindings
31
- def initialize(bindings = {})
32
+ # Initializes the query solution.
33
+ #
34
+ # @param [Hash{Symbol => RDF::Value}] bindings
35
+ # @yield [solution]
36
+ def initialize(bindings = {}, &block)
32
37
  @bindings = bindings.to_hash
38
+
39
+ if block_given?
40
+ case block.arity
41
+ when 1 then block.call(self)
42
+ else instance_eval(&block)
43
+ end
44
+ end
33
45
  end
34
46
 
47
+ # @private
48
+ attr_reader :bindings
49
+
35
50
  ##
36
51
  # Enumerates over every variable binding in this solution.
37
52
  #
38
53
  # @yield [name, value]
39
- # @yieldparam [Symbol, Value]
54
+ # @yieldparam [Symbol] name
55
+ # @yieldparam [RDF::Value] value
40
56
  # @return [Enumerator]
41
57
  def each_binding(&block)
42
58
  @bindings.each(&block)
43
59
  end
44
-
45
60
  alias_method :each, :each_binding
46
61
 
47
62
  ##
48
63
  # Enumerates over every variable name in this solution.
49
64
  #
50
65
  # @yield [name]
51
- # @yieldparam [Symbol]
66
+ # @yieldparam [Symbol] name
52
67
  # @return [Enumerator]
53
68
  def each_name(&block)
54
69
  @bindings.each_key(&block)
55
70
  end
56
-
57
71
  alias_method :each_key, :each_name
58
72
 
59
73
  ##
60
74
  # Enumerates over every variable value in this solution.
61
75
  #
62
76
  # @yield [value]
63
- # @yieldparam [Value]
77
+ # @yieldparam [RDF::Value] value
64
78
  # @return [Enumerator]
65
79
  def each_value(&block)
66
80
  @bindings.each_value(&block)
67
81
  end
68
82
 
83
+ ##
84
+ # Returns `true` if this solution contains bindings for any of the given
85
+ # `variables`.
86
+ #
87
+ # @param [Array<Symbol, #to_sym>] variables
88
+ # an array of variables to check
89
+ # @return [Boolean] `true` or `false`
90
+ # @since 0.3.0
91
+ def has_variables?(variables)
92
+ variables.any? { |variable| bound?(variable) }
93
+ end
94
+
69
95
  ##
70
96
  # Enumerates over every variable in this solution.
71
97
  #
@@ -81,8 +107,9 @@ class RDF::Query
81
107
  ##
82
108
  # Returns `true` if the variable `name` is bound in this solution.
83
109
  #
84
- # @param [Symbol] name
85
- # @return [Boolean]
110
+ # @param [Symbol, #to_sym] name
111
+ # the variable name
112
+ # @return [Boolean] `true` or `false`
86
113
  def bound?(name)
87
114
  !unbound?(name)
88
115
  end
@@ -90,8 +117,9 @@ class RDF::Query
90
117
  ##
91
118
  # Returns `true` if the variable `name` is unbound in this solution.
92
119
  #
93
- # @param [Symbol] name
94
- # @return [Boolean]
120
+ # @param [Symbol, #to_sym] name
121
+ # the variable name
122
+ # @return [Boolean] `true` or `false`
95
123
  def unbound?(name)
96
124
  @bindings[name.to_sym].nil?
97
125
  end
@@ -99,20 +127,58 @@ class RDF::Query
99
127
  ##
100
128
  # Returns the value of the variable `name`.
101
129
  #
102
- # @param [Symbol] name
103
- # @return [Value]
130
+ # @param [Symbol, #to_sym] name
131
+ # the variable name
132
+ # @return [RDF::Value]
104
133
  def [](name)
105
134
  @bindings[name.to_sym]
106
135
  end
107
136
 
108
137
  ##
109
- # @return [Array<Array(Symbol, Value)>}
138
+ # Binds or rebinds the variable `name` to the given `value`.
139
+ #
140
+ # @param [Symbol, #to_sym] name
141
+ # the variable name
142
+ # @param [RDF::Value] value
143
+ # @return [RDF::Value]
144
+ # @since 0.3.0
145
+ def []=(name, value)
146
+ @bindings[name.to_sym] = value
147
+ end
148
+
149
+ ##
150
+ # Merges the bindings from the given `other` query solution into this
151
+ # one, overwriting any existing ones having the same name.
152
+ #
153
+ # @param [RDF::Query::Solution, #to_hash] other
154
+ # another query solution or hash bindings
155
+ # @return [void] self
156
+ # @since 0.3.0
157
+ def merge!(other)
158
+ @bindings.merge!(other.to_hash)
159
+ self
160
+ end
161
+
162
+ ##
163
+ # Merges the bindings from the given `other` query solution with a copy
164
+ # of this one.
165
+ #
166
+ # @param [RDF::Query::Solution, #to_hash] other
167
+ # another query solution or hash bindings
168
+ # @return [RDF::Query::Solution]
169
+ # @since 0.3.0
170
+ def merge(other)
171
+ self.class.new(@bindings.dup).merge!(other)
172
+ end
173
+
174
+ ##
175
+ # @return [Array<Array(Symbol, RDF::Value)>}
110
176
  def to_a
111
177
  @bindings.to_a
112
178
  end
113
179
 
114
180
  ##
115
- # @return [Hash{Symbol => Value}}
181
+ # @return [Hash{Symbol => RDF::Value}}
116
182
  def to_hash
117
183
  @bindings.dup
118
184
  end
@@ -123,18 +189,17 @@ class RDF::Query
123
189
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, @bindings.inspect)
124
190
  end
125
191
 
126
- protected
192
+ protected
127
193
 
128
- ##
129
- # @param [Symbol] name
130
- # @return [Value]
131
- def method_missing(name, *args, &block)
132
- if args.empty? && @bindings.has_key?(name.to_sym)
133
- @bindings[name.to_sym]
134
- else
135
- super
136
- end
194
+ ##
195
+ # @param [Symbol] name
196
+ # @return [RDF::Value]
197
+ def method_missing(name, *args, &block)
198
+ if args.empty? && @bindings.has_key?(name.to_sym)
199
+ @bindings[name.to_sym]
200
+ else
201
+ super # raises NoMethodError
137
202
  end
138
-
139
- end
140
- end
203
+ end
204
+ end # Solution
205
+ end # RDF::Query
@@ -0,0 +1,202 @@
1
+ module RDF; class Query
2
+ ##
3
+ # An RDF basic graph pattern (BGP) query solution sequence.
4
+ #
5
+ # @example Filtering solutions using a hash
6
+ # solutions.filter(:author => RDF::URI("http://ar.to/#self"))
7
+ # solutions.filter(:author => "Arto Bendiken")
8
+ # solutions.filter(:author => [RDF::URI("http://ar.to/#self"), "Arto Bendiken"])
9
+ # solutions.filter(:updated => RDF::Literal(Date.today))
10
+ #
11
+ # @example Filtering solutions using a block
12
+ # solutions.filter { |solution| solution.author.literal? }
13
+ # solutions.filter { |solution| solution.title =~ /^SPARQL/ }
14
+ # solutions.filter { |solution| solution.price < 30.5 }
15
+ # solutions.filter { |solution| solution.bound?(:date) }
16
+ # solutions.filter { |solution| solution.age.datatype == RDF::XSD.integer }
17
+ # solutions.filter { |solution| solution.name.language == :es }
18
+ #
19
+ # @example Reordering solutions based on a variable
20
+ # solutions.order_by(:updated)
21
+ # solutions.order_by(:updated, :created)
22
+ #
23
+ # @example Selecting particular variables only
24
+ # solutions.select(:title)
25
+ # solutions.select(:title, :description)
26
+ #
27
+ # @example Eliminating duplicate solutions
28
+ # solutions.distinct
29
+ #
30
+ # @example Limiting the number of solutions
31
+ # solutions.offset(20).limit(10)
32
+ #
33
+ # @example Counting the number of matching solutions
34
+ # solutions.count
35
+ # solutions.count { |solution| solution.price < 30.5 }
36
+ #
37
+ # @example Iterating over all found solutions
38
+ # solutions.each { |solution| puts solution.inspect }
39
+ #
40
+ # @since 0.3.0
41
+ class Solutions < Array
42
+ alias_method :each_solution, :each
43
+
44
+ ##
45
+ # Returns the number of matching query solutions.
46
+ #
47
+ # @overload count
48
+ # @return [Integer]
49
+ #
50
+ # @overload count { |solution| ... }
51
+ # @yield [solution]
52
+ # @yieldparam [RDF::Query::Solution] solution
53
+ # @yieldreturn [Boolean]
54
+ # @return [Integer]
55
+ #
56
+ # @return [Integer]
57
+ def count(&block)
58
+ super
59
+ end
60
+
61
+ ##
62
+ # Filters this solution sequence by the given `criteria`.
63
+ #
64
+ # @param [Hash{Symbol => Object}] criteria
65
+ # @yield [solution]
66
+ # @yieldparam [RDF::Query::Solution] solution
67
+ # @yieldreturn [Boolean]
68
+ # @return [void] `self`
69
+ def filter(criteria = {}, &block)
70
+ if block_given?
71
+ self.reject! do |solution|
72
+ !block.call(solution.is_a?(Solution) ? solution : Solution.new(solution))
73
+ end
74
+ else
75
+ self.reject! do |solution|
76
+ solution = solution.is_a?(Solution) ? solution : Solution.new(solution)
77
+ results = criteria.map do |name, value|
78
+ solution[name] == value
79
+ end
80
+ !results.all?
81
+ end
82
+ end
83
+ self
84
+ end
85
+ alias_method :filter!, :filter
86
+
87
+ ##
88
+ # Reorders this solution sequence by the given `variables`.
89
+ #
90
+ # @param [Array<Symbol, #to_sym>] variables
91
+ # @return [void] `self`
92
+ def order(*variables)
93
+ if variables.empty?
94
+ raise ArgumentError, "wrong number of arguments (0 for 1)"
95
+ else
96
+ # TODO: support for descending sort, e.g. `order(:s => :asc, :p => :desc)`
97
+ variables.map!(&:to_sym)
98
+ self.sort! do |a, b|
99
+ a = variables.map { |variable| a[variable].to_s } # FIXME
100
+ b = variables.map { |variable| b[variable].to_s } # FIXME
101
+ a <=> b
102
+ end
103
+ end
104
+ self
105
+ end
106
+ alias_method :order_by, :order
107
+
108
+ ##
109
+ # Restricts this solution sequence to the given `variables` only.
110
+ #
111
+ # @param [Array<Symbol, #to_sym>] variables
112
+ # @return [void] `self`
113
+ def project(*variables)
114
+ if variables.empty?
115
+ raise ArgumentError, "wrong number of arguments (0 for 1)"
116
+ else
117
+ variables.map!(&:to_sym)
118
+ self.each do |solution|
119
+ solution.bindings.delete_if { |k, v| !variables.include?(k.to_sym) }
120
+ end
121
+ end
122
+ self
123
+ end
124
+ alias_method :select, :project
125
+
126
+ ##
127
+ # Ensures that the solutions in this solution sequence are unique.
128
+ #
129
+ # @return [void] `self`
130
+ def distinct
131
+ self.uniq!
132
+ self
133
+ end
134
+ alias_method :distinct!, :distinct
135
+ alias_method :reduced, :distinct
136
+ alias_method :reduced!, :distinct
137
+
138
+ ##
139
+ # Limits this solution sequence to bindings starting from the `start`
140
+ # offset in the overall solution sequence.
141
+ #
142
+ # @param [Integer, #to_i] start
143
+ # zero or a positive or negative integer
144
+ # @return [void] `self`
145
+ def offset(start)
146
+ case start = start.to_i
147
+ when 0 then nil
148
+ else self.slice!(0...start)
149
+ end
150
+ self
151
+ end
152
+ alias_method :offset!, :offset
153
+
154
+ ##
155
+ # Limits the number of solutions in this solution sequence to a maximum
156
+ # of `length`.
157
+ #
158
+ # @param [Integer, #to_i] length
159
+ # zero or a positive integer
160
+ # @return [void] `self`
161
+ # @raise [ArgumentError] if `length` is negative
162
+ def limit(length)
163
+ length = length.to_i
164
+ raise ArgumentError, "expected zero or a positive integer, got #{length}" if length < 0
165
+ case length
166
+ when 0 then self.clear
167
+ else self.slice!(length..-1) if length < self.size
168
+ end
169
+ self
170
+ end
171
+ alias_method :limit!, :limit
172
+
173
+ ##
174
+ # Returns an array of the distinct variable names used in this solution
175
+ # sequence.
176
+ #
177
+ # @return [Array<Symbol>]
178
+ def variable_names
179
+ variables = self.inject({}) do |result, solution|
180
+ solution.each_name do |name|
181
+ result[name] ||= true
182
+ end
183
+ result
184
+ end
185
+ variables.keys
186
+ end
187
+
188
+ ##
189
+ # Returns `true` if this solution sequence contains bindings for any of
190
+ # the given `variables`.
191
+ #
192
+ # @param [Array<Symbol, #to_sym>] variables
193
+ # an array of variables to check
194
+ # @return [Boolean] `true` or `false`
195
+ # @see RDF::Query::Solution#has_variables?
196
+ # @see RDF::Query#execute
197
+ def have_variables?(variables)
198
+ self.any? { |solution| solution.has_variables?(variables) }
199
+ end
200
+ alias_method :has_variables?, :have_variables?
201
+ end # Solutions
202
+ end; end # RDF::Query