diff --git a/CMakeLists.txt b/CMakeLists.txt index 488878f45d5b6206dfe720256b7a1fef08df0010..f9c6718af235dad3d558d571c0e39dc76984ee69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/include/caosdb/entity.h b/include/caosdb/entity.h index d925f62abb5d732a3955b433f69c30b1c6a13a11..eb93b36875bfe7778345152bcbb53777d9509759 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -466,12 +466,10 @@ public: return this->file_transmission_id != nullptr; } -protected: +private: static auto CreateProtoEntity() -> ProtoEntity *; auto SetId(const std::string &id) -> void; auto SetVersionId(const std::string &id) -> void; - -private: FileTransmissionId *file_transmission_id = nullptr; ProtoEntity *wrapped; Properties properties; 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 6e5f7587ea0e114e4eab61c4bd3960f80cdb514d..a6fcda905284ae9dfa9b7e46c6be8da36bb32272 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 { READY = -4, GO_ON = -3, diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index a1295904cc68488568a0f6753bdfded5d9ae5db5..90044e1d8bf8cf99604184bff83fffb9c9b79b27 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -18,12 +18,8 @@ * 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 "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... @@ -35,25 +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 -/* +/** * 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: \ @@ -63,12 +62,26 @@ "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) { \ @@ -85,12 +98,13 @@ "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())) { \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -107,12 +121,13 @@ "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())) { \ + if (!IsStatus(TransactionStatus::INITIAL()) && \ + !IsStatus(TransactionStatus::GO_ON())) { \ return StatusCode::TRANSACTION_STATUS_ERROR; \ } \ switch (this->transaction_type) { \ @@ -136,6 +151,11 @@ "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; @@ -154,43 +174,48 @@ 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 { + 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; + }; }; +/** + * 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 inline MultiResultSet(MultiTransactionResponse *response) { - auto responses = response->mutable_responses(); - Entity *entity = nullptr; - for (auto sub_response : *responses) { - switch (sub_response.wrapped_response_case()) { - case WrappedResponseCase::kRetrieveResponse: - entity = new Entity( - sub_response.mutable_retrieve_response()->release_entity()); - break; - case WrappedResponseCase::kInsertResponse: - - entity = new Entity(sub_response.release_insert_response()); - break; - case WrappedResponseCase::kDeleteResponse: - entity = new Entity(sub_response.release_delete_response()); - break; - default: - // TODO(tf) Updates - break; - } - if (entity) { - this->entities.push_back(std::unique_ptr<Entity>(entity)); - } - } - } + explicit MultiResultSet(MultiTransactionResponse *response); [[nodiscard]] inline auto Size() const noexcept -> int override { return this->entities.size(); } @@ -201,6 +226,12 @@ public: 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() = default; @@ -222,6 +253,17 @@ private: std::unique_ptr<Entity> entity; }; +// TODO(fspreck) One possibility for count-queries: Transaction gets a +// GetQueryCount function that returns exactly this. In case of a +// single FIND Query this will be equivalent to the Size of the +// ResultSet, but it may be different in case of Query + RetrieveById, +// and in case of COUNT queries which have an empty ResultSet. We have +// to think about how to extend this to transactions with more than +// one query in the future. My suggestion is to treat a transaction +// with more than one query as the union of the query results in which +// case the query count would return the cumulative number of +// results. This would even allow the combination of FIND, FIND +// unique, and COUNT, albeit a little strangely. /** * @brief Create a transaction via `CaosDBConnection.createTransaction()` */ @@ -235,14 +277,21 @@ public: -> void; auto RegisterDownloadFile(const RegisterFileDownloadRequest &request, RegisterFileDownloadResponse *response) -> void; + /** + * 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, - READ_ONLY, - INSERT, - UPDATE, - DELETE, - MIXED_WRITE, - MIXED_READ_AND_WRITE + 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> entity_service, std::shared_ptr<FileTransmissionService::Stub> file_service); @@ -353,6 +402,7 @@ public: } private: + bool has_query = false; TransactionType transaction_type = TransactionType::NONE; mutable std::unique_ptr<ResultSet> result_set; mutable TransactionStatus status = TransactionStatus::INITIAL(); @@ -376,7 +426,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 8e54c8ba79c944f6b157a577cc9db64be01e1473..6f2f5cefd7b36f8c986726b4955f53381339f0a2 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 @@ -42,47 +33,85 @@ using caosdb::exceptions::AuthenticationError; using caosdb::exceptions::ConnectionError; using caosdb::exceptions::Exception; using caosdb::exceptions::TransactionError; +using caosdb::exceptions::TransactionStatusError; +using caosdb::exceptions::TransactionTypeError; /** - * Status of a Request or Transaction. + * Define static factory method in the TransactionStatus class. */ -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; +#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; \ } - 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; - } +/** + * 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: + /** + * 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( @@ -90,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( @@ -110,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. */ @@ -135,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 1c889fef4d51486e244f20bd65996eedb465a951..c32d0090392e9b8c44cc8d1e0886f69747337066 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -75,7 +75,8 @@ auto get_status_description(int code) -> const std::string & { "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 have " + "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, @@ -109,6 +110,59 @@ 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) { + 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; @@ -131,16 +185,23 @@ 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(); } +// TODO(fspreck) What about count queries (mixed with retrievals), +// what about unique queries? 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 { @@ -149,7 +210,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 { @@ -164,7 +226,8 @@ auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { sub_request->mutable_insert_request()->mutable_upload_id()->CopyFrom( entity->GetFileTransmissionId()); } - return StatusCode::INITIAL; + this->status = TransactionStatus::READY(); + return this->status.GetCode(); } auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode { @@ -173,20 +236,23 @@ auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode { auto *sub_request = this->request->add_requests(); auto *proto_entity = sub_request->mutable_update_request()->mutable_entity(); - // copy the original entity for the transaction entity->CopyTo(proto_entity); - return StatusCode::INITIAL; + 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) { @@ -283,7 +349,7 @@ 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 { diff --git a/test/test_entity.cpp b/test/test_entity.cpp index ede68149ea6bf81c0b2517d283300285efdef260..2794e9bf8979f7e74ad7810b70d0eb49a2ea0e7c 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; @@ -190,7 +188,6 @@ TEST(test_entity, test_insert_with_parent) { transaction.InsertEntity(&entity); - std::cout << entity.ToString() << std::endl; EXPECT_EQ(entity.GetName(), "entity_name"); EXPECT_EQ(entity.GetParents().Size(), 1); auto inserted_parent = entity.GetParents().At(0); @@ -255,18 +252,14 @@ TEST(test_entity, test_from_id_response) { info->set_description("info_desc"); info->set_code(MessageCode::UNSPECIFIED); - std::cout << entity.ToString() << std::endl; Entity other_ent(&idr_warnings_and_infos); - std::cout << entity.ToString() << std::endl; EXPECT_EQ(other_ent.GetId(), "other_entity_id"); - std::cout << entity.ToString() << std::endl; EXPECT_EQ(other_ent.GetWarnings().Size(), 1); EXPECT_TRUE(other_ent.HasWarnings()); EXPECT_EQ(other_ent.GetWarnings().At(0).GetDescription(), "warning_desc"); EXPECT_EQ(other_ent.GetWarnings().At(0).GetCode(), MessageCode::ENTITY_HAS_NO_PROPERTIES); - std::cout << entity.ToString() << std::endl; EXPECT_EQ(other_ent.GetInfos().Size(), 1); EXPECT_EQ(other_ent.GetInfos().At(0).GetDescription(), "info_desc"); EXPECT_EQ(other_ent.GetInfos().At(0).GetCode(), MessageCode::UNSPECIFIED); diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index b1a51ab4aa0a256f902f5ebe833cab21c83e38e4..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,6 @@ 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) {