Files
secondo/Algebras/DBService2/RelationTest.cpp
2026-01-23 17:03:45 +08:00

519 lines
17 KiB
C++

/*
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 <string>
#include <vector>
#include <boost/algorithm/string.hpp>
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<DatabaseAdapter> 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<DatabaseAdapter> 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<DBService::Relation> relation = DBService::Relation::build();
relation->setDatabase(test_db_name);
shared_ptr<DatabaseAdapter> 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<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
shared_ptr<DBService::Relation> relation2 = DBService::Relation::build(
"db2", "relation1");
REQUIRE( !(relation1 == relation2) );
REQUIRE( relation1 != relation2 );
}
SECTION("Relations are different if there relation names are different") {
shared_ptr<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
shared_ptr<DBService::Relation> relation2 = DBService::Relation::build(
"db1", "relation2");
REQUIRE( !(*relation1 == *relation2) );
REQUIRE( *relation1 != *relation2 );
}
SECTION("Relations are equal if db and names are equal") {
shared_ptr<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
shared_ptr<DBService::Relation> relation2 = DBService::Relation::build(
"db1", "relation1");
REQUIRE( *relation1 == *relation2 );
REQUIRE( !(*relation1 != *relation2) );
}
}
SECTION("Create a Relation along with its original Node") {
shared_ptr<DBService::Relation> 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<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
relation1->setDatabase(test_db_name);
shared_ptr<Replica> replica = make_shared<Replica>();
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<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
relation1->setDatabase(test_db_name);
REQUIRE(relation1->getIsNew() == true);
shared_ptr<Replica> replica = make_shared<Replica>();
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<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
relation1->setDatabase(test_db_name);
REQUIRE(relation1->getReplicaCount() == 0);
shared_ptr<Replica> replica = make_shared<Replica>();
REQUIRE_NOTHROW(relation1->addReplica(replica));
REQUIRE(relation1->getReplicaCount() == 1);
shared_ptr<Replica> replica2 = make_shared<Replica>();
REQUIRE_NOTHROW(relation1->addReplica(replica2));
REQUIRE(relation1->getReplicaCount() == 2);
relation1->resetReplicas();
}
SECTION("In-Memory adding a Replica for a given Target Node") {
shared_ptr<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
relation1->setDatabase(test_db_name);
// Create a target node
// shared_ptr<DBService::Node> targetNode = make_shared<DBService::Node>(
// "localhost", 4716, "", "/home/doesnt_exist/secondo", 9941, 9942);
// targetNode->setDatabase(test_db_name);
// targetNode->setType(DBService::Node::nodeTypeDBService());
shared_ptr<DBService::Node> 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<DBService::Relation> 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<DBService::Relation> relation1 = DBService::Relation::build(
"db1", "relation1");
relation1->setDatabase(test_db_name);
shared_ptr<DBService::Node> node = make_shared<DBService::Node>(
"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<DBService::Relation> relation2 = DBService::Relation::build(
"db1", "relation2");
relation2->setDatabase(test_db_name);
shared_ptr<DBService::Node> node = make_shared<DBService::Node>(
"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<DBService::Relation> relationWithOriginalNode =
DBServiceTest::RelationTestFactory::buildRelationWithOriginalNode(
test_db_name, "relationWithOriginalNode", 4712);
// Target node
shared_ptr<DBService::Node> 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<DBService::Relation> relationWithReplica =
DBServiceTest::RelationTestFactory::buildRelationWithOriginalNode(
test_db_name, "relationWithReplica", 4712, true); // save the relation
// Target node -
shared_ptr<DBService::Node> 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<DBService::Relation> 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<DatabaseAdapter> dbAdapter = DatabaseAdapter::getInstance();
dbAdapter->openDatabase(test_db_name);
vector<shared_ptr<DBService::Relation> > relations =
DBService::Relation::findAll(test_db_name);
//TODO Create fixtures to decouple from previous test cases.
shared_ptr<DBService::Relation> 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<DBService::Node> 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<DBService::Relation> 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<shared_ptr<Replica> > replicas =
relationWithReplica->getReplicas();
REQUIRE(replicas.size() > 0);
// Acquire a shared_ptr from shared_ptr
shared_ptr<Replica> 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
}