diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dcc7c2f1df03b70fbf48ee48c41fb45be52ef8f..2f454fe5907ea2c6b6da41cd73e89b055ee529c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.13) -set(libcaosdb_VERSION 0.0.7) +set(libcaosdb_VERSION 0.0.8) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5) set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0) @@ -299,7 +299,7 @@ if(_LINTING) else() message(STATUS "clang-tidy: ${clang_tidy}") set(_CMAKE_CXX_CLANG_TIDY_CHECKS - "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type") + "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type,-bugprone-branch-clone") set(_CMAKE_C_CLANG_TIDY_CHECKS "${_CMAKE_CXX_CLANG_TIDY_CHECKS}") set(_CMAKE_CXX_CLANG_TIDY "${clang_tidy}" "--header-filter=caosdb/.*[^\(\.pb\.h\)]$" diff --git a/conanfile.py b/conanfile.py index 23e04a68a1b2a6881edbfe8c30e9612fea908a5f..0e922bc74936d4341c8a574220c6149459ac6b7e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -3,7 +3,7 @@ from conans import ConanFile, CMake, tools class CaosdbConan(ConanFile): name = "caosdb" - version = "0.0.7" + version = "0.0.8" license = "AGPL-3.0-or-later" author = "Timm C. Fitschen <t.fitschen@indiscale.com>" url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git" diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index d3eb7846d05d80ce406748799db9141d1bdd76fa..4249b91b275902a5b155484922b552d0d5238d67 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -86,10 +86,6 @@ public: private: inline Messages() : wrapped(nullptr){}; - explicit inline Messages( - ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message> - *wrapped) - : wrapped(wrapped){}; ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message> *wrapped; diff --git a/include/caosdb/logging.h b/include/caosdb/logging.h index fcebc6df8b5db2eb9f800893f8d296d6174285ce..2f04ec6087cfec19e1a5fbe87483544bd4e144a6 100644 --- a/include/caosdb/logging.h +++ b/include/caosdb/logging.h @@ -212,4 +212,10 @@ void caosdb_log_trace(const char *channel, const char *msg); BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ CAOSDB_LOG_LEVEL_TRACE) +#define CAOSDB_LOG_ERROR_AND_RETURN_STATUS(Channel, StatusCode, Message) \ + CAOSDB_LOG_ERROR(Channel) \ + << "StatusCode (" << StatusCode << ") " \ + << caosdb::get_status_description(StatusCode) << ": " << Message; \ + return StatusCode; + #endif diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h index 66574df7636e8ce55e2c53bae1dd445f797796ae..26f4abd4ea4c8c256f66e23268dcac744e4db3c0 100644 --- a/include/caosdb/status_code.h +++ b/include/caosdb/status_code.h @@ -24,6 +24,8 @@ #include <string> +namespace caosdb { + /** * StatusCodes represent the status of this client, it's connections, * configuration and so on. @@ -33,9 +35,6 @@ * GENERIC_TRANSACTION_ERROR indicates that *there are* errors in a * transaction). */ - -namespace caosdb { - enum StatusCode { INITIAL = -2, EXECUTING = -1, @@ -48,6 +47,8 @@ enum StatusCode { CONFIGURATION_ERROR = 23, UNKNOWN_CONNECTION_ERROR = 24, TRANSACTION_STATUS_ERROR = 25, + TRANSACTION_TYPE_ERROR = 26, + UNSUPPORTED_FEATURE = 27, }; auto get_status_description(int code) -> const std::string &; diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index 8b7dd9593f3d8b3ada661bf02e5e141ab1f7b0c4..9d957f08e3f2db4d7e4fb0e52d8351629c2c0f94 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -18,21 +18,98 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ - #ifndef CAOSDB_TRANSACTION_H #define CAOSDB_TRANSACTION_H -/** - * @brief Creation and execution of transactions. - */ -#include "caosdb/entity.h" // for Entity +#include "boost/log/core/record.hpp" // for record +#include "boost/log/sources/record_ostream.hpp" // for basic_record_o... +#include "boost/preprocessor/seq/limits/enum_256.hpp" // for BOOST_PP_SEQ_E... +#include "boost/preprocessor/seq/limits/size_256.hpp" // for BOOST_PP_SEQ_S... +#include "caosdb/entity.h" // for Entity +#include "caosdb/logging.h" #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe... #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RetrieveReq... #include "caosdb/transaction_status.h" // for TransactionStatus +#include "caosdb/status_code.h" // for StatusCode #include "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso... -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string +#include <stdexcept> +#include <iterator> +// IWYU pragma: no_include <ext/alloc_traits.h> +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string +#include <vector> // for vector +/** + * @brief Creation and execution of transactions. + * @author Timm Fitschen + * @date 2021-08-05 + */ namespace caosdb::transaction { + +/** + * Do all necessary checks and assure that another retrieval (by id or by + * query) can be added as a sub-request to a transaction. + */ +#define ASSERT_CAN_ADD_RETRIEVAL \ + if (!IsStatus(TransactionStatus::INITIAL())) { \ + return StatusCode::TRANSACTION_STATUS_ERROR; \ + } \ + switch (this->transaction_type) { \ + case NONE: \ + this->transaction_type = TransactionType::READ_ONLY; \ + case READ_ONLY: \ + case MIXED_READ_AND_WRITE: \ + break; \ + default: \ + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( \ + logger_name, StatusCode::TRANSACTION_TYPE_ERROR, \ + "You cannot add a retrieval to this transaction because it has the " \ + "wrong TransactionType.") \ + } + +/** + * Do all necessary checks and assure that another deletion can be added as a + * sub-request to a transaction. + */ +#define ASSERT_CAN_ADD_DELETION \ + if (!IsStatus(TransactionStatus::INITIAL())) { \ + return StatusCode::TRANSACTION_STATUS_ERROR; \ + } \ + switch (this->transaction_type) { \ + case NONE: \ + this->transaction_type = TransactionType::DELETE; \ + case DELETE: \ + case MIXED_WRITE: \ + case MIXED_READ_AND_WRITE: \ + break; \ + default: \ + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( \ + logger_name, StatusCode::TRANSACTION_TYPE_ERROR, \ + "You cannot add a deletion to this transaction because it has the " \ + "wrong TransactionType.") \ + } + +/** + * Do all necessary checks and assure that another insertion can be added as a + * sub-request to a transaction. + */ +#define ASSERT_CAN_ADD_INSERTION \ + if (!IsStatus(TransactionStatus::INITIAL())) { \ + return StatusCode::TRANSACTION_STATUS_ERROR; \ + } \ + switch (this->transaction_type) { \ + case NONE: \ + this->transaction_type = TransactionType::INSERT; \ + case INSERT: \ + case MIXED_WRITE: \ + case MIXED_READ_AND_WRITE: \ + break; \ + default: \ + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( \ + logger_name, StatusCode::TRANSACTION_TYPE_ERROR, \ + "You cannot add an insertion to this transaction because it has the " \ + "wrong TransactionType.") \ + } + using caosdb::entity::Entity; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using caosdb::entity::v1alpha1::EntityTransactionService; @@ -40,20 +117,65 @@ using caosdb::entity::v1alpha1::IdResponse; using caosdb::entity::v1alpha1::MultiTransactionRequest; using caosdb::entity::v1alpha1::MultiTransactionResponse; using caosdb::transaction::TransactionStatus; +using WrappedResponseCase = + caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase; + +class Transaction; +static const std::string logger_name = "caosdb::transaction"; + +/** + * Abstract base class for the results of a Transaction. + */ class ResultSet { public: virtual ~ResultSet() = default; + [[nodiscard]] virtual auto Size() const noexcept -> int = 0; + [[nodiscard]] virtual auto At(const int index) const -> const Entity & = 0; +}; + +/** + * Container with results of a transaction. + * + * In contrast to UniqueResult, this one can also hold multiple entities or zero + * entities. + */ +class MultiResultSet : public ResultSet { +public: + ~MultiResultSet() = default; + explicit MultiResultSet(MultiTransactionResponse *response); + [[nodiscard]] inline auto Size() const noexcept -> int override { + return this->entities.size(); + } + [[nodiscard]] inline auto At(const int index) const + -> const Entity & override { + return *(this->entities.at(index)); + } + std::vector<std::unique_ptr<Entity>> entities; }; +/** + * Container with the single result of a transaction. + * + * In contrast to MultiResultSet, this one guarantees to hold exactly one + * entity. + */ class UniqueResult : public ResultSet { public: - ~UniqueResult(){}; + ~UniqueResult() = default; explicit inline UniqueResult(ProtoEntity *protoEntity) : entity(new Entity(protoEntity)){}; explicit inline UniqueResult(IdResponse *idResponse) : entity(new Entity(idResponse)){}; [[nodiscard]] auto GetEntity() const -> const Entity &; + [[nodiscard]] inline auto Size() const noexcept -> int override { return 1; } + [[nodiscard]] inline auto At(const int index) const + -> const Entity & override { + if (index != 0) { + throw std::out_of_range("Index out of range. Length is 1."); + } + return *(this->entity); + } private: std::unique_ptr<Entity> entity; @@ -63,15 +185,23 @@ private: * @brief Create a transaction via `CaosDBConnection.createTransaction()` */ class Transaction { -private: - mutable std::unique_ptr<ResultSet> result_set; - mutable TransactionStatus status = TransactionStatus::INITIAL(); - std::shared_ptr<EntityTransactionService::Stub> service_stub; - MultiTransactionRequest *request; - mutable MultiTransactionResponse *response; - std::string error_message; - public: + /** + * The transaction type restricts the kind of sub-transaction which may be + * added to a transaction (insertion, update, deletion, retrieval). + * + * @note MIXED_READ_AND_WRITE and MIXED_WRITE transaction are not supported + * yet. + */ + enum TransactionType { + NONE, /// Unspecified or not specified yet. + READ_ONLY, /// Only retrievals (by id, by query) + INSERT, /// Only insertions + UPDATE, /// Only updates + DELETE, /// Only deletions + MIXED_WRITE, /// Only insertions, deletions, updates + MIXED_READ_AND_WRITE /// all kind of transaction. + }; Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub); /** @@ -80,7 +210,21 @@ public: * The retrieval is being processed when the Execute() or * ExecuteAsynchronously() methods of this transaction are called. */ - auto RetrieveById(const std::string &id) -> void; + auto RetrieveById(const std::string &id) noexcept -> StatusCode; + + /** + * Add all entity ids to this transaction for retrieval. + */ + template <class InputIterator> + inline auto RetrieveById(InputIterator begin, InputIterator end) noexcept + -> StatusCode; + + /** + * Add a query to this transaction. + * + * Currently, only FIND and COUNT queries are supported. + */ + auto Query(const std::string &query) noexcept -> StatusCode; /** * Add the entity to this transaction for an insertion. @@ -90,7 +234,7 @@ public: * * Changing the entity afterwards results in undefined behavior. */ - auto InsertEntity(Entity *entity) -> void; + auto InsertEntity(Entity *entity) noexcept -> StatusCode; /** * Add an entity id to this transaction for deletion. @@ -98,7 +242,7 @@ public: * The deletion is being processed when the Execute() or * ExecuteAsynchronously() methods of this transaction are called. */ - auto DeleteById(const std::string &id) -> void; + auto DeleteById(const std::string &id) noexcept -> StatusCode; inline auto IsStatus(const TransactionStatus &status) const noexcept -> bool { return this->status.GetCode() == status.GetCode(); @@ -116,7 +260,7 @@ public: * * Use WaitForIt() to join the back-ground execution of this transaction. */ - auto ExecuteAsynchronously() noexcept -> void; + auto ExecuteAsynchronously() noexcept -> StatusCode; /** * Join the background execution and return the status when the execution @@ -138,13 +282,48 @@ public: return *result_set; } + /** + * Return the number of sub-requests in this transaction. + * + * This is meant for debugging because the number of sub-requests is a + * GRPC-API detail. + */ + [[nodiscard]] inline auto GetRequestCount() const -> int { + return this->request->requests_size(); + } + inline auto RequestToString() const -> const std::string { google::protobuf::util::JsonOptions options; std::string out; google::protobuf::util::MessageToJsonString(*this->request, &out, options); return out; } + +private: + TransactionType transaction_type = TransactionType::NONE; + mutable std::unique_ptr<ResultSet> result_set; + mutable TransactionStatus status = TransactionStatus::INITIAL(); + std::shared_ptr<EntityTransactionService::Stub> service_stub; + MultiTransactionRequest *request; + mutable MultiTransactionResponse *response; + std::string error_message; }; +template <class InputIterator> +inline auto Transaction::RetrieveById(InputIterator begin, + InputIterator end) noexcept + -> StatusCode { + ASSERT_CAN_ADD_RETRIEVAL + + auto next = begin; + while (next != end) { + auto *sub_request = this->request->add_requests(); + sub_request->mutable_retrieve_request()->set_id(*next); + next = std::next(next); + } + + return StatusCode::INITIAL; +} + } // namespace caosdb::transaction #endif diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h index 8e54c8ba79c944f6b157a577cc9db64be01e1473..56c6b005112fd15c76f3c2741803a58837baed9e 100644 --- a/include/caosdb/transaction_status.h +++ b/include/caosdb/transaction_status.h @@ -22,15 +22,6 @@ #ifndef CAOSDB_TRANSACTION_STATUS_H #define CAOSDB_TRANSACTION_STATUS_H -/** - * TransactionStatus indicates the current status of a transaction and, when it - * has already terminated, whether the transaction has been successful or not. - * - * A status code of 0 denotes a generic success state, positive values indicate - * errors, and negative values indicate other states, such as different stages - * of a transaction in process. - */ - #include "caosdb/status_code.h" #include "caosdb/exceptions.h" #include <memory> // for shared_ptr, unique_ptr @@ -44,7 +35,12 @@ using caosdb::exceptions::Exception; using caosdb::exceptions::TransactionError; /** - * Status of a Request or Transaction. + * TransactionStatus indicates the current status of a transaction and, when it + * has already terminated, whether the transaction has been successful or not. + * + * A status code of 0 denotes a generic success state, positive values indicate + * errors, and negative values indicate other states, such as different stages + * of a transaction in process. */ class TransactionStatus { public: diff --git a/src/caosdb/configuration.cpp b/src/caosdb/configuration.cpp index ac78a257f49137a165e7083233dfb7c5f6e7ac88..76c437234a48fb339f1e04d71afe3ae84ce94ceb 100644 --- a/src/caosdb/configuration.cpp +++ b/src/caosdb/configuration.cpp @@ -35,15 +35,16 @@ #include "caosdb/log_level.h" // for CAOSDB_DEFAULT... #include "caosdb/status_code.h" // for StatusCode #include "caosdb/utility.h" // for get_home_direc... -#include <bits/exception.h> // for exception #include <cassert> // for assert #include <cstdlib> // for getenv #include <cstring> // for strcmp -#include <grpcpp/security/credentials.h> // for SslCredentials -#include <iterator> // for next -#include <map> // for map -#include <stdexcept> // for out_of_range -#include <string> // for string, operator+ +#include <exception> // IWYU pragma: keep +// IWYU pragma: no_include <bits/exception.h> +#include <grpcpp/security/credentials.h> // for SslCredentials +#include <iterator> // for next +#include <map> // for map +#include <stdexcept> // for out_of_range +#include <string> // for string, operator+ namespace caosdb::configuration { using boost::filesystem::exists; diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index 66bfac7b98e6f404076f6fbcab82dde563c8be12..d0b9025b7b1a5b48741e8687db9c21136864c683 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -18,9 +18,9 @@ * */ #include "caosdb/transaction.h" -#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS... -#include "caosdb/entity/v1alpha1/main.pb.h" // for SingleRetrieveRequest -#include "caosdb/exceptions.h" // for TransactionError, ... +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS... +#include "caosdb/entity/v1alpha1/main.pb.h" // for SingleRetrieveRequest +#include "caosdb/logging.h" #include "caosdb/protobuf_helper.h" // for get_arena #include "caosdb/status_code.h" // for StatusCode, AUTHEN... #include "google/protobuf/arena.h" // for Arena @@ -64,7 +64,12 @@ auto get_status_description(int code) -> const std::string & { {StatusCode::UNKNOWN_CONNECTION_ERROR, "The ConnectionManager does not know any connection of this name."}, {StatusCode::TRANSACTION_STATUS_ERROR, - "The Transaction is in a wrong state for the attempted action."}}; + "The Transaction is in a wrong state for the attempted action."}, + {StatusCode::TRANSACTION_TYPE_ERROR, + "The Transaction has a transaction type which does not allow the " + "attempted action."}, + {StatusCode::UNSUPPORTED_FEATURE, + "This feature is not available in the this client implementation."}}; try { return descriptions.at(code); } catch (const std::out_of_range &exc) { @@ -80,13 +85,34 @@ using caosdb::entity::v1alpha1::MultiTransactionRequest; using caosdb::entity::v1alpha1::MultiTransactionResponse; using WrappedResponseCase = caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase; -using caosdb::exceptions::TransactionError; -using caosdb::exceptions::TransactionStatusError; using caosdb::utility::get_arena; using grpc::ClientAsyncResponseReader; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using grpc::CompletionQueue; +MultiResultSet::MultiResultSet(MultiTransactionResponse *response) { + auto *responses = response->mutable_responses(); + for (auto sub_response : *responses) { + switch (sub_response.wrapped_response_case()) { + case WrappedResponseCase::kRetrieveResponse: + this->entities.push_back(std::make_unique<Entity>( + sub_response.mutable_retrieve_response()->release_entity())); + break; + case WrappedResponseCase::kInsertResponse: + this->entities.push_back( + std::make_unique<Entity>(sub_response.release_insert_response())); + break; + case WrappedResponseCase::kDeleteResponse: + this->entities.push_back( + std::make_unique<Entity>(sub_response.release_insert_response())); + break; + default: + // TODO(tf) Updates + break; + } + } +} + [[nodiscard]] auto UniqueResult::GetEntity() const -> const Entity & { const Entity *result = this->entity.get(); return *result; @@ -101,55 +127,42 @@ Transaction::Transaction( this->service_stub = std::move(service_stub); } -auto Transaction::RetrieveById(const std::string &id) -> void { - if (!IsStatus(TransactionStatus::INITIAL())) { - throw TransactionStatusError( - caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR)); - } - - // TODO(tf) remove checks when the server is ready - if (this->request->requests_size() > 0) { - throw TransactionError( - "This request object cannot handle another RetrieveById sub-request"); - } +auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode { + ASSERT_CAN_ADD_RETRIEVAL auto *sub_request = this->request->add_requests(); sub_request->mutable_retrieve_request()->set_id(id); + + return StatusCode::INITIAL; } -auto Transaction::DeleteById(const std::string &id) -> void { - if (!IsStatus(TransactionStatus::INITIAL())) { - throw TransactionStatusError( - caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR)); - } +auto Transaction::Query(const std::string &query) noexcept -> StatusCode { + ASSERT_CAN_ADD_RETRIEVAL - // TODO(tf) remove checks when the server is ready - if (this->request->requests_size() > 0) { - throw TransactionError( - "This request object cannot handle another DeleteById sub-request"); - } + auto *sub_request = this->request->add_requests(); + sub_request->mutable_retrieve_request()->mutable_query()->set_query(query); + + return StatusCode::INITIAL; +} + +auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode { + ASSERT_CAN_ADD_DELETION auto *sub_request = this->request->add_requests(); sub_request->mutable_delete_request()->set_id(id); -} -auto Transaction::InsertEntity(Entity *entity) -> void { - if (!IsStatus(TransactionStatus::INITIAL())) { - throw TransactionStatusError( - caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR)); - } + return StatusCode::INITIAL; +} - // TODO(tf) remove checks when the server is ready - if (this->request->requests_size() > 0) { - throw TransactionError( - "This request object cannot handle another DeleteById sub-request"); - } +auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { + ASSERT_CAN_ADD_INSERTION auto *sub_request = this->request->add_requests(); auto *proto_entity = sub_request->mutable_insert_request(); // copy the original entity for the transaction entity->CopyTo(proto_entity); + return StatusCode::INITIAL; } auto Transaction::Execute() -> TransactionStatus { @@ -159,7 +172,26 @@ auto Transaction::Execute() -> TransactionStatus { return status; } -auto Transaction::ExecuteAsynchronously() noexcept -> void { +auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { + if (!IsStatus(TransactionStatus::INITIAL())) { + return StatusCode::TRANSACTION_STATUS_ERROR; + } + switch (this->transaction_type) { + case MIXED_WRITE: + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( + logger_name, StatusCode::UNSUPPORTED_FEATURE, + "MIXED_WRITE UNSUPPORTED: The current implementation does not support " + "mixed write transactions (containing insertions, deletions, and updates " + "in one transaction).") + case MIXED_READ_AND_WRITE: + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( + logger_name, StatusCode::UNSUPPORTED_FEATURE, + "MIXED_WRITE UNSUPPORTED: The current implementation does not support " + "mixed read and write transactions (containing retrievals, insertions, " + "deletions, and updates in one transaction).") + default: + break; + } this->status = TransactionStatus::EXECUTING(); grpc::Status grpc_status; @@ -198,6 +230,7 @@ auto Transaction::ExecuteAsynchronously() noexcept -> void { } else { this->status = TransactionStatus::SUCCESS(); } + return StatusCode::EXECUTING; } auto Transaction::WaitForIt() const noexcept -> TransactionStatus { @@ -229,11 +262,11 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus { this->result_set = std::make_unique<UniqueResult>(deletedIdResponse); } break; default: - // TODO(tf) + // TODO(tf) Error and Update break; } } else { - // TODO(tf) + this->result_set = std::make_unique<MultiResultSet>(this->response); } return this->status; diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index ab1979f7e455d8b1b9fd7e8d672bd1b8ab1f5775..118208595ea5d100dacb8302be555772c8fcd80e 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -1,5 +1,4 @@ /* - * * This file is a part of the CaosDB Project. * * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> @@ -17,7 +16,6 @@ * * 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/configuration.h" // for InsecureConnectionConfig... #include "caosdb/connection.h" // for Connection @@ -31,7 +29,10 @@ #include "gtest/gtest-message.h" // for Message #include "gtest/gtest-test-part.h" // for SuiteApiResolver, TestPa... #include "gtest/gtest_pred_impl.h" // for Test, TestInfo, TEST -#include <memory> // for allocator, unique_ptr +#include <iostream> +#include <memory> // for allocator, unique_ptr +#include <string> // for string, basic_string +#include <vector> // for vector namespace caosdb::transaction { using caosdb::configuration::InsecureConnectionConfiguration; @@ -73,9 +74,62 @@ TEST(test_transaction, test_unavailable) { transaction->RetrieveById("100"); transaction->ExecuteAsynchronously(); + EXPECT_EQ(transaction->GetRequestCount(), 1); auto status = transaction->WaitForIt(); EXPECT_EQ(status.GetCode(), StatusCode::CONNECTION_ERROR); } +TEST(test_transaction, test_retrieve_by_ids) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + + std::vector<std::string> ids = {"100", "101", "102"}; + transaction->RetrieveById(ids.begin(), ids.end()); + + EXPECT_EQ(transaction->GetRequestCount(), 3); +} + +TEST(test_transaction, test_multi_result_set_empty) { + MultiTransactionResponse response; + + MultiResultSet rs(&response); + EXPECT_EQ(rs.Size(), 0); +} + +TEST(test_transaction, test_multi_result_set_one) { + MultiTransactionResponse response; + response.add_responses() + ->mutable_retrieve_response() + ->mutable_entity() + ->set_id("100"); + + MultiResultSet rs(&response); + EXPECT_EQ(rs.Size(), 1); + EXPECT_EQ(rs.At(0).GetId(), "100"); +} + +TEST(test_transaction, test_multi_result_set_three) { + MultiTransactionResponse response; + response.add_responses() + ->mutable_retrieve_response() + ->mutable_entity() + ->set_id("100"); + auto *entity_with_error = + response.add_responses()->mutable_retrieve_response()->mutable_entity(); + entity_with_error->set_id("101"); + entity_with_error->add_errors()->set_code(1); + response.add_responses() + ->mutable_retrieve_response() + ->mutable_entity() + ->set_id("102"); + + MultiResultSet rs(&response); + EXPECT_EQ(rs.Size(), 3); + EXPECT_TRUE(rs.At(1).HasErrors()); + std::cout << rs.At(1).ToString(); +} + } // namespace caosdb::transaction