rdf 0.2.3 → 0.3.0.pre
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.
- 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
|