Merge lp:~rockwalrus/activerdf/devel into lp:activerdf

Proposed by Rockwalrus
Status: Needs review
Proposed branch: lp:~rockwalrus/activerdf/devel
Merge into: lp:activerdf
Diff against target: None lines
To merge this branch: bzr merge lp:~rockwalrus/activerdf/devel
Reviewer Review Type Date Requested Status
ActiveRDF Pending
Review via email: mp+6713@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Rockwalrus (rockwalrus) wrote :

Support for UNION queries.

Unmerged revisions

466. By Rockwalrus

Support for UNION queries.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'activerdf-rdflite/lib/activerdf_rdflite/rdflite.rb'
--- activerdf-rdflite/lib/activerdf_rdflite/rdflite.rb 2008-06-11 12:11:39 +0000
+++ activerdf-rdflite/lib/activerdf_rdflite/rdflite.rb 2009-05-20 03:36:52 +0000
@@ -27,7 +27,7 @@
27class RDFLite < ActiveRdfAdapter27class RDFLite < ActiveRdfAdapter
28 ConnectionPool.register_adapter(:rdflite,self)28 ConnectionPool.register_adapter(:rdflite,self)
29 bool_accessor :keyword_search, :reasoning29 bool_accessor :keyword_search, :reasoning
3030
31 # instantiates RDFLite database31 # instantiates RDFLite database
32 # available parameters:32 # available parameters:
33 # * :location => filepath (defaults to memory)33 # * :location => filepath (defaults to memory)
@@ -235,7 +235,20 @@
235 # translates ActiveRDF query into internal sqlite query string235 # translates ActiveRDF query into internal sqlite query string
236 def translate(query)236 def translate(query)
237 where, conditions = construct_where(query)237 where, conditions = construct_where(query)
238 [construct_select(query) + construct_join(query) + where + construct_sort(query) + construct_limit(query), conditions]238
239 statement = construct_select(query) + construct_join(query.where_clauses) + where
240
241 query.union_groups.each do |union|
242 where, new_conditions = construct_union_where(union, query.reasoning?)
243
244 conditions += new_conditions
245
246 statement << "union " + construct_select(query, union) + construct_join(union.clauses) + where
247 end
248
249
250 statement << construct_sort(query) + construct_limit(query)
251 [statement, conditions]
239 end252 end
240253
241 private254 private
@@ -243,14 +256,18 @@
243 SPOC = ['s','p','o','c']256 SPOC = ['s','p','o','c']
244257
245 # construct select clause258 # construct select clause
246 def construct_select(query)259 def construct_select(query, union = nil)
247 # ASK queries counts the results, and return true if results > 0260 # ASK queries counts the results, and return true if results > 0
248 return "select count(*)" if query.ask?261 return "select count(*)" if query.ask?
249262
250 # add select terms for each selectclause in the query263 # add select terms for each selectclause in the query
251 # the term names depend on the join conditions, e.g. t0.s or t1.p264 # the term names depend on the join conditions, e.g. t0.s or t1.p
252 select = query.select_clauses.collect do |term|265 select = query.select_clauses.collect do |term|
253 variable_name(query, term)266 if union.nil?
267 variable_name(query, term)
268 else
269 union_variable_name(union, term)
270 end
254 end271 end
255272
256 # add possible distinct and count functions to select clause273 # add possible distinct and count functions to select clause
@@ -274,7 +291,7 @@
274291
275 clause << " limit #{limit} offset #{offset}"292 clause << " limit #{limit} offset #{offset}"
276 clause293 clause
277 end294 end
278295
279 # sort query results on variable clause (optionally)296 # sort query results on variable clause (optionally)
280 def construct_sort(query)297 def construct_sort(query)
@@ -294,13 +311,13 @@
294 # only, and we should only alias tables we didnt alias yet)311 # only, and we should only alias tables we didnt alias yet)
295 # we should only look for one join clause in each where-clause: when we find 312 # we should only look for one join clause in each where-clause: when we find
296 # one, we skip the rest of the variables in this clause.313 # one, we skip the rest of the variables in this clause.
297 def construct_join(query)314 def construct_join(clauses)
298 join_stmt = ''315 join_stmt = ''
299316
300 # no join necessary if only one where clause given317 # no join necessary if only one where clause given
301 return ' from triple as t0 ' if query.where_clauses.size == 1318 return ' from triple as t0 ' if clauses.size == 1
302319
303 where_clauses = query.where_clauses.flatten320 where_clauses = clauses.flatten
304 considering = where_clauses.uniq.select{|w| w.is_a?(Symbol)}321 considering = where_clauses.uniq.select{|w| w.is_a?(Symbol)}
305322
306 # constructing hash with indices for all terms323 # constructing hash with indices for all terms
@@ -370,32 +387,7 @@
370387
371 # construct where clause388 # construct where clause
372 def construct_where(query)389 def construct_where(query)
373 # collecting where clauses, these will be added to the sql string later390 where, right_hand_sides = convert_where_clauses(query.where_clauses, query.reasoning?)
374 where = []
375
376 # collecting all the right-hand sides of where clauses (e.g. where name =
377 # 'abc'), to add to query string later using ?-notation, because then
378 # sqlite will automatically encode quoted literals correctly
379 right_hand_sides = []
380
381 # convert each where clause to SQL:
382 # add where clause for each subclause, except if it's a variable
383 query.where_clauses.each_with_index do |clause,level|
384 raise ActiveRdfError, "where clause #{clause} is not a triple" unless clause.is_a?(Array)
385 clause.each_with_index do |subclause, i|
386 # dont add where clause for variables
387 unless subclause.is_a?(Symbol) || subclause.nil?
388 conditions = compute_where_condition(i, subclause, query.reasoning? && reasoning?)
389 if conditions.size == 1
390 where << "t#{level}.#{SPOC[i]} = ?"
391 right_hand_sides << conditions.first
392 else
393 conditions = conditions.collect {|c| "'#{c}'"}
394 where << "t#{level}.#{SPOC[i]} in (#{conditions.join(',')})"
395 end
396 end
397 end
398 end
399391
400 # if keyword clause given, convert it using keyword index392 # if keyword clause given, convert it using keyword index
401 if query.keyword? && keyword_search?393 if query.keyword? && keyword_search?
@@ -418,6 +410,48 @@
418 ["where " + where.join(' and '), right_hand_sides]410 ["where " + where.join(' and '), right_hand_sides]
419 end411 end
420 end412 end
413
414# construct union where clause
415 def construct_union_where(union, reasoning)
416 where, right_hand_sides = convert_where_clauses(union.clauses, reasoning)
417
418 if where.empty?
419 ['',[]]
420 else
421 ["where " + where.join(' and '), right_hand_sides]
422 end
423 end
424
425 def convert_where_clauses(clauses, reasoning)
426 # collecting where clauses, these will be added to the sql string later
427 where = []
428
429 # collecting all the right-hand sides of where clauses (e.g. where name =
430 # 'abc'), to add to query string later using ?-notation, because then
431 # sqlite will automatically encode quoted literals correctly
432 right_hand_sides = []
433
434 # convert each where clause to SQL:
435 # add where clause for each subclause, except if it's a variable
436 clauses.each_with_index do |clause,level|
437 raise ActiveRdfError, "where clause #{clause} is not a triple" unless clause.is_a?(Array)
438 clause.each_with_index do |subclause, i|
439 # dont add where clause for variables
440 unless subclause.is_a?(Symbol) || subclause.nil?
441 conditions = compute_where_condition(i, subclause, reasoning && reasoning?)
442 if conditions.size == 1
443 where << "t#{level}.#{SPOC[i]} = ?"
444 right_hand_sides << conditions.first
445 else
446 conditions = conditions.collect {|c| "'#{c}'"}
447 where << "t#{level}.#{SPOC[i]} in (#{conditions.join(',')})"
448 end
449 end
450 end
451 end
452
453 return where, right_hand_sides
454 end
421455
422 def compute_where_condition(index, subclause, reasoning)456 def compute_where_condition(index, subclause, reasoning)
423 conditions = [subclause]457 conditions = [subclause]
@@ -490,6 +524,20 @@
490 termspo = SPOC[index % 4]524 termspo = SPOC[index % 4]
491 return "#{termtable}.#{termspo}"525 return "#{termtable}.#{termspo}"
492 end526 end
527
528 def union_variable_name(union,term)
529 # look up the first occurence of this term in the where clauses, and compute
530 # the level and s/p/o position of it
531 index = union.clauses.flatten.index(term)
532
533 if index.nil?
534 raise ActiveRdfError, "unbound variable :#{term.to_s} in select of #{union}"
535 end
536
537 termtable = "t#{index / 4}"
538 termspo = SPOC[index % 4]
539 return "#{termtable}.#{termspo}"
540 end
493541
494 # wrap resources into ActiveRDF resources, literals into Strings542 # wrap resources into ActiveRDF resources, literals into Strings
495 def wrap(query, results)543 def wrap(query, results)
496544
=== modified file 'activerdf-redland/lib/activerdf_redland/redland.rb'
--- activerdf-redland/lib/activerdf_redland/redland.rb 2008-11-28 09:29:34 +0000
+++ activerdf-redland/lib/activerdf_redland/redland.rb 2009-05-20 03:36:52 +0000
@@ -106,6 +106,7 @@
106 def query(query)106 def query(query)
107 qs = Query2SPARQL.translate(query)107 qs = Query2SPARQL.translate(query)
108 $activerdflog.debug "RedlandAdapter: executing SPARQL query #{qs}"108 $activerdflog.debug "RedlandAdapter: executing SPARQL query #{qs}"
109 $activerdflog.warn "RedlandAdapter: UNION not supported before Rasqal 0.9.17" unless query.union_groups.empty? || Redland::rasqal_version_decimal > 916
109 110
110 clauses = query.select_clauses.size111 clauses = query.select_clauses.size
111 redland_query = Redland::Query.new(qs, 'sparql')112 redland_query = Redland::Query.new(qs, 'sparql')
112113
=== modified file 'lib/active_rdf/queryengine/query.rb'
--- lib/active_rdf/queryengine/query.rb 2007-09-20 20:35:26 +0000
+++ lib/active_rdf/queryengine/query.rb 2009-05-20 03:36:52 +0000
@@ -6,7 +6,7 @@
6# data source. In all clauses symbols represent variables: 6# data source. In all clauses symbols represent variables:
7# Query.new.select(:s).where(:s,:p,:o).7# Query.new.select(:s).where(:s,:p,:o).
8class Query8class Query
9 attr_reader :select_clauses, :where_clauses, :sort_clauses, :keywords, :limits, :offsets, :reverse_sort_clauses, :filter_clauses9 attr_reader :select_clauses, :where_clauses, :sort_clauses, :keywords, :limits, :offsets, :reverse_sort_clauses, :filter_clauses, :union_groups
1010
11 bool_accessor :distinct, :ask, :select, :count, :keyword, :reasoning11 bool_accessor :distinct, :ask, :select, :count, :keyword, :reasoning
1212
@@ -21,6 +21,7 @@
21 @keywords = {}21 @keywords = {}
22 @reasoning = true22 @reasoning = true
23 @reverse_sort_clauses = []23 @reverse_sort_clauses = []
24 @union_groups = []
24 end25 end
2526
26 # Clears the select clauses27 # Clears the select clauses
@@ -159,6 +160,72 @@
159 self160 self
160 end161 end
161162
163 class UnionGroup
164 attr_reader :clauses, :filter_clauses
165
166 def initialize
167 @clauses = []
168 @filter_clauses = []
169 end
170
171 # adds a clause to this union group
172 def where s,p,o,c=nil
173 unless s.respond_to?(:uri) or s.is_a?(Symbol)
174 raise(ActiveRdfError, "cannot add a where clause with s #{s}: s must be a resource or a variable")
175 end
176 unless p.respond_to?(:uri) or p.is_a?(Symbol)
177 raise(ActiveRdfError, "cannot add a where clause with p #{p}: p must be a resource or a variable")
178 end
179
180 @clauses << [s,p,o,c].collect{|arg| parametrise(arg)}
181
182 self
183 end
184
185 # adds one or more generic filters
186 # NOTE: you have to use SPARQL syntax for variables, eg. regex(?s, 'abc')
187 def filter *s
188 # add filter clauses
189 @filter_clauses << s
190 @filter_clauses.uniq!
191
192 self
193 end
194
195 private
196 #borrowed from outer class
197 def parametrise s
198 case s
199 when Symbol, RDFS::Resource, Literal, Class
200 s
201 when nil
202 nil
203 else
204 '"' + s.to_s + '"'
205 end
206 end
207 end
208
209 # adds a union group to this query
210 #
211 # usage:: union_group = Query.new.select(:s).where(:s, :p, 'eyal').union
212 # usage:: Query.new.select(:s).where(:s, :p, 'eyal').union {|g| g.where(:s, :p, 'benjamin')}.execute
213 # usage:: Query.new.select(:s).where(:s, :p, 'eyal').union(:s, :p, 'benjamin').execute
214 def union *args
215 union_group = UnionGroup.new
216 @union_groups << union_group
217
218 union_group.where *args unless args.empty?
219
220 yield(union_group) if block_given?
221
222 if !args.empty? || block_given?
223 self
224 else
225 union_group
226 end
227 end
228
162 # Adds keyword constraint to the query. You can use all Ferret query syntax in 229 # Adds keyword constraint to the query. You can use all Ferret query syntax in
163 # the constraint (e.g. keyword_where(:s,'eyal|benjamin')230 # the constraint (e.g. keyword_where(:s,'eyal|benjamin')
164 def keyword_where s,o231 def keyword_where s,o
165232
=== modified file 'lib/active_rdf/queryengine/query2sparql.rb'
--- lib/active_rdf/queryengine/query2sparql.rb 2008-02-08 12:37:42 +0000
+++ lib/active_rdf/queryengine/query2sparql.rb 2009-05-20 03:36:52 +0000
@@ -13,7 +13,8 @@
13 select_clauses = query.select_clauses.collect{|s| construct_clause(s)}13 select_clauses = query.select_clauses.collect{|s| construct_clause(s)}
1414
15 str << "SELECT #{distinct}#{select_clauses.join(' ')} "15 str << "SELECT #{distinct}#{select_clauses.join(' ')} "
16 str << "WHERE { #{where_clauses(query)} #{filter_clauses(query)}} "16 str << "WHERE { { #{where_clauses(query)} #{filter_clauses(query.filter_clauses)}} #{union_groups(query)}} "
17 str <<
17 str << "LIMIT #{query.limits} " if query.limits18 str << "LIMIT #{query.limits} " if query.limits
18 str << "OFFSET #{query.offsets} " if query.offsets19 str << "OFFSET #{query.offsets} " if query.offsets
19 elsif query.ask?20 elsif query.ask?
@@ -24,8 +25,8 @@
24 end25 end
2526
26 # concatenate filters in query27 # concatenate filters in query
27 def self.filter_clauses(query)28 def self.filter_clauses(filter_clauses)
28 "FILTER (#{query.filter_clauses.join(" && ")})" unless query.filter_clauses.empty?29 "FILTER (#{filter_clauses.join(" && ")})" unless filter_clauses.empty?
29 end30 end
3031
31 # concatenate each where clause using space (e.g. 's p o')32 # concatenate each where clause using space (e.g. 's p o')
@@ -44,18 +45,22 @@
44 end45 end
45 end46 end
4647
47 where_clauses = query.where_clauses.collect do |s,p,o,c|48 statements(query.where_clauses)
49 end
50
51 def self.statements(clauses)
52 where_clauses = clauses.collect do |s,p,o,c|
48 # does there where clause use a context ? 53 # does there where clause use a context ?
49 if c.nil?54 if c.nil?
50 [s,p,o].collect {|term| construct_clause(term) }.join(' ')55 [s,p,o].collect {|term| construct_clause(term) }.join(' ')
51 else56 else
52 "GRAPH #{construct_clause(c)} { #{construct_clause(s)} #{construct_clause(p)} #{construct_clause(o)} }"57 "GRAPH #{construct_clause(c)} { #{construct_clause(s)} #{construct_clause(p)} #{construct_clause(o)} }"
53 end58 end
54 end59 end
5560
56 "#{where_clauses.join(' . ')} ."61 "#{where_clauses.join(' . ')} ."
57 end62 end
5863
59 def self.construct_clause(term)64 def self.construct_clause(term)
60 if term.is_a?(Symbol)65 if term.is_a?(Symbol)
61 "?#{term}"66 "?#{term}"
@@ -63,6 +68,10 @@
63 term.to_ntriple68 term.to_ntriple
64 end69 end
65 end70 end
71
72 def self.union_groups(query)
73 query.union_groups.collect {|union| "UNION { #{statements(union.clauses)} #{filter_clauses(union.filter_clauses)}} "}.join
74 end
6675
67 def self.sparql_engine76 def self.sparql_engine
68 sparql_adapters = ConnectionPool.read_adapters.select{|adp| adp.is_a? SparqlAdapter}77 sparql_adapters = ConnectionPool.read_adapters.select{|adp| adp.is_a? SparqlAdapter}
6978
=== modified file 'test/queryengine/test_query2sparql.rb'
--- test/queryengine/test_query2sparql.rb 2008-02-08 12:37:42 +0000
+++ test/queryengine/test_query2sparql.rb 2009-05-20 03:36:52 +0000
@@ -19,7 +19,7 @@
19 query.where(:s, RDFS::Resource.new('predicate'), 30)19 query.where(:s, RDFS::Resource.new('predicate'), 30)
2020
21 generated = Query2SPARQL.translate(query)21 generated = Query2SPARQL.translate(query)
22 expected = "SELECT ?s WHERE { ?s <predicate> \"30\"^^<http://www.w3.org/2001/XMLSchema#integer> . } "22 expected = "SELECT ?s WHERE { { ?s <predicate> \"30\"^^<http://www.w3.org/2001/XMLSchema#integer> . } } "
23 assert_equal expected, generated23 assert_equal expected, generated
2424
25 query = Query.new25 query = Query.new
@@ -27,7 +27,7 @@
27 query.where(:s, RDFS::Resource.new('foaf:age'), :a)27 query.where(:s, RDFS::Resource.new('foaf:age'), :a)
28 query.where(:a, RDFS::Resource.new('rdf:type'), RDFS::Resource.new('xsd:int'))28 query.where(:a, RDFS::Resource.new('rdf:type'), RDFS::Resource.new('xsd:int'))
29 generated = Query2SPARQL.translate(query)29 generated = Query2SPARQL.translate(query)
30 expected = "SELECT ?s WHERE { ?s <foaf:age> ?a . ?a <rdf:type> <xsd:int> . } "30 expected = "SELECT ?s WHERE { { ?s <foaf:age> ?a . ?a <rdf:type> <xsd:int> . } } "
31 assert_equal expected, generated31 assert_equal expected, generated
32 end32 end
3333
@@ -36,7 +36,7 @@
36 query.distinct(:s)36 query.distinct(:s)
37 query.where(:s, RDFS::Resource.new('foaf:age'), :a)37 query.where(:s, RDFS::Resource.new('foaf:age'), :a)
38 generated = Query2SPARQL.translate(query)38 generated = Query2SPARQL.translate(query)
39 expected = "SELECT DISTINCT ?s WHERE { ?s <foaf:age> ?a . } "39 expected = "SELECT DISTINCT ?s WHERE { { ?s <foaf:age> ?a . } } "
40 assert_equal expected, generated40 assert_equal expected, generated
41 end41 end
4242
@@ -46,4 +46,15 @@
46 q2 = Query.new.select(:s).select(:a)46 q2 = Query.new.select(:s).select(:a)
47 assert_equal Query2SPARQL.translate(q1),Query2SPARQL.translate(q2)47 assert_equal Query2SPARQL.translate(q1),Query2SPARQL.translate(q2)
48 end48 end
49
50 def test_union
51 query = Query.new
52 query.distinct(:s)
53 query.where(:s, RDFS::Resource.new('foaf:age'), :a)
54 query.union.where(:s, RDFS::Resource.new('foaf:name'), :n)
55 query.union{|u| u.where(:s, RDFS::Resource.new('foaf:mbox'), :m)}
56 generated = Query2SPARQL.translate(query)
57 expected = "SELECT DISTINCT ?s WHERE { { ?s <foaf:age> ?a . } UNION { ?s <foaf:name> ?n . } UNION { ?s <foaf:mbox> ?m . } } "
58 assert_equal expected, generated
59 end
49end60end

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: