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

realm / realm-core / thomas.goyne_232

13 Mar 2024 01:00AM UTC coverage: 91.787% (+0.9%) from 90.924%
thomas.goyne_232

Pull #7402

Evergreen

tgoyne
Add more UpdateIfNeeded tests
Pull Request #7402: Make Obj trivial and add a separate ObjCollectionParent type

94460 of 174600 branches covered (54.1%)

496 of 559 new or added lines in 21 files covered. (88.73%)

848 existing lines in 34 files now uncovered.

242761 of 264484 relevant lines covered (91.79%)

6342666.36 hits per line

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

91.38
/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,043,869✔
37
}
3,043,869✔
38

39

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

2,647,650✔
46
    while (REALM_LIKELY(start < end)) {
5,011,782✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
4,988,802✔
48

2,813,586✔
49
        if (m != start) {
4,988,802✔
50
            // Pointer advanced - we will have to check all other conditions
1,440,057✔
51
            nb_cond_to_test = sz;
2,722,854✔
52
            start = m;
2,722,854✔
53
        }
2,722,854✔
54

2,813,586✔
55
        nb_cond_to_test--;
4,988,802✔
56

2,813,586✔
57
        // Optimized for one condition where this will be true first time
2,813,586✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
4,988,802✔
59
            return m;
4,817,103✔
60

179,214✔
61
        current_cond++;
350,913✔
62

179,214✔
63
        if (current_cond == sz)
350,913✔
64
            current_cond = 0;
118,905✔
65
    }
350,913✔
66
    return not_found;
2,657,352✔
67
}
4,660,869✔
68

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

79
bool ParentNode::match(const Obj& obj)
80
{
1,265,361✔
81
    return obj.evaluate([this](const Cluster* cluster, size_t row) {
1,265,358✔
82
        set_cluster(cluster);
1,265,295✔
83
        size_t m = find_first(row, row + 1);
1,265,295✔
84
        return m != npos;
1,265,295✔
85
    });
1,265,295✔
86
}
1,265,361✔
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,571,453✔
91
    // aggregate called on non-integer column type. Speed of this function is not as critical as speed of the
2,220,030✔
92
    // integer version, because find_first_local() is relatively slower here (because it's non-integers).
2,220,030✔
93
    //
2,220,030✔
94
    // Todo: Two speedups are possible. Simple: Initially test if there are no sub criterias and run
2,220,030✔
95
    // find_first_local()
2,220,030✔
96
    // in a tight loop if so (instead of testing if there are sub criterias after each match). Harder: Specialize
2,220,030✔
97
    // data type array to make array call match() directly on each match, like for integers.
2,220,030✔
98

2,220,030✔
99
    m_state = st;
5,571,453✔
100
    m_state->set_payload_column(source_column);
5,571,453✔
101
    size_t local_matches = 0;
5,571,453✔
102

2,220,030✔
103
    if (m_children.size() == 1) {
6,191,985✔
104
        return find_all_local(start, end);
6,168,108✔
105
    }
6,168,108✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,507,524✔
108
    for (;;) {
2,147,908,792✔
109
        if (local_matches == local_limit) {
970,743✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
15,960✔
111
            return r + 1;
15,960✔
112
        }
15,960✔
113

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

517,698✔
122
        local_matches++;
918,717✔
123

517,698✔
124
        // Find first match in remaining condition nodes
517,698✔
125
        size_t m = r;
918,717✔
126

517,698✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,419,342✔
128
            m = m_children[c]->find_first_local(r, r + 1);
977,949✔
129
            if (m != r) {
977,949✔
130
                break;
477,324✔
131
            }
477,324✔
132
        }
977,949✔
133

517,698✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
517,698✔
135
        // match
517,698✔
136
        if (m == r) {
918,717✔
137
            bool cont = st->match(r);
441,393✔
138
            if (!cont) {
441,393✔
139
                return static_cast<size_t>(-1);
18✔
140
            }
18✔
141
        }
441,393✔
142
    }
918,717✔
143
}
2,147,507,524✔
144

145
size_t ParentNode::find_all_local(size_t start, size_t end)
146
{
320,295✔
147
    while (start < end) {
10,707,156✔
148
        start = find_first_local(start, end);
10,386,891✔
149
        if (start != not_found) {
10,386,891✔
150
            bool cont = m_state->match(start);
10,183,734✔
151
            if (!cont) {
10,183,734✔
152
                return static_cast<size_t>(-1);
30✔
153
            }
30✔
154
            start++;
10,183,704✔
155
        }
10,183,704✔
156
    }
10,386,891✔
157
    return end;
320,280✔
158
}
320,295✔
159

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

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

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

255✔
180
    if (m_index_evaluator) {
510✔
181
        return m_index_evaluator->do_search_index(m_cluster, start, end);
18✔
182
    }
18✔
183
    else {
492✔
184
        return m_leaf->find_first(m_value, start, end);
492✔
185
    }
492✔
186

187
    return not_found;
×
188
}
×
189

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

48✔
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
6✔
211
            // having the same StringIndex hash values
6✔
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
9✔
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

57✔
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

57✔
267
    return not_found;
90✔
268
}
114✔
269

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

18,939✔
274
    const bool uses_index = has_search_index();
38,151✔
275
    if (m_is_string_enum) {
38,151✔
276
        m_dT = 1.0;
2,658✔
277
    }
2,658✔
278
    else if (uses_index) {
35,493✔
279
        m_dT = 0.0;
3,510✔
280
    }
3,510✔
281
    else {
31,983✔
282
        m_dT = 10.0;
31,983✔
283
    }
31,983✔
284

18,939✔
285
    if (uses_index) {
38,151✔
286
        _search_index_init();
3,972✔
287
    }
3,972✔
288
}
38,151✔
289

290
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
291
{
1,065,366✔
292
    REALM_ASSERT(m_table);
1,065,366✔
293

551,910✔
294
    if (m_index_evaluator) {
1,065,366✔
295
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,620✔
296
    }
241,620✔
297

425,619✔
298
    return _find_first_local(start, end);
823,746✔
299
}
823,746✔
300

301

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

328,392✔
309
    m_last_start_key = ObjKey();
656,790✔
310
    m_results_start = 0;
656,790✔
311
    fr = index->find_all_no_copy(value, res);
656,790✔
312

328,392✔
313
    m_index_matches.reset();
656,790✔
314
    switch (fr) {
656,790✔
315
        case FindRes_single:
12,354✔
316
            m_actual_key = ObjKey(res.payload);
12,354✔
317
            m_results_end = 1;
12,354✔
318
            break;
12,354✔
319
        case FindRes_column:
1,947✔
320
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
1,947✔
321
            m_results_start = res.start_ndx;
1,947✔
322
            m_results_end = res.end_ndx;
1,947✔
323
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
1,947✔
324
            break;
1,947✔
325
        case FindRes_not_found:
642,495✔
326
            m_results_end = 0;
642,495✔
327
            break;
642,495✔
328
    }
656,769✔
329
    m_results_ndx = m_results_start;
656,769✔
330
}
656,769✔
331

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

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

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

448,290✔
361
    // Check if we can expect to find more keys
448,290✔
362
    if (m_results_ndx < m_results_end) {
885,609✔
363
        // Check if we should advance to next key to search for
102,705✔
364
        while (first_key > m_actual_key) {
72,267,204✔
365
            m_results_ndx++;
72,078,027✔
366
            if (m_results_ndx == m_results_end) {
72,078,027✔
367
                return not_found;
1,473✔
368
            }
1,473✔
369
            m_actual_key = get_internal(m_results_ndx);
72,076,554✔
370
        }
72,076,554✔
371

102,705✔
372
        // If actual_key is bigger than last key, it is not in this leaf
102,705✔
373
        ObjKey last_key = cluster->get_real_key(end - 1);
189,897✔
374
        if (m_actual_key > last_key)
189,177✔
375
            return not_found;
116,442✔
376

39,606✔
377
        // Now actual_key must be found in leaf keys
39,606✔
378
        return cluster->lower_bound_key(ObjKey(m_actual_key.value - cluster->get_offset()));
72,735✔
379
    }
72,735✔
380
    return not_found;
694,959✔
381
}
694,959✔
382

383
void StringNode<Equal>::_search_index_init()
384
{
3,468✔
385
    REALM_ASSERT(bool(m_index_evaluator));
3,468✔
386
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,468✔
387
    m_index_evaluator->init(index, StringNodeBase::m_string_value);
3,468✔
388
}
3,468✔
389

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

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

413
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
414
{
822,219✔
415
    if (m_needles.empty()) {
822,219✔
416
        return m_leaf->find_first(m_string_value, start, end);
821,565✔
417
    }
821,565✔
418
    else {
654✔
419
        if (end == npos)
654✔
420
            end = m_leaf->size();
×
421
        REALM_ASSERT_3(start, <=, end);
654✔
422
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
654✔
423
    }
654✔
424
}
822,219✔
425

426
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
427
{
5,244✔
428
    if (m_needles.empty()) {
5,244✔
429
        return StringNodeEqualBase::describe(state);
5,178✔
430
    }
5,178✔
431

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

444

445
void StringNode<EqualIns>::_search_index_init()
446
{
126✔
447
    auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
126✔
448
    m_index_matches.clear();
126✔
449
    constexpr bool case_insensitive = true;
126✔
450
    index->find_all(m_index_matches, StringNodeBase::m_string_value, case_insensitive);
126✔
451
    m_index_evaluator->init(&m_index_matches);
126✔
452
}
126✔
453

454
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
455
{
1,398✔
456
    EqualIns cond;
1,398✔
457
    for (size_t s = start; s < end; ++s) {
26,214✔
458
        StringData t = get_string(s);
24,936✔
459

12,468✔
460
        if (cond(m_string_value, m_ucase.c_str(), m_lcase.c_str(), t))
24,936✔
461
            return s;
120✔
462
    }
24,936✔
463

699✔
464
    return not_found;
1,338✔
465
}
1,398✔
466

467
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
468
    : StringNodeEqualBase(v, column)
469
    , m_link_map(std::move(lm))
470
{
342✔
471
    if (!m_link_map)
342✔
472
        m_link_map = std::make_unique<LinkMap>();
330✔
473
}
342✔
474

475
void StringNodeFulltext::table_changed()
476
{
354✔
477
    StringNodeEqualBase::table_changed();
354✔
478
    m_link_map->set_base_table(m_table);
354✔
479
}
354✔
480

481
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
482
    : StringNodeEqualBase(other)
483
{
468✔
484
    m_link_map = std::make_unique<LinkMap>(*other.m_link_map);
468✔
485
}
468✔
486

487
void StringNodeFulltext::_search_index_init()
488
{
378✔
489
    StringIndex* index = m_link_map->get_target_table()->get_string_index(ParentNode::m_condition_column_key);
378✔
490
    REALM_ASSERT(index && index->is_fulltext_index());
378✔
491
    m_index_matches.clear();
378✔
492
    index->find_all_fulltext(m_index_matches, StringNodeBase::m_string_value);
378✔
493

189✔
494
    // If links exists, use backlinks to find the original objects
189✔
495
    if (m_link_map->links_exist()) {
378✔
496
        std::set<ObjKey> tmp;
12✔
497
        for (auto k : m_index_matches) {
24✔
498
            auto keys = m_link_map->get_origin_objkeys(k);
24✔
499
            tmp.insert(keys.begin(), keys.end());
24✔
500
        }
24✔
501
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
502
    }
12✔
503

189✔
504
    m_index_evaluator = IndexEvaluator{};
378✔
505
    m_index_evaluator->init(&m_index_matches);
378✔
506
}
378✔
507

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

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

628
size_t NotNode::find_first_local(size_t start, size_t end)
629
{
18,216✔
630
    if (start <= m_known_range_start && end >= m_known_range_end) {
18,216✔
631
        return find_first_covers_known(start, end);
2,484✔
632
    }
2,484✔
633
    else if (start >= m_known_range_start && end <= m_known_range_end) {
15,732✔
634
        return find_first_covered_by_known(start, end);
15,228✔
635
    }
15,228✔
636
    else if (start < m_known_range_start && end >= m_known_range_start) {
504!
637
        return find_first_overlap_lower(start, end);
×
638
    }
×
639
    else if (start <= m_known_range_end && end > m_known_range_end) {
504✔
640
        return find_first_overlap_upper(start, end);
168✔
641
    }
168✔
642
    else { // start > m_known_range_end || end < m_known_range_start
336✔
643
        return find_first_no_overlap(start, end);
336✔
644
    }
336✔
645
}
18,216✔
646

647
bool NotNode::evaluate_at(size_t rowndx)
648
{
23,904✔
649
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
23,904✔
650
}
23,904✔
651

652
void NotNode::update_known(size_t start, size_t end, size_t first)
653
{
2,892✔
654
    m_known_range_start = start;
2,892✔
655
    m_known_range_end = end;
2,892✔
656
    m_first_in_known_range = first;
2,892✔
657
}
2,892✔
658

659
size_t NotNode::find_first_loop(size_t start, size_t end)
660
{
20,700✔
661
    for (size_t i = start; i < end; ++i) {
27,288✔
662
        if (evaluate_at(i)) {
23,904✔
663
            return i;
17,316✔
664
        }
17,316✔
665
    }
23,904✔
666
    return not_found;
12,042✔
667
}
20,700✔
668

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

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

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

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

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

761
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
762
    : m_expression(std::move(expression))
763
{
70,452✔
764
    m_dT = 50.0;
70,452✔
765
}
70,452✔
766

767
void ExpressionNode::table_changed()
768
{
75,810✔
769
    m_expression->set_base_table(m_table);
75,810✔
770
}
75,810✔
771

772
void ExpressionNode::cluster_changed()
773
{
122,370✔
774
    m_expression->set_cluster(m_cluster);
122,370✔
775
}
122,370✔
776

777
void ExpressionNode::init(bool will_query_ranges)
778
{
85,917✔
779
    ParentNode::init(will_query_ranges);
85,917✔
780
    m_dT = m_expression->init();
85,917✔
781
}
85,917✔
782

783
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
784
{
20,808✔
785
    if (m_expression) {
20,808✔
786
        return m_expression->description(state);
20,808✔
787
    }
20,808✔
UNCOV
788
    else {
×
UNCOV
789
        return "empty expression";
×
UNCOV
790
    }
×
791
}
20,808✔
792

793
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
794
{
9,600✔
795
    m_expression->collect_dependencies(tables);
9,600✔
796
}
9,600✔
797

798
size_t ExpressionNode::find_first_local(size_t start, size_t end)
799
{
1,785,456✔
800
    return m_expression->find_first(start, end);
1,785,456✔
801
}
1,785,456✔
802

803
std::unique_ptr<ParentNode> ExpressionNode::clone() const
804
{
72,228✔
805
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
72,228✔
806
}
72,228✔
807

808
ExpressionNode::ExpressionNode(const ExpressionNode& from)
809
    : ParentNode(from)
810
    , m_expression(from.m_expression->clone())
811
{
72,228✔
812
}
72,228✔
813

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

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

3,025,017✔
864
    return not_found;
3,039,840✔
865
}
6,050,034✔
866

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

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

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

3,378✔
922
    return not_found;
3,591✔
923
}
6,756✔
924

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