openlogic-rdf 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +3 -0
- data/CREDITS +9 -0
- data/README +361 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/bin/rdf +18 -0
- data/etc/doap.nt +62 -0
- data/lib/df.rb +1 -0
- data/lib/rdf/cli.rb +200 -0
- data/lib/rdf/format.rb +383 -0
- data/lib/rdf/mixin/countable.rb +39 -0
- data/lib/rdf/mixin/durable.rb +31 -0
- data/lib/rdf/mixin/enumerable.rb +637 -0
- data/lib/rdf/mixin/indexable.rb +26 -0
- data/lib/rdf/mixin/inferable.rb +5 -0
- data/lib/rdf/mixin/mutable.rb +191 -0
- data/lib/rdf/mixin/queryable.rb +265 -0
- data/lib/rdf/mixin/readable.rb +15 -0
- data/lib/rdf/mixin/type_check.rb +21 -0
- data/lib/rdf/mixin/writable.rb +152 -0
- data/lib/rdf/model/graph.rb +263 -0
- data/lib/rdf/model/list.rb +731 -0
- data/lib/rdf/model/literal/boolean.rb +121 -0
- data/lib/rdf/model/literal/date.rb +73 -0
- data/lib/rdf/model/literal/datetime.rb +72 -0
- data/lib/rdf/model/literal/decimal.rb +86 -0
- data/lib/rdf/model/literal/double.rb +189 -0
- data/lib/rdf/model/literal/integer.rb +126 -0
- data/lib/rdf/model/literal/numeric.rb +184 -0
- data/lib/rdf/model/literal/time.rb +87 -0
- data/lib/rdf/model/literal/token.rb +47 -0
- data/lib/rdf/model/literal/xml.rb +39 -0
- data/lib/rdf/model/literal.rb +373 -0
- data/lib/rdf/model/node.rb +156 -0
- data/lib/rdf/model/resource.rb +28 -0
- data/lib/rdf/model/statement.rb +296 -0
- data/lib/rdf/model/term.rb +77 -0
- data/lib/rdf/model/uri.rb +570 -0
- data/lib/rdf/model/value.rb +133 -0
- data/lib/rdf/nquads.rb +152 -0
- data/lib/rdf/ntriples/format.rb +48 -0
- data/lib/rdf/ntriples/reader.rb +239 -0
- data/lib/rdf/ntriples/writer.rb +219 -0
- data/lib/rdf/ntriples.rb +104 -0
- data/lib/rdf/query/pattern.rb +329 -0
- data/lib/rdf/query/solution.rb +252 -0
- data/lib/rdf/query/solutions.rb +237 -0
- data/lib/rdf/query/variable.rb +221 -0
- data/lib/rdf/query.rb +404 -0
- data/lib/rdf/reader.rb +511 -0
- data/lib/rdf/repository.rb +389 -0
- data/lib/rdf/transaction.rb +161 -0
- data/lib/rdf/util/aliasing.rb +63 -0
- data/lib/rdf/util/cache.rb +139 -0
- data/lib/rdf/util/file.rb +38 -0
- data/lib/rdf/util/uuid.rb +36 -0
- data/lib/rdf/util.rb +6 -0
- data/lib/rdf/version.rb +19 -0
- data/lib/rdf/vocab/cc.rb +18 -0
- data/lib/rdf/vocab/cert.rb +13 -0
- data/lib/rdf/vocab/dc.rb +63 -0
- data/lib/rdf/vocab/dc11.rb +23 -0
- data/lib/rdf/vocab/doap.rb +45 -0
- data/lib/rdf/vocab/exif.rb +168 -0
- data/lib/rdf/vocab/foaf.rb +69 -0
- data/lib/rdf/vocab/geo.rb +13 -0
- data/lib/rdf/vocab/http.rb +26 -0
- data/lib/rdf/vocab/owl.rb +59 -0
- data/lib/rdf/vocab/rdfs.rb +17 -0
- data/lib/rdf/vocab/rsa.rb +12 -0
- data/lib/rdf/vocab/rss.rb +14 -0
- data/lib/rdf/vocab/sioc.rb +93 -0
- data/lib/rdf/vocab/skos.rb +36 -0
- data/lib/rdf/vocab/wot.rb +21 -0
- data/lib/rdf/vocab/xhtml.rb +9 -0
- data/lib/rdf/vocab/xsd.rb +58 -0
- data/lib/rdf/vocab.rb +215 -0
- data/lib/rdf/writer.rb +475 -0
- data/lib/rdf.rb +192 -0
- metadata +173 -0
data/lib/rdf/query.rb
ADDED
@@ -0,0 +1,404 @@
|
|
1
|
+
module RDF
|
2
|
+
##
|
3
|
+
# An RDF basic graph pattern (BGP) query.
|
4
|
+
#
|
5
|
+
# Named queries either match against a specifically named
|
6
|
+
# contexts if the name is an RDF::Term or bound RDF::Query::Variable.
|
7
|
+
# Names that are against unbound variables match either default
|
8
|
+
# or named contexts.
|
9
|
+
# The name of `false' will only match against the default context.
|
10
|
+
#
|
11
|
+
# Variable names cause the variable to be added to the solution set
|
12
|
+
# elements.
|
13
|
+
#
|
14
|
+
# @example Constructing a basic graph pattern query (1)
|
15
|
+
# query = RDF::Query.new do
|
16
|
+
# pattern [:person, RDF.type, FOAF.Person]
|
17
|
+
# pattern [:person, FOAF.name, :name]
|
18
|
+
# pattern [:person, FOAF.mbox, :email]
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example Constructing a basic graph pattern query (2)
|
22
|
+
# query = RDF::Query.new({
|
23
|
+
# :person => {
|
24
|
+
# RDF.type => FOAF.Person,
|
25
|
+
# FOAF.name => :name,
|
26
|
+
# FOAF.mbox => :email,
|
27
|
+
# }
|
28
|
+
# })
|
29
|
+
#
|
30
|
+
# @example Executing a basic graph pattern query
|
31
|
+
# graph = RDF::Graph.load('etc/doap.nt')
|
32
|
+
# query.execute(graph).each do |solution|
|
33
|
+
# puts solution.inspect
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @example Constructing and executing a query in one go (1)
|
37
|
+
# solutions = RDF::Query.execute(graph) do
|
38
|
+
# pattern [:person, RDF.type, FOAF.Person]
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @example Constructing and executing a query in one go (2)
|
42
|
+
# solutions = RDF::Query.execute(graph, {
|
43
|
+
# :person => {
|
44
|
+
# RDF.type => FOAF.Person,
|
45
|
+
# }
|
46
|
+
# })
|
47
|
+
#
|
48
|
+
# @example In this example, the default graph contains the names of the publishers of two named graphs. The triples in the named graphs are not visible in the default graph in this example.
|
49
|
+
# # default graph
|
50
|
+
# @prefix dc: <http://purl.org/dc/elements/1.1/
|
51
|
+
#
|
52
|
+
# <http://example.org/bob> dc:publisher "Bob" .
|
53
|
+
# <http://example.org/alice> dc:publisher "Alice" .
|
54
|
+
#
|
55
|
+
# # Named graph: http://example.org/bob
|
56
|
+
# @prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
57
|
+
#
|
58
|
+
# _:a foaf:name "Bob" .
|
59
|
+
# _:a foaf:mbox <mailto:bob@oldcorp.example.org> .
|
60
|
+
#
|
61
|
+
# # Named graph: http://example.org/alice
|
62
|
+
# @prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
63
|
+
#
|
64
|
+
# _:a foaf:name "Alice" .
|
65
|
+
# _:a foaf:mbox <mailto:alice@work.example.org> .
|
66
|
+
#
|
67
|
+
#
|
68
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#rdfDataset
|
69
|
+
# @since 0.3.0
|
70
|
+
class Query
|
71
|
+
autoload :Pattern, 'rdf/query/pattern'
|
72
|
+
autoload :Solution, 'rdf/query/solution'
|
73
|
+
autoload :Solutions, 'rdf/query/solutions'
|
74
|
+
autoload :Variable, 'rdf/query/variable'
|
75
|
+
|
76
|
+
##
|
77
|
+
# Executes a query on the given `queryable` graph or repository.
|
78
|
+
#
|
79
|
+
# @param [RDF::Queryable] queryable
|
80
|
+
# the graph or repository to query
|
81
|
+
# @param [Hash{Object => Object}] patterns
|
82
|
+
# optional hash patterns to initialize the query with
|
83
|
+
# @param [Hash{Symbol => Object}] options
|
84
|
+
# any additional keyword options (see {RDF::Query#initialize})
|
85
|
+
# @yield [query]
|
86
|
+
# @yieldparam [RDF::Query] query
|
87
|
+
# @yieldreturn [void] ignored
|
88
|
+
# @return [RDF::Query::Solutions]
|
89
|
+
# the resulting solution sequence
|
90
|
+
# @see RDF::Query#execute
|
91
|
+
def self.execute(queryable, patterns = nil, options = {}, &block)
|
92
|
+
self.new(patterns, options, &block).execute(queryable, options)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# The variables used in this query.
|
97
|
+
#
|
98
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
99
|
+
attr_reader :variables
|
100
|
+
|
101
|
+
##
|
102
|
+
# The patterns that constitute this query.
|
103
|
+
#
|
104
|
+
# @return [Array<RDF::Query::Pattern>]
|
105
|
+
attr_reader :patterns
|
106
|
+
|
107
|
+
##
|
108
|
+
# The solution sequence for this query.
|
109
|
+
#
|
110
|
+
# @return [RDF::Query::Solutions]
|
111
|
+
attr_reader :solutions
|
112
|
+
|
113
|
+
##
|
114
|
+
# Any additional options for this query.
|
115
|
+
#
|
116
|
+
# @return [Hash]
|
117
|
+
attr_reader :options
|
118
|
+
|
119
|
+
##
|
120
|
+
# Initializes a new basic graph pattern query.
|
121
|
+
#
|
122
|
+
# @overload initialize(patterns = [], options = {})
|
123
|
+
# @param [Array<RDF::Query::Pattern>] patterns
|
124
|
+
# ...
|
125
|
+
# @param [Hash{Symbol => Object}] options
|
126
|
+
# any additional keyword options
|
127
|
+
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
128
|
+
# @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
|
129
|
+
# Default context for matching against queryable.
|
130
|
+
# Named queries either match against a specifically named
|
131
|
+
# contexts if the name is an RDF::Term or bound RDF::Query::Variable.
|
132
|
+
# Names that are against unbound variables match either detault
|
133
|
+
# or named contexts.
|
134
|
+
# The name of `false' will only match against the default context.
|
135
|
+
# @yield [query]
|
136
|
+
# @yieldparam [RDF::Query] query
|
137
|
+
# @yieldreturn [void] ignored
|
138
|
+
#
|
139
|
+
# @overload initialize(patterns, options = {})
|
140
|
+
# @param [Hash{Object => Object}] patterns
|
141
|
+
# ...
|
142
|
+
# @param [Hash{Symbol => Object}] options
|
143
|
+
# any additional keyword options
|
144
|
+
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
145
|
+
# @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
|
146
|
+
# Default context for matching against queryable.
|
147
|
+
# Named queries either match against a specifically named
|
148
|
+
# contexts if the name is an RDF::Term or bound RDF::Query::Variable.
|
149
|
+
# Names that are against unbound variables match either detault
|
150
|
+
# or named contexts.
|
151
|
+
# @yield [query]
|
152
|
+
# @yieldparam [RDF::Query] query
|
153
|
+
# @yieldreturn [void] ignored
|
154
|
+
def initialize(*patterns, &block)
|
155
|
+
@options = patterns.last.is_a?(Hash) ? patterns.pop.dup : {}
|
156
|
+
patterns << @options if patterns.empty?
|
157
|
+
@variables = {}
|
158
|
+
@solutions = @options.delete(:solutions) || Solutions.new
|
159
|
+
context = @options.delete(:context)
|
160
|
+
|
161
|
+
@patterns = case patterns.first
|
162
|
+
when Hash then compile_hash_patterns(patterns.first.dup)
|
163
|
+
when Array then patterns.first
|
164
|
+
else patterns
|
165
|
+
end
|
166
|
+
|
167
|
+
self.context = context
|
168
|
+
|
169
|
+
if block_given?
|
170
|
+
case block.arity
|
171
|
+
when 1 then block.call(self)
|
172
|
+
else instance_eval(&block)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Appends the given query `pattern` to this query.
|
179
|
+
#
|
180
|
+
# @param [RDF::Query::Pattern] pattern
|
181
|
+
# a triple query pattern
|
182
|
+
# @return [void] self
|
183
|
+
def <<(pattern)
|
184
|
+
@patterns << Pattern.from(pattern)
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Appends the given query `pattern` to this query.
|
190
|
+
#
|
191
|
+
# @param [RDF::Query::Pattern] pattern
|
192
|
+
# a triple query pattern
|
193
|
+
# @param [Hash{Symbol => Object}] options
|
194
|
+
# any additional keyword options
|
195
|
+
# @option options [Boolean] :optional (false)
|
196
|
+
# whether this is an optional pattern
|
197
|
+
# @return [void] self
|
198
|
+
def pattern(pattern, options = {})
|
199
|
+
@patterns << Pattern.from(pattern, options)
|
200
|
+
self
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Returns an optimized copy of this query.
|
205
|
+
#
|
206
|
+
# @param [Hash{Symbol => Object}] options
|
207
|
+
# any additional options for optimization
|
208
|
+
# @return [RDF::Query] a copy of `self`
|
209
|
+
# @since 0.3.0
|
210
|
+
def optimize(options = {})
|
211
|
+
self.dup.optimize!(options)
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Optimizes this query by reordering its constituent triple patterns
|
216
|
+
# according to their cost estimates.
|
217
|
+
#
|
218
|
+
# @param [Hash{Symbol => Object}] options
|
219
|
+
# any additional options for optimization
|
220
|
+
# @return [void] `self`
|
221
|
+
# @see RDF::Query::Pattern#cost
|
222
|
+
# @since 0.3.0
|
223
|
+
def optimize!(options = {})
|
224
|
+
@patterns.sort! do |a, b|
|
225
|
+
(a.cost || 0) <=> (b.cost || 0)
|
226
|
+
end
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Executes this query on the given `queryable` graph or repository.
|
232
|
+
#
|
233
|
+
# Named queries either match against a specifically named
|
234
|
+
# contexts if the name is an RDF::Term or bound RDF::Query::Variable.
|
235
|
+
# Names that are against unbound variables match either detault
|
236
|
+
# or named contexts.
|
237
|
+
# The name of `false' will only match against the default context.
|
238
|
+
#
|
239
|
+
# @param [RDF::Queryable] queryable
|
240
|
+
# the graph or repository to query
|
241
|
+
# @param [Hash{Symbol => Object}] options
|
242
|
+
# any additional keyword options
|
243
|
+
# @option options [Hash{Symbol => RDF::Term}] bindings
|
244
|
+
# optional variable bindings to use
|
245
|
+
# @option options [Hash{Symbol => RDF::Term}] solutions
|
246
|
+
# optional initial solutions for chained queries
|
247
|
+
# @return [RDF::Query::Solutions]
|
248
|
+
# the resulting solution sequence
|
249
|
+
# @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
|
250
|
+
def execute(queryable, options = {})
|
251
|
+
options = options.dup
|
252
|
+
|
253
|
+
# just so we can call #keys below without worrying
|
254
|
+
options[:bindings] ||= {}
|
255
|
+
|
256
|
+
# Use provided solutions to allow for query chaining
|
257
|
+
# Otherwise, a quick empty solution simplifies the logic below; no special case for
|
258
|
+
# the first pattern
|
259
|
+
@solutions = options[:solutions] || (Solutions.new << RDF::Query::Solution.new({}))
|
260
|
+
|
261
|
+
patterns = @patterns
|
262
|
+
|
263
|
+
# Add context to pattern, if necessary
|
264
|
+
unless self.context.nil?
|
265
|
+
if patterns.empty?
|
266
|
+
patterns = [Pattern.new(nil, nil, nil, :context => self.context)]
|
267
|
+
elsif patterns.first.context.nil?
|
268
|
+
patterns.first.context = self.context
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
patterns.each do |pattern|
|
273
|
+
|
274
|
+
old_solutions, @solutions = @solutions, Solutions.new
|
275
|
+
|
276
|
+
options[:bindings].keys.each do |variable|
|
277
|
+
if pattern.variables.include?(variable)
|
278
|
+
unbound_solutions, old_solutions = old_solutions, Solutions.new
|
279
|
+
options[:bindings][variable].each do |binding|
|
280
|
+
unbound_solutions.each do |solution|
|
281
|
+
old_solutions << solution.merge(variable => binding)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
options[:bindings].delete(variable)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
old_solutions.each do |solution|
|
289
|
+
pattern.execute(queryable, solution) do |statement|
|
290
|
+
@solutions << solution.merge(pattern.solution(statement))
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
#puts "solutions after #{pattern} are #{@solutions.to_a.inspect}"
|
295
|
+
|
296
|
+
# It's important to abort failed queries quickly because later patterns
|
297
|
+
# that can have constraints are often broad without them.
|
298
|
+
# We have no solutions at all:
|
299
|
+
return @solutions if @solutions.empty?
|
300
|
+
# We have no solutions for variables we should have solutions for:
|
301
|
+
if !pattern.optional? && pattern.variables.keys.any? { |variable| !@solutions.variable_names.include?(variable) }
|
302
|
+
return Solutions.new
|
303
|
+
end
|
304
|
+
end
|
305
|
+
@solutions
|
306
|
+
end
|
307
|
+
|
308
|
+
##
|
309
|
+
# Returns `true` if this query did not match when last executed.
|
310
|
+
#
|
311
|
+
# When the solution sequence is empty, this method can be used to
|
312
|
+
# determine whether the query failed to match or not.
|
313
|
+
#
|
314
|
+
# @return [Boolean]
|
315
|
+
# @see #matched?
|
316
|
+
def failed?
|
317
|
+
@solutions.empty?
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Returns `true` if this query matched when last executed.
|
322
|
+
#
|
323
|
+
# When the solution sequence is empty, this method can be used to
|
324
|
+
# determine whether the query matched successfully or not.
|
325
|
+
#
|
326
|
+
# @return [Boolean]
|
327
|
+
# @see #failed?
|
328
|
+
def matched?
|
329
|
+
!@failed
|
330
|
+
end
|
331
|
+
|
332
|
+
# Add patterns from another query to form a new Query
|
333
|
+
# @param [RDF::Query] other
|
334
|
+
# @return [RDF::Query]
|
335
|
+
def +(other)
|
336
|
+
Query.new(self.patterns + other.patterns)
|
337
|
+
end
|
338
|
+
|
339
|
+
# Is this is a named query?
|
340
|
+
# @return [Boolean]
|
341
|
+
def named?
|
342
|
+
!!options[:context]
|
343
|
+
end
|
344
|
+
|
345
|
+
# Is this is an unamed query?
|
346
|
+
# @return [Boolean]
|
347
|
+
def unnamed?
|
348
|
+
!named?
|
349
|
+
end
|
350
|
+
|
351
|
+
# Add name to query
|
352
|
+
# @param [RDF::Value] value
|
353
|
+
# @return [RDF::Value]
|
354
|
+
def context=(value)
|
355
|
+
options[:context] = value
|
356
|
+
end
|
357
|
+
|
358
|
+
# Name of this query, if any
|
359
|
+
# @return [RDF::Value]
|
360
|
+
def context
|
361
|
+
options[:context]
|
362
|
+
end
|
363
|
+
|
364
|
+
# Query has no patterns
|
365
|
+
# @return [Boolean]
|
366
|
+
def empty?
|
367
|
+
patterns.empty?
|
368
|
+
end
|
369
|
+
|
370
|
+
##
|
371
|
+
# Enumerates over each matching query solution.
|
372
|
+
#
|
373
|
+
# @yield [solution]
|
374
|
+
# @yieldparam [RDF::Query::Solution] solution
|
375
|
+
# @return [Enumerator]
|
376
|
+
def each_solution(&block)
|
377
|
+
@solutions.each(&block)
|
378
|
+
end
|
379
|
+
alias_method :each, :each_solution
|
380
|
+
|
381
|
+
protected
|
382
|
+
|
383
|
+
##
|
384
|
+
# @private
|
385
|
+
def compile_hash_patterns(hash_patterns)
|
386
|
+
patterns = []
|
387
|
+
hash_patterns.each do |s, pos|
|
388
|
+
raise ArgumentError, "invalid hash pattern: #{hash_patterns.inspect}" unless pos.is_a?(Hash)
|
389
|
+
pos.each do |p, os|
|
390
|
+
case os
|
391
|
+
when Hash
|
392
|
+
patterns += os.keys.map { |o| [s, p, o] }
|
393
|
+
patterns += compile_hash_patterns(os)
|
394
|
+
when Array
|
395
|
+
patterns += os.map { |o| [s, p, o] }
|
396
|
+
else
|
397
|
+
patterns << [s, p, os]
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
patterns.map { |pattern| Pattern.from(pattern) }
|
402
|
+
end
|
403
|
+
end # Query
|
404
|
+
end # RDF
|