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