From 5449a8314cb07f8c29b8f2b5fb7642751f3cf0b6 Mon Sep 17 00:00:00 2001
From: Daniel <d.hornung@indiscale.com>
Date: Tue, 24 Aug 2021 15:13:08 +0200
Subject: [PATCH] FIX: Workaround: manually parse and convert non-string
 values.

---
 include/caosdb/entity.h |  21 ++++++-
 src/caosdb/entity.cpp   | 123 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 141 insertions(+), 3 deletions(-)

diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index 831200c..f93c349 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 16ea5db..e927f68 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
-- 
GitLab