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

realm / realm-core / 1771

20 Oct 2023 08:58AM UTC coverage: 91.567% (-0.009%) from 91.576%
1771

push

Evergreen

web-flow
Fix blocked DB::open on multiprocess access on exFAT filesystem (#6959)

Fix double file lock and DB::open being blocked with multiple concurrent realm access on fat32/exfat file systems.

When file is truncated on fat32/exfat its uid is available for other files. With multiple processes opening and truncating the same set of files could lead to the situation when within one process get_unique_id will return the same value for different files in timing is right. This breaks proper initialization of static data for interprocess mutexes, so that subsequent locks will hang by trying to lock essentially the same file twice.

94304 of 173552 branches covered (0.0%)

59 of 82 new or added lines in 5 files covered. (71.95%)

53 existing lines in 13 files now uncovered.

230544 of 251776 relevant lines covered (91.57%)

6594884.0 hits per line

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

90.31
/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,148,377✔
37
}
3,148,377✔
38

39

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

2,210,025✔
46
    while (REALM_LIKELY(start < end)) {
4,959,096✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
4,926,894✔
48

2,366,127✔
49
        if (m != start) {
4,926,894✔
50
            // Pointer advanced - we will have to check all other conditions
1,307,772✔
51
            nb_cond_to_test = sz;
2,650,368✔
52
            start = m;
2,650,368✔
53
        }
2,650,368✔
54

2,366,127✔
55
        nb_cond_to_test--;
4,926,894✔
56

2,366,127✔
57
        // Optimized for one condition where this will be true first time
2,366,127✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
4,926,894✔
59
            return m;
4,744,800✔
60

170,964✔
61
        current_cond++;
353,058✔
62

170,964✔
63
        if (current_cond == sz)
353,058✔
64
            current_cond = 0;
117,882✔
65
    }
353,058✔
66
    return not_found;
2,227,365✔
67
}
4,606,038✔
68

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

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

1,816,899✔
99
    m_state = st;
5,229,852✔
100
    m_source_column = source_column;
5,229,852✔
101
    size_t local_matches = 0;
5,229,852✔
102

1,816,899✔
103
    if (m_children.size() == 1) {
6,187,443✔
104
        return find_all_local(start, end);
6,183,474✔
105
    }
6,183,474✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,487,616✔
108
    for (;;) {
2,147,988,931✔
109
        if (local_matches == local_limit) {
978,459✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
16,413✔
111
            return r + 1;
16,413✔
112
        }
16,413✔
113

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

448,722✔
122
        local_matches++;
928,929✔
123

448,722✔
124
        // Find first match in remaining condition nodes
448,722✔
125
        size_t m = r;
928,929✔
126

448,722✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,446,165✔
128
            m = m_children[c]->find_first_local(r, r + 1);
994,479✔
129
            if (m != r) {
994,479✔
130
                break;
477,243✔
131
            }
477,243✔
132
        }
994,479✔
133

448,722✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
448,722✔
135
        // match
448,722✔
136
        if (m == r) {
928,929✔
137
            Mixed val;
451,686✔
138
            if (source_column) {
451,686✔
139
                val = source_column->get_any(r);
408✔
140
            }
408✔
141
            bool cont = st->match(r, val);
451,686✔
142
            if (!cont) {
451,686✔
143
                return static_cast<size_t>(-1);
39✔
144
            }
39✔
145
        }
451,686✔
146
    }
928,929✔
147
}
2,147,487,616✔
148

149
size_t ParentNode::find_all_local(size_t start, size_t end)
150
{
305,922✔
151
    while (start < end) {
10,599,543✔
152
        start = find_first_local(start, end);
10,293,651✔
153
        if (start != not_found) {
10,293,651✔
154
            Mixed val;
10,122,198✔
155
            if (m_source_column) {
10,122,198✔
156
                val = m_source_column->get_any(start);
1,014✔
157
            }
1,014✔
158
            bool cont = m_state->match(start, val);
10,122,198✔
159
            if (!cont) {
10,122,198✔
160
                return static_cast<size_t>(-1);
30✔
161
            }
30✔
162
            start++;
10,122,168✔
163
        }
10,122,168✔
164
    }
10,293,651✔
165
    return end;
305,907✔
166
}
305,922✔
167

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

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

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

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

195
    return not_found;
×
196
}
×
197

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

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

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

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

36✔
270
    return not_found;
54✔
271
}
72✔
272

273
void StringNodeEqualBase::init(bool will_query_ranges)
274
{
78,180✔
275
    StringNodeBase::init(will_query_ranges);
78,180✔
276

38,775✔
277
    const bool uses_index = has_search_index();
78,180✔
278
    if (m_is_string_enum) {
78,180✔
279
        m_dT = 1.0;
2,658✔
280
    }
2,658✔
281
    else if (uses_index) {
75,522✔
282
        m_dT = 0.0;
3,204✔
283
    }
3,204✔
284
    else {
72,318✔
285
        m_dT = 10.0;
72,318✔
286
    }
72,318✔
287

38,775✔
288
    if (uses_index) {
78,180✔
289
        _search_index_init();
3,666✔
290
    }
3,666✔
291
}
78,180✔
292

293
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
294
{
1,104,639✔
295
    REALM_ASSERT(m_table);
1,104,639✔
296

568,737✔
297
    if (m_index_evaluator) {
1,104,639✔
298
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,620✔
299
    }
241,620✔
300

442,446✔
301
    return _find_first_local(start, end);
863,019✔
302
}
863,019✔
303

304

305
void IndexEvaluator::init(StringIndex* index, Mixed value)
306
{
653,994✔
307
    REALM_ASSERT(index);
653,994✔
308
    m_matching_keys = nullptr;
653,994✔
309
    FindRes fr;
653,994✔
310
    InternalFindResult res;
653,994✔
311

326,997✔
312
    m_last_start_key = ObjKey();
653,994✔
313
    m_results_start = 0;
653,994✔
314
    fr = index->find_all_no_copy(value, res);
653,994✔
315

326,997✔
316
    m_index_matches.reset();
653,994✔
317
    switch (fr) {
653,994✔
318
        case FindRes_single:
9,990✔
319
            m_actual_key = ObjKey(res.payload);
9,990✔
320
            m_results_end = 1;
9,990✔
321
            break;
9,990✔
322
        case FindRes_column:
1,524✔
323
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
1,524✔
324
            m_results_start = res.start_ndx;
1,524✔
325
            m_results_end = res.end_ndx;
1,524✔
326
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
1,524✔
327
            break;
1,524✔
328
        case FindRes_not_found:
642,480✔
329
            m_results_end = 0;
642,480✔
330
            break;
642,480✔
331
    }
653,994✔
332
    m_results_ndx = m_results_start;
653,994✔
333
}
653,994✔
334

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

349
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
350
{
885,600✔
351
    if (start >= end) {
885,600✔
352
        return not_found;
9✔
353
    }
9✔
354

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

448,281✔
364
    // Check if we can expect to find more keys
448,281✔
365
    if (m_results_ndx < m_results_end) {
885,591✔
366
        // Check if we should advance to next key to search for
102,699✔
367
        while (first_key > m_actual_key) {
72,267,330✔
368
            m_results_ndx++;
72,078,165✔
369
            if (m_results_ndx == m_results_end) {
72,078,165✔
370
                return not_found;
1,473✔
371
            }
1,473✔
372
            m_actual_key = get_internal(m_results_ndx);
72,076,692✔
373
        }
72,076,692✔
374

102,699✔
375
        // If actual_key is bigger than last key, it is not in this leaf
102,699✔
376
        ObjKey last_key = cluster->get_real_key(end - 1);
189,885✔
377
        if (m_actual_key > last_key)
189,165✔
378
            return not_found;
116,442✔
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()));
72,723✔
382
    }
72,723✔
383
    return not_found;
694,953✔
384
}
694,953✔
385

386
void StringNode<Equal>::_search_index_init()
387
{
3,180✔
388
    REALM_ASSERT(bool(m_index_evaluator));
3,180✔
389
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,180✔
390
    m_index_evaluator->init(index, StringData(StringNodeBase::m_value));
3,180✔
391
}
3,180✔
392

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

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

416
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
417
{
861,786✔
418
    if (m_needles.empty()) {
861,786✔
419
        return m_leaf->find_first(m_value, start, end);
861,081✔
420
    }
861,081✔
421
    else {
705✔
422
        if (end == npos)
705✔
423
            end = m_leaf->size();
×
424
        REALM_ASSERT_3(start, <=, end);
705✔
425
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
705✔
426
    }
705✔
427
}
861,786✔
428

429
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
430
{
58,416✔
431
    if (m_needles.empty()) {
58,416✔
432
        return StringNodeEqualBase::describe(state);
58,350✔
433
    }
58,350✔
434

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

447

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

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

12,468✔
463
        if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t))
24,936✔
464
            return s;
120✔
465
    }
24,936✔
466

699✔
467
    return not_found;
1,338✔
468
}
1,398✔
469

470
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
471
    : StringNodeEqualBase(v, column)
472
    , m_link_map(std::move(lm))
473
{
324✔
474
    if (!m_link_map)
324✔
475
        m_link_map = std::make_unique<LinkMap>();
312✔
476
}
324✔
477

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

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

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

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

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

511
std::unique_ptr<ArrayPayload> TwoColumnsNodeBase::update_cached_leaf_pointers_for_column(Allocator& alloc,
512
                                                                                         const ColKey& col_key)
513
{
58,248✔
514
    switch (col_key.get_type()) {
58,248✔
515
        case col_type_Int:
12,840✔
516
            if (col_key.is_nullable()) {
12,840✔
517
                return std::make_unique<ArrayIntNull>(alloc);
3,444✔
518
            }
3,444✔
519
            return std::make_unique<ArrayInteger>(alloc);
9,396✔
520
        case col_type_Bool:
6,306✔
521
            return std::make_unique<ArrayBoolNull>(alloc);
3,216✔
522
        case col_type_String:
6,426✔
523
            return std::make_unique<ArrayString>(alloc);
3,456✔
524
        case col_type_Binary:
4,698✔
525
            return std::make_unique<ArrayBinary>(alloc);
×
526
        case col_type_Mixed:
6,432✔
527
            return std::make_unique<ArrayMixed>(alloc);
3,468✔
528
        case col_type_Timestamp:
7,974✔
529
            return std::make_unique<ArrayTimestamp>(alloc);
6,552✔
530
        case col_type_Float:
12,606✔
531
            return std::make_unique<ArrayFloatNull>(alloc);
12,606✔
532
        case col_type_Double:
12,654✔
533
            return std::make_unique<ArrayDoubleNull>(alloc);
12,654✔
534
        case col_type_Decimal:
6,426✔
535
            return std::make_unique<ArrayDecimal128>(alloc);
3,456✔
536
        case col_type_Link:
4,698✔
537
            return std::make_unique<ArrayKey>(alloc);
×
538
        case col_type_ObjectId:
4,698✔
539
            return std::make_unique<ArrayObjectIdNull>(alloc);
×
540
        case col_type_UUID:
4,698✔
541
            return std::make_unique<ArrayUUIDNull>(alloc);
×
542
        case col_type_TypedLink:
4,698✔
543
        case col_type_BackLink:
✔
544
        case col_type_LinkList:
✔
545
            break;
×
546
    };
×
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
{
204✔
553
    switch (col_type) {
204✔
554
        case col_type_Int: {
84✔
555
            if (is_nullable) {
84✔
556
                BPlusTree<util::Optional<Int>> list(alloc);
6✔
557
                list.init_from_ref(ref);
6✔
558
                return list.size();
6✔
559
            }
6✔
560
            else {
78✔
561
                BPlusTree<Int> list(alloc);
78✔
562
                list.init_from_ref(ref);
78✔
563
                return list.size();
78✔
564
            }
78✔
565
        }
×
566
        case col_type_Bool: {
12✔
567
            BPlusTree<Bool> list(alloc);
12✔
568
            list.init_from_ref(ref);
12✔
569
            return list.size();
12✔
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: {
✔
612
            BPlusTree<Mixed> list(alloc);
×
613
            list.init_from_ref(ref);
×
614
            return list.size();
×
615
        }
×
616
        case col_type_LinkList: {
12✔
617
            BPlusTree<ObjKey> list(alloc);
12✔
618
            list.init_from_ref(ref);
12✔
619
            return list.size();
12✔
620
        }
×
621
        case col_type_TypedLink: {
✔
622
            BPlusTree<ObjLink> list(alloc);
×
623
            list.init_from_ref(ref);
×
624
            return list.size();
×
625
        }
×
626
        case col_type_Link:
✔
627
        case col_type_BackLink:
✔
628
            break;
×
629
    }
×
630
    REALM_TERMINATE("Unsupported column type.");
×
631
}
×
632

633
size_t NotNode::find_first_local(size_t start, size_t end)
634
{
13,716✔
635
    if (start <= m_known_range_start && end >= m_known_range_end) {
13,716✔
636
        return find_first_covers_known(start, end);
1,992✔
637
    }
1,992✔
638
    else if (start >= m_known_range_start && end <= m_known_range_end) {
11,724✔
639
        return find_first_covered_by_known(start, end);
11,208✔
640
    }
11,208✔
641
    else if (start < m_known_range_start && end >= m_known_range_start) {
516!
642
        return find_first_overlap_lower(start, end);
×
643
    }
×
644
    else if (start <= m_known_range_end && end > m_known_range_end) {
516✔
645
        return find_first_overlap_upper(start, end);
168✔
646
    }
168✔
647
    else { // start > m_known_range_end || end < m_known_range_start
348✔
648
        return find_first_no_overlap(start, end);
348✔
649
    }
348✔
650
}
13,716✔
651

652
bool NotNode::evaluate_at(size_t rowndx)
653
{
17,616✔
654
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
17,616✔
655
}
17,616✔
656

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

664
size_t NotNode::find_first_loop(size_t start, size_t end)
665
{
15,708✔
666
    for (size_t i = start; i < end; ++i) {
20,376✔
667
        if (evaluate_at(i)) {
17,616✔
668
            return i;
12,948✔
669
        }
12,948✔
670
    }
17,616✔
671
    return not_found;
9,234✔
672
}
15,708✔
673

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

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

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);
×
717
    static_cast<void>(end);
×
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
{
168✔
731
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
168✔
732
    // CASE: partial overlap, upper end
84✔
733
    // ####[###    ]
84✔
734
    size_t result;
168✔
735
    if (m_first_in_known_range != not_found) {
168✔
736
        if (m_first_in_known_range >= start) {
144✔
737
            result = m_first_in_known_range;
×
738
            update_known(m_known_range_start, end, result);
×
739
        }
×
740
        else {
144✔
741
            result = find_first_loop(start, end);
144✔
742
            update_known(m_known_range_start, end, m_first_in_known_range);
144✔
743
        }
144✔
744
    }
144✔
745
    else {
24✔
746
        result = find_first_loop(m_known_range_end, end);
24✔
747
        update_known(m_known_range_start, end, result);
24✔
748
    }
24✔
749
    return result;
168✔
750
}
168✔
751

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

766
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
767
    : m_expression(std::move(expression))
768
{
63,954✔
769
    m_dT = 50.0;
63,954✔
770
}
63,954✔
771

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

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

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

788
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
789
{
17,376✔
790
    if (m_expression) {
17,376✔
791
        return m_expression->description(state);
17,376✔
792
    }
17,376✔
UNCOV
793
    else {
×
UNCOV
794
        return "empty expression";
×
UNCOV
795
    }
×
796
}
17,376✔
797

798
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
799
{
3,564✔
800
    m_expression->collect_dependencies(tables);
3,564✔
801
}
3,564✔
802

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

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

813
ExpressionNode::ExpressionNode(const ExpressionNode& from)
814
    : ParentNode(from)
815
    , m_expression(from.m_expression->clone())
816
{
59,298✔
817
}
59,298✔
818

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

3,006,579✔
846
            BPlusTree<ObjKey> links(alloc);
6,013,158✔
847
            for (size_t i = start; i < end; i++) {
6,021,483✔
848
                if (ref_type ref = get_ref(i)) {
6,020,949✔
849
                    links.init_from_ref(ref);
6,016,680✔
850
                    for (auto& key : m_target_keys) {
6,016,692✔
851
                        if (key) {
6,016,692✔
852
                            if (links.find_first(key) != not_found)
6,016,692✔
853
                                return i;
6,012,624✔
854
                        }
6,016,692✔
855
                    }
6,016,692✔
856
                }
6,016,680✔
857
            }
6,020,949✔
858
        }
6,013,158✔
859
    }
6,013,260✔
860
    else if (m_list) {
36,738✔
861
        for (auto& key : m_target_keys) {
36,738✔
862
            auto pos = m_list->find_first(key, start, end);
36,738✔
863
            if (pos != realm::npos) {
36,738✔
864
                return pos;
7,650✔
865
            }
7,650✔
866
        }
36,738✔
867
    }
36,738✔
868

3,024,999✔
869
    return not_found;
3,039,822✔
870
}
6,049,998✔
871

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

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

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

3,348✔
927
    return not_found;
3,555✔
928
}
6,696✔
929

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

© 2025 Coveralls, Inc