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.
- data/AUTHORS +1 -0
- data/{CONTRIBUTORS → CREDITS} +3 -1
- data/README +17 -8
- data/VERSION +1 -1
- data/etc/doap.nt +28 -10
- data/lib/rdf/format.rb +55 -47
- data/lib/rdf/mixin/countable.rb +3 -6
- data/lib/rdf/mixin/enumerable.rb +69 -69
- data/lib/rdf/mixin/indexable.rb +26 -0
- data/lib/rdf/mixin/mutable.rb +2 -2
- data/lib/rdf/mixin/queryable.rb +50 -12
- data/lib/rdf/mixin/writable.rb +8 -19
- data/lib/rdf/model/literal/boolean.rb +42 -6
- data/lib/rdf/model/literal/date.rb +17 -5
- data/lib/rdf/model/literal/datetime.rb +18 -6
- data/lib/rdf/model/literal/decimal.rb +32 -5
- data/lib/rdf/model/literal/double.rb +32 -5
- data/lib/rdf/model/literal/integer.rb +16 -5
- data/lib/rdf/model/literal/time.rb +6 -6
- data/lib/rdf/model/literal/token.rb +5 -5
- data/lib/rdf/model/literal/xml.rb +5 -5
- data/lib/rdf/model/literal.rb +24 -11
- data/lib/rdf/model/node.rb +14 -13
- data/lib/rdf/model/uri.rb +315 -42
- data/lib/rdf/model/value.rb +1 -1
- data/lib/rdf/ntriples/reader.rb +23 -15
- data/lib/rdf/ntriples/writer.rb +1 -1
- data/lib/rdf/query/pattern.rb +131 -15
- data/lib/rdf/query/solution.rb +94 -29
- data/lib/rdf/query/solutions.rb +202 -0
- data/lib/rdf/query/variable.rb +42 -18
- data/lib/rdf/query.rb +210 -160
- data/lib/rdf/reader.rb +300 -112
- data/lib/rdf/repository.rb +88 -6
- data/lib/rdf/transaction.rb +161 -0
- data/lib/rdf/util/cache.rb +5 -0
- data/lib/rdf/util/file.rb +31 -0
- data/lib/rdf/util/uuid.rb +36 -0
- data/lib/rdf/util.rb +2 -0
- data/lib/rdf/version.rb +3 -3
- data/lib/rdf/vocab.rb +43 -35
- data/lib/rdf/writer.rb +105 -50
- data/lib/rdf.rb +29 -27
- metadata +26 -17
data/lib/rdf/query/pattern.rb
CHANGED
@@ -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(
|
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
|
-
#
|
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
|
-
|
55
|
-
|
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
|
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
|
-
# @
|
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
|
-
|
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
|
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 #
|
164
|
-
end; end #
|
279
|
+
end # Pattern
|
280
|
+
end; end # RDF::Query
|
data/lib/rdf/query/solution.rb
CHANGED
@@ -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__,
|
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
|
-
#
|
31
|
-
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
192
|
+
protected
|
127
193
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|