Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not allow migrations with asymmetric tables #5603

Merged
merged 7 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
* Fixed a segfault in sync compiled by MSVC 2022. ([#5557](https://github.com/realm/realm-core/pull/5557), since 12.1.0)
* Fix a data race when opening a flexible sync Realm (since v12.1.0).
* Fixed a missing backlink removal when setting a Mixed from a TypedLink to null or any other non-link value. Users may have seen exception of "key not found" or assertion failures such as `mixed.hpp:165: [realm-core-12.1.0] Assertion failed: m_type` when removing the destination link object. ([#5574](https://github.com/realm/realm-core/pull/5573), since the introduction of Mixed in v11.0.0)
* Asymmetric sync now works with embedded objects. (Issue [#5565](https://github.com/realm/realm-core/issues/5565), since 12.1.0)
* Asymmetric sync now works with embedded objects. (Issue [#5565](https://github.com/realm/realm-core/issues/5565), since v12.1.0)
* Fixed an issue on Windows that would cause high CPU usage by the sync client when there are no active sync sessions. (Issue [#5591](https://github.com/realm/realm-core/issues/5591), since the introduction of Sync support for Windows)

* Fixed a bug that prevented the detection of tables being changed to or from asymmetric during migrations. ([#5603](https://github.com/realm/realm-core/pull/5603), since v12.1.0)

### Breaking changes
* `realm_sync_before_client_reset_func_t` and `realm_sync_after_client_reset_func_t` in the C API now return a boolean value to indicate whether the callback succeeded or not, which signals to the sync client that a fatal error occurred. (PR [#5564](https://github.com/realm/realm-core/pull/5564))

Expand Down
2 changes: 1 addition & 1 deletion evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ tasks:

- name: object-store-tests
tags: [ "test_suite", "for_pull_requests" ]
exec_timeout_secs: 1800
exec_timeout_secs: 2100
commands:
- func: "compile"
# If we need to start a local copy of baas, do it in the background here in a separate script.
Expand Down
2 changes: 2 additions & 0 deletions src/realm.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ typedef struct realm_class_info {
typedef enum realm_class_flags {
RLM_CLASS_NORMAL = 0,
RLM_CLASS_EMBEDDED = 1,
RLM_CLASS_ASYMMETRIC = 2,
RLM_CLASS_MASK = 3,
} realm_class_flags_e;

typedef enum realm_property_flags {
Expand Down
12 changes: 4 additions & 8 deletions src/realm/exec/realm_trawler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <realm/array.hpp>
#include <realm/column_type.hpp>
#include <realm/data_type.hpp>
#include <realm/table.hpp>

#include <iostream>
#include <fstream>
Expand Down Expand Up @@ -268,8 +269,7 @@ class Table : public Array {
}
if (size() > 12) {
auto flags = get_val(12);
m_is_embedded = (flags & 0x3) == 1;
m_is_asymmetric = (flags & 0x3) == 2;
m_table_type = static_cast<realm::Table::Type>(flags & 0x3);
}
}
}
Expand Down Expand Up @@ -301,8 +301,7 @@ class Table : public Array {
Array m_column_colkeys;
Array m_opposite_table;
realm::ColKey m_pk_col;
bool m_is_embedded = false;
bool m_is_asymmetric = false;
realm::Table::Type m_table_type = realm::Table::Type::TopLevel;
};

class Group : public Array {
Expand Down Expand Up @@ -455,10 +454,7 @@ std::ostream& operator<<(std::ostream& ostr, const Group& g)

void Table::print_columns(const Group& group) const
{
if (m_is_embedded)
std::cout << " <embedded>" << std::endl;
if (m_is_asymmetric)
std::cout << " <asymmetric>" << std::endl;
std::cout << " <" << m_table_type << ">" << std::endl;
for (unsigned i = 0; i < m_column_names.size(); i++) {
auto type = realm::ColumnType(m_column_types.get_val(i) & 0xFFFF);
auto attr = realm::ColumnAttr(m_column_attributes.get_val(i));
Expand Down
20 changes: 15 additions & 5 deletions src/realm/object-store/c_api/conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,11 +423,21 @@ static inline realm_class_info_t to_capi(const ObjectSchema& o)
info.num_properties = o.persisted_properties.size();
info.num_computed_properties = o.computed_properties.size();
info.key = o.table_key.value;
if (o.is_embedded) {
info.flags = RLM_CLASS_EMBEDDED;
}
else {
info.flags = RLM_CLASS_NORMAL;
switch (o.table_type) {
case ObjectSchema::ObjectType::Embedded: {
info.flags = RLM_CLASS_EMBEDDED;
break;
}
case ObjectSchema::ObjectType::TopLevelAsymmetric: {
info.flags = RLM_CLASS_ASYMMETRIC;
break;
}
case ObjectSchema::ObjectType::TopLevel: {
info.flags = RLM_CLASS_NORMAL;
break;
}
default:
REALM_TERMINATE(util::format("Invalid table type: %1", uint8_t(o.table_type)).c_str());
}
return info;
}
Expand Down
2 changes: 1 addition & 1 deletion src/realm/object-store/c_api/schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ RLM_API realm_schema_t* realm_schema_new(const realm_class_info_t* classes, size
ObjectSchema object_schema;
object_schema.name = class_info.name;
object_schema.primary_key = class_info.primary_key;
object_schema.is_embedded = ObjectSchema::IsEmbedded{bool(class_info.flags & RLM_CLASS_EMBEDDED)};
object_schema.table_type = static_cast<ObjectSchema::ObjectType>(class_info.flags & RLM_CLASS_MASK);
object_schema.persisted_properties.reserve(class_info.num_properties);
object_schema.computed_properties.reserve(class_info.num_computed_properties);

Expand Down
8 changes: 4 additions & 4 deletions src/realm/object-store/object_accessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
auto table = realm->read_group().get_table(object_schema.table_key);

// Asymmetric objects cannot be updated through Object::create.
if (object_schema.is_asymmetric) {
if (object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
REALM_ASSERT(!policy.update);
REALM_ASSERT(!current_obj);
REALM_ASSERT(object_schema.primary_key_property());
Expand Down Expand Up @@ -323,7 +323,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
if (!obj) {
if (current_obj)
obj = table->get_object(current_obj);
else if (object_schema.is_embedded)
else if (object_schema.table_type == ObjectSchema::ObjectType::Embedded)
obj = ctx.create_embedded_object();
else
obj = table->create_object();
Expand All @@ -334,7 +334,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
// KVO in Cocoa requires that the obj ivar on the wrapper object be set
// *before* we start setting the properties, so it passes in a pointer to
// that.
if (out_row && !object_schema.is_asymmetric)
if (out_row && object_schema.table_type != ObjectSchema::ObjectType::TopLevelAsymmetric)
*out_row = obj;
for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
auto& prop = object_schema.persisted_properties[i];
Expand All @@ -361,7 +361,7 @@ Object Object::create(ContextType& ctx, std::shared_ptr<Realm> const& realm, Obj
if (v)
object.set_property_value_impl(ctx, prop, *v, policy, is_default);
}
if (object_schema.is_asymmetric) {
if (object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
return Object{};
}
return object;
Expand Down
59 changes: 20 additions & 39 deletions src/realm/object-store/object_schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@

using namespace realm;

static_assert(uint8_t(ObjectSchema::ObjectType::TopLevel) == uint8_t(Table::Type::TopLevel) &&
uint8_t(ObjectSchema::ObjectType::Embedded) == uint8_t(Table::Type::Embedded) &&
uint8_t(ObjectSchema::ObjectType::TopLevelAsymmetric) == uint8_t(Table::Type::TopLevelAsymmetric),
"Values of 'ObjectSchema::ObjectType' and 'Table::Type' enums don't match");

ObjectSchema::ObjectSchema() = default;
ObjectSchema::~ObjectSchema() = default;

Expand All @@ -37,48 +42,25 @@ ObjectSchema::ObjectSchema(std::string name, std::initializer_list<Property> per
{
}

ObjectSchema::ObjectSchema(std::string name, IsEmbedded is_embedded,
std::initializer_list<Property> persisted_properties)
: ObjectSchema(std::move(name), is_embedded, persisted_properties, {})
{
}

ObjectSchema::ObjectSchema(std::string name, IsAsymmetric is_asymmetric,
ObjectSchema::ObjectSchema(std::string name, ObjectType table_type,
std::initializer_list<Property> persisted_properties)
: ObjectSchema(std::move(name), is_asymmetric, persisted_properties, {})
: ObjectSchema(std::move(name), table_type, persisted_properties, {})
{
}

ObjectSchema::ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias)
: ObjectSchema(std::move(name), IsEmbedded{false}, persisted_properties, computed_properties, name_alias)
{
}

ObjectSchema::ObjectSchema(std::string name, IsEmbedded is_embedded,
std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias)
: name(std::move(name))
, persisted_properties(persisted_properties)
, computed_properties(computed_properties)
, is_embedded(is_embedded)
, alias(name_alias)
: ObjectSchema(std::move(name), ObjectType::TopLevel, persisted_properties, computed_properties, name_alias)
{
for (auto const& prop : persisted_properties) {
if (prop.is_primary) {
primary_key = prop.name;
break;
}
}
}

ObjectSchema::ObjectSchema(std::string name, IsAsymmetric is_asymmetric,
ObjectSchema::ObjectSchema(std::string name, ObjectType table_type,
std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias)
: name(std::move(name))
, persisted_properties(persisted_properties)
, computed_properties(computed_properties)
, is_asymmetric(is_asymmetric)
, table_type(table_type)
, alias(name_alias)
{
for (auto const& prop : persisted_properties) {
Expand Down Expand Up @@ -153,8 +135,7 @@ ObjectSchema::ObjectSchema(Group const& group, StringData name, TableKey key)
table = ObjectStore::table_for_object_type(group, name);
}
table_key = table->get_key();
is_embedded = table->is_embedded();
is_asymmetric = table->is_asymmetric();
table_type = static_cast<ObjectSchema::ObjectType>(table->get_table_type());

size_t count = table->get_column_count();
ColKey pk_col = table->get_primary_key_column();
Expand Down Expand Up @@ -308,18 +289,19 @@ static void validate_property(Schema const& schema, ObjectSchema const& parent_o
string_for_property_type(prop.type), prop.object_type);
return;
}
if (is_set(prop.type) && it->is_embedded) {
if (is_set(prop.type) && it->table_type == ObjectSchema::ObjectType::Embedded) {
exceptions.emplace_back("Set property '%1.%2' cannot contain embedded object type '%3'. Set semantics are "
"not applicable to embedded objects.",
object_name, prop.name, prop.object_type);
return;
}
if (parent_object_schema.is_asymmetric && !it->is_embedded) {
if (parent_object_schema.table_type == ObjectSchema::ObjectType::TopLevelAsymmetric &&
it->table_type != ObjectSchema::ObjectType::Embedded) {
exceptions.emplace_back("Asymmetric table with property '%1.%2' of type '%3' cannot have an object type.",
object_name, prop.name, string_for_property_type(prop.type));
return;
}
if (it->is_asymmetric) {
if (it->table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
exceptions.emplace_back("Property '%1.%2' of type '%3' cannot be a link to an asymmetric object.",
object_name, prop.name, string_for_property_type(prop.type));
return;
Expand Down Expand Up @@ -426,14 +408,14 @@ void ObjectSchema::validate(Schema const& schema, std::vector<ObjectSchemaValida
validate_property(schema, *this, prop, &primary, exceptions);
}

if (!primary_key.empty() && is_embedded) {
if (!primary_key.empty() && table_type == ObjectSchema::ObjectType::Embedded) {
exceptions.emplace_back("Embedded object type '%1' cannot have a primary key.", name);
}
if (!primary_key.empty() && !primary && !primary_key_property()) {
exceptions.emplace_back("Specified primary key '%1.%2' does not exist.", name, primary_key);
}

if (for_sync && !is_embedded) {
if (for_sync && table_type != ObjectSchema::ObjectType::Embedded) {
if (primary_key.empty()) {
exceptions.emplace_back(util::format("There must be a primary key property named '_id' on a synchronized "
"Realm but none was found for type '%1'",
Expand All @@ -446,16 +428,15 @@ void ObjectSchema::validate(Schema const& schema, std::vector<ObjectSchemaValida
}
}

if (!for_sync && is_asymmetric) {
if (!for_sync && table_type == ObjectSchema::ObjectType::TopLevelAsymmetric) {
exceptions.emplace_back(util::format("Asymmetric table '%1' not allowed in a local Realm", name));
}
}

namespace realm {
bool operator==(ObjectSchema const& a, ObjectSchema const& b) noexcept
{
return std::tie(a.name, a.is_embedded, a.is_asymmetric, a.primary_key, a.persisted_properties,
a.computed_properties) == std::tie(b.name, b.is_embedded, b.is_asymmetric, b.primary_key,
b.persisted_properties, b.computed_properties);
return std::tie(a.name, a.table_type, a.primary_key, a.persisted_properties, a.computed_properties) ==
std::tie(b.name, b.table_type, b.primary_key, b.persisted_properties, b.computed_properties);
}
} // namespace realm
32 changes: 20 additions & 12 deletions src/realm/object-store/object_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
#ifndef REALM_OBJECT_SCHEMA_HPP
#define REALM_OBJECT_SCHEMA_HPP

#include <realm/object-store/util/tagged_bool.hpp>

#include <realm/keys.hpp>
#include <realm/string_data.hpp>

Expand All @@ -37,18 +35,16 @@ struct Property;

class ObjectSchema {
public:
using IsEmbedded = util::TaggedBool<class IsEmbeddedTag>;
using IsAsymmetric = util::TaggedBool<class IsAsymmetricTag>;
/// The type of tables supported by a realm.
/// Note: Enumeration value assignments must be kept in sync with <realm/table.hpp>.
enum class ObjectType : uint8_t { TopLevel = 0, Embedded = 0x1, TopLevelAsymmetric = 0x2 };

ObjectSchema();
ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties);
ObjectSchema(std::string name, IsEmbedded is_embedded, std::initializer_list<Property> persisted_properties);
ObjectSchema(std::string name, IsAsymmetric is_asymmetric, std::initializer_list<Property> persisted_properties);
ObjectSchema(std::string name, ObjectType table_type, std::initializer_list<Property> persisted_properties);
ObjectSchema(std::string name, std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias = "");
ObjectSchema(std::string name, IsEmbedded is_embedded, std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias = "");
ObjectSchema(std::string name, IsAsymmetric is_asymmetric, std::initializer_list<Property> persisted_properties,
ObjectSchema(std::string name, ObjectType table_type, std::initializer_list<Property> persisted_properties,
std::initializer_list<Property> computed_properties, std::string name_alias = "");
~ObjectSchema();

Expand All @@ -66,9 +62,7 @@ class ObjectSchema {
std::vector<Property> computed_properties;
std::string primary_key;
TableKey table_key;
// Cannot be both true at the same time.
IsEmbedded is_embedded = false;
IsAsymmetric is_asymmetric = false;
ObjectType table_type = ObjectType::TopLevel;
std::string alias;

Property* property_for_public_name(StringData public_name) noexcept;
Expand Down Expand Up @@ -96,6 +90,20 @@ class ObjectSchema {
private:
void set_primary_key_property() noexcept;
};

inline std::ostream& operator<<(std::ostream& o, ObjectSchema::ObjectType table_type)
{
switch (table_type) {
case ObjectSchema::ObjectType::TopLevel:
return o << "TopLevel";
case ObjectSchema::ObjectType::Embedded:
return o << "Embedded";
case ObjectSchema::ObjectType::TopLevelAsymmetric:
return o << "TopLevelAsymmetric";
}
return o << "Invalid table type: " << uint8_t(table_type);
}

} // namespace realm

#endif /* defined(REALM_OBJECT_SCHEMA_HPP) */
Loading