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/variable.rb
CHANGED
@@ -45,17 +45,24 @@ class RDF::Query
|
|
45
45
|
class Variable
|
46
46
|
include RDF::Value
|
47
47
|
|
48
|
-
|
48
|
+
##
|
49
|
+
# The variable's name.
|
50
|
+
#
|
51
|
+
# @return [Symbol]
|
49
52
|
attr_accessor :name
|
50
|
-
|
51
53
|
alias_method :to_sym, :name
|
52
54
|
|
53
|
-
|
55
|
+
##
|
56
|
+
# The variable's value.
|
57
|
+
#
|
58
|
+
# @return [RDF::Value]
|
54
59
|
attr_accessor :value
|
55
60
|
|
56
61
|
##
|
57
|
-
# @param [Symbol] name
|
58
|
-
#
|
62
|
+
# @param [Symbol, #to_sym] name
|
63
|
+
# the variable name
|
64
|
+
# @param [RDF::Value] value
|
65
|
+
# an optional variable value
|
59
66
|
def initialize(name = nil, value = nil)
|
60
67
|
@name = (name || "g#{__id__.to_i.abs}").to_sym
|
61
68
|
@value = value
|
@@ -98,50 +105,67 @@ class RDF::Query
|
|
98
105
|
##
|
99
106
|
# Rebinds this variable to the given `value`.
|
100
107
|
#
|
101
|
-
# @param [Value] value
|
102
|
-
# @return [Value]
|
108
|
+
# @param [RDF::Value] value
|
109
|
+
# @return [RDF::Value] the previous value, if any.
|
103
110
|
def bind(value)
|
104
111
|
old_value = self.value
|
105
112
|
self.value = value
|
106
113
|
old_value
|
107
114
|
end
|
108
|
-
|
109
115
|
alias_method :bind!, :bind
|
110
116
|
|
111
117
|
##
|
112
118
|
# Unbinds this variable, discarding any currently bound value.
|
113
119
|
#
|
114
|
-
# @return [Value]
|
120
|
+
# @return [RDF::Value] the previous value, if any.
|
115
121
|
def unbind
|
116
122
|
old_value = self.value
|
117
123
|
self.value = nil
|
118
124
|
old_value
|
119
125
|
end
|
120
|
-
|
121
126
|
alias_method :unbind!, :unbind
|
122
127
|
|
123
128
|
##
|
124
129
|
# Returns this variable as `Hash`.
|
125
130
|
#
|
126
|
-
# @return [Hash{Symbol => Variable}]
|
131
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
127
132
|
def variables
|
128
|
-
{
|
133
|
+
{name => self}
|
129
134
|
end
|
130
|
-
|
131
135
|
alias_method :to_hash, :variables
|
132
136
|
|
133
137
|
##
|
134
138
|
# Returns this variable's bindings (if any) as a `Hash`.
|
135
139
|
#
|
136
|
-
# @return [Hash{Symbol => Value}]
|
140
|
+
# @return [Hash{Symbol => RDF::Value}]
|
137
141
|
def bindings
|
138
|
-
unbound? ? {} : {
|
142
|
+
unbound? ? {} : {name => value}
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Returns a hash code for this variable.
|
147
|
+
#
|
148
|
+
# @return [Fixnum]
|
149
|
+
# @since 0.3.0
|
150
|
+
def hash
|
151
|
+
@name.hash
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Returns `true` if this variable is equivalent to a given `other`
|
156
|
+
# variable.
|
157
|
+
#
|
158
|
+
# @param [Object] other
|
159
|
+
# @return [Boolean] `true` or `false`
|
160
|
+
# @since 0.3.0
|
161
|
+
def eql?(other)
|
162
|
+
other.is_a?(RDF::Query::Variable) && @name.eql?(other.name)
|
139
163
|
end
|
140
164
|
|
141
165
|
##
|
142
166
|
# Compares this variable with the given value.
|
143
167
|
#
|
144
|
-
# @param [Value] other
|
168
|
+
# @param [RDF::Value] other
|
145
169
|
# @return [Boolean]
|
146
170
|
def ===(other)
|
147
171
|
if unbound?
|
@@ -158,5 +182,5 @@ class RDF::Query
|
|
158
182
|
def to_s
|
159
183
|
unbound? ? "?#{name}" : "?#{name}=#{value}"
|
160
184
|
end
|
161
|
-
end
|
162
|
-
end
|
185
|
+
end # Variable
|
186
|
+
end # RDF::Query
|
data/lib/rdf/query.rb
CHANGED
@@ -1,224 +1,274 @@
|
|
1
1
|
module RDF
|
2
2
|
##
|
3
|
-
# An RDF basic graph pattern query.
|
3
|
+
# An RDF basic graph pattern (BGP) query.
|
4
4
|
#
|
5
|
-
# @example
|
6
|
-
# query
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
5
|
+
# @example Constructing a basic graph pattern query (1)
|
6
|
+
# query = RDF::Query.new do
|
7
|
+
# pattern [:person, RDF.type, FOAF.Person]
|
8
|
+
# pattern [:person, FOAF.name, :name]
|
9
|
+
# pattern [:person, FOAF.mbox, :email]
|
10
|
+
# end
|
10
11
|
#
|
11
|
-
# @example
|
12
|
-
# query
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
12
|
+
# @example Constructing a basic graph pattern query (2)
|
13
|
+
# query = RDF::Query.new({
|
14
|
+
# :person => {
|
15
|
+
# RDF.type => FOAF.Person,
|
16
|
+
# FOAF.name => :name,
|
17
|
+
# FOAF.mbox => :email,
|
18
|
+
# }
|
19
|
+
# })
|
18
20
|
#
|
19
|
-
# @example
|
20
|
-
#
|
21
|
-
# query.
|
21
|
+
# @example Executing a basic graph pattern query
|
22
|
+
# graph = RDF::Graph.load('etc/doap.nt')
|
23
|
+
# query.execute(graph).each do |solution|
|
24
|
+
# solution.inspect
|
25
|
+
# end
|
22
26
|
#
|
23
|
-
# @example
|
24
|
-
#
|
25
|
-
#
|
27
|
+
# @example Constructing and executing a query in one go (1)
|
28
|
+
# solutions = RDF::Query.execute(graph) do
|
29
|
+
# pattern [:person, RDF.type, FOAF.Person]
|
30
|
+
# end
|
26
31
|
#
|
27
|
-
# @example
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# @example Counting the number of solutions
|
34
|
-
# query.count
|
35
|
-
#
|
36
|
-
# @example Iterating over all found solutions
|
37
|
-
# query.each_solution { |solution| puts solution.inspect }
|
32
|
+
# @example Constructing and executing a query in one go (2)
|
33
|
+
# solutions = RDF::Query.execute(graph, {
|
34
|
+
# :person => {
|
35
|
+
# RDF.type => FOAF.Person,
|
36
|
+
# }
|
37
|
+
# })
|
38
38
|
#
|
39
|
+
# @since 0.3.0
|
39
40
|
class Query
|
40
|
-
autoload :Pattern,
|
41
|
-
autoload :Solution,
|
42
|
-
autoload :
|
41
|
+
autoload :Pattern, 'rdf/query/pattern'
|
42
|
+
autoload :Solution, 'rdf/query/solution'
|
43
|
+
autoload :Solutions, 'rdf/query/solutions'
|
44
|
+
autoload :Variable, 'rdf/query/variable'
|
43
45
|
|
44
|
-
|
46
|
+
##
|
47
|
+
# Executes a query on the given `queryable` graph or repository.
|
48
|
+
#
|
49
|
+
# @param [RDF::Queryable] queryable
|
50
|
+
# the graph or repository to query
|
51
|
+
# @param [Hash{Object => Object}] patterns
|
52
|
+
# optional hash patterns to initialize the query with
|
53
|
+
# @param [Hash{Symbol => Object}] options
|
54
|
+
# any additional keyword options (see {RDF::Query#initialize})
|
55
|
+
# @yield [query]
|
56
|
+
# @yieldparam [RDF::Query] query
|
57
|
+
# @yieldreturn [void] ignored
|
58
|
+
# @return [RDF::Query::Solutions]
|
59
|
+
# the resulting solution sequence
|
60
|
+
# @see RDF::Query#execute
|
61
|
+
def self.execute(queryable, patterns = nil, options = {}, &block)
|
62
|
+
self.new(patterns, options, &block).execute(queryable, options)
|
63
|
+
end
|
45
64
|
|
65
|
+
##
|
66
|
+
# The variables used in this query.
|
67
|
+
#
|
46
68
|
# @return [Hash{Symbol => RDF::Query::Variable}]
|
47
69
|
attr_reader :variables
|
48
70
|
|
71
|
+
##
|
72
|
+
# The patterns that constitute this query.
|
73
|
+
#
|
49
74
|
# @return [Array<RDF::Query::Pattern>]
|
50
75
|
attr_reader :patterns
|
51
76
|
|
52
|
-
|
53
|
-
|
77
|
+
##
|
78
|
+
# The solution sequence for this query.
|
79
|
+
#
|
80
|
+
# @return [RDF::Query::Solutions]
|
81
|
+
attr_reader :solutions
|
54
82
|
|
83
|
+
##
|
84
|
+
# Any additional options for this query.
|
85
|
+
#
|
55
86
|
# @return [Hash]
|
56
87
|
attr_reader :options
|
57
88
|
|
58
89
|
##
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# @
|
62
|
-
|
90
|
+
# Initializes a new basic graph pattern query.
|
91
|
+
#
|
92
|
+
# @overload initialize(patterns = [], options = {})
|
93
|
+
# @param [Array<RDF::Query::Pattern>] patterns
|
94
|
+
# ...
|
95
|
+
# @param [Hash{Symbol => Object}] options
|
96
|
+
# any additional keyword options
|
97
|
+
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
98
|
+
# @yield [query]
|
99
|
+
# @yieldparam [RDF::Query] query
|
100
|
+
# @yieldreturn [void] ignored
|
101
|
+
#
|
102
|
+
# @overload initialize(patterns, options = {})
|
103
|
+
# @param [Hash{Object => Object}] patterns
|
104
|
+
# ...
|
105
|
+
# @param [Hash{Symbol => Object}] options
|
106
|
+
# any additional keyword options
|
107
|
+
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
108
|
+
# @yield [query]
|
109
|
+
# @yieldparam [RDF::Query] query
|
110
|
+
# @yieldreturn [void] ignored
|
111
|
+
def initialize(patterns = nil, options = {}, &block)
|
63
112
|
@options = options.dup
|
64
|
-
@variables =
|
65
|
-
@
|
66
|
-
|
113
|
+
@variables = {}
|
114
|
+
@solutions = @options.delete(:solutions) || Solutions.new
|
115
|
+
|
116
|
+
@patterns = case patterns
|
117
|
+
when Hash then compile_hash_patterns(patterns.dup)
|
118
|
+
when Array then patterns
|
119
|
+
else []
|
120
|
+
end
|
67
121
|
|
68
122
|
if block_given?
|
69
123
|
case block.arity
|
70
|
-
when
|
71
|
-
else
|
124
|
+
when 0 then instance_eval(&block)
|
125
|
+
else block.call(self)
|
72
126
|
end
|
73
127
|
end
|
74
128
|
end
|
75
129
|
|
76
130
|
##
|
77
|
-
#
|
131
|
+
# Appends the given query `pattern` to this query.
|
78
132
|
#
|
79
|
-
# @
|
80
|
-
#
|
81
|
-
# @return [
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
else
|
86
|
-
solutions.each do |solution|
|
87
|
-
block.call(solution.is_a?(Solution) ? solution : Solution.new(solution))
|
88
|
-
end
|
89
|
-
end
|
133
|
+
# @param [RDF::Query::Pattern] pattern
|
134
|
+
# a triple query pattern
|
135
|
+
# @return [void] self
|
136
|
+
def <<(pattern)
|
137
|
+
@patterns << Pattern.from(pattern)
|
138
|
+
self
|
90
139
|
end
|
91
140
|
|
92
|
-
alias_method :each, :each_solution
|
93
|
-
|
94
141
|
##
|
95
|
-
#
|
142
|
+
# Appends the given query `pattern` to this query.
|
96
143
|
#
|
97
|
-
# @
|
98
|
-
|
99
|
-
|
144
|
+
# @param [RDF::Query::Pattern] pattern
|
145
|
+
# a triple query pattern
|
146
|
+
# @param [Hash{Symbol => Object}] options
|
147
|
+
# any additional keyword options
|
148
|
+
# @option options [Boolean] :optional (false)
|
149
|
+
# whether this is an optional pattern
|
150
|
+
# @return [void] self
|
151
|
+
def pattern(pattern, options = {})
|
152
|
+
@patterns << Pattern.from(pattern, options)
|
153
|
+
self
|
100
154
|
end
|
101
155
|
|
102
|
-
alias_method :size, :count
|
103
|
-
|
104
156
|
##
|
105
|
-
#
|
157
|
+
# Executes this query on the given `queryable` graph or repository.
|
106
158
|
#
|
107
|
-
# @param [
|
108
|
-
#
|
109
|
-
# @
|
110
|
-
#
|
111
|
-
# @return [Query]
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
self
|
127
|
-
end
|
159
|
+
# @param [RDF::Queryable] queryable
|
160
|
+
# the graph or repository to query
|
161
|
+
# @param [Hash{Symbol => Object}] options
|
162
|
+
# any additional keyword options
|
163
|
+
# @return [RDF::Query::Solutions]
|
164
|
+
# the resulting solution sequence
|
165
|
+
# @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
|
166
|
+
def execute(queryable, options = {})
|
167
|
+
@solutions = Solutions.new
|
168
|
+
@failed = false
|
169
|
+
@patterns.each do |pattern|
|
170
|
+
case pattern.variable_count
|
171
|
+
when 0 # no variables
|
172
|
+
if pattern.execute(queryable).empty?
|
173
|
+
# return an empty solution sequence:
|
174
|
+
@solutions.clear
|
175
|
+
@failed = true
|
176
|
+
break
|
177
|
+
end
|
128
178
|
|
129
|
-
|
179
|
+
when 3 # only variables
|
180
|
+
pattern.execute(queryable) do |statement|
|
181
|
+
@solutions << pattern.solution(statement)
|
182
|
+
end
|
130
183
|
|
131
|
-
|
132
|
-
# Reorders the solution sequence based on `variables`.
|
133
|
-
#
|
134
|
-
# @param [Array<Symbol>] variables
|
135
|
-
# @return [Query]
|
136
|
-
def order(*variables)
|
137
|
-
if variables.empty?
|
138
|
-
raise ArgumentError.new("wrong number of arguments (0 for 1)")
|
139
|
-
else
|
140
|
-
# TODO: support for descending sort, e.g. order(:s => :asc, :p => :desc)
|
141
|
-
variables.map!(&:to_sym)
|
142
|
-
solutions.sort! do |a, b|
|
143
|
-
a = variables.map { |variable| a[variable].to_s }
|
144
|
-
b = variables.map { |variable| b[variable].to_s }
|
145
|
-
a <=> b
|
146
|
-
end
|
147
|
-
end
|
148
|
-
self
|
149
|
-
end
|
184
|
+
else case # 1 or 2 variables
|
150
185
|
|
151
|
-
|
186
|
+
when !@solutions.have_variables?(pattern.variables.values)
|
187
|
+
if @solutions.empty?
|
188
|
+
pattern.execute(queryable) do |statement|
|
189
|
+
@solutions << pattern.solution(statement)
|
190
|
+
end
|
191
|
+
else # union
|
192
|
+
old_solutions, @solutions = @solutions, Solutions.new
|
193
|
+
old_solutions.each do |solution|
|
194
|
+
pattern.execute(queryable) do |statement|
|
195
|
+
@solutions << solution.merge(pattern.solution(statement))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
152
199
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
200
|
+
else # intersection
|
201
|
+
@solutions.each_with_index do |solution, index|
|
202
|
+
failed = true
|
203
|
+
pattern.execute(queryable, solution) do |statement|
|
204
|
+
failed = false
|
205
|
+
solution.merge!(pattern.solution(statement))
|
206
|
+
end
|
207
|
+
@solutions[index] = nil if failed && !pattern.optional?
|
208
|
+
end
|
209
|
+
@solutions.compact! # remove `nil` entries
|
210
|
+
end
|
163
211
|
end
|
164
212
|
end
|
165
|
-
|
213
|
+
@solutions
|
166
214
|
end
|
167
215
|
|
168
|
-
alias_method :select, :project
|
169
|
-
|
170
216
|
##
|
171
|
-
#
|
217
|
+
# Returns `true` if this query did not match when last executed.
|
172
218
|
#
|
173
|
-
#
|
174
|
-
|
175
|
-
|
176
|
-
|
219
|
+
# When the solution sequence is empty, this method can be used to
|
220
|
+
# determine whether the query failed to match or not.
|
221
|
+
#
|
222
|
+
# @return [Boolean]
|
223
|
+
# @see #matched?
|
224
|
+
def failed?
|
225
|
+
@failed
|
177
226
|
end
|
178
227
|
|
179
|
-
alias_method :distinct!, :distinct
|
180
|
-
alias_method :reduced, :distinct
|
181
|
-
alias_method :reduced!, :distinct
|
182
|
-
|
183
228
|
##
|
184
|
-
#
|
185
|
-
#
|
229
|
+
# Returns `true` if this query matched when last executed.
|
230
|
+
#
|
231
|
+
# When the solution sequence is empty, this method can be used to
|
232
|
+
# determine whether the query matched successfully or not.
|
186
233
|
#
|
187
|
-
# @
|
188
|
-
# @
|
189
|
-
def
|
190
|
-
|
234
|
+
# @return [Boolean]
|
235
|
+
# @see #failed?
|
236
|
+
def matched?
|
237
|
+
!@failed
|
191
238
|
end
|
192
239
|
|
193
|
-
alias_method :offset!, :offset
|
194
|
-
|
195
240
|
##
|
196
|
-
#
|
241
|
+
# Enumerates over each matching query solution.
|
197
242
|
#
|
198
|
-
# @
|
199
|
-
# @
|
200
|
-
|
201
|
-
|
243
|
+
# @yield [solution]
|
244
|
+
# @yieldparam [RDF::Query::Solution] solution
|
245
|
+
# @return [Enumerator]
|
246
|
+
def each_solution(&block)
|
247
|
+
@solutions.each(&block)
|
202
248
|
end
|
249
|
+
alias_method :each, :each_solution
|
203
250
|
|
204
|
-
|
251
|
+
protected
|
205
252
|
|
206
253
|
##
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
254
|
+
# @private
|
255
|
+
def compile_hash_patterns(hash_patterns)
|
256
|
+
patterns = []
|
257
|
+
hash_patterns.each do |s, pos|
|
258
|
+
raise ArgumentError, "invalid hash pattern: #{hash_patterns.inspect}" unless pos.is_a?(Hash)
|
259
|
+
pos.each do |p, os|
|
260
|
+
case os
|
261
|
+
when Hash
|
262
|
+
patterns += os.keys.map { |o| [s, p, o] }
|
263
|
+
patterns += compile_hash_patterns(os)
|
264
|
+
when Array
|
265
|
+
patterns += os.map { |o| [s, p, o] }
|
266
|
+
else
|
267
|
+
patterns << [s, p, os]
|
268
|
+
end
|
269
|
+
end
|
218
270
|
end
|
219
|
-
|
271
|
+
patterns.map { |pattern| Pattern.from(pattern) }
|
220
272
|
end
|
221
|
-
|
222
|
-
|
223
|
-
end
|
224
|
-
end
|
273
|
+
end # Query
|
274
|
+
end # RDF
|