From 6d48d0cc44a18afd693550991a290aa581ded18f Mon Sep 17 00:00:00 2001
From: Florian Spreckelsen <f.spreckelsen@indiscale.com>
Date: Tue, 17 Aug 2021 08:28:14 +0000
Subject: [PATCH] ENH: Add retrieval and queries to Extern C interface

---
 doc/capi/index.rst.in        |  15 +
 include/caosdb/entity.h      | 288 +++++++++++-----
 include/caosdb/status_code.h |   4 +-
 include/ccaosdb.h            | 149 +++++++--
 src/caosdb/entity.cpp        |  32 +-
 src/caosdb/transaction.cpp   |   9 +-
 src/ccaosdb.cpp              | 621 ++++++++++++++++++++++++++++++++++-
 test/test_ccaosdb.cpp        | 356 +++++++++++++++++++-
 test/test_entity.cpp         | 110 ++++++-
 test/test_transaction.cpp    |   7 +-
 10 files changed, 1447 insertions(+), 144 deletions(-)

diff --git a/doc/capi/index.rst.in b/doc/capi/index.rst.in
index 887f7ee..a15f3b8 100644
--- a/doc/capi/index.rst.in
+++ b/doc/capi/index.rst.in
@@ -24,6 +24,21 @@
 C API
 =====
 
+.. note::
+
+   When working with libcaosdb's C API keep the following in
+   mind. Delete all objects (transactions, entities, properties,
+   parents, ...) that you created using a `caosdb_..._create_...`
+   function and only those.
+
+   The underlying reason is that all C++ objects are realized in the
+   Extern C interface as mutable structs containing a void pointer to
+   the actuall C++ object which is not filled when initializing the
+   struct but after calling a create function instead. If the C++
+   object wasn't created using a create function, e.g., the parent
+   object when getting a parent of an entity, it is owned by another
+   object and deleted together with that object.
+
 .. toctree::
     :glob:
 
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index f18a0d9..3917e93 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -43,9 +43,11 @@
 #include <google/protobuf/message.h>                   // for RepeatedPtrField
 #include <google/protobuf/util/json_util.h>            // for MessageToJson...
 #include <iosfwd>                                      // for streamsize
-#include <random>                                      // for mt19937, rand...
-#include <stdexcept>                                   // for out_of_range
-#include <string>                                      // for string, basic...
+#include <iterator>  // for iterator, output_iterato...
+#include <map>       // for map
+#include <random>    // for mt19937, rand...
+#include <stdexcept> // for out_of_range
+#include <string>    // for string, basic...
 
 namespace caosdb::entity {
 using boost::filesystem::exists;
@@ -59,10 +61,10 @@ using ProtoMessage = caosdb::entity::v1alpha1::Message;
 using caosdb::StatusCode;
 using caosdb::entity::v1alpha1::EntityResponse;
 using caosdb::entity::v1alpha1::FileTransmissionId;
+using ::google::protobuf::RepeatedPtrField;
 using google::protobuf::RepeatedPtrField;
-using ProtoMessage = caosdb::entity::v1alpha1::Message;
 
-const std::string logger_name = "caosdb::entity";
+static const std::string logger_name = "caosdb::entity";
 
 struct FileDescriptor {
   FileTransmissionId *file_transmission_id;
@@ -71,16 +73,197 @@ struct FileDescriptor {
 };
 
 /**
- * Messages convey information about the state and result of transactions.
+ * Abstract base class for Messages, Properties and Parents container classes.
+ *
+ * This is a list-like class.
+ */
+template <typename T, typename P> class RepeatedPtrFieldWrapper {
+  class iterator;
+
+public:
+  /**
+   * Return the current size of the container.
+   */
+  [[nodiscard]] inline auto size() const -> int { return wrapped->size(); }
+  /**
+   * Return a const reference to the element at the given index.
+   */
+  [[nodiscard]] inline auto at(int index) const -> const T & {
+    return *mutable_at(index);
+  }
+
+  /**
+   * Return a mutable pointer to the element at the given index.
+   */
+  [[nodiscard]] inline auto mutable_at(int index) const -> T * {
+    if (index >= size() || index < 0) {
+      throw std::out_of_range("Container has size " + std::to_string(size()));
+    }
+    if (cache.count(index) == 0) {
+      cache.emplace(index, T(&(wrapped->at(index))));
+    }
+    return &(cache.at(index));
+  }
+  /**
+   * Return iterator positioned at the beginning of the list.
+   */
+  auto begin() -> iterator;
+  /**
+   * Return iterator positioned at the end of the list.
+   */
+  auto end() -> iterator;
+  /**
+   * Return constant iterator positioned at the beginning of the list.
+   */
+  auto begin() const -> const iterator;
+  /**
+   * Return constant iterator positioned at the end of the list.
+   */
+  auto end() const -> const iterator;
+
+  friend class Entity;
+
+  virtual ~RepeatedPtrFieldWrapper(){};
+
+protected:
+  RepeatedPtrFieldWrapper(){};
+  explicit inline RepeatedPtrFieldWrapper(
+    ::google::protobuf::RepeatedPtrField<P> *wrapped)
+    : wrapped(wrapped){};
+
+  /**
+   * Append an element. This adds the element to the end of the wrapped list
+   * and increases the size by one.
+   */
+  inline auto Append(const T &element) -> void {
+    auto *destination = this->wrapped->Add();
+    destination->Swap(element.wrapped);
+
+    // Clear the originally wrapped object and return it to the Arena
+    element.wrapped->Clear();
+
+    // set the pointer to the new object which is owned by the RepeatedPtrField
+    element.wrapped = destination;
+  }
+
+  /**
+   * Remove the element at the given index.
+   */
+  inline auto remove(int index) -> void {
+    this->wrapped->DeleteSubrange(index, 1);
+    if (cache.count(index) > 0) {
+      cache.erase(index);
+    }
+
+    // shift all indices in the cache above index (such that the values do not
+    // get deleted/copied because this could destroy pointers (c-interface).
+    for (int i = index + 1; i < size(); i++) {
+      if (cache.count(i) > 0) {
+        auto handle = cache.extract(i);
+        handle.key()--;
+        cache.insert(std::move(handle));
+      }
+    }
+  }
+
+  ::google::protobuf::RepeatedPtrField<P> *wrapped;
+  mutable std::map<int, T> cache;
+
+private:
+  class iterator : public std::iterator<std::output_iterator_tag, T> {
+  public:
+    explicit iterator(const RepeatedPtrFieldWrapper<T, P> *instance,
+                      int index = 0);
+    auto operator*() const -> T &;
+    auto operator++() -> iterator &;
+    auto operator++(int) -> iterator;
+    auto operator!=(const iterator &rhs) const -> bool;
+
+  private:
+    int current_index = 0;
+    const RepeatedPtrFieldWrapper<T, P> *instance;
+  };
+};
+
+template <class T, class P>
+RepeatedPtrFieldWrapper<T, P>::iterator::iterator(
+  const RepeatedPtrFieldWrapper<T, P> *instance, int index)
+  : current_index(index), instance(instance) {}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::iterator::operator*() const -> T & {
+  return *(this->instance->mutable_at(current_index));
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::iterator::operator++()
+  -> RepeatedPtrFieldWrapper<T, P>::iterator & {
+  current_index++;
+  return *this;
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::iterator::operator++(int)
+  -> RepeatedPtrFieldWrapper<T, P>::iterator {
+  iterator tmp(*this);
+  operator++();
+  return tmp;
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::iterator::operator!=(
+  const iterator &rhs) const -> bool {
+  return this->current_index != rhs.current_index;
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::begin()
+  -> RepeatedPtrFieldWrapper<T, P>::iterator {
+  return RepeatedPtrFieldWrapper<T, P>::iterator(this, 0);
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::end()
+  -> RepeatedPtrFieldWrapper<T, P>::iterator {
+  return RepeatedPtrFieldWrapper<T, P>::iterator(this, size());
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::begin() const
+  -> const RepeatedPtrFieldWrapper<T, P>::iterator {
+  return RepeatedPtrFieldWrapper<T, P>::iterator(this, 0);
+}
+
+template <typename T, typename P>
+auto RepeatedPtrFieldWrapper<T, P>::end() const
+  -> const RepeatedPtrFieldWrapper<T, P>::iterator {
+  return RepeatedPtrFieldWrapper<T, P>::iterator(this, size());
+}
+
+/**
+ * Messages convey information about the state of entities and result of
+ * transactions.
  *
  * A Message object can be thought of as kinf of a generalized error object in
  * other frameworks. Please have a look at MessageCodes for more details.
  */
 class Message {
 public:
+  /**
+   * Get the code of this message.
+   *
+   * The message code is a unique identifier of the type of message and is
+   * intended to make it easiert to identify messages in a machine-readable
+   * way.
+   */
   [[nodiscard]] inline auto GetCode() const -> MessageCode {
     return get_message_code(wrapped->code());
   }
+  /**
+   * Get the description of this message.
+   *
+   * The description is intended for a human reader.
+   */
   [[nodiscard]] inline auto GetDescription() const -> std::string {
     return wrapped->description();
   }
@@ -91,6 +274,7 @@ public:
   // friend class Parent;
   // friend class Property;
   friend class Messages;
+  friend class RepeatedPtrFieldWrapper<Message, ProtoMessage>;
 
 private:
   explicit inline Message(ProtoMessage *wrapped) : wrapped(wrapped){};
@@ -101,20 +285,9 @@ private:
 /**
  * Container for Messages.
  */
-class Messages {
+class Messages : public RepeatedPtrFieldWrapper<Message, ProtoMessage> {
 public:
-  [[nodiscard]] inline auto size() const -> int {
-    if (wrapped == nullptr) {
-      return 0;
-    }
-    return wrapped->size();
-  }
-  [[nodiscard]] inline auto at(int index) const -> const Message {
-    if (wrapped == nullptr) {
-      throw std::out_of_range("Number of messages: 0");
-    }
-    return Message(&(wrapped->at(index)));
-  }
+  ~Messages();
 
   friend class Entity;
   // TODO(fspreck) Same here.
@@ -122,9 +295,7 @@ public:
   // friend class Property;
 
 private:
-  inline Messages() : wrapped(nullptr){};
-
-  RepeatedPtrField<ProtoMessage> *wrapped;
+  inline Messages() : RepeatedPtrFieldWrapper(){};
 };
 
 /**
@@ -203,6 +374,7 @@ public:
 
   friend class Entity;
   friend class Parents;
+  friend class RepeatedPtrFieldWrapper<Parent, ProtoParent>;
 
 private:
   /**
@@ -231,40 +403,17 @@ private:
  *
  * Should only be instantiated and write-accessed by the owning entity.
  */
-class Parents {
+class Parents : public RepeatedPtrFieldWrapper<Parent, ProtoParent> {
 public:
-  /**
-   * Return the current size of the parent container.
-   *
-   * That is also the number of parents the owning entity currently has.
-   */
-  [[nodiscard]] inline auto size() const -> int { return wrapped->size(); }
-  /**
-   * Return the parent at the given index.
-   */
-  [[nodiscard]] inline auto at(int index) const -> const Parent {
-    return Parent(&(wrapped->at(index)));
-  }
-
+  ~Parents() = default;
   friend class Entity;
 
 private:
-  inline Parents(){};
+  inline Parents() : RepeatedPtrFieldWrapper(){};
   explicit inline Parents(
-    RepeatedPtrField<caosdb::entity::v1alpha1::Parent> *wrapped)
-    : wrapped(wrapped){};
-
-  /**
-   * Append a parent.
-   *
-   * This increases the size() by one.
-   */
-  auto Append(const Parent &parent) -> void;
-  /**
-   * The collection of parent messages which serves as a backend for this
-   * class.
-   */
-  RepeatedPtrField<caosdb::entity::v1alpha1::Parent> *wrapped;
+    ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent>
+      *wrapped)
+    : RepeatedPtrFieldWrapper(wrapped){};
 };
 
 /**
@@ -357,6 +506,7 @@ public:
 
   friend class Entity;
   friend class Properties;
+  friend class RepeatedPtrFieldWrapper<Property, ProtoProperty>;
 
 private:
   static auto CreateProtoProperty() -> ProtoProperty *;
@@ -369,37 +519,20 @@ private:
  *
  * Should only be instantiated and write-accessed by the owning entity.
  */
-class Properties {
+class Properties
+  : public RepeatedPtrFieldWrapper<Property,
+                                   caosdb::entity::v1alpha1::Property> {
 public:
-  /**
-   * Return the current size of the properties container.
-   *
-   * This is also the number of properties the owningn entity currently has.
-   */
-  [[nodiscard]] inline auto size() const -> int { return wrapped->size(); }
-  /**
-   * Return the property at the given index.
-   */
-  [[nodiscard]] auto at(int index) const -> const Property {
-    return Property(&(wrapped->at(index)));
-  }
-
+  ~Properties() = default;
   friend class Entity;
 
 private:
   inline Properties(){};
   explicit inline Properties(
-    RepeatedPtrField<caosdb::entity::v1alpha1::Property> *wrapped)
-    : wrapped(wrapped){};
-
-  /**
-   * Append a property
-   *
-   * This increases the size() by one.
-   */
-  auto Append(const Property &property) -> void;
-
-  RepeatedPtrField<caosdb::entity::v1alpha1::Property> *wrapped;
+    ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Property>
+      *wrapped)
+    : RepeatedPtrFieldWrapper<Property, caosdb::entity::v1alpha1::Property>(
+        wrapped){};
 };
 
 /**
@@ -491,9 +624,12 @@ public:
   auto SetUnit(const std::string &unit) -> void;
   // Currently no references or lists.
   auto SetDatatype(const std::string &datatype) -> void;
+
   auto AppendProperty(const Property &property) -> void;
+  auto RemoveProperty(int index) -> void;
 
   auto AppendParent(const Parent &parent) -> void;
+  auto RemoveParent(int index) -> void;
   /**
    * Copy all of this entity's features to the target ProtoEntity.
    */
diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h
index 6cbb055..f996901 100644
--- a/include/caosdb/status_code.h
+++ b/include/caosdb/status_code.h
@@ -53,11 +53,13 @@ enum StatusCode {
   TRANSACTION_TYPE_ERROR = 26,
   UNSUPPORTED_FEATURE = 27,
   ORIGINAL_ENTITY_MISSING_ID = 28,
-  NOT_A_FILE_ENTITY = 29,
+  EXTERN_C_ASSIGNMENT_ERROR = 29,
   PATH_IS_A_DIRECTORY = 30,
   FILE_DOES_NOT_EXIST_LOCALLY = 31,
   FILE_UPLOAD_ERROR = 32,
   FILE_DOWNLOAD_ERROR = 33,
+  NOT_A_FILE_ENTITY = 34,
+  OTHER_CLIENT_ERROR = 9999,
 };
 
 auto get_status_description(int code) -> const std::string &;
diff --git a/include/ccaosdb.h b/include/ccaosdb.h
index bb62a51..4597690 100644
--- a/include/ccaosdb.h
+++ b/include/ccaosdb.h
@@ -1,3 +1,24 @@
+/*
+ * This file is a part of the CaosDB Project.
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@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/>.
+ *
+ */
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -31,6 +52,12 @@ int caosdb_constants_COMPATIBLE_SERVER_VERSION_PATCH();
  */
 const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE();
 
+/**
+ * Return the status code reserved for errors in clients wrapping this
+ * interface.
+ */
+int caosdb_status_code_OTHER_CLIENT_ERROR();
+
 /**
  * A wrapper of the C++ Connection class.
  *
@@ -40,6 +67,7 @@ const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE();
  */
 typedef struct {
   void *wrapped_connection;
+  bool _deletable = false;
 } caosdb_connection_connection;
 
 /**
@@ -51,6 +79,7 @@ typedef struct {
  */
 typedef struct {
   void *wrapped_connection_configuration;
+  bool _deletable = false;
 } caosdb_connection_connection_configuration;
 
 /**
@@ -70,10 +99,12 @@ typedef struct {
 
 typedef struct {
   void *wrapped_certificate_provider;
+  bool _deletable = false;
 } caosdb_connection_certificate_provider;
 
 typedef struct {
   void *wrapped_authenticator;
+  bool _deletable = false;
 } caosdb_authentication_authenticator;
 
 /**
@@ -242,62 +273,77 @@ int caosdb_connection_connection_manager_get_connection(
 // not sufficient yet.
 typedef struct {
   void *wrapped_transaction;
+  bool _deletable = false;
 } caosdb_transaction_transaction;
 
+/**
+ * Create a transaction on an existing connection.
+ *
+ * This transaction has to be deleted manually by
+ * caosdb_transaction_delete_transaction() later on.
+ */
 int caosdb_connection_connection_create_transaction(
   caosdb_connection_connection *connection,
   caosdb_transaction_transaction *out);
+int caosdb_transaction_delete_transaction(
+  caosdb_transaction_transaction *transaction);
 int caosdb_transaction_transaction_retrieve_by_id(
   caosdb_transaction_transaction *transaction, const char *id);
+int caosdb_transaction_transaction_retrieve_by_ids(
+  caosdb_transaction_transaction *transaction, const char *ids[], int length);
+int caosdb_transaction_transaction_query(
+  caosdb_transaction_transaction *transaction, const char *query);
 int caosdb_transaction_transaction_execute(
   caosdb_transaction_transaction *transaction);
+// TODO(fspreck) execute_asynchronously may be added as a separate
+// function once we actually support asynchronous execution.
 
 typedef struct {
   void *wrapped_result_set;
+  bool _deletable = false;
 } caosdb_transaction_result_set;
 
 int caosdb_transaction_transaction_get_result_set(
   caosdb_transaction_transaction *transaction,
   caosdb_transaction_result_set *out);
 
+int caosdb_transaction_transaction_get_count_result(
+  caosdb_transaction_transaction *transaction, long *out);
+
 typedef struct {
   void *wrapped_entity;
-  char **id;
-  char **role;
-  char **name;
-  char **description;
-  char **datatype;
-  char **unit;
-  char **value;
-  char **version_id;
+  bool _deletable = false;
 } caosdb_entity_entity;
 
-int caosdb_transaction_result_set_get_entity(
-  caosdb_transaction_result_set *result_set, caosdb_entity_entity *entity,
-  int index);
+int caosdb_transaction_result_set_at(caosdb_transaction_result_set *result_set,
+                                     caosdb_entity_entity *entity, int index);
+int caosdb_transaction_result_set_size(
+  caosdb_transaction_result_set *result_set, int *out);
 
 typedef struct {
   void *wrapped_property;
-  char **id;
-  char **name;
-  char **description;
-  char **datatype;
-  char **unit;
-  char **value;
+  bool _deletable = false;
 } caosdb_entity_property;
 typedef struct {
   void *wrapped_parent;
-  char **id;
-  char **name;
-  char **description;
+  bool _deletable = false;
 } caosdb_entity_parent;
 typedef struct {
   void *wrapped_message;
-  int *code;
-  char **description;
+  bool _deletable = false;
 } caosdb_entity_message;
 
-// GETTERS FOR COMPLEX OBJECTS
+// GETTERS FOR EVERYTHING
+int caosdb_entity_entity_get_id(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_role(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_name(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_description(caosdb_entity_entity *entity,
+                                         char *out);
+int caosdb_entity_entity_get_datatype(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_unit(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_value(caosdb_entity_entity *entity, char *out);
+int caosdb_entity_entity_get_version_id(caosdb_entity_entity *entity,
+                                        char *out);
 int caosdb_entity_entity_get_errors_size(caosdb_entity_entity *entity,
                                          int *out);
 int caosdb_entity_entity_get_error(caosdb_entity_entity *entity,
@@ -318,6 +364,29 @@ int caosdb_entity_entity_get_parents_size(caosdb_entity_entity *entity,
 int caosdb_entity_entity_get_parent(caosdb_entity_entity *entity,
                                     caosdb_entity_parent *out, int index);
 
+int caosdb_entity_property_get_id(caosdb_entity_property *property, char *out);
+int caosdb_entity_property_get_name(caosdb_entity_property *property,
+                                    char *out);
+int caosdb_entity_property_get_description(caosdb_entity_property *property,
+                                           char *out);
+int caosdb_entity_property_get_importance(caosdb_entity_property *property,
+                                          char *out);
+int caosdb_entity_property_get_datatype(caosdb_entity_property *property,
+                                        char *out);
+int caosdb_entity_property_get_unit(caosdb_entity_property *property,
+                                    char *out);
+int caosdb_entity_property_get_value(caosdb_entity_property *property,
+                                     char *out);
+
+int caosdb_entity_parent_get_id(caosdb_entity_parent *parent, char *out);
+int caosdb_entity_parent_get_name(caosdb_entity_parent *parent, char *out);
+int caosdb_entity_parent_get_description(caosdb_entity_parent *parent,
+                                         char *out);
+
+int caosdb_entity_message_get_code(caosdb_entity_message *message, int *out);
+int caosdb_entity_message_get_description(caosdb_entity_message *message,
+                                          char *out);
+
 // CONSTRUCTORS AND DESTRUCTORS
 int caosdb_entity_create_entity(caosdb_entity_entity *out);
 int caosdb_entity_delete_entity(caosdb_entity_entity *out);
@@ -326,11 +395,43 @@ int caosdb_entity_delete_property(caosdb_entity_property *out);
 int caosdb_entity_create_parent(caosdb_entity_parent *out);
 int caosdb_entity_delete_parent(caosdb_entity_parent *out);
 
-// SETTERS FOR COMPLEX OBJECTS
+// SETTERS FOR EVERYTHING THAT MAY BE SET
+int caosdb_entity_entity_set_role(caosdb_entity_entity *entity,
+                                  const char *role);
+int caosdb_entity_entity_set_name(caosdb_entity_entity *entity,
+                                  const char *name);
+int caosdb_entity_entity_set_description(caosdb_entity_entity *entity,
+                                         const char *description);
+int caosdb_entity_entity_set_datatype(caosdb_entity_entity *entity,
+                                      const char *datatype);
+int caosdb_entity_entity_set_unit(caosdb_entity_entity *entity,
+                                  const char *unit);
+int caosdb_entity_entity_set_value(caosdb_entity_entity *entity,
+                                   const char *value);
 int caosdb_entity_entity_append_parent(caosdb_entity_entity *entity,
                                        caosdb_entity_parent *parent);
+int caosdb_entity_entity_remove_parent(caosdb_entity_entity *entity, int index);
 int caosdb_entity_entity_append_property(caosdb_entity_entity *entity,
                                          caosdb_entity_property *property);
+int caosdb_entity_entity_remove_property(caosdb_entity_entity *entity,
+                                         int index);
+
+int caosdb_entity_property_set_id(caosdb_entity_property *property,
+                                  const char *id);
+int caosdb_entity_property_set_name(caosdb_entity_property *property,
+                                    const char *name);
+int caosdb_entity_property_set_datatype(caosdb_entity_property *property,
+                                        const char *datatype);
+int caosdb_entity_property_set_importance(caosdb_entity_property *property,
+                                          const char *importance);
+int caosdb_entity_property_set_unit(caosdb_entity_property *property,
+                                    const char *unit);
+int caosdb_entity_property_set_value(caosdb_entity_property *property,
+                                     const char *value);
+
+int caosdb_entity_parent_set_id(caosdb_entity_parent *parent, const char *id);
+int caosdb_entity_parent_set_name(caosdb_entity_parent *parent,
+                                  const char *name);
 
 #ifdef __cplusplus
 }
diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp
index e08c554..8f410a3 100644
--- a/src/caosdb/entity.cpp
+++ b/src/caosdb/entity.cpp
@@ -35,6 +35,8 @@ using ProtoFileDescriptor = caosdb::entity::v1alpha1::FileDescriptor;
 using caosdb::utility::get_arena;
 using google::protobuf::Arena;
 
+Messages::~Messages() = default;
+
 Parent::Parent() : wrapped(Parent::CreateProtoParent()) {
   // TODO(fspreck) Re-enable once we have decided how to attach
   // messages to parents.
@@ -51,10 +53,6 @@ auto Parent::SetName(const std::string &name) -> void {
   this->wrapped->set_name(name);
 }
 
-auto Parent::GetDescription() const -> const std::string & {
-  return this->wrapped->description();
-}
-
 auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
 
 [[nodiscard]] auto Parent::GetId() const -> const std::string & {
@@ -65,15 +63,8 @@ auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
   return this->wrapped->name();
 }
 
-auto Parents::Append(const Parent &parent) -> void {
-  auto *destination = this->wrapped->Add();
-  destination->Swap(parent.wrapped);
-
-  // Clear the originally wrapped object and return it to the Arena
-  parent.wrapped->Clear();
-
-  // set the pointer to the new object which is owned by the RepeatedPtrField
-  parent.wrapped = destination;
+[[nodiscard]] auto Parent::GetDescription() const -> const std::string & {
+  return this->wrapped->description();
 }
 
 Property::Property() : wrapped(Property::CreateProtoProperty()) {}
@@ -138,15 +129,6 @@ auto Property::SetDatatype(const std::string &datatype) -> void {
   this->wrapped->set_datatype(datatype);
 }
 
-auto Properties::Append(const Property &property) -> void {
-  auto *destination = this->wrapped->Add();
-  destination->Swap(property.wrapped);
-
-  property.wrapped->Clear();
-
-  property.wrapped = destination;
-}
-
 [[nodiscard]] auto Entity::GetParents() const -> const Parents & {
   return parents;
 }
@@ -155,6 +137,8 @@ auto Entity::AppendParent(const Parent &parent) -> void {
   this->parents.Append(parent);
 }
 
+auto Entity::RemoveParent(int index) -> void { this->parents.remove(index); }
+
 [[nodiscard]] auto Entity::GetProperties() const -> const Properties & {
   return properties;
 }
@@ -163,6 +147,10 @@ auto Entity::AppendProperty(const Property &property) -> void {
   this->properties.Append(property);
 }
 
+auto Entity::RemoveProperty(int index) -> void {
+  this->properties.remove(index);
+}
+
 auto Entity::CreateProtoEntity() -> ProtoEntity * {
   return Arena::CreateMessage<ProtoEntity>(get_arena());
 }
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index 149bfd5..a46019f 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -97,7 +97,14 @@ auto get_status_description(int code) -> const std::string & {
     {StatusCode::FILE_UPLOAD_ERROR,
      "The transaction failed during the upload of the files"},
     {StatusCode::UNSUPPORTED_FEATURE,
-     "This feature is not available in the this client implementation."}};
+     "This feature is not available in the this client implementation."},
+    {StatusCode::EXTERN_C_ASSIGNMENT_ERROR,
+     "You tried to assign a new object to the wrapped void pointer. You have "
+     "to delete the old pointee first."},
+    {StatusCode::OTHER_CLIENT_ERROR,
+     "This is code is reserved to errors raised by other clients wrapping the "
+     "C++ client (or its Extern C interface).  This should never occur when "
+     "working with the C++ code itself."}};
   try {
     return descriptions.at(code);
   } catch (const std::out_of_range &exc) {
diff --git a/src/ccaosdb.cpp b/src/ccaosdb.cpp
index 5f89af1..d962c5b 100644
--- a/src/ccaosdb.cpp
+++ b/src/ccaosdb.cpp
@@ -1,3 +1,24 @@
+/*
+ * This file is a part of the CaosDB Project.
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@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 "ccaosdb.h"
 #include "caosdb/connection.h"
 #include "caosdb/constants.h"
@@ -5,10 +26,12 @@
 #include "caosdb/status_code.h"
 #include "caosdb/logging.h"
 #include <cassert>
+#include <cstring>
 #include <exception>
 #include <iostream>
 #include <stdio.h>
 #include <string.h>
+#include <vector>
 
 extern "C" {
 
@@ -20,6 +43,7 @@ extern "C" {
  */
 #define ERROR_RETURN_CODE(code, fun, body)                                     \
   fun {                                                                        \
+    CAOSDB_LOG_TRACE(CCAOSDB_LOGGER_NAME) << "Enter " << #fun;                 \
     try {                                                                      \
       body                                                                     \
     } catch (const std::exception &exc) {                                      \
@@ -28,6 +52,90 @@ extern "C" {
     }                                                                          \
   }
 
+/**
+ * Macro for entity getters
+ */
+#define CAOSDB_ENTITY_GET(element, body_part)                                  \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_entity_get_##element(caosdb_entity_entity *entity,       \
+                                           char *out),                         \
+    {                                                                          \
+      auto *wrapped_entity =                                                   \
+        static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);         \
+      body_part return 0;                                                      \
+    })
+
+/**
+ * Macro for entity setters
+ */
+#define CAOSDB_ENTITY_SET(element, value, body_part)                           \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_entity_set_##element(caosdb_entity_entity *entity,       \
+                                           const char *value),                 \
+    {                                                                          \
+      auto *wrapped_entity =                                                   \
+        static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);         \
+      body_part return 0;                                                      \
+    })
+
+/**
+ * Macro for property getters
+ */
+#define CAOSDB_PROPERTY_GET(element, body_part)                                \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_property_get_##element(caosdb_entity_property *property, \
+                                             char *out),                       \
+    {                                                                          \
+      auto *wrapped_property =                                                 \
+        static_cast<caosdb::entity::Property *>(property->wrapped_property);   \
+      body_part return 0;                                                      \
+    })
+
+/**
+ * Macro for property setters
+ */
+#define CAOSDB_PROPERTY_SET(element, value, body_part)                         \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_property_set_##element(caosdb_entity_property *property, \
+                                             const char *value),               \
+    {                                                                          \
+      auto *wrapped_property =                                                 \
+        static_cast<caosdb::entity::Property *>(property->wrapped_property);   \
+      body_part return 0;                                                      \
+    })
+
+/**
+ * Macro for parent getters
+ */
+#define CAOSDB_PARENT_GET(element, body_part)                                  \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_parent_get_##element(caosdb_entity_parent *parent,       \
+                                           char *out),                         \
+    {                                                                          \
+      auto *wrapped_parent =                                                   \
+        static_cast<caosdb::entity::Parent *>(parent->wrapped_parent);         \
+      body_part return 0;                                                      \
+    })
+
+/**
+ * Macro for parent setters
+ */
+#define CAOSDB_PARENT_SET(element, value, body_part)                           \
+  ERROR_RETURN_CODE(                                                           \
+    GENERIC_ERROR,                                                             \
+    int caosdb_entity_parent_set_##element(caosdb_entity_parent *parent,       \
+                                           const char *value),                 \
+    {                                                                          \
+      auto *wrapped_parent =                                                   \
+        static_cast<caosdb::entity::Parent *>(parent->wrapped_parent);         \
+      body_part return 0;                                                      \
+    })
+
 int caosdb_constants_LIBCAOSDB_VERSION_MAJOR() {
   return caosdb::LIBCAOSDB_VERSION_MAJOR;
 }
@@ -56,6 +164,10 @@ const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE() {
   return caosdb::COMPATIBLE_SERVER_VERSION_PRE_RELEASE;
 }
 
+int caosdb_status_code_OTHER_CLIENT_ERROR() {
+  return caosdb::StatusCode::OTHER_CLIENT_ERROR;
+}
+
 const char *caosdb_utility_get_env_var(const char *name, const char *fallback) {
   return caosdb::utility::get_env_var(name, fallback);
 }
@@ -69,9 +181,13 @@ ERROR_RETURN_CODE(GENERIC_ERROR,
                     caosdb_connection_certificate_provider *out,
                     const char *path),
                   {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
                     out->wrapped_certificate_provider =
                       new caosdb::configuration::PemFileCertificateProvider(
                         std::string(path));
+                    out->_deletable = true;
                     return 0;
                   })
 
@@ -80,8 +196,11 @@ ERROR_RETURN_CODE(
   int caosdb_connection_delete_certificate_provider(
     caosdb_connection_certificate_provider *provider),
   {
-    delete static_cast<caosdb::configuration::CertificateProvider *>(
-      provider->wrapped_certificate_provider);
+    if (provider->_deletable) {
+      delete static_cast<caosdb::configuration::CertificateProvider *>(
+        provider->wrapped_certificate_provider);
+    }
+    provider->_deletable = false;
     return 0;
   })
 
@@ -90,20 +209,28 @@ ERROR_RETURN_CODE(GENERIC_ERROR,
                     caosdb_authentication_authenticator *out,
                     const char *username, const char *password),
                   {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
                     out->wrapped_authenticator =
                       new caosdb::authentication::PlainPasswordAuthenticator(
                         std::string(username), std::string(password));
+                    out->_deletable = true;
                     return 0;
                   })
 
-ERROR_RETURN_CODE(GENERIC_ERROR,
-                  int caosdb_authentication_delete_authenticator(
-                    caosdb_authentication_authenticator *authenticator),
-                  {
-                    delete static_cast<caosdb::authentication::Authenticator *>(
-                      authenticator->wrapped_authenticator);
-                    return 0;
-                  })
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_authentication_delete_authenticator(
+    caosdb_authentication_authenticator *authenticator),
+  {
+    if (authenticator->_deletable) {
+      delete static_cast<caosdb::authentication::Authenticator *>(
+        authenticator->wrapped_authenticator);
+    }
+    authenticator->_deletable = false;
+    return 0;
+  })
 
 ERROR_RETURN_CODE(
   GENERIC_ERROR,
@@ -112,6 +239,9 @@ ERROR_RETURN_CODE(
     const int port, caosdb_authentication_authenticator *authenticator,
     caosdb_connection_certificate_provider *provider),
   {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
     auto host_str = std::string(host);
     if (authenticator != nullptr && provider != nullptr) {
       auto wrapped_provider =
@@ -141,6 +271,7 @@ ERROR_RETURN_CODE(
       out->wrapped_connection_configuration =
         new caosdb::configuration::TlsConnectionConfiguration(host_str, port);
     }
+    out->_deletable = true;
     return 0;
   })
 
@@ -150,8 +281,12 @@ ERROR_RETURN_CODE(
     caosdb_connection_connection_configuration *out, const char *host,
     const int port),
   {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
     out->wrapped_connection_configuration =
       new caosdb::configuration::InsecureConnectionConfiguration(host, port);
+    out->_deletable = true;
     return 0;
   })
 
@@ -160,8 +295,11 @@ ERROR_RETURN_CODE(
   int caosdb_connection_delete_connection_configuration(
     caosdb_connection_connection_configuration *configuration),
   {
-    delete static_cast<caosdb::configuration::ConnectionConfiguration *>(
-      configuration->wrapped_connection_configuration);
+    if (configuration->_deletable) {
+      delete static_cast<caosdb::configuration::ConnectionConfiguration *>(
+        configuration->wrapped_connection_configuration);
+    }
+    configuration->_deletable = false;
     return 0;
   })
 
@@ -171,10 +309,14 @@ ERROR_RETURN_CODE(
     caosdb_connection_connection *out,
     const caosdb_connection_connection_configuration *configuration),
   {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
     caosdb::configuration::ConnectionConfiguration *config =
       static_cast<caosdb::configuration::ConnectionConfiguration *>(
         configuration->wrapped_connection_configuration);
     out->wrapped_connection = new caosdb::connection::Connection(*config);
+    out->_deletable = true;
     return 0;
   })
 
@@ -182,8 +324,11 @@ ERROR_RETURN_CODE(GENERIC_ERROR,
                   int caosdb_connection_delete_connection(
                     caosdb_connection_connection *connection),
                   {
-                    delete static_cast<caosdb::connection::Connection *>(
-                      connection->wrapped_connection);
+                    if (connection->_deletable) {
+                      delete static_cast<caosdb::connection::Connection *>(
+                        connection->wrapped_connection);
+                    }
+                    connection->_deletable = false;
                     return 0;
                   })
 
@@ -226,8 +371,12 @@ ERROR_RETURN_CODE(
   int caosdb_connection_connection_manager_get_default_connection(
     caosdb_connection_connection *out),
   {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
     out->wrapped_connection =
       caosdb::connection::ConnectionManager::GetDefaultConnection().get();
+    out->_deletable = false;
     return 0;
   })
 
@@ -235,10 +384,454 @@ ERROR_RETURN_CODE(GENERIC_ERROR,
                   int caosdb_connection_connection_manager_get_connection(
                     caosdb_connection_connection *out, const char *name),
                   {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
                     out->wrapped_connection =
                       caosdb::connection::ConnectionManager::GetConnection(
                         std::string(name))
                         .get();
+                    // managed by the connection manager now, so not
+                    // to be deleted manually
+                    out->_deletable = false;
+                    return 0;
+                  })
+
+/****************************************************************************
+ * ENTITY STUFF AND TRANSACTIONS
+ ****************************************************************************/
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_connection_connection_create_transaction(
+                    caosdb_connection_connection *connection,
+                    caosdb_transaction_transaction *out),
+                  {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
+                    auto *wrapped_connection =
+                      static_cast<caosdb::connection::Connection *>(
+                        connection->wrapped_connection);
+                    out->wrapped_transaction =
+                      wrapped_connection->CreateTransaction().release();
+                    out->_deletable = true;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_delete_transaction(
+                    caosdb_transaction_transaction *transaction),
+                  {
+                    if (transaction->_deletable) {
+                      delete static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    }
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_retrieve_by_id(
+                    caosdb_transaction_transaction *transaction,
+                    const char *id),
+                  {
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    return wrapped_transaction->RetrieveById(std::string(id));
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_retrieve_by_ids(
+                    caosdb_transaction_transaction *transaction,
+                    const char *ids[], int length),
+                  {
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    return wrapped_transaction->RetrieveById(ids, ids + length);
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_query(
+                    caosdb_transaction_transaction *transaction,
+                    const char *query),
+                  {
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    return wrapped_transaction->Query(std::string(query));
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_execute(
+                    caosdb_transaction_transaction *transaction),
+                  {
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    wrapped_transaction->ExecuteAsynchronously();
+                    auto status = wrapped_transaction->WaitForIt();
+                    return status.GetCode();
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_get_result_set(
+                    caosdb_transaction_transaction *transaction,
+                    caosdb_transaction_result_set *out),
+                  {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    out->wrapped_result_set =
+                      (void *)(&(wrapped_transaction->GetResultSet()));
+                    out->_deletable = false;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_transaction_get_count_result(
+                    caosdb_transaction_transaction *transaction, long *out),
+                  {
+                    auto *wrapped_transaction =
+                      static_cast<caosdb::transaction::Transaction *>(
+                        transaction->wrapped_transaction);
+                    long cr(wrapped_transaction->GetCountResult());
+                    *out = cr;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_result_set_at(
+                    caosdb_transaction_result_set *result_set,
+                    caosdb_entity_entity *entity, int index),
+                  {
+                    auto *wrapped_result_set =
+                      static_cast<caosdb::transaction::MultiResultSet *>(
+                        result_set->wrapped_result_set);
+                    entity->wrapped_entity =
+                      wrapped_result_set->mutable_at(index);
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_transaction_result_set_size(
+                    caosdb_transaction_result_set *result_set, int *out),
+                  {
+                    auto *wrapped_result_set =
+                      static_cast<caosdb::transaction::MultiResultSet *>(
+                        result_set->wrapped_result_set);
+                    int size(wrapped_result_set->size());
+                    *out = size;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_entity_create_entity(caosdb_entity_entity *out), {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
+                    out->wrapped_entity = new caosdb::entity::Entity();
+                    out->_deletable = true;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_entity_delete_entity(caosdb_entity_entity *out), {
+                    if (out->_deletable) {
+                      delete static_cast<caosdb::entity::Entity *>(
+                        out->wrapped_entity);
+                    }
+                    out->_deletable = false;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR, int caosdb_entity_create_property(caosdb_entity_property *out),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    out->wrapped_property = new caosdb::entity::Property();
+    out->_deletable = true;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR, int caosdb_entity_delete_property(caosdb_entity_property *out),
+  {
+    if (out->_deletable) {
+      delete static_cast<caosdb::entity::Property *>(out->wrapped_property);
+    }
+    out->_deletable = false;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_entity_create_parent(caosdb_entity_parent *out), {
+                    if (out->_deletable) {
+                      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+                    }
+                    out->wrapped_parent = new caosdb::entity::Parent();
+                    out->_deletable = true;
+                    return 0;
+                  })
+
+ERROR_RETURN_CODE(GENERIC_ERROR,
+                  int caosdb_entity_delete_parent(caosdb_entity_parent *out), {
+                    if (out->_deletable) {
+                      delete static_cast<caosdb::entity::Parent *>(
+                        out->wrapped_parent);
+                    }
+                    out->_deletable = false;
                     return 0;
                   })
+
+CAOSDB_ENTITY_GET(id, strcpy(out, wrapped_entity->GetId().c_str());)
+CAOSDB_ENTITY_GET(role, strcpy(out, wrapped_entity->GetRole().c_str());)
+CAOSDB_ENTITY_GET(name, strcpy(out, wrapped_entity->GetName().c_str());)
+CAOSDB_ENTITY_GET(description,
+                  strcpy(out, wrapped_entity->GetDescription().c_str());)
+CAOSDB_ENTITY_GET(datatype, strcpy(out, wrapped_entity->GetDatatype().c_str());)
+CAOSDB_ENTITY_GET(value, strcpy(out, wrapped_entity->GetValue().c_str());)
+CAOSDB_ENTITY_GET(unit, strcpy(out, wrapped_entity->GetUnit().c_str());)
+CAOSDB_ENTITY_GET(version_id,
+                  strcpy(out, wrapped_entity->GetVersionId().c_str());)
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_errors_size(caosdb_entity_entity *entity,
+                                           int *out),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    *out = wrapped_entity->GetErrors().size();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_error(caosdb_entity_entity *entity,
+                                     caosdb_entity_message *out, int index),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    out->wrapped_message = wrapped_entity->GetErrors().mutable_at(index);
+    out->_deletable = false;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_warnings_size(caosdb_entity_entity *entity,
+                                             int *out),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    *out = wrapped_entity->GetWarnings().size();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_warning(caosdb_entity_entity *entity,
+                                       caosdb_entity_message *out, int index),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    out->wrapped_message = wrapped_entity->GetWarnings().mutable_at(index);
+    out->_deletable = false;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_infos_size(caosdb_entity_entity *entity,
+                                          int *out),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    *out = wrapped_entity->GetInfos().size();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_info(caosdb_entity_entity *entity,
+                                    caosdb_entity_message *out, int index),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    out->wrapped_message = wrapped_entity->GetInfos().mutable_at(index);
+    out->_deletable = false;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_properties_size(caosdb_entity_entity *entity,
+                                               int *out),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    *out = wrapped_entity->GetProperties().size();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_property(caosdb_entity_entity *entity,
+                                        caosdb_entity_property *out, int index),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    out->wrapped_property = wrapped_entity->GetProperties().mutable_at(index);
+    out->_deletable = false;
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_parents_size(caosdb_entity_entity *entity,
+                                            int *out),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    *out = wrapped_entity->GetParents().size();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_get_parent(caosdb_entity_entity *entity,
+                                      caosdb_entity_parent *out, int index),
+  {
+    if (out->_deletable) {
+      return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR;
+    }
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    out->wrapped_parent = wrapped_entity->GetParents().mutable_at(index);
+    out->_deletable = false;
+    return 0;
+  })
+
+CAOSDB_PARENT_GET(id, strcpy(out, wrapped_parent->GetId().c_str());)
+CAOSDB_PARENT_GET(name, strcpy(out, wrapped_parent->GetName().c_str());)
+CAOSDB_PARENT_GET(description,
+                  strcpy(out, wrapped_parent->GetDescription().c_str());)
+
+CAOSDB_PROPERTY_GET(id, strcpy(out, wrapped_property->GetId().c_str());)
+CAOSDB_PROPERTY_GET(name, strcpy(out, wrapped_property->GetName().c_str());)
+CAOSDB_PROPERTY_GET(description,
+                    strcpy(out, wrapped_property->GetDescription().c_str());)
+CAOSDB_PROPERTY_GET(importance,
+                    strcpy(out, wrapped_property->GetImportance().c_str());)
+CAOSDB_PROPERTY_GET(datatype,
+                    strcpy(out, wrapped_property->GetDatatype().c_str());)
+CAOSDB_PROPERTY_GET(unit, strcpy(out, wrapped_property->GetUnit().c_str());)
+CAOSDB_PROPERTY_GET(value, strcpy(out, wrapped_property->GetValue().c_str());)
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_message_get_code(caosdb_entity_message *message, int *out),
+  {
+    auto *wrapped_message =
+      static_cast<caosdb::entity::Message *>(message->wrapped_message);
+    *out = wrapped_message->GetCode();
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_message_get_description(caosdb_entity_message *message,
+                                            char *out),
+  {
+    auto *wrapped_message =
+      static_cast<caosdb::entity::Message *>(message->wrapped_message);
+    strcpy(out, wrapped_message->GetDescription().c_str());
+    return 0;
+  })
+
+CAOSDB_ENTITY_SET(role, role, wrapped_entity->SetRole(std::string(role));)
+CAOSDB_ENTITY_SET(name, name, wrapped_entity->SetName(std::string(name));)
+CAOSDB_ENTITY_SET(description, description,
+                  wrapped_entity->SetDescription(std::string(description));)
+CAOSDB_ENTITY_SET(datatype, datatype,
+                  wrapped_entity->SetDatatype(std::string(datatype));)
+CAOSDB_ENTITY_SET(unit, unit, wrapped_entity->SetUnit(std::string(unit));)
+CAOSDB_ENTITY_SET(value, value, wrapped_entity->SetValue(std::string(value));)
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_append_parent(caosdb_entity_entity *entity,
+                                         caosdb_entity_parent *parent),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    auto *wrapped_parent =
+      static_cast<caosdb::entity::Parent *>(parent->wrapped_parent);
+    wrapped_entity->AppendParent(*wrapped_parent);
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_remove_parent(caosdb_entity_entity *entity,
+                                         int index),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    wrapped_entity->RemoveParent(index);
+    return 0;
+  })
+
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_append_property(caosdb_entity_entity *entity,
+                                           caosdb_entity_property *property),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    auto *wrapped_property =
+      static_cast<caosdb::entity::Property *>(property->wrapped_property);
+    wrapped_entity->AppendProperty(*wrapped_property);
+    return 0;
+  })
+ERROR_RETURN_CODE(
+  GENERIC_ERROR,
+  int caosdb_entity_entity_remove_property(caosdb_entity_entity *entity,
+                                           int index),
+  {
+    auto *wrapped_entity =
+      static_cast<caosdb::entity::Entity *>(entity->wrapped_entity);
+    wrapped_entity->RemoveProperty(index);
+    return 0;
+  })
+
+CAOSDB_PARENT_SET(id, id, wrapped_parent->SetId(std::string(id));)
+CAOSDB_PARENT_SET(name, name, wrapped_parent->SetName(std::string(name));)
+
+CAOSDB_PROPERTY_SET(name, name, wrapped_property->SetName(std::string(name));)
+CAOSDB_PROPERTY_SET(id, id, wrapped_property->SetId(std::string(id));)
+CAOSDB_PROPERTY_SET(datatype, datatype,
+                    wrapped_property->SetDatatype(std::string(datatype));)
+CAOSDB_PROPERTY_SET(importance, importance,
+                    wrapped_property->SetImportance(std::string(importance));)
+CAOSDB_PROPERTY_SET(unit, unit, wrapped_property->SetUnit(std::string(unit));)
+CAOSDB_PROPERTY_SET(value, value,
+                    wrapped_property->SetValue(std::string(value));)
 }
diff --git a/test/test_ccaosdb.cpp b/test/test_ccaosdb.cpp
index abb06dd..1009cc5 100644
--- a/test/test_ccaosdb.cpp
+++ b/test/test_ccaosdb.cpp
@@ -21,12 +21,15 @@
  */
 
 #include "caosdb/configuration.h"
+#include "caosdb/status_code.h"    // for StatusCode
 #include "caosdb_test_utility.h"   // for EXPECT_THROW_MESSAGE, TEST_DATA_DIR
 #include "ccaosdb.h"               // for caosdb_utility_get_env_var
+#include <cstring>                 // for strcmp
 #include <gtest/gtest-message.h>   // for Message
 #include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl
 #include <gtest/gtest_pred_impl.h> // for Test, TestInfo, EXPECT_EQ, TEST
-#include <string>                  // for allocator
+#include <iostream>
+#include <string> // for allocator
 
 class test_ccaosdb : public ::testing::Test {
 protected:
@@ -47,6 +50,11 @@ TEST_F(test_ccaosdb, test_get_env_var) {
   EXPECT_EQ("fall-back", some_var);
 }
 
+TEST_F(test_ccaosdb, test_other_client_error) {
+  EXPECT_EQ(caosdb_status_code_OTHER_CLIENT_ERROR(),
+            caosdb::StatusCode::OTHER_CLIENT_ERROR);
+}
+
 TEST_F(test_ccaosdb, test_get_default_connection) {
   caosdb_connection_connection out;
 
@@ -61,3 +69,349 @@ TEST_F(test_ccaosdb, test_get_connection) {
                                                       "local-caosdb-admin");
   EXPECT_TRUE(out.wrapped_connection);
 }
+
+TEST_F(test_ccaosdb, test_execute_transaction) {
+  caosdb_connection_connection connection;
+  caosdb_connection_connection_manager_get_connection(&connection,
+                                                      "local-caosdb-admin");
+
+  caosdb_transaction_transaction transaction;
+  caosdb_connection_connection_create_transaction(&connection, &transaction);
+
+  EXPECT_TRUE(transaction.wrapped_transaction);
+
+  int return_code(
+    caosdb_transaction_transaction_retrieve_by_id(&transaction, "some_id"));
+  EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON);
+
+  return_code = caosdb_transaction_transaction_execute(&transaction);
+  EXPECT_EQ(return_code, caosdb::StatusCode::CONNECTION_ERROR);
+
+  return_code = caosdb_transaction_delete_transaction(&transaction);
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_transaction_transaction multi_transaction;
+  caosdb_connection_connection_create_transaction(&connection,
+                                                  &multi_transaction);
+
+  // We explicitely want to define a C-style array here, so we disable
+  // linting
+  const char *ids[] = {"id1", "id2", "id3"}; // NOLINT
+  return_code =
+    caosdb_transaction_transaction_retrieve_by_ids(&multi_transaction, ids, 3);
+  EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON);
+
+  return_code = caosdb_transaction_delete_transaction(&multi_transaction);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_multi_retrieve) {
+  std::cout << "Entering test_multi_retrieve ..." << std::endl;
+  caosdb_connection_connection connection;
+  caosdb_connection_connection_manager_get_connection(&connection,
+                                                      "local-caosdb-admin");
+
+  std::cout << "Creating transaction" << std::endl;
+  caosdb_transaction_transaction multi_transaction;
+  caosdb_connection_connection_create_transaction(&connection,
+                                                  &multi_transaction);
+
+  // We explicitely want to define a C-style array here, so we disable
+  // linting
+  const char *ids[] = {"id1", "id2", "id3"}; // NOLINT
+  std::cout << "Adding mutli retrieval ..." << std::endl;
+  int return_code(
+    caosdb_transaction_transaction_retrieve_by_ids(&multi_transaction, ids, 3));
+  EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON);
+
+  std::cout << "Deleting transaction ..." << std::endl;
+  return_code = caosdb_transaction_delete_transaction(&multi_transaction);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_query) {
+  caosdb_connection_connection connection;
+  caosdb_connection_connection_manager_get_connection(&connection,
+                                                      "local-caosdb-admin");
+
+  caosdb_transaction_transaction transaction;
+  caosdb_connection_connection_create_transaction(&connection, &transaction);
+
+  int return_code(caosdb_transaction_transaction_query(
+    &transaction, "FIND ENTITY WITH id=123"));
+  EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON);
+
+  return_code = caosdb_transaction_delete_transaction(&transaction);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_entity) {
+  caosdb_entity_entity entity;
+
+  int return_code(caosdb_entity_create_entity(&entity));
+  EXPECT_EQ(return_code, 0);
+
+  // cannot be created again without deletion
+  return_code = caosdb_entity_create_entity(&entity);
+  EXPECT_EQ(return_code, caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR);
+
+  // deletion and re-creation is ok
+  return_code = caosdb_entity_delete_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_create_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+
+  // In-depth check for one pair of setter and getter, just compare
+  // the strings for the rest
+  return_code = caosdb_entity_entity_set_name(&entity, "length");
+  EXPECT_EQ(return_code, 0);
+  char out[255] = {"a"}; // NOLINT
+  return_code = caosdb_entity_entity_get_name(&entity, out);
+  EXPECT_EQ(return_code, 0);
+  EXPECT_EQ(strcmp(out, "length"), 0);
+
+  caosdb_entity_entity_set_role(&entity, "Property");
+  caosdb_entity_entity_get_role(&entity, out);
+  EXPECT_EQ(strcmp(out, "Property"), 0);
+
+  caosdb_entity_entity_set_description(&entity, "The length of an object");
+  caosdb_entity_entity_get_description(&entity, out);
+  EXPECT_EQ(strcmp(out, "The length of an object"), 0);
+
+  caosdb_entity_entity_set_datatype(&entity, "DOUBLE");
+  caosdb_entity_entity_get_datatype(&entity, out);
+  EXPECT_EQ(strcmp(out, "DOUBLE"), 0);
+
+  caosdb_entity_entity_set_unit(&entity, "m");
+  caosdb_entity_entity_get_unit(&entity, out);
+  EXPECT_EQ(strcmp(out, "m"), 0);
+
+  caosdb_entity_entity_set_value(&entity, "5.0");
+  caosdb_entity_entity_get_value(&entity, out);
+  EXPECT_EQ(strcmp(out, "5.0"), 0);
+
+  return_code = caosdb_entity_delete_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_parent) {
+  caosdb_entity_parent parent;
+
+  int return_code(caosdb_entity_create_parent(&parent));
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_parent_set_id(&parent, "some_id");
+  caosdb_entity_parent_set_name(&parent, "some_name");
+
+  char out[255] = {"a"}; // NOLINT
+  caosdb_entity_parent_get_id(&parent, out);
+  EXPECT_EQ(strcmp(out, "some_id"), 0);
+
+  caosdb_entity_parent_get_name(&parent, out);
+  EXPECT_EQ(strcmp(out, "some_name"), 0);
+
+  return_code = caosdb_entity_delete_parent(&parent);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_property) {
+  caosdb_entity_property property;
+
+  int return_code(caosdb_entity_create_property(&property));
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_property_set_id(&property, "some_id");
+  caosdb_entity_property_set_name(&property, "some_name");
+  caosdb_entity_property_set_datatype(&property, "some_datatype");
+  caosdb_entity_property_set_importance(&property, "some_importance");
+  caosdb_entity_property_set_unit(&property, "some_unit");
+  caosdb_entity_property_set_value(&property, "some_value");
+
+  char out[255] = {"a"}; // NOLINT
+  caosdb_entity_property_get_id(&property, out);
+  EXPECT_EQ(strcmp(out, "some_id"), 0);
+
+  caosdb_entity_property_get_name(&property, out);
+  EXPECT_EQ(strcmp(out, "some_name"), 0);
+
+  caosdb_entity_property_get_datatype(&property, out);
+  EXPECT_EQ(strcmp(out, "some_datatype"), 0);
+
+  caosdb_entity_property_get_importance(&property, out);
+  EXPECT_EQ(strcmp(out, "some_importance"), 0);
+
+  caosdb_entity_property_get_unit(&property, out);
+  EXPECT_EQ(strcmp(out, "some_unit"), 0);
+
+  caosdb_entity_property_get_value(&property, out);
+  EXPECT_EQ(strcmp(out, "some_value"), 0);
+
+  return_code = caosdb_entity_delete_property(&property);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_entity_with_parent_and_property) {
+  std::cout << "Creating objects ... " << std::endl;
+  caosdb_entity_parent input_parent;
+  int return_code(caosdb_entity_create_parent(&input_parent));
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_parent_set_id(&input_parent, "parent_id");
+  caosdb_entity_parent_set_name(&input_parent, "parent_name");
+
+  caosdb_entity_property input_property;
+  return_code = caosdb_entity_create_property(&input_property);
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_property_set_id(&input_property, "property_id");
+  caosdb_entity_property_set_name(&input_property, "property_name");
+  caosdb_entity_property_set_datatype(&input_property, "property_datatype");
+  caosdb_entity_property_set_value(&input_property, "property_value");
+
+  caosdb_entity_entity entity;
+  return_code = caosdb_entity_create_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+
+  std::cout << "Appending parent and property ..." << std::endl;
+  return_code = caosdb_entity_entity_append_parent(&entity, &input_parent);
+  EXPECT_EQ(return_code, 0);
+
+  return_code = caosdb_entity_entity_append_property(&entity, &input_property);
+  EXPECT_EQ(return_code, 0);
+
+  std::cout << "Counting parents and properties ..." << std::endl;
+  int count[] = {0}; // NOLINT
+  return_code = caosdb_entity_entity_get_parents_size(&entity, count);
+  EXPECT_EQ(return_code, 0);
+  EXPECT_EQ(*count, 1);
+
+  return_code = caosdb_entity_entity_get_properties_size(&entity, count);
+  EXPECT_EQ(return_code, 0);
+  EXPECT_EQ(*count, 1);
+
+  char in[255] = {"a"};  // NOLINT
+  char out[255] = {"b"}; // NOLINT
+
+  std::cout << "Comparing ..." << std::endl;
+  // cannot assign an already assigned property
+  return_code = caosdb_entity_entity_get_property(&entity, &input_property, 0);
+  EXPECT_EQ(return_code, caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR);
+  caosdb_entity_property output_property;
+  return_code = caosdb_entity_entity_get_property(&entity, &output_property, 0);
+  std::cout << "Got output property." << std::endl;
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_property_get_id(&input_property, in);
+  std::cout << "Got input id." << std::endl;
+  caosdb_entity_property_get_id(&output_property, out);
+  std::cout << "Got output id." << std::endl;
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  caosdb_entity_property_get_name(&input_property, in);
+  caosdb_entity_property_get_name(&output_property, out);
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  caosdb_entity_property_get_datatype(&input_property, in);
+  caosdb_entity_property_get_datatype(&output_property, out);
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  caosdb_entity_property_get_value(&input_property, in);
+  caosdb_entity_property_get_value(&output_property, out);
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  std::cout << "Comparing parent..." << std::endl;
+  caosdb_entity_parent output_parent;
+  return_code = caosdb_entity_entity_get_parent(&entity, &output_parent, 0);
+  std::cout << "Got output parent." << std::endl;
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_parent_get_id(&input_parent, in);
+  caosdb_entity_parent_get_id(&output_parent, out);
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  caosdb_entity_parent_get_name(&input_parent, in);
+  caosdb_entity_parent_get_name(&output_parent, out);
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  // Delete everything
+  std::cout << "Deleting ..." << std::endl;
+  return_code = caosdb_entity_delete_parent(&input_parent);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_delete_property(&input_property);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_delete_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+
+  // This tests the `_deletable` flag. The wrapped cpp objects of
+  // `output_parent` and `output_property` are owned by the entity, so
+  // they have been deleted together with the entity. With a wrong
+  // `_deletable` flag, the following would cause segfaults.
+  //
+  return_code = caosdb_entity_delete_parent(&output_parent);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_delete_property(&output_property);
+  EXPECT_EQ(return_code, 0);
+}
+
+TEST_F(test_ccaosdb, test_remove_property) {
+  caosdb_entity_entity entity;
+  int return_code(caosdb_entity_create_entity(&entity));
+  EXPECT_EQ(return_code, 0);
+
+  // Create two properties with names
+  caosdb_entity_property in_prop_1;
+  return_code = caosdb_entity_create_property(&in_prop_1);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_property_set_name(&in_prop_1, "Property 1");
+  EXPECT_EQ(return_code, 0);
+
+  caosdb_entity_property in_prop_2;
+  return_code = caosdb_entity_create_property(&in_prop_2);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_property_set_name(&in_prop_2, "Property 2");
+  EXPECT_EQ(return_code, 0);
+
+  // Append them
+  return_code = caosdb_entity_entity_append_property(&entity, &in_prop_1);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_entity_append_property(&entity, &in_prop_2);
+  EXPECT_EQ(return_code, 0);
+
+  // Delete one and see that the number of properties decreases by one
+  int count[] = {0}; // NOLINT
+  return_code = caosdb_entity_entity_get_properties_size(&entity, count);
+  EXPECT_EQ(return_code, 0);
+  EXPECT_EQ(*count, 2);
+
+  return_code = caosdb_entity_entity_remove_property(&entity, 0);
+  EXPECT_EQ(return_code, 0);
+
+  return_code = caosdb_entity_entity_get_properties_size(&entity, count);
+  EXPECT_EQ(return_code, 0);
+  EXPECT_EQ(*count, 1);
+
+  caosdb_entity_property out_prop;
+  return_code = caosdb_entity_entity_get_property(&entity, &out_prop, 0);
+  EXPECT_EQ(return_code, 0);
+
+  char in[255] = {"a"};  // NOLINT
+  char out[255] = {"b"}; // NOLINT
+
+  // Deleted the first property, so the second one should remain.
+  return_code = caosdb_entity_property_get_name(&in_prop_2, in);
+  EXPECT_EQ(return_code, 0);
+  return_code = caosdb_entity_property_get_name(&out_prop, out);
+  EXPECT_EQ(return_code, 0);
+
+  EXPECT_EQ(strcmp(in, out), 0);
+
+  // Delete everything we have created
+  return_code = caosdb_entity_delete_property(&in_prop_2);
+  EXPECT_EQ(return_code, 0);
+
+  return_code = caosdb_entity_delete_property(&in_prop_1);
+  EXPECT_EQ(return_code, 0);
+
+  return_code = caosdb_entity_delete_entity(&entity);
+  EXPECT_EQ(return_code, 0);
+}
diff --git a/test/test_entity.cpp b/test/test_entity.cpp
index 6ff7004..3d2343b 100644
--- a/test/test_entity.cpp
+++ b/test/test_entity.cpp
@@ -32,8 +32,9 @@
 #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 <memory>                                // for allocator, shared_ptr
-#include <string>                                // for operator+, string
+#include <iostream>
+#include <memory> // for allocator, shared_ptr
+#include <string> // for operator+, string
 
 namespace caosdb::entity {
 using caosdb::entity::v1alpha1::IdResponse;
@@ -287,6 +288,111 @@ TEST(test_entity, test_add_directory_path) {
   EXPECT_EQ(entity.SetLocalPath("./"), StatusCode::PATH_IS_A_DIRECTORY);
 }
 
+TEST(test_entity, test_remove_property) {
+  Entity entity;
+  for (int i = 0; i < 5; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+
+    Property property;
+    property.SetName(name);
+    EXPECT_EQ(property.GetName(), name);
+
+    entity.AppendProperty(property);
+    EXPECT_EQ(property.GetName(), name);
+
+    // not initializing the cache
+  }
+  ASSERT_EQ(entity.GetProperties().size(), 5);
+  for (int i = 5; i < 10; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+
+    Property property;
+    property.SetName(name);
+    EXPECT_EQ(property.GetName(), name);
+
+    entity.AppendProperty(property);
+    EXPECT_EQ(property.GetName(), name);
+
+    // initializing the cache
+    const auto &property_2 = entity.GetProperties().at(i);
+    EXPECT_EQ(property_2.GetName(), name);
+  }
+
+  ASSERT_EQ(entity.GetProperties().size(), 10);
+
+  for (int i = 5; i < 10; i++) {
+    // double checking the cache
+    auto name = "PROPERTY-" + std::to_string(i);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+
+  // Remove at index 3
+  // P0,P1,P2,P3,P4,P5,P6,P7,P8,P9
+  //          ^
+  // P0,P1,P2,   P4,P5,P6,P7,P8,P9
+  entity.RemoveProperty(3);
+
+  // Remove at index 6
+  // P0,P1,P2,   P4,P5,P6,P7,P8,P9
+  //                      ^
+  // P0,P1,P2,   P4,P5,P6,   P8,P9
+  entity.RemoveProperty(6);
+  ASSERT_EQ(entity.GetProperties().size(), 8);
+
+  // AppendProperty another property
+  // P0,P1,P2,   P4,P5,P6,   P8,P9
+  //                               ^
+  // P0,P1,P2,   P4,P5,P6,   P8,P9,P10
+  Property property10;
+  property10.SetName("PROPERTY-10");
+  entity.AppendProperty(property10);
+  ASSERT_EQ(entity.GetProperties().size(), 9);
+
+  std::cout << "[" << std::endl;
+  for (int i = 0; i < 9; i++) {
+    std::cout << "  " << entity.GetProperties().at(i).GetName() << ",\n";
+  }
+  std::cout << "]" << std::endl;
+
+  for (int i = 0; i < 3; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+  for (int i = 3; i < 6; i++) {
+    auto name = "PROPERTY-" + std::to_string(i + 1);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+  for (int i = 6; i < 9; i++) {
+    auto name = "PROPERTY-" + std::to_string(i + 2);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+}
+
+TEST(test_entity, test_property_iterator) {
+  Entity entity;
+  for (int i = 0; i < 5; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+
+    Property property;
+    property.SetName(name);
+    EXPECT_EQ(property.GetName(), name);
+
+    entity.AppendProperty(property);
+  }
+  ASSERT_EQ(entity.GetProperties().size(), 5);
+  int counter = 0;
+  for (auto &property : entity.GetProperties()) {
+    auto name = "PROPERTY-" + std::to_string(counter);
+    EXPECT_EQ(property.GetName(), name);
+    counter++;
+  }
+  EXPECT_EQ(counter, 5);
+}
+
 TEST(test_entity, test_description) {
   Entity entity;
   Property property;
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index e378796..70ba13a 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -27,9 +27,9 @@
 #include "caosdb/transaction_handler.h" // for MultiTransactionResponse
 #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 <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 <stdexcept>                    // for out_of_range
 #include <string>                       // for string, basic_string
@@ -40,6 +40,7 @@ namespace caosdb::transaction {
 using caosdb::configuration::InsecureConnectionConfiguration;
 using caosdb::connection::Connection;
 using caosdb::entity::Entity;
+using caosdb::exceptions::ConnectionError;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using caosdb::entity::v1alpha1::RetrieveResponse;
 
-- 
GitLab