/* * This file is a part of the CaosDB Project. * * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> * Copyright (C) 2021 Daniel Hornung <d.hornung@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ #include "caosdb/connection.h" // for Connection, Connec... #include "caosdb/data_type.h" // for AtomicDataType #include "caosdb/entity.h" // for Entity, Property #include "caosdb/file_transmission/file_reader.h" // for path, FileReader #include "caosdb/file_transmission/file_writer.h" // for FileWriter #include "caosdb/message_code.h" // for MessageCode, ENTIT... #include "caosdb/status_code.h" // for StatusCode, SUCCESS #include "caosdb/transaction.h" // for Entity, Transaction #include "caosdb/transaction_status.h" // for TransactionStatus #include "caosdb/value.h" // for Value #include <boost/filesystem/operations.hpp> // for remove #include <boost/filesystem/path.hpp> // for path #include <boost/filesystem/path_traits.hpp> // for filesystem #include <cstddef> // for size_t #include <cstdint> // for int32_t #include <gtest/gtest-message.h> // for Message #include <gtest/gtest-test-part.h> // for TestPartResult #include <gtest/gtest_pred_impl.h> // for AssertionResult #include <iostream> // for operator<<, endl #include <limits> // for numeric_limits #include <memory> // for unique_ptr, allocator #include <stdexcept> // for logic_error #include <string> // for string, operator+ #include <vector> // for vector, operator!= namespace fs = boost::filesystem; namespace caosdb::transaction { using caosdb::entity::AtomicDataType; using caosdb::entity::Entity; using caosdb::entity::Importance; using caosdb::entity::MessageCode; using caosdb::entity::Parent; using caosdb::entity::Property; using caosdb::entity::Role; using caosdb::entity::Value; class test_transaction : public ::testing::Test { public: // public utility functions // //////////////////////////////////////////////////////// /** * Generate a vector with useful values for testing. */ template <typename T> static auto generateValues() -> std::vector<T> { std::vector<T> values = { static_cast<T>(0), static_cast<T>(1), static_cast<T>(6.91629132943846e-310), std::numeric_limits<T>::max(), std::numeric_limits<T>::min(), std::numeric_limits<T>::denorm_min(), std::numeric_limits<T>::lowest(), std::numeric_limits<T>::epsilon() // 0 for integers, but who cares? }; return values; } template <typename T> static auto getValueAs(const Value & /*value*/) -> T { throw std::logic_error("Template not implemented for this type."); } static void DeleteEntities() { // delete all entities const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto query_transaction(connection->CreateTransaction()); query_transaction->Query("FIND ENTITY WITH id > 99"); query_transaction->Execute(); if (query_transaction->GetResultSet().size() > 0) { std::cout << "Cleanup: Deleting " << query_transaction->GetResultSet().size() << " entities." << std::endl; auto delete_transaction(connection->CreateTransaction()); for (const Entity &entity : query_transaction->GetResultSet()) { delete_transaction->DeleteById(entity.GetId()); } delete_transaction->Execute(); } } protected: fs::path test_upload_file_1; fs::path test_download_file_1; // Fixture methods ////////////////////////////////////////////////////////// void SetUp() override { DeleteEntities(); test_upload_file_1 = fs::path("test_upload_file_1_delete_me.dat"); test_download_file_1 = fs::path("test_download_file_1_delete_me.dat"); // fill the file that shall be uploaded FileWriter writer(test_upload_file_1); std::string buffer(1024, 'c'); for (int i = 0; i < 8; i++) { writer.write(buffer); } } void TearDown() override { // delete files fs::remove(test_upload_file_1); fs::remove(test_download_file_1); DeleteEntities(); } }; template <> auto test_transaction::getValueAs<double>(const Value &value) -> double { return value.AsDouble(); } template <> auto test_transaction::getValueAs<int32_t>(const Value &value) -> int32_t { return value.AsInteger(); } template <> auto test_transaction::getValueAs<bool>(const Value &value) -> bool { return value.AsBool(); } /* * Test the retrieval of a non-existing entity * * The transaction returns an error code and the entity has the appropriate * error message */ TEST_F(test_transaction, retrieve_non_existing) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto transaction(connection->CreateTransaction()); const auto *id = "non-existing-id"; transaction->RetrieveById(id); transaction->ExecuteAsynchronously(); auto status = transaction->WaitForIt(); EXPECT_EQ(status.GetCode(), TransactionStatus::TRANSACTION_ERROR().GetCode()); ASSERT_EQ(status.GetCode(), StatusCode::GENERIC_TRANSACTION_ERROR); const auto &result_set = transaction->GetResultSet(); const auto &entity = result_set.at(0); EXPECT_EQ(id, entity.GetId()); EXPECT_TRUE(entity.HasErrors()); ASSERT_EQ(entity.GetErrors().size(), 1); EXPECT_EQ(entity.GetErrors().at(0).GetCode(), MessageCode::ENTITY_DOES_NOT_EXIST); } /* * Testing the insertion of a basic entity * Transaction should terminate without errors. * Returned entity should have an id, no errors and a warning since no property * is set. */ TEST_F(test_transaction, insert_without_delete) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto insert_transaction(connection->CreateTransaction()); Entity entity; entity.SetRole(Role::RECORD_TYPE); entity.SetName("RT1"); insert_transaction->InsertEntity(&entity); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); ASSERT_FALSE(insert_status.IsError()); const auto &insert_result_set = insert_transaction->GetResultSet(); const auto &new_entity = insert_result_set.at(0); EXPECT_FALSE(new_entity.GetId().empty()); EXPECT_FALSE(new_entity.HasErrors()); // Should have a warning since it has no properties EXPECT_TRUE(new_entity.HasWarnings()); EXPECT_EQ(new_entity.GetWarnings().size(), 1); EXPECT_EQ(new_entity.GetWarnings().at(0).GetCode(), MessageCode::ENTITY_HAS_NO_PROPERTIES); } /* * Test deletion of an entity * Insert an entity first. */ TEST_F(test_transaction, insert_delete) { // same as in insert_without_delete // until marked END SAME const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto insert_transaction(connection->CreateTransaction()); Entity entity; entity.SetRole(Role::RECORD_TYPE); entity.SetName("RT1"); insert_transaction->InsertEntity(&entity); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); ASSERT_FALSE(insert_status.IsError()); const auto &insert_result_set = insert_transaction->GetResultSet(); const auto &new_entity = insert_result_set.at(0); EXPECT_FALSE(new_entity.GetId().empty()); // END SAME auto delete_transaction(connection->CreateTransaction()); delete_transaction->DeleteById(new_entity.GetId()); delete_transaction->ExecuteAsynchronously(); auto delete_status = delete_transaction->WaitForIt(); ASSERT_TRUE(delete_status.IsTerminated()); ASSERT_FALSE(delete_status.IsError()); const auto &delete_result_set = delete_transaction->GetResultSet(); const auto &deleted_entity = delete_result_set.at(0); EXPECT_EQ(deleted_entity.GetId(), new_entity.GetId()); EXPECT_FALSE(deleted_entity.HasErrors()); } /* * Insert a Record with a parent, retrieve and delete it again */ TEST_F(test_transaction, insert_delete_with_parent) { // same as in insert_without_delete // until marked END SAME const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto insert_transaction(connection->CreateTransaction()); // insert RT Entity rt; rt.SetRole(Role::RECORD_TYPE); rt.SetName("TestRT"); insert_transaction->InsertEntity(&rt); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); ASSERT_FALSE(insert_status.IsError()); const auto &insert_result_set = insert_transaction->GetResultSet(); const auto &inserted_rt = insert_result_set.at(0); // END SAME // create Record with parent Entity rec; rec.SetRole(Role::RECORD); rec.SetName("TestRec"); Parent parent; parent.SetName(rt.GetName()); parent.SetId(inserted_rt.GetId()); rec.AppendParent(parent); // insert Record auto rec_transaction(connection->CreateTransaction()); rec_transaction->InsertEntity(&rec); rec_transaction->ExecuteAsynchronously(); auto rec_insert_status = rec_transaction->WaitForIt(); ASSERT_TRUE(rec_insert_status.IsTerminated()); ASSERT_FALSE(rec_insert_status.IsError()); const auto &rec_result_set = rec_transaction->GetResultSet(); const auto &inserted_rec = rec_result_set.at(0); EXPECT_FALSE(inserted_rec.GetId().empty()); // retrieve inserted Record auto retrieve_transaction(connection->CreateTransaction()); retrieve_transaction->RetrieveById(inserted_rec.GetId()); retrieve_transaction->ExecuteAsynchronously(); auto rec_retrieve_status = retrieve_transaction->WaitForIt(); ASSERT_TRUE(rec_retrieve_status.IsTerminated()); ASSERT_FALSE(rec_retrieve_status.IsError()); const auto &retrieve_result_set = retrieve_transaction->GetResultSet(); const auto &retrieved_rec = retrieve_result_set.at(0); // compare retrieved record with the one that was inserted EXPECT_EQ(retrieved_rec.GetName(), rec.GetName()); EXPECT_EQ(retrieved_rec.GetParents().size(), 1); EXPECT_EQ(retrieved_rec.GetParents().at(0).GetId(), inserted_rt.GetId()); EXPECT_EQ(retrieved_rec.GetParents().at(0).GetName(), rt.GetName()); // delete the inserted record again auto rec_deletion(connection->CreateTransaction()); rec_deletion->DeleteById(retrieved_rec.GetId()); rec_deletion->ExecuteAsynchronously(); auto rec_delete_status = rec_deletion->WaitForIt(); ASSERT_TRUE(rec_delete_status.IsTerminated()); ASSERT_FALSE(rec_delete_status.IsError()); // delete the record type again auto rt_deletion(connection->CreateTransaction()); rt_deletion->DeleteById(inserted_rt.GetId()); rt_deletion->ExecuteAsynchronously(); auto rt_delete_status = rt_deletion->WaitForIt(); ASSERT_TRUE(rt_delete_status.IsTerminated()); ASSERT_FALSE(rt_delete_status.IsError()); } /* * insert a property, record type with that property and a record with it */ TEST_F(test_transaction, insert_delete_with_property) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); // Create and insert Text property Entity prop_ent; prop_ent.SetRole(Role::PROPERTY); prop_ent.SetName("TestProperty"); prop_ent.SetDataType(AtomicDataType::TEXT); auto prop_insertion(connection->CreateTransaction()); prop_insertion->InsertEntity(&prop_ent); prop_insertion->ExecuteAsynchronously(); auto prop_insert_status = prop_insertion->WaitForIt(); ASSERT_TRUE(prop_insert_status.IsTerminated()); ASSERT_FALSE(prop_insert_status.IsError()); const auto &prop_result_set = prop_insertion->GetResultSet(); const auto &inserted_prop = prop_result_set.at(0); EXPECT_FALSE(inserted_prop.GetId().empty()); // Create and insert Double property Entity prop_double_ent; prop_double_ent.SetRole(Role::PROPERTY); prop_double_ent.SetName("TestDoubleProperty"); prop_double_ent.SetDataType(AtomicDataType::DOUBLE); auto prop_double_insertion = connection->CreateTransaction(); prop_double_insertion->InsertEntity(&prop_double_ent); prop_double_insertion->ExecuteAsynchronously(); prop_insert_status = prop_double_insertion->WaitForIt(); ASSERT_TRUE(prop_insert_status.IsTerminated()); ASSERT_FALSE(prop_insert_status.IsError()); const auto &prop_double_result_set = prop_double_insertion->GetResultSet(); const auto &inserted_prop_double = prop_double_result_set.at(0); EXPECT_FALSE(inserted_prop_double.GetId().empty()); // create and insert record type with the above properties Property prop_rt; prop_rt.SetName(prop_ent.GetName()); prop_rt.SetId(inserted_prop.GetId()); prop_rt.SetImportance(Importance::SUGGESTED); Property prop_double; prop_double.SetName(prop_double_ent.GetName()); prop_double.SetId(inserted_prop_double.GetId()); prop_double.SetImportance(Importance::SUGGESTED); Entity rt; rt.SetRole(Role::RECORD_TYPE); rt.SetName("TestRT"); rt.SetDescription("Some description"); rt.AppendProperty(prop_rt); rt.AppendProperty(prop_double); auto rt_insertion(connection->CreateTransaction()); rt_insertion->InsertEntity(&rt); rt_insertion->ExecuteAsynchronously(); auto rt_insert_status = rt_insertion->WaitForIt(); ASSERT_TRUE(rt_insert_status.IsTerminated()); ASSERT_FALSE(rt_insert_status.IsError()); const auto &rt_result_set = rt_insertion->GetResultSet(); const auto &inserted_rt = rt_result_set.at(0); EXPECT_FALSE(inserted_rt.GetId().empty()); // retrieve inserted rt for testing auto rt_retrieval(connection->CreateTransaction()); rt_retrieval->RetrieveById(inserted_rt.GetId()); rt_retrieval->ExecuteAsynchronously(); auto rt_retrieve_status = rt_retrieval->WaitForIt(); ASSERT_TRUE(rt_retrieve_status.IsTerminated()); ASSERT_FALSE(rt_retrieve_status.IsError()); const auto &rt_retrieve_results = rt_retrieval->GetResultSet(); const auto &retrieved_rt = rt_retrieve_results.at(0); EXPECT_EQ(inserted_rt.GetId(), retrieved_rt.GetId()); EXPECT_EQ(rt.GetName(), retrieved_rt.GetName()); EXPECT_EQ(rt.GetDescription(), retrieved_rt.GetDescription()); EXPECT_EQ(retrieved_rt.GetProperties().size(), 2); const Property *retrieved_prop_rt = nullptr; // get by name is not possible yet if (retrieved_rt.GetProperties().at(0).GetName() == prop_rt.GetName()) { retrieved_prop_rt = &retrieved_rt.GetProperties().at(0); } else { retrieved_prop_rt = &retrieved_rt.GetProperties().at(1); } EXPECT_EQ(retrieved_prop_rt->GetName(), prop_rt.GetName()); EXPECT_EQ(retrieved_prop_rt->GetId(), inserted_prop.GetId()); EXPECT_EQ(retrieved_prop_rt->GetDataType(), prop_ent.GetDataType()); EXPECT_EQ(retrieved_prop_rt->GetImportance(), prop_rt.GetImportance()); // create and insert record of the above record type with a property // with a value. Parent parent; parent.SetName(rt.GetName()); parent.SetId(inserted_rt.GetId()); Property prop_rec; prop_rec.SetName(prop_ent.GetName()); prop_rec.SetId(inserted_prop.GetId()); prop_rec.SetValue(std::string("Test")); Property prop_double_rec; prop_double_rec.SetName(prop_double_ent.GetName()); prop_double_rec.SetId(inserted_prop_double.GetId()); prop_double_rec.SetValue(123456789.98700001); Entity rec; rec.SetName("TestRec"); rec.SetRole(Role::RECORD); rec.AppendParent(parent); rec.AppendProperty(prop_rec); rec.AppendProperty(prop_double_rec); auto rec_insertion(connection->CreateTransaction()); rec_insertion->InsertEntity(&rec); rec_insertion->ExecuteAsynchronously(); auto rec_insert_status = rec_insertion->WaitForIt(); ASSERT_TRUE(rec_insert_status.IsTerminated()); ASSERT_FALSE(rec_insert_status.IsError()); const auto &rec_result_set = rec_insertion->GetResultSet(); const auto &inserted_rec = rec_result_set.at(0); EXPECT_FALSE(inserted_rec.GetId().empty()); // Retrieve the record and verify parent and property auto rec_retrieval(connection->CreateTransaction()); rec_retrieval->RetrieveById(inserted_rec.GetId()); rec_retrieval->ExecuteAsynchronously(); auto rec_retrieve_status = rec_retrieval->WaitForIt(); ASSERT_TRUE(rec_retrieve_status.IsTerminated()); ASSERT_FALSE(rec_retrieve_status.IsError()); const auto &rec_retrieve_results = rec_retrieval->GetResultSet(); const auto &retrieved_rec = rec_retrieve_results.at(0); EXPECT_EQ(rec.GetName(), retrieved_rec.GetName()); EXPECT_EQ(inserted_rec.GetId(), retrieved_rec.GetId()); EXPECT_EQ(retrieved_rec.GetParents().size(), 1); EXPECT_EQ(retrieved_rec.GetProperties().size(), 2); const auto &retrieved_parent_rec = retrieved_rec.GetParents().at(0); EXPECT_EQ(retrieved_parent_rec.GetName(), rt.GetName()); EXPECT_EQ(retrieved_parent_rec.GetId(), inserted_rt.GetId()); EXPECT_EQ(retrieved_parent_rec.GetDescription(), rt.GetDescription()); const auto &retrieved_prop_0 = retrieved_rec.GetProperties().at(0); const auto &retrieved_prop_1 = retrieved_rec.GetProperties().at(1); const Property *retrieved_prop_rec = nullptr; const Property *retrieved_prop_double = nullptr; if (retrieved_prop_0.GetName() == prop_ent.GetName()) { retrieved_prop_rec = &retrieved_prop_0; retrieved_prop_double = &retrieved_prop_1; } else { retrieved_prop_rec = &retrieved_prop_1; retrieved_prop_double = &retrieved_prop_0; } EXPECT_EQ(retrieved_prop_rec->GetName(), prop_ent.GetName()); EXPECT_EQ(retrieved_prop_rec->GetId(), inserted_prop.GetId()); EXPECT_EQ(retrieved_prop_rec->GetDataType(), prop_ent.GetDataType()); EXPECT_EQ(retrieved_prop_rec->GetValue(), prop_rec.GetValue()); EXPECT_EQ(retrieved_prop_double->GetName(), prop_double_ent.GetName()); EXPECT_EQ(retrieved_prop_double->GetId(), inserted_prop_double.GetId()); EXPECT_EQ(retrieved_prop_double->GetDataType(), prop_double_ent.GetDataType()); EXPECT_TRUE(retrieved_prop_double->GetValue().IsDouble()); EXPECT_EQ(retrieved_prop_double->GetValue(), prop_double_rec.GetValue()); } /* * test retrieving multiple entities at once */ TEST_F(test_transaction, test_multi_retrieve) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto transaction(connection->CreateTransaction()); const std::vector<std::string> ids = {"20", "21", "22"}; transaction->RetrieveById(ids.begin(), ids.end()); transaction->ExecuteAsynchronously(); auto status = transaction->WaitForIt(); ASSERT_TRUE(status.IsTerminated()); // Should have an error since entity 22 doesn't exist ASSERT_TRUE(status.IsError()); const auto &result_set = transaction->GetResultSet(); EXPECT_EQ(result_set.size(), 3); EXPECT_EQ(result_set.at(1).GetId(), "21"); EXPECT_EQ(result_set.at(1).GetName(), "unit"); // Exists so should be fine ... EXPECT_FALSE(result_set.at(1).HasErrors()); // ... but this does not EXPECT_EQ(result_set.at(2).GetId(), "22"); EXPECT_TRUE(result_set.at(2).HasErrors()); EXPECT_EQ(result_set.at(2).GetErrors().at(0).GetCode(), MessageCode::ENTITY_DOES_NOT_EXIST); } /* * test insert, then update and then delete a RecordType */ TEST_F(test_transaction, insert_update_delete) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); // INSERT auto insert_transaction(connection->CreateTransaction()); Entity entity; entity.SetRole(Role::RECORD_TYPE); entity.SetName("RT1"); insert_transaction->InsertEntity(&entity); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); ASSERT_FALSE(insert_status.IsError()); const auto &insert_result_set = insert_transaction->GetResultSet(); const auto &new_entity = insert_result_set.at(0); EXPECT_FALSE(new_entity.GetId().empty()); EXPECT_FALSE(new_entity.HasErrors()); // RETRIEVE auto retrieve_transaction(connection->CreateTransaction()); retrieve_transaction->RetrieveById(new_entity.GetId()); retrieve_transaction->Execute(); // UPDATE auto update_transaction(connection->CreateTransaction()); auto update_entity(retrieve_transaction->GetResultSet().at(0)); update_entity.SetName("RT1-Update"); update_transaction->UpdateEntity(&update_entity); update_transaction->ExecuteAsynchronously(); auto update_status = update_transaction->WaitForIt(); ASSERT_TRUE(update_status.IsTerminated()); ASSERT_FALSE(update_status.IsError()); EXPECT_EQ(update_transaction->GetResultSet().size(), 1); const auto &updated_entity = update_transaction->GetResultSet().at(0); EXPECT_EQ(updated_entity.GetId(), new_entity.GetId()); EXPECT_FALSE(updated_entity.HasErrors()); // DELETE auto delete_transaction(connection->CreateTransaction()); delete_transaction->DeleteById(new_entity.GetId()); delete_transaction->ExecuteAsynchronously(); auto delete_status = delete_transaction->WaitForIt(); ASSERT_TRUE(delete_status.IsTerminated()); ASSERT_FALSE(delete_status.IsError()); const auto &delete_result_set = delete_transaction->GetResultSet(); const auto &deleted_entity = delete_result_set.at(0); EXPECT_EQ(deleted_entity.GetId(), new_entity.GetId()); EXPECT_FALSE(deleted_entity.HasErrors()); } /* * test insert a RecordType and then submit a find and a count query */ TEST_F(test_transaction, test_query) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); auto insert_transaction(connection->CreateTransaction()); Entity entity; entity.SetRole(Role::RECORD_TYPE); entity.SetName("RT1"); insert_transaction->InsertEntity(&entity); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); ASSERT_FALSE(insert_status.IsError()); const auto &insert_result_set = insert_transaction->GetResultSet(); const auto &new_entity = insert_result_set.at(0); EXPECT_FALSE(new_entity.GetId().empty()); EXPECT_FALSE(new_entity.HasErrors()); auto query_transaction(connection->CreateTransaction()); query_transaction->Query("FIND ENTITY WITH id = " + new_entity.GetId()); query_transaction->Execute(); EXPECT_EQ(query_transaction->GetResultSet().size(), 1); EXPECT_EQ(query_transaction->GetResultSet().at(0).GetId(), new_entity.GetId()); // No count query, so no count result should be present EXPECT_TRUE((query_transaction->GetCountResult() < 0)); auto count_query_trans(connection->CreateTransaction()); std::cout << "Creating count query ..." << std::endl; count_query_trans->Query("COUNT ENTITY WITH id = " + new_entity.GetId()); std::cout << "Executing count query ..." << std::endl; count_query_trans->Execute(); // No result set in a count query EXPECT_EQ(count_query_trans->GetResultSet().size(), 0); EXPECT_EQ(count_query_trans->GetCountResult(), 1); } /** * Test numeric values (template). */ template <typename T> auto test_numeric_values_impl(AtomicDataType a_type) -> void { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); // Insert entities auto values_orig = test_transaction::generateValues<T>(); auto props_orig = std::vector<Entity>(); size_t i = 0; for (auto value : values_orig) { auto insert_transaction(connection->CreateTransaction()); Entity prop; prop.SetRole(Role::PROPERTY); const auto name = std::string("Prop ") + std::to_string(i); std::cout << "Creating: " << name << std::endl; prop.SetName(name); prop.SetDataType(a_type); std::cout << "Setting value " << value << std::endl; prop.SetValue(value); props_orig.push_back(prop); auto i_stat = insert_transaction->InsertEntity(&prop); EXPECT_EQ(i_stat, StatusCode::READY); insert_transaction->ExecuteAsynchronously(); auto t_stat = insert_transaction->WaitForIt(); EXPECT_TRUE(t_stat.IsTerminated()); EXPECT_FALSE(t_stat.IsError()); ++i; } // Retrieve and verify i = 0; for (const auto value : values_orig) { auto retrieve_transaction(connection->CreateTransaction()); const auto prop = props_orig[i]; const auto name = std::string("Prop ") + std::to_string(i); std::cout << "Retrieving: " << name << std::endl; const auto query = std::string("FIND ENTITY \"") + name + "\""; retrieve_transaction->Query(query); retrieve_transaction->ExecuteAsynchronously(); const auto t_stat = retrieve_transaction->WaitForIt(); EXPECT_TRUE(t_stat.IsTerminated()); EXPECT_FALSE(t_stat.IsError()); const auto result = retrieve_transaction->GetResultSet().at(0); EXPECT_EQ(result.GetDataType(), a_type); const auto &retrieved_value = test_transaction::getValueAs<T>(result.GetValue()); // std::cout << "retrieved_value: " << retrieved_value << std::endl; EXPECT_EQ(retrieved_value, value); ++i; } } /** * Test numeric values (wrapper for types). */ TEST_F(test_transaction, test_numeric_values) { test_numeric_values_impl<double>(AtomicDataType::DOUBLE); test_transaction::DeleteEntities(); test_numeric_values_impl<int32_t>(AtomicDataType::INTEGER); test_transaction::DeleteEntities(); test_numeric_values_impl<bool>(AtomicDataType::BOOLEAN); } // /* // * test miscellaneous queries // */ // TEST_F(test_transaction, test_queries_misc) { // const auto &connection = // caosdb::connection::ConnectionManager::GetDefaultConnection(); // // query empty database // auto query_transaction(connection->CreateTransaction()); // query_transaction->Query("FIND Property \"Prop *\""); // query_transaction->ExecuteAsynchronously(); // auto t_stat = query_transaction->WaitForIt(); // std::cout << "status: " << t_stat.GetCode() << " // " // << t_stat.GetDescription() << std::endl; // EXPECT_TRUE(t_stat.GetCode() >= 0); // } /* * insert three recordtypes and the submit multiple queries in different * combinations */ TEST_F(test_transaction, test_query_with_retrieve) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); // rt1 Entity rt1; rt1.SetRole(Role::RECORD_TYPE); rt1.SetName("TestRT1"); auto insert_rt1_transaction(connection->CreateTransaction()); insert_rt1_transaction->InsertEntity(&rt1); insert_rt1_transaction->ExecuteAsynchronously(); auto insert_rt1_status = insert_rt1_transaction->WaitForIt(); ASSERT_TRUE(insert_rt1_status.IsTerminated()); ASSERT_FALSE(insert_rt1_status.IsError()); const auto &insert_rt1_results = insert_rt1_transaction->GetResultSet(); const auto &inserted_rt1 = insert_rt1_results.at(0); EXPECT_FALSE(inserted_rt1.GetId().empty()); EXPECT_FALSE(inserted_rt1.HasErrors()); // rt2 Entity rt2; rt2.SetRole(Role::RECORD_TYPE); rt2.SetName("TestRT2"); auto insert_rt2_transaction(connection->CreateTransaction()); insert_rt2_transaction->InsertEntity(&rt2); insert_rt2_transaction->ExecuteAsynchronously(); auto insert_rt2_status = insert_rt2_transaction->WaitForIt(); ASSERT_TRUE(insert_rt2_status.IsTerminated()); ASSERT_FALSE(insert_rt2_status.IsError()); const auto &insert_rt2_results = insert_rt2_transaction->GetResultSet(); const auto &inserted_rt2 = insert_rt2_results.at(0); EXPECT_FALSE(inserted_rt2.GetId().empty()); EXPECT_FALSE(inserted_rt2.HasErrors()); // rt3 Entity rt3; rt3.SetRole(Role::RECORD_TYPE); rt3.SetName("TestRT3"); auto insert_rt3_transaction(connection->CreateTransaction()); insert_rt3_transaction->InsertEntity(&rt3); insert_rt3_transaction->ExecuteAsynchronously(); auto insert_rt3_status = insert_rt3_transaction->WaitForIt(); ASSERT_TRUE(insert_rt3_status.IsTerminated()); ASSERT_FALSE(insert_rt3_status.IsError()); const auto &insert_rt3_results = insert_rt3_transaction->GetResultSet(); const auto &inserted_rt3 = insert_rt3_results.at(0); EXPECT_FALSE(inserted_rt3.GetId().empty()); EXPECT_FALSE(inserted_rt3.HasErrors()); // only FIND auto find_transaction(connection->CreateTransaction()); find_transaction->Query("FIND ENTITY WITH name LIKE 'TestRT*'"); find_transaction->ExecuteAsynchronously(); const auto find_status = find_transaction->WaitForIt(); ASSERT_TRUE(find_status.IsTerminated()); ASSERT_FALSE(find_status.IsError()); const auto &find_results = find_transaction->GetResultSet(); EXPECT_EQ(find_results.size(), 3); // only retrieve rt1 and rt2 by id const std::vector<std::string> ids = {inserted_rt1.GetId(), inserted_rt2.GetId()}; // retrieve rt3 with a FIND query auto find_and_retrieve(connection->CreateTransaction()); find_and_retrieve->Query("FIND ENTITY WITH id = " + inserted_rt3.GetId()); find_and_retrieve->RetrieveById(ids.begin(), ids.end()); find_and_retrieve->ExecuteAsynchronously(); auto find_and_retrieve_status = find_and_retrieve->WaitForIt(); ASSERT_TRUE(find_and_retrieve_status.IsTerminated()); ASSERT_FALSE(find_and_retrieve_status.IsError()); const auto &result_set_a = find_and_retrieve->GetResultSet(); EXPECT_EQ(result_set_a.size(), 3); // retrieve rt1 and rt2 by ID and count all TestRTs auto count_and_retrieve(connection->CreateTransaction()); count_and_retrieve->Query("COUNT ENTITY WITH name LIKE 'TestRT*'"); count_and_retrieve->RetrieveById(ids.begin(), ids.end()); count_and_retrieve->ExecuteAsynchronously(); auto count_and_retrieve_status = count_and_retrieve->WaitForIt(); ASSERT_TRUE(count_and_retrieve_status.IsTerminated()); ASSERT_FALSE(count_and_retrieve_status.IsError()); const auto &result_set_b = count_and_retrieve->GetResultSet(); // TODO(fspreck) Re-enable once we implemented this // EXPECT_EQ(result_set_b.size(), 2); EXPECT_EQ(count_and_retrieve->GetCountResult(), 3); } /* * create a file object and upload it */ TEST_F(test_transaction, test_file_upload) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); Entity file; file.SetRole(Role::FILE); file.SetFilePath("test.txt"); file.SetLocalPath(test_upload_file_1); auto insert_transaction(connection->CreateTransaction()); insert_transaction->InsertEntity(&file); insert_transaction->ExecuteAsynchronously(); auto insert_status = insert_transaction->WaitForIt(); ASSERT_TRUE(insert_status.IsTerminated()); EXPECT_EQ(insert_status.GetCode(), StatusCode::SUCCESS); const auto &insert_results = insert_transaction->GetResultSet(); const auto &inserted_file = insert_results.at(0); EXPECT_FALSE(inserted_file.GetId().empty()); EXPECT_FALSE(inserted_file.HasErrors()); // Check file once more auto count_query(connection->CreateTransaction()); count_query->Query("COUNT FILE WHICH IS STORED AT 'test.txt'"); count_query->ExecuteAsynchronously(); auto count_and_retrieve_status = count_query->WaitForIt(); ASSERT_TRUE(count_and_retrieve_status.IsTerminated()); ASSERT_FALSE(count_and_retrieve_status.IsError()); EXPECT_EQ(count_query->GetCountResult(), 1); } /* * create a file object, upload and then download it */ TEST_F(test_transaction, test_file_up_n_download) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); Entity file; file.SetRole(Role::FILE); file.SetFilePath("test.txt"); file.SetLocalPath(test_upload_file_1); auto insert_transaction(connection->CreateTransaction()); insert_transaction->InsertEntity(&file); insert_transaction->Execute(); const auto &insert_results = insert_transaction->GetResultSet(); const auto &inserted_file = insert_results.at(0); ASSERT_FALSE(inserted_file.GetId().empty()); ASSERT_FALSE(inserted_file.HasErrors()); auto download_transaction(connection->CreateTransaction()); download_transaction->RetrieveAndDownloadFileById( inserted_file.GetId(), test_download_file_1.string()); download_transaction->ExecuteAsynchronously(); ASSERT_EQ(download_transaction->WaitForIt().GetCode(), StatusCode::SUCCESS); const auto &download_results = download_transaction->GetResultSet(); ASSERT_EQ(download_results.size(), 1); const auto &downloaded_file = download_results.at(0); ASSERT_FALSE(downloaded_file.GetId().empty()); ASSERT_FALSE(downloaded_file.HasErrors()); EXPECT_EQ(downloaded_file.GetLocalPath().string(), test_download_file_1.string()); FileReader reader_remote(test_upload_file_1); std::string buffer_local(1024, 'c'); std::string buffer_remote(1024, 'c'); for (int i = 0; i < 8; i++) { reader_remote.read(buffer_remote); EXPECT_EQ(buffer_remote, buffer_local); } } /* * Test a small worklfow */ TEST_F(test_transaction, test_full_workflow) { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); // ###### Create Data Model ###### Entity dataRT; dataRT.SetRole(Role::RECORD_TYPE); dataRT.SetName("DataRT"); Entity voltage; voltage.SetRole(Role::PROPERTY); voltage.SetName("voltage"); voltage.SetUnit("V"); voltage.SetDataType(AtomicDataType::DOUBLE); Entity notes; notes.SetRole(Role::PROPERTY); notes.SetName("notes"); notes.SetDataType(AtomicDataType::TEXT); Entity participants; participants.SetRole(Role::PROPERTY); participants.SetName("participants"); participants.SetDataType(AtomicDataType::INTEGER); // participants.SetDataType(ListDataType::); Entity success; success.SetRole(Role::PROPERTY); success.SetName("success"); success.SetDataType(AtomicDataType::BOOLEAN); Entity experiment; experiment.SetRole(Role::RECORD_TYPE); experiment.SetName("Experiment"); // TODO(henrik): creating this extra Property (additionally to the Entity // above) is VERY clumsy Property volt_for_rt; volt_for_rt.SetName(voltage.GetName()); volt_for_rt.SetImportance(Importance::RECOMMENDED); experiment.AppendProperty(volt_for_rt); Property notes_for_rt2; notes_for_rt2.SetName(notes.GetName()); notes_for_rt2.SetImportance(Importance::RECOMMENDED); experiment.AppendProperty(notes_for_rt2); Property part_for_rt3; part_for_rt3.SetName(participants.GetName()); part_for_rt3.SetImportance(Importance::SUGGESTED); experiment.AppendProperty(part_for_rt3); Property succ_for_rt; succ_for_rt.SetName(success.GetName()); succ_for_rt.SetImportance(Importance::SUGGESTED); experiment.AppendProperty(succ_for_rt); auto insert_transaction(connection->CreateTransaction()); insert_transaction->InsertEntity(&dataRT); auto insert_status = insert_transaction->Execute(); const auto &insert_results = insert_transaction->GetResultSet(); const auto &inserted_ent = insert_results.at(0); ASSERT_FALSE(inserted_ent.GetId().empty()); ASSERT_FALSE(inserted_ent.HasErrors()); insert_transaction = connection->CreateTransaction(); insert_transaction->InsertEntity(&voltage); insert_status = insert_transaction->Execute(); insert_transaction = connection->CreateTransaction(); insert_transaction->InsertEntity(¬es); insert_status = insert_transaction->Execute(); insert_transaction = connection->CreateTransaction(); insert_transaction->InsertEntity(&participants); insert_status = insert_transaction->Execute(); insert_transaction = connection->CreateTransaction(); insert_transaction->InsertEntity(&success); insert_status = insert_transaction->Execute(); insert_transaction = connection->CreateTransaction(); insert_transaction->InsertEntity(&experiment); insert_status = insert_transaction->Execute(); auto retr_transaction(connection->CreateTransaction()); retr_transaction->Query("FIND Experiment"); retr_transaction->Execute(); EXPECT_EQ(retr_transaction->GetResultSet().size(), 1); Parent experiment_parent; experiment_parent.SetName("Experiment"); for (int i = 0; i < 8; i++) { Entity experiment_rec; experiment_rec.SetRole(Role::RECORD); experiment_rec.AppendParent(experiment_parent); volt_for_rt.SetValue(1.6); experiment_rec.AppendProperty(volt_for_rt); notes_for_rt2.SetValue("This is important!"); experiment_rec.AppendProperty(notes_for_rt2); part_for_rt3.SetValue(static_cast<int32_t>(6)); experiment_rec.AppendProperty(part_for_rt3); succ_for_rt.SetValue(true); experiment_rec.AppendProperty(succ_for_rt); auto rec_insert_transaction(connection->CreateTransaction()); rec_insert_transaction->InsertEntity(&experiment_rec); std::cout << "before ..." << std::endl; insert_status = rec_insert_transaction->Execute(); std::cout << "after ..." << std::endl; } retr_transaction = connection->CreateTransaction(); retr_transaction->Query("FIND Record Experiment"); retr_transaction->Execute(); EXPECT_EQ(retr_transaction->GetResultSet().size(), 8); auto to_be_updated = retr_transaction->GetResultSet().at(0); // TODO(henrik) using the index for deletion is very inconvenient to_be_updated.RemoveProperty(0); to_be_updated.SetName("changedone"); auto update_transaction(connection->CreateTransaction()); update_transaction->UpdateEntity(&to_be_updated); update_transaction->Execute(); retr_transaction = connection->CreateTransaction(); retr_transaction->Query("FIND Record Experiment with name=changedone"); retr_transaction->Execute(); EXPECT_EQ(retr_transaction->GetResultSet().size(), 1); } } // namespace caosdb::transaction