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

realm / realm-core / 1810

02 Nov 2023 05:09PM UTC coverage: 91.68% (+0.02%) from 91.661%
1810

push

Evergreen

web-flow
Loosen checks for uids on exfat fs (#7105)

92128 of 168856 branches covered (0.0%)

0 of 5 new or added lines in 1 file covered. (0.0%)

51 existing lines in 9 files now uncovered.

230753 of 251694 relevant lines covered (91.68%)

6261176.89 hits per line

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

90.44
/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)
30
    , m_condition_column_key(from.m_condition_column_key)
31
    , m_dD(from.m_dD)
32
    , m_dT(from.m_dT)
33
    , m_probes(from.m_probes)
34
    , m_matches(from.m_matches)
35
    , m_table(from.m_table)
36
{
3,053,721✔
37
}
3,053,721✔
38

39

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

2,427,387✔
46
    while (REALM_LIKELY(start < end)) {
5,498,427✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
5,475,351✔
48

2,593,857✔
49
        if (m != start) {
5,475,351✔
50
            // Pointer advanced - we will have to check all other conditions
1,364,958✔
51
            nb_cond_to_test = sz;
2,838,861✔
52
            start = m;
2,838,861✔
53
        }
2,838,861✔
54

2,593,857✔
55
        nb_cond_to_test--;
5,475,351✔
56

2,593,857✔
57
        // Optimized for one condition where this will be true first time
2,593,857✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
5,475,351✔
59
            return m;
5,286,150✔
60

178,692✔
61
        current_cond++;
367,893✔
62

178,692✔
63
        if (current_cond == sz)
367,893✔
64
            current_cond = 0;
124,149✔
65
    }
367,893✔
66
    return not_found;
2,438,241✔
67
}
5,130,534✔
68

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

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

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

2,080,551✔
99
    m_state = st;
5,454,021✔
100
    m_state->set_payload_column(source_column);
5,454,021✔
101
    size_t local_matches = 0;
5,454,021✔
102

2,080,551✔
103
    if (m_children.size() == 1) {
5,946,294✔
104
        return find_all_local(start, end);
5,916,567✔
105
    }
5,916,567✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,513,374✔
108
    for (;;) {
2,148,034,489✔
109
        if (local_matches == local_limit) {
1,050,915✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
18,003✔
111
            return r + 1;
18,003✔
112
        }
18,003✔
113

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

474,459✔
122
        local_matches++;
998,868✔
123

474,459✔
124
        // Find first match in remaining condition nodes
474,459✔
125
        size_t m = r;
998,868✔
126

474,459✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,557,240✔
128
            m = m_children[c]->find_first_local(r, r + 1);
1,058,100✔
129
            if (m != r) {
1,058,100✔
130
                break;
499,728✔
131
            }
499,728✔
132
        }
1,058,100✔
133

474,459✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
474,459✔
135
        // match
474,459✔
136
        if (m == r) {
998,868✔
137
            bool cont = st->match(r);
499,140✔
138
            if (!cont) {
499,140✔
139
                return static_cast<size_t>(-1);
30✔
140
            }
30✔
141
        }
499,140✔
142
    }
998,868✔
143
}
2,147,513,374✔
144

145
size_t ParentNode::find_all_local(size_t start, size_t end)
146
{
307,320✔
147
    while (start < end) {
10,796,481✔
148
        start = find_first_local(start, end);
10,489,191✔
149
        if (start != not_found) {
10,489,191✔
150
            bool cont = m_state->match(start);
10,318,125✔
151
            if (!cont) {
10,318,125✔
152
                return static_cast<size_t>(-1);
30✔
153
            }
30✔
154
            start++;
10,318,095✔
155
        }
10,318,095✔
156
    }
10,489,191✔
157
    return end;
307,305✔
158
}
307,320✔
159

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

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

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

222✔
180
    if (m_index_evaluator) {
444✔
181
        return m_index_evaluator->do_search_index(m_cluster, start, end);
×
182
    }
×
183
    else {
444✔
184
        return m_leaf->find_first(m_value, start, end);
444✔
185
    }
444✔
186

187
    return not_found;
×
188
}
×
189

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

30✔
194
    StringData val_as_string;
60✔
195
    if (m_value.is_type(type_String)) {
60✔
196
        val_as_string = m_value.get<StringData>();
24✔
197
    }
24✔
198
    else if (m_value.is_type(type_Binary)) {
36✔
199
        BinaryData bin = m_value.get<BinaryData>();
×
200
        val_as_string = StringData(bin.data(), bin.size());
×
201
    }
×
202
    REALM_ASSERT(bool(m_index_evaluator) ==
60✔
203
                 (m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General));
60✔
204
    if (m_index_evaluator) {
60✔
205
        auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
24✔
206
        if (!val_as_string.is_null()) {
24✔
207
            m_index_matches.clear();
6✔
208
            constexpr bool case_insensitive = true;
6✔
209
            index->find_all(m_index_matches, val_as_string, case_insensitive);
6✔
210
            m_index_evaluator->init(&m_index_matches);
6✔
211
        }
6✔
212
        else {
18✔
213
            // search for non string can use exact match
9✔
214
            m_index_evaluator->init(index, m_value);
18✔
215
        }
18✔
216
        m_dT = 0.0;
24✔
217
    }
24✔
218
    else {
36✔
219
        m_dT = 10.0;
36✔
220
        if (!val_as_string.is_null()) {
36✔
221
            auto upper = case_map(val_as_string, true);
18✔
222
            auto lower = case_map(val_as_string, false);
18✔
223
            if (!upper || !lower) {
18✔
224
                throw query_parser::InvalidQueryError(util::format("Malformed UTF-8: %1", val_as_string));
×
225
            }
×
226
            else {
18✔
227
                m_ucase = std::move(*upper);
18✔
228
                m_lcase = std::move(*lower);
18✔
229
            }
18✔
230
        }
18✔
231
    }
36✔
232
}
60✔
233

234
size_t MixedNode<EqualIns>::find_first_local(size_t start, size_t end)
235
{
72✔
236
    REALM_ASSERT(m_table);
72✔
237

36✔
238
    EqualIns cond;
72✔
239
    if (m_value.is_type(type_String)) {
72✔
240
        for (size_t i = start; i < end; i++) {
1,818✔
241
            QueryValue val(m_leaf->get(i));
1,800✔
242
            StringData val_as_str;
1,800✔
243
            if (val.is_type(type_String)) {
1,800✔
244
                val_as_str = val.get<StringData>();
432✔
245
            }
432✔
246
            else if (val.is_type(type_Binary)) {
1,368✔
247
                val_as_str = StringData(val.get<BinaryData>().data(), val.get<BinaryData>().size());
18✔
248
            }
18✔
249
            if (!val_as_str.is_null() &&
1,800✔
250
                cond(m_value.get<StringData>(), m_ucase.c_str(), m_lcase.c_str(), val_as_str))
1,125✔
251
                return i;
18✔
252
        }
1,800✔
253
    }
36✔
254
    else {
36✔
255
        for (size_t i = start; i < end; i++) {
1,818✔
256
            QueryValue val(m_leaf->get(i));
1,800✔
257
            if (cond(val, m_value))
1,800✔
258
                return i;
18✔
259
        }
1,800✔
260
    }
36✔
261

36✔
262
    return not_found;
54✔
263
}
72✔
264

265
void StringNodeEqualBase::init(bool will_query_ranges)
266
{
78,204✔
267
    StringNodeBase::init(will_query_ranges);
78,204✔
268

38,787✔
269
    const bool uses_index = has_search_index();
78,204✔
270
    if (m_is_string_enum) {
78,204✔
271
        m_dT = 1.0;
2,658✔
272
    }
2,658✔
273
    else if (uses_index) {
75,546✔
274
        m_dT = 0.0;
3,204✔
275
    }
3,204✔
276
    else {
72,342✔
277
        m_dT = 10.0;
72,342✔
278
    }
72,342✔
279

38,787✔
280
    if (uses_index) {
78,204✔
281
        _search_index_init();
3,666✔
282
    }
3,666✔
283
}
78,204✔
284

285
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
286
{
1,084,107✔
287
    REALM_ASSERT(m_table);
1,084,107✔
288

557,838✔
289
    if (m_index_evaluator) {
1,084,107✔
290
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,620✔
291
    }
241,620✔
292

431,547✔
293
    return _find_first_local(start, end);
842,487✔
294
}
842,487✔
295

296

297
void IndexEvaluator::init(StringIndex* index, Mixed value)
298
{
653,994✔
299
    REALM_ASSERT(index);
653,994✔
300
    m_matching_keys = nullptr;
653,994✔
301
    FindRes fr;
653,994✔
302
    InternalFindResult res;
653,994✔
303

326,997✔
304
    m_last_start_key = ObjKey();
653,994✔
305
    m_results_start = 0;
653,994✔
306
    fr = index->find_all_no_copy(value, res);
653,994✔
307

326,997✔
308
    m_index_matches.reset();
653,994✔
309
    switch (fr) {
653,994✔
310
        case FindRes_single:
9,999✔
311
            m_actual_key = ObjKey(res.payload);
9,999✔
312
            m_results_end = 1;
9,999✔
313
            break;
9,999✔
314
        case FindRes_column:
1,518✔
315
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
1,518✔
316
            m_results_start = res.start_ndx;
1,518✔
317
            m_results_end = res.end_ndx;
1,518✔
318
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
1,518✔
319
            break;
1,518✔
320
        case FindRes_not_found:
642,477✔
321
            m_results_end = 0;
642,477✔
322
            break;
642,477✔
323
    }
653,991✔
324
    m_results_ndx = m_results_start;
653,991✔
325
}
653,991✔
326

327
void IndexEvaluator::init(std::vector<ObjKey>* storage)
328
{
438✔
329
    REALM_ASSERT(storage);
438✔
330
    m_matching_keys = storage;
438✔
331
    m_actual_key = ObjKey();
438✔
332
    m_last_start_key = ObjKey();
438✔
333
    m_results_start = 0;
438✔
334
    m_results_end = m_matching_keys->size();
438✔
335
    m_results_ndx = 0;
438✔
336
    if (m_results_start != m_results_end) {
438✔
337
        m_actual_key = m_matching_keys->at(0);
378✔
338
    }
378✔
339
}
438✔
340

341
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
342
{
885,600✔
343
    if (start >= end) {
885,600✔
344
        return not_found;
9✔
345
    }
9✔
346

448,281✔
347
    ObjKey first_key = cluster->get_real_key(start);
885,591✔
348
    if (first_key < m_last_start_key) {
885,591✔
349
        // We are not advancing through the clusters. We basically don't know where we are,
48,003✔
350
        // so just start over from the beginning.
48,003✔
351
        m_results_ndx = m_results_start;
96,006✔
352
        m_actual_key = (m_results_start != m_results_end) ? get_internal(m_results_start) : ObjKey();
84,006✔
353
    }
96,006✔
354
    m_last_start_key = first_key;
885,591✔
355

448,281✔
356
    // Check if we can expect to find more keys
448,281✔
357
    if (m_results_ndx < m_results_end) {
885,591✔
358
        // Check if we should advance to next key to search for
102,699✔
359
        while (first_key > m_actual_key) {
72,267,321✔
360
            m_results_ndx++;
72,078,156✔
361
            if (m_results_ndx == m_results_end) {
72,078,156✔
362
                return not_found;
1,473✔
363
            }
1,473✔
364
            m_actual_key = get_internal(m_results_ndx);
72,076,683✔
365
        }
72,076,683✔
366

102,699✔
367
        // If actual_key is bigger than last key, it is not in this leaf
102,699✔
368
        ObjKey last_key = cluster->get_real_key(end - 1);
189,885✔
369
        if (m_actual_key > last_key)
189,165✔
370
            return not_found;
116,442✔
371

39,600✔
372
        // Now actual_key must be found in leaf keys
39,600✔
373
        return cluster->lower_bound_key(ObjKey(m_actual_key.value - cluster->get_offset()));
72,723✔
374
    }
72,723✔
375
    return not_found;
694,953✔
376
}
694,953✔
377

378
void StringNode<Equal>::_search_index_init()
379
{
3,180✔
380
    REALM_ASSERT(bool(m_index_evaluator));
3,180✔
381
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,180✔
382
    m_index_evaluator->init(index, StringData(StringNodeBase::m_value));
3,180✔
383
}
3,180✔
384

385
bool StringNode<Equal>::do_consume_condition(ParentNode& node)
386
{
462✔
387
    // Don't use the search index if present since we're in a scenario where
231✔
388
    // it'd be slower
231✔
389
    m_index_evaluator.reset();
462✔
390

231✔
391
    auto& other = static_cast<StringNode<Equal>&>(node);
462✔
392
    REALM_ASSERT(m_condition_column_key == other.m_condition_column_key);
462✔
393
    REALM_ASSERT(other.m_needles.empty());
462✔
394
    if (m_needles.empty()) {
462✔
395
        m_needles.insert(m_value ? StringData(*m_value) : StringData());
156✔
396
    }
162✔
397
    if (auto& str = other.m_value) {
462✔
398
        m_needle_storage.push_back(std::make_unique<char[]>(str->size()));
444✔
399
        std::copy(str->data(), str->data() + str->size(), m_needle_storage.back().get());
444✔
400
        m_needles.insert(StringData(m_needle_storage.back().get(), str->size()));
444✔
401
    }
444✔
402
    else {
18✔
403
        m_needles.emplace();
18✔
404
    }
18✔
405
    return true;
462✔
406
}
462✔
407

408
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
409
{
841,065✔
410
    if (m_needles.empty()) {
841,065✔
411
        return m_leaf->find_first(m_value, start, end);
840,387✔
412
    }
840,387✔
413
    else {
678✔
414
        if (end == npos)
678✔
415
            end = m_leaf->size();
×
416
        REALM_ASSERT_3(start, <=, end);
678✔
417
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
678✔
418
    }
678✔
419
}
841,065✔
420

421
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
422
{
58,488✔
423
    if (m_needles.empty()) {
58,488✔
424
        return StringNodeEqualBase::describe(state);
58,422✔
425
    }
58,422✔
426

33✔
427
    std::string list_contents;
66✔
428
    bool is_first = true;
66✔
429
    for (auto it : m_needles) {
414✔
430
        StringData sd(it.data(), it.size());
414✔
431
        list_contents += util::format("%1%2", is_first ? "" : ", ", util::serializer::print_value(sd));
381✔
432
        is_first = false;
414✔
433
    }
414✔
434
    std::string col_descr = state.describe_column(ParentNode::m_table, m_condition_column_key);
66✔
435
    std::string desc = util::format("%1 IN {%2}", col_descr, list_contents);
66✔
436
    return desc;
66✔
437
}
66✔
438

439

440
void StringNode<EqualIns>::_search_index_init()
441
{
126✔
442
    auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
126✔
443
    m_index_matches.clear();
126✔
444
    constexpr bool case_insensitive = true;
126✔
445
    index->find_all(m_index_matches, StringData(StringNodeBase::m_value), case_insensitive);
126✔
446
    m_index_evaluator->init(&m_index_matches);
126✔
447
}
126✔
448

449
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
450
{
1,398✔
451
    EqualIns cond;
1,398✔
452
    for (size_t s = start; s < end; ++s) {
26,214✔
453
        StringData t = get_string(s);
24,936✔
454

12,468✔
455
        if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t))
24,936✔
456
            return s;
120✔
457
    }
24,936✔
458

699✔
459
    return not_found;
1,338✔
460
}
1,398✔
461

462
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
463
    : StringNodeEqualBase(v, column)
464
    , m_link_map(std::move(lm))
465
{
324✔
466
    if (!m_link_map)
324✔
467
        m_link_map = std::make_unique<LinkMap>();
312✔
468
}
324✔
469

470
void StringNodeFulltext::table_changed()
471
{
336✔
472
    StringNodeEqualBase::table_changed();
336✔
473
    m_link_map->set_base_table(m_table);
336✔
474
}
336✔
475

476
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
477
    : StringNodeEqualBase(other)
478
{
432✔
479
    m_link_map = std::make_unique<LinkMap>(*other.m_link_map);
432✔
480
}
432✔
481

482
void StringNodeFulltext::_search_index_init()
483
{
360✔
484
    auto index = m_link_map->get_target_table()->get_search_index(ParentNode::m_condition_column_key);
360✔
485
    REALM_ASSERT(index && index->is_fulltext_index());
360✔
486
    m_index_matches.clear();
360✔
487
    index->find_all_fulltext(m_index_matches, StringData(StringNodeBase::m_value));
360✔
488

180✔
489
    // If links exists, use backlinks to find the original objects
180✔
490
    if (m_link_map->links_exist()) {
360✔
491
        std::set<ObjKey> tmp;
12✔
492
        for (auto k : m_index_matches) {
24✔
493
            auto ndxs = m_link_map->get_origin_ndxs(k);
24✔
494
            tmp.insert(ndxs.begin(), ndxs.end());
24✔
495
        }
24✔
496
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
497
    }
12✔
498

180✔
499
    m_index_evaluator = IndexEvaluator{};
360✔
500
    m_index_evaluator->init(&m_index_matches);
360✔
501
}
360✔
502

503
std::unique_ptr<ArrayPayload> TwoColumnsNodeBase::update_cached_leaf_pointers_for_column(Allocator& alloc,
504
                                                                                         const ColKey& col_key)
505
{
58,248✔
506
    switch (col_key.get_type()) {
58,248✔
507
        case col_type_Int:
12,840✔
508
            if (col_key.is_nullable()) {
12,840✔
509
                return std::make_unique<ArrayIntNull>(alloc);
3,444✔
510
            }
3,444✔
511
            return std::make_unique<ArrayInteger>(alloc);
9,396✔
512
        case col_type_Bool:
6,306✔
513
            return std::make_unique<ArrayBoolNull>(alloc);
3,216✔
514
        case col_type_String:
6,426✔
515
            return std::make_unique<ArrayString>(alloc);
3,456✔
516
        case col_type_Binary:
4,698✔
517
            return std::make_unique<ArrayBinary>(alloc);
×
518
        case col_type_Mixed:
6,432✔
519
            return std::make_unique<ArrayMixed>(alloc);
3,468✔
520
        case col_type_Timestamp:
7,974✔
521
            return std::make_unique<ArrayTimestamp>(alloc);
6,552✔
522
        case col_type_Float:
12,606✔
523
            return std::make_unique<ArrayFloatNull>(alloc);
12,606✔
524
        case col_type_Double:
12,654✔
525
            return std::make_unique<ArrayDoubleNull>(alloc);
12,654✔
526
        case col_type_Decimal:
6,426✔
527
            return std::make_unique<ArrayDecimal128>(alloc);
3,456✔
528
        case col_type_Link:
4,698✔
529
            return std::make_unique<ArrayKey>(alloc);
×
530
        case col_type_ObjectId:
4,698✔
531
            return std::make_unique<ArrayObjectIdNull>(alloc);
×
532
        case col_type_UUID:
4,698✔
533
            return std::make_unique<ArrayUUIDNull>(alloc);
×
534
        case col_type_TypedLink:
4,698✔
535
        case col_type_BackLink:
✔
536
        case col_type_LinkList:
✔
537
            break;
×
538
    };
×
539
    REALM_UNREACHABLE();
540
    return {};
×
541
}
×
542

543
size_t size_of_list_from_ref(ref_type ref, Allocator& alloc, ColumnType col_type, bool is_nullable)
544
{
204✔
545
    switch (col_type) {
204✔
546
        case col_type_Int: {
84✔
547
            if (is_nullable) {
84✔
548
                BPlusTree<util::Optional<Int>> list(alloc);
6✔
549
                list.init_from_ref(ref);
6✔
550
                return list.size();
6✔
551
            }
6✔
552
            else {
78✔
553
                BPlusTree<Int> list(alloc);
78✔
554
                list.init_from_ref(ref);
78✔
555
                return list.size();
78✔
556
            }
78✔
557
        }
×
558
        case col_type_Bool: {
12✔
559
            BPlusTree<Bool> list(alloc);
12✔
560
            list.init_from_ref(ref);
12✔
561
            return list.size();
12✔
562
        }
×
563
        case col_type_String: {
12✔
564
            BPlusTree<String> list(alloc);
12✔
565
            list.init_from_ref(ref);
12✔
566
            return list.size();
12✔
567
        }
×
568
        case col_type_Binary: {
12✔
569
            BPlusTree<Binary> list(alloc);
12✔
570
            list.init_from_ref(ref);
12✔
571
            return list.size();
12✔
572
        }
×
573
        case col_type_Timestamp: {
12✔
574
            BPlusTree<Timestamp> list(alloc);
12✔
575
            list.init_from_ref(ref);
12✔
576
            return list.size();
12✔
577
        }
×
578
        case col_type_Float: {
12✔
579
            BPlusTree<Float> list(alloc);
12✔
580
            list.init_from_ref(ref);
12✔
581
            return list.size();
12✔
582
        }
×
583
        case col_type_Double: {
12✔
584
            BPlusTree<Double> list(alloc);
12✔
585
            list.init_from_ref(ref);
12✔
586
            return list.size();
12✔
587
        }
×
588
        case col_type_Decimal: {
12✔
589
            BPlusTree<Decimal128> list(alloc);
12✔
590
            list.init_from_ref(ref);
12✔
591
            return list.size();
12✔
592
        }
×
593
        case col_type_ObjectId: {
12✔
594
            BPlusTree<ObjectId> list(alloc);
12✔
595
            list.init_from_ref(ref);
12✔
596
            return list.size();
12✔
597
        }
×
598
        case col_type_UUID: {
12✔
599
            BPlusTree<UUID> list(alloc);
12✔
600
            list.init_from_ref(ref);
12✔
601
            return list.size();
12✔
602
        }
×
603
        case col_type_Mixed: {
✔
604
            BPlusTree<Mixed> list(alloc);
×
605
            list.init_from_ref(ref);
×
606
            return list.size();
×
607
        }
×
608
        case col_type_LinkList: {
12✔
609
            BPlusTree<ObjKey> list(alloc);
12✔
610
            list.init_from_ref(ref);
12✔
611
            return list.size();
12✔
612
        }
×
613
        case col_type_TypedLink: {
✔
614
            BPlusTree<ObjLink> list(alloc);
×
615
            list.init_from_ref(ref);
×
616
            return list.size();
×
617
        }
×
618
        case col_type_Link:
✔
619
        case col_type_BackLink:
✔
620
            break;
×
621
    }
×
622
    REALM_TERMINATE("Unsupported column type.");
623
}
×
624

625
size_t NotNode::find_first_local(size_t start, size_t end)
626
{
13,716✔
627
    if (start <= m_known_range_start && end >= m_known_range_end) {
13,716✔
628
        return find_first_covers_known(start, end);
1,992✔
629
    }
1,992✔
630
    else if (start >= m_known_range_start && end <= m_known_range_end) {
11,724✔
631
        return find_first_covered_by_known(start, end);
11,208✔
632
    }
11,208✔
633
    else if (start < m_known_range_start && end >= m_known_range_start) {
516!
634
        return find_first_overlap_lower(start, end);
×
635
    }
×
636
    else if (start <= m_known_range_end && end > m_known_range_end) {
516✔
637
        return find_first_overlap_upper(start, end);
168✔
638
    }
168✔
639
    else { // start > m_known_range_end || end < m_known_range_start
348✔
640
        return find_first_no_overlap(start, end);
348✔
641
    }
348✔
642
}
13,716✔
643

644
bool NotNode::evaluate_at(size_t rowndx)
645
{
17,616✔
646
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
17,616✔
647
}
17,616✔
648

649
void NotNode::update_known(size_t start, size_t end, size_t first)
650
{
2,412✔
651
    m_known_range_start = start;
2,412✔
652
    m_known_range_end = end;
2,412✔
653
    m_first_in_known_range = first;
2,412✔
654
}
2,412✔
655

656
size_t NotNode::find_first_loop(size_t start, size_t end)
657
{
15,708✔
658
    for (size_t i = start; i < end; ++i) {
20,376✔
659
        if (evaluate_at(i)) {
17,616✔
660
            return i;
12,948✔
661
        }
12,948✔
662
    }
17,616✔
663
    return not_found;
9,234✔
664
}
15,708✔
665

666
size_t NotNode::find_first_covers_known(size_t start, size_t end)
667
{
1,992✔
668
    // CASE: start-end covers the known range
996✔
669
    // [    ######    ]
996✔
670
    REALM_ASSERT_DEBUG(start <= m_known_range_start && end >= m_known_range_end);
1,992✔
671
    size_t result = find_first_loop(start, m_known_range_start);
1,992✔
672
    if (result != not_found) {
1,992✔
673
        update_known(start, m_known_range_end, result);
×
674
    }
×
675
    else {
1,992✔
676
        if (m_first_in_known_range != not_found) {
1,992✔
677
            update_known(start, m_known_range_end, m_first_in_known_range);
×
678
            result = m_first_in_known_range;
×
679
        }
×
680
        else {
1,992✔
681
            result = find_first_loop(m_known_range_end, end);
1,992✔
682
            update_known(start, end, result);
1,992✔
683
        }
1,992✔
684
    }
1,992✔
685
    return result;
1,992✔
686
}
1,992✔
687

688
size_t NotNode::find_first_covered_by_known(size_t start, size_t end)
689
{
11,208✔
690
    REALM_ASSERT_DEBUG(start >= m_known_range_start && end <= m_known_range_end);
11,208✔
691
    // CASE: the known range covers start-end
5,604✔
692
    // ###[#####]###
5,604✔
693
    if (m_first_in_known_range != not_found) {
11,208✔
694
        if (m_first_in_known_range > end) {
11,208✔
695
            return not_found;
×
696
        }
×
697
        else if (m_first_in_known_range >= start) {
11,208✔
698
            return m_first_in_known_range;
×
699
        }
×
700
    }
11,208✔
701
    // The first known match is before start, so we can't use the results to improve
5,604✔
702
    // heuristics.
5,604✔
703
    return find_first_loop(start, end);
11,208✔
704
}
11,208✔
705

706
size_t NotNode::find_first_overlap_lower(size_t start, size_t end)
707
{
×
708
    REALM_ASSERT_DEBUG(start < m_known_range_start && end >= m_known_range_start && end <= m_known_range_end);
×
709
    static_cast<void>(end);
×
710
    // CASE: partial overlap, lower end
711
    // [   ###]#####
712
    size_t result;
×
713
    result = find_first_loop(start, m_known_range_start);
×
714
    if (result == not_found) {
×
715
        result = m_first_in_known_range;
×
716
    }
×
717
    update_known(start, m_known_range_end, result);
×
718
    return result < end ? result : not_found;
×
719
}
×
720

721
size_t NotNode::find_first_overlap_upper(size_t start, size_t end)
722
{
168✔
723
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
168✔
724
    // CASE: partial overlap, upper end
84✔
725
    // ####[###    ]
84✔
726
    size_t result;
168✔
727
    if (m_first_in_known_range != not_found) {
168✔
728
        if (m_first_in_known_range >= start) {
144✔
729
            result = m_first_in_known_range;
×
730
            update_known(m_known_range_start, end, result);
×
731
        }
×
732
        else {
144✔
733
            result = find_first_loop(start, end);
144✔
734
            update_known(m_known_range_start, end, m_first_in_known_range);
144✔
735
        }
144✔
736
    }
144✔
737
    else {
24✔
738
        result = find_first_loop(m_known_range_end, end);
24✔
739
        update_known(m_known_range_start, end, result);
24✔
740
    }
24✔
741
    return result;
168✔
742
}
168✔
743

744
size_t NotNode::find_first_no_overlap(size_t start, size_t end)
745
{
348✔
746
    REALM_ASSERT_DEBUG((start < m_known_range_start && end < m_known_range_start) ||
348!
747
                       (start > m_known_range_end && end > m_known_range_end));
348✔
748
    // CASE: no overlap
174✔
749
    // ### [    ]   or    [    ] ####
174✔
750
    // if input is a larger range, discard and replace with results.
174✔
751
    size_t result = find_first_loop(start, end);
348✔
752
    if (end - start > m_known_range_end - m_known_range_start) {
348✔
753
        update_known(start, end, result);
252✔
754
    }
252✔
755
    return result;
348✔
756
}
348✔
757

758
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
759
    : m_expression(std::move(expression))
760
{
63,954✔
761
    m_dT = 50.0;
63,954✔
762
}
63,954✔
763

764
void ExpressionNode::table_changed()
765
{
69,303✔
766
    m_expression->set_base_table(m_table);
69,303✔
767
}
69,303✔
768

769
void ExpressionNode::cluster_changed()
770
{
92,802✔
771
    m_expression->set_cluster(m_cluster);
92,802✔
772
}
92,802✔
773

774
void ExpressionNode::init(bool will_query_ranges)
775
{
73,431✔
776
    ParentNode::init(will_query_ranges);
73,431✔
777
    m_dT = m_expression->init();
73,431✔
778
}
73,431✔
779

780
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
781
{
17,376✔
782
    if (m_expression) {
17,376✔
783
        return m_expression->description(state);
17,376✔
784
    }
17,376✔
UNCOV
785
    else {
×
UNCOV
786
        return "empty expression";
×
UNCOV
787
    }
×
788
}
17,376✔
789

790
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
791
{
3,564✔
792
    m_expression->collect_dependencies(tables);
3,564✔
793
}
3,564✔
794

795
size_t ExpressionNode::find_first_local(size_t start, size_t end)
796
{
1,730,304✔
797
    return m_expression->find_first(start, end);
1,730,304✔
798
}
1,730,304✔
799

800
std::unique_ptr<ParentNode> ExpressionNode::clone() const
801
{
59,295✔
802
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
59,295✔
803
}
59,295✔
804

805
ExpressionNode::ExpressionNode(const ExpressionNode& from)
806
    : ParentNode(from)
807
    , m_expression(from.m_expression->clone())
808
{
59,298✔
809
}
59,298✔
810

811
template <>
812
size_t LinksToNode<Equal>::find_first_local(size_t start, size_t end)
813
{
6,049,998✔
814
    if (m_condition_column_key.is_collection()) {
6,049,998✔
815
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
6,013,260✔
816
        if (m_condition_column_key.is_dictionary()) {
6,013,260✔
817
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
96✔
818
            Array top(alloc);
96✔
819
            for (size_t i = start; i < end; i++) {
132✔
820
                if (ref_type ref = get_ref(i)) {
108✔
821
                    top.init_from_ref(ref);
108✔
822
                    BPlusTree<Mixed> values(alloc);
108✔
823
                    values.set_parent(&top, 1);
108✔
824
                    values.init_from_parent();
108✔
825
                    for (auto& key : m_target_keys) {
156✔
826
                        ObjLink link(target_table_key, key);
156✔
827
                        if (values.find_first(link) != not_found)
156✔
828
                            return i;
72✔
829
                    }
156✔
830
                }
108✔
831
            }
108✔
832
        }
96✔
833
        else {
6,013,164✔
834
            // LinkLists never contain null
3,006,582✔
835
            if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end)
6,013,164✔
836
                return not_found;
6✔
837

3,006,579✔
838
            BPlusTree<ObjKey> links(alloc);
6,013,158✔
839
            for (size_t i = start; i < end; i++) {
6,021,486✔
840
                if (ref_type ref = get_ref(i)) {
6,020,952✔
841
                    links.init_from_ref(ref);
6,016,680✔
842
                    for (auto& key : m_target_keys) {
6,016,692✔
843
                        if (key) {
6,016,692✔
844
                            if (links.find_first(key) != not_found)
6,016,692✔
845
                                return i;
6,012,624✔
846
                        }
6,016,692✔
847
                    }
6,016,692✔
848
                }
6,016,680✔
849
            }
6,020,952✔
850
        }
6,013,158✔
851
    }
6,013,260✔
852
    else if (m_list) {
36,738✔
853
        for (auto& key : m_target_keys) {
36,738✔
854
            auto pos = m_list->find_first(key, start, end);
36,738✔
855
            if (pos != realm::npos) {
36,738✔
856
                return pos;
7,650✔
857
            }
7,650✔
858
        }
36,738✔
859
    }
36,738✔
860

3,024,999✔
861
    return not_found;
3,039,822✔
862
}
6,049,998✔
863

864
template <>
865
size_t LinksToNode<NotEqual>::find_first_local(size_t start, size_t end)
866
{
6,696✔
867
    // NotEqual only makes sense for a single value
3,348✔
868
    REALM_ASSERT(m_target_keys.size() == 1);
6,696✔
869
    ObjKey key = m_target_keys[0];
6,696✔
870

3,348✔
871
    if (m_condition_column_key.is_collection()) {
6,696✔
872
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
2,418✔
873
        if (m_condition_column_key.is_dictionary()) {
2,418✔
874
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
36✔
875
            Array top(alloc);
36✔
876
            for (size_t i = start; i < end; i++) {
48✔
877
                if (ref_type ref = get_ref(i)) {
36✔
878
                    top.init_from_ref(ref);
36✔
879
                    BPlusTree<Mixed> values(alloc);
36✔
880
                    values.set_parent(&top, 1);
36✔
881
                    values.init_from_parent();
36✔
882

18✔
883
                    ObjLink link(target_table_key, key);
36✔
884
                    bool found = false;
36✔
885
                    values.for_all([&](const Mixed& val) {
48✔
886
                        if (val != link) {
48✔
887
                            found = true;
24✔
888
                        }
24✔
889
                        return !found;
48✔
890
                    });
48✔
891
                    if (found)
36✔
892
                        return i;
24✔
893
                }
36✔
894
            }
36✔
895
        }
36✔
896
        else {
2,382✔
897
            BPlusTree<ObjKey> links(alloc);
2,382✔
898
            for (size_t i = start; i < end; i++) {
4,746✔
899
                if (ref_type ref = get_ref(i)) {
4,512✔
900
                    links.init_from_ref(ref);
2,364✔
901
                    auto sz = links.size();
2,364✔
902
                    for (size_t j = 0; j < sz; j++) {
2,610✔
903
                        if (links.get(j) != key) {
2,394✔
904
                            return i;
2,148✔
905
                        }
2,148✔
906
                    }
2,394✔
907
                }
2,364✔
908
            }
4,512✔
909
        }
2,382✔
910
    }
2,418✔
911
    else if (m_list) {
4,278✔
912
        for (size_t i = start; i < end; i++) {
4,782✔
913
            if (m_list->get(i) != key) {
4,614✔
914
                return i;
4,110✔
915
            }
4,110✔
916
        }
4,614✔
917
    }
4,278✔
918

3,348✔
919
    return not_found;
3,555✔
920
}
6,696✔
921

922
} // 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

© 2026 Coveralls, Inc