diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index 831200c0ff7fdafbd265bb4778b62f51003b6836..f93c3497ad64e6fb03186b92f2e5bc06f998d3fd 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,13 @@ 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 +611,7 @@ public: errors.wrapped = CreateMessagesField(); warnings.wrapped = CreateMessagesField(); infos.wrapped = CreateMessagesField(); + FixValue(); }; explicit Entity(IdResponse *id_response); explicit Entity(ProtoEntity *other) @@ -614,11 +624,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 +756,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..e927f686f26bb4c6f11e78d974e7ad00ff32ce3d 100644 --- a/src/caosdb/entity.cpp +++ b/src/caosdb/entity.cpp @@ -20,10 +20,12 @@ * */ #include "caosdb/entity.h" +#include "caosdb/exceptions.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 <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,13 @@ 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 +75,9 @@ 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 +163,11 @@ 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 +190,12 @@ 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 +275,104 @@ 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<long> 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()) { + 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) { + 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