From 3538c44f1b561e8cc04660858e34ad42e422ebf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Edelbo?= Date: Thu, 8 Jun 2023 16:08:35 +0200 Subject: [PATCH] Use Columns when property is Dictionary if links If a Dictionary property has links as value type, we can use Columns to handle the links instead of the basic Columns. This has the effect that when we compare with a single value, we will optimize to use LinksToNode. So we need to make LinksToNode handle the Dictionary case. When we compare with a list of links, we must ensure that the list is converted to a list obj ObjKeys - which is the type that Column evaluates to. --- src/realm/parser/driver.cpp | 85 +++++++++++++++++++++--------------- src/realm/query_engine.cpp | 86 ++++++++++++++++++++++++++++--------- src/realm/query_engine.hpp | 16 +++---- test/test_parser.cpp | 13 ++++-- 4 files changed, 133 insertions(+), 67 deletions(-) diff --git a/src/realm/parser/driver.cpp b/src/realm/parser/driver.cpp index 4b2915b022a..ff6cbd32404 100644 --- a/src/realm/parser/driver.cpp +++ b/src/realm/parser/driver.cpp @@ -435,37 +435,54 @@ Query EqualityNode::visit(ParserDriver* drv) auto left_type = left->get_type(); auto right_type = right->get_type(); - auto handle_typed_link = [drv](std::unique_ptr& list, std::unique_ptr& value, DataType& type) { + auto handle_typed_links = [drv](std::unique_ptr& list, std::unique_ptr& expr, DataType& type) { if (auto link_column = dynamic_cast*>(list.get())) { - if (value->get_mixed().is_null()) { - type = ColumnTypeTraits::id; - value = std::make_unique>(); - } - else { - auto left_dest_table_key = link_column->link_map().get_target_table()->get_key(); - auto right_table_key = value->get_mixed().get_link().get_table_key(); - auto right_obj_key = value->get_mixed().get_link().get_obj_key(); - if (left_dest_table_key == right_table_key) { - value = std::make_unique>(right_obj_key); - type = type_Link; + // Change all TypedLink values to ObjKey values + auto value = dynamic_cast(expr.get()); + auto left_dest_table_key = link_column->link_map().get_target_table()->get_key(); + auto sz = value->size(); + for (size_t i = 0; i < sz; i++) { + auto val = value->get(i); + if (val.is_null()) { + if (sz == 1) { + type = ColumnTypeTraits::id; + expr = std::make_unique>(); + } } else { - const Group* g = drv->m_base_table->get_parent_group(); - throw InvalidQueryArgError(util::format( - "The relationship '%1' which links to type '%2' cannot be compared to an argument of type %3", - link_column->link_map().description(drv->m_serializer_state), - link_column->link_map().get_target_table()->get_class_name(), - print_pretty_objlink(value->get_mixed().get_link(), g))); + if (val.is_type(type_TypedLink)) { + auto right_table_key = val.get_link().get_table_key(); + auto right_obj_key = val.get_link().get_obj_key(); + if (left_dest_table_key == right_table_key) { + if (sz == 1) { + expr = std::make_unique>(right_obj_key); + type = type_Link; + } + else { + REALM_ASSERT(type == type_Mixed); + value->set(i, right_obj_key); + } + } + else { + const Group* g = drv->m_base_table->get_parent_group(); + throw InvalidQueryArgError( + util::format("The relationship '%1' which links to type '%2' cannot be compared to " + "an argument of type %3", + link_column->link_map().description(drv->m_serializer_state), + link_column->link_map().get_target_table()->get_class_name(), + print_pretty_objlink(value->get(0).get_link(), g))); + } + } } } } }; - if (left_type == type_Link && right_type == type_TypedLink && right->has_single_value()) { - handle_typed_link(left, right, right_type); + if (left_type == type_Link && right->has_constant_evaluation()) { + handle_typed_links(left, right, right_type); } - if (right_type == type_Link && left_type == type_TypedLink && left->has_single_value()) { - handle_typed_link(right, left, left_type); + if (right_type == type_Link && left->has_constant_evaluation()) { + handle_typed_links(right, left, left_type); } if (left_type.is_valid() && right_type.is_valid() && !Mixed::data_types_are_comparable(left_type, right_type)) { @@ -1783,11 +1800,20 @@ std::unique_ptr LinkChain::column(const std::string& col) } } + auto col_type{col_key.get_type()}; + if (col_type == col_type_LinkList) { + col_type = col_type_Link; + } + if (col_type == col_type_Link) { + add(col_key); + return create_subexpr(col_key); + } + if (col_key.is_dictionary()) { return create_subexpr(col_key); } else if (col_key.is_set()) { - switch (col_key.get_type()) { + switch (col_type) { case col_type_Int: return create_subexpr>(col_key); case col_type_Bool: @@ -1810,15 +1836,12 @@ std::unique_ptr LinkChain::column(const std::string& col) return create_subexpr>(col_key); case col_type_Mixed: return create_subexpr>(col_key); - case col_type_Link: - add(col_key); - return create_subexpr(col_key); default: break; } } else if (col_key.is_list()) { - switch (col_key.get_type()) { + switch (col_type) { case col_type_Int: return create_subexpr>(col_key); case col_type_Bool: @@ -1841,9 +1864,6 @@ std::unique_ptr LinkChain::column(const std::string& col) return create_subexpr>(col_key); case col_type_Mixed: return create_subexpr>(col_key); - case col_type_LinkList: - add(col_key); - return create_subexpr(col_key); default: break; } @@ -1854,7 +1874,7 @@ std::unique_ptr LinkChain::column(const std::string& col) expression_cmp_type_to_str(m_comparison_type))); } - switch (col_key.get_type()) { + switch (col_type) { case col_type_Int: return create_subexpr(col_key); case col_type_Bool: @@ -1877,9 +1897,6 @@ std::unique_ptr LinkChain::column(const std::string& col) return create_subexpr(col_key); case col_type_Mixed: return create_subexpr(col_key); - case col_type_Link: - add(col_key); - return create_subexpr(col_key); default: break; } diff --git a/src/realm/query_engine.cpp b/src/realm/query_engine.cpp index 855709f4150..e24d243efa0 100644 --- a/src/realm/query_engine.cpp +++ b/src/realm/query_engine.cpp @@ -819,23 +819,43 @@ ExpressionNode::ExpressionNode(const ExpressionNode& from) template <> size_t LinksToNode::find_first_local(size_t start, size_t end) { - if (m_column_type == col_type_LinkList || m_condition_column_key.is_set()) { - // LinkLists never contain null - if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end) - return not_found; - - BPlusTree links(m_table.unchecked_ptr()->get_alloc()); - for (size_t i = start; i < end; i++) { - if (ref_type ref = get_ref(i)) { - links.init_from_ref(ref); - for (auto& key : m_target_keys) { - if (key) { - if (links.find_first(key) != not_found) + if (m_condition_column_key.is_collection()) { + Allocator& alloc = m_table.unchecked_ptr()->get_alloc(); + if (m_condition_column_key.is_dictionary()) { + auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key(); + Array top(alloc); + for (size_t i = start; i < end; i++) { + if (ref_type ref = get_ref(i)) { + top.init_from_ref(ref); + BPlusTree values(alloc); + values.set_parent(&top, 1); + values.init_from_parent(); + for (auto& key : m_target_keys) { + ObjLink link(target_table_key, key); + if (values.find_first(link) != not_found) return i; } } } } + else { + // LinkLists never contain null + if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end) + return not_found; + + BPlusTree links(alloc); + for (size_t i = start; i < end; i++) { + if (ref_type ref = get_ref(i)) { + links.init_from_ref(ref); + for (auto& key : m_target_keys) { + if (key) { + if (links.find_first(key) != not_found) + return i; + } + } + } + } + } } else if (m_list) { for (auto& key : m_target_keys) { @@ -856,15 +876,41 @@ size_t LinksToNode::find_first_local(size_t start, size_t end) REALM_ASSERT(m_target_keys.size() == 1); ObjKey key = m_target_keys[0]; - if (m_column_type == col_type_LinkList || m_condition_column_key.is_set()) { - BPlusTree links(m_table.unchecked_ptr()->get_alloc()); - for (size_t i = start; i < end; i++) { - if (ref_type ref = get_ref(i)) { - links.init_from_ref(ref); - auto sz = links.size(); - for (size_t j = 0; j < sz; j++) { - if (links.get(j) != key) { + if (m_condition_column_key.is_collection()) { + Allocator& alloc = m_table.unchecked_ptr()->get_alloc(); + if (m_condition_column_key.is_dictionary()) { + auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key(); + Array top(alloc); + for (size_t i = start; i < end; i++) { + if (ref_type ref = get_ref(i)) { + top.init_from_ref(ref); + BPlusTree values(alloc); + values.set_parent(&top, 1); + values.init_from_parent(); + + ObjLink link(target_table_key, key); + bool found = false; + values.for_all([&](const Mixed& val) { + if (val != link) { + found = true; + } + return !found; + }); + if (found) return i; + } + } + } + else { + BPlusTree links(alloc); + for (size_t i = start; i < end; i++) { + if (ref_type ref = get_ref(i)) { + links.init_from_ref(ref); + auto sz = links.size(); + for (size_t j = 0; j < sz; j++) { + if (links.get(j) != key) { + return i; + } } } } diff --git a/src/realm/query_engine.hpp b/src/realm/query_engine.hpp index af97159c359..33fb0deeab3 100644 --- a/src/realm/query_engine.hpp +++ b/src/realm/query_engine.hpp @@ -2351,21 +2351,21 @@ class LinksToNodeBase : public ParentNode { { m_dT = 50.0; m_condition_column_key = origin_column_key; - m_column_type = origin_column_key.get_type(); - REALM_ASSERT(m_column_type == col_type_Link || m_column_type == col_type_LinkList); + auto column_type = origin_column_key.get_type(); + REALM_ASSERT(column_type == col_type_Link || column_type == col_type_LinkList); REALM_ASSERT(!m_target_keys.empty()); } void cluster_changed() override { - if (m_column_type == col_type_Link) { - m_list.emplace(m_table.unchecked_ptr()->get_alloc()); - m_leaf = &*m_list; - } - else if (m_column_type == col_type_LinkList) { + if (m_condition_column_key.is_collection()) { m_linklist.emplace(m_table.unchecked_ptr()->get_alloc()); m_leaf = &*m_linklist; } + else { + m_list.emplace(m_table.unchecked_ptr()->get_alloc()); + m_leaf = &*m_list; + } m_cluster->init_leaf(this->m_condition_column_key, m_leaf); } @@ -2381,7 +2381,6 @@ class LinksToNodeBase : public ParentNode { protected: std::vector m_target_keys; - ColumnType m_column_type; std::optional m_list; std::optional m_linklist; ArrayPayload* m_leaf = nullptr; @@ -2389,7 +2388,6 @@ class LinksToNodeBase : public ParentNode { LinksToNodeBase(const LinksToNodeBase& source) : ParentNode(source) , m_target_keys(source.m_target_keys) - , m_column_type(source.m_column_type) { } diff --git a/test/test_parser.cpp b/test/test_parser.cpp index 64d4760a376..4f402cf0654 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -5040,6 +5040,7 @@ TEST(Parser_DictionaryObjects) Obj adam = persons->create_object_with_primary_key("adam"); Obj bernie = persons->create_object_with_primary_key("bernie"); + Obj charlie = persons->create_object_with_primary_key("charlie"); Obj astro = dogs->create_object_with_primary_key("astro", {{col_age, 4}}); Obj pluto = dogs->create_object_with_primary_key("pluto", {{col_age, 5}}); @@ -5055,17 +5056,21 @@ TEST(Parser_DictionaryObjects) bernie_pets.insert("dog1", astro); bernie_pets.insert("dog2", snoopy); + auto charlie_pets = charlie.get_dictionary(col_dict); + charlie_pets.insert("dog1", pluto); + adam.set(col_friend, bernie.get_key()); bernie.set(col_friend, adam.get_key()); auto q = persons->link(col_dict).column(col_age) > 4; - CHECK_EQUAL(q.count(), 1); + CHECK_EQUAL(q.count(), 2); q = persons->link(col_friend).link(col_dict).column(col_age) > 4; CHECK_EQUAL(q.count(), 1); - verify_query(test_context, persons, "pets.@values.age > 4", 1); - verify_query(test_context, persons, "pets.@values == obj('dog', 'pluto')", 1); - verify_query(test_context, persons, "pets.@values == ANY { obj('dog', 'pluto'), obj('dog', 'astro') }", 2); + verify_query(test_context, persons, "pets.@values.age > 4", 2); + verify_query(test_context, persons, "pets.@values == obj('dog', 'pluto')", 2); + verify_query(test_context, persons, "pets.@values != obj('dog', 'pluto')", 2); + verify_query(test_context, persons, "pets.@values == ANY { obj('dog', 'lady'), obj('dog', 'astro') }", 2); } TEST(Parser_DictionarySorting)