rdf 0.3.3 → 0.3.4

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.
@@ -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