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 CHANGED
@@ -1,4 +1,5 @@
1
1
  * Călin Ardelean <calinucs@gmail.com>
2
+ * Mark Borkum <m.i.borkum@soton.ac.uk>
2
3
  * Danny Gagne <danny@dannygagne.com>
3
4
  * Joey Geiger <jgeiger@gmail.com>
4
5
  * Fumihiro Kato <fumi@fumi.me>
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.0
1
+ 1.0.1
@@ -4,7 +4,7 @@ module RDF
4
4
  #
5
5
  # This has not yet been implemented as of RDF.rb 0.3.x.
6
6
  module NQuads
7
- include NTriples
7
+ include RDF::NTriples
8
8
 
9
9
  ##
10
10
  # N-Quads format specification.
@@ -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::Term or bound RDF::Query::Variable.
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::Term, RDF::Query::Variable, Boolean] :context (nil)
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::Term or bound RDF::Query::Variable.
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::Term, RDF::Query::Variable, Boolean] :context (nil)
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::Term or bound RDF::Query::Variable.
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::Term or bound RDF::Query::Variable.
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 self.context.nil?
270
+ unless context.nil?
265
271
  if patterns.empty?
266
- patterns = [Pattern.new(nil, nil, nil, :context => self.context)]
272
+ patterns = [Pattern.new(nil, nil, nil, :context => context)]
267
273
  elsif patterns.first.context.nil?
268
- patterns.first.context = self.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
@@ -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).
@@ -224,7 +224,7 @@ module RDF
224
224
  # Returns the base URI used for this writer.
225
225
  #
226
226
  # @example
227
- # reader.prefixes[:dc] #=> RDF::URI('http://purl.org/dc/terms/')
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.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-01-19 00:00:00.000000000 Z
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.24
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.