rdf 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.