diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f454fe5907ea2c6b6da41cd73e89b055ee529c5..39d15c9c54cf933e04bd2827d8175ff91adee090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.13) -set(libcaosdb_VERSION 0.0.8) +set(libcaosdb_VERSION 0.0.9) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5) set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0) diff --git a/conanfile.py b/conanfile.py index 0e922bc74936d4341c8a574220c6149459ac6b7e..e2133cea76ce826d168364259d6a519ba28a75d3 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.8" + version = "0.0.9" 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 4249b91b275902a5b155484922b552d0d5238d67..11b425f3f33fecfabc2a63fdaf001a1dd5fb92e1 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -374,6 +374,9 @@ private: class Entity { public: Entity(); + inline Entity(const Entity &original) : Entity(CreateProtoEntity()) { + this->wrapped->CopyFrom(*original.wrapped); + }; explicit Entity(IdResponse *idResponse); explicit inline Entity(ProtoEntity *wrapped) : wrapped(wrapped) { errors.wrapped = this->wrapped->mutable_errors(); @@ -383,9 +386,12 @@ public: parents.wrapped = this->wrapped->mutable_parents(); }; - [[nodiscard]] inline auto GetId() const -> const std::string & { + [[nodiscard]] inline auto GetId() const noexcept -> const std::string & { return wrapped->id(); }; + [[nodiscard]] inline auto HasId() const noexcept -> bool { + return !wrapped->id().empty(); + } [[nodiscard]] inline auto GetVersionId() const -> const std::string & { return wrapped->version().id(); }; @@ -435,9 +441,7 @@ public: } auto SetRole(const std::string &role) -> void; - auto SetId(const std::string &id) -> void; auto SetName(const std::string &name) -> void; - auto SetVersionId(const std::string &id) -> void; auto SetValue(const std::string &value) -> void; auto SetUnit(const std::string &unit) -> void; @@ -451,8 +455,10 @@ public: */ auto CopyTo(ProtoEntity *target) -> void; -private: +protected: static auto CreateProtoEntity() -> ProtoEntity *; + auto SetId(const std::string &id) -> void; + auto SetVersionId(const std::string &id) -> void; ProtoEntity *wrapped; Properties properties; Parents parents; diff --git a/include/caosdb/exceptions.h b/include/caosdb/exceptions.h index d60f7a162b6a36d803a37c1e40dfc43d21a3f172..9ac46d2fd2e0a24fab08c49606b21033893c14e7 100644 --- a/include/caosdb/exceptions.h +++ b/include/caosdb/exceptions.h @@ -73,10 +73,22 @@ public: : Exception(StatusCode::GENERIC_TRANSACTION_ERROR, what_arg) {} }; -class TransactionStatusError : public TransactionError { +/** + * @brief The transaction is in the wrong state for your action. + */ +class TransactionStatusError : public Exception { public: explicit TransactionStatusError(const std::string &what_arg) - : TransactionError(StatusCode::TRANSACTION_STATUS_ERROR, what_arg) {} + : Exception(StatusCode::TRANSACTION_STATUS_ERROR, what_arg) {} +}; + +/** + * @brief The transaction has a wrong type for your action. + */ +class TransactionTypeError : public Exception { +public: + explicit TransactionTypeError(const std::string &what_arg) + : Exception(StatusCode::TRANSACTION_TYPE_ERROR, what_arg) {} }; /** diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h index 26f4abd4ea4c8c256f66e23268dcac744e4db3c0..a6fcda905284ae9dfa9b7e46c6be8da36bb32272 100644 --- a/include/caosdb/status_code.h +++ b/include/caosdb/status_code.h @@ -36,6 +36,8 @@ namespace caosdb { * transaction). */ enum StatusCode { + READY = -4, + GO_ON = -3, INITIAL = -2, EXECUTING = -1, SUCCESS = 0, @@ -49,6 +51,7 @@ enum StatusCode { TRANSACTION_STATUS_ERROR = 25, TRANSACTION_TYPE_ERROR = 26, UNSUPPORTED_FEATURE = 27, + ORIGINAL_ENTITY_MISSING_ID = 28, }; auto get_status_description(int code) -> const std::string &; diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index 9d957f08e3f2db4d7e4fb0e52d8351629c2c0f94..48762db4396e8996442ef5be287e3965836ecd4e 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -31,32 +31,28 @@ #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 <stdexcept> #include <iterator> // IWYU pragma: no_include <ext/alloc_traits.h> #include <memory> // for shared_ptr, unique_ptr +#include <stdexcept> #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())) { \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ case NONE: \ this->transaction_type = TransactionType::READ_ONLY; \ + break; \ case READ_ONLY: \ + break; \ case MIXED_READ_AND_WRITE: \ break; \ default: \ @@ -66,12 +62,26 @@ namespace caosdb::transaction { "wrong TransactionType.") \ } +/** + * 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_QUERY \ + ASSERT_CAN_ADD_RETRIEVAL \ + if (this->has_query) { \ + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( \ + logger_name, StatusCode::UNSUPPORTED_FEATURE, \ + "Currently the number of queries which can be processed in a single " \ + "transaction is limitted to one."); \ + } + /** * 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())) { \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -93,7 +103,8 @@ namespace caosdb::transaction { * sub-request to a transaction. */ #define ASSERT_CAN_ADD_INSERTION \ - if (!IsStatus(TransactionStatus::INITIAL())) { \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -110,6 +121,42 @@ namespace caosdb::transaction { "wrong TransactionType.") \ } +/** + * Do all necessary checks and assure that another update can be added as a + * sub-request to a transaction. + */ +#define ASSERT_CAN_ADD_UPDATE \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ + 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 update to this transaction because it has the " \ + "wrong TransactionType.") \ + } \ + if (!entity->HasId()) { \ + CAOSDB_LOG_ERROR_AND_RETURN_STATUS( \ + logger_name, StatusCode::ORIGINAL_ENTITY_MISSING_ID, \ + "You cannot update this entity without any id. Probably you did not " \ + "retrieve it first? Entity updates should always start with the " \ + "retrieval of the existing entity which may then be changed.") \ + } + +/** + * @brief Creation and execution of transactions. + * @author Timm Fitschen + * @date 2021-08-05 + */ +namespace caosdb::transaction { using caosdb::entity::Entity; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using caosdb::entity::v1alpha1::EntityTransactionService; @@ -128,10 +175,28 @@ static const std::string logger_name = "caosdb::transaction"; * Abstract base class for the results of a Transaction. */ class ResultSet { + class iterator; + public: virtual ~ResultSet() = default; [[nodiscard]] virtual auto Size() const noexcept -> int = 0; [[nodiscard]] virtual auto At(const int index) const -> const Entity & = 0; + auto begin() const -> iterator; + auto end() const -> iterator; + +private: + class iterator : public std::iterator<std::output_iterator_tag, Entity> { + public: + explicit iterator(const ResultSet *result_set, int index = 0); + auto operator*() const -> const Entity &; + auto operator++() -> iterator &; + auto operator++(int) -> iterator; + auto operator!=(const iterator &rhs) const -> bool; + + private: + int current_index = 0; + const ResultSet *result_set; + }; }; /** @@ -236,6 +301,16 @@ public: */ auto InsertEntity(Entity *entity) noexcept -> StatusCode; + /** + * Add the entity to this transaction for an update. + * + * The update is being processed when the Execute() or + * ExecuteAsynchronously() methods of this transaction are called. + * + * Changing the entity afterwards results in undefined behavior. + */ + auto UpdateEntity(Entity *entity) noexcept -> StatusCode; + /** * Add an entity id to this transaction for deletion. * @@ -300,6 +375,7 @@ public: } private: + bool has_query = false; TransactionType transaction_type = TransactionType::NONE; mutable std::unique_ptr<ResultSet> result_set; mutable TransactionStatus status = TransactionStatus::INITIAL(); @@ -322,7 +398,8 @@ inline auto Transaction::RetrieveById(InputIterator begin, next = std::next(next); } - return StatusCode::INITIAL; + this->status = TransactionStatus::GO_ON(); + return this->status.GetCode(); } } // namespace caosdb::transaction diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h index 56c6b005112fd15c76f3c2741803a58837baed9e..6f2f5cefd7b36f8c986726b4955f53381339f0a2 100644 --- a/include/caosdb/transaction_status.h +++ b/include/caosdb/transaction_status.h @@ -33,6 +33,18 @@ using caosdb::exceptions::AuthenticationError; using caosdb::exceptions::ConnectionError; using caosdb::exceptions::Exception; using caosdb::exceptions::TransactionError; +using caosdb::exceptions::TransactionStatusError; +using caosdb::exceptions::TransactionTypeError; + +/** + * Define static factory method in the TransactionStatus class. + */ +#define CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(_StatusName, _StatusCode) \ + inline static auto _StatusName()->const TransactionStatus & { \ + static const TransactionStatus instance( \ + _StatusCode, caosdb::get_status_description(_StatusCode)); \ + return instance; \ + } /** * TransactionStatus indicates the current status of a transaction and, when it @@ -44,41 +56,62 @@ using caosdb::exceptions::TransactionError; */ class TransactionStatus { public: - // REFACTORING NEEDED: When you touch this code again consider writing a - // macro, because this is a lot of redundant code here... - inline static auto INITIAL() -> const TransactionStatus & { - static const TransactionStatus initial( - StatusCode::INITIAL, caosdb::get_status_description(StatusCode::INITIAL)); - return initial; - } - inline static auto EXECUTING() -> const TransactionStatus & { - static const TransactionStatus executing( - StatusCode::EXECUTING, - caosdb::get_status_description(StatusCode::EXECUTING)); - return executing; - } - inline static auto SUCCESS() -> const TransactionStatus & { - static const TransactionStatus success( - StatusCode::SUCCESS, caosdb::get_status_description(StatusCode::SUCCESS)); - return success; - } - inline static auto RPC_ERROR(const std::string &details) - -> const TransactionStatus { - // We use the GENERIC_RPC_ERROR here because we might want to add further - // RPC_ERROR states with different error codes (which stem from GRPC) here - // in the future. - return TransactionStatus( - StatusCode::GENERIC_RPC_ERROR, - caosdb::get_status_description(StatusCode::GENERIC_RPC_ERROR) + - " Original error: " + details); - } - inline static auto CONNECTION_ERROR() -> const TransactionStatus & { - static const TransactionStatus connection_error( - StatusCode::CONNECTION_ERROR, - caosdb::get_status_description(StatusCode::CONNECTION_ERROR)); - - return connection_error; - } + /** + * Factory for an INITIAL status. + * + * This status means that the transaction has not been executed yet and the + * transaction does not contain any sub-transactions yet. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(INITIAL, StatusCode::INITIAL) + /** + * Factory for a GO_ON status. + * + * This status means that the transaction has not been executed yet but it + * already contains sub-transaction and more subtransactions may be added. + * + * However, it also can be executed right now. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(GO_ON, StatusCode::GO_ON) + /** + * Factory for a READY status. + * + * This status means that the transaction has not been executed yet but it is + * ready to be executed and it is not possible anymore to add further + * sub-transactions. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(READY, StatusCode::READY) + /** + * Factory for an EXECUTING status. + * + * This status means that the transaction is currently being executed. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(EXECUTING, StatusCode::EXECUTING) + /** + * Factory for a SUCCESS status. + * + * This status means that the transaction has been executed successfully. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(SUCCESS, StatusCode::SUCCESS) + /** + * Factory for a CONNECTION_ERROR status. + * + * This status means that the connection could not be established. This is + * possibly due to misconfiguration of the client, errors in the network or + * because the server is down. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(CONNECTION_ERROR, + StatusCode::CONNECTION_ERROR) + /** + * Factory for an AUTHENTICATION_ERROR status. + * + * This status means that the RPC layer reported an authentication error. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(AUTHENTICATION_ERROR, + StatusCode::AUTHENTICATION_ERROR) + /** + * Another factory for an TRANSACTION_ERROR Status with a detailed + * description. + */ inline static auto AUTHENTICATION_ERROR(const std::string &details) -> const TransactionStatus { return TransactionStatus( @@ -86,19 +119,18 @@ public: caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR) + " Original error: " + details); } - inline static auto AUTHENTICATION_ERROR() -> const TransactionStatus & { - static const TransactionStatus authentication_error( - StatusCode::AUTHENTICATION_ERROR, - caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR)); - - return authentication_error; - } - inline static auto TRANSACTION_ERROR() -> const TransactionStatus & { - static const TransactionStatus transaction_error( - StatusCode::GENERIC_TRANSACTION_ERROR, - caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR)); - return transaction_error; - } + /** + * Factory for a TRANSACTION_ERROR status. + * + * This status means that the transaction failed due to errors thrown by the + * server. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY( + TRANSACTION_ERROR, StatusCode::GENERIC_TRANSACTION_ERROR) + /** + * Another factory for a TRANSACTION_ERROR status with a detailed + * description. + */ inline static auto TRANSACTION_ERROR(const std::string &details) -> const TransactionStatus { return TransactionStatus( @@ -106,23 +138,53 @@ public: caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR) + " Original error: " + details); } + /** + * Factory for a RPC_ERROR with a detailed description. + * + * This status is used for any error on the RPC layer. + */ + inline static auto RPC_ERROR(const std::string &details) + -> const TransactionStatus { + // We use the GENERIC_RPC_ERROR here because we might want to add further + // RPC_ERROR states with different error codes (which stem from GRPC) here + // in the future. + return TransactionStatus( + StatusCode::GENERIC_RPC_ERROR, + caosdb::get_status_description(StatusCode::GENERIC_RPC_ERROR) + + " Original error: " + details); + } inline auto ThrowExceptionIfError() const -> void { - if (!IsError()) { + TransactionStatus::ThrowExceptionIfError(this->code, this->description); + } + + inline static auto ThrowExceptionIfError(StatusCode code, + const std::string &description) + -> void { + if (!IsError(code)) { return; } - switch (this->code) { + switch (code) { case StatusCode::CONNECTION_ERROR: - throw ConnectionError(this->description); + throw ConnectionError(description); case StatusCode::AUTHENTICATION_ERROR: - throw AuthenticationError(this->description); + throw AuthenticationError(description); case StatusCode::GENERIC_TRANSACTION_ERROR: - throw TransactionError(this->description); + throw TransactionError(description); + case StatusCode::TRANSACTION_STATUS_ERROR: + throw TransactionStatusError(description); + case StatusCode::TRANSACTION_TYPE_ERROR: + throw TransactionTypeError(description); default: - throw Exception(StatusCode::GENERIC_ERROR, this->description); + throw Exception(StatusCode::GENERIC_ERROR, description); } } + /** + * Return true if the given StatusCode represents an erroneous state. + */ + inline static auto IsError(StatusCode code) -> bool { return code > 0; }; + /** * Return true if this TransactionStatus represents a terminated state. */ @@ -131,7 +193,9 @@ public: /** * Return true if this TransactionStatus represents an erroneous state. */ - inline auto IsError() const -> bool { return this->code > 0; }; + inline auto IsError() const -> bool { + return TransactionStatus::IsError(this->code); + }; /** * Return a description of the erroneous state. diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index d0b9025b7b1a5b48741e8687db9c21136864c683..f946175f3b33397e9398188500cf0ef51cc16d26 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -42,8 +42,14 @@ namespace caosdb { auto get_status_description(int code) -> const std::string & { static const std::string MISSING_DESCRIPTION = "MISSING DESCRIPTION"; static const std::map<int, std::string> descriptions = { - {StatusCode::INITIAL, "The transaction has not been executed yet and " - "clients can stil change it."}, + {StatusCode::INITIAL, + "The transaction has just been intialized. It has not been executed yet " + "and clients can still change it and add sub-transactions."}, + {StatusCode::GO_ON, + "The transaction has a transaction_type yet and clients may add matching " + "sub-transaction or execute it right-away."}, + {StatusCode::READY, + "The transaction is ready for execution and cannot be changed anymore."}, {StatusCode::EXECUTING, "The transaction is currently being executed."}, {StatusCode::SUCCESS, "The action was successful"}, {StatusCode::CONNECTION_ERROR, @@ -68,6 +74,11 @@ auto get_status_description(int code) -> const std::string & { {StatusCode::TRANSACTION_TYPE_ERROR, "The Transaction has a transaction type which does not allow the " "attempted action."}, + {StatusCode::ORIGINAL_ENTITY_MISSING_ID, + "The attempt to update this entity failed because this entity does not " + "have " + "an id. This is the case when you did not retrieve it before applying any " + "changes and instantiated the Entity class explicitely."}, {StatusCode::UNSUPPORTED_FEATURE, "This feature is not available in the this client implementation."}}; try { @@ -90,6 +101,36 @@ using grpc::ClientAsyncResponseReader; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using grpc::CompletionQueue; +ResultSet::iterator::iterator(const ResultSet *result_set_param, int index) + : current_index(index), result_set(result_set_param) {} + +auto ResultSet::iterator::operator*() const -> const Entity & { + return this->result_set->At(current_index); +} + +auto ResultSet::iterator::operator++() -> ResultSet::iterator & { + current_index++; + return *this; +} + +auto ResultSet::iterator::operator++(int) -> ResultSet::iterator { + iterator tmp(*this); + operator++(); + return tmp; +} + +auto ResultSet::iterator::operator!=(const iterator &rhs) const -> bool { + return this->current_index != rhs.current_index; +} + +auto ResultSet::begin() const -> ResultSet::iterator { + return ResultSet::iterator(this, 0); +} + +auto ResultSet::end() const -> ResultSet::iterator { + return ResultSet::iterator(this, Size()); +} + MultiResultSet::MultiResultSet(MultiTransactionResponse *response) { auto *responses = response->mutable_responses(); for (auto sub_response : *responses) { @@ -133,16 +174,21 @@ auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode { auto *sub_request = this->request->add_requests(); sub_request->mutable_retrieve_request()->set_id(id); - return StatusCode::INITIAL; + this->status = TransactionStatus::GO_ON(); + return this->status.GetCode(); } auto Transaction::Query(const std::string &query) noexcept -> StatusCode { - ASSERT_CAN_ADD_RETRIEVAL + ASSERT_CAN_ADD_QUERY + // currently, the server can only process one query at a time (but mixed with + // other retrievals). + this->has_query = true; auto *sub_request = this->request->add_requests(); sub_request->mutable_retrieve_request()->mutable_query()->set_query(query); - return StatusCode::INITIAL; + this->status = TransactionStatus::GO_ON(); + return this->status.GetCode(); } auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode { @@ -151,7 +197,8 @@ auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode { auto *sub_request = this->request->add_requests(); sub_request->mutable_delete_request()->set_id(id); - return StatusCode::INITIAL; + this->status = TransactionStatus::READY(); + return this->status.GetCode(); } auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { @@ -162,18 +209,33 @@ auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { // copy the original entity for the transaction entity->CopyTo(proto_entity); - return StatusCode::INITIAL; + this->status = TransactionStatus::READY(); + return this->status.GetCode(); +} + +auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode { + ASSERT_CAN_ADD_UPDATE + + auto *sub_request = this->request->add_requests(); + auto *proto_entity = sub_request->mutable_update_request(); + + entity->CopyTo(proto_entity); + this->status = TransactionStatus::READY(); + return this->status.GetCode(); } auto Transaction::Execute() -> TransactionStatus { - ExecuteAsynchronously(); + auto status_code = ExecuteAsynchronously(); + TransactionStatus::ThrowExceptionIfError( + status_code, caosdb::get_status_description(status_code)); auto status = WaitForIt(); status.ThrowExceptionIfError(); return status; } auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { - if (!IsStatus(TransactionStatus::INITIAL())) { + if (!IsStatus(TransactionStatus::READY()) && + !IsStatus(TransactionStatus::GO_ON())) { return StatusCode::TRANSACTION_STATUS_ERROR; } switch (this->transaction_type) { @@ -245,6 +307,14 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus { } this->result_set = std::make_unique<UniqueResult>(entity); } break; + case WrappedResponseCase::kUpdateResponse: { + auto *updatedIdResponse = responses->mutable_update_response(); + if (!updatedIdResponse->entity_errors().empty()) { + this->status = TransactionStatus::TRANSACTION_ERROR( + "The request returned with errors."); + } + this->result_set = std::make_unique<UniqueResult>(updatedIdResponse); + } break; case WrappedResponseCase::kInsertResponse: { auto *insertedIdResponse = responses->mutable_insert_response(); if (!insertedIdResponse->entity_errors().empty()) { diff --git a/test/test_entity.cpp b/test/test_entity.cpp index 6a24ab7cb11ae59e6a0cbffed499286c773a399f..3639f8c574ae90e70f0ef9abeb77a4403fadc06b 100644 --- a/test/test_entity.cpp +++ b/test/test_entity.cpp @@ -30,9 +30,7 @@ #include "gtest/gtest-message.h" // for Message #include "gtest/gtest-test-part.h" // for TestPartResult, Sui... #include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ -#include <iostream> // for endl, basic_ostream #include <memory> // for allocator, shared_ptr -#include <string> // for operator<< namespace caosdb::entity { using caosdb::entity::v1alpha1::IdResponse; @@ -105,7 +103,7 @@ TEST(test_entity, test_append_property) { TEST(test_entity, test_copy_to) { auto entity = Entity(); - entity.SetId("original_id"); + entity.SetRole("original_role"); entity.SetName("orignial_name"); auto parent = Parent(); @@ -125,7 +123,7 @@ TEST(test_entity, test_copy_to) { entity.CopyTo(proto_copy); auto copied = Entity(proto_copy); - EXPECT_EQ(entity.GetId(), copied.GetId()); + EXPECT_EQ(entity.GetRole(), copied.GetRole()); EXPECT_EQ(entity.GetName(), copied.GetName()); EXPECT_EQ(copied.GetParents().At(0).GetId(), parent.GetId()); EXPECT_EQ(copied.GetParents().At(0).GetName(), parent.GetName()); @@ -138,16 +136,16 @@ TEST(test_entity, test_insert_entity) { std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); auto entity = Entity(); - entity.SetId("entity_id"); - entity.SetVersionId("version_id"); + entity.SetRole("entity_role"); + entity.SetName("entity_name"); - EXPECT_EQ(entity.GetId(), "entity_id"); - EXPECT_EQ(entity.GetVersionId(), "version_id"); + EXPECT_EQ(entity.GetRole(), "entity_role"); + EXPECT_EQ(entity.GetName(), "entity_name"); transaction.InsertEntity(&entity); - EXPECT_EQ(entity.GetId(), "entity_id"); - EXPECT_EQ(entity.GetVersionId(), "version_id"); + EXPECT_EQ(entity.GetRole(), "entity_role"); + EXPECT_EQ(entity.GetName(), "entity_name"); } TEST(test_entity, test_insert_with_role) { @@ -155,8 +153,6 @@ TEST(test_entity, test_insert_with_role) { std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); auto entity = Entity(); - entity.SetId("entity_id"); - entity.SetVersionId("version_id"); entity.SetRole("Property"); entity.SetDatatype("DOUBLE"); entity.SetName("Length"); @@ -165,8 +161,6 @@ TEST(test_entity, test_insert_with_role) { transaction.InsertEntity(&entity); - EXPECT_EQ(entity.GetId(), "entity_id"); - EXPECT_EQ(entity.GetVersionId(), "version_id"); EXPECT_EQ(entity.GetRole(), "Property"); EXPECT_EQ(entity.GetDatatype(), "DOUBLE"); EXPECT_EQ(entity.GetName(), "Length"); @@ -179,7 +173,7 @@ TEST(test_entity, test_insert_with_parent) { std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); auto entity = Entity(); - entity.SetId("entity_id"); + entity.SetName("entity_name"); auto parent = Parent(); parent.SetId("parent_id"); @@ -191,8 +185,7 @@ TEST(test_entity, test_insert_with_parent) { transaction.InsertEntity(&entity); - std::cout << entity.ToString() << std::endl; - EXPECT_EQ(entity.GetId(), "entity_id"); + EXPECT_EQ(entity.GetName(), "entity_name"); EXPECT_EQ(entity.GetParents().Size(), 1); auto inserted_parent = entity.GetParents().At(0); EXPECT_EQ(inserted_parent.GetId(), parent.GetId()); @@ -204,7 +197,7 @@ TEST(test_entity, test_insert_with_property) { std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); auto entity = Entity(); - entity.SetId("entity_id"); + entity.SetName("entity_name"); auto prop = Property(); prop.SetName("prop_name"); @@ -239,7 +232,6 @@ TEST(test_entity, test_from_id_response) { Entity entity(&idResponse); - std::cout << entity.ToString() << std::endl; EXPECT_EQ(entity.GetId(), "entity_id"); EXPECT_TRUE(entity.HasErrors()); EXPECT_EQ(entity.GetErrors().Size(), 1); @@ -268,5 +260,4 @@ TEST(test_entity, test_from_id_response) { EXPECT_EQ(other_ent.GetInfos().At(0).GetDescription(), "info_desc"); EXPECT_EQ(other_ent.GetInfos().At(0).GetCode(), MessageCode::UNSPECIFIED); } - } // namespace caosdb::entity diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index 118208595ea5d100dacb8302be555772c8fcd80e..557ded7b87fe9947391bb47f6c37b49ee1824cfb 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -29,10 +29,9 @@ #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 <iostream> -#include <memory> // for allocator, unique_ptr -#include <string> // for string, basic_string -#include <vector> // for vector +#include <memory> // for allocator, unique_ptr +#include <string> // for string, basic_string +#include <vector> // for vector namespace caosdb::transaction { using caosdb::configuration::InsecureConnectionConfiguration; @@ -47,7 +46,7 @@ TEST(test_transaction, create_transaction) { Connection connection(configuration); auto transaction = connection.CreateTransaction(); - transaction->RetrieveById("100"); + ASSERT_EQ(StatusCode::GO_ON, transaction->RetrieveById("100")); EXPECT_THROW_MESSAGE( transaction->Execute(), ConnectionError, "The attempt to execute this transaction was not successful because the " @@ -99,6 +98,33 @@ TEST(test_transaction, test_multi_result_set_empty) { EXPECT_EQ(rs.Size(), 0); } +TEST(test_transaction, test_multi_result_iterator) { + MultiTransactionResponse response; + + response.add_responses() + ->mutable_retrieve_response() + ->mutable_entity() + ->set_id("100"); + + MultiResultSet rs(&response); + EXPECT_EQ(rs.Size(), 1); + + for (const Entity &entity : rs) { + EXPECT_EQ(entity.GetId(), "100"); + } +} + +TEST(test_transaction, test_unique_result_iterator) { + caosdb::entity::v1alpha1::Entity response; + response.set_id("100"); + + UniqueResult rs(&response); + EXPECT_EQ(rs.Size(), 1); + for (const Entity &entity : rs) { + EXPECT_EQ(entity.GetId(), "100"); + } +} + TEST(test_transaction, test_multi_result_set_one) { MultiTransactionResponse response; response.add_responses() @@ -129,7 +155,19 @@ TEST(test_transaction, test_multi_result_set_three) { MultiResultSet rs(&response); EXPECT_EQ(rs.Size(), 3); EXPECT_TRUE(rs.At(1).HasErrors()); - std::cout << rs.At(1).ToString(); +} + +TEST(test_transaction, test_update_entity) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + + caosdb::entity::Entity update_entity; + update_entity.SetRole("New role"); + auto error = transaction->UpdateEntity(&update_entity); + + EXPECT_EQ(error, StatusCode::ORIGINAL_ENTITY_MISSING_ID); } } // namespace caosdb::transaction