Skip to content

Commit 13273f0

Browse files
committed
Fix handling backlink column as last element in KeyPath
1 parent b641e12 commit 13273f0

File tree

4 files changed

+53
-38
lines changed

4 files changed

+53
-38
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
### Fixed
88
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
9-
* Using an empty KeyPath in C API would result in no filtering being done ([#7805](https://github.com/realm/realm-core/issues/7805))
9+
* Using an empty KeyPath in C API would result in no filtering being done ([#7805](https://github.com/realm/realm-core/issues/7805), since 13.24.0)
10+
* Filtering notifications with backlink columns as last element could sometimes give wrong results ([#7530](https://github.com/realm/realm-core/issues/7530), since 11.1.0)
1011

1112
### Breaking changes
1213
* None.

src/realm/object-store/impl/deep_change_checker.cpp

+33-32
Original file line numberDiff line numberDiff line change
@@ -396,40 +396,24 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
396396

397397
if (depth >= key_path.size()) {
398398
// We've reached the end of the key path.
399-
400-
// For the special case of having a backlink at the end of a key path we need to check this level too.
401-
// Modifications to a backlink are found via the insertions on the origin table (which we are in right
402-
// now).
403-
auto last_key_path_element = key_path[key_path.size() - 1];
404-
auto last_column_key = last_key_path_element.second;
405-
if (last_column_key.get_type() == col_type_BackLink) {
406-
auto iterator = m_info.tables.find(table.get_key());
407-
auto table_has_changed = [iterator] {
408-
return !iterator->second.insertions_empty() || !iterator->second.modifications_empty() ||
409-
!iterator->second.deletions_empty();
410-
};
411-
if (iterator != m_info.tables.end() && table_has_changed()) {
412-
ColKey root_column_key = key_path[0].second;
413-
changed_columns.push_back(root_column_key);
414-
}
415-
}
416-
417399
return;
418400
}
419401

420402
auto [table_key, column_key] = key_path.at(depth);
421403

422404
// Check for a change on the current depth level.
423405
auto iterator = m_info.tables.find(table_key);
424-
if (iterator != m_info.tables.end() && (iterator->second.modifications_contains(object_key, {column_key}) ||
425-
iterator->second.insertions_contains(object_key))) {
426-
// If an object linked to the root object was changed we only mark the
427-
// property of the root objects as changed.
428-
// This is also the reason why we can return right after doing so because we would only mark the same root
429-
// property again in case we find another change deeper down the same path.
430-
auto root_column_key = key_path[0].second;
431-
changed_columns.push_back(root_column_key);
432-
return;
406+
if (iterator != m_info.tables.end()) {
407+
auto& changes = iterator->second;
408+
if (changes.modifications_contains(object_key, {column_key}) || changes.insertions_contains(object_key)) {
409+
// If an object linked to the root object was changed we only mark the
410+
// property of the root objects as changed.
411+
// This is also the reason why we can return right after doing so because we would only mark the same root
412+
// property again in case we find another change deeper down the same path.
413+
auto root_column_key = key_path[0].second;
414+
changed_columns.push_back(root_column_key);
415+
return;
416+
}
433417
}
434418

435419
// Only continue for any kind of link.
@@ -448,11 +432,12 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
448432
auto target_table_key = mixed_object.get_link().get_table_key();
449433
Group* group = table.get_parent_group();
450434
auto target_table = group->get_table(target_table_key);
451-
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, object_key);
435+
find_changed_columns(changed_columns, key_path, depth, *target_table, object_key);
452436
}
453437
};
454438

455439
// Advance one level deeper into the key path.
440+
depth++;
456441
auto object = table.get_object(object_key);
457442
if (column_key.is_list()) {
458443
if (column_type == col_type_Mixed) {
@@ -468,7 +453,7 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
468453
auto target_table = table.get_link_target(column_key);
469454
for (size_t i = 0; i < list.size(); i++) {
470455
auto target_object = list.get(i);
471-
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object);
456+
find_changed_columns(changed_columns, key_path, depth, *target_table, target_object);
472457
}
473458
}
474459
}
@@ -484,7 +469,7 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
484469
auto set = object.get_linkset(column_key);
485470
auto target_table = table.get_link_target(column_key);
486471
for (auto& target_object : set) {
487-
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object);
472+
find_changed_columns(changed_columns, key_path, depth, *target_table, target_object);
488473
}
489474
}
490475
}
@@ -505,7 +490,7 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
505490
return;
506491
}
507492
auto target_table = table.get_link_target(column_key);
508-
find_changed_columns(changed_columns, key_path, depth + 1, *target_table, target_object);
493+
find_changed_columns(changed_columns, key_path, depth, *target_table, target_object);
509494
}
510495
else if (column_type == col_type_BackLink) {
511496
// A backlink can have multiple origin objects. We need to iterate over all of them.
@@ -514,7 +499,23 @@ void CollectionKeyPathChangeChecker::find_changed_columns(std::vector<ColKey>& c
514499
size_t backlink_count = object.get_backlink_count(*origin_table, origin_column_key);
515500
for (size_t i = 0; i < backlink_count; i++) {
516501
auto origin_object = object.get_backlink(*origin_table, origin_column_key, i);
517-
find_changed_columns(changed_columns, key_path, depth + 1, *origin_table, origin_object);
502+
if (depth == key_path.size()) {
503+
// For the special case of having a backlink at the end of a key path we need to check this level too.
504+
// Modifications to a backlink are found via the insertions on the origin table (which we are in right
505+
// now).
506+
auto iterator = m_info.tables.find(origin_table->get_key());
507+
if (iterator != m_info.tables.end()) {
508+
auto& changes = iterator->second;
509+
if (changes.modifications_contains(origin_object, {origin_column_key}) ||
510+
changes.insertions_contains(origin_object)) {
511+
ColKey root_column_key = key_path[0].second;
512+
changed_columns.push_back(root_column_key);
513+
}
514+
}
515+
}
516+
else {
517+
find_changed_columns(changed_columns, key_path, depth, *origin_table, origin_object);
518+
}
518519
}
519520
}
520521
else {

src/realm/object-store/shared_realm.cpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -1601,14 +1601,15 @@ bool KeyPathResolver::_resolve(PropId& current, const char* path)
16011601
current.origin_prop->public_name, m_full_path));
16021602
}
16031603
// Check if the rest of the path is stars. If not, we should exclude this property
1604+
auto tmp = path;
16041605
do {
1605-
auto p = find_chr(path, '.');
1606-
StringData property(path, p - path);
1607-
path = p;
1606+
auto p = find_chr(tmp, '.');
1607+
StringData property(tmp, p - tmp);
1608+
tmp = p;
16081609
if (property != "*") {
16091610
return false;
16101611
}
1611-
} while (*path++ == '.');
1612+
} while (*tmp++ == '.');
16121613
return true;
16131614
}
16141615
// Target schema exists - proceed

test/object-store/object.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ TEST_CASE("object") {
312312
};
313313

314314
SECTION("add_notification_callback()") {
315+
bool first;
315316
auto table = r->read_group().get_table("class_table");
316317
auto col_keys = table->get_column_keys();
317318
std::vector<int64_t> pks = {3, 4, 7, 9, 10, 21, 24, 34, 42, 50};
@@ -345,7 +346,7 @@ TEST_CASE("object") {
345346
};
346347

347348
auto require_no_change = [&](Object& object, std::optional<KeyPathArray> key_path_array = std::nullopt) {
348-
bool first = true;
349+
first = true;
349350
auto token = object.add_notification_callback(
350351
[&](CollectionChangeSet) {
351352
REQUIRE(first);
@@ -817,6 +818,7 @@ TEST_CASE("object") {
817818
r->commit_transaction();
818819

819820
KeyPathArray kpa_to_depth_5 = r->create_key_path_array("table2", {"link2.link2.link2.link2.value"});
821+
KeyPathArray kpa_wildcard_wildcard = r->create_key_path_array("table2", {"*.*"});
820822
KeyPathArray kpa_to_depth_6 =
821823
r->create_key_path_array("table2", {"link2.link2.link2.link2.link2.value"});
822824

@@ -833,6 +835,16 @@ TEST_CASE("object") {
833835
REQUIRE_INDICES(change.columns[col_origin_link2.value], 0);
834836
}
835837

838+
SECTION("modifying table 'table2', property 'link2' 5 levels deep "
839+
"while observing table 'table2', property '*.*'"
840+
"-> DOES NOT send a notification") {
841+
auto token = require_no_change(object_depth1, kpa_wildcard_wildcard);
842+
843+
write([&] {
844+
object_depth5.set_column_value("value", 555);
845+
});
846+
}
847+
836848
SECTION("modifying table 'table2', property 'link2' 6 depths deep "
837849
"while observing table 'table2', property 'link2' 5 depths deep "
838850
"-> does NOT send a notification") {

0 commit comments

Comments
 (0)