From 307e21b4457eee2be05d61acae555006e140daf9 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Thu, 29 Jul 2021 01:49:48 +0200
Subject: [PATCH] WIP: insert delete

---
 .gitlab-ci.yml                   |   3 +
 include/CMakeLists.txt           |   1 +
 include/caosdb/entity.h          |  49 +++++++-------
 include/caosdb/exceptions.h      |  10 +++
 include/caosdb/protobuf_helper.h |  34 ++++++++++
 include/caosdb/status_code.h     |   1 +
 include/caosdb/transaction.h     |  54 ++++++++++++++--
 proto                            |   2 +-
 src/CMakeLists.txt               |   1 +
 src/caosdb/entity.cpp            |  68 +++++++++++++++----
 src/caosdb/protobuf_helper.cpp   |  33 ++++++++++
 src/caosdb/transaction.cpp       | 108 ++++++++++++++++++++++++++-----
 test/CMakeLists.txt              |   1 +
 test/test_entity.cpp             |  68 +++++++++++++++----
 test/test_protobuf.cpp           |  84 ++++++++++++++++++++++++
 test/test_transaction.cpp        |  28 ++++++--
 16 files changed, 467 insertions(+), 78 deletions(-)
 create mode 100644 include/caosdb/protobuf_helper.h
 create mode 100644 src/caosdb/protobuf_helper.cpp
 create mode 100644 test/test_protobuf.cpp

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a79ad19..8f77eea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -83,6 +83,9 @@ test:
     - mkdir build
     - cd build
     - conan install .. -s "compiler.libcxx=libstdc++11"
+    - FILE_TO_BE_PATCHED="$(grep "std::unique_ptr<ContextAllocator> context_allocator) {}" -l -r $(conan config home))"
+    - echo "FILE_TO_BE_PATCHED=$FILE_TO_BE_PATCHED"
+    - sed -e "s|std::unique_ptr<ContextAllocator> context_allocator) {}|std::unique_ptr<ContextAllocator> /*context_allocator*/) {}|" -i $FILE_TO_BE_PATCHED
     - cmake -DCMAKE_BUILD_TYPE=Debug ..
     - cmake --build .
     - cmake --build . --target unit_test_coverage
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 126c54e..542ad97 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -31,6 +31,7 @@ set(libcaosdb_INCL
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/log_level.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/message_code.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/status_code.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_status.h
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index 9141e32..2819244 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -28,18 +28,16 @@
 #ifndef CAOSDB_ENTITY_H
 #define CAOSDB_ENTITY_H
 
-#include "caosdb/message_code.h"
-#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RepeatedPtrField
-#include "google/protobuf/util/json_util.h"
-#include <memory> // for unique_ptr
-#include <string> // for string
+#include <string>                           // for string
+#include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Message
+#include "caosdb/message_code.h"            // for get_message_code, Messag...
+#include "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso...
 
 namespace caosdb::entity {
+using caosdb::entity::v1alpha1::IdResponse;
 using ProtoParent = caosdb::entity::v1alpha1::Parent;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 
-class Parents;
-
 /**
  * Parent of an Entity.
  */
@@ -47,20 +45,26 @@ class Parent {
 public:
   explicit inline Parent(caosdb::entity::v1alpha1::Parent *wrapped)
     : wrapped(wrapped){};
-  inline Parent() : wrapped(new ProtoParent){};
+  Parent();
   [[nodiscard]] auto GetId() const -> const std::string &;
   [[nodiscard]] auto GetName() const -> const std::string &;
   [[nodiscard]] auto GetDescription() const -> const std::string &;
   auto SetId(const std::string &id) -> void;
   auto SetName(const std::string &name) -> void;
 
+  inline auto ToString() const -> const std::string {
+    google::protobuf::util::JsonOptions options;
+    std::string out;
+    google::protobuf::util::MessageToJsonString(*(this->wrapped), &out,
+                                                options);
+    return out;
+  }
+
   friend class Parents;
 
 private:
-  auto AppendTo(
-    ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent>
-      *parents) const -> void;
-  std::shared_ptr<caosdb::entity::v1alpha1::Parent> wrapped;
+  static auto CreateProtoParent() -> ProtoParent *;
+  mutable caosdb::entity::v1alpha1::Parent *wrapped;
 };
 
 /**
@@ -175,11 +179,8 @@ private:
  */
 class Entity {
 public:
-  inline Entity() : wrapped(new ProtoEntity) {
-    properties.wrapped = this->wrapped->mutable_properties();
-    parents.wrapped = this->wrapped->mutable_parents();
-    errors.wrapped = this->wrapped->mutable_errors();
-  };
+  Entity();
+  explicit Entity(IdResponse *idResponse);
   explicit inline Entity(ProtoEntity *wrapped) : wrapped(wrapped) {
     errors.wrapped = this->wrapped->mutable_errors();
     properties.wrapped = this->wrapped->mutable_properties();
@@ -189,7 +190,7 @@ public:
   [[nodiscard]] inline auto GetId() const -> const std::string & {
     return wrapped->id();
   };
-  [[nodiscard]] inline auto GetVersion() const -> const std::string & {
+  [[nodiscard]] inline auto GetVersionId() const -> const std::string & {
     return wrapped->version().id();
   };
 
@@ -203,10 +204,10 @@ public:
     return wrapped->description();
   };
 
-  [[nodiscard]] auto GetDatatype() const -> const std::string & {
+  [[nodiscard]] inline auto GetDatatype() const -> const std::string & {
     return wrapped->datatype();
   };
-  [[nodiscard]] auto GetUnit() const -> const std::string & {
+  [[nodiscard]] inline auto GetUnit() const -> const std::string & {
     return wrapped->unit();
   };
 
@@ -222,22 +223,24 @@ public:
   inline auto ToString() const -> const std::string {
     google::protobuf::util::JsonOptions options;
     std::string out;
-    google::protobuf::util::MessageToJsonString(*(this->wrapped.get()), &out,
+    google::protobuf::util::MessageToJsonString(*(this->wrapped), &out,
                                                 options);
     return out;
   }
 
   auto SetId(const std::string &id) -> void;
   auto SetName(const std::string &name) -> void;
-  auto SetImportance(const std::string &importance) -> void;
+  auto SetVersionId(const std::string &id) -> void;
   auto SetValue(const std::string &value) -> void;
   auto SetUnit(const std::string &unit) -> void;
   auto SetDatatype(const std::string &datatype) -> void;
   auto AppendProperty(const Property &property) -> void;
   auto AppendParent(const Parent &parent) -> void;
+  auto Switch(ProtoEntity *entity) -> void;
 
 private:
-  std::unique_ptr<ProtoEntity> wrapped;
+  static auto CreateProtoEntity() -> ProtoEntity *;
+  ProtoEntity *wrapped;
   Properties properties;
   Parents parents;
   Messages errors;
diff --git a/include/caosdb/exceptions.h b/include/caosdb/exceptions.h
index cd60323..6dc44cb 100644
--- a/include/caosdb/exceptions.h
+++ b/include/caosdb/exceptions.h
@@ -64,11 +64,21 @@ public:
  * @brief The transaction terminated unsuccessfully.
  */
 class TransactionError : public GenericException {
+protected:
+  TransactionError(StatusCode code, const std::string &what_arg)
+    : GenericException(code, what_arg) {}
+
 public:
   explicit TransactionError(const std::string &what_arg)
     : GenericException(StatusCode::GENERIC_TRANSACTION_ERROR, what_arg) {}
 };
 
+class TransactionStatusError : public TransactionError {
+public:
+  explicit TransactionStatusError(const std::string &what_arg)
+    : TransactionError(StatusCode::TRANSACTION_STATUS_ERROR, what_arg) {}
+};
+
 /**
  * @brief Exception for errors of the ConfigurationManager or other components
  * of the configuration.
diff --git a/include/caosdb/protobuf_helper.h b/include/caosdb/protobuf_helper.h
new file mode 100644
index 0000000..ce7dd5e
--- /dev/null
+++ b/include/caosdb/protobuf_helper.h
@@ -0,0 +1,34 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@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/>.
+ *
+ */
+
+#ifndef CAOSDB_PROTOBUF_HELPER_H
+#define CAOSDB_PROTOBUF_HELPER_H
+
+#include <google/protobuf/arena.h>
+
+namespace caosdb::utility {
+
+using google::protobuf::Arena;
+
+auto get_arena() -> Arena *;
+
+} // namespace caosdb::utility
+#endif
diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h
index 6fb8db3..021f893 100644
--- a/include/caosdb/status_code.h
+++ b/include/caosdb/status_code.h
@@ -45,6 +45,7 @@ enum StatusCode {
   GENERIC_TRANSACTION_ERROR = 22,
   CONFIGURATION_ERROR = 23,
   UNKNOWN_CONNECTION_ERROR = 24,
+  TRANSACTION_STATUS_ERROR = 25,
 };
 
 auto get_status_description(int code) -> const std::string &;
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index 106e0d5..cc7502c 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -24,23 +24,26 @@
 /**
  * @brief Creation and execution of transactions.
  */
-#include <memory>                                // for shared_ptr, unique_ptr
-#include <string>                                // for string
 #include "caosdb/entity.h"                       // for Entity
 #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 "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso...
+#include <memory>                           // for shared_ptr, unique_ptr
+#include <string>                           // for string
 
 namespace caosdb::transaction {
 using caosdb::entity::Entity;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using caosdb::entity::v1alpha1::EntityTransactionService;
-using caosdb::entity::v1alpha1::SingleRetrieveRequest;
+using caosdb::entity::v1alpha1::IdResponse;
+using caosdb::entity::v1alpha1::MultiTransactionRequest;
+using caosdb::entity::v1alpha1::MultiTransactionResponse;
 using caosdb::transaction::TransactionStatus;
 
 class ResultSet {
 public:
-  virtual ~ResultSet(){};
+  virtual ~ResultSet() = default;
 };
 
 class UniqueResult : public ResultSet {
@@ -48,6 +51,8 @@ public:
   ~UniqueResult(){};
   explicit inline UniqueResult(ProtoEntity *protoEntity)
     : entity(new Entity(protoEntity)){};
+  explicit inline UniqueResult(IdResponse *idResponse)
+    : entity(new Entity(idResponse)){};
   [[nodiscard]] auto GetEntity() const -> const Entity &;
 
 private:
@@ -59,16 +64,46 @@ private:
  */
 class Transaction {
 private:
-  std::unique_ptr<ResultSet> result_set;
+  mutable std::unique_ptr<ResultSet> result_set;
   TransactionStatus status = TransactionStatus::INITIAL();
   std::shared_ptr<EntityTransactionService::Stub> service_stub;
-  SingleRetrieveRequest request; // TODO(tf)
+  MultiTransactionRequest *request;
+  mutable MultiTransactionResponse *response;
   std::string error_message;
 
 public:
   Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub);
+
+  /**
+   * Add an entity id to this transaction for retrieval.
+   *
+   * The retrieval is being processed when the Execute() or
+   * ExecuteAsynchronously() methods of this transaction are called.
+   */
   auto RetrieveById(const std::string &id) -> void;
 
+  /**
+   * Add the entity to this transaction for an insertion.
+   *
+   * The insertion is being processed when the Execute() or
+   * ExecuteAsynchronously() methods of this transaction are called.
+   *
+   * Changing the entity afterwards results in undefined behavior.
+   */
+  auto InsertEntity(Entity *entity) -> void;
+
+  /**
+   * Add an entity id to this transaction for deletion.
+   *
+   * The deletion is being processed when the Execute() or
+   * ExecuteAsynchronously() methods of this transaction are called.
+   */
+  auto DeleteById(const std::string &id) -> void;
+
+  inline auto IsStatus(const TransactionStatus &status) const noexcept -> bool {
+    return this->status.GetCode() == status.GetCode();
+  };
+
   /**
    * Execute this transaction in blocking mode and return the status.
    */
@@ -102,6 +137,13 @@ public:
     const ResultSet *result_set = this->result_set.get();
     return *result_set;
   }
+
+  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;
+  }
 };
 
 } // namespace caosdb::transaction
diff --git a/proto b/proto
index 72306a7..d6f2197 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 72306a73676e6880a7164108ab0ab17b8978f7e1
+Subproject commit d6f2197731060a66934a9d45825cc9635a31131a
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 67074f7..95b2dab 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,7 @@ set(libcaosdb_SRC
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/configuration.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.cpp
     )
 
diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp
index 65a641e..c4bd565 100644
--- a/src/caosdb/entity.cpp
+++ b/src/caosdb/entity.cpp
@@ -18,33 +18,45 @@
  *
  */
 #include "caosdb/entity.h"
-#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS...
-#include "caosdb/entity/v1alpha1/main.pb.h"      // for SingleRetrieveRequest
-#include <cassert>                               // for assert
-#include <map>                                   // for map
-#include <memory>                                // for allocator, unique_ptr
-#include <stdexcept>                             // for out_of_range
-#include <utility>                               // for move
+#include "caosdb/entity/v1alpha1/main.pb.h" // for Parent, Arena::CreateMay...
+#include "caosdb/protobuf_helper.h"         // for get_arena
+#include "google/protobuf/arena.h"          // for Arena
 
 namespace caosdb::entity {
+using caosdb::entity::v1alpha1::IdResponse;
+using ProtoParent = caosdb::entity::v1alpha1::Parent;
+using ProtoEntity = caosdb::entity::v1alpha1::Entity;
+using caosdb::utility::get_arena;
+
+Parent::Parent() : wrapped(Parent::CreateProtoParent()) {}
+
+auto Parent::CreateProtoParent() -> ProtoParent * {
+  return google::protobuf::Arena::CreateMessage<ProtoParent>(get_arena());
+}
+
 auto Parent::SetName(const std::string &name) -> void {
   this->wrapped->set_name(name);
 }
 
 auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
 
-auto Parent::AppendTo(
-  ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent>
-    *parents) const -> void {
-  auto *destination = parents->Add();
-  destination = std::move(this->wrapped.get());
+[[nodiscard]] auto Parent::GetId() const -> const std::string & {
+  return this->wrapped->id();
+}
+
+[[nodiscard]] auto Parent::GetName() const -> const std::string & {
+  return this->wrapped->name();
 }
 
 auto Parents::Append(const Parent &parent) -> void {
   auto *destination = this->wrapped->Add();
-  destination = std::move(parent.wrapped.get());
+  destination->Swap(parent.wrapped);
+
+  // Clear the originally wrapped object and return it to the Arena
+  parent.wrapped->Clear();
 
-  // parent.AppendTo(this->wrapped);
+  // set the pointer to the new object which is owned by the RepeatedPtrField
+  parent.wrapped = destination;
 }
 
 [[nodiscard]] auto Entity::GetParents() const -> const Parents & {
@@ -54,4 +66,32 @@ auto Parents::Append(const Parent &parent) -> void {
 auto Entity::AppendParent(const Parent &parent) -> void {
   this->parents.Append(parent);
 }
+
+auto Entity::CreateProtoEntity() -> ProtoEntity * {
+  return google::protobuf::Arena::CreateMessage<ProtoEntity>(get_arena());
+}
+
+Entity::Entity() : wrapped(Entity::CreateProtoEntity()) {
+  properties.wrapped = this->wrapped->mutable_properties();
+  parents.wrapped = this->wrapped->mutable_parents();
+  errors.wrapped = this->wrapped->mutable_errors();
+}
+
+Entity::Entity(IdResponse *idResponse) : Entity() {
+  this->wrapped->set_id(idResponse->id());
+  this->wrapped->mutable_errors()->Swap(idResponse->mutable_entity_errors());
+}
+
+auto Entity::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
+
+auto Entity::SetVersionId(const std::string &id) -> void {
+  this->wrapped->mutable_version()->set_id(id);
+}
+
+auto Entity::Switch(ProtoEntity *entity) -> void {
+  this->wrapped->Swap(entity);
+  this->wrapped->Clear();
+  this->wrapped = entity;
+}
+
 } // namespace caosdb::entity
diff --git a/src/caosdb/protobuf_helper.cpp b/src/caosdb/protobuf_helper.cpp
new file mode 100644
index 0000000..a9ad000
--- /dev/null
+++ b/src/caosdb/protobuf_helper.cpp
@@ -0,0 +1,33 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@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/protobuf_helper.h"
+#include <google/protobuf/arena.h>
+
+namespace caosdb::utility {
+
+using google::protobuf::Arena;
+
+auto get_arena() -> Arena * {
+  static Arena arena;
+  return &arena;
+}
+
+} // namespace caosdb::utility
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index 302eb48..71e9300 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -20,7 +20,10 @@
 #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/protobuf_helper.h"               // for get_arena
 #include "caosdb/status_code.h"                   // for StatusCode, AUTHEN...
+#include "google/protobuf/arena.h"                // for Arena
 #include "grpcpp/grpcpp.h"                        // for CompletionQueue
 #include "grpcpp/impl/codegen/async_unary_call.h" // for ClientAsyncRespons...
 #include "grpcpp/impl/codegen/client_context.h"   // for ClientContext
@@ -59,7 +62,9 @@ auto get_status_description(int code) -> const std::string & {
     {StatusCode::CONFIGURATION_ERROR,
      "An error occurred during the configuration of the ConfigurationManager."},
     {StatusCode::UNKNOWN_CONNECTION_ERROR,
-     "The ConnectionManager does not know any connection of this name."}};
+     "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."}};
   try {
     return descriptions.at(code);
   } catch (const std::out_of_range &exc) {
@@ -71,8 +76,13 @@ auto get_status_description(int code) -> const std::string & {
 
 namespace caosdb::transaction {
 using caosdb::entity::v1alpha1::EntityTransactionService;
-using caosdb::entity::v1alpha1::SingleRetrieveRequest;
-using caosdb::entity::v1alpha1::SingleRetrieveResponse;
+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;
@@ -83,16 +93,63 @@ using grpc::CompletionQueue;
 }
 
 Transaction::Transaction(
-  std::shared_ptr<EntityTransactionService::Stub> service_stub) {
+  std::shared_ptr<EntityTransactionService::Stub> service_stub)
+  : request(google::protobuf::Arena::CreateMessage<MultiTransactionRequest>(
+      get_arena())),
+    response(google::protobuf::Arena::CreateMessage<MultiTransactionResponse>(
+      get_arena())) {
   this->service_stub = std::move(service_stub);
 }
 
 auto Transaction::RetrieveById(const std::string &id) -> void {
-  SingleRetrieveRequest request;
-  // this copies the id, so we're safe.
-  request.mutable_retrieve_request()->set_id(id);
+  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 *sub_request = this->request->add_requests();
+  sub_request->mutable_retrieve_request()->set_id(id);
+}
+
+auto Transaction::DeleteById(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 DeleteById sub-request");
+  }
 
-  this->request = request;
+  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));
+  }
+
+  // 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();
+  auto *proto_entity = sub_request->mutable_insert_request()->mutable_entity();
+
+  // swap and switch
+  entity->Switch(proto_entity);
 }
 
 auto Transaction::Execute() -> TransactionStatus {
@@ -106,17 +163,16 @@ auto Transaction::ExecuteAsynchronously() noexcept -> void {
 
   grpc::Status grpc_status;
   CompletionQueue cq;
-  SingleRetrieveResponse response;
 
   grpc::ClientContext context;
-  std::unique_ptr<ClientAsyncResponseReader<SingleRetrieveResponse>> rpc(
-    this->service_stub->PrepareAsyncSingleRetrieve(&context, this->request,
-                                                   &cq));
+  std::unique_ptr<ClientAsyncResponseReader<MultiTransactionResponse>> rpc(
+    this->service_stub->PrepareAsyncMultiTransaction(&context, *(this->request),
+                                                     &cq));
   rpc->StartCall();
 
   int tag = 1;
   void *send_tag = static_cast<void *>(&tag);
-  rpc->Finish(&response, &grpc_status, send_tag);
+  rpc->Finish(this->response, &grpc_status, send_tag);
   void *recv_tag = nullptr;
   bool ok = false;
 
@@ -141,12 +197,32 @@ auto Transaction::ExecuteAsynchronously() noexcept -> void {
   } else {
     this->status = TransactionStatus::SUCCESS();
   }
-
-  auto *entity = response.mutable_retrieve_response()->release_entity();
-  this->result_set = std::make_unique<UniqueResult>(entity);
 }
 
 auto Transaction::WaitForIt() const noexcept -> TransactionStatus {
+  if (this->response->responses_size() == 1) {
+    auto *responses = this->response->mutable_responses(0);
+    switch (responses->wrapped_response_case()) {
+    case WrappedResponseCase::kRetrieveResponse: {
+      auto *entity = responses->mutable_retrieve_response()->release_entity();
+      this->result_set = std::make_unique<UniqueResult>(entity);
+    } break;
+    case WrappedResponseCase::kInsertResponse: {
+      auto *insertedIdResponse = responses->mutable_insert_response();
+      this->result_set = std::make_unique<UniqueResult>(insertedIdResponse);
+    } break;
+    case WrappedResponseCase::kDeleteResponse: {
+      auto *deletedIdResponse = responses->mutable_delete_response();
+      this->result_set = std::make_unique<UniqueResult>(deletedIdResponse);
+    } break;
+    default:
+      // TODO(tf)
+      break;
+    }
+  } else {
+    // TODO(tf)
+  }
+
   return this->status;
 }
 
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4eb876b..6a9216a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -24,6 +24,7 @@ set(test_cases
     test_connection
     test_entity
     test_info
+    test_protobuf
     test_transaction
     test_utility
     test_ccaosdb
diff --git a/test/test_entity.cpp b/test/test_entity.cpp
index ff0fa68..fa552e8 100644
--- a/test/test_entity.cpp
+++ b/test/test_entity.cpp
@@ -19,28 +19,74 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  *
  */
-#include "caosdb/entity.h"                  // for Entity
-#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity
-#include "caosdb/exceptions.h"              // for ConnectionError
-#include "caosdb/transaction.h"             // for Transaction, UniqueResult
-#include "caosdb/transaction_status.h"      // for ConnectionError
-#include "caosdb_test_utility.h"            // for EXPECT_THROW_MESSAGE
-#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 "caosdb/entity.h"                       // for Entity, Parent, Par...
+#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe...
+#include "caosdb/entity/v1alpha1/main.pb.h"      // for IdResponse, Message
+#include "caosdb/transaction.h"                  // for Transaction
+#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;
 
-TEST(test_entity, test_append_parent) {
+TEST(test_entity, test_parent_setters) {
   auto parent = Parent();
   parent.SetName("RT1");
   parent.SetId("some-id");
 
+  EXPECT_EQ(parent.GetName(), "RT1");
+  EXPECT_EQ(parent.GetId(), "some-id");
+}
+
+TEST(test_entity, test_append_parent) {
+  auto parent = Parent();
+  parent.SetId("some-id");
+
   auto entity = Entity();
   EXPECT_EQ(entity.GetParents().Size(), 0);
   entity.AppendParent(parent);
   EXPECT_EQ(entity.GetParents().Size(), 1);
+
+  auto same_parent = entity.GetParents().At(0);
+  EXPECT_EQ(same_parent.GetId(), "some-id");
+}
+
+TEST(test_entity, test_insert_entity) {
+  auto transaction = caosdb::transaction::Transaction(
+    std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr));
+
+  auto entity = Entity();
+  entity.SetId("entity_id");
+  entity.SetVersionId("version_id");
+
+  EXPECT_EQ(entity.GetId(), "entity_id");
+  EXPECT_EQ(entity.GetVersionId(), "version_id");
+
+  transaction.InsertEntity(&entity);
+
+  EXPECT_EQ(entity.GetId(), "entity_id");
+  EXPECT_EQ(entity.GetVersionId(), "version_id");
+}
+
+TEST(test_entity, test_from_id_response) {
+  IdResponse idResponse;
+  idResponse.set_id("entity_id");
+  auto *error = idResponse.add_entity_errors();
+  error->set_code(1234);
+  error->set_description("error_desc");
+
+  Entity entity(&idResponse);
+
+  std::cout << entity.ToString() << std::endl;
+  EXPECT_EQ(entity.GetId(), "entity_id");
+  EXPECT_EQ(entity.GetErrors().Size(), 1);
+  EXPECT_EQ(entity.GetErrors().At(0).GetDescription(), "error_desc");
+  EXPECT_EQ(entity.GetErrors().At(0).GetDescription(), "wrong");
+  EXPECT_EQ(entity.GetErrors().At(0).GetCode(), 1234);
 }
 
 } // namespace caosdb::entity
diff --git a/test/test_protobuf.cpp b/test/test_protobuf.cpp
new file mode 100644
index 0000000..9dc957a
--- /dev/null
+++ b/test/test_protobuf.cpp
@@ -0,0 +1,84 @@
+/*
+ *
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@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/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Message
+#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
+
+namespace caosdb {
+using caosdb::entity::v1alpha1::Entity;
+using caosdb::entity::v1alpha1::Message;
+
+TEST(test_protobuf, test_swap_trivial) {
+  Message message_source;
+  message_source.set_code(1234);
+  message_source.set_description("desc");
+
+  Message message_destination;
+
+  EXPECT_EQ(message_source.code(), 1234);
+  EXPECT_EQ(message_source.description(), "desc");
+  EXPECT_EQ(message_destination.code(), 0);
+  EXPECT_EQ(message_destination.description(), "");
+
+  message_source.Swap(&message_destination);
+
+  EXPECT_EQ(message_source.code(), 0);
+  EXPECT_EQ(message_source.description(), "");
+  EXPECT_EQ(message_destination.code(), 1234);
+  EXPECT_EQ(message_destination.description(), "desc");
+}
+
+TEST(test_protobuf, test_swap_nested) {
+  Entity entity_source;
+  entity_source.set_id("entity_id");
+  auto *version_source = entity_source.mutable_version();
+  version_source->set_id("version_id");
+
+  Entity entity_destination;
+  auto *version_destination = entity_destination.mutable_version();
+
+  EXPECT_EQ(entity_source.id(), "entity_id");
+  EXPECT_EQ(entity_source.version().id(), "version_id");
+  EXPECT_EQ(version_source->id(), "version_id");
+  EXPECT_EQ(entity_destination.id(), "");
+  EXPECT_EQ(entity_destination.version().id(), "");
+  EXPECT_EQ(version_destination->id(), "");
+
+  entity_source.Swap(&entity_destination);
+
+  EXPECT_EQ(entity_source.id(), "");
+  EXPECT_EQ(entity_source.version().id(), "");
+  EXPECT_EQ(entity_destination.id(), "entity_id");
+  EXPECT_EQ(entity_destination.version().id(), "version_id");
+
+  // has not been swapped!
+  EXPECT_EQ(version_source->id(), "version_id");
+  EXPECT_EQ(version_destination->id(), "");
+
+  // Member pointers to nested messages have been swapped
+  EXPECT_EQ(entity_source.mutable_version(), version_destination);
+  EXPECT_EQ(entity_destination.mutable_version(), version_source);
+}
+
+} // namespace caosdb
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index c2c7696..ab1979f 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -24,13 +24,14 @@
 #include "caosdb/entity.h"                  // for Entity
 #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity
 #include "caosdb/exceptions.h"              // for ConnectionError
-#include "caosdb/transaction.h"             // for Transaction, UniqueResult
-#include "caosdb/transaction_status.h"      // for ConnectionError
-#include "caosdb_test_utility.h"            // for EXPECT_THROW_MESSAGE
-#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 "caosdb/status_code.h"
+#include "caosdb/transaction.h"        // for Transaction, UniqueResult
+#include "caosdb/transaction_status.h" // for ConnectionError
+#include "caosdb_test_utility.h"       // for EXPECT_THROW_MESSAGE
+#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
 
 namespace caosdb::transaction {
 using caosdb::configuration::InsecureConnectionConfiguration;
@@ -64,4 +65,17 @@ TEST(test_transaction, unique_result) {
   // delete entity;
 }
 
+TEST(test_transaction, test_unavailable) {
+  const auto *host = "localhost";
+  auto configuration = InsecureConnectionConfiguration(host, 8000);
+  Connection connection(configuration);
+  auto transaction = connection.CreateTransaction();
+
+  transaction->RetrieveById("100");
+  transaction->ExecuteAsynchronously();
+
+  auto status = transaction->WaitForIt();
+  EXPECT_EQ(status.GetCode(), StatusCode::CONNECTION_ERROR);
+}
+
 } // namespace caosdb::transaction
-- 
GitLab