Skip to content

Commit 8e4887f

Browse files
jedelboironage
andauthored
RCORE-2098: Fix issue with OR of two IN queries (#7628)
* Fix issue with OR of two IN queries * same fix for other node types, fix combination for empty string --------- Co-authored-by: James Stone <[email protected]>
1 parent b96a012 commit 8e4887f

File tree

5 files changed

+65
-11
lines changed

5 files changed

+65
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
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-
* None.
9+
* Fix assertion failure or wrong results when evaluating a RQL query with multiple IN conditions on the same property. Applies to non-indexed int/string/ObjectId/UUID properties, or if they were indexed and had > 100 conditions. ((RCORE-2098) [PR #7628](https://github.com/realm/realm-core/pull/7628) since v14.6.0).
10+
* Fixed a bug when running a IN query (or a query of the pattern `x == 1 OR x == 2 OR x == 3`) when evaluating on a string property with an empty string in the search condition. Matches with an empty string would have been evaluated as if searching for a null string instead. ([PR #7628](https://github.com/realm/realm-core/pull/7628) since v10.0.0-beta.9)
1011

1112
### Breaking changes
1213
* None.

src/realm/query_engine.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,17 +417,31 @@ bool StringNode<Equal>::do_consume_condition(ParentNode& node)
417417

418418
auto& other = static_cast<StringNode<Equal>&>(node);
419419
REALM_ASSERT(m_condition_column_key == other.m_condition_column_key);
420-
REALM_ASSERT(other.m_needles.empty());
420+
421421
if (m_needles.empty()) {
422422
m_needles.insert(m_string_value);
423423
}
424-
if (auto& str = other.m_value) {
425-
m_needle_storage.push_back(std::make_unique<char[]>(str->size()));
426-
std::copy(str->data(), str->data() + str->size(), m_needle_storage.back().get());
427-
m_needles.insert(StringData(m_needle_storage.back().get(), str->size()));
424+
auto add_string = [&](const StringData& str) {
425+
if (m_needles.count(str) == 0) {
426+
if (str.size()) {
427+
m_needle_storage.push_back(std::make_unique<char[]>(str.size()));
428+
std::copy(str.data(), str.data() + str.size(), m_needle_storage.back().get());
429+
m_needles.insert(StringData(m_needle_storage.back().get(), str.size()));
430+
}
431+
else {
432+
// this code path is different because we need to
433+
// distinguish null from the empty string
434+
m_needles.insert(str);
435+
}
436+
}
437+
};
438+
if (!other.m_needles.empty()) {
439+
for (const auto& str : other.m_needles) {
440+
add_string(str);
441+
}
428442
}
429443
else {
430-
m_needles.emplace();
444+
add_string(other.m_string_value);
431445
}
432446
return true;
433447
}

src/realm/query_engine.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -532,11 +532,17 @@ class IntegerNode<LeafType, Equal> : public IntegerNodeBase<LeafType> {
532532
{
533533
auto& other = static_cast<ThisType&>(node);
534534
REALM_ASSERT(this->m_condition_column_key == other.m_condition_column_key);
535-
REALM_ASSERT(other.m_needles.empty());
536535
if (m_needles.empty()) {
537536
m_needles.insert(this->m_value);
538537
}
539-
m_needles.insert(other.m_value);
538+
if (other.m_needles.empty()) {
539+
m_needles.insert(other.m_value);
540+
}
541+
else {
542+
for (const auto& val : other.m_needles) {
543+
m_needles.insert(val);
544+
}
545+
}
540546
return true;
541547
}
542548

@@ -1294,11 +1300,17 @@ class FixedBytesNode<Equal, ObjectType, ArrayType> : public FixedBytesNodeBase<O
12941300
{
12951301
auto& other = static_cast<ThisType&>(node);
12961302
REALM_ASSERT(this->m_condition_column_key == other.m_condition_column_key);
1297-
REALM_ASSERT(other.m_needles.empty());
12981303
if (m_needles.empty()) {
12991304
m_needles.insert(this->m_value_is_null ? std::nullopt : std::make_optional(this->m_value));
13001305
}
1301-
m_needles.insert(other.m_value_is_null ? std::nullopt : std::make_optional(other.m_value));
1306+
if (other.m_needles.empty()) {
1307+
m_needles.insert(other.m_value_is_null ? std::nullopt : std::make_optional(other.m_value));
1308+
}
1309+
else {
1310+
for (const auto& val : other.m_needles) {
1311+
m_needles.insert(val);
1312+
}
1313+
}
13021314
return true;
13031315
}
13041316

test/test_parser.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,6 +4093,25 @@ TEST(Parser_OperatorIN)
40934093
CHECK_EQUAL(e.what(), "The keypath following 'IN' must contain a list. Found 'fav_item.price'"));
40944094
}
40954095

4096+
TEST(Parser_OrOfIn)
4097+
{
4098+
Group g;
4099+
4100+
TableRef persons = g.add_table("class_Person");
4101+
constexpr bool nullable = true;
4102+
auto col_name = persons->add_column(type_String, "name", nullable);
4103+
persons->create_object().set(col_name, "Ani");
4104+
persons->create_object().set(col_name, "Teddy");
4105+
persons->create_object().set(col_name, "Poly");
4106+
persons->create_object().set(col_name, ""); // empty string
4107+
persons->create_object(); // null value
4108+
4109+
verify_query(test_context, persons, "name IN {'Ani', 'Teddy'} OR name IN {'Poly', 'Teddy'}", 3);
4110+
verify_query(test_context, persons, "name IN {'Ani', 'Teddy'} OR name IN {'Poly', 'Teddy'} OR name IN {null}", 4);
4111+
verify_query(test_context, persons,
4112+
"name IN {'Ani', 'Teddy'} OR name IN {'Poly', 'Teddy'} OR name IN {null} OR name IN {''}", 5);
4113+
}
4114+
40964115
TEST(Parser_KeyPathSubstitution)
40974116
{
40984117
Group g;

test/test_query2.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6007,6 +6007,14 @@ TEST_TYPES(Query_ManyIn, Prop<Int>, Prop<String>, Prop<Float>, Prop<Double>, Pro
60076007
bool order = first == mixed_vals[1];
60086008
CHECK_EQUAL(first, order ? mixed_vals[1] : mixed_vals[2]);
60096009
CHECK_EQUAL(second, order ? mixed_vals[2] : mixed_vals[1]);
6010+
size_t count_of_two_ins = t->where()
6011+
.in(col, mixed_vals.data() + 1, mixed_vals.data() + 2)
6012+
.Or()
6013+
.in(col, mixed_vals.data() + 2, mixed_vals.data() + 3)
6014+
.count();
6015+
size_t count_of_one_in = t->where().in(col, mixed_vals.data() + 1, mixed_vals.data() + 3).count();
6016+
CHECK_EQUAL(count_of_one_in, 2);
6017+
CHECK_EQUAL(count_of_two_ins, 2);
60106018
}
60116019

60126020
TEST(Query_ManyIntConditionsAgg)

0 commit comments

Comments
 (0)