• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 2551

08 Aug 2024 04:17PM UTC coverage: 91.084% (-0.02%) from 91.108%
2551

push

Evergreen

web-flow
fix RQL BETWEEN regression for properties over links (#7965)

102766 of 181580 branches covered (56.6%)

30 of 30 new or added lines in 2 files covered. (100.0%)

98 existing lines in 16 files now uncovered.

217015 of 238257 relevant lines covered (91.08%)

5959744.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.37
/src/realm/query_engine.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/query_engine.hpp>
20

21
#include <realm/query_expression.hpp>
22
#include <realm/index_string.hpp>
23
#include <realm/db.hpp>
24
#include <realm/utilities.hpp>
25

26
namespace realm {
27

28
ParentNode::ParentNode(const ParentNode& from)
29
    : m_child(from.m_child ? from.m_child->clone() : nullptr)
1,522,542✔
30
    , m_condition_column_key(from.m_condition_column_key)
1,522,542✔
31
    , m_dD(from.m_dD)
1,522,542✔
32
    , m_dT(from.m_dT)
1,522,542✔
33
    , m_probes(from.m_probes)
1,522,542✔
34
    , m_matches(from.m_matches)
1,522,542✔
35
    , m_table(from.m_table)
1,522,542✔
36
{
3,095,403✔
37
}
3,095,403✔
38

39

40
size_t ParentNode::find_first(size_t start, size_t end)
41
{
6,211,146✔
42
    size_t sz = m_children.size();
6,211,146✔
43
    size_t current_cond = 0;
6,211,146✔
44
    size_t nb_cond_to_test = sz;
6,211,146✔
45

46
    while (REALM_LIKELY(start < end)) {
6,646,902✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
6,614,238✔
48

49
        if (m != start) {
6,614,238✔
50
            // Pointer advanced - we will have to check all other conditions
51
            nb_cond_to_test = sz;
2,956,464✔
52
            start = m;
2,956,464✔
53
        }
2,956,464✔
54

55
        nb_cond_to_test--;
6,614,238✔
56

57
        // Optimized for one condition where this will be true first time
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
6,614,238✔
59
            return m;
6,178,482✔
60

61
        current_cond++;
435,756✔
62

63
        if (current_cond == sz)
435,756✔
64
            current_cond = 0;
147,330✔
65
    }
435,756✔
66
    return not_found;
32,664✔
67
}
6,211,146✔
68

69
template <class T>
70
inline bool Obj::evaluate(T func) const
71
{
1,266,732✔
72
    REALM_ASSERT(is_valid());
1,266,732✔
73
    Cluster cluster(0, get_alloc(), m_table->m_clusters);
1,266,732✔
74
    cluster.init(m_mem);
1,266,732✔
75
    cluster.set_offset(m_key.value - cluster.get_key_value(m_row_ndx));
1,266,732✔
76
    return func(&cluster, m_row_ndx);
1,266,732✔
77
}
1,266,732✔
78

79
bool ParentNode::match(const Obj& obj)
80
{
1,266,732✔
81
    return obj.evaluate([this](const Cluster* cluster, size_t row) {
1,266,738✔
82
        set_cluster(cluster);
1,266,738✔
83
        size_t m = find_first(row, row + 1);
1,266,738✔
84
        return m != npos;
1,266,738✔
85
    });
1,266,738✔
86
}
1,266,732✔
87

88
size_t ParentNode::aggregate_local(QueryStateBase* st, size_t start, size_t end, size_t local_limit,
89
                                   ArrayPayload* source_column)
90
{
6,582,093✔
91
    // aggregate called on non-integer column type. Speed of this function is not as critical as speed of the
92
    // integer version, because find_first_local() is relatively slower here (because it's non-integers).
93
    //
94
    // Todo: Two speedups are possible. Simple: Initially test if there are no sub criterias and run
95
    // find_first_local()
96
    // in a tight loop if so (instead of testing if there are sub criterias after each match). Harder: Specialize
97
    // data type array to make array call match() directly on each match, like for integers.
98

99
    m_state = st;
6,582,093✔
100
    m_state->set_payload_column(source_column);
6,582,093✔
101
    size_t local_matches = 0;
6,582,093✔
102

103
    if (m_children.size() == 1) {
6,671,070✔
104
        return find_all_local(start, end);
6,606,129✔
105
    }
6,606,129✔
106

107
    size_t r = start - 1;
2,147,548,588✔
108
    for (;;) {
2,148,177,976✔
109
        if (local_matches == local_limit) {
1,356,021✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
23,691✔
111
            return r + 1;
23,691✔
112
        }
23,691✔
113

114
        // Find first match in this condition node
115
        auto pos = r + 1;
1,332,330✔
116
        r = find_first_local(pos, end);
1,332,330✔
117
        if (r == not_found) {
1,332,330✔
118
            m_dD = double(pos - start) / (local_matches + 1.1);
49,464✔
119
            return end;
49,464✔
120
        }
49,464✔
121

122
        local_matches++;
1,282,866✔
123

124
        // Find first match in remaining condition nodes
125
        size_t m = r;
1,282,866✔
126

127
        for (size_t c = 1; c < m_children.size(); c++) {
2,000,061✔
128
            m = m_children[c]->find_first_local(r, r + 1);
1,342,134✔
129
            if (m != r) {
1,342,134✔
130
                break;
624,939✔
131
            }
624,939✔
132
        }
1,342,134✔
133

134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
135
        // match
136
        if (m == r) {
1,282,866✔
137
            bool cont = st->match(r);
657,963✔
138
            if (!cont) {
657,963✔
139
                return static_cast<size_t>(-1);
21✔
140
            }
21✔
141
        }
657,963✔
142
    }
1,282,866✔
143
}
2,147,548,588✔
144

145
size_t ParentNode::find_all_local(size_t start, size_t end)
146
{
336,528✔
147
    while (start < end) {
11,646,003✔
148
        start = find_first_local(start, end);
11,309,505✔
149
        if (start != not_found) {
11,309,505✔
150
            bool cont = m_state->match(start);
11,103,825✔
151
            if (!cont) {
11,103,825✔
152
                return static_cast<size_t>(-1);
30✔
153
            }
30✔
154
            start++;
11,103,795✔
155
        }
11,103,795✔
156
    }
11,309,505✔
157
    return end;
336,498✔
158
}
336,528✔
159

160
void MixedNode<Equal>::init(bool will_query_ranges)
161
{
5,586✔
162
    MixedNodeBase::init(will_query_ranges);
5,586✔
163

164
    REALM_ASSERT(bool(m_index_evaluator) ==
5,586✔
165
                 (m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General));
5,586✔
166
    if (m_index_evaluator) {
5,586✔
167
        auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
2,898✔
168
        m_index_evaluator->init(index, m_value);
2,898✔
169
        m_dT = 0.0;
2,898✔
170
    }
2,898✔
171
    else {
2,688✔
172
        m_dT = 10.0;
2,688✔
173
    }
2,688✔
174
}
5,586✔
175

176
size_t MixedNode<Equal>::find_first_local(size_t start, size_t end)
177
{
24,738✔
178
    REALM_ASSERT(m_table);
24,738✔
179

180
    if (m_index_evaluator) {
24,738✔
181
        return m_index_evaluator->do_search_index(m_cluster, start, end);
12,132✔
182
    }
12,132✔
183
    else {
12,606✔
184
        return m_leaf->find_first(m_value, start, end);
12,606✔
185
    }
12,606✔
186

187
    return not_found;
×
188
}
24,738✔
189

190
void MixedNode<EqualIns>::init(bool will_query_ranges)
191
{
96✔
192
    MixedNodeBase::init(will_query_ranges);
96✔
193

194
    StringData val_as_string;
96✔
195
    if (m_value.is_type(type_String)) {
96✔
196
        val_as_string = m_value.get<StringData>();
36✔
197
    }
36✔
198
    else if (m_value.is_type(type_Binary)) {
60✔
199
        BinaryData bin = m_value.get<BinaryData>();
24✔
200
        val_as_string = StringData(bin.data(), bin.size());
24✔
201
    }
24✔
202
    REALM_ASSERT(bool(m_index_evaluator) ==
96✔
203
                 (m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General));
96✔
204
    if (m_index_evaluator) {
96✔
205
        auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
30✔
206
        if (!val_as_string.is_null()) {
30✔
207
            m_index_matches.clear();
12✔
208
            constexpr bool case_insensitive = true;
12✔
209
            index->find_all(m_index_matches, val_as_string, case_insensitive);
12✔
210
            // It is unfortunate but necessary to check the type due to Binary and String
211
            // having the same StringIndex hash values
212
            m_index_matches.erase(std::remove_if(m_index_matches.begin(), m_index_matches.end(),
12✔
213
                                                 [this](const ObjKey& obj_key) {
12✔
214
                                                     Mixed to_check =
12✔
215
                                                         m_table->get_object(obj_key).get_any(m_condition_column_key);
12✔
216
                                                     return (!Mixed::types_are_comparable(to_check, m_value));
12✔
217
                                                 }),
12✔
218
                                  m_index_matches.end());
12✔
219
            m_index_evaluator->init(&m_index_matches);
12✔
220
        }
12✔
221
        else {
18✔
222
            // search for non string can use exact match
223
            m_index_evaluator->init(index, m_value);
18✔
224
        }
18✔
225
        m_dT = 0.0;
30✔
226
    }
30✔
227
    else {
66✔
228
        m_dT = 10.0;
66✔
229
        if (!val_as_string.is_null()) {
66✔
230
            auto upper = case_map(val_as_string, true);
48✔
231
            auto lower = case_map(val_as_string, false);
48✔
232
            if (!upper || !lower) {
48✔
233
                throw query_parser::InvalidQueryError(util::format("Malformed UTF-8: %1", val_as_string));
×
234
            }
×
235
            else {
48✔
236
                m_ucase = std::move(*upper);
48✔
237
                m_lcase = std::move(*lower);
48✔
238
            }
48✔
239
        }
48✔
240
    }
66✔
241
}
96✔
242

243
size_t MixedNode<EqualIns>::find_first_local(size_t start, size_t end)
244
{
114✔
245
    REALM_ASSERT(m_table);
114✔
246

247
    EqualIns cond;
114✔
248
    if (m_value.is_type(type_String, type_Binary)) {
114✔
249
        for (size_t i = start; i < end; i++) {
4,848✔
250
            QueryValue val(m_leaf->get(i));
4,800✔
251
            if (!Mixed::types_are_comparable(m_value, val)) {
4,800✔
252
                continue;
4,062✔
253
            }
4,062✔
254
            StringData val_as_str = val.export_to_type<StringData>();
738✔
255
            if (cond(m_value.export_to_type<StringData>(), m_ucase.c_str(), m_lcase.c_str(), val_as_str))
738✔
256
                return i;
30✔
257
        }
738✔
258
    }
78✔
259
    else {
36✔
260
        for (size_t i = start; i < end; i++) {
1,818✔
261
            QueryValue val(m_leaf->get(i));
1,800✔
262
            if (cond(val, m_value))
1,800✔
263
                return i;
18✔
264
        }
1,800✔
265
    }
36✔
266

267
    return not_found;
66✔
268
}
114✔
269

270
void StringNodeEqualBase::init(bool will_query_ranges)
271
{
60,093✔
272
    StringNodeBase::init(will_query_ranges);
60,093✔
273

274
    const bool uses_index = has_search_index();
60,093✔
275
    if (m_is_string_enum) {
60,093✔
276
        m_dT = 1.0;
2,658✔
277
    }
2,658✔
278
    else if (uses_index) {
57,435✔
279
        m_dT = 0.0;
3,687✔
280
    }
3,687✔
281
    else {
53,748✔
282
        m_dT = 10.0;
53,748✔
283
    }
53,748✔
284

285
    if (uses_index) {
60,093✔
286
        m_index_evaluator = std::make_optional(IndexEvaluator{});
4,149✔
287
        _search_index_init();
4,149✔
288
    }
4,149✔
289
}
60,093✔
290

291
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
292
{
1,147,209✔
293
    REALM_ASSERT(m_table);
1,147,209✔
294

295
    if (m_index_evaluator) {
1,147,209✔
296
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,689✔
297
    }
241,689✔
298

299
    return _find_first_local(start, end);
905,520✔
300
}
1,147,209✔
301

302

303
void IndexEvaluator::init(SearchIndex* index, Mixed value)
304
{
19,821✔
305
    REALM_ASSERT(index);
19,821✔
306
    m_matching_keys = nullptr;
19,821✔
307
    FindRes fr;
19,821✔
308
    InternalFindResult res;
19,821✔
309

310
    m_last_start_key = ObjKey();
19,821✔
311
    m_results_start = 0;
19,821✔
312
    fr = index->find_all_no_copy(value, res);
19,821✔
313

314
    m_index_matches.reset();
19,821✔
315
    switch (fr) {
19,821✔
316
        case FindRes_single:
17,019✔
317
            m_actual_key = ObjKey(res.payload);
17,019✔
318
            m_results_end = 1;
17,019✔
319
            break;
17,019✔
320
        case FindRes_column:
2,211✔
321
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
2,211✔
322
            m_results_start = res.start_ndx;
2,211✔
323
            m_results_end = res.end_ndx;
2,211✔
324
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
2,211✔
325
            break;
2,211✔
326
        case FindRes_not_found:
591✔
327
            m_results_end = 0;
591✔
328
            break;
591✔
329
    }
19,821✔
330
    m_results_ndx = m_results_start;
19,821✔
331
}
19,821✔
332

333
void IndexEvaluator::init(std::vector<ObjKey>* storage)
334
{
462✔
335
    REALM_ASSERT(storage);
462✔
336
    m_matching_keys = storage;
462✔
337
    m_actual_key = ObjKey();
462✔
338
    m_last_start_key = ObjKey();
462✔
339
    m_results_start = 0;
462✔
340
    m_results_end = m_matching_keys->size();
462✔
341
    m_results_ndx = 0;
462✔
342
    if (m_results_start != m_results_end) {
462✔
343
        m_actual_key = m_matching_keys->at(0);
378✔
344
    }
378✔
345
}
462✔
346

347
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
348
{
260,586✔
349
    if (start >= end) {
260,586✔
350
        return not_found;
9✔
351
    }
9✔
352

353
    ObjKey first_key = cluster->get_real_key(start);
260,577✔
354
    if (first_key < m_last_start_key) {
260,577✔
355
        // We are not advancing through the clusters. We basically don't know where we are,
356
        // so just start over from the beginning.
357
        m_results_ndx = m_results_start;
96,006✔
358
        m_actual_key = (m_results_start != m_results_end) ? get_internal(m_results_start) : ObjKey();
96,006✔
359
    }
96,006✔
360
    m_last_start_key = first_key;
260,577✔
361

362
    // Check if we can expect to find more keys
363
    if (m_results_ndx < m_results_end) {
260,577✔
364
        // Check if we should advance to next key to search for
365
        while (first_key > m_actual_key) {
72,285,930✔
366
            m_results_ndx++;
72,084,663✔
367
            if (m_results_ndx == m_results_end) {
72,084,663✔
368
                return not_found;
6,297✔
369
            }
6,297✔
370
            m_actual_key = get_internal(m_results_ndx);
72,078,366✔
371
        }
72,078,366✔
372

373
        // If actual_key is bigger than last key, it is not in this leaf
374
        ObjKey last_key = cluster->get_real_key(end - 1);
201,267✔
375
        if (m_actual_key > last_key)
201,267✔
376
            return not_found;
116,439✔
377

378
        // Now actual_key must be found in leaf keys
379
        REALM_ASSERT(uint64_t(m_actual_key.value) >= cluster->get_offset());
84,828✔
380
        return cluster->lower_bound_key(ClusterNode::RowKey(m_actual_key.value - cluster->get_offset()));
84,828✔
381
    }
201,267✔
382
    return not_found;
53,013✔
383
}
260,577✔
384

385
StringNode<Equal>::StringNode(ColKey col, const Mixed* begin, const Mixed* end)
386
    : StringNodeEqualBase(StringData(), col)
156✔
387
{
300✔
388
    // Don't use the search index if present since we're in a scenario where
389
    // it'd be slower
390
    m_index_evaluator.reset();
300✔
391

392
    for (const Mixed* it = begin; it != end; ++it) {
8,052✔
393
        if (it->is_null()) {
7,752✔
394
            m_needles.emplace();
66✔
395
        }
66✔
396
        else if (const StringData* str = it->get_if<StringData>()) {
7,686✔
397
            m_needle_storage.push_back(std::make_unique<char[]>(str->size() + 1));
7,650✔
398
            std::copy(str->data(), str->data() + str->size(), m_needle_storage.back().get());
7,650✔
399
            m_needle_storage.back()[str->size()] = '\0';
7,650✔
400
            m_needles.insert(StringData(m_needle_storage.back().get(), str->size()));
7,650✔
401
        }
7,650✔
402
    }
7,752✔
403
    if (m_needles.empty()) {
300✔
404
        throw InvalidArgument("No string arguments in query");
×
405
    }
×
406
}
300✔
407

408
void StringNode<Equal>::_search_index_init()
409
{
3,645✔
410
    if (!m_needles.empty()) {
3,645✔
411
        m_index_evaluator.reset();
60✔
412
        return;
60✔
413
    }
60✔
414
    REALM_ASSERT(bool(m_index_evaluator));
3,585✔
415
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,585✔
416
    m_index_evaluator->init(index, StringNodeBase::m_string_value);
3,585✔
417
}
3,585✔
418

419
bool StringNode<Equal>::do_consume_condition(ParentNode& node)
420
{
234✔
421
    // Don't use the search index if present since we're in a scenario where
422
    // it'd be slower
423
    m_index_evaluator.reset();
234✔
424

425
    auto& other = static_cast<StringNode<Equal>&>(node);
234✔
426
    REALM_ASSERT(m_condition_column_key == other.m_condition_column_key);
234✔
427

428
    if (m_needles.empty()) {
234✔
429
        m_needles.insert(m_string_value);
162✔
430
    }
162✔
431
    auto add_string = [&](const StringData& str) {
264✔
432
        if (m_needles.count(str) == 0) {
264✔
433
            if (str.size()) {
246✔
434
                m_needle_storage.push_back(std::make_unique<char[]>(str.size()));
204✔
435
                std::copy(str.data(), str.data() + str.size(), m_needle_storage.back().get());
204✔
436
                m_needles.insert(StringData(m_needle_storage.back().get(), str.size()));
204✔
437
            }
204✔
438
            else {
42✔
439
                // this code path is different because we need to
440
                // distinguish null from the empty string
441
                m_needles.insert(str);
42✔
442
            }
42✔
443
        }
246✔
444
    };
264✔
445
    if (!other.m_needles.empty()) {
234✔
446
        for (const auto& str : other.m_needles) {
78✔
447
            add_string(str);
78✔
448
        }
78✔
449
    }
48✔
450
    else {
186✔
451
        add_string(other.m_string_value);
186✔
452
    }
186✔
453
    return true;
234✔
454
}
234✔
455

456
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
457
{
904,164✔
458
    if (m_needles.empty()) {
904,164✔
459
        return m_leaf->find_first(m_string_value, start, end);
895,926✔
460
    }
895,926✔
461
    else {
8,238✔
462
        if (end == npos)
8,238✔
463
            end = m_leaf->size();
×
464
        REALM_ASSERT_3(start, <=, end);
8,238✔
465
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
8,238✔
466
    }
8,238✔
467
}
904,164✔
468

469
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
470
{
6,633✔
471
    if (m_needles.empty()) {
6,633✔
472
        return StringNodeEqualBase::describe(state);
6,543✔
473
    }
6,543✔
474

475
    std::string list_contents;
90✔
476
    bool is_first = true;
90✔
477
    for (auto it : m_needles) {
222✔
478
        StringData sd(it.data(), it.size());
222✔
479
        list_contents += util::format("%1%2", is_first ? "" : ", ", util::serializer::print_value(sd));
222✔
480
        is_first = false;
222✔
481
    }
222✔
482
    std::string col_descr = state.describe_column(ParentNode::m_table, m_condition_column_key);
90✔
483
    std::string desc = util::format("%1 IN {%2}", col_descr, list_contents);
90✔
484
    return desc;
90✔
485
}
6,633✔
486

487

488
void StringNode<EqualIns>::_search_index_init()
489
{
126✔
490
    auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
126✔
491
    m_index_matches.clear();
126✔
492
    constexpr bool case_insensitive = true;
126✔
493
    index->find_all(m_index_matches, StringNodeBase::m_string_value, case_insensitive);
126✔
494
    m_index_evaluator->init(&m_index_matches);
126✔
495
}
126✔
496

497
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
498
{
1,398✔
499
    EqualIns cond;
1,398✔
500
    for (size_t s = start; s < end; ++s) {
26,214✔
501
        StringData t = get_string(s);
24,936✔
502

503
        if (cond(m_string_value, m_ucase.c_str(), m_lcase.c_str(), t))
24,936✔
504
            return s;
120✔
505
    }
24,936✔
506

507
    return not_found;
1,278✔
508
}
1,398✔
509

510
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
511
    : StringNodeEqualBase(v, column)
171✔
512
    , m_link_map(std::move(lm))
171✔
513
{
342✔
514
    if (!m_link_map)
342✔
515
        m_link_map = std::make_unique<LinkMap>();
330✔
516
}
342✔
517

518
void StringNodeFulltext::table_changed()
519
{
354✔
520
    StringNodeEqualBase::table_changed();
354✔
521
    m_link_map->set_base_table(m_table);
354✔
522
}
354✔
523

524
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
525
    : StringNodeEqualBase(other)
234✔
526
{
468✔
527
    m_link_map = std::make_unique<LinkMap>(*other.m_link_map);
468✔
528
}
468✔
529

530
void StringNodeFulltext::_search_index_init()
531
{
378✔
532
    StringIndex* index = m_link_map->get_target_table()->get_string_index(ParentNode::m_condition_column_key);
378✔
533
    REALM_ASSERT(index && index->is_fulltext_index());
378✔
534
    m_index_matches.clear();
378✔
535
    index->find_all_fulltext(m_index_matches, StringNodeBase::m_string_value);
378✔
536

537
    // If links exists, use backlinks to find the original objects
538
    if (m_link_map->links_exist()) {
378✔
539
        std::set<ObjKey> tmp;
12✔
540
        for (auto k : m_index_matches) {
24✔
541
            auto keys = m_link_map->get_origin_objkeys(k);
24✔
542
            tmp.insert(keys.begin(), keys.end());
24✔
543
        }
24✔
544
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
545
    }
12✔
546

547
    m_index_evaluator = IndexEvaluator{};
378✔
548
    m_index_evaluator->init(&m_index_matches);
378✔
549
}
378✔
550

551
std::unique_ptr<ArrayPayload> TwoColumnsNodeBase::update_cached_leaf_pointers_for_column(Allocator& alloc,
552
                                                                                         const ColKey& col_key)
553
{
58,248✔
554
    switch (col_key.get_type()) {
58,248✔
555
        case col_type_Int:
12,840✔
556
            if (col_key.is_nullable()) {
12,840✔
557
                return std::make_unique<ArrayIntNull>(alloc);
3,444✔
558
            }
3,444✔
559
            return std::make_unique<ArrayInteger>(alloc);
9,396✔
560
        case col_type_Bool:
3,216✔
561
            return std::make_unique<ArrayBoolNull>(alloc);
3,216✔
562
        case col_type_String:
3,456✔
563
            return std::make_unique<ArrayString>(alloc);
3,456✔
564
        case col_type_Binary:
✔
565
            return std::make_unique<ArrayBinary>(alloc);
×
566
        case col_type_Mixed:
3,468✔
567
            return std::make_unique<ArrayMixed>(alloc);
3,468✔
568
        case col_type_Timestamp:
6,552✔
569
            return std::make_unique<ArrayTimestamp>(alloc);
6,552✔
570
        case col_type_Float:
12,606✔
571
            return std::make_unique<ArrayFloatNull>(alloc);
12,606✔
572
        case col_type_Double:
12,654✔
573
            return std::make_unique<ArrayDoubleNull>(alloc);
12,654✔
574
        case col_type_Decimal:
3,456✔
575
            return std::make_unique<ArrayDecimal128>(alloc);
3,456✔
576
        case col_type_Link:
✔
577
            return std::make_unique<ArrayKey>(alloc);
×
578
        case col_type_ObjectId:
✔
579
            return std::make_unique<ArrayObjectIdNull>(alloc);
×
580
        case col_type_UUID:
✔
581
            return std::make_unique<ArrayUUIDNull>(alloc);
×
582
        case col_type_TypedLink:
✔
583
        case col_type_BackLink:
✔
584
            break;
×
585
    };
58,248✔
586
    REALM_UNREACHABLE();
587
    return {};
×
588
}
58,248✔
589

590
size_t size_of_list_from_ref(ref_type ref, Allocator& alloc, ColumnType col_type, bool is_nullable)
591
{
204✔
592
    switch (col_type) {
204✔
593
        case col_type_Int: {
84✔
594
            if (is_nullable) {
84✔
595
                BPlusTree<util::Optional<Int>> list(alloc);
6✔
596
                list.init_from_ref(ref);
6✔
597
                return list.size();
6✔
598
            }
6✔
599
            else {
78✔
600
                BPlusTree<Int> list(alloc);
78✔
601
                list.init_from_ref(ref);
78✔
602
                return list.size();
78✔
603
            }
78✔
604
        }
84✔
605
        case col_type_Bool: {
12✔
606
            BPlusTree<Bool> list(alloc);
12✔
607
            list.init_from_ref(ref);
12✔
608
            return list.size();
12✔
609
        }
84✔
610
        case col_type_String: {
12✔
611
            BPlusTree<String> list(alloc);
12✔
612
            list.init_from_ref(ref);
12✔
613
            return list.size();
12✔
614
        }
84✔
615
        case col_type_Binary: {
12✔
616
            BPlusTree<Binary> list(alloc);
12✔
617
            list.init_from_ref(ref);
12✔
618
            return list.size();
12✔
619
        }
84✔
620
        case col_type_Timestamp: {
12✔
621
            BPlusTree<Timestamp> list(alloc);
12✔
622
            list.init_from_ref(ref);
12✔
623
            return list.size();
12✔
624
        }
84✔
625
        case col_type_Float: {
12✔
626
            BPlusTree<Float> list(alloc);
12✔
627
            list.init_from_ref(ref);
12✔
628
            return list.size();
12✔
629
        }
84✔
630
        case col_type_Double: {
12✔
631
            BPlusTree<Double> list(alloc);
12✔
632
            list.init_from_ref(ref);
12✔
633
            return list.size();
12✔
634
        }
84✔
635
        case col_type_Decimal: {
12✔
636
            BPlusTree<Decimal128> list(alloc);
12✔
637
            list.init_from_ref(ref);
12✔
638
            return list.size();
12✔
639
        }
84✔
640
        case col_type_ObjectId: {
12✔
641
            BPlusTree<ObjectId> list(alloc);
12✔
642
            list.init_from_ref(ref);
12✔
643
            return list.size();
12✔
644
        }
84✔
645
        case col_type_UUID: {
12✔
646
            BPlusTree<UUID> list(alloc);
12✔
647
            list.init_from_ref(ref);
12✔
648
            return list.size();
12✔
649
        }
84✔
650
        case col_type_Mixed: {
✔
651
            BPlusTree<Mixed> list(alloc);
×
652
            list.init_from_ref(ref);
×
653
            return list.size();
×
654
        }
84✔
655
        case col_type_Link: {
12✔
656
            BPlusTree<ObjKey> list(alloc);
12✔
657
            list.init_from_ref(ref);
12✔
658
            return list.size();
12✔
659
        }
84✔
660
        case col_type_TypedLink: {
✔
661
            BPlusTree<ObjLink> list(alloc);
×
662
            list.init_from_ref(ref);
×
663
            return list.size();
×
664
        }
84✔
665
        case col_type_BackLink:
✔
666
            break;
×
667
    }
204✔
668
    REALM_TERMINATE("Unsupported column type.");
669
}
×
670

671
size_t NotNode::find_first_local(size_t start, size_t end)
672
{
18,216✔
673
    if (start <= m_known_range_start && end >= m_known_range_end) {
18,216✔
674
        return find_first_covers_known(start, end);
2,484✔
675
    }
2,484✔
676
    else if (start >= m_known_range_start && end <= m_known_range_end) {
15,732✔
677
        return find_first_covered_by_known(start, end);
15,228✔
678
    }
15,228✔
679
    else if (start < m_known_range_start && end >= m_known_range_start) {
504!
680
        return find_first_overlap_lower(start, end);
×
681
    }
×
682
    else if (start <= m_known_range_end && end > m_known_range_end) {
504✔
683
        return find_first_overlap_upper(start, end);
168✔
684
    }
168✔
685
    else { // start > m_known_range_end || end < m_known_range_start
336✔
686
        return find_first_no_overlap(start, end);
336✔
687
    }
336✔
688
}
18,216✔
689

690
bool NotNode::evaluate_at(size_t rowndx)
691
{
23,904✔
692
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
23,904✔
693
}
23,904✔
694

695
void NotNode::update_known(size_t start, size_t end, size_t first)
696
{
2,892✔
697
    m_known_range_start = start;
2,892✔
698
    m_known_range_end = end;
2,892✔
699
    m_first_in_known_range = first;
2,892✔
700
}
2,892✔
701

702
size_t NotNode::find_first_loop(size_t start, size_t end)
703
{
20,700✔
704
    for (size_t i = start; i < end; ++i) {
27,288✔
705
        if (evaluate_at(i)) {
23,904✔
706
            return i;
17,316✔
707
        }
17,316✔
708
    }
23,904✔
709
    return not_found;
3,384✔
710
}
20,700✔
711

712
size_t NotNode::find_first_covers_known(size_t start, size_t end)
713
{
2,484✔
714
    // CASE: start-end covers the known range
715
    // [    ######    ]
716
    REALM_ASSERT_DEBUG(start <= m_known_range_start && end >= m_known_range_end);
2,484✔
717
    size_t result = find_first_loop(start, m_known_range_start);
2,484✔
718
    if (result != not_found) {
2,484✔
719
        update_known(start, m_known_range_end, result);
×
720
    }
×
721
    else {
2,484✔
722
        if (m_first_in_known_range != not_found) {
2,484✔
723
            update_known(start, m_known_range_end, m_first_in_known_range);
×
724
            result = m_first_in_known_range;
×
725
        }
×
726
        else {
2,484✔
727
            result = find_first_loop(m_known_range_end, end);
2,484✔
728
            update_known(start, end, result);
2,484✔
729
        }
2,484✔
730
    }
2,484✔
731
    return result;
2,484✔
732
}
2,484✔
733

734
size_t NotNode::find_first_covered_by_known(size_t start, size_t end)
735
{
15,228✔
736
    REALM_ASSERT_DEBUG(start >= m_known_range_start && end <= m_known_range_end);
15,228✔
737
    // CASE: the known range covers start-end
738
    // ###[#####]###
739
    if (m_first_in_known_range != not_found) {
15,228✔
740
        if (m_first_in_known_range > end) {
15,228✔
741
            return not_found;
×
742
        }
×
743
        else if (m_first_in_known_range >= start) {
15,228✔
744
            return m_first_in_known_range;
×
745
        }
×
746
    }
15,228✔
747
    // The first known match is before start, so we can't use the results to improve
748
    // heuristics.
749
    return find_first_loop(start, end);
15,228✔
750
}
15,228✔
751

752
size_t NotNode::find_first_overlap_lower(size_t start, size_t end)
753
{
×
754
    REALM_ASSERT_DEBUG(start < m_known_range_start && end >= m_known_range_start && end <= m_known_range_end);
×
755
    static_cast<void>(end);
×
756
    // CASE: partial overlap, lower end
757
    // [   ###]#####
758
    size_t result;
×
759
    result = find_first_loop(start, m_known_range_start);
×
760
    if (result == not_found) {
×
761
        result = m_first_in_known_range;
×
762
    }
×
763
    update_known(start, m_known_range_end, result);
×
764
    return result < end ? result : not_found;
×
765
}
×
766

767
size_t NotNode::find_first_overlap_upper(size_t start, size_t end)
768
{
168✔
769
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
168✔
770
    // CASE: partial overlap, upper end
771
    // ####[###    ]
772
    size_t result;
168✔
773
    if (m_first_in_known_range != not_found) {
168✔
774
        if (m_first_in_known_range >= start) {
144✔
775
            result = m_first_in_known_range;
×
776
            update_known(m_known_range_start, end, result);
×
777
        }
×
778
        else {
144✔
779
            result = find_first_loop(start, end);
144✔
780
            update_known(m_known_range_start, end, m_first_in_known_range);
144✔
781
        }
144✔
782
    }
144✔
783
    else {
24✔
784
        result = find_first_loop(m_known_range_end, end);
24✔
785
        update_known(m_known_range_start, end, result);
24✔
786
    }
24✔
787
    return result;
168✔
788
}
168✔
789

790
size_t NotNode::find_first_no_overlap(size_t start, size_t end)
791
{
336✔
792
    REALM_ASSERT_DEBUG((start < m_known_range_start && end < m_known_range_start) ||
336!
793
                       (start > m_known_range_end && end > m_known_range_end));
336✔
794
    // CASE: no overlap
795
    // ### [    ]   or    [    ] ####
796
    // if input is a larger range, discard and replace with results.
797
    size_t result = find_first_loop(start, end);
336✔
798
    if (end - start > m_known_range_end - m_known_range_start) {
336✔
799
        update_known(start, end, result);
240✔
800
    }
240✔
801
    return result;
336✔
802
}
336✔
803

804
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
805
    : m_expression(std::move(expression))
35,655✔
806
{
71,310✔
807
    m_dT = 50.0;
71,310✔
808
}
71,310✔
809

810
void ExpressionNode::table_changed()
811
{
76,662✔
812
    m_expression->set_base_table(m_table);
76,662✔
813
}
76,662✔
814

815
void ExpressionNode::cluster_changed()
816
{
125,124✔
817
    m_expression->set_cluster(m_cluster);
125,124✔
818
}
125,124✔
819

820
void ExpressionNode::init(bool will_query_ranges)
821
{
86,781✔
822
    ParentNode::init(will_query_ranges);
86,781✔
823
    m_dT = m_expression->init();
86,781✔
824
}
86,781✔
825

826
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
827
{
21,438✔
828
    if (m_expression) {
21,438✔
829
        return m_expression->description(state);
21,438✔
830
    }
21,438✔
UNCOV
831
    else {
×
UNCOV
832
        return "empty expression";
×
UNCOV
833
    }
×
834
}
21,438✔
835

836
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
837
{
9,648✔
838
    m_expression->collect_dependencies(tables);
9,648✔
839
}
9,648✔
840

841
size_t ExpressionNode::find_first_local(size_t start, size_t end)
842
{
1,924,167✔
843
    return m_expression->find_first(start, end);
1,924,167✔
844
}
1,924,167✔
845

846
std::unique_ptr<ParentNode> ExpressionNode::clone() const
847
{
72,768✔
848
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
72,768✔
849
}
72,768✔
850

851
ExpressionNode::ExpressionNode(const ExpressionNode& from)
852
    : ParentNode(from)
36,384✔
853
    , m_expression(from.m_expression->clone())
36,384✔
854
{
72,768✔
855
}
72,768✔
856

857
template <>
858
size_t LinksToNode<Equal>::find_first_local(size_t start, size_t end)
859
{
6,050,034✔
860
    if (m_condition_column_key.is_collection()) {
6,050,034✔
861
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
6,013,281✔
862
        if (m_condition_column_key.is_dictionary()) {
6,013,281✔
863
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
120✔
864
            Array top(alloc);
120✔
865
            for (size_t i = start; i < end; i++) {
168✔
866
                if (ref_type ref = get_ref(i)) {
144✔
867
                    top.init_from_ref(ref);
144✔
868
                    BPlusTree<Mixed> values(alloc);
144✔
869
                    values.set_parent(&top, 1);
144✔
870
                    values.init_from_parent();
144✔
871
                    for (auto& key : m_target_keys) {
192✔
872
                        ObjLink link(target_table_key, key);
192✔
873
                        if (values.find_first(link) != not_found)
192✔
874
                            return i;
96✔
875
                    }
192✔
876
                }
144✔
877
            }
144✔
878
        }
120✔
879
        else {
6,013,161✔
880
            // LinkLists never contain null
881
            if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end)
6,013,161✔
882
                return not_found;
6✔
883

884
            BPlusTree<ObjKey> links(alloc);
6,013,155✔
885
            for (size_t i = start; i < end; i++) {
6,021,483✔
886
                if (ref_type ref = get_ref(i)) {
6,020,952✔
887
                    links.init_from_ref(ref);
6,016,677✔
888
                    for (auto& key : m_target_keys) {
6,016,692✔
889
                        if (key) {
6,016,692✔
890
                            if (links.find_first(key) != not_found)
6,016,692✔
891
                                return i;
6,012,624✔
892
                        }
6,016,692✔
893
                    }
6,016,692✔
894
                }
6,016,677✔
895
            }
6,020,952✔
896
        }
6,013,155✔
897
    }
6,013,281✔
898
    else if (m_list) {
36,753✔
899
        for (auto& key : m_target_keys) {
36,750✔
900
            auto pos = m_list->find_first(key, start, end);
36,750✔
901
            if (pos != realm::npos) {
36,750✔
902
                return pos;
7,662✔
903
            }
7,662✔
904
        }
36,750✔
905
    }
36,750✔
906

907
    return not_found;
29,646✔
908
}
6,050,034✔
909

910
template <>
911
size_t LinksToNode<NotEqual>::find_first_local(size_t start, size_t end)
912
{
6,756✔
913
    // NotEqual only makes sense for a single value
914
    REALM_ASSERT(m_target_keys.size() == 1);
6,756✔
915
    ObjKey key = m_target_keys[0];
6,756✔
916

917
    if (m_condition_column_key.is_collection()) {
6,756✔
918
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
2,418✔
919
        if (m_condition_column_key.is_dictionary()) {
2,418✔
920
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
36✔
921
            Array top(alloc);
36✔
922
            for (size_t i = start; i < end; i++) {
48✔
923
                if (ref_type ref = get_ref(i)) {
36✔
924
                    top.init_from_ref(ref);
36✔
925
                    BPlusTree<Mixed> values(alloc);
36✔
926
                    values.set_parent(&top, 1);
36✔
927
                    values.init_from_parent();
36✔
928

929
                    ObjLink link(target_table_key, key);
36✔
930
                    bool found = false;
36✔
931
                    values.for_all([&](const Mixed& val) {
48✔
932
                        if (val != link) {
48✔
933
                            found = true;
24✔
934
                        }
24✔
935
                        return !found;
48✔
936
                    });
48✔
937
                    if (found)
36✔
938
                        return i;
24✔
939
                }
36✔
940
            }
36✔
941
        }
36✔
942
        else {
2,382✔
943
            BPlusTree<ObjKey> links(alloc);
2,382✔
944
            for (size_t i = start; i < end; i++) {
4,746✔
945
                if (ref_type ref = get_ref(i)) {
4,512✔
946
                    links.init_from_ref(ref);
2,364✔
947
                    auto sz = links.size();
2,364✔
948
                    for (size_t j = 0; j < sz; j++) {
2,610✔
949
                        if (links.get(j) != key) {
2,394✔
950
                            return i;
2,148✔
951
                        }
2,148✔
952
                    }
2,394✔
953
                }
2,364✔
954
            }
4,512✔
955
        }
2,382✔
956
    }
2,418✔
957
    else if (m_list) {
4,338✔
958
        for (size_t i = start; i < end; i++) {
4,854✔
959
            if (m_list->get(i) != key) {
4,674✔
960
                return i;
4,158✔
961
            }
4,158✔
962
        }
4,674✔
963
    }
4,338✔
964

965
    return not_found;
426✔
966
}
6,756✔
967

968
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc