diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6e0ce7bf1..ba1f5c4ff33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed * Fixed opening Realms on Apple devices where the file resided on a filesystem that does not support preallocation, such as ExFAT. ([cocoa-6508](https://github.com/realm/realm-cocoa/issues/6508)). +* Fixed incorrect results when querying on a LnkLst where the target property over a link has an index and the LnkLst has a different order from the target table. ([Cocoa #6540](https://github.com/realm/realm-cocoa/issues/6540), since 5.23.6. ### Breaking changes * None. diff --git a/src/realm/keys.hpp b/src/realm/keys.hpp index 53ceba59a23..078444a2e80 100644 --- a/src/realm/keys.hpp +++ b/src/realm/keys.hpp @@ -181,10 +181,18 @@ struct ObjKey { { return value < rhs.value; } + bool operator<=(const ObjKey& rhs) const noexcept + { + return value <= rhs.value; + } bool operator>(const ObjKey& rhs) const noexcept { return value > rhs.value; } + bool operator>=(const ObjKey& rhs) const noexcept + { + return value >= rhs.value; + } explicit operator bool() const noexcept { return value != -1; diff --git a/src/realm/query_expression.hpp b/src/realm/query_expression.hpp index 3a14d4a7cc8..e96a3f95c78 100644 --- a/src/realm/query_expression.hpp +++ b/src/realm/query_expression.hpp @@ -3901,10 +3901,17 @@ class Compare : public Expression { size_t find_first(size_t start, size_t end) const override { if (m_has_matches) { - if (m_index_get < m_index_end && start < end) { - ObjKey first_key = m_cluster->get_real_key(start); - auto actual_key = m_matches[m_index_get]; + if (m_index_end == 0 || start >= end) + return not_found; + ObjKey first_key = m_cluster->get_real_key(start); + ObjKey actual_key; + + // Sequential lookup optimization: when the query isn't constrained + // to a LnkLst we'll get find_first() requests in ascending order, + // so we can do a simple linear scan. + if (m_index_get < m_index_end && m_matches[m_index_get] <= first_key) { + actual_key = m_matches[m_index_get]; // skip through keys which are in "earlier" leafs than the one selected by start..end: while (first_key > actual_key) { m_index_get++; @@ -3912,16 +3919,23 @@ class Compare : public Expression { return not_found; actual_key = m_matches[m_index_get]; } - // if actual key is bigger than last key, it is not in this leaf - ObjKey last_key = m_cluster->get_real_key(end - 1); - if (actual_key > last_key) + } + // Otherwise if we get requests out of order we have to do a more + // expensive binary search + else { + auto it = std::lower_bound(m_matches.begin(), m_matches.end(), first_key); + if (it == m_matches.end()) return not_found; + actual_key = *it; + } - // key is known to be in this leaf, so find key whithin leaf keys - return m_cluster->lower_bound_key(ObjKey(actual_key.value - m_cluster->get_offset())); + // if actual key is bigger than last key, it is not in this leaf + ObjKey last_key = start + 1 == end ? first_key : m_cluster->get_real_key(end - 1); + if (actual_key > last_key) + return not_found; - } - return not_found; + // key is known to be in this leaf, so find key whithin leaf keys + return m_cluster->lower_bound_key(ObjKey(actual_key.value - m_cluster->get_offset())); } size_t match; diff --git a/test/test_link_query_view.cpp b/test/test_link_query_view.cpp index 4412d4903d7..58570915541 100644 --- a/test/test_link_query_view.cpp +++ b/test/test_link_query_view.cpp @@ -1097,6 +1097,38 @@ TEST(LinkList_QueryOnLinkListWithDuplicates) CHECK_EQUAL(target_keys[2], some_rows.get_key(3)); } +TEST(LinkList_QueryOnLinkListWithNonTableOrderThroughLinkWithIndex) +{ + Group group; + + TableRef target = group.add_table("target"); + auto int_col = target->add_column(type_Int, "col1"); + target->add_search_index(int_col); + TableRef middle = group.add_table("middle"); + auto link_col = middle->add_column_link(type_Link, "link", *target); + TableRef origin = group.add_table("origin"); + auto list_col = origin->add_column_link(type_LinkList, "linklist", *middle); + + auto target_obj = target->create_object().set_all(1); + + std::vector middle_keys; + middle->create_objects(3, middle_keys); + for (int i = 0; i < 3; ++i) + middle->get_object(middle_keys[i]).set_all(target_obj.get_key()); + + Obj origin_obj = origin->create_object(); + LnkLst list = origin_obj.get_linklist(list_col); + + list.add(middle_keys[1]); + list.add(middle_keys[0]); + list.add(middle_keys[2]); + + auto unfiltered = middle->where(list).find_all(); + CHECK_EQUAL(3, unfiltered.size()); + auto filtered = middle->where(list).and_query(middle->link(link_col).column(int_col) == 1).find_all(); + CHECK_EQUAL(3, filtered.size()); +} + TEST(LinkList_QueryOnIndexedPropertyOfLinkListSingleMatch) { Group group;