Skip to content

Commit

Permalink
Use Columns<Link> when property is Dictionary if links
Browse files Browse the repository at this point in the history
If a Dictionary property has links as value type, we can use Columns<Link> to handle
the links instead of the basic Columns<Dictionary>. 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<Link> evaluates to.
  • Loading branch information
jedelbo committed Jun 8, 2023
1 parent a3a08bd commit 3538c44
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 67 deletions.
85 changes: 51 additions & 34 deletions src/realm/parser/driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Subexpr>& list, std::unique_ptr<Subexpr>& value, DataType& type) {
auto handle_typed_links = [drv](std::unique_ptr<Subexpr>& list, std::unique_ptr<Subexpr>& expr, DataType& type) {
if (auto link_column = dynamic_cast<const Columns<Link>*>(list.get())) {
if (value->get_mixed().is_null()) {
type = ColumnTypeTraits<realm::null>::id;
value = std::make_unique<Value<realm::null>>();
}
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<Value<ObjKey>>(right_obj_key);
type = type_Link;
// Change all TypedLink values to ObjKey values
auto value = dynamic_cast<ValueBase*>(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<realm::null>::id;
expr = std::make_unique<Value<realm::null>>();
}
}
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<Value<ObjKey>>(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)) {
Expand Down Expand Up @@ -1783,11 +1800,20 @@ std::unique_ptr<Subexpr> 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<Link>(col_key);
}

if (col_key.is_dictionary()) {
return create_subexpr<Dictionary>(col_key);
}
else if (col_key.is_set()) {
switch (col_key.get_type()) {
switch (col_type) {
case col_type_Int:
return create_subexpr<Set<Int>>(col_key);
case col_type_Bool:
Expand All @@ -1810,15 +1836,12 @@ std::unique_ptr<Subexpr> LinkChain::column(const std::string& col)
return create_subexpr<Set<ObjectId>>(col_key);
case col_type_Mixed:
return create_subexpr<Set<Mixed>>(col_key);
case col_type_Link:
add(col_key);
return create_subexpr<Link>(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<Lst<Int>>(col_key);
case col_type_Bool:
Expand All @@ -1841,9 +1864,6 @@ std::unique_ptr<Subexpr> LinkChain::column(const std::string& col)
return create_subexpr<Lst<ObjectId>>(col_key);
case col_type_Mixed:
return create_subexpr<Lst<Mixed>>(col_key);
case col_type_LinkList:
add(col_key);
return create_subexpr<Link>(col_key);
default:
break;
}
Expand All @@ -1854,7 +1874,7 @@ std::unique_ptr<Subexpr> 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<Int>(col_key);
case col_type_Bool:
Expand All @@ -1877,9 +1897,6 @@ std::unique_ptr<Subexpr> LinkChain::column(const std::string& col)
return create_subexpr<ObjectId>(col_key);
case col_type_Mixed:
return create_subexpr<Mixed>(col_key);
case col_type_Link:
add(col_key);
return create_subexpr<Link>(col_key);
default:
break;
}
Expand Down
86 changes: 66 additions & 20 deletions src/realm/query_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,23 +819,43 @@ ExpressionNode::ExpressionNode(const ExpressionNode& from)
template <>
size_t LinksToNode<Equal>::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<ObjKey> 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<Mixed> 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<ObjKey> 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) {
Expand All @@ -856,15 +876,41 @@ size_t LinksToNode<NotEqual>::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<ObjKey> 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<Mixed> 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<ObjKey> 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;
}
}
}
}
Expand Down
16 changes: 7 additions & 9 deletions src/realm/query_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -2381,15 +2381,13 @@ class LinksToNodeBase : public ParentNode {

protected:
std::vector<ObjKey> m_target_keys;
ColumnType m_column_type;
std::optional<ArrayKey> m_list;
std::optional<ArrayList> m_linklist;
ArrayPayload* m_leaf = nullptr;

LinksToNodeBase(const LinksToNodeBase& source)
: ParentNode(source)
, m_target_keys(source.m_target_keys)
, m_column_type(source.m_column_type)
{
}

Expand Down
13 changes: 9 additions & 4 deletions test/test_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}});
Expand All @@ -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<Int>(col_age) > 4;
CHECK_EQUAL(q.count(), 1);
CHECK_EQUAL(q.count(), 2);
q = persons->link(col_friend).link(col_dict).column<Int>(col_age) > 4;
CHECK_EQUAL(q.count(), 1);

verify_query(test_context, persons, "[email protected] > 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, "[email protected] > 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)
Expand Down

0 comments on commit 3538c44

Please sign in to comment.