Skip to content

Commit

Permalink
Use LinksToNode for lists in QueryParser
Browse files Browse the repository at this point in the history
  • Loading branch information
jedelbo committed Jun 9, 2023
1 parent 7a33c49 commit 3a1cad3
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

### Fixed
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
* Querying with object list arguments does not work. ([#6688](https://github.com/realm/realm-core/issues/6688), since v10.3.3)
* Properties in the frozen _before_ Realm instance in the client reset callbacks may have had properties reordered which could lead to exceptions if accessed. ([PR 6693](https://github.com/realm/realm-core/issues/6693), since v13.11.0)

### Breaking changes
Expand Down
90 changes: 59 additions & 31 deletions src/realm/parser/driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,40 +441,50 @@ Query EqualityNode::visit(ParserDriver* drv)
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();
auto obj_keys = std::make_unique<Value<ObjKey>>();
obj_keys->init(expr->has_multiple_values(), sz);
obj_keys->set_comparison_type(expr->get_comparison_type());
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>>();
return;
}
}
else {
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(i).get_link(), g)));
}
TableKey right_table_key;
ObjKey right_obj_key;
if (val.is_type(type_Link)) {
right_table_key = left_dest_table_key;
right_obj_key = val.get<ObjKey>();
}
else if (val.is_type(type_TypedLink)) {
right_table_key = val.get_link().get_table_key();
right_obj_key = val.get_link().get_obj_key();
}
else {
throw InvalidQueryError(util::format("Unsupported comparison between type '%1' and type '%2'",
get_data_type_name(type_Link),
get_data_type_name(val.get_type())));
}
if (left_dest_table_key == right_table_key) {
obj_keys->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(ObjLink(right_table_key, right_obj_key), g)));
}
}
}
expr = std::move(obj_keys);
type = type_Link;
}
};

Expand Down Expand Up @@ -505,9 +515,32 @@ Query EqualityNode::visit(ParserDriver* drv)
}
}

const ObjPropertyBase* prop = dynamic_cast<const ObjPropertyBase*>(left.get());
if (right->has_single_value() && (left_type == right_type || left_type == type_Mixed)) {
if (left_type == type_Link && left_type == right_type && right->has_constant_evaluation()) {
if (auto link_column = dynamic_cast<const Columns<Link>*>(left.get())) {
if (link_column->link_map().get_nb_hops() == 1 &&
link_column->get_comparison_type().value_or(ExpressionComparisonType::Any) ==
ExpressionComparisonType::Any) {
REALM_ASSERT(dynamic_cast<const Value<ObjKey>*>(right.get()));
auto link_values = static_cast<const Value<ObjKey>*>(right.get());
// We can use a LinksToNode based query
std::vector<ObjKey> values;
for (auto val : *link_values) {
values.emplace_back(val.get<ObjKey>());
}
if (op == CompareNode::EQUAL) {
return drv->m_base_table->where().links_to(link_column->link_map().get_first_column_key(),
values);
}
else if (op == CompareNode::NOT_EQUAL) {
return drv->m_base_table->where().not_links_to(link_column->link_map().get_first_column_key(),
values);
}
}
}
}
else if (right->has_single_value() && (left_type == right_type || left_type == type_Mixed)) {
Mixed val = right->get_mixed();
const ObjPropertyBase* prop = dynamic_cast<const ObjPropertyBase*>(left.get());
if (prop && !prop->links_exist()) {
auto col_key = prop->column_key();
if (val.is_null()) {
Expand Down Expand Up @@ -547,17 +580,12 @@ Query EqualityNode::visit(ParserDriver* drv)
}
}
else if (left_type == type_Link) {
REALM_ASSERT(false);
auto link_column = dynamic_cast<const Columns<Link>*>(left.get());
REALM_ASSERT(link_column);
if (link_column && link_column->link_map().get_nb_hops() == 1 &&
link_column->get_comparison_type().value_or(ExpressionComparisonType::Any) ==
ExpressionComparisonType::Any) {
// We can use equal/not_equal and get a LinksToNode based query
if (op == CompareNode::EQUAL) {
return drv->m_base_table->where().equal(link_column->link_map().get_first_column_key(), val);
}
else if (op == CompareNode::NOT_EQUAL) {
return drv->m_base_table->where().not_equal(link_column->link_map().get_first_column_key(), val);
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/realm/query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,12 @@ Query& Query::links_to(ColKey origin_column, const std::vector<ObjKey>& target_k
return *this;
}

Query& Query::not_links_to(ColKey origin_column_key, const std::vector<ObjKey>& target_keys)
{
add_node(std::unique_ptr<ParentNode>(new LinksToNode<NotEqual>(origin_column_key, target_keys)));
return *this;
}

// int64 constant vs column
Query& Query::equal(ColKey column_key, int64_t value)
{
Expand Down
3 changes: 3 additions & 0 deletions src/realm/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class Query final {
// Find links that point to specific target objects
Query& links_to(ColKey column_key, const std::vector<ObjKey>& target_obj);

// Find links that does not point to specific target objects
Query& not_links_to(ColKey column_key, const std::vector<ObjKey>& target_obj);

// Conditions: null
Query& equal(ColKey column_key, null);
Query& not_equal(ColKey column_key, null);
Expand Down
18 changes: 14 additions & 4 deletions src/realm/query_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2372,11 +2372,21 @@ class LinksToNodeBase : public ParentNode {
std::string describe(util::serializer::SerialisationState& state) const override
{
REALM_ASSERT(m_condition_column_key);
if (m_target_keys.size() > 1)
throw SerializationError("Serializing a query which links to multiple objects is currently unsupported.");
ObjLink link(m_table->get_opposite_table(m_condition_column_key)->get_key(), m_target_keys[0]);
std::string links = m_target_keys.size() > 1 ? "{" : "";
Group* g = m_table->get_parent_group();
auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
int cnt = 0;
for (auto key : m_target_keys) {
if (cnt++) {
links += ",";
}
links += util::serializer::print_value(ObjLink(target_table_key, key), g);
}
if (m_target_keys.size() > 1) {
links += "}";
}
return state.describe_column(ParentNode::m_table, m_condition_column_key) + " " + describe_condition() + " " +
util::serializer::print_value(link, m_table->get_parent_group());
links;
}

protected:
Expand Down
31 changes: 31 additions & 0 deletions test/object-store/c_api/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2408,6 +2408,37 @@ TEST_CASE("C API", "[c_api]") {
CHECK(1 == count_list);
}

SECTION("link in list") {
auto link = rlm_link_val(class_bar.key, realm_object_get_key(obj2.get()));
realm_value_t link_value = link;
write([&]() {
CHECK(realm_set_value(obj1.get(), foo_properties["link"], link_value, false));
});

static const size_t num_args = 1;
realm_query_arg_t args[num_args] = {realm_query_arg_t{1, false, &link_value}};
realm_query_arg_t* arg = &args[0];

realm_value_t list_arg[num_args] = {link_value};
realm_query_arg_t args_in_list[num_args] = {realm_query_arg_t{num_args, true, &list_arg[0]}};
realm_query_arg_t* arg_list = &args_in_list[0];

auto q_link_single_param =
cptr_checked(realm_query_parse(realm, class_foo.key, "link == $0", num_args, arg));
auto q_link_in_list =
cptr_checked(realm_query_parse(realm, class_foo.key, "link IN $0", num_args, arg_list));

size_t count, count_list;

// change the link
link = rlm_null();

CHECK(checked(realm_query_count(q_link_single_param.get(), &count)));
CHECK(1 == count);
CHECK(checked(realm_query_count(q_link_in_list.get(), &count_list)));
CHECK(1 == count_list);
}

SECTION("decimal NaN") {
realm_value_t decimal = rlm_decimal_nan();

Expand Down

0 comments on commit 3a1cad3

Please sign in to comment.