diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index 831200c0ff7fdafbd265bb4778b62f51003b6836..08a7be6b0f6f2b42752ff78f2a004e7196e9e757 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -454,7 +454,9 @@ class Property { public: explicit inline Property(ProtoProperty *other) : value(Value(other->mutable_value())), data_type(DataType(other->mutable_data_type())), - wrapped(other){}; + wrapped(other) { + FixValue(); + }; Property(); /** @@ -551,6 +553,12 @@ public: friend class RepeatedPtrFieldWrapper<Property, ProtoProperty>; private: + /** + * Workaround until non-string values are supported by the server. + * + * Only has an effect if there is a DataType. + */ + auto FixValue() -> void; static auto CreateProtoProperty() -> ProtoProperty *; Value value; DataType data_type; @@ -602,6 +610,7 @@ public: errors.wrapped = CreateMessagesField(); warnings.wrapped = CreateMessagesField(); infos.wrapped = CreateMessagesField(); + FixValue(); }; explicit Entity(IdResponse *id_response); explicit Entity(ProtoEntity *other) @@ -614,11 +623,13 @@ public: errors.wrapped = CreateMessagesField(); warnings.wrapped = CreateMessagesField(); infos.wrapped = CreateMessagesField(); + FixValue(); }; explicit inline Entity(EntityResponse *response) : Entity(response->release_entity()) { errors.wrapped->Swap(response->mutable_errors()); warnings.wrapped->Swap(response->mutable_warnings()); infos.wrapped->Swap(response->mutable_infos()); + FixValue(); }; [[nodiscard]] inline auto GetId() const noexcept -> const std::string & { return wrapped->id(); }; @@ -744,6 +755,13 @@ private: auto SetId(const std::string &id) -> void; auto SetVersionId(const std::string &id) -> void; + /** + * Workaround until non-string values are supported by the server. + * + * Only has an effect if there is a DataType. + */ + auto FixValue() -> void; + private: FileDescriptor file_descriptor; ProtoEntity *wrapped; diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp index 16ea5dbfc99b173efdac9bdbbfe0d87a2e289fee..21ad8af8da006b14fb13860278758aec15376fe4 100644 --- a/src/caosdb/entity.cpp +++ b/src/caosdb/entity.cpp @@ -20,10 +20,12 @@ * */ #include "caosdb/entity.h" -#include "caosdb/data_type.h" // for DataType -#include "caosdb/entity/v1alpha1/main.pb.h" // for Messages -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/value.h" // for Value +#include "caosdb/data_type.h" // for DataType +#include "caosdb/entity/v1alpha1/main.pb.h" // for Messages +#include "caosdb/exceptions.h" +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/value.h" // for Value +#include <boost/algorithm/string.hpp> #include <google/protobuf/arena.h> // for Arena #include <google/protobuf/generated_message_util.h> // for Arena::Create... #include <new> // for operator new @@ -42,6 +44,12 @@ using google::protobuf::Arena; Messages::~Messages() = default; +// Forward declarations /////////////////////////////////////////////////////// + +template <typename E> auto FixValueImpl(E *ent) -> void; + +// Parent ///////////////////////////////////////////////////////////////////// + Parent::Parent() : wrapped(Parent::CreateProtoParent()) { // TODO(fspreck) Re-enable once we have decided how to attach // messages to parents. @@ -66,7 +74,7 @@ auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); } return this->wrapped->description(); } -Property::Property() : Property(Property::CreateProtoProperty()) {} +Property::Property() : Property(Property::CreateProtoProperty()) { FixValue(); } auto Property::CreateProtoProperty() -> ProtoProperty * { return Arena::CreateMessage<ProtoProperty>(get_arena()); @@ -152,6 +160,9 @@ auto Property::SetDataType(const std::string &new_data_type, bool list_type) -> return SetDataType(DataType(new_data_type, list_type)); } +auto Property::FixValue() -> void { FixValueImpl(this); } + +// Entity ///////////////////////////////////////////////////////////////////// [[nodiscard]] auto Entity::GetParents() const -> const Parents & { return parents; } auto Entity::AppendParent(const Parent &parent) -> void { this->parents.Append(parent); } @@ -174,9 +185,10 @@ Entity::Entity(IdResponse *id_response) : Entity() { this->errors.wrapped->Swap(id_response->mutable_errors()); this->warnings.wrapped->Swap(id_response->mutable_warnings()); this->infos.wrapped->Swap(id_response->mutable_infos()); + FixValue(); } -Entity::Entity() : Entity(Entity::CreateProtoEntity()) {} +Entity::Entity() : Entity(Entity::CreateProtoEntity()) { FixValue(); } auto Entity::CreateMessagesField() -> RepeatedPtrField<ProtoMessage> * { return Arena::CreateMessage<RepeatedPtrField<ProtoMessage>>(get_arena()); @@ -256,4 +268,98 @@ auto Entity::SetFilePath(const std::string &path) -> void { this->wrapped->mutable_file_descriptor()->set_path(path); } +auto Entity::FixValue() -> void { FixValueImpl(this); } + +// Utility functions ////////////////////////////////////////////////////////// + +template <typename E> auto FixValueImpl(E *ent) -> void { + const auto &dtype = ent->GetDataType(); + const auto &value = ent->GetValue(); + auto new_value = Value(); + if (value.IsNull() || !value.IsString()) { // Don't treat NULL and non-string values. + return; + } + if (value.IsList()) { // Also don't treat empty or non-string lists. + const auto &list = value.AsList(); + if (list.empty() || !list[0].IsString()) { + return; + } + } + auto atype = AtomicDataType::UNSPECIFIED; + if (dtype.IsList()) { // List Datatype + if (!value.IsList()) { + throw caosdb::exceptions::Exception(StatusCode::OTHER_CLIENT_ERROR, + "DataType is list, but Value is scalar."); + } + auto &list_type = dtype.AsList(); + atype = list_type.GetAtomicDataType(); + if (!list_type.IsListOfAtomic() // References, strings etc. need no treatment. + || atype == AtomicDataType::UNSPECIFIED || atype == AtomicDataType::TEXT || + atype == AtomicDataType::DATETIME) { + return; + } + if (atype == AtomicDataType::DOUBLE) { + std::vector<double> data; + for (auto &d : value.AsList()) { + data.push_back(std::stod(d.AsString())); + } + new_value = Value(data); + } else if (atype == AtomicDataType::INTEGER) { + std::vector<int64_t> data; + for (auto &d : value.AsList()) { + data.push_back(std::stol(d.AsString())); + } + new_value = Value(data); + } else if (atype == AtomicDataType::BOOLEAN) { + std::vector<bool> data; + for (auto &d : value.AsList()) { + const auto &bool_value = d.AsString(); + if (boost::to_upper_copy(bool_value) == "TRUE") { + data.push_back(true); + } else if (boost::to_upper_copy(bool_value) == "FALSE") { + data.push_back(false); + } else { + throw caosdb::exceptions::Exception(StatusCode::OTHER_CLIENT_ERROR, + "Boolean value is neither true nor false."); + } + } + new_value = Value(data); + } else { + std::cout << "Unhandled datatype: " << ent->ToString() << std::endl; + throw std::logic_error("Unhandled datatype"); + } + } else { // Scalar Datatype + if (value.IsList()) { + throw caosdb::exceptions::Exception(StatusCode::OTHER_CLIENT_ERROR, + "Value is list, but DataType is scalar."); + } + atype = dtype.AsAtomic(); + if (!dtype.IsAtomic() // References, strings etc. need no treatment. + || atype == AtomicDataType::UNSPECIFIED || atype == AtomicDataType::TEXT || + atype == AtomicDataType::DATETIME) { + return; + } + if (atype == AtomicDataType::DOUBLE) { + new_value = Value(std::stod(value.AsString())); + } else if (atype == AtomicDataType::INTEGER) { + new_value = Value(std::stol(value.AsString())); + } else if (atype == AtomicDataType::BOOLEAN) { + const auto &bool_value = value.AsString(); + if (boost::to_upper_copy(bool_value) == "TRUE") { + new_value = Value(true); + } else if (boost::to_upper_copy(bool_value) == "FALSE") { + new_value = Value(false); + } else { + throw caosdb::exceptions::Exception(StatusCode::OTHER_CLIENT_ERROR, + "Boolean value is neither true nor false."); + } + } else { + std::cout << "Unhandled datatype: " << ent->ToString() << std::endl; + throw std::logic_error("Unhandled datatype"); + } + } + + ent->SetValue(new_value); +} + } // namespace caosdb::entity