/* 1.1.1 Class Implementation ---- This file is part of SECONDO. Copyright (C) 2017, Faculty of Mathematics and Computer Science, Database Systems for New Applications. SECONDO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. SECONDO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SECONDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- */ #include "catch.hh" // https://github.com/catchorg/Catch2 #include "Algebras/DBService2/Relation.hpp" #include "Algebras/DBService2/Node.hpp" #include "Algebras/DBService2/Replica.hpp" #include "Algebras/DBService2/Derivative.hpp" #include "Algebras/DBService2/DatabaseEnvironment.hpp" #include "Algebras/DBService2/SecondoRelationAdapter.hpp" #include "Algebras/DBService2/DatabaseAdapter.hpp" #include "Algebras/DBService2/RelationTestFactory.hpp" #include "Algebras/DBService2/NodeTestFactory.hpp" #include #include #include using namespace DBService; using namespace DBServiceTest; using namespace std; using Catch::Matchers::Contains; using Catch::Matchers::Equals; TEST_CASE("Constructing DBService::Relations") { DBService::Derivative::disableCache(); DBService::Relation::disableCache(); DBService::Replica::disableCache(); DBService::Node::disableCache(); const string test_db_name = DatabaseEnvironment::test; SECTION("Creating the Replica Relation required for Relations") { shared_ptr adapter = DatabaseAdapter::getInstance(); bool doesRelationExist = adapter->doesRelationExist( test_db_name, DBService::Replica::getRelationName() ); REQUIRE(doesRelationExist == false); REQUIRE_NOTHROW( adapter->createRelation( test_db_name, DBService::Replica::getRelationName(), DBService::Replica::createRelationStatement() ) ); REQUIRE(adapter->doesRelationExist( test_db_name, DBService::Replica::getRelationName()) == true); } SECTION("Creating the Derivatives Relation requires for Relations") { shared_ptr adapter = DatabaseAdapter::getInstance(); bool doesRelationExist = adapter->doesRelationExist( test_db_name, DBService::Derivative::getRelationName() ); REQUIRE(doesRelationExist == false); REQUIRE_NOTHROW( adapter->createRelation( test_db_name, DBService::Derivative::getRelationName(), DBService::Derivative::createRelationStatement() ) ); REQUIRE(adapter->doesRelationExist( test_db_name, DBService::Derivative::getRelationName()) == true); } SECTION("Creating the Relation relation (table)") { shared_ptr relation = DBService::Relation::build(); relation->setDatabase(test_db_name); shared_ptr adapter = DatabaseAdapter::getInstance(); bool doesRelationExist = adapter->doesRelationExist( test_db_name, DBService::Relation::getRelationName()); REQUIRE(doesRelationExist == false); REQUIRE_NOTHROW( adapter->createRelation( test_db_name, DBService::Relation::getRelationName(), DBService::Relation::createRelationStatement() ) ); REQUIRE(adapter->doesRelationExist( test_db_name, DBService::Relation::getRelationName()) == true); } SECTION("A DBService::Relation comparisons") { SECTION("Relations are different if there databases are different") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); shared_ptr relation2 = DBService::Relation::build( "db2", "relation1"); REQUIRE( !(relation1 == relation2) ); REQUIRE( relation1 != relation2 ); } SECTION("Relations are different if there relation names are different") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); shared_ptr relation2 = DBService::Relation::build( "db1", "relation2"); REQUIRE( !(*relation1 == *relation2) ); REQUIRE( *relation1 != *relation2 ); } SECTION("Relations are equal if db and names are equal") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); shared_ptr relation2 = DBService::Relation::build( "db1", "relation1"); REQUIRE( *relation1 == *relation2 ); REQUIRE( !(*relation1 != *relation2) ); } } SECTION("Create a Relation along with its original Node") { shared_ptr relWithOriginal = DBService::Relation::build( test_db_name, "relation_with_original_node", "localhost", 4714, "/home/doesnt_exist/secondo" ); relWithOriginal->setDatabase(test_db_name); REQUIRE( relWithOriginal->getOriginalNode()->getHost().getHostname() == "localhost" ); REQUIRE( relWithOriginal->getOriginalNode()->getPort() == 4714 ); REQUIRE( relWithOriginal->getOriginalNode()->getDiskPath() == "/home/doesnt_exist/secondo" ); REQUIRE( relWithOriginal->getOriginalNode()->getType() == DBService::Node::nodeTypeOriginal() ); } SECTION("In-Memory Handling of Replicas") { SECTION("In-Memory adding a Replica") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); shared_ptr replica = make_shared(); REQUIRE_NOTHROW(relation1->addReplica(replica)); REQUIRE(relation1->getReplicaCount() == 1); REQUIRE(relation1->doesReplicaExist(replica) == true); // Verify that the replica has been adapted to the relation REQUIRE(replica->getDatabase() == relation1->getDatabase()); REQUIRE(replica->getRelation() == relation1); } SECTION("In-Memory adding a Replica twice shouldn't be possible") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); REQUIRE(relation1->getIsNew() == true); shared_ptr replica = make_shared(); REQUIRE(relation1->doesReplicaExist(replica) == false); REQUIRE_NOTHROW(relation1->addReplica(replica)); REQUIRE(relation1->doesReplicaExist(replica) == true); REQUIRE_THROWS_WITH(relation1->addReplica(replica), Contains( "Can't add replica. Replica already exists!" ) ); REQUIRE(relation1->getReplicaCount() == 1); REQUIRE(relation1->getIsDirty() == true); //TODO Also test for peristet relations! REQUIRE_NOTHROW(relation1->resetReplicas()); REQUIRE(relation1->getReplicaCount() == 0); REQUIRE(relation1->getIsDirty() == true); } SECTION("Resetting a Relation with two in-memory (new) Replicas") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); REQUIRE(relation1->getReplicaCount() == 0); shared_ptr replica = make_shared(); REQUIRE_NOTHROW(relation1->addReplica(replica)); REQUIRE(relation1->getReplicaCount() == 1); shared_ptr replica2 = make_shared(); REQUIRE_NOTHROW(relation1->addReplica(replica2)); REQUIRE(relation1->getReplicaCount() == 2); relation1->resetReplicas(); } SECTION("In-Memory adding a Replica for a given Target Node") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); // Create a target node // shared_ptr targetNode = make_shared( // "localhost", 4716, "", "/home/doesnt_exist/secondo", 9941, 9942); // targetNode->setDatabase(test_db_name); // targetNode->setType(DBService::Node::nodeTypeDBService()); shared_ptr targetNode = NodeTestFactory::buildTargetNode( test_db_name, 4716, false // do not save ); // Build and add a Replica for the given target node REQUIRE_NOTHROW(relation1->addReplica(targetNode)); REQUIRE(relation1->getReplicaCount() == 1); } } SECTION("Saving Relation records") { SECTION("A new Relation can't be saved without an original Node") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); REQUIRE_THROWS_WITH( relation1->save(), Contains( "Can't execute Relation::createStatement() without original Node!" ) ); } SECTION("A new Relation record can be saved") { shared_ptr relation1 = DBService::Relation::build( "db1", "relation1"); relation1->setDatabase(test_db_name); shared_ptr node = make_shared( "localhost", 4718, "", "/home/doesnt_exist/secondo", 9941, 9942); node->setDatabase(test_db_name); node->setType(DBService::Node::nodeTypeOriginal()); REQUIRE( node->save() == true ); relation1->setOriginalNode(node); REQUIRE( relation1->save() == true ); } SECTION("A new Relation also saves a new OriginalNode") { shared_ptr relation2 = DBService::Relation::build( "db1", "relation2"); relation2->setDatabase(test_db_name); shared_ptr node = make_shared( "localhost", 4712, "", "/home/doesnt_exist/secondo", 9941, 9942); node->setDatabase(test_db_name); node->setType(DBService::Node::nodeTypeOriginal()); relation2->setOriginalNode(node); REQUIRE( relation2->save() == true ); REQUIRE( relation2->getOriginalNode()->getIsNew() == false); } SECTION("A new Relation with Replicas") { shared_ptr relationWithOriginalNode = DBServiceTest::RelationTestFactory::buildRelationWithOriginalNode( test_db_name, "relationWithOriginalNode", 4712); // Target node shared_ptr targetNode = NodeTestFactory::buildTargetNode( test_db_name, 4713, true // do save ); // /* // TODO More elegant way to create Replica for a given Relation // Also this Replica is not part of the in-memory relation as it is not // contained in the relation's replicas-vector. // */ Replica replica = Replica(); replica.setDatabase(test_db_name); replica.setTargetNode(targetNode); replica.setRelation(relationWithOriginalNode); REQUIRE( replica.save() == true); } SECTION("Updating a Relation") { } // relation->save -> after_save -> replicas.save() SECTION("Saving a Relation with new Replicas using a save cascade") { shared_ptr relationWithReplica = DBServiceTest::RelationTestFactory::buildRelationWithOriginalNode( test_db_name, "relationWithReplica", 4712, true); // save the relation // Target node - shared_ptr targetNode = NodeTestFactory::buildTargetNode( test_db_name, 4724, true // target Node will be saved ); REQUIRE( relationWithReplica->getIsDirty() == false); relationWithReplica->addReplica(targetNode); REQUIRE( relationWithReplica->getIsDirty() == true); relationWithReplica->save(); REQUIRE( relationWithReplica->getIsDirty() == false); // recorddb, relationDb, relationName Query query = DBService::Relation::queryByDatabaseAndName(test_db_name, test_db_name, "relationWithReplica"); shared_ptr loadedRelation = DBService::Relation::findOne(test_db_name, query); REQUIRE(loadedRelation != nullptr); REQUIRE(loadedRelation->getReplicaCount() == relationWithReplica->getReplicaCount()); } } SECTION("Relation Queries") { string relationName = "relation1"; SECTION("It should generate a Query for a given Relation") { Query query = DBService::Relation::query(test_db_name); REQUIRE(query.str() == "query dbs_relations"); } SECTION("It should generate a filter condition") { Query query = DBService::Relation::query(test_db_name); Query filteredQuery = query.filter(".Name = ?", relationName); REQUIRE(filteredQuery.str() == "query dbs_relations \ filter[.Name = \"relation1\"]"); } SECTION("It should be able to create query train racks") { Query query = DBService::Relation::query(test_db_name).filter( ".Name = ?", relationName); REQUIRE(query.str() == "query dbs_relations \ filter[.Name = \"relation1\"]"); } SECTION("Generate a query for all Relations using feed consume") { Query query = DBService::Relation::query( test_db_name).feed().addid().consume(); REQUIRE(query.str() == "query dbs_relations feed addid consume"); } SECTION("Generate a query for all Relations using findAllStatement") { string query = DBService::Relation::findAllStatement(test_db_name); REQUIRE(query == "query dbs_relations feed addid consume"); /* Result: Name : relation1 Database : dbservice_test OriginalNodeId : 7 TID : 1 */ } SECTION("Query a Relation by RelationId") { Query query = DBService::Relation::query(test_db_name).feed().filter( ".RelationId = ?", 1).addid().consume(); REQUIRE(query.str() == "query dbs_relations feed \ filter[.RelationId = 1] addid consume"); } } SECTION("Loading Relations") { SECTION("Load all Relations") { //TODO Delete all relations, Create a relation //TODO Move this load functionality to an apropriate class shared_ptr dbAdapter = DatabaseAdapter::getInstance(); dbAdapter->openDatabase(test_db_name); vector > relations = DBService::Relation::findAll(test_db_name); //TODO Create fixtures to decouple from previous test cases. shared_ptr relation = relations.back(); REQUIRE(relation->getId() > 0); REQUIRE(relation->getDatabase() == test_db_name); // Case in-sensitive comparison REQUIRE_THAT(relation->getRelationDatabase(), Equals(test_db_name, Catch::CaseSensitive::No)); //TODO Remove assumption about ordinality of records REQUIRE(relation->getName() == "relationWithReplica"); // Testing whether the original Node has been loaded successfully shared_ptr originalNode = relation->getOriginalNode(); REQUIRE(originalNode->getHost().getHostname() == "localhost"); REQUIRE(originalNode->getType() == DBService::Node::nodeTypeOriginal()); } SECTION("Load Relation with Replica") { Replica::invalidateCache(); //TODO Remove dependency to prio test cast. Use fixture. string relationNameWithReplica = "relationWithReplica"; // query(test_db_name).feed().filter(".Database = ?", // boost::to_upper_copy(test_db_name)).filter(".Name = ?", // relationNameWithReplica).addid().consume(); Query query = DBService::Relation::queryByDatabaseAndName(test_db_name, boost::to_upper_copy(test_db_name), relationNameWithReplica); shared_ptr relationWithReplica = DBService::Relation::findOne(test_db_name, query); REQUIRE(relationWithReplica != nullptr); relationWithReplica->setDatabase(test_db_name); // Case in-sensitive comparison REQUIRE_THAT(relationWithReplica->getRelationDatabase(), Equals(test_db_name, Catch::CaseSensitive::No)); REQUIRE(relationWithReplica->getName() == relationNameWithReplica); vector > replicas = relationWithReplica->getReplicas(); REQUIRE(replicas.size() > 0); // Acquire a shared_ptr from shared_ptr shared_ptr replica = replicas.back(); REQUIRE(replica->getStatus() == Replica::statusWaiting); } // Load relation by db and relation name; } // Set name for new node // Set name for non-new node // Set originalNode for new node // Set originalNode for non-new node }