graffiti 2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Graffiti RDF Store tests
4
+ #
5
+ # Copyright (c) 2002-2009 Dmitry Borodaenko <angdraug@debian.org>
6
+ #
7
+ # This program is free software.
8
+ # You can distribute/modify this program under the terms of
9
+ # the GNU General Public License version 3 or later.
10
+ #
11
+ # vim: et sw=2 sts=2 ts=8 tw=0
12
+
13
+ require 'test/unit'
14
+ require 'yaml'
15
+ require 'sequel'
16
+ require 'graffiti'
17
+
18
+ include Graffiti
19
+
20
+ class TC_Storage < Test::Unit::TestCase
21
+
22
+ def setup
23
+ config = File.open(
24
+ File.join(
25
+ File.dirname(File.dirname(__FILE__)),
26
+ 'doc', 'examples', 'samizdat-rdf-config.yaml'
27
+ )
28
+ ) {|f| YAML.load(f.read) }
29
+ @db = create_mock_db
30
+ @store = Store.new(@db, config)
31
+ @ns = @store.config.ns
32
+ end
33
+
34
+ def test_query_select
35
+ squish = %{
36
+ SELECT ?msg, ?title, ?name, ?date, ?rating
37
+ WHERE (dc::title ?msg ?title)
38
+ (dc::creator ?msg ?creator)
39
+ (s::fullName ?creator ?name)
40
+ (dc::date ?msg ?date)
41
+ (rdf::subject ?stmt ?msg)
42
+ (rdf::predicate ?stmt dc::relation)
43
+ (rdf::object ?stmt s::Quality)
44
+ (s::rating ?stmt ?rating)
45
+ LITERAL ?rating >= -1
46
+ ORDER BY ?rating DESC
47
+ USING PRESET NS}
48
+
49
+ sql = "SELECT DISTINCT c.id AS msg, c.title AS title, b.full_name AS name, d.published_date AS date, a.rating AS rating
50
+ FROM statement AS a
51
+ INNER JOIN message AS c ON (c.id = a.subject)
52
+ INNER JOIN member AS b ON (c.creator = b.id)
53
+ INNER JOIN resource AS d ON (c.id = d.id)
54
+ INNER JOIN resource AS e ON (a.object = e.id) AND (e.uriref = 't' AND e.label = 'http://www.nongnu.org/samizdat/rdf/schema#Quality')
55
+ INNER JOIN resource AS f ON (a.predicate = f.id) AND (f.uriref = 't' AND f.label = 'http://purl.org/dc/elements/1.1/relation')
56
+ WHERE (a.id IS NOT NULL)
57
+ AND (d.published_date IS NOT NULL)
58
+ AND (b.full_name IS NOT NULL)
59
+ AND (c.title IS NOT NULL)
60
+ AND (a.rating >= -1)
61
+ ORDER BY a.rating DESC"
62
+
63
+ test_squish_select(squish, sql) do |query|
64
+ assert_equal %w[?msg ?title ?name ?date ?rating], query.nodes
65
+ assert query.pattern.include?(["#{@ns['dc']}title", "?msg", "?title", nil, false])
66
+ assert_equal '?rating >= -1', query.literal
67
+ assert_equal '?rating', query.order
68
+ assert_equal 'DESC', query.order_dir
69
+ assert_equal @ns['s'], query.ns['s']
70
+ end
71
+
72
+ assert_equal [], @store.select_all(squish)
73
+ end
74
+
75
+ def test_query_assert
76
+ # initialize
77
+ query_text = %{
78
+ INSERT ?msg
79
+ UPDATE ?title = 'Test Message', ?content = 'Some ''text''.'
80
+ WHERE (dc::creator ?msg 1)
81
+ (dc::title ?msg ?title)
82
+ (s::content ?msg ?content)
83
+ USING dc FOR #{@ns['dc']}
84
+ s FOR #{@ns['s']}}
85
+ begin
86
+ query = SquishAssert.new(@store.config, query_text)
87
+ rescue
88
+ assert false, "SquishAssert initialization raised #{$!.class}: #{$!}"
89
+ end
90
+
91
+ # query parser
92
+ assert_equal ['?msg'], query.insert
93
+ assert_equal({'?title' => "'0'", '?content' => "'1'"}, query.update)
94
+ assert query.pattern.include?(["#{@ns['dc']}title", "?msg", "?title", nil, false])
95
+ assert_equal @ns['s'], query.ns['s']
96
+ assert_equal "'Test Message'", query.substitute_literals("'0'")
97
+ assert_equal "'Some ''text''.'", query.substitute_literals("'1'")
98
+
99
+ # mock db
100
+ ids = @store.assert(query_text)
101
+ assert_equal [1], ids
102
+ assert_equal 'Test Message', @db[:Message][:id => 1][:title]
103
+
104
+ id2 = @store.assert(query_text)
105
+ query_text = %{
106
+ UPDATE ?rating = :rating
107
+ WHERE (rdf::subject ?stmt :related)
108
+ (rdf::predicate ?stmt dc::relation)
109
+ (rdf::object ?stmt 1)
110
+ (s::voteProposition ?vote ?stmt)
111
+ (s::voteMember ?vote :member)
112
+ (s::voteRating ?vote ?rating)}
113
+ params = {:rating => -1, :related => 2, :member => 3}
114
+ ids = @store.assert(query_text, params)
115
+ assert_equal [], ids
116
+ assert vote = @db[:vote].order(:id).last
117
+ assert_equal -1, vote[:rating].to_i
118
+
119
+ params[:rating] = -2
120
+ @store.assert(query_text, params)
121
+ assert vote2 = @db[:vote].order(:id).last
122
+ assert_equal -2, vote2[:rating].to_i
123
+ assert_equal vote[:id], vote2[:id]
124
+ end
125
+
126
+ def test_query_assert_expression
127
+ query_text = %{
128
+ UPDATE ?rating = 2 * :rating
129
+ WHERE (rdf::subject ?stmt :related)
130
+ (rdf::predicate ?stmt dc::relation)
131
+ (rdf::object ?stmt 1)
132
+ (s::voteProposition ?vote ?stmt)
133
+ (s::voteMember ?vote :member)
134
+ (s::voteRating ?vote ?rating)}
135
+ params = {:rating => -1, :related => 2, :member => 3}
136
+ @store.assert(query_text, params)
137
+ assert vote = @db[:vote].order(:id).last
138
+ assert_equal -2, vote[:rating].to_i
139
+ end
140
+ private :test_query_assert_expression
141
+
142
+ def test_dangling_blank_node
143
+ squish = %{
144
+ SELECT ?msg
145
+ WHERE (s::inReplyTo ?msg ?parent)
146
+ USING s FOR #{@ns['s']}}
147
+
148
+ sql = "SELECT DISTINCT a.id AS msg
149
+ FROM resource AS a
150
+ INNER JOIN resource AS b ON (a.part_of_subproperty = b.id) AND (b.uriref = 't' AND b.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
151
+ WHERE (a.id IS NOT NULL)"
152
+
153
+ test_squish_select(squish, sql) do |query|
154
+ assert_equal %w[?msg], query.nodes
155
+ assert query.pattern.include?(["#{@ns['s']}inReplyTo", "?msg", "?parent", nil, false])
156
+ assert_equal @ns['s'], query.ns['s']
157
+ end
158
+ end
159
+
160
+ def test_external_resource_no_self_join
161
+ squish = %{SELECT ?id WHERE (s::id tag::Translation ?id)}
162
+
163
+ sql = "SELECT DISTINCT a.id AS id
164
+ FROM resource AS a
165
+ WHERE (a.id IS NOT NULL)
166
+ AND ((a.uriref = 't' AND a.label = 'http://www.nongnu.org/samizdat/rdf/tag#Translation'))"
167
+
168
+ test_squish_select(squish, sql) do |query|
169
+ assert_equal %w[?id], query.nodes
170
+ assert query.pattern.include?(["#{@ns['s']}id", "#{@ns['tag']}Translation", "?id", nil, false])
171
+ assert_equal @ns['s'], query.ns['s']
172
+ end
173
+ end
174
+
175
+ #def test_internal_resource
176
+ #end
177
+
178
+ #def test_external_subject_internal_property
179
+ #end
180
+
181
+ def test_except
182
+ squish = %{
183
+ SELECT ?msg
184
+ WHERE (dc::date ?msg ?date)
185
+ EXCEPT (s::inReplyTo ?msg ?parent)
186
+ (dct::isVersionOf ?msg ?version_of)
187
+ (dc::creator ?version_of 1)
188
+ ORDER BY ?date DESC}
189
+
190
+ sql = "SELECT DISTINCT b.id AS msg, b.published_date AS date
191
+ FROM resource AS b
192
+ LEFT JOIN (
193
+ SELECT b.id AS _field_f
194
+ FROM message AS a
195
+ INNER JOIN resource AS b ON (b.part_of = a.id)
196
+ INNER JOIN resource AS c ON (b.part_of_subproperty = c.id) AND (c.uriref = 't' AND c.label = 'http://purl.org/dc/terms/isVersionOf')
197
+ WHERE (a.creator = 1)
198
+ ) AS _subquery_a ON (b.id = _subquery_a._field_f)
199
+ LEFT JOIN resource AS d ON (b.part_of_subproperty = d.id) AND (d.uriref = 't' AND d.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
200
+ WHERE (b.published_date IS NOT NULL)
201
+ AND (b.id IS NOT NULL)
202
+ AND (_subquery_a._field_f IS NULL)
203
+ AND (d.id IS NULL)
204
+ ORDER BY b.published_date DESC"
205
+
206
+ test_squish_select(squish, sql)
207
+ end
208
+
209
+ def test_except_group_by
210
+ squish = %{
211
+ SELECT ?msg
212
+ WHERE (rdf::predicate ?stmt dc::relation)
213
+ (rdf::subject ?stmt ?msg)
214
+ (rdf::object ?stmt ?tag)
215
+ (dc::date ?stmt ?date)
216
+ (s::rating ?stmt ?rating FILTER ?rating >= 1.5)
217
+ (s::hidden ?msg ?hidden FILTER ?hidden = 'f')
218
+ EXCEPT (dct::isPartOf ?msg ?parent)
219
+ GROUP BY ?msg
220
+ ORDER BY max(?date) DESC}
221
+
222
+ sql = "SELECT DISTINCT a.subject AS msg, max(b.published_date)
223
+ FROM statement AS a
224
+ INNER JOIN resource AS b ON (a.id = b.id)
225
+ INNER JOIN message AS c ON (a.subject = c.id) AND (c.hidden = 'f')
226
+ INNER JOIN resource AS d ON (a.subject = d.id)
227
+ INNER JOIN resource AS e ON (a.predicate = e.id) AND (e.uriref = 't' AND e.label = 'http://purl.org/dc/elements/1.1/relation')
228
+ WHERE (c.hidden IS NOT NULL)
229
+ AND (b.published_date IS NOT NULL)
230
+ AND (a.object IS NOT NULL)
231
+ AND (a.rating IS NOT NULL)
232
+ AND (d.part_of IS NULL)
233
+ AND ((a.rating >= 1.5))
234
+ GROUP BY a.subject
235
+ ORDER BY max(b.published_date) DESC"
236
+
237
+ test_squish_select(squish, sql)
238
+ end
239
+
240
+ def test_optional
241
+ squish = %{
242
+ SELECT ?date, ?creator, ?lang, ?parent, ?version_of, ?hidden, ?open
243
+ WHERE (dc::date 1 ?date)
244
+ OPTIONAL (dc::creator 1 ?creator)
245
+ (dc::language 1 ?lang)
246
+ (s::inReplyTo 1 ?parent)
247
+ (dct::isVersionOf 1 ?version_of)
248
+ (s::hidden 1 ?hidden)
249
+ (s::openForAll 1 ?open)}
250
+
251
+ sql = "SELECT DISTINCT a.published_date AS date, b.creator AS creator, b.language AS lang, select_subproperty(a.part_of, d.id) AS parent, select_subproperty(a.part_of, c.id) AS version_of, b.hidden AS hidden, b.open AS open
252
+ FROM resource AS a
253
+ INNER JOIN message AS b ON (a.id = b.id)
254
+ LEFT JOIN resource AS c ON (a.part_of_subproperty = c.id) AND (c.uriref = 't' AND c.label = 'http://purl.org/dc/terms/isVersionOf')
255
+ LEFT JOIN resource AS d ON (a.part_of_subproperty = d.id) AND (d.uriref = 't' AND d.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
256
+ WHERE (a.published_date IS NOT NULL)
257
+ AND ((a.id = 1))"
258
+
259
+ test_squish_select(squish, sql)
260
+ end
261
+
262
+ def test_except_optional_transitive
263
+ squish = %{
264
+ SELECT ?msg
265
+ WHERE (rdf::subject ?stmt ?msg)
266
+ (rdf::predicate ?stmt dc::relation)
267
+ (rdf::object ?stmt ?tag)
268
+ (s::rating ?stmt ?rating FILTER ?rating > 0)
269
+ (dc::date ?msg ?date)
270
+ EXCEPT (dct::isPartOf ?msg ?parent)
271
+ OPTIONAL (dct::isPartOf ?tag ?supertag TRANSITIVE)
272
+ LITERAL ?tag = 1 OR ?supertag = 1
273
+ ORDER BY ?date DESC}
274
+
275
+ sql = "SELECT DISTINCT a.subject AS msg, b.published_date AS date
276
+ FROM statement AS a
277
+ INNER JOIN resource AS b ON (a.subject = b.id)
278
+ INNER JOIN resource AS d ON (a.predicate = d.id) AND (d.uriref = 't' AND d.label = 'http://purl.org/dc/elements/1.1/relation')
279
+ LEFT JOIN part AS c ON (a.object = c.id)
280
+ WHERE (a.id IS NOT NULL)
281
+ AND (b.published_date IS NOT NULL)
282
+ AND (a.rating IS NOT NULL)
283
+ AND (b.part_of IS NULL)
284
+ AND ((a.rating > 0))
285
+ AND (a.object = 1 OR c.part_of = 1)
286
+ ORDER BY b.published_date DESC"
287
+
288
+ test_squish_select(squish, sql)
289
+ end
290
+
291
+ def test_optional_connect_by_object
292
+ squish = %{
293
+ SELECT ?event
294
+ WHERE (ical::dtstart ?event ?dtstart FILTER ?dtstart >= 'now')
295
+ (ical::dtend ?event ?dtend)
296
+ OPTIONAL (s::rruleEvent ?rrule ?event)
297
+ (ical::until ?rrule ?until FILTER ?until IS NULL OR ?until > 'now')
298
+ LITERAL ?dtend > 'now' OR ?rrule IS NOT NULL
299
+ ORDER BY ?event DESC}
300
+
301
+ sql = "SELECT DISTINCT b.id AS event
302
+ FROM event AS b
303
+ LEFT JOIN recurrence AS a ON (b.id = a.event) AND (a.until IS NULL OR a.until > 'now')
304
+ WHERE (b.dtstart IS NOT NULL)
305
+ AND ((b.dtstart >= 'now'))
306
+ AND (b.dtend > 'now' OR a.id IS NOT NULL)
307
+ ORDER BY b.id DESC"
308
+
309
+ test_squish_select(squish, sql)
310
+ end
311
+ private :test_optional_connect_by_object
312
+
313
+ def test_many_to_many
314
+ # pretend that Vote is a many-to-many relation table
315
+ squish = %{
316
+ SELECT ?p, ?date
317
+ WHERE (s::voteRating ?p ?vote1 FILTER ?vote1 > 0)
318
+ (s::voteRating ?p ?vote2 FILTER ?vote2 < 0)
319
+ (dc::date ?p ?date)
320
+ ORDER BY ?date DESC}
321
+
322
+ sql = "SELECT DISTINCT a.id AS p, c.published_date AS date
323
+ FROM vote AS a
324
+ INNER JOIN vote AS b ON (a.id = b.id) AND (b.rating < 0)
325
+ INNER JOIN resource AS c ON (a.id = c.id)
326
+ WHERE (c.published_date IS NOT NULL)
327
+ AND (a.rating IS NOT NULL)
328
+ AND (b.rating IS NOT NULL)
329
+ AND ((a.rating > 0))
330
+ ORDER BY c.published_date DESC"
331
+
332
+ test_squish_select(squish, sql)
333
+ end
334
+
335
+ def test_update_null_and_subproperty
336
+ query_text =
337
+ %{INSERT ?msg
338
+ UPDATE ?parent = :parent
339
+ WHERE (dct::isPartOf ?msg ?parent)}
340
+ @store.assert(query_text, :id => 1, :parent => 3)
341
+ assert_equal 3, @db[:resource].filter(:id => 1).get(:part_of)
342
+
343
+ # check that subproperty is set
344
+ query_text =
345
+ %{UPDATE ?parent = :parent
346
+ WHERE (s::subTagOf :id ?parent)}
347
+ @store.assert(query_text, :id => 1, :parent => 3)
348
+ assert_equal 3, @db[:resource].filter(:id => 1).get(:part_of)
349
+ assert_equal 2, @db[:resource].filter(:id => 1).get(:part_of_subproperty)
350
+
351
+ # check that NULL is handled correctly and that subproperty is unset
352
+ query_text =
353
+ %{UPDATE ?parent = NULL
354
+ WHERE (dct::isPartOf :id ?parent)}
355
+ @store.assert(query_text, :id => 1)
356
+ assert_equal nil, @db[:resource].filter(:id => 1).get(:part_of)
357
+ assert_equal nil, @db[:resource].filter(:id => 1).get(:part_of_subproperty)
358
+ end
359
+
360
+ private
361
+
362
+ def test_squish_select(squish, sql)
363
+ begin
364
+ query = SquishSelect.new(@store.config, squish)
365
+ rescue
366
+ assert false, "SquishSelect initialization raised #{$!.class}: #{$!}"
367
+ end
368
+
369
+ yield query if block_given?
370
+
371
+ # query result
372
+ begin
373
+ sql1 = @store.select(query)
374
+ rescue
375
+ assert false, "select with pre-parsed query raised #{$!.class}: #{$!}"
376
+ end
377
+ begin
378
+ sql2 = @store.select(squish)
379
+ rescue
380
+ assert false, "select with query text raised #{$!.class}: #{$!}"
381
+ end
382
+ assert sql1 == sql2
383
+
384
+ # transform result
385
+ assert_equal normalize(sql), normalize(sql1),
386
+ "Query doesn't match. Expected:\n#{sql}\nReceived:\n#{sql1}"
387
+ end
388
+
389
+ def normalize(sql)
390
+ # alias labels and where conditions may be reordered, but the query string
391
+ # length should remain the same
392
+ sql.size
393
+ end
394
+
395
+ def create_mock_db
396
+ db = Sequel.sqlite(:quote_identifiers => false)
397
+
398
+ db.create_table(:resource) do
399
+ primary_key :id
400
+ Time :published_date
401
+ Integer :part_of
402
+ Integer :part_of_subproperty
403
+ Integer :part_sequence_number
404
+ TrueClass :literal
405
+ TrueClass :uriref
406
+ String :label
407
+ end
408
+
409
+ db.create_table(:statement) do
410
+ primary_key :id
411
+ Integer :subject
412
+ Integer :predicate
413
+ Integer :object
414
+ BigDecimal :rating, :size => [4, 2]
415
+ end
416
+
417
+ db.create_table(:member) do
418
+ primary_key :id
419
+ String :login
420
+ String :full_name
421
+ String :email
422
+ end
423
+
424
+ db.create_table(:message) do
425
+ primary_key :id
426
+ String :title
427
+ Integer :creator
428
+ String :format
429
+ String :language
430
+ TrueClass :open
431
+ TrueClass :hidden
432
+ TrueClass :locked
433
+ String :content
434
+ String :html_full
435
+ String :html_short
436
+ end
437
+
438
+ db.create_table(:vote) do
439
+ primary_key :id
440
+ Integer :proposition
441
+ Integer :member
442
+ BigDecimal :rating, :size => 2
443
+ end
444
+
445
+ db
446
+ end
447
+
448
+ def create_mock_member(db)
449
+ db[:member].insert(
450
+ :login => 'test',
451
+ :full_name => 'test',
452
+ :email => 'test@localhost'
453
+ )
454
+ end
455
+ end