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

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

95.16
/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,195,237✔
37
}
3,195,237✔
38

39

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

2,048,397✔
46
    while (REALM_LIKELY(start < end)) {
4,469,271✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
4,438,002✔
48

2,203,158✔
49
        if (m != start) {
4,438,002✔
50
            // Pointer advanced - we will have to check all other conditions
1,252,713✔
51
            nb_cond_to_test = sz;
2,459,643✔
52
            start = m;
2,459,643✔
53
        }
2,459,643✔
54

2,203,158✔
55
        nb_cond_to_test--;
4,438,002✔
56

2,203,158✔
57
        // Optimized for one condition where this will be true first time
2,203,158✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
4,438,002✔
59
            return m;
4,268,490✔
60

167,901✔
61
        current_cond++;
337,413✔
62

167,901✔
63
        if (current_cond == sz)
337,413✔
64
            current_cond = 0;
110,718✔
65
    }
337,413✔
66
    return not_found;
2,066,526✔
67
}
4,131,858✔
68

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

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

2,383,572✔
99
    m_state = st;
5,760,630✔
100
    m_source_column = source_column;
5,760,630✔
101
    size_t local_matches = 0;
5,760,630✔
102

2,383,572✔
103
    if (m_children.size() == 1) {
6,166,380✔
104
        return find_all_local(start, end);
6,144,600✔
105
    }
6,144,600✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,505,427✔
108
    for (;;) {
2,147,934,991✔
109
        if (local_matches == local_limit) {
896,988✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
14,265✔
111
            return r + 1;
14,265✔
112
        }
14,265✔
113

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

419,697✔
122
        local_matches++;
845,922✔
123

419,697✔
124
        // Find first match in remaining condition nodes
419,697✔
125
        size_t m = r;
845,922✔
126

419,697✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,322,796✔
128
            m = m_children[c]->find_first_local(r, r + 1);
911,751✔
129
            if (m != r) {
911,751✔
130
                break;
434,877✔
131
            }
434,877✔
132
        }
911,751✔
133

419,697✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
419,697✔
135
        // match
419,697✔
136
        if (m == r) {
845,922✔
137
            Mixed val;
411,159✔
138
            if (source_column) {
411,159✔
139
                val = source_column->get_any(r);
408✔
140
            }
408✔
141
            bool cont = st->match(r, val);
411,159✔
142
            if (!cont) {
411,159✔
143
                return static_cast<size_t>(-1);
30✔
144
            }
30✔
145
        }
411,159✔
146
    }
845,922✔
147
}
2,147,505,427✔
148

149
size_t ParentNode::find_all_local(size_t start, size_t end)
150
{
306,093✔
151
    while (start < end) {
10,407,204✔
152
        start = find_first_local(start, end);
10,101,111✔
153
        if (start != not_found) {
10,101,111✔
154
            Mixed val;
9,931,968✔
155
            if (m_source_column) {
9,931,968✔
156
                val = m_source_column->get_any(start);
1,014✔
157
            }
1,014✔
158
            bool cont = m_state->match(start, val);
9,931,968✔
159
            if (!cont) {
9,931,968✔
160
                return static_cast<size_t>(-1);
×
161
            }
×
162
            start++;
9,931,968✔
163
        }
9,931,968✔
164
    }
10,101,111✔
165
    return end;
306,093✔
166
}
306,093✔
167

168
void MixedNode<Equal>::init(bool will_query_ranges)
169
{
300✔
170
    MixedNodeBase::init(will_query_ranges);
300✔
171

141✔
172
    REALM_ASSERT(bool(m_index_evaluator) ==
300✔
173
                 (m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General));
300✔
174
    if (m_index_evaluator) {
300✔
175
        auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
99✔
176
        m_index_evaluator->init(index, m_value);
99✔
177
        m_dT = 0.0;
99✔
178
    }
99✔
179
    else {
201✔
180
        m_dT = 10.0;
201✔
181
    }
201✔
182
}
300✔
183

184
size_t MixedNode<Equal>::find_first_local(size_t start, size_t end)
185
{
468✔
186
    REALM_ASSERT(m_table);
468✔
187

222✔
188
    if (m_index_evaluator) {
468✔
189
        return m_index_evaluator->do_search_index(m_cluster, start, end);
×
190
    }
×
191
    else {
468✔
192
        return m_leaf->find_first(m_value, start, end);
468✔
193
    }
468✔
194

195
    return not_found;
×
196
}
×
197

198
void MixedNode<EqualIns>::init(bool will_query_ranges)
199
{
78✔
200
    MixedNodeBase::init(will_query_ranges);
78✔
201

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

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

60✔
246
    EqualIns cond;
60✔
247
    if (m_value.is_type(type_String)) {
60✔
248
        for (size_t i = start; i < end; i++) {
942✔
249
            QueryValue val(m_leaf->get(i));
948✔
250
            StringData val_as_str;
900✔
251
            if (val.is_type(type_String)) {
900✔
252
                val_as_str = val.get<StringData>();
273✔
253
            }
273✔
254
            else if (val.is_type(type_Binary)) {
684✔
255
                val_as_str = StringData(val.get<BinaryData>().data(), val.get<BinaryData>().size());
66✔
256
            }
66✔
257
            if (!val_as_str.is_null() &&
3,324✔
258
                cond(m_value.get<StringData>(), m_ucase.c_str(), m_lcase.c_str(), val_as_str))
3,300✔
259
                return i;
2,409✔
260
        }
2,931✔
261
    }
2,049✔
262
    else {
387✔
263
        for (size_t i = start; i < end; i++) {
1,278✔
264
            QueryValue val(m_leaf->get(i));
915✔
265
            if (cond(val, m_value))
1,269✔
266
                return i;
48✔
267
        }
918✔
268
    }
927✔
269

936✔
270
    return not_found;
936✔
271
}
45✔
272

900✔
273
void StringNodeEqualBase::init(bool will_query_ranges)
18✔
274
{
38,748✔
275
    StringNodeBase::init(will_query_ranges);
38,781✔
276

38,805✔
277
    const bool uses_index = has_search_index();
38,748✔
278
    if (m_is_string_enum) {
38,748✔
279
        m_dT = 1.0;
40,704✔
280
    }
40,704✔
281
    else if (uses_index) {
37,419✔
282
        m_dT = 0.0;
40,977✔
283
    }
40,977✔
284
    else {
37,146✔
285
        m_dT = 10.0;
37,146✔
286
    }
73,863✔
287

40,350✔
288
    if (uses_index) {
40,350✔
289
        _search_index_init();
38,277✔
290
    }
38,277✔
291
}
75,192✔
292

293
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
39,375✔
294
{
565,152✔
295
    REALM_ASSERT(m_table);
565,152✔
296

602,694✔
297
    if (m_index_evaluator) {
563,319✔
298
        return m_index_evaluator->do_search_index(m_cluster, start, end);
126,291✔
299
    }
650,598✔
300

961,335✔
301
    return _find_first_local(start, end);
437,028✔
302
}
961,335✔
303

115,329✔
304

115,329✔
305
void IndexEvaluator::init(StringIndex* index, Mixed value)
306
{
737,823✔
307
    REALM_ASSERT(index);
737,823✔
308
    m_matching_keys = nullptr;
328,845✔
309
    FindRes fr;
328,845✔
310
    InternalFindResult res;
328,845✔
311

657,693✔
312
    m_last_start_key = ObjKey();
657,693✔
313
    m_results_start = 0;
657,693✔
314
    fr = index->find_all_no_copy(value, res);
657,693✔
315

657,693✔
316
    m_index_matches.reset();
328,845✔
317
    switch (fr) {
657,693✔
318
        case FindRes_single:
335,685✔
319
            m_actual_key = ObjKey(res.payload);
335,685✔
320
            m_results_end = 1;
6,837✔
321
            break;
335,685✔
322
        case FindRes_column:
329,616✔
323
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
7,623✔
324
            m_results_start = res.start_ndx;
7,623✔
325
            m_results_end = res.end_ndx;
7,623✔
326
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
7,623✔
327
            break;
1,527✔
328
        case FindRes_not_found:
321,999✔
329
            m_results_end = 0;
321,999✔
330
            break;
321,999✔
331
    }
329,604✔
332
    m_results_ndx = m_results_start;
329,604✔
333
}
650,079✔
334

321,234✔
335
void IndexEvaluator::init(std::vector<ObjKey>* storage)
321,234✔
336
{
329,067✔
337
    REALM_ASSERT(storage);
329,067✔
338
    m_matching_keys = storage;
329,067✔
339
    m_actual_key = ObjKey();
219✔
340
    m_last_start_key = ObjKey();
219✔
341
    m_results_start = 0;
441✔
342
    m_results_end = m_matching_keys->size();
441✔
343
    m_results_ndx = 0;
441✔
344
    if (m_results_start != m_results_end) {
441✔
345
        m_actual_key = m_matching_keys->at(0);
411✔
346
    }
411✔
347
}
441✔
348

222✔
349
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
222✔
350
{
448,473✔
351
    if (start >= end) {
448,473✔
352
        return not_found;
225✔
353
    }
3✔
354

448,281✔
355
    ObjKey first_key = cluster->get_real_key(start);
885,597✔
356
    if (first_key < m_last_start_key) {
885,597✔
357
        // We are not advancing through the clusters. We basically don't know where we are,
48,009✔
358
        // so just start over from the beginning.
48,009✔
359
        m_results_ndx = m_results_start;
48,003✔
360
        m_actual_key = (m_results_start != m_results_end) ? get_internal(m_results_start) : ObjKey();
485,313✔
361
    }
485,313✔
362
    m_last_start_key = first_key;
448,281✔
363

448,281✔
364
    // Check if we can expect to find more keys
496,284✔
365
    if (m_results_ndx < m_results_end) {
484,284✔
366
        // Check if we should advance to next key to search for
150,702✔
367
        while (first_key > m_actual_key) {
36,590,811✔
368
            m_results_ndx++;
36,051,522✔
369
            if (m_results_ndx == m_results_end) {
36,051,522✔
370
                return not_found;
438,030✔
371
            }
720✔
372
            m_actual_key = get_internal(m_results_ndx);
72,164,625✔
373
        }
72,077,439✔
374

36,129,336✔
375
        // If actual_key is bigger than last key, it is not in this leaf
103,452✔
376
        ObjKey last_key = cluster->get_real_key(end - 1);
103,452✔
377
        if (m_actual_key > last_key)
36,127,863✔
378
            return not_found;
36,088,263✔
379

39,600✔
380
        // Now actual_key must be found in leaf keys
39,600✔
381
        return cluster->lower_bound_key(ObjKey(m_actual_key.value - cluster->get_offset()));
126,786✔
382
    }
126,786✔
383
    return not_found;
399,645✔
384
}
345,582✔
385

386
void StringNode<Equal>::_search_index_init()
33,123✔
387
{
34,713✔
388
    REALM_ASSERT(bool(m_index_evaluator));
350,961✔
389
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
350,961✔
390
    m_index_evaluator->init(index, StringData(StringNodeBase::m_value));
1,590✔
391
}
1,590✔
392

1,590✔
393
bool StringNode<Equal>::do_consume_condition(ParentNode& node)
1,590✔
394
{
1,821✔
395
    // Don't use the search index if present since we're in a scenario where
1,821✔
396
    // it'd be slower
1,821✔
397
    m_index_evaluator.reset();
231✔
398

231✔
399
    auto& other = static_cast<StringNode<Equal>&>(node);
462✔
400
    REALM_ASSERT(m_condition_column_key == other.m_condition_column_key);
231✔
401
    REALM_ASSERT(other.m_needles.empty());
231✔
402
    if (m_needles.empty()) {
462✔
403
        m_needles.insert(m_value ? StringData(*m_value) : StringData());
81✔
404
    }
312✔
405
    if (auto& str = other.m_value) {
462✔
406
        m_needle_storage.push_back(std::make_unique<char[]>(str->size()));
453✔
407
        std::copy(str->data(), str->data() + str->size(), m_needle_storage.back().get());
453✔
408
        m_needles.insert(StringData(m_needle_storage.back().get(), str->size()));
297✔
409
    }
303✔
410
    else {
240✔
411
        m_needles.emplace();
231✔
412
    }
231✔
413
    return true;
453✔
414
}
453✔
415

9✔
416
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
9✔
417
{
436,233✔
418
    if (m_needles.empty()) {
436,455✔
419
        return m_leaf->find_first(m_value, start, end);
436,140✔
420
    }
435,909✔
421
    else {
315✔
422
        if (end == npos)
408,600✔
423
            end = m_leaf->size();
408,285✔
424
        REALM_ASSERT_3(start, <=, end);
408,249✔
425
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
408,249✔
426
    }
666✔
427
}
436,575✔
428

429
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
351✔
430
{
29,202✔
431
    if (m_needles.empty()) {
29,202✔
432
        return StringNodeEqualBase::describe(state);
437,103✔
433
    }
28,818✔
434

33✔
435
    std::string list_contents;
29,514✔
436
    bool is_first = true;
29,514✔
437
    for (auto it : m_needles) {
29,655✔
438
        StringData sd(it.data(), it.size());
29,655✔
439
        list_contents += util::format("%1%2", is_first ? "" : ", ", util::serializer::print_value(sd));
207✔
440
        is_first = false;
240✔
441
    }
240✔
442
    std::string col_descr = state.describe_column(ParentNode::m_table, m_condition_column_key);
240✔
443
    std::string desc = util::format("%1 IN {%2}", col_descr, list_contents);
240✔
444
    return desc;
207✔
445
}
240✔
446

207✔
447

33✔
448
void StringNode<EqualIns>::_search_index_init()
33✔
449
{
96✔
450
    auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
96✔
451
    m_index_matches.clear();
63✔
452
    constexpr bool case_insensitive = true;
63✔
453
    index->find_all(m_index_matches, StringData(StringNodeBase::m_value), case_insensitive);
63✔
454
    m_index_evaluator->init(&m_index_matches);
126✔
455
}
126✔
456

63✔
457
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
63✔
458
{
762✔
459
    EqualIns cond;
762✔
460
    for (size_t s = start; s < end; ++s) {
13,170✔
461
        StringData t = get_string(s);
12,468✔
462

12,468✔
463
        if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t))
13,167✔
464
            return s;
759✔
465
    }
25,575✔
466

13,167✔
467
    return not_found;
699✔
468
}
13,167✔
469

60✔
470
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
12,468✔
471
    : StringNodeEqualBase(v, column)
472
    , m_link_map(std::move(lm))
639✔
473
{
861✔
474
    if (!m_link_map)
162✔
475
        m_link_map = std::make_unique<LinkMap>();
156✔
476
}
162✔
477

478
void StringNodeFulltext::table_changed()
162✔
479
{
330✔
480
    StringNodeEqualBase::table_changed();
324✔
481
    m_link_map->set_base_table(m_table);
330✔
482
}
168✔
483

484
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
168✔
485
    : StringNodeEqualBase(other)
168✔
486
{
384✔
487
    m_link_map = std::make_unique<LinkMap>(*other.m_link_map);
384✔
488
}
216✔
489

490
void StringNodeFulltext::_search_index_init()
491
{
396✔
492
    auto index = m_link_map->get_target_table()->get_search_index(ParentNode::m_condition_column_key);
396✔
493
    REALM_ASSERT(index && index->is_fulltext_index());
396✔
494
    m_index_matches.clear();
180✔
495
    index->find_all_fulltext(m_index_matches, StringData(StringNodeBase::m_value));
180✔
496

360✔
497
    // If links exists, use backlinks to find the original objects
360✔
498
    if (m_link_map->links_exist()) {
360✔
499
        std::set<ObjKey> tmp;
186✔
500
        for (auto k : m_index_matches) {
192✔
501
            auto ndxs = m_link_map->get_origin_ndxs(k);
12✔
502
            tmp.insert(ndxs.begin(), ndxs.end());
12✔
503
        }
192✔
504
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
505
    }
18✔
506

192✔
507
    m_index_evaluator = IndexEvaluator{};
192✔
508
    m_index_evaluator->init(&m_index_matches);
192✔
509
}
186✔
510

6✔
511
std::unique_ptr<ArrayPayload> TwoColumnsNodeBase::update_cached_leaf_pointers_for_column(Allocator& alloc,
512
                                                                                         const ColKey& col_key)
180✔
513
{
29,304✔
514
    switch (col_key.get_type()) {
29,304✔
515
        case col_type_Int:
6,420✔
516
            if (col_key.is_nullable()) {
6,420✔
517
                return std::make_unique<ArrayIntNull>(alloc);
1,722✔
518
            }
30,846✔
519
            return std::make_unique<ArrayInteger>(alloc);
33,822✔
520
        case col_type_Bool:
11,118✔
521
            return std::make_unique<ArrayBoolNull>(alloc);
8,028✔
522
        case col_type_String:
6,420✔
523
            return std::make_unique<ArrayString>(alloc);
3,450✔
524
        case col_type_Binary:
9,396✔
525
            return std::make_unique<ArrayBinary>(alloc);
1,608✔
526
        case col_type_Mixed:
6,306✔
527
            return std::make_unique<ArrayMixed>(alloc);
3,462✔
528
        case col_type_Timestamp:
6,426✔
529
            return std::make_unique<ArrayTimestamp>(alloc);
3,276✔
530
        case col_type_Float:
6,303✔
531
            return std::make_unique<ArrayFloatNull>(alloc);
8,037✔
532
        case col_type_Double:
8,061✔
533
            return std::make_unique<ArrayDoubleNull>(alloc);
9,603✔
534
        case col_type_Decimal:
7,974✔
535
            return std::make_unique<ArrayDecimal128>(alloc);
8,031✔
536
        case col_type_Link:
11,001✔
537
            return std::make_unique<ArrayKey>(alloc);
6,327✔
538
        case col_type_ObjectId:
11,025✔
539
            return std::make_unique<ArrayObjectIdNull>(alloc);
1,728✔
540
        case col_type_UUID:
6,426✔
UNCOV
541
            return std::make_unique<ArrayUUIDNull>(alloc);
✔
542
        case col_type_TypedLink:
4,698✔
UNCOV
543
        case col_type_BackLink:
✔
544
        case col_type_LinkList:
×
UNCOV
545
            break;
✔
546
    };
×
UNCOV
547
    REALM_UNREACHABLE();
✔
548
    return {};
✔
549
}
✔
550

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

2✔
633
size_t NotNode::find_first_local(size_t start, size_t end)
634
{
6,858✔
635
    if (start <= m_known_range_start && end >= m_known_range_end) {
6,858✔
636
        return find_first_covers_known(start, end);
996✔
637
    }
996✔
638
    else if (start >= m_known_range_start && end <= m_known_range_end) {
5,862✔
639
        return find_first_covered_by_known(start, end);
12,492✔
640
    }
12,492✔
641
    else if (start < m_known_range_start && end >= m_known_range_start) {
1,260✔
642
        return find_first_overlap_lower(start, end);
1,002✔
643
    }
5,886✔
644
    else if (start <= m_known_range_end && end > m_known_range_end) {
5,892✔
645
        return find_first_overlap_upper(start, end);
5,718✔
646
    }
336!
647
    else { // start > m_known_range_end || end < m_known_range_start
174✔
648
        return find_first_no_overlap(start, end);
174✔
649
    }
426✔
650
}
6,942✔
651

84✔
652
bool NotNode::evaluate_at(size_t rowndx)
168✔
653
{
8,976✔
654
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
8,976✔
655
}
15,696✔
656

657
void NotNode::update_known(size_t start, size_t end, size_t first)
658
{
10,038✔
659
    m_known_range_start = start;
10,038✔
660
    m_known_range_end = end;
10,038✔
661
    m_first_in_known_range = first;
1,206✔
662
}
1,206✔
663

1,206✔
664
size_t NotNode::find_first_loop(size_t start, size_t end)
1,206✔
665
{
9,060✔
666
    for (size_t i = start; i < end; ++i) {
11,394✔
667
        if (evaluate_at(i)) {
10,014✔
668
            return i;
6,474✔
669
        }
6,474✔
670
    }
16,698✔
671
    return not_found;
18,078✔
672
}
16,686✔
673

6,498✔
674
size_t NotNode::find_first_covers_known(size_t start, size_t end)
6,498✔
675
{
9,828✔
676
    // CASE: start-end covers the known range
2,388✔
677
    // [    ######    ]
8,886✔
678
    REALM_ASSERT_DEBUG(start <= m_known_range_start && end >= m_known_range_end);
996✔
679
    size_t result = find_first_loop(start, m_known_range_start);
996✔
680
    if (result != not_found) {
1,998✔
681
        update_known(start, m_known_range_end, result);
682
    }
683
    else {
1,998✔
684
        if (m_first_in_known_range != not_found) {
1,998✔
685
            update_known(start, m_known_range_end, m_first_in_known_range);
1,002✔
686
            result = m_first_in_known_range;
×
687
        }
×
688
        else {
1,998✔
689
            result = find_first_loop(m_known_range_end, end);
1,998✔
690
            update_known(start, end, result);
996✔
691
        }
996✔
692
    }
996✔
693
    return result;
1,998✔
694
}
1,998✔
695

1,002✔
696
size_t NotNode::find_first_covered_by_known(size_t start, size_t end)
1,002✔
697
{
6,606✔
698
    REALM_ASSERT_DEBUG(start >= m_known_range_start && end <= m_known_range_end);
6,606✔
699
    // CASE: the known range covers start-end
6,606✔
700
    // ###[#####]###
5,604✔
701
    if (m_first_in_known_range != not_found) {
5,604✔
702
        if (m_first_in_known_range > end) {
11,238✔
703
            return not_found;
5,634✔
704
        }
705
        else if (m_first_in_known_range >= start) {
5,604✔
706
            return m_first_in_known_range;
5,634✔
707
        }
5,634✔
708
    }
5,604✔
709
    // The first known match is before start, so we can't use the results to improve
5,604✔
710
    // heuristics.
11,238✔
711
    return find_first_loop(start, end);
5,604✔
712
}
5,604✔
713

5,634✔
714
size_t NotNode::find_first_overlap_lower(size_t start, size_t end)
715
{
716
    REALM_ASSERT_DEBUG(start < m_known_range_start && end >= m_known_range_start && end <= m_known_range_end);
5,634✔
717
    static_cast<void>(end);
5,634✔
718
    // CASE: partial overlap, lower end
719
    // [   ###]#####
720
    size_t result;
×
721
    result = find_first_loop(start, m_known_range_start);
×
722
    if (result == not_found) {
×
723
        result = m_first_in_known_range;
724
    }
725
    update_known(start, m_known_range_end, result);
×
726
    return result < end ? result : not_found;
×
727
}
×
728

729
size_t NotNode::find_first_overlap_upper(size_t start, size_t end)
730
{
84✔
731
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
84!
732
    // CASE: partial overlap, upper end
84✔
733
    // ####[###    ]
84✔
734
    size_t result;
84✔
735
    if (m_first_in_known_range != not_found) {
168✔
736
        if (m_first_in_known_range >= start) {
156✔
737
            result = m_first_in_known_range;
738
            update_known(m_known_range_start, end, result);
739
        }
84✔
740
        else {
156✔
741
            result = find_first_loop(start, end);
144✔
742
            update_known(m_known_range_start, end, m_first_in_known_range);
72✔
743
        }
72✔
744
    }
72✔
745
    else {
84✔
746
        result = find_first_loop(m_known_range_end, end);
84✔
747
        update_known(m_known_range_start, end, result);
84✔
748
    }
84✔
749
    return result;
156✔
750
}
96✔
751

12✔
752
size_t NotNode::find_first_no_overlap(size_t start, size_t end)
12✔
753
{
186✔
754
    REALM_ASSERT_DEBUG((start < m_known_range_start && end < m_known_range_start) ||
258✔
755
                       (start > m_known_range_end && end > m_known_range_end));
258✔
756
    // CASE: no overlap
174✔
757
    // ### [    ]   or    [    ] ####
174✔
758
    // if input is a larger range, discard and replace with results.
342✔
759
    size_t result = find_first_loop(start, end);
342!
760
    if (end - start > m_known_range_end - m_known_range_start) {
342✔
761
        update_known(start, end, result);
126✔
762
    }
126✔
763
    return result;
174✔
764
}
342✔
765

168✔
766
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
120✔
767
    : m_expression(std::move(expression))
120✔
768
{
32,145✔
769
    m_dT = 50.0;
32,145✔
770
}
31,977✔
771

772
void ExpressionNode::table_changed()
773
{
67,479✔
774
    m_expression->set_base_table(m_table);
67,479✔
775
}
67,479✔
776

777
void ExpressionNode::cluster_changed()
778
{
81,351✔
779
    m_expression->set_cluster(m_cluster);
81,351✔
780
}
81,351✔
781

782
void ExpressionNode::init(bool will_query_ranges)
783
{
83,106✔
784
    ParentNode::init(will_query_ranges);
83,106✔
785
    m_dT = m_expression->init();
83,106✔
786
}
36,750✔
787

788
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
37,599✔
789
{
46,314✔
790
    if (m_expression) {
46,314✔
791
        return m_expression->description(state);
46,314✔
792
    }
8,715✔
793
    else {
794
        return "empty expression";
9,384✔
795
    }
9,384✔
796
}
18,099✔
797

9,384✔
798
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
799
{
1,782✔
800
    m_expression->collect_dependencies(tables);
1,782✔
801
}
11,166✔
802

803
size_t ExpressionNode::find_first_local(size_t start, size_t end)
804
{
832,200✔
805
    return m_expression->find_first(start, end);
832,200✔
806
}
832,200✔
807

808
std::unique_ptr<ParentNode> ExpressionNode::clone() const
809
{
859,371✔
810
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
859,371✔
811
}
859,371✔
812

813
ExpressionNode::ExpressionNode(const ExpressionNode& from)
814
    : ParentNode(from)
30,489✔
815
    , m_expression(from.m_expression->clone())
30,489✔
816
{
60,138✔
817
}
29,649✔
818

819
template <>
820
size_t LinksToNode<Equal>::find_first_local(size_t start, size_t end)
821
{
3,055,488✔
822
    if (m_condition_column_key.is_collection()) {
3,055,488✔
823
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
3,006,630✔
824
        if (m_condition_column_key.is_dictionary()) {
3,006,630✔
825
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
48✔
826
            Array top(alloc);
3,025,047✔
827
            for (size_t i = start; i < end; i++) {
3,025,065✔
828
                if (ref_type ref = get_ref(i)) {
3,006,684✔
829
                    top.init_from_ref(ref);
3,006,684✔
830
                    BPlusTree<Mixed> values(alloc);
102✔
831
                    values.set_parent(&top, 1);
102✔
832
                    values.init_from_parent();
120✔
833
                    for (auto& key : m_target_keys) {
132✔
834
                        ObjLink link(target_table_key, key);
132✔
835
                        if (values.find_first(link) != not_found)
132✔
836
                            return i;
90✔
837
                    }
132✔
838
                }
132✔
839
            }
132✔
840
        }
126✔
841
        else {
3,006,618✔
842
            // LinkLists never contain null
3,006,660✔
843
            if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end)
3,006,636✔
844
                return not_found;
57✔
845

3,006,627✔
846
            BPlusTree<ObjKey> links(alloc);
6,013,161✔
847
            for (size_t i = start; i < end; i++) {
3,010,743✔
848
                if (ref_type ref = get_ref(i)) {
6,017,058✔
849
                    links.init_from_ref(ref);
3,008,343✔
850
                    for (auto& key : m_target_keys) {
3,008,346✔
851
                        if (key) {
6,014,925✔
852
                            if (links.find_first(key) != not_found)
6,019,089✔
853
                                return i;
6,016,788✔
854
                        }
6,016,686✔
855
                    }
6,016,692✔
856
                }
6,016,686✔
857
            }
6,018,822✔
858
        }
6,012,891✔
859
    }
6,014,976✔
860
    else if (m_list) {
3,026,715✔
861
        for (auto& key : m_target_keys) {
3,026,709✔
862
            auto pos = m_list->find_first(key, start, end);
3,028,845✔
863
            if (pos != realm::npos) {
3,024,948✔
864
                return pos;
3,010,455✔
865
            }
22,194✔
866
        }
36,738✔
867
    }
36,738✔
868

3,043,368✔
869
    return not_found;
3,028,824✔
870
}
3,028,824✔
871

18,369✔
872
template <>
18,369✔
873
size_t LinksToNode<NotEqual>::find_first_local(size_t start, size_t end)
874
{
18,171✔
875
    // NotEqual only makes sense for a single value
3,028,347✔
876
    REALM_ASSERT(m_target_keys.size() == 1);
3,348✔
877
    ObjKey key = m_target_keys[0];
3,348✔
878

3,348✔
879
    if (m_condition_column_key.is_collection()) {
6,696✔
880
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
1,209✔
881
        if (m_condition_column_key.is_dictionary()) {
4,557✔
882
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
3,366✔
883
            Array top(alloc);
18✔
884
            for (size_t i = start; i < end; i++) {
3,372✔
885
                if (ref_type ref = get_ref(i)) {
1,227✔
886
                    top.init_from_ref(ref);
1,227✔
887
                    BPlusTree<Mixed> values(alloc);
36✔
888
                    values.set_parent(&top, 1);
36✔
889
                    values.init_from_parent();
42✔
890

36✔
891
                    ObjLink link(target_table_key, key);
36✔
892
                    bool found = false;
36✔
893
                    values.for_all([&](const Mixed& val) {
42✔
894
                        if (val != link) {
42✔
895
                            found = true;
12✔
896
                        }
30✔
897
                        return !found;
42✔
898
                    });
48✔
899
                    if (found)
42✔
900
                        return i;
24✔
901
                }
30✔
902
            }
42✔
903
        }
42✔
904
        else {
1,209✔
905
            BPlusTree<ObjKey> links(alloc);
1,203✔
906
            for (size_t i = start; i < end; i++) {
2,391✔
907
                if (ref_type ref = get_ref(i)) {
2,274✔
908
                    links.init_from_ref(ref);
1,200✔
909
                    auto sz = links.size();
2,373✔
910
                    for (size_t j = 0; j < sz; j++) {
2,496✔
911
                        if (links.get(j) != key) {
3,570✔
912
                            return i;
3,330✔
913
                        }
2,256✔
914
                    }
2,379✔
915
                }
2,487✔
916
            }
3,453✔
917
        }
2,265✔
918
    }
2,283✔
919
    else if (m_list) {
3,336✔
920
        for (size_t i = start; i < end; i++) {
3,573✔
921
            if (m_list->get(i) != key) {
4,563✔
922
                return i;
3,246✔
923
            }
3,264✔
924
        }
4,446✔
925
    }
4,530✔
926

5,655✔
927
    return not_found;
5,403✔
928
}
5,403✔
929

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

© 2025 Coveralls, Inc