2 * Copyright (c) 2013 SURFnet bv
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*****************************************************************************
30 Contains lowest level test cases for the database backend implementation.
31 *****************************************************************************/
35 #include <cppunit/extensions/HelperMacros.h>
38 CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db);
40 static int dummy_print(const char *, va_list )
45 void test_a_db::setUp()
47 CPPUNIT_ASSERT(!system("mkdir testdir"));
51 void test_a_db::tearDown()
54 CPPUNIT_ASSERT(!system("rm -rf testdir"));
56 CPPUNIT_ASSERT(!system("rmdir /s /q testdir 2> nul"));
60 void test_a_db::checks_for_empty_connection_parameters()
62 DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
64 DB::Connection *connection = DB::Connection::Create("","TestToken");
65 CPPUNIT_ASSERT_EQUAL(connection, null);
67 connection = DB::Connection::Create("testdir","");
68 CPPUNIT_ASSERT_EQUAL(connection, null);
70 connection = DB::Connection::Create("","");
71 CPPUNIT_ASSERT_EQUAL(connection, null);
73 DB::setLogErrorHandler(eh);
76 void test_a_db::can_be_connected_to_database()
79 DB::Connection *connection = DB::Connection::Create("testdir","TestToken");
80 CPPUNIT_ASSERT(connection != null);
81 bool isConnected = connection->connect();
83 CPPUNIT_ASSERT(isConnected);
85 CPPUNIT_ASSERT_EQUAL(system("test -f ./testdir/TestToken"), 0);
87 CPPUNIT_ASSERT(GetFileAttributes("testdir\\TestToken") != INVALID_FILE_ATTRIBUTES);
91 CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection);
93 void test_a_db_with_a_connection::setUp()
96 connection = DB::Connection::Create("testdir","TestToken");
97 CPPUNIT_ASSERT(connection != null);
98 CPPUNIT_ASSERT(connection->connect());
101 void test_a_db_with_a_connection::tearDown()
103 CPPUNIT_ASSERT(connection != null);
106 test_a_db::tearDown();
109 void test_a_db_with_a_connection::can_prepare_statements()
111 DB::Statement statement = connection->prepare("PRAGMA database_list;");
112 CPPUNIT_ASSERT(statement.isValid());
115 void test_a_db_with_a_connection::can_perform_statements()
117 DB::Statement statement = connection->prepare("PRAGMA database_list;");
118 CPPUNIT_ASSERT(statement.isValid());
119 DB::Result result = connection->perform(statement);
120 CPPUNIT_ASSERT(result.isValid());
121 // only expect a single row in the result, so nextRow should now fail
122 CPPUNIT_ASSERT(!result.nextRow());
125 void test_a_db_with_a_connection::maintains_correct_refcounts()
127 DB::Statement statement = connection->prepare("PRAGMA database_list;");
128 CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);
130 DB::Statement statement1 = statement;
131 DB::Statement statement2 = statement;
132 CPPUNIT_ASSERT_EQUAL(statement.refcount(), 3);
133 CPPUNIT_ASSERT(statement1.isValid());
134 CPPUNIT_ASSERT(statement2.isValid());
136 CPPUNIT_ASSERT(statement.isValid());
137 CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);
139 DB::Result result = connection->perform(statement);
140 CPPUNIT_ASSERT(result.isValid());
142 // Statement is referenced by the result because it provides the query record cursor state.
143 CPPUNIT_ASSERT_EQUAL(statement.refcount(), 2);
145 result = DB::Result();
146 CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);
148 void test_a_db_with_a_connection::can_create_tables()
150 CPPUNIT_ASSERT(!connection->tableExists("object"));
151 DB::Statement cr_object = connection->prepare("create table object (id integer primary key autoincrement);");
152 CPPUNIT_ASSERT(connection->execute(cr_object));
153 CPPUNIT_ASSERT(connection->tableExists("object"));
156 CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection_with_tables);
158 void test_a_db_with_a_connection_with_tables::setUp()
160 test_a_db_with_a_connection::setUp();
164 CPPUNIT_ASSERT(!connection->tableExists("attribute_text"));
165 DB::Statement cr_attr_text = connection->prepare(
166 "create table attribute_text ("
169 "object_id integer references object(id) on delete cascade,"
170 "id integer primary key autoincrement)"
172 CPPUNIT_ASSERT(connection->execute(cr_attr_text));
173 CPPUNIT_ASSERT(connection->tableExists("attribute_text"));
176 CPPUNIT_ASSERT(!connection->tableExists("attribute_integer"));
177 DB::Statement cr_attr_integer = connection->prepare(
178 "create table attribute_integer ("
181 "object_id integer references object(id) on delete cascade,"
182 "id integer primary key autoincrement)"
184 CPPUNIT_ASSERT(connection->execute(cr_attr_integer));
185 CPPUNIT_ASSERT(connection->tableExists("attribute_integer"));
188 CPPUNIT_ASSERT(!connection->tableExists("attribute_blob"));
189 DB::Statement cr_attr_blob = connection->prepare(
190 "create table attribute_blob ("
193 "object_id integer references object(id) on delete cascade,"
194 "id integer primary key autoincrement)"
196 CPPUNIT_ASSERT(connection->execute(cr_attr_blob));
197 CPPUNIT_ASSERT(connection->tableExists("attribute_blob"));
200 CPPUNIT_ASSERT(!connection->tableExists("attribute_boolean"));
201 DB::Statement cr_attr_boolean = connection->prepare(
202 "create table attribute_boolean ("
205 "object_id integer references object(id) on delete cascade,"
206 "id integer primary key autoincrement)"
208 CPPUNIT_ASSERT(connection->execute(cr_attr_boolean));
209 CPPUNIT_ASSERT(connection->tableExists("attribute_boolean"));
211 // attribute_datetime
212 CPPUNIT_ASSERT(!connection->tableExists("attribute_datetime"));
213 DB::Statement cr_attr_datetime = connection->prepare(
214 "create table attribute_datetime ("
217 "object_id integer references object(id) on delete cascade,"
218 "id integer primary key autoincrement)"
220 CPPUNIT_ASSERT(connection->execute(cr_attr_datetime));
221 CPPUNIT_ASSERT(connection->tableExists("attribute_datetime"));
224 CPPUNIT_ASSERT(!connection->tableExists("attribute_real"));
225 DB::Statement cr_attr_real = connection->prepare(
226 "create table attribute_real ("
229 "object_id integer references object(id) on delete cascade,"
230 "id integer primary key autoincrement)"
232 CPPUNIT_ASSERT(connection->execute(cr_attr_real));
233 CPPUNIT_ASSERT(connection->tableExists("attribute_real"));
236 void test_a_db_with_a_connection_with_tables::tearDown()
238 test_a_db_with_a_connection::tearDown();
241 void test_a_db_with_a_connection_with_tables::can_insert_records()
243 DB::Statement statement = connection->prepare("insert into object default values");
244 CPPUNIT_ASSERT(connection->execute(statement));
245 long long object_id = connection->lastInsertRowId();
246 CPPUNIT_ASSERT(object_id != 0);
248 statement = connection->prepare(
249 "insert into attribute_text (value,type,object_id) values ('%s',%d,%lld)",
250 "testing testing testing",
253 CPPUNIT_ASSERT(connection->execute(statement));
256 void test_a_db_with_a_connection_with_tables::can_retrieve_records()
258 can_insert_records();
260 DB::Statement statement = connection->prepare(
261 "select value from attribute_text as t where t.type=%d",
263 DB::Result result = connection->perform(statement);
264 CPPUNIT_ASSERT_EQUAL(std::string(result.getString(1)), std::string("testing testing testing"));
267 void test_a_db_with_a_connection_with_tables::can_cascade_delete_objects_and_attributes()
269 can_insert_records();
271 DB::Statement statement = connection->prepare("select id from object");
272 DB::Result result = connection->perform(statement);
273 CPPUNIT_ASSERT(result.isValid());
275 long long object_id = result.getLongLong(1);
277 statement = connection->prepare("delete from object where id=%lld",object_id);
278 CPPUNIT_ASSERT(connection->execute(statement));
280 statement = connection->prepare("select * from attribute_text where object_id=%lld",object_id);
281 result = connection->perform(statement);
283 // Check cascade delete was successful.
284 CPPUNIT_ASSERT(!result.isValid());
288 void test_a_db_with_a_connection_with_tables::can_update_text_attribute()
290 can_insert_records();
293 DB::Statement statement = connection->prepare("select id from object");
294 CPPUNIT_ASSERT(statement.isValid());
295 DB::Result result = connection->perform(statement);
296 CPPUNIT_ASSERT(result.isValid());
298 long long object_id = result.getLongLong(1); // field indices start at 1
300 statement = connection->prepare(
301 "update attribute_text set value='test test test' where type=%d and object_id=%lld",
304 CPPUNIT_ASSERT(statement.isValid());
305 CPPUNIT_ASSERT(connection->execute(statement));
308 void test_a_db_with_a_connection_with_tables::can_update_text_attribute_bound_value()
310 can_insert_records();
313 DB::Statement statement = connection->prepare("select id from object");
314 CPPUNIT_ASSERT(statement.isValid());
315 DB::Result result = connection->perform(statement);
316 CPPUNIT_ASSERT(result.isValid());
318 long long object_id = result.getLongLong(1); // field indices start at 1
320 statement = connection->prepare(
321 "update attribute_text set value=? where type=%d and object_id=%lld",
324 CPPUNIT_ASSERT(statement.isValid());
326 std::string msg("testing quote ' and accents é.");
328 CPPUNIT_ASSERT(DB::Bindings(statement).bindText(1,msg.c_str(),msg.size(),NULL));
329 CPPUNIT_ASSERT(connection->execute(statement));
331 statement = connection->prepare(
332 "select value from attribute_text as t where t.type=%d and t.object_id=%lld",
335 result = connection->perform(statement);
336 CPPUNIT_ASSERT_EQUAL(std::string(result.getString(1)), msg);
339 void test_a_db_with_a_connection_with_tables::can_update_integer_attribute_bound_value()
342 DB::Statement statement = connection->prepare(
343 "insert into object default values");
344 CPPUNIT_ASSERT(statement.isValid());
345 CPPUNIT_ASSERT(connection->execute(statement));
346 long long object_id = connection->lastInsertRowId();
347 CPPUNIT_ASSERT(object_id != 0);
349 // insert integer attribute
350 statement = connection->prepare(
351 "insert into attribute_integer (value,type,object_id) values (%lld,%d,%lld)",
355 CPPUNIT_ASSERT(statement.isValid());
356 CPPUNIT_ASSERT(connection->execute(statement));
358 // prepare update integer attribute statement
359 statement = connection->prepare(
360 "update attribute_integer set value=? where type=%d and object_id=%lld",
363 CPPUNIT_ASSERT(statement.isValid());
365 // bind long long value to the parameter an update the record
366 CPPUNIT_ASSERT(DB::Bindings(statement).bindInt64(1,2222));
367 CPPUNIT_ASSERT(connection->execute(statement));
369 // Retrieve the value from the record
370 DB::Statement retrieveStmt = connection->prepare(
371 "select value from attribute_integer as t where t.type=%d and t.object_id=%lld",
374 CPPUNIT_ASSERT(retrieveStmt.isValid());
375 DB::Result result = connection->perform(retrieveStmt);
376 CPPUNIT_ASSERT_EQUAL(result.getLongLong(1), (long long)2222);
378 // verify that binding to a parameter before resetting the statement will fail.
379 DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
380 DB::Bindings bindings(statement);
381 CPPUNIT_ASSERT(!bindings.bindInt(1,3333));
382 DB::setLogErrorHandler(eh);
384 // reset statement and bind another value to the statement
385 CPPUNIT_ASSERT(bindings.reset());
386 CPPUNIT_ASSERT(bindings.bindInt(1,3333));
388 // perform the update statement again with the newly bound value
389 CPPUNIT_ASSERT(connection->execute(statement));
391 // reset the retrieve statement and perform it again to get the latest value of the integer attribute
392 CPPUNIT_ASSERT(retrieveStmt.reset());
393 result = connection->perform(retrieveStmt);
394 CPPUNIT_ASSERT(result.isValid());
395 CPPUNIT_ASSERT_EQUAL(result.getLongLong(1), (long long)3333);
398 void test_a_db_with_a_connection_with_tables::can_update_blob_attribute_bound_value()
401 DB::Statement statement = connection->prepare(
402 "insert into object default values");
403 CPPUNIT_ASSERT(statement.isValid());
404 CPPUNIT_ASSERT(connection->execute(statement));
405 long long object_id = connection->lastInsertRowId();
406 CPPUNIT_ASSERT(object_id != 0);
408 // insert blob attribute
409 statement = connection->prepare(
410 "insert into attribute_blob (value,type,object_id) values (X'012345',%d,%lld)",
413 CPPUNIT_ASSERT(statement.isValid());
414 CPPUNIT_ASSERT(connection->execute(statement));
416 // prepare update blob attribute statement
417 statement = connection->prepare(
418 "update attribute_blob set value=? where type=%d and object_id=%lld",
421 CPPUNIT_ASSERT(statement.isValid());
423 // bind blob (with embedded zero!) to the parameter
424 const char data[] = {10,11,0,12,13,14,15,16};
425 std::string msg(data,sizeof(data));
426 CPPUNIT_ASSERT(DB::Bindings(statement).bindBlob(1,msg.data(),msg.size(),NULL));
428 // update the blob value of the attribute
429 CPPUNIT_ASSERT(connection->execute(statement));
431 // retrieve the blob value from the attribute
432 statement = connection->prepare(
433 "select value from attribute_blob as t where t.type=%d and t.object_id=%lld",
436 CPPUNIT_ASSERT(statement.isValid());
437 DB::Result result = connection->perform(statement);
438 CPPUNIT_ASSERT(result.isValid());
440 // check that the retrieved blob value matches the original data.
441 CPPUNIT_ASSERT_EQUAL(result.getFieldLength(1), sizeof(data));
442 std::string msgstored((const char *)result.getBinary(1),result.getFieldLength(1));
443 CPPUNIT_ASSERT_EQUAL(msg, msgstored);
447 void test_a_db_with_a_connection_with_tables::will_not_insert_non_existing_attribute_on_update()
449 DB::Statement statement;
453 statement = connection->prepare(
454 "insert into object default values");
455 CPPUNIT_ASSERT(statement.isValid());
456 CPPUNIT_ASSERT(connection->execute(statement));
457 long long object_id = connection->lastInsertRowId();
458 CPPUNIT_ASSERT(object_id != 0);
460 // Updating an attribute before it is created will succeed, but will not insert an attribute.
461 statement = connection->prepare(
462 "update attribute_boolean set value=1 where type=%d and object_id=%lld",
465 CPPUNIT_ASSERT(statement.isValid());
466 CPPUNIT_ASSERT(connection->execute(statement));
468 // Retrieve the boolean value from the attribute should fail
469 statement = connection->prepare(
470 "select value from attribute_boolean as t where t.type=%d and t.object_id=%lld",
473 CPPUNIT_ASSERT(statement.isValid());
474 result = connection->perform(statement);
475 CPPUNIT_ASSERT(!result.isValid());
479 void test_a_db_with_a_connection_with_tables::can_update_boolean_attribute_bound_value()
481 //SQLite doesn't have a boolean data type, use 0 (false) and 1 (true)
483 DB::Statement statement;
487 statement = connection->prepare(
488 "insert into object default values");
489 CPPUNIT_ASSERT(statement.isValid());
490 CPPUNIT_ASSERT(connection->execute(statement));
491 long long object_id = connection->lastInsertRowId();
492 CPPUNIT_ASSERT(object_id != 0);
494 // insert boolean attribute
495 statement = connection->prepare(
496 "insert into attribute_boolean (value,type,object_id) values (1,%d,%lld)",
499 CPPUNIT_ASSERT(statement.isValid());
500 CPPUNIT_ASSERT(connection->execute(statement));
502 // prepare update boolean attribute statement
503 statement = connection->prepare(
504 "update attribute_boolean set value=? where type=%d and object_id=%lld",
507 CPPUNIT_ASSERT(statement.isValid());
509 // Bind 0 (false) to the first parameter
510 CPPUNIT_ASSERT(DB::Bindings(statement).bindInt(1,0));
512 // Execute the statement to update the attribute value.
513 CPPUNIT_ASSERT(connection->execute(statement));
515 // Retrieve the boolean value from the attribute
516 statement = connection->prepare(
517 "select value from attribute_boolean as t where t.type=%d and t.object_id=%lld",
520 CPPUNIT_ASSERT(statement.isValid());
521 result = connection->perform(statement);
522 CPPUNIT_ASSERT(result.isValid());
524 // check that the retrieved value matches the original value
525 CPPUNIT_ASSERT_EQUAL(result.getInt(1), 0);
529 void test_a_db_with_a_connection_with_tables::can_update_real_attribute_bound_value()
532 DB::Statement statement = connection->prepare(
533 "insert into object default values");
534 CPPUNIT_ASSERT(statement.isValid());
535 CPPUNIT_ASSERT(connection->execute(statement));
536 long long object_id = connection->lastInsertRowId();
537 CPPUNIT_ASSERT(object_id != 0);
540 statement = connection->prepare(
541 "insert into attribute_real (value,type,object_id) values(%f,%d,%lld)",
545 CPPUNIT_ASSERT(statement.isValid());
546 CPPUNIT_ASSERT(connection->execute(statement));
548 // prepare update real attribute statement
549 statement = connection->prepare(
550 "update attribute_real set value=? where type=%d and object_id=%lld",
553 CPPUNIT_ASSERT(statement.isValid());
555 // Bind 3333.3333 to the first parameter
556 CPPUNIT_ASSERT(DB::Bindings(statement).bindDouble(1,3333.3333));
558 // Execute the statement to update the attribute value
559 CPPUNIT_ASSERT(connection->execute(statement));
561 // Retrieve the double value from the attribute
562 statement = connection->prepare(
563 "select value from attribute_real as t where t.type=%d and t.object_id=%lld",
566 CPPUNIT_ASSERT(statement.isValid());
567 DB::Result result = connection->perform(statement);
568 CPPUNIT_ASSERT(result.isValid());
570 // check that the retrieved value matches the original value.
571 CPPUNIT_ASSERT_DOUBLES_EQUAL(result.getDouble(1), 3333.3333, 0.00001);
574 void test_a_db_with_a_connection_with_tables::supports_transactions()
576 DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
577 CPPUNIT_ASSERT(!connection->rollbackTransaction());
578 DB::setLogErrorHandler(eh);
580 CPPUNIT_ASSERT(connection->beginTransactionRW());
581 CPPUNIT_ASSERT(connection->rollbackTransaction());
583 eh = DB::setLogErrorHandler(dummy_print);
584 CPPUNIT_ASSERT(!connection->commitTransaction());
585 DB::setLogErrorHandler(eh);
587 CPPUNIT_ASSERT(connection->beginTransactionRW());
588 can_update_real_attribute_bound_value();
589 CPPUNIT_ASSERT(connection->commitTransaction());
592 CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection_with_tables_with_a_second_connection_open);
594 void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::setUp()
596 test_a_db_with_a_connection_with_tables::setUp();
597 connection2 = DB::Connection::Create("testdir","TestToken");
598 CPPUNIT_ASSERT(connection2 != null);
599 CPPUNIT_ASSERT(connection2->connect());
600 connection2->setBusyTimeout(10);
603 void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::tearDown()
605 CPPUNIT_ASSERT(connection2 != null);
606 connection2->close();
608 test_a_db_with_a_connection_with_tables::tearDown();
611 void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::handles_nested_transactions()
613 DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
615 DB::Connection *connection1 = connection;
617 CPPUNIT_ASSERT(connection1->beginTransactionRW());
619 CPPUNIT_ASSERT(connection2->beginTransactionRO());
620 CPPUNIT_ASSERT(connection2->rollbackTransaction());
621 CPPUNIT_ASSERT(!connection2->beginTransactionRW());
623 CPPUNIT_ASSERT(connection1->commitTransaction());
625 DB::setLogErrorHandler(eh);
629 void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::supports_transactions_with_other_connections_open()
631 CPPUNIT_ASSERT(connection2->beginTransactionRO());
633 supports_transactions();
635 // Retrieve the double value from the attribute
636 DB::Statement statement = connection2->prepare(
637 "select value from attribute_real as t where t.type=%d and t.object_id=%lld",
639 connection->lastInsertRowId());
640 CPPUNIT_ASSERT(statement.isValid());
641 DB::Result result = connection2->perform(statement);
642 CPPUNIT_ASSERT(result.isValid());
644 // check that the retrieved value matches the original value.
645 CPPUNIT_ASSERT_DOUBLES_EQUAL(result.getDouble(1), 3333.3333, 0.00001);
647 CPPUNIT_ASSERT(connection2->commitTransaction());