rdf 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,6 +22,7 @@ module RDF; class Query
22
22
  # @option options [Variable, URI] :predicate (nil)
23
23
  # @option options [Variable, Term] :object (nil)
24
24
  # @option options [Variable, Resource] :context (nil)
25
+ # A context of nil matches any context, a context of false, matches only the default context.
25
26
  # @option options [Boolean] :optional (false)
26
27
  #
27
28
  # @overload initialize(subject, predicate, object, options = {})
@@ -118,10 +119,12 @@ module RDF; class Query
118
119
  ##
119
120
  # Executes this query pattern on the given `queryable` object.
120
121
  #
121
- # By default any variable terms in this pattern will be treated as `nil`
122
- # wildcards when executing the query. If the optional `bindings` are
123
- # given, variables will be substituted with their values when executing
124
- # the query.
122
+ # Values are matched using using Queryable#query_pattern.
123
+ #
124
+ # If the optional `bindings` are given, variables will be substituted with their values
125
+ # when executing the query.
126
+ #
127
+ # To match triples only in the default context, set context to `false'.
125
128
  #
126
129
  # @example
127
130
  # Pattern.new(:s, :p, :o).execute(RDF::Repository.load('data.nt'))
@@ -140,11 +143,11 @@ module RDF; class Query
140
143
  # @since 0.3.0
141
144
  def execute(queryable, bindings = {}, &block)
142
145
  query = {
143
- :subject => subject && subject.variable? ? bindings[subject.to_sym] : subject,
144
- :predicate => predicate && predicate.variable? ? bindings[predicate.to_sym] : predicate,
145
- :object => object && object.variable? ? bindings[object.to_sym] : object,
146
- # TODO: context handling?
147
- }
146
+ :subject => subject.is_a?(Variable) && bindings[subject.to_sym] ? bindings[subject.to_sym] : subject,
147
+ :predicate => predicate.is_a?(Variable) && bindings[predicate.to_sym] ? bindings[predicate.to_sym] : predicate,
148
+ :object => object.is_a?(Variable) && bindings[object.to_sym] ? bindings[object.to_sym] : object,
149
+ :context => context.is_a?(Variable) && bindings[context.to_sym] ? bindings[context.to_sym] : context,
150
+ }.delete_if{|k,v| v.nil?}
148
151
 
149
152
  # Do all the variable terms refer to distinct variables?
150
153
  variables = self.variables
@@ -184,9 +187,10 @@ module RDF; class Query
184
187
  # @since 0.3.0
185
188
  def solution(statement)
186
189
  RDF::Query::Solution.new do |solution|
187
- solution[subject.to_sym] = statement.subject if subject.variable?
188
- solution[predicate.to_sym] = statement.predicate if predicate.variable?
189
- solution[object.to_sym] = statement.object if object.variable?
190
+ solution[subject.to_sym] = statement.subject if subject.is_a?(Variable)
191
+ solution[predicate.to_sym] = statement.predicate if predicate.is_a?(Variable)
192
+ solution[object.to_sym] = statement.object if object.is_a?(Variable)
193
+ solution[context.to_sym] = statement.context if context.is_a?(Variable)
190
194
  end
191
195
  end
192
196
 
@@ -205,6 +209,7 @@ module RDF; class Query
205
209
  terms << :subject if subject.is_a?(Variable) && (!name || name.eql?(subject.name))
206
210
  terms << :predicate if predicate.is_a?(Variable) && (!name || name.eql?(predicate.name))
207
211
  terms << :object if object.is_a?(Variable) && (!name || name.eql?(object.name))
212
+ terms << :context if context.is_a?(Variable) && (!name || name.eql?(context.name))
208
213
  terms
209
214
  end
210
215
 
@@ -220,6 +225,7 @@ module RDF; class Query
220
225
  count += 1 if subject.is_a?(Variable)
221
226
  count += 1 if predicate.is_a?(Variable)
222
227
  count += 1 if object.is_a?(Variable)
228
+ count += 1 if context.is_a?(Variable)
223
229
  count
224
230
  end
225
231
  alias_method :cardinality, :variable_count
@@ -236,6 +242,7 @@ module RDF; class Query
236
242
  variables.merge!(subject.variables) if subject.is_a?(Variable)
237
243
  variables.merge!(predicate.variables) if predicate.is_a?(Variable)
238
244
  variables.merge!(object.variables) if object.is_a?(Variable)
245
+ variables.merge!(context.variables) if context.is_a?(Variable)
239
246
  variables
240
247
  end
241
248
 
@@ -264,6 +271,7 @@ module RDF; class Query
264
271
  bindings.merge!(subject.bindings) if subject.is_a?(Variable)
265
272
  bindings.merge!(predicate.bindings) if predicate.is_a?(Variable)
266
273
  bindings.merge!(object.bindings) if object.is_a?(Variable)
274
+ bindings.merge!(context.bindings) if context.is_a?(Variable)
267
275
  bindings
268
276
  end
269
277
 
@@ -306,9 +314,14 @@ module RDF; class Query
306
314
  def to_s
307
315
  StringIO.open do |buffer| # FIXME in RDF::Statement
308
316
  buffer << 'OPTIONAL ' if optional?
309
- buffer << (subject.is_a?(Variable) ? subject.to_s : "<#{subject}>") << ' '
310
- buffer << (predicate.is_a?(Variable) ? predicate.to_s : "<#{predicate}>") << ' '
311
- buffer << (object.is_a?(Variable) ? object.to_s : "<#{object}>") << ' .'
317
+ buffer << [subject, predicate, object].map do |r|
318
+ r.is_a?(RDF::Query::Variable) ? r.to_s : RDF::NTriples.serialize(r)
319
+ end.join(" ")
320
+ buffer << case context
321
+ when nil, false then " ."
322
+ when Variable then " #{context.to_s} ."
323
+ else " #{RDF::NTriples.serialize(context)} ."
324
+ end
312
325
  buffer.string
313
326
  end
314
327
  end
@@ -171,6 +171,33 @@ class RDF::Query
171
171
  self.class.new(@bindings.dup).merge!(other)
172
172
  end
173
173
 
174
+ ##
175
+ # Compatible Mappings
176
+ # Two solution mappings μ1 and μ2 are compatible if, for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
177
+ #
178
+ # @param [RDF::Query::Solution, #to_hash] other
179
+ # another query solution or hash bindings
180
+ # @return [Boolean]
181
+ def compatible?(other)
182
+ @bindings.all? do |k, v|
183
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Isomorphic Mappings
189
+ # Two solution mappings μ1 and μ2 are isomorphic if,
190
+ # for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
191
+ #
192
+ # @param [RDF::Query::Solution, #to_hash] other
193
+ # another query solution or hash bindings
194
+ # @return [Boolean]
195
+ def isomorphic_with?(other)
196
+ @bindings.all? do |k, v|
197
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
198
+ end
199
+ end
200
+
174
201
  ##
175
202
  # @return [Array<Array(Symbol, RDF::Term)>}
176
203
  def to_a
@@ -182,6 +209,26 @@ class RDF::Query
182
209
  def to_hash
183
210
  @bindings.dup
184
211
  end
212
+
213
+ ##
214
+ # Integer hash of this solution
215
+ # @return [Integer]
216
+ def hash
217
+ @bindings.hash
218
+ end
219
+
220
+ ##
221
+ # Equivalence of solution
222
+ def eql?(other)
223
+ other.is_a?(Solution) && @bindings.eql?(other.bindings)
224
+ end
225
+ alias_method :==, :eql?
226
+
227
+ ##
228
+ # Equals of solution
229
+ def ==(other)
230
+ other.is_a?(Solution) && @bindings == other.bindings
231
+ end
185
232
 
186
233
  ##
187
234
  # @return [String]
@@ -16,13 +16,15 @@ module RDF; class Query
16
16
  # solutions.filter { |solution| solution.age.datatype == RDF::XSD.integer }
17
17
  # solutions.filter { |solution| solution.name.language == :es }
18
18
  #
19
- # @example Reordering solutions based on a variable
19
+ # @example Reordering solutions based on a variable or proc
20
20
  # solutions.order_by(:updated)
21
21
  # solutions.order_by(:updated, :created)
22
+ # solutions.order_by(:updated, lambda {|a, b| b <=> a})
22
23
  #
23
- # @example Selecting particular variables only
24
+ # @example Selecting/Projecting particular variables only
24
25
  # solutions.select(:title)
25
26
  # solutions.select(:title, :description)
27
+ # solutions.project(:title)
26
28
  #
27
29
  # @example Eliminating duplicate solutions
28
30
  # solutions.distinct
@@ -58,6 +60,22 @@ module RDF; class Query
58
60
  super
59
61
  end
60
62
 
63
+ ##
64
+ # Returns hash of bindings from each solution. Each bound variable will have
65
+ # an array of bound values representing those from each solution, where a given
66
+ # solution will have just a single value for each bound variable
67
+ # @return [Hash{Symbol => Array<RDF::Term>}]
68
+ def bindings
69
+ bindings = {}
70
+ each do |solution|
71
+ solution.each do |key, value|
72
+ bindings[key] ||= []
73
+ bindings[key] << value
74
+ end
75
+ end
76
+ bindings
77
+ end
78
+
61
79
  ##
62
80
  # Filters this solution sequence by the given `criteria`.
63
81
  #
@@ -87,18 +105,35 @@ module RDF; class Query
87
105
  ##
88
106
  # Reorders this solution sequence by the given `variables`.
89
107
  #
90
- # @param [Array<Symbol, #to_sym>] variables
108
+ # Variables may be symbols or {Query::Variable} instances.
109
+ # A variable may also be a Procedure/Lambda, compatible with {::Enumerable#sort}.
110
+ # This takes two arguments (solutions) and returns -1, 0, or 1 equivalently to <=>.
111
+ #
112
+ # If called with a block, variables are ignored, and the block is invoked with
113
+ # pairs of solutions. The block is expected to return -1, 0, or 1 equivalently to <=>.
114
+ #
115
+ # @param [Array<Proc, Query::Variable, Symbol, #to_sym>] variables
116
+ # @yield [solution]
117
+ # @yieldparam [RDF::Query::Solution] q
118
+ # @yieldparam [RDF::Query::Solution] b
119
+ # @yieldreturn [Integer] -1, 0, or 1 depending on value of comparator
91
120
  # @return [void] `self`
92
- def order(*variables)
93
- if variables.empty?
121
+ def order(*variables, &block)
122
+ if variables.empty? && !block_given?
94
123
  raise ArgumentError, "wrong number of arguments (0 for 1)"
95
124
  else
96
- # TODO: support for descending sort, e.g. `order(:s => :asc, :p => :desc)`
97
- variables.map!(&:to_sym)
98
125
  self.sort! do |a, b|
99
- a = variables.map { |variable| a[variable].to_s } # FIXME
100
- b = variables.map { |variable| b[variable].to_s } # FIXME
101
- a <=> b
126
+ if block_given?
127
+ block.call((a.is_a?(Solution) ? a : Solution.new(a)), (b.is_a?(Solution) ? b : Solution.new(b)))
128
+ else
129
+ # Try each variable until a difference is found.
130
+ variables.inject(nil) do |memo, v|
131
+ memo || begin
132
+ comp = v.is_a?(Proc) ? v.call(a, b) : (v = v.to_sym; a[v] <=> b[v])
133
+ comp == 0 ? false : comp
134
+ end
135
+ end || 0
136
+ end
102
137
  end
103
138
  end
104
139
  self
@@ -102,6 +102,23 @@ class RDF::Query
102
102
  value.nil?
103
103
  end
104
104
 
105
+ ##
106
+ # Returns `true` if this variable is distinguished.
107
+ #
108
+ # @return [Boolean]
109
+ def distinguished?
110
+ @distinguished.nil? || @distinguished
111
+ end
112
+
113
+ ##
114
+ # Sets if variable is distinguished or non-distinguished.
115
+ # By default, variables are distinguished
116
+ #
117
+ # @return [Boolean]
118
+ def distinguished=(value)
119
+ @distinguished = value
120
+ end
121
+
105
122
  ##
106
123
  # Rebinds this variable to the given `value`.
107
124
  #
@@ -153,14 +170,21 @@ class RDF::Query
153
170
 
154
171
  ##
155
172
  # Returns `true` if this variable is equivalent to a given `other`
156
- # variable.
173
+ # variable. Or, to another Term if bound, or to any other Term
157
174
  #
158
175
  # @param [Object] other
159
176
  # @return [Boolean] `true` or `false`
160
177
  # @since 0.3.0
161
178
  def eql?(other)
162
- other.is_a?(RDF::Query::Variable) && @name.eql?(other.name)
179
+ if unbound?
180
+ other.is_a?(RDF::Term) # match any Term when unbound
181
+ elsif other.is_a?(RDF::Query::Variable)
182
+ @name.eql?(other.name)
183
+ else
184
+ value.eql?(other)
185
+ end
163
186
  end
187
+ alias_method :==, :eql?
164
188
 
165
189
  ##
166
190
  # Compares this variable with the given value.
@@ -169,7 +193,7 @@ class RDF::Query
169
193
  # @return [Boolean]
170
194
  def ===(other)
171
195
  if unbound?
172
- true # match anything when unbound
196
+ other.is_a?(RDF::Term) # match any Term when unbound
173
197
  else
174
198
  value === other
175
199
  end
@@ -178,9 +202,20 @@ class RDF::Query
178
202
  ##
179
203
  # Returns a string representation of this variable.
180
204
  #
205
+ # Distinguished variables are indicated with a single `?`.
206
+ #
207
+ # Non-distinguished variables are indicated with a double `??`
208
+ #
209
+ # @example
210
+ # v = Variable.new("a")
211
+ # v.to_s => '?a'
212
+ # v.distinguished = false
213
+ # v.to_s => '??a'
214
+ #
181
215
  # @return [String]
182
216
  def to_s
183
- unbound? ? "?#{name}" : "?#{name}=#{value}"
217
+ prefix = distinguished? ? '?' : "??"
218
+ unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}"
184
219
  end
185
220
  end # Variable
186
221
  end # RDF::Query
@@ -55,6 +55,8 @@ module RDF
55
55
  ##
56
56
  # Finds an RDF reader class based on the given criteria.
57
57
  #
58
+ # If the reader class has a defined format, use that.
59
+ #
58
60
  # @overload for(format)
59
61
  # Finds an RDF reader class based on a symbolic name.
60
62
  #
@@ -75,16 +77,23 @@ module RDF
75
77
  # @option options [Symbol, #to_sym] :file_extension (nil)
76
78
  # @option options [String, #to_s] :content_type (nil)
77
79
  # @return [Class]
80
+ # @option options [String] :sample (nil)
81
+ # A sample of input used for performing format detection.
82
+ # If we find no formats, or we find more than one, and we have a sample, we can
83
+ # perform format detection to find a specific format to use, in which case
84
+ # we pick the first one we find
85
+ # @return [Class]
86
+ # @yieldreturn [String] another way to provide a sample, allows lazy for retrieving the sample.
78
87
  #
79
88
  # @return [Class]
80
- def self.for(options = {})
81
- if format = Format.for(options)
89
+ def self.for(options = {}, &block)
90
+ if format = self.format || Format.for(options, &block)
82
91
  format.reader
83
92
  end
84
93
  end
85
94
 
86
95
  ##
87
- # Retrieves the RDF serialization format class for this writer class.
96
+ # Retrieves the RDF serialization format class for this reader class.
88
97
  #
89
98
  # @return [Class]
90
99
  def self.format(klass = nil)
@@ -118,7 +127,12 @@ module RDF
118
127
  format_options = options.dup
119
128
  format_options[:content_type] ||= file.content_type if file.respond_to?(:content_type)
120
129
  format_options[:file_name] ||= filename
121
- reader = self.for(format_options[:format] || format_options)
130
+ reader = self.for(format_options[:format] || format_options) do
131
+ # Return a sample from the input file
132
+ sample = file.read(1000)
133
+ file.rewind
134
+ sample
135
+ end
122
136
  if reader
123
137
  reader.new(file, options, &block)
124
138
  else
@@ -127,6 +141,23 @@ module RDF
127
141
  end
128
142
  end
129
143
 
144
+ ##
145
+ # Returns a symbol appropriate to use with RDF::Reader.for()
146
+ # @return [Symbol]
147
+ def self.to_sym
148
+ elements = self.to_s.split("::")
149
+ sym = elements.pop
150
+ sym = elements.pop if sym == 'Reader'
151
+ sym.downcase.to_s.to_sym
152
+ end
153
+
154
+ ##
155
+ # Returns a symbol appropriate to use with RDF::Reader.for()
156
+ # @return [Symbol]
157
+ def to_sym
158
+ self.class.to_sym
159
+ end
160
+
130
161
  ##
131
162
  # Initializes the reader.
132
163
  #
@@ -177,6 +208,18 @@ module RDF
177
208
  # @since 0.3.0
178
209
  attr_reader :options
179
210
 
211
+ ##
212
+ # Returns the base URI determined by this reader.
213
+ #
214
+ # @example
215
+ # reader.prefixes[:dc] #=> RDF::URI('http://purl.org/dc/terms/')
216
+ #
217
+ # @return [Hash{Symbol => RDF::URI}]
218
+ # @since 0.3.0
219
+ def base_uri
220
+ @options[:base_uri]
221
+ end
222
+
180
223
  ##
181
224
  # Returns the URI prefixes currently defined for this reader.
182
225
  #
@@ -70,6 +70,8 @@ module RDF
70
70
  # Loads one or more RDF files into a new transient in-memory repository.
71
71
  #
72
72
  # @param [String, Array<String>] filenames
73
+ # @param [Hash{Symbol => Object}] options
74
+ # Options from {RDF::Reader#initialize}, {RDF::Format.for} and {RDF::Repository#initialize}
73
75
  # @yield [repository]
74
76
  # @yieldparam [Repository]
75
77
  # @return [void]
@@ -324,6 +326,8 @@ module RDF
324
326
  protected
325
327
 
326
328
  ##
329
+ # Match elements with eql?, not ==
330
+ # Context of `false` matches default context. Unbound variable matches non-false context
327
331
  # @private
328
332
  # @see RDF::Queryable#query
329
333
  def query_pattern(pattern, &block)
@@ -334,16 +338,16 @@ module RDF
334
338
 
335
339
  cs = @data.has_key?(context) ? {context => @data[context]} : @data.dup
336
340
  cs.each do |c, ss|
337
- next unless context.nil? || context == c
341
+ next unless context.nil? || context == false && !c || context.eql?(c)
338
342
  ss = ss.has_key?(subject) ? {subject => ss[subject]} : ss.dup
339
343
  ss.each do |s, ps|
340
- next unless subject.nil? || subject == s
344
+ next unless subject.nil? || subject.eql?(s)
341
345
  ps = ps.has_key?(predicate) ? {predicate => ps[predicate]} : ps.dup
342
346
  ps.each do |p, os|
343
- next unless predicate.nil? || predicate == p
347
+ next unless predicate.nil? || predicate.eql?(p)
344
348
  os = os.dup # TODO: is this really needed?
345
349
  os.each do |o|
346
- next unless object.nil? || object == o
350
+ next unless object.nil? || object.eql?(o)
347
351
  block.call(RDF::Statement.new(s, p, o, :context => c.equal?(DEFAULT_CONTEXT) ? nil : c))
348
352
  end
349
353
  end