diff --git a/include/caosdb/value.h b/include/caosdb/value.h index 6031e75dbd69c262cc69079c63323d916cec223d..48e8580cee0e3b2a008d00cdc44d69e5fb6b3713 100644 --- a/include/caosdb/value.h +++ b/include/caosdb/value.h @@ -25,9 +25,10 @@ #include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Message #include "caosdb/logging.h" #include <google/protobuf/util/json_util.h> // for MessageToJson... -#include <memory> // for unique_ptr -#include <string> // for string -#include <vector> // for vector +#include <map> +#include <memory> // for unique_ptr +#include <string> // for string +#include <vector> // for vector #define LIST_VALUE_CONSTRUCTOR(TYPE, SETTER) \ explicit inline Value(const std::vector<TYPE> &values) : ProtoMessageWrapper<ProtoValue>() { \ @@ -37,7 +38,9 @@ } namespace caosdb::entity { +using caosdb::utility::get_arena; using caosdb::utility::ProtoMessageWrapper; +using google::protobuf::Arena; using ProtoSpecialValue = caosdb::entity::v1alpha1::SpecialValue; using ProtoValue = caosdb::entity::v1alpha1::Value; using ProtoScalarValue = caosdb::entity::v1alpha1::ScalarValue; @@ -46,6 +49,8 @@ using ScalarValueCase = caosdb::entity::v1alpha1::ScalarValue::ScalarValueCase; class Entity; class Property; +class ScalarValue; +class Value; // Represents special values which are otherwise hard to tranfer via protobuf. enum SpecialValue { @@ -55,46 +60,225 @@ enum SpecialValue { EMPTY_STRING = ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING, }; -class ScalarValue : public ProtoMessageWrapper<ProtoScalarValue> { +class AbstractValue { public: + /** + * Virtual destructor. + */ + virtual ~AbstractValue(){}; + /** + * Return true iff the value is a NULL value (NULL in the CaosDB sense). + */ + [[nodiscard]] virtual auto IsNull() const noexcept -> bool = 0; + /** + * Return true iff the value is best represented in C++ types as a + * std::string. + * + * If true, clients may call GetAsString to receive a std::string + * representation of the value. + */ + [[nodiscard]] virtual auto IsString() const noexcept -> bool = 0; + /** + * Return true iff the value is best represented in C++ types as a + * bool. + * + * If true, clients may call GetAsBool to receive a bool + * representation of the value. + */ + [[nodiscard]] virtual auto IsBool() const noexcept -> bool = 0; + /** + * Return true iff the value is best represented in C++ types as a + * double. + * + * If true, clients may call GetAsDouble to receive a double + * representation of the value. + */ + [[nodiscard]] virtual auto IsDouble() const noexcept -> bool = 0; + /** + * Return true iff the value is best represented in C++ types as an + * int64_t. + * + * If true, clients may call GetAsInt64 to receive an int64_t + * representation of the value. + */ + [[nodiscard]] virtual auto IsInt64() const noexcept -> bool = 0; + /** + * Return true iff the value is best represented in C++ types as a + * std::vector<ScalarValue>. + * + * If true, clients may call GetAsVector to receive a + * std::vector<ScalarValue> representation of the value. + */ + [[nodiscard]] virtual auto IsVector() const noexcept -> bool = 0; + + /** + * Return a std::string representation of this value. + * + * Clients should call IsString before calling this function in order to + * assure that this value is indeed best represented by a std::string. + * + * The return value is undefined if IsString is false. + */ + [[nodiscard]] virtual auto GetAsString() const noexcept -> const std::string & = 0; + /** + * Return a bool representation of this value. + * + * Clients should call IsBool before calling this function in order to + * assure that this value is indeed best represented by a bool. + * + * The return value is undefined if IsBool is false. + */ + [[nodiscard]] virtual auto GetAsBool() const noexcept -> bool = 0; + /** + * Return a double representation of this value. + * + * Clients should call IsDouble before calling this function in order to + * assure that this value is indeed best represented by a double. + * + * The return value is undefined if IsDouble is false. + */ + [[nodiscard]] virtual auto GetAsDouble() const noexcept -> double = 0; + /** + * Return an int64_t representation of this value. + * + * Clients should call IsInt64 before calling this function in order to + * assure that this value is indeed best represented by an int64_t. + * + * The return value is undefined if IsInt64 is false. + */ + [[nodiscard]] virtual auto GetAsInt64() const noexcept -> int64_t = 0; + /** + * Return a std::vector<ScalarValue> representation of this value. + * + * Clients should call IsVector before calling this function in order to + * assure that this value is indeed best represented by a + * std::vector<ScalarValue>. + * + * The return value is undefined if IsVector is false. + */ + [[nodiscard]] virtual auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & = 0; + friend class Value; + +protected: + [[nodiscard]] virtual auto GetProtoValue() const noexcept -> const ProtoValue * = 0; +}; + +class ScalarValue : public AbstractValue, public ProtoMessageWrapper<ProtoScalarValue> { +public: + /** + * Destructor. + */ + inline ~ScalarValue(){}; + /** + * Copy constructor. + */ + inline ScalarValue(const ScalarValue &original) : ScalarValue() { + this->wrapped->CopyFrom(*original.wrapped); + } + /** + * Move constructor. + */ + inline ScalarValue(ScalarValue &&other) : ScalarValue(std::move(other.wrapped)){}; + /** + * Copy assignment operator. + */ + inline auto operator=(const ScalarValue &original) -> ScalarValue & { + if (&original != this) { + this->wrapped->CopyFrom(*original.wrapped); + } + return *this; + } + /** + * Move assignment operator. + */ + inline auto operator=(ScalarValue &&other) -> ScalarValue & { + if (&other != this) { + this->wrapped = std::move(other.wrapped); + } + return *this; + } + inline ScalarValue(ProtoScalarValue *wrapped) : ProtoMessageWrapper<ProtoScalarValue>(wrapped) {} + [[nodiscard]] inline auto IsNull() const noexcept -> bool { + return (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue && + this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED); + } [[nodiscard]] inline auto IsString() const noexcept -> bool { return (this->wrapped->scalar_value_case() == ScalarValueCase::kStringValue) || (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue && this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING); } - [[nodiscard]] inline auto AsString() const noexcept -> const std::string & { + [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & { return this->wrapped->string_value(); } [[nodiscard]] inline auto IsDouble() const noexcept -> bool { return (this->wrapped->scalar_value_case() == ScalarValueCase::kDoubleValue); } - [[nodiscard]] inline auto AsDouble() const noexcept -> double { + [[nodiscard]] inline auto GetAsDouble() const noexcept -> double { return this->wrapped->double_value(); } - [[nodiscard]] inline auto IsInteger() const noexcept -> bool { + [[nodiscard]] inline auto IsInt64() const noexcept -> bool { return (this->wrapped->scalar_value_case() == ScalarValueCase::kIntegerValue); } - [[nodiscard]] inline auto AsInteger() const noexcept -> int64_t { + [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t { return this->wrapped->integer_value(); } [[nodiscard]] inline auto IsBool() const noexcept -> bool { return (this->wrapped->scalar_value_case() == ScalarValueCase::kBooleanValue); } - [[nodiscard]] inline auto AsBool() const noexcept -> bool { + [[nodiscard]] inline auto GetAsBool() const noexcept -> bool { return this->wrapped->boolean_value(); } + [[nodiscard]] auto IsVector() const noexcept -> bool { + // Always false b/c a scalar value is never a collection. + return false; + } + [[nodiscard]] auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & { + // Always return an empty vector. + static const std::vector<ScalarValue> empty_collection; + return empty_collection; + } + friend class Value; + +protected: + [[nodiscard]] auto GetProtoValue() const noexcept -> const ProtoValue * { + if (this->proto_value == nullptr) { + this->proto_value = Arena::CreateMessage<ProtoValue>(get_arena()); + this->proto_value->mutable_scalar_value()->CopyFrom(*this->wrapped); + } + return this->proto_value; + }; + inline ScalarValue() : ProtoMessageWrapper<ProtoScalarValue>() {} + +private: + mutable ProtoValue *proto_value; }; -class Value : public ProtoMessageWrapper<ProtoValue> { +class Value : public AbstractValue, public ProtoMessageWrapper<ProtoValue> { public: + /** + * Copy constructor. + */ + inline Value(const Value &original) : Value() { this->wrapped->CopyFrom(*original.wrapped); } + /** + * Move constructor. + */ + inline Value(Value &&other) : Value(std::move(other.wrapped)) {} + /** + * Destructor. + */ + inline ~Value() {} inline Value() : ProtoMessageWrapper<ProtoValue>() { // has NULL_VALUE now } + explicit inline Value(const ScalarValue &value) : Value() { + this->wrapped->mutable_scalar_value()->CopyFrom(*value.wrapped); + } + explicit inline Value(const AbstractValue &value) : Value(value.GetProtoValue()) {} explicit inline Value(ProtoValue *wrapped) : ProtoMessageWrapper<ProtoValue>(wrapped) {} explicit inline Value(const std::string &value) : ProtoMessageWrapper<ProtoValue>() { this->wrapped->mutable_scalar_value()->set_string_value(value); @@ -121,7 +305,10 @@ public: LIST_VALUE_CONSTRUCTOR(bool, set_boolean_value) [[nodiscard]] inline auto IsNull() const noexcept -> bool { - return this->wrapped->value_case() == ValueCase::VALUE_NOT_SET; + return this->wrapped->value_case() == ValueCase::VALUE_NOT_SET || + (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kSpecialValue && + this->wrapped->scalar_value().special_value() == + ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED); } [[nodiscard]] inline auto IsString() const noexcept -> bool { @@ -134,7 +321,7 @@ public: } return false; } - [[nodiscard]] inline auto AsString() const noexcept -> const std::string & { + [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & { return this->wrapped->scalar_value().string_value(); ; } @@ -146,18 +333,18 @@ public: } return false; } - [[nodiscard]] inline auto AsDouble() const noexcept -> double { + [[nodiscard]] inline auto GetAsDouble() const noexcept -> double { return this->wrapped->scalar_value().double_value(); } - [[nodiscard]] inline auto IsInteger() const noexcept -> bool { + [[nodiscard]] inline auto IsInt64() const noexcept -> bool { if (this->wrapped->value_case() == ValueCase::kScalarValue) { return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kIntegerValue); } return false; } - [[nodiscard]] inline auto AsInteger() const noexcept -> int64_t { + [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t { return this->wrapped->scalar_value().integer_value(); } @@ -168,31 +355,55 @@ public: } return false; } - [[nodiscard]] inline auto AsBool() const noexcept -> bool { + [[nodiscard]] inline auto GetAsBool() const noexcept -> bool { return this->wrapped->scalar_value().boolean_value(); } - [[nodiscard]] inline auto IsList() const noexcept -> bool { + [[nodiscard]] inline auto IsVector() const noexcept -> bool { return this->wrapped->value_case() == ValueCase::kListValues; } - [[nodiscard]] inline auto AsList() const noexcept -> const std::vector<ScalarValue> & { - if (!IsList()) { + [[nodiscard]] inline auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & { + if (!IsVector()) { // create empty list - this->list_values = std::make_unique<std::vector<ScalarValue>>(); + this->collection_values = std::make_unique<std::vector<ScalarValue>>(); } - if (this->list_values == nullptr) { - this->list_values = std::make_unique<std::vector<ScalarValue>>(); + if (this->collection_values == nullptr) { + this->collection_values = std::make_unique<std::vector<ScalarValue>>(); for (auto &scalar : *(this->wrapped->mutable_list_values()->mutable_values())) { - this->list_values->push_back(ScalarValue(&scalar)); + this->collection_values->push_back(ScalarValue(&scalar)); } } - return *(this->list_values); + return *(this->collection_values); } + /** + * Return true if the underlying Protobuf messages have the same + * serialization. + */ inline auto operator==(const Value &other) const noexcept -> bool { return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString(); } + /** + * Copy assignment operator. + */ + inline auto operator=(const Value &other) -> Value & { + if (&other != this) { + this->wrapped->CopyFrom(*other.wrapped); + } + return *this; + } + + /** + * Move assignment operator. + */ + inline auto operator=(Value &&other) -> Value & { + if (&other != this) { + this->wrapped = std::move(other.wrapped); + } + return *this; + } + inline auto ToString() const noexcept -> const std::string { CAOSDB_DEBUG_MESSAGE_STRING(*wrapped, out) return out; @@ -201,8 +412,11 @@ public: friend class Entity; friend class Property; +protected: + [[nodiscard]] auto GetProtoValue() const noexcept -> const ProtoValue * { return this->wrapped; }; + private: - mutable std::unique_ptr<std::vector<ScalarValue>> list_values; + mutable std::unique_ptr<std::vector<ScalarValue>> collection_values; }; } // namespace caosdb::entity diff --git a/src/ccaosdb.cpp b/src/ccaosdb.cpp index 7dd555bcada9df80623f43511f75b1c9f2e64ee5..cb0278f83791efbe9558bd512baa69388ba50bb7 100644 --- a/src/ccaosdb.cpp +++ b/src/ccaosdb.cpp @@ -23,6 +23,7 @@ #include "caosdb/connection.h" #include "caosdb/constants.h" #include "caosdb/data_type.h" // for DataType, AtomicDat... +#include "caosdb/entity.h" #include "caosdb/value.h" #include "caosdb/utility.h" #include "caosdb/status_code.h" @@ -47,7 +48,7 @@ extern "C" { #define WRAPPED_MESSAGE_CAST(name) static_cast<caosdb::entity::Message *>(name->wrapped_message) -#define WRAPPED_VALUE_CAST(name) static_cast<caosdb::entity::Value *>(name->wrapped_value) +#define WRAPPED_VALUE_CAST(name) static_cast<caosdb::entity::AbstractValue *>(name->wrapped_value) #define ENUM_NAME_FROM_VALUE(arg, etype) \ caosdb::utility::getEnumNameFromValue<caosdb::entity::etype>(arg)