-
Notifications
You must be signed in to change notification settings - Fork 168
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
DeepChangeChecker: follow links in Mixed columns #4828
Changes from 4 commits
64d02a7
1a9532b
7a2f5b1
f91a9b1
fc9c24b
72060c9
746b83d
62cdcba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -276,10 +276,24 @@ bool DeepChangeChecker::check_outgoing_links(Table const& table, ObjKey obj_key, | |
return do_check_for_collection_modifications(obj, outgoing_link_column, filtered_columns, depth); | ||
} | ||
|
||
ObjKey dst_key = obj.get<ObjKey>(outgoing_link_column); | ||
ConstTableRef dst_table; | ||
ObjKey dst_key; | ||
if (outgoing_link_column.get_type() == col_type_Link) { | ||
dst_table = table.get_link_target(outgoing_link_column); | ||
dst_key = obj.get<ObjKey>(outgoing_link_column); | ||
} | ||
else if (outgoing_link_column.get_type() == col_type_Mixed) { | ||
TableRef no_cached; | ||
Mixed value = obj.get<Mixed>(outgoing_link_column); | ||
return do_check_mixed_for_link(*table.get_parent_group(), no_cached, value, filtered_columns, depth); | ||
} | ||
else { | ||
REALM_UNREACHABLE(); | ||
} | ||
|
||
if (!dst_key) // do not descend into a null or unresolved link | ||
return false; | ||
return check_row(*table.get_link_target(outgoing_link_column), dst_key.value, filtered_columns, depth + 1); | ||
return check_row(*dst_table, dst_key.value, filtered_columns, depth + 1); | ||
}; | ||
|
||
// Check the `links` of all `m_related_tables` and return true if any of them has a `linked_object_changed`. | ||
|
@@ -291,6 +305,10 @@ bool DeepChangeChecker::check_row(Table const& table, ObjKeyType object_key, | |
{ | ||
TableKey table_key = table.get_key(); | ||
|
||
if (ObjKey(object_key).is_unresolved()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would we get an unresolved key here? Each of the callers is already checking for that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only way is to call the checker directly with an unresolved key. This shouldn't happen in from any of the notifiers in production, but some of the new test code I added calls the checker for all objects under test, including an unresolved one. I felt it was better to handle it here explicitly rather than not test it. I could add a comment about this, unless you'd prefer we not do it this way. I could also move this up a level if it detracts from the intended design. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very much a personal opinion thing, but I'd prefer an assert that the key isn't unresolved. The general idea is that the more that a function's inputs are constrained, the easier it is to modify that function in the future. It's hard to imagine how it would happen in this specific case, but suppose that a future change made properly handling unresolved links here a lot more complicated. A future maintainer could then invest a bunch of effort into some code which isn't actually reachable in normal usage, or alternatively have to spend a while trying to figure out how this function could get an unresolved link so that they could solve the problem at a different layer. Asserting that the link isn't unresolved instead tells anyone reading this code that they don't have to consider that possibility, while still making it likely that changes in other places which accidentally result in this function being passed an unresolved link will be caught. Even better would be to shove this off to the type system and have different types for possibly unresolved or null ObjKeys and definitely valid ObjKeys so that we could statically check that they're used properly, but that's also a lot more work than just slapping in some asserts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the assert because I think that's the clearest option and could help us catch other mistakes. To accommodate the test code, I move the check up to the top level calls with a comment explaining why it is needed. Unresolved keys were intended to be a hidden detail and having to deal with them at this level is not ideal, so hopefully this is an acceptable compromise until we find a way to improve that situation. |
||
return false; | ||
} | ||
|
||
// First check if the object was modified directly. We skip this if we're | ||
// looking at the root object because that check is done more efficiently | ||
// in operator() before calling this. | ||
|
@@ -384,6 +402,10 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<int64_t>& | |
return; | ||
} | ||
|
||
if (ObjKey(object_key_value).is_unresolved()) { | ||
return; | ||
} | ||
|
||
auto [table_key, column_key] = key_path.at(depth); | ||
|
||
// Check for a change on the current depth level. | ||
|
@@ -409,6 +431,9 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<int64_t>& | |
auto check_mixed_object = [&](const Mixed& mixed_object) { | ||
if (mixed_object.is_type(type_Link, type_TypedLink)) { | ||
auto object_key = mixed_object.get<ObjKey>(); | ||
if (object_key.is_unresolved()) { | ||
return; | ||
} | ||
auto target_table_key = mixed_object.get_link().get_table_key(); | ||
Group* group = table.get_parent_group(); | ||
auto target_table = group->get_table(target_table_key); | ||
|
@@ -439,43 +464,37 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<int64_t>& | |
else if (column_key.is_set()) { | ||
if (column_type == col_type_Mixed) { | ||
auto set = object.get_set<Mixed>(column_key); | ||
for (size_t i = 0; i < set.size(); i++) { | ||
auto target_object = set.get(i); | ||
for (auto it = set.begin(); it != set.end(); ++it) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the changes to use iterators in this section is just an optimization There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use range-based for here? |
||
auto target_object = *it; | ||
check_mixed_object(target_object); | ||
} | ||
} | ||
else { | ||
REALM_ASSERT(column_type == col_type_Link || column_type == col_type_LinkList); | ||
auto set = object.get_linkset(column_key); | ||
auto target_table = table.get_link_target(column_key); | ||
for (size_t i = 0; i < set.size(); i++) { | ||
auto target_object = set.get(i); | ||
for (auto it = set.begin(); it != set.end(); ++it) { | ||
auto target_object = *it; | ||
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object.value); | ||
} | ||
} | ||
} | ||
else if (column_key.is_dictionary()) { | ||
if (column_type == col_type_Mixed) { | ||
auto dictionary = object.get_dictionary(column_key); | ||
for (size_t i = 0; i < dictionary.size(); i++) { | ||
auto target_object = dictionary.get(dictionary.get_key(i)); | ||
check_mixed_object(target_object); | ||
} | ||
} | ||
else { | ||
REALM_ASSERT(column_type == col_type_Link || column_type == col_type_LinkList); | ||
auto dictionary = object.get_dictionary(column_key); | ||
auto linked_dictionary = std::make_unique<DictionaryLinkValues>(dictionary); | ||
auto target_table = table.get_link_target(column_key); | ||
for (size_t i = 0; i < linked_dictionary->size(); i++) { | ||
auto target_object = linked_dictionary->get_key(i); | ||
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object.value); | ||
} | ||
} | ||
// a dictionary always stores mixed values | ||
auto dictionary = object.get_dictionary(column_key); | ||
dictionary.for_all_values([&](Mixed val) { | ||
check_mixed_object(val); | ||
}); | ||
} | ||
else if (column_type == col_type_Mixed) { | ||
check_mixed_object(object.get_any(column_key)); | ||
} | ||
else if (column_type == col_type_Link) { | ||
// A forward link will only have one target object. | ||
auto target_object = object.get<ObjKey>(column_key); | ||
if (!target_object || target_object.is_unresolved()) { | ||
return; | ||
} | ||
auto target_table = table.get_link_target(column_key); | ||
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object.value); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This flow is pretty weird now. It declares variables, populates them in one branch of the if, but then doesn't use them in the other branch (and returns so the fact that they aren't used isn't important either). I think it'd be clearer with just
if (outgoing_link_column.get_type() == col_type_Mixed) { ... } REALM_ASSERT(outgoing_link_column.get_type() == col_type_Link);
and then all the non-mixed logic after the check for Mixed.