aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/devicemanager/model
diff options
context:
space:
mode:
authorDan Timoney <dtimoney@att.com>2020-03-05 15:52:09 -0500
committerSingal, Kapil (ks220y) <ks220y@att.com>2020-03-06 09:12:18 -0500
commitfc05c3c97e4591e09403ecd24d33341975f6d411 (patch)
tree1abdd3857c7b182fc1b5eccd6e596c6e11c6b994 /sdnr/wt/devicemanager/model
parentf70419cb91b9ab0bf124ba667b7c25c4e5214650 (diff)
Roll master to Guilin
Roll master to new versions for early Guilin developent Change-Id: I072f4657e16d5ad5b3fe81978f407c631a0048ed Issue-ID: CCSDK-2152 Signed-off-by: Dan Timoney <dtimoney@att.com>
Diffstat (limited to 'sdnr/wt/devicemanager/model')
-rw-r--r--sdnr/wt/devicemanager/model/pom.xml4
1 files changed, 2 insertions, 2 deletions
diff --git a/sdnr/wt/devicemanager/model/pom.xml b/sdnr/wt/devicemanager/model/pom.xml
index 3e36dd032..6466835ca 100644
--- a/sdnr/wt/devicemanager/model/pom.xml
+++ b/sdnr/wt/devicemanager/model/pom.xml
@@ -29,13 +29,13 @@
<parent>
<groupId>org.onap.ccsdk.parent</groupId>
<artifactId>binding-parent</artifactId>
- <version>1.5.2</version>
+ <version>2.0.0-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>org.onap.ccsdk.features.sdnr.wt</groupId>
<artifactId>sdnr-wt-devicemanager-model</artifactId>
- <version>0.7.1-SNAPSHOT</version>
+ <version>1.0.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>ccsdk-features :: ${project.artifactId}</name>
>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 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
/*
 * Copyright (c) 2013 SURFnet bv
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*****************************************************************************
 DBTests.cpp

 Contains lowest level test cases for the database backend implementation.
 *****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <cppunit/extensions/HelperMacros.h>
#include "DBTests.h"

CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db);

static int dummy_print(const char *, va_list )
{
	return 0;
}

void test_a_db::setUp()
{
	CPPUNIT_ASSERT(!system("mkdir testdir"));
	null = NULL;
}

void test_a_db::tearDown()
{
#ifndef _WIN32
	CPPUNIT_ASSERT(!system("rm -rf testdir"));
#else
	CPPUNIT_ASSERT(!system("rmdir /s /q testdir 2> nul"));
#endif
}

void test_a_db::checks_for_empty_connection_parameters()
{
	DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);

	DB::Connection *connection = DB::Connection::Create("","TestToken");
	CPPUNIT_ASSERT_EQUAL(connection, null);

	connection = DB::Connection::Create("testdir","");
	CPPUNIT_ASSERT_EQUAL(connection, null);

	connection = DB::Connection::Create("","");
	CPPUNIT_ASSERT_EQUAL(connection, null);

	DB::setLogErrorHandler(eh);
}

void test_a_db::can_be_connected_to_database()
{

	DB::Connection *connection = DB::Connection::Create("testdir","TestToken");
	CPPUNIT_ASSERT(connection != null);
	bool isConnected = connection->connect();
	delete connection;
	CPPUNIT_ASSERT(isConnected);
#ifndef _WIN32
	CPPUNIT_ASSERT_EQUAL(system("test -f ./testdir/TestToken"), 0);
#else
	CPPUNIT_ASSERT(GetFileAttributes("testdir\\TestToken") != INVALID_FILE_ATTRIBUTES);
#endif
}

CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection);

void test_a_db_with_a_connection::setUp()
{
	test_a_db::setUp();
	connection = DB::Connection::Create("testdir","TestToken");
	CPPUNIT_ASSERT(connection != null);
	CPPUNIT_ASSERT(connection->connect());
}

void test_a_db_with_a_connection::tearDown()
{
	CPPUNIT_ASSERT(connection != null);
	connection->close();
	delete connection;
	test_a_db::tearDown();
}

void test_a_db_with_a_connection::can_prepare_statements()
{
	DB::Statement statement = connection->prepare("PRAGMA database_list;");
	CPPUNIT_ASSERT(statement.isValid());
}

void test_a_db_with_a_connection::can_perform_statements()
{
	DB::Statement statement = connection->prepare("PRAGMA database_list;");
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());
	// only expect a single row in the result, so nextRow should now fail
	CPPUNIT_ASSERT(!result.nextRow());
}

void test_a_db_with_a_connection::maintains_correct_refcounts()
{
	DB::Statement statement = connection->prepare("PRAGMA database_list;");
	CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);
	{
		DB::Statement statement1 = statement;
		DB::Statement statement2 = statement;
		CPPUNIT_ASSERT_EQUAL(statement.refcount(), 3);
		CPPUNIT_ASSERT(statement1.isValid());
		CPPUNIT_ASSERT(statement2.isValid());
	}
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);

	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	// Statement is referenced by the result because it provides the query record cursor state.
	CPPUNIT_ASSERT_EQUAL(statement.refcount(), 2);

	result = DB::Result();
	CPPUNIT_ASSERT_EQUAL(statement.refcount(), 1);
}
void test_a_db_with_a_connection::can_create_tables()
{
	CPPUNIT_ASSERT(!connection->tableExists("object"));
	DB::Statement cr_object = connection->prepare("create table object (id integer primary key autoincrement);");
	CPPUNIT_ASSERT(connection->execute(cr_object));
	CPPUNIT_ASSERT(connection->tableExists("object"));
}

CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection_with_tables);

void test_a_db_with_a_connection_with_tables::setUp()
{
	test_a_db_with_a_connection::setUp();
	can_create_tables();

	// attribute_text
	CPPUNIT_ASSERT(!connection->tableExists("attribute_text"));
	DB::Statement cr_attr_text = connection->prepare(
		"create table attribute_text ("
		"value text,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_text));
	CPPUNIT_ASSERT(connection->tableExists("attribute_text"));

	// attribute_integer
	CPPUNIT_ASSERT(!connection->tableExists("attribute_integer"));
	DB::Statement cr_attr_integer = connection->prepare(
		"create table attribute_integer ("
		"value integer,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_integer));
	CPPUNIT_ASSERT(connection->tableExists("attribute_integer"));

	// attribute_blob
	CPPUNIT_ASSERT(!connection->tableExists("attribute_blob"));
	DB::Statement cr_attr_blob = connection->prepare(
		"create table attribute_blob ("
		"value blob,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_blob));
	CPPUNIT_ASSERT(connection->tableExists("attribute_blob"));

	// attribute_boolean
	CPPUNIT_ASSERT(!connection->tableExists("attribute_boolean"));
	DB::Statement cr_attr_boolean = connection->prepare(
		"create table attribute_boolean ("
		"value boolean,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_boolean));
	CPPUNIT_ASSERT(connection->tableExists("attribute_boolean"));

	// attribute_datetime
	CPPUNIT_ASSERT(!connection->tableExists("attribute_datetime"));
	DB::Statement cr_attr_datetime = connection->prepare(
		"create table attribute_datetime ("
		"value datetime,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_datetime));
	CPPUNIT_ASSERT(connection->tableExists("attribute_datetime"));

	// attribute_real
	CPPUNIT_ASSERT(!connection->tableExists("attribute_real"));
	DB::Statement cr_attr_real = connection->prepare(
		"create table attribute_real ("
		"value real,"
		"type integer,"
		"object_id integer references object(id) on delete cascade,"
		"id integer primary key autoincrement)"
		);
	CPPUNIT_ASSERT(connection->execute(cr_attr_real));
	CPPUNIT_ASSERT(connection->tableExists("attribute_real"));
}

void test_a_db_with_a_connection_with_tables::tearDown()
{
	test_a_db_with_a_connection::tearDown();
}

void test_a_db_with_a_connection_with_tables::can_insert_records()
{
	DB::Statement statement = connection->prepare("insert into object default values");
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	statement = connection->prepare(
				"insert into attribute_text (value,type,object_id) values ('%s',%d,%lld)",
				"testing testing testing",
				1234,
				object_id);
	CPPUNIT_ASSERT(connection->execute(statement));
}

void test_a_db_with_a_connection_with_tables::can_retrieve_records()
{
	can_insert_records();

	DB::Statement statement = connection->prepare(
				"select value from attribute_text as t where t.type=%d",
				1234);
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT_EQUAL(std::string(result.getString(1)), std::string("testing testing testing"));
}

void test_a_db_with_a_connection_with_tables::can_cascade_delete_objects_and_attributes()
{
	can_insert_records();

	DB::Statement statement = connection->prepare("select id from object");
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	long long object_id = result.getLongLong(1);

	statement = connection->prepare("delete from object where id=%lld",object_id);
	CPPUNIT_ASSERT(connection->execute(statement));

	statement = connection->prepare("select * from attribute_text where object_id=%lld",object_id);
	result = connection->perform(statement);

	// Check cascade delete was successful.
	CPPUNIT_ASSERT(!result.isValid());
}


void test_a_db_with_a_connection_with_tables::can_update_text_attribute()
{
	can_insert_records();

	// query all objects
	DB::Statement statement = connection->prepare("select id from object");
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	long long object_id = result.getLongLong(1); // field indices start at 1

	statement = connection->prepare(
				"update attribute_text set value='test test test' where type=%d and object_id=%lld",
				1234,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
}

void test_a_db_with_a_connection_with_tables::can_update_text_attribute_bound_value()
{
	can_insert_records();

	// query all objects
	DB::Statement statement = connection->prepare("select id from object");
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	long long object_id = result.getLongLong(1); // field indices start at 1

	statement = connection->prepare(
				"update attribute_text set value=? where type=%d and object_id=%lld",
				1234,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());

	std::string msg("testing quote ' and accents é.");

	CPPUNIT_ASSERT(DB::Bindings(statement).bindText(1,msg.c_str(),msg.size(),NULL));
	CPPUNIT_ASSERT(connection->execute(statement));

	statement = connection->prepare(
				"select value from attribute_text as t where t.type=%d and t.object_id=%lld",
				1234,
				object_id);
	result = connection->perform(statement);
	CPPUNIT_ASSERT_EQUAL(std::string(result.getString(1)), msg);
}

void test_a_db_with_a_connection_with_tables::can_update_integer_attribute_bound_value()
{
	// insert new object
	DB::Statement statement = connection->prepare(
				"insert into object default values");
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	// insert integer attribute
	statement = connection->prepare(
				"insert into attribute_integer (value,type,object_id) values (%d,%d,%lld)",
				1111,
				1235,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));

	// prepare update integer attribute statement
	statement = connection->prepare(
				"update attribute_integer set value=? where type=%d and object_id=%lld",
				1235,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());

	// bind long long value to the parameter an update the record
	CPPUNIT_ASSERT(DB::Bindings(statement).bindInt64(1,2222));
	CPPUNIT_ASSERT(connection->execute(statement));

	// Retrieve the value from the record
	DB::Statement retrieveStmt = connection->prepare(
				"select value from attribute_integer as t where t.type=%d and t.object_id=%lld",
				1235,
				object_id);
	CPPUNIT_ASSERT(retrieveStmt.isValid());
	DB::Result result = connection->perform(retrieveStmt);
	CPPUNIT_ASSERT_EQUAL(result.getLongLong(1), (long long)2222);

	// verify that binding to a parameter before resetting the statement will fail.
	DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
	DB::Bindings bindings(statement);
	CPPUNIT_ASSERT(!bindings.bindInt(1,3333));
	DB::setLogErrorHandler(eh);

	// reset statement and bind another value to the statement
	CPPUNIT_ASSERT(bindings.reset());
	CPPUNIT_ASSERT(bindings.bindInt(1,3333));

	// perform the update statement again with the newly bound value
	CPPUNIT_ASSERT(connection->execute(statement));

	// reset the retrieve statement and perform it again to get the latest value of the integer attribute
	CPPUNIT_ASSERT(retrieveStmt.reset());
	result = connection->perform(retrieveStmt);
	CPPUNIT_ASSERT(result.isValid());
	CPPUNIT_ASSERT_EQUAL(result.getLongLong(1), (long long)3333);
}

void test_a_db_with_a_connection_with_tables::can_update_blob_attribute_bound_value()
{
	// insert new object
	DB::Statement statement = connection->prepare(
				"insert into object default values");
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	// insert blob attribute
	statement = connection->prepare(
				"insert into attribute_blob (value,type,object_id) values (X'012345',%d,%lld)",
				1236,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));

	// prepare update blob attribute statement
	statement = connection->prepare(
				"update attribute_blob set value=? where type=%d and object_id=%lld",
				1236,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());

	// bind blob (with embedded zero!) to the parameter
	const char data[] = {10,11,0,12,13,14,15,16};
	std::string msg(data,sizeof(data));
	CPPUNIT_ASSERT(DB::Bindings(statement).bindBlob(1,msg.data(),msg.size(),NULL));

	// update the blob value of the attribute
	CPPUNIT_ASSERT(connection->execute(statement));

	// retrieve the blob value from the attribute
	statement = connection->prepare(
				"select value from attribute_blob as t where t.type=%d and t.object_id=%lld",
				1236,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	// check that the retrieved blob value matches the original data.
	CPPUNIT_ASSERT_EQUAL(result.getFieldLength(1), sizeof(data));
	std::string msgstored((const char *)result.getBinary(1),result.getFieldLength(1));
	CPPUNIT_ASSERT_EQUAL(msg, msgstored);
}


void test_a_db_with_a_connection_with_tables::will_not_insert_non_existing_attribute_on_update()
{
	DB::Statement statement;
	DB::Result result;

	// Insert new object
	statement = connection->prepare(
				"insert into object default values");
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	// Updating an attribute before it is created will succeed, but will not insert an attribute.
	statement = connection->prepare(
				"update attribute_boolean set value=1 where type=%d and object_id=%lld",
				1237,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));

	// Retrieve the boolean value from the attribute should fail
	statement = connection->prepare(
				"select value from attribute_boolean as t where t.type=%d and t.object_id=%lld",
				1237,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	result = connection->perform(statement);
	CPPUNIT_ASSERT(!result.isValid());
}


void test_a_db_with_a_connection_with_tables::can_update_boolean_attribute_bound_value()
{
	//SQLite doesn't have a boolean data type, use 0 (false) and 1 (true)

	DB::Statement statement;
	DB::Result result;

	// Insert new object
	statement = connection->prepare(
				"insert into object default values");
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	// insert boolean attribute
	statement = connection->prepare(
				"insert into attribute_boolean (value,type,object_id) values (1,%d,%lld)",
				1237,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));

	// prepare update boolean attribute statement
	statement = connection->prepare(
				"update attribute_boolean set value=? where type=%d and object_id=%lld",
				1237,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());

	// Bind 0 (false) to the first parameter
	CPPUNIT_ASSERT(DB::Bindings(statement).bindInt(1,0));

	// Execute the statement to update the attribute value.
	CPPUNIT_ASSERT(connection->execute(statement));

	// Retrieve the boolean value from the attribute
	statement = connection->prepare(
				"select value from attribute_boolean as t where t.type=%d and t.object_id=%lld",
				1237,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	// check that the retrieved value matches the original value
	CPPUNIT_ASSERT_EQUAL(result.getInt(1), 0);
}


void test_a_db_with_a_connection_with_tables::can_update_real_attribute_bound_value()
{
	// insert new object
	DB::Statement statement = connection->prepare(
				"insert into object default values");
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));
	long long object_id = connection->lastInsertRowId();
	CPPUNIT_ASSERT(object_id != 0);

	// insert real value
	statement = connection->prepare(
				"insert into attribute_real (value,type,object_id) values(%f,%d,%lld)",
				1.238,
				1238,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	CPPUNIT_ASSERT(connection->execute(statement));

	// prepare update real attribute statement
	statement = connection->prepare(
				"update attribute_real set value=? where type=%d and object_id=%lld",
				1238,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());

	// Bind 3333.3333 to the first parameter
	CPPUNIT_ASSERT(DB::Bindings(statement).bindDouble(1,3333.3333));

	// Execute the statement to update the attribute value
	CPPUNIT_ASSERT(connection->execute(statement));

	// Retrieve the double value from the attribute
	statement = connection->prepare(
				"select value from attribute_real as t where t.type=%d and t.object_id=%lld",
				1238,
				object_id);
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	// check that the retrieved value matches the original value.
	CPPUNIT_ASSERT_DOUBLES_EQUAL(result.getDouble(1), 3333.3333, 0.00001);
}

void test_a_db_with_a_connection_with_tables::supports_transactions()
{
	DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);
	CPPUNIT_ASSERT(!connection->rollbackTransaction());
	DB::setLogErrorHandler(eh);

	CPPUNIT_ASSERT(connection->beginTransactionRW());
	CPPUNIT_ASSERT(connection->rollbackTransaction());

	eh = DB::setLogErrorHandler(dummy_print);
	CPPUNIT_ASSERT(!connection->commitTransaction());
	DB::setLogErrorHandler(eh);

	CPPUNIT_ASSERT(connection->beginTransactionRW());
	can_update_real_attribute_bound_value();
	CPPUNIT_ASSERT(connection->commitTransaction());
}

CPPUNIT_TEST_SUITE_REGISTRATION(test_a_db_with_a_connection_with_tables_with_a_second_connection_open);

void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::setUp()
{
	test_a_db_with_a_connection_with_tables::setUp();
	connection2 = DB::Connection::Create("testdir","TestToken");
	CPPUNIT_ASSERT(connection2 != null);
	CPPUNIT_ASSERT(connection2->connect());
	connection2->setBusyTimeout(10);
}

void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::tearDown()
{
	CPPUNIT_ASSERT(connection2 != null);
	connection2->close();
	delete connection2;
	test_a_db_with_a_connection_with_tables::tearDown();
}

void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::handles_nested_transactions()
{
	DB::LogErrorHandler eh = DB::setLogErrorHandler(dummy_print);

	DB::Connection *connection1 = connection;

	CPPUNIT_ASSERT(connection1->beginTransactionRW());

	CPPUNIT_ASSERT(connection2->beginTransactionRO());
	CPPUNIT_ASSERT(connection2->rollbackTransaction());
	CPPUNIT_ASSERT(!connection2->beginTransactionRW());

	CPPUNIT_ASSERT(connection1->commitTransaction());

	DB::setLogErrorHandler(eh);
}


void test_a_db_with_a_connection_with_tables_with_a_second_connection_open::supports_transactions_with_other_connections_open()
{
	CPPUNIT_ASSERT(connection2->beginTransactionRO());

	supports_transactions();

	// Retrieve the double value from the attribute
	DB::Statement statement = connection2->prepare(
				"select value from attribute_real as t where t.type=%d and t.object_id=%lld",
				1238,
				connection->lastInsertRowId());
	CPPUNIT_ASSERT(statement.isValid());
	DB::Result result = connection2->perform(statement);
	CPPUNIT_ASSERT(result.isValid());

	// check that the retrieved value matches the original value.
	CPPUNIT_ASSERT_DOUBLES_EQUAL(result.getDouble(1), 3333.3333, 0.00001);

	CPPUNIT_ASSERT(connection2->commitTransaction());
}