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

realm / realm-core / 2085

01 Mar 2024 12:26PM UTC coverage: 90.926% (-0.001%) from 90.927%
2085

push

Evergreen

jedelbo
Avoid doing unneeded logger work in Replication

Most of the replication log statements do some work including memory
allocations which are then thrown away if the log level it too high, so always
check the log level first. A few places don't actually benefit from this, but
it's easier to consistently check the log level every time.

93986 of 173116 branches covered (54.29%)

63 of 100 new or added lines in 2 files covered. (63.0%)

114 existing lines in 17 files now uncovered.

238379 of 262169 relevant lines covered (90.93%)

6007877.32 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,094,935✔
37
}
3,094,935✔
38

39

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

2,588,580✔
46
    while (REALM_LIKELY(start < end)) {
5,553,888✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
5,526,771✔
48

2,760,087✔
49
        if (m != start) {
5,526,771✔
50
            // Pointer advanced - we will have to check all other conditions
1,418,472✔
51
            nb_cond_to_test = sz;
2,797,926✔
52
            start = m;
2,797,926✔
53
        }
2,797,926✔
54

2,760,087✔
55
        nb_cond_to_test--;
5,526,771✔
56

2,760,087✔
57
        // Optimized for one condition where this will be true first time
2,760,087✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
5,526,771✔
59
            return m;
5,342,547✔
60

185,106✔
61
        current_cond++;
369,330✔
62

185,106✔
63
        if (current_cond == sz)
369,330✔
64
            current_cond = 0;
123,651✔
65
    }
369,330✔
66
    return not_found;
2,602,098✔
67
}
5,184,558✔
68

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

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

2,751,447✔
99
    m_state = st;
6,076,569✔
100
    m_state->set_payload_column(source_column);
6,076,569✔
101
    size_t local_matches = 0;
6,076,569✔
102

2,751,447✔
103
    if (m_children.size() == 1) {
6,141,900✔
104
        return find_all_local(start, end);
6,117,627✔
105
    }
6,117,627✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,507,920✔
108
    for (;;) {
2,148,028,489✔
109
        if (local_matches == local_limit) {
1,087,110✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
18,426✔
111
            return r + 1;
18,426✔
112
        }
18,426✔
113

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

514,905✔
122
        local_matches++;
1,032,618✔
123

514,905✔
124
        // Find first match in remaining condition nodes
514,905✔
125
        size_t m = r;
1,032,618✔
126

514,905✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,618,776✔
128
            m = m_children[c]->find_first_local(r, r + 1);
1,091,850✔
129
            if (m != r) {
1,091,850✔
130
                break;
505,692✔
131
            }
505,692✔
132
        }
1,091,850✔
133

514,905✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
514,905✔
135
        // match
514,905✔
136
        if (m == r) {
1,032,618✔
137
            bool cont = st->match(r);
526,965✔
138
            if (!cont) {
526,965✔
139
                return static_cast<size_t>(-1);
33✔
140
            }
33✔
141
        }
526,965✔
142
    }
1,032,618✔
143
}
2,147,507,920✔
144

145
size_t ParentNode::find_all_local(size_t start, size_t end)
146
{
297,105✔
147
    while (start < end) {
10,806,546✔
148
        start = find_first_local(start, end);
10,509,471✔
149
        if (start != not_found) {
10,509,471✔
150
            bool cont = m_state->match(start);
10,330,821✔
151
            if (!cont) {
10,330,821✔
152
                return static_cast<size_t>(-1);
30✔
153
            }
30✔
154
            start++;
10,330,791✔
155
        }
10,330,791✔
156
    }
10,509,471✔
157
    return end;
297,090✔
158
}
297,105✔
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,154✔
272
    StringNodeBase::init(will_query_ranges);
38,154✔
273

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

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

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

557,055✔
294
    if (m_index_evaluator) {
1,081,446✔
295
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,620✔
296
    }
241,620✔
297

430,764✔
298
    return _find_first_local(start, end);
839,826✔
299
}
839,826✔
300

301

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

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

328,398✔
313
    m_index_matches.reset();
656,796✔
314
    switch (fr) {
656,796✔
315
        case FindRes_single:
12,396✔
316
            m_actual_key = ObjKey(res.payload);
12,396✔
317
            m_results_end = 1;
12,396✔
318
            break;
12,396✔
319
        case FindRes_column:
1,911✔
320
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
1,911✔
321
            m_results_start = res.start_ndx;
1,911✔
322
            m_results_end = res.end_ndx;
1,911✔
323
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
1,911✔
324
            break;
1,911✔
325
        case FindRes_not_found:
642,489✔
326
            m_results_end = 0;
642,489✔
327
            break;
642,489✔
328
    }
656,796✔
329
    m_results_ndx = m_results_start;
656,796✔
330
}
656,796✔
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,300✔
365
            m_results_ndx++;
72,078,123✔
366
            if (m_results_ndx == m_results_end) {
72,078,123✔
367
                return not_found;
1,473✔
368
            }
1,473✔
369
            m_actual_key = get_internal(m_results_ndx);
72,076,650✔
370
        }
72,076,650✔
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
{
838,434✔
415
    if (m_needles.empty()) {
838,434✔
416
        return m_leaf->find_first(m_string_value, start, end);
837,750✔
417
    }
837,750✔
418
    else {
684✔
419
        if (end == npos)
684✔
420
            end = m_leaf->size();
×
421
        REALM_ASSERT_3(start, <=, end);
684✔
422
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
684✔
423
    }
684✔
424
}
838,434✔
425

426
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
427
{
3,654✔
428
    if (m_needles.empty()) {
3,654✔
429
        return StringNodeEqualBase::describe(state);
3,588✔
430
    }
3,588✔
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,248✔
511
    switch (col_key.get_type()) {
58,248✔
512
        case col_type_Int:
12,840✔
513
            if (col_key.is_nullable()) {
12,840✔
514
                return std::make_unique<ArrayIntNull>(alloc);
3,444✔
515
            }
3,444✔
516
            return std::make_unique<ArrayInteger>(alloc);
9,396✔
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,606✔
528
            return std::make_unique<ArrayFloatNull>(alloc);
12,606✔
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,338✔
764
    m_dT = 50.0;
70,338✔
765
}
70,338✔
766

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

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

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

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

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

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

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

808
ExpressionNode::ExpressionNode(const ExpressionNode& from)
809
    : ParentNode(from)
810
    , m_expression(from.m_expression->clone())
811
{
66,105✔
812
}
66,105✔
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