diff --git a/src/object_accessor.hpp b/src/object_accessor.hpp index e8924269..fd561187 100644 --- a/src/object_accessor.hpp +++ b/src/object_accessor.hpp @@ -22,10 +22,13 @@ #include "list.hpp" #include "object_schema.hpp" #include "object_store.hpp" +#include "results.hpp" #include "schema.hpp" #include "shared_realm.hpp" +#include "util/format.hpp" #include +#include namespace realm { @@ -103,6 +106,8 @@ namespace realm { static ValueType list_value_at_index(ContextType ctx, ValueType &val, size_t index); static ValueType from_list(ContextType ctx, List); + static ValueType from_results(ContextType ctx, Results); + // // Deprecated // @@ -125,9 +130,16 @@ namespace realm { const std::string property_name; }; - class MutationOutsideTransactionException : public std::runtime_error - { - public: + class ReadOnlyPropertyValueException : public std::runtime_error { + public: + ReadOnlyPropertyValueException(const std::string& object_type, const std::string& property_name, const std::string& message) + : std::runtime_error(message), object_type(object_type), property_name(property_name) {} + const std::string object_type; + const std::string property_name; + }; + + class MutationOutsideTransactionException : public std::runtime_error { + public: MutationOutsideTransactionException(std::string message) : std::runtime_error(message) {} }; @@ -217,6 +229,10 @@ namespace realm { } break; } + case PropertyType::LinkingObjects: + throw ReadOnlyPropertyValueException(m_object_schema->name, property.name, + util::format("Cannot modify read-only property '%1.%2'", + m_object_schema->name, property.name)); } } @@ -259,6 +275,14 @@ namespace realm { auto arrayObjectSchema = m_realm->config().schema->find(property.object_type); return Accessor::from_list(ctx, std::move(List(m_realm, *arrayObjectSchema, static_cast(m_row.get_linklist(column))))); } + case PropertyType::LinkingObjects: { + auto target_object_schema = m_realm->config().schema->find(property.object_type); + auto link_property = target_object_schema->property_for_name(property.link_origin_property_name); + TableRef table = ObjectStore::table_for_object_type(m_realm->read_group(), target_object_schema->name); + auto tv = m_row.get_table()->get_backlink_view(m_row.get_index(), table.get(), link_property->table_column); + Results results(m_realm, *m_object_schema, std::move(tv), {}); + return Accessor::from_results(ctx, std::move(results)); + } } } @@ -303,7 +327,7 @@ namespace realm { // populate Object object(realm, object_schema, table->get(row_index)); - for (const Property &prop : object_schema.properties) { + for (const Property& prop : object_schema.persisted_properties) { if (created || !prop.is_primary) { if (Accessor::dict_has_value_for_key(ctx, value, prop.name)) { object.set_property_value_impl(ctx, prop, Accessor::dict_value_for_key(ctx, value, prop.name), try_update); diff --git a/src/object_store.cpp b/src/object_store.cpp index d44d9674..141b6970 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -565,11 +565,24 @@ MissingPropertyException::MissingPropertyException(std::string const& object_typ InvalidNullabilityException::InvalidNullabilityException(std::string const& object_type, Property const& property) : ObjectSchemaPropertyException(object_type, property) { - if (property.type == PropertyType::Object) { - m_what = util::format("'Object' property '%1' must be nullable.", property.name); - } - else { - m_what = util::format("Array or Mixed property '%1' cannot be nullable", property.name); + switch (property.type) { + case PropertyType::Object: + m_what = util::format("'Object' property '%1' must be nullable.", property.name); + break; + case PropertyType::Any: + case PropertyType::Array: + case PropertyType::LinkingObjects: + m_what = util::format("Property '%1' of type '%2' cannoy be nullable", + property.name, string_for_property_type(property.type)); + break; + case PropertyType::Int: + case PropertyType::Bool: + case PropertyType::Data: + case PropertyType::Date: + case PropertyType::Float: + case PropertyType::Double: + case PropertyType::String: + REALM_ASSERT(false); } } @@ -627,3 +640,24 @@ DuplicatePrimaryKeysException::DuplicatePrimaryKeysException(std::string const& m_what = util::format("Duplicate primary keys for object '%1'.", object_type); } +InvalidLinkingObjectsPropertyException::InvalidLinkingObjectsPropertyException(Type error_type, std::string const& object_type, Property const& property) +: ObjectSchemaPropertyException(object_type, property) +{ + switch (error_type) { + case Type::OriginPropertyDoesNotExist: + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' does not exist", + property.object_type, property.link_origin_property_name, + object_type, property.name); + break; + case Type::OriginPropertyIsNotALink: + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' is not a link", + property.object_type, property.link_origin_property_name, + object_type, property.name); + break; + case Type::OriginPropertyInvalidLinkTarget: + m_what = util::format("Property '%1.%2' declared as origin of linking objects property '%3.%4' links to a different class", + property.object_type, property.link_origin_property_name, + object_type, property.name); + break; + } +} diff --git a/src/object_store.hpp b/src/object_store.hpp index 670cbf53..3ecfc956 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -240,6 +240,16 @@ namespace realm { private: std::string m_primary_key; }; + + class InvalidLinkingObjectsPropertyException : public ObjectSchemaPropertyException { + public: + enum class Type { + OriginPropertyDoesNotExist, + OriginPropertyIsNotALink, + OriginPropertyInvalidLinkTarget, + }; + InvalidLinkingObjectsPropertyException(Type error_type, std::string const& object_type, Property const& property); + }; } #endif /* defined(REALM_OBJECT_STORE_HPP) */ diff --git a/src/property.hpp b/src/property.hpp index edd22a75..7c26c750 100644 --- a/src/property.hpp +++ b/src/property.hpp @@ -33,12 +33,14 @@ namespace realm { Date = 8, Object = 12, Array = 13, + LinkingObjects = 14, }; struct Property { std::string name; PropertyType type; std::string object_type; + std::string link_origin_property_name; bool is_primary = false; bool is_indexed = false; bool is_nullable = false; @@ -55,11 +57,13 @@ namespace realm { #if __GNUC__ < 5 // GCC 4.9 does not support C++14 braced-init with NSDMIs - Property(std::string name="", PropertyType type=PropertyType::Int, std::string object_type="", + Property(std::string name="", PropertyType type=PropertyType::Int, + std::string object_type="", std::string link_origin_property_name="", bool is_primary=false, bool is_indexed=false, bool is_nullable=false) : name(std::move(name)) , type(type) , object_type(std::move(object_type)) + , link_origin_property_name(std::move(link_origin_property_name)) , is_primary(is_primary) , is_indexed(is_indexed) , is_nullable(is_nullable) diff --git a/src/schema.cpp b/src/schema.cpp index e794fb67..79451ec7 100644 --- a/src/schema.cpp +++ b/src/schema.cpp @@ -72,13 +72,36 @@ void Schema::validate() const for (auto const& prop : all_properties) { // check object_type existence - if (!prop.object_type.empty() && find(prop.object_type) == end()) { - exceptions.emplace_back(MissingObjectTypeException(object.name, prop)); + if (!prop.object_type.empty()) { + auto it = find(prop.object_type); + if (it == end()) { + exceptions.emplace_back(MissingObjectTypeException(object.name, prop)); + } + // validate linking objects property. + else if (!prop.link_origin_property_name.empty()) { + using ErrorType = InvalidLinkingObjectsPropertyException::Type; + util::Optional error; + + const Property *origin_property = it->property_for_name(prop.link_origin_property_name); + if (!origin_property) { + error = ErrorType::OriginPropertyDoesNotExist; + } + else if (origin_property->type != PropertyType::Object && origin_property->type != PropertyType::Array) { + error = ErrorType::OriginPropertyIsNotALink; + } + else if (origin_property->object_type != object.name) { + error = ErrorType::OriginPropertyInvalidLinkTarget; + } + + if (error) { + exceptions.emplace_back(InvalidLinkingObjectsPropertyException(*error, object.name, prop)); + } + } } // check nullablity if (prop.is_nullable) { - if (prop.type == PropertyType::Array || prop.type == PropertyType::Any) { + if (prop.type == PropertyType::Array || prop.type == PropertyType::Any || prop.type == PropertyType::LinkingObjects) { exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); } } diff --git a/tests/results.cpp b/tests/results.cpp index c8e82ba9..001a144a 100644 --- a/tests/results.cpp +++ b/tests/results.cpp @@ -24,13 +24,13 @@ TEST_CASE("[results] notifications") { config.schema = std::make_unique(Schema{ {"object", "", { {"value", PropertyType::Int}, - {"link", PropertyType::Object, "linked to object", false, false, true} + {"link", PropertyType::Object, "linked to object", "", false, false, true} }}, {"other object", "", { {"value", PropertyType::Int} }}, {"linking object", "", { - {"link", PropertyType::Object, "object", false, false, true} + {"link", PropertyType::Object, "object", "", false, false, true} }}, {"linked to object", "", { {"value", PropertyType::Int} diff --git a/tests/transaction_log_parsing.cpp b/tests/transaction_log_parsing.cpp index fd35503b..4ff97415 100644 --- a/tests/transaction_log_parsing.cpp +++ b/tests/transaction_log_parsing.cpp @@ -114,7 +114,7 @@ TEST_CASE("Transaction log parsing") { config.schema = std::make_unique(Schema{ {"table", "", { {"unindexed", PropertyType::Int}, - {"indexed", PropertyType::Int, "", false, true} + {"indexed", PropertyType::Int, "", "", false, true} }}, }); auto r = Realm::get_shared_realm(config); @@ -807,7 +807,7 @@ TEST_CASE("DeepChangeChecker") { config.schema = std::make_unique(Schema{ {"table", "", { {"int", PropertyType::Int}, - {"link", PropertyType::Object, "table", false, false, true}, + {"link", PropertyType::Object, "table", "", false, false, true}, {"array", PropertyType::Array, "table"} }}, });