rdf 1.0.0 → 1.0.1
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/CREDITS +1 -0
- data/README +2 -0
- data/VERSION +1 -1
- data/lib/rdf/nquads.rb +1 -1
- data/lib/rdf/query.rb +60 -10
- data/lib/rdf/query/hash_pattern_normalizer.rb +191 -0
- data/lib/rdf/query/solution.rb +7 -0
- data/lib/rdf/writer.rb +1 -1
- metadata +7 -3
data/CREDITS
CHANGED
data/README
CHANGED
@@ -227,6 +227,7 @@ The meta-gem [LinkedData][LinkedData doc] includes many of these gems.
|
|
227
227
|
### RDF Querying
|
228
228
|
|
229
229
|
* {RDF::Query}
|
230
|
+
* {RDF::Query::HashPatternNormalizer}
|
230
231
|
* {RDF::Query::Pattern}
|
231
232
|
* {RDF::Query::Solution}
|
232
233
|
* {RDF::Query::Solutions}
|
@@ -302,6 +303,7 @@ follows:
|
|
302
303
|
## Contributors
|
303
304
|
|
304
305
|
* [Călin Ardelean](http://github.com/clnx) - <http://github.com/clnx>
|
306
|
+
* [Mark Borkum](http://github.com/markborkum) - <http://github.com/markborkum>
|
305
307
|
* [Danny Gagne](http://github.com/danny) - <http://www.dannygagne.com/>
|
306
308
|
* [Joey Geiger](http://github.com/jgeiger) - <http://github.com/jgeiger>
|
307
309
|
* [Fumihiro Kato](http://github.com/fumi) - <http://fumi.me/>
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.1
|
data/lib/rdf/nquads.rb
CHANGED
data/lib/rdf/query.rb
CHANGED
@@ -3,7 +3,7 @@ module RDF
|
|
3
3
|
# An RDF basic graph pattern (BGP) query.
|
4
4
|
#
|
5
5
|
# Named queries either match against a specifically named
|
6
|
-
# contexts if the name is an RDF::
|
6
|
+
# contexts if the name is an RDF::Resource or bound RDF::Query::Variable.
|
7
7
|
# Names that are against unbound variables match either default
|
8
8
|
# or named contexts.
|
9
9
|
# The name of `false` will only match against the default context.
|
@@ -72,6 +72,7 @@ module RDF
|
|
72
72
|
autoload :Solution, 'rdf/query/solution'
|
73
73
|
autoload :Solutions, 'rdf/query/solutions'
|
74
74
|
autoload :Variable, 'rdf/query/variable'
|
75
|
+
autoload :HashPatternNormalizer, 'rdf/query/hash_pattern_normalizer'
|
75
76
|
|
76
77
|
##
|
77
78
|
# Executes a query on the given `queryable` graph or repository.
|
@@ -125,10 +126,10 @@ module RDF
|
|
125
126
|
# @param [Hash{Symbol => Object}] options
|
126
127
|
# any additional keyword options
|
127
128
|
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
128
|
-
# @option options [RDF::
|
129
|
+
# @option options [RDF::Resource, RDF::Query::Variable, false] :context (nil)
|
129
130
|
# Default context for matching against queryable.
|
130
131
|
# Named queries either match against a specifically named
|
131
|
-
# contexts if the name is an RDF::
|
132
|
+
# contexts if the name is an {RDF::Resource} or bound {RDF::Query::Variable}.
|
132
133
|
# Names that are against unbound variables match either detault
|
133
134
|
# or named contexts.
|
134
135
|
# The name of `false` will only match against the default context.
|
@@ -142,10 +143,10 @@ module RDF
|
|
142
143
|
# @param [Hash{Symbol => Object}] options
|
143
144
|
# any additional keyword options
|
144
145
|
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
|
145
|
-
# @option options [RDF::
|
146
|
+
# @option options [RDF::Resource, RDF::Query::Variable, false] :context (nil)
|
146
147
|
# Default context for matching against queryable.
|
147
148
|
# Named queries either match against a specifically named
|
148
|
-
# contexts if the name is an RDF::
|
149
|
+
# contexts if the name is an {RDF::Resource} or bound {RDF::Query::Variable}.
|
149
150
|
# Names that are against unbound variables match either detault
|
150
151
|
# or named contexts.
|
151
152
|
# @yield [query]
|
@@ -159,7 +160,7 @@ module RDF
|
|
159
160
|
context = @options.delete(:context)
|
160
161
|
|
161
162
|
@patterns = case patterns.first
|
162
|
-
when Hash then compile_hash_patterns(patterns.first.dup)
|
163
|
+
when Hash then compile_hash_patterns(HashPatternNormalizer.normalize!(patterns.first.dup, @options))
|
163
164
|
when Array then patterns.first
|
164
165
|
else patterns
|
165
166
|
end
|
@@ -231,7 +232,7 @@ module RDF
|
|
231
232
|
# Executes this query on the given `queryable` graph or repository.
|
232
233
|
#
|
233
234
|
# Named queries either match against a specifically named
|
234
|
-
# contexts if the name is an RDF::
|
235
|
+
# contexts if the name is an RDF::Resource or bound RDF::Query::Variable.
|
235
236
|
# Names that are against unbound variables match either detault
|
236
237
|
# or named contexts.
|
237
238
|
# The name of `false` will only match against the default context.
|
@@ -242,12 +243,16 @@ module RDF
|
|
242
243
|
# any additional keyword options
|
243
244
|
# @option options [Hash{Symbol => RDF::Term}] bindings
|
244
245
|
# optional variable bindings to use
|
246
|
+
# @option options [RDF::Resource, RDF::Query::Variable, false] context (nil)
|
247
|
+
# Specific context for matching against queryable;
|
248
|
+
# overrides default context defined on query.
|
245
249
|
# @option options [Hash{Symbol => RDF::Term}] solutions
|
246
250
|
# optional initial solutions for chained queries
|
247
251
|
# @return [RDF::Query::Solutions]
|
248
252
|
# the resulting solution sequence
|
249
253
|
# @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
|
250
254
|
def execute(queryable, options = {})
|
255
|
+
validate!
|
251
256
|
options = options.dup
|
252
257
|
|
253
258
|
# just so we can call #keys below without worrying
|
@@ -259,13 +264,14 @@ module RDF
|
|
259
264
|
@solutions = options[:solutions] || (Solutions.new << RDF::Query::Solution.new({}))
|
260
265
|
|
261
266
|
patterns = @patterns
|
267
|
+
context = options.fetch(:context, self.context)
|
262
268
|
|
263
269
|
# Add context to pattern, if necessary
|
264
|
-
unless
|
270
|
+
unless context.nil?
|
265
271
|
if patterns.empty?
|
266
|
-
patterns = [Pattern.new(nil, nil, nil, :context =>
|
272
|
+
patterns = [Pattern.new(nil, nil, nil, :context => context)]
|
267
273
|
elsif patterns.first.context.nil?
|
268
|
-
patterns.first.context =
|
274
|
+
patterns.first.context = context
|
269
275
|
end
|
270
276
|
end
|
271
277
|
|
@@ -286,9 +292,16 @@ module RDF
|
|
286
292
|
end
|
287
293
|
|
288
294
|
old_solutions.each do |solution|
|
295
|
+
found_match = false
|
289
296
|
pattern.execute(queryable, solution) do |statement|
|
297
|
+
found_match = true
|
290
298
|
@solutions << solution.merge(pattern.solution(statement))
|
291
299
|
end
|
300
|
+
# If this pattern was optional, and we didn't find any matches,
|
301
|
+
# just copy it over as-is.
|
302
|
+
if !found_match && pattern.optional?
|
303
|
+
@solutions << solution
|
304
|
+
end
|
292
305
|
end
|
293
306
|
|
294
307
|
#puts "solutions after #{pattern} are #{@solutions.to_a.inspect}"
|
@@ -378,6 +391,43 @@ module RDF
|
|
378
391
|
end
|
379
392
|
alias_method :each, :each_solution
|
380
393
|
|
394
|
+
##
|
395
|
+
# Duplicate query, including patterns and solutions
|
396
|
+
# @return [RDF::Query]
|
397
|
+
def dup
|
398
|
+
patterns = @patterns.map {|p| p.dup}
|
399
|
+
patterns << @options.merge(:solutions => @solutions.dup)
|
400
|
+
Query.new(*patterns)
|
401
|
+
end
|
402
|
+
|
403
|
+
##
|
404
|
+
# Validate this query, making sure it can be executed by our query engine.
|
405
|
+
# This method is public so that it may be called by implementations of
|
406
|
+
# RDF::Queryable#query_execute that bypass our built-in query engine.
|
407
|
+
#
|
408
|
+
# @return [void]
|
409
|
+
# @raise [ArgumentError] This query cannot be executed.
|
410
|
+
def validate!
|
411
|
+
# Our first pattern, if it exists, cannot be an optional pattern.
|
412
|
+
if @patterns.length > 0 && @patterns[0].optional?
|
413
|
+
raise ArgumentError.new("Query must not begin with an optional pattern")
|
414
|
+
end
|
415
|
+
|
416
|
+
# All optional patterns must appear after the regular patterns. We
|
417
|
+
# could test this more cleanly using Ruby 1.9-specific features, but
|
418
|
+
# we want to run under Ruby 1.8, too.
|
419
|
+
i = 0
|
420
|
+
i += 1 while i < @patterns.length && !@patterns[i].optional?
|
421
|
+
while i < @patterns.length
|
422
|
+
unless @patterns[i].optional?
|
423
|
+
raise ArgumentError.new("Optional patterns must appear at end of query")
|
424
|
+
end
|
425
|
+
i += 1
|
426
|
+
end
|
427
|
+
|
428
|
+
nil
|
429
|
+
end
|
430
|
+
|
381
431
|
protected
|
382
432
|
|
383
433
|
##
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module RDF; class Query
|
2
|
+
##
|
3
|
+
# An RDF query pattern normalizer.
|
4
|
+
class HashPatternNormalizer
|
5
|
+
##
|
6
|
+
# A counter that can be incremented and decremented.
|
7
|
+
class Counter
|
8
|
+
##
|
9
|
+
# The offset (or initial value) for this counter.
|
10
|
+
#
|
11
|
+
# @return [Numeric]
|
12
|
+
attr_reader :offset
|
13
|
+
|
14
|
+
##
|
15
|
+
# The increment for this counter.
|
16
|
+
#
|
17
|
+
# @return [Numeric]
|
18
|
+
attr_reader :increment
|
19
|
+
|
20
|
+
##
|
21
|
+
# @param [Numeric] offset
|
22
|
+
# the offset (or initial value) for this counter.
|
23
|
+
# @param [Numeric] increment
|
24
|
+
# the increment for this counter.
|
25
|
+
def initialize(offset = 0, increment = 1)
|
26
|
+
@offset = offset
|
27
|
+
@increment = increment
|
28
|
+
|
29
|
+
@value = @offset
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Decrements this counter, and returns the new value.
|
34
|
+
#
|
35
|
+
# @return [RDF::Query::HashPatternNormalizer::Counter]
|
36
|
+
def decrement!
|
37
|
+
@value -= @increment
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Increments this counter, and returns the new value.
|
44
|
+
#
|
45
|
+
# @return [RDF::Query::HashPatternNormalizer::Counter]
|
46
|
+
def increment!
|
47
|
+
@value += @increment
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Returns a floating point representation of this counter.
|
54
|
+
#
|
55
|
+
# @return [Float]
|
56
|
+
def to_f
|
57
|
+
@value.to_f
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Returns an integer representation of this counter.
|
62
|
+
#
|
63
|
+
# @return [Integer]
|
64
|
+
def to_i
|
65
|
+
@value.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
## Returns a string representation of this counter.
|
69
|
+
#
|
70
|
+
# @return [String]
|
71
|
+
def to_s
|
72
|
+
@value.to_s
|
73
|
+
end
|
74
|
+
end # RDF::Query::HashPatternNormalizer::Counter
|
75
|
+
|
76
|
+
class << self
|
77
|
+
##
|
78
|
+
# Returns the normalization of the specified `hash_pattern`.
|
79
|
+
#
|
80
|
+
# @param [Hash{Symbol => Object}] hash_pattern (Hash.new)
|
81
|
+
# the query pattern as a hash.
|
82
|
+
# @param [Hash{Symbol => Object}] options (Hash.new)
|
83
|
+
# any additional normalization options.
|
84
|
+
# @option options [String] :anonymous_subject_format ("__%s__")
|
85
|
+
# the string format for anonymous subjects.
|
86
|
+
# @return [Hash{Symbol => Object}]
|
87
|
+
# the resulting query pattern as a normalized hash.
|
88
|
+
def normalize!(hash_pattern = {}, options = {})
|
89
|
+
raise ArgumentError, "invalid hash pattern: #{hash_pattern.inspect}" unless hash_pattern.is_a?(Hash)
|
90
|
+
|
91
|
+
counter = RDF::Query::HashPatternNormalizer::Counter.new
|
92
|
+
|
93
|
+
anonymous_subject_format = (options[:anonymous_subject_format] || '__%s__').to_s
|
94
|
+
|
95
|
+
hash_pattern.inject({}) { |acc, pair|
|
96
|
+
subject, predicate_to_object = pair
|
97
|
+
|
98
|
+
ensure_absence_of_duplicate_subjects!(acc, subject)
|
99
|
+
normalized_predicate_to_object = normalize_hash!(predicate_to_object, acc, counter, anonymous_subject_format)
|
100
|
+
ensure_absence_of_duplicate_subjects!(acc, subject)
|
101
|
+
|
102
|
+
acc[subject] = normalized_predicate_to_object
|
103
|
+
acc
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
##
|
110
|
+
# @private
|
111
|
+
def ensure_absence_of_duplicate_subjects!(acc, subject)
|
112
|
+
raise "duplicate subject #{subject.inspect} in normalized hash pattern: #{acc.inspect}" if acc.key?(subject)
|
113
|
+
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# @private
|
119
|
+
def normalize_array!(array, *args)
|
120
|
+
raise ArgumentError, "invalid array pattern: #{array.inspect}" unless array.is_a?(Array)
|
121
|
+
|
122
|
+
array.collect { |object|
|
123
|
+
normalize_object!(object, *args)
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# @private
|
129
|
+
def normalize_hash!(hash, *args)
|
130
|
+
raise ArgumentError, "invalid hash pattern: #{hash.inspect}" unless hash.is_a?(Hash)
|
131
|
+
|
132
|
+
hash.inject({}) { |acc, pair|
|
133
|
+
acc[pair.first] = normalize_object!(pair.last, *args)
|
134
|
+
acc
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# @private
|
140
|
+
def normalize_object!(object, *args)
|
141
|
+
case object
|
142
|
+
when Array then normalize_array!(object, *args)
|
143
|
+
when Hash then replace_hash_with_anonymous_subject!(object, *args)
|
144
|
+
else object
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# @private
|
150
|
+
def replace_hash_with_anonymous_subject!(hash, acc, counter, anonymous_subject_format)
|
151
|
+
raise ArgumentError, "invalid hash pattern: #{hash.inspect}" unless hash.is_a?(Hash)
|
152
|
+
|
153
|
+
subject = (anonymous_subject_format % counter.increment!).to_sym
|
154
|
+
|
155
|
+
ensure_absence_of_duplicate_subjects!(acc, subject)
|
156
|
+
normalized_hash = normalize_hash!(hash, acc, counter, anonymous_subject_format)
|
157
|
+
ensure_absence_of_duplicate_subjects!(acc, subject)
|
158
|
+
|
159
|
+
acc[subject] = normalized_hash
|
160
|
+
|
161
|
+
subject
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# The options for this hash pattern normalizer.
|
167
|
+
#
|
168
|
+
# @return [Hash{Symbol => Object}]
|
169
|
+
attr_reader :options
|
170
|
+
|
171
|
+
##
|
172
|
+
# @param [Hash{Symbol => Object}] options (Hash.new)
|
173
|
+
# any additional normalization options.
|
174
|
+
# @option options [String] :anonymous_subject_format ("__%s__")
|
175
|
+
# the string format for anonymous subjects.
|
176
|
+
def initialize(options = {})
|
177
|
+
@options = options.dup
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Equivalent to calling `self.class.normalize!(hash_pattern, self.options)`.
|
182
|
+
#
|
183
|
+
# @param [Hash{Symbol => Object}] hash_pattern
|
184
|
+
# the query pattern as a hash.
|
185
|
+
# @return [Hash{Symbol => Object}]
|
186
|
+
# the resulting query pattern as a normalized hash.
|
187
|
+
def normalize!(hash_pattern = {})
|
188
|
+
self.class.normalize!(hash_pattern, @options)
|
189
|
+
end
|
190
|
+
end # RDF::Query::HashPatternNormalizer
|
191
|
+
end; end # RDF::Query
|
data/lib/rdf/query/solution.rb
CHANGED
@@ -171,6 +171,13 @@ class RDF::Query
|
|
171
171
|
self.class.new(@bindings.dup).merge!(other)
|
172
172
|
end
|
173
173
|
|
174
|
+
##
|
175
|
+
# Duplicate solution, preserving patterns
|
176
|
+
# @return [RDF::Statement]
|
177
|
+
def dup
|
178
|
+
merge({})
|
179
|
+
end
|
180
|
+
|
174
181
|
##
|
175
182
|
# Compatible Mappings
|
176
183
|
# Two solution mappings u1 and u2 are compatible if, for every variable v in dom(u1) and in dom(u2), u1(v) = u2(v).
|
data/lib/rdf/writer.rb
CHANGED
@@ -224,7 +224,7 @@ module RDF
|
|
224
224
|
# Returns the base URI used for this writer.
|
225
225
|
#
|
226
226
|
# @example
|
227
|
-
#
|
227
|
+
# writer.prefixes[:dc] #=> RDF::URI('http://purl.org/dc/terms/')
|
228
228
|
#
|
229
229
|
# @return [Hash{Symbol => RDF::URI}]
|
230
230
|
# @since 0.3.4
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: addressable
|
@@ -130,6 +130,7 @@ files:
|
|
130
130
|
- lib/rdf/ntriples/reader.rb
|
131
131
|
- lib/rdf/ntriples/writer.rb
|
132
132
|
- lib/rdf/ntriples.rb
|
133
|
+
- lib/rdf/query/hash_pattern_normalizer.rb
|
133
134
|
- lib/rdf/query/pattern.rb
|
134
135
|
- lib/rdf/query/solution.rb
|
135
136
|
- lib/rdf/query/solutions.rb
|
@@ -185,9 +186,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
186
|
- - ! '>='
|
186
187
|
- !ruby/object:Gem::Version
|
187
188
|
version: '0'
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
hash: -3489670263527444797
|
188
192
|
requirements: []
|
189
193
|
rubyforge_project: rdf
|
190
|
-
rubygems_version: 1.8.
|
194
|
+
rubygems_version: 1.8.25
|
191
195
|
signing_key:
|
192
196
|
specification_version: 3
|
193
197
|
summary: A Ruby library for working with Resource Description Framework (RDF) data.
|