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