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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

92.1
/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)
1,595,625✔
30
    , m_condition_column_key(from.m_condition_column_key)
1,595,625✔
31
    , m_dD(from.m_dD)
1,595,625✔
32
    , m_dT(from.m_dT)
1,595,625✔
33
    , m_probes(from.m_probes)
1,595,625✔
34
    , m_matches(from.m_matches)
1,595,625✔
35
    , m_table(from.m_table)
1,595,625✔
36
{
3,167,058✔
37
}
3,167,058✔
38

39

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

46
    while (REALM_LIKELY(start < end)) {
7,488,516✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
7,439,877✔
48

49
        if (m != start) {
7,439,877✔
50
            // Pointer advanced - we will have to check all other conditions
51
            nb_cond_to_test = sz;
3,156,861✔
52
            start = m;
3,156,861✔
53
        }
3,156,861✔
54

55
        nb_cond_to_test--;
7,439,877✔
56

57
        // Optimized for one condition where this will be true first time
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
7,439,877✔
59
            return m;
6,968,904✔
60

61
        current_cond++;
470,973✔
62

63
        if (current_cond == sz)
470,973✔
64
            current_cond = 0;
157,977✔
65
    }
470,973✔
66
    return not_found;
48,639✔
67
}
7,017,543✔
68

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

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

99
    m_state = st;
6,417,609✔
100
    m_state->set_payload_column(source_column);
6,417,609✔
101
    size_t local_matches = 0;
6,417,609✔
102

103
    if (m_children.size() == 1) {
6,486,198✔
104
        return find_all_local(start, end);
6,409,776✔
105
    }
6,409,776✔
106

107
    size_t r = start - 1;
2,147,560,069✔
108
    for (;;) {
2,148,185,470✔
109
        if (local_matches == local_limit) {
1,457,598✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
25,950✔
111
            return r + 1;
25,950✔
112
        }
25,950✔
113

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

122
        local_matches++;
1,381,755✔
123

124
        // Find first match in remaining condition nodes
125
        size_t m = r;
1,381,755✔
126

127
        for (size_t c = 1; c < m_children.size(); c++) {
2,153,238✔
128
            m = m_children[c]->find_first_local(r, r + 1);
1,429,950✔
129
            if (m != r) {
1,429,950✔
130
                break;
658,467✔
131
            }
658,467✔
132
        }
1,429,950✔
133

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

145
size_t ParentNode::find_all_local(size_t start, size_t end)
146
{
335,271✔
147
    while (start < end) {
11,430,474✔
148
        start = find_first_local(start, end);
11,095,233✔
149
        if (start != not_found) {
11,095,233✔
150
            bool cont = m_state->match(start);
10,891,359✔
151
            if (!cont) {
10,891,359✔
152
                return static_cast<size_t>(-1);
30✔
153
            }
30✔
154
            start++;
10,891,329✔
155
        }
10,891,329✔
156
    }
11,095,233✔
157
    return end;
335,241✔
158
}
335,271✔
159

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

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

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

180
    if (m_index_evaluator) {
24,738✔
181
        return m_index_evaluator->do_search_index(m_cluster, start, end);
12,132✔
182
    }
12,132✔
183
    else {
12,606✔
184
        return m_leaf->find_first(m_value, start, end);
12,606✔
185
    }
12,606✔
186

187
    return not_found;
×
188
}
24,738✔
189

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

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

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

267
    return not_found;
66✔
268
}
114✔
269

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

274
    const bool uses_index = has_search_index();
57,465✔
275
    if (uses_index) {
57,465✔
276
        m_dT = 0.0;
3,495✔
277
    }
3,495✔
278
    else {
53,970✔
279
        m_dT = 10.0;
53,970✔
280
    }
53,970✔
281

282
    if (uses_index) {
57,465✔
283
        m_index_evaluator = std::make_optional(IndexEvaluator{});
3,495✔
284
        _search_index_init();
3,495✔
285
    }
3,495✔
286
}
57,465✔
287

288
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
289
{
595,284✔
290
    REALM_ASSERT(m_table);
595,284✔
291

292
    if (m_index_evaluator) {
595,284✔
293
        return m_index_evaluator->do_search_index(m_cluster, start, end);
221,736✔
294
    }
221,736✔
295

296
    return _find_first_local(start, end);
373,548✔
297
}
595,284✔
298

299

300
void IndexEvaluator::init(SearchIndex* index, Mixed value)
301
{
19,545✔
302
    REALM_ASSERT(index);
19,545✔
303
    m_matching_keys = nullptr;
19,545✔
304
    FindRes fr;
19,545✔
305
    InternalFindResult res;
19,545✔
306

307
    m_last_start_key = ObjKey();
19,545✔
308
    m_results_start = 0;
19,545✔
309
    fr = index->find_all_no_copy(value, res);
19,545✔
310

311
    m_index_matches.reset();
19,545✔
312
    switch (fr) {
19,545✔
313
        case FindRes_single:
16,920✔
314
            m_actual_key = ObjKey(res.payload);
16,920✔
315
            m_results_end = 1;
16,920✔
316
            break;
16,920✔
317
        case FindRes_column:
2,112✔
318
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
2,112✔
319
            m_results_start = res.start_ndx;
2,112✔
320
            m_results_end = res.end_ndx;
2,112✔
321
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
2,112✔
322
            break;
2,112✔
323
        case FindRes_not_found:
513✔
324
            m_results_end = 0;
513✔
325
            break;
513✔
326
    }
19,545✔
327
    m_results_ndx = m_results_start;
19,545✔
328
}
19,545✔
329

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

344
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
345
{
240,636✔
346
    if (start >= end) {
240,636✔
UNCOV
347
        return not_found;
×
UNCOV
348
    }
×
349

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

359
    // Check if we can expect to find more keys
360
    if (m_results_ndx < m_results_end) {
240,636✔
361
        // Check if we should advance to next key to search for
362
        while (first_key > m_actual_key) {
72,251,715✔
363
            m_results_ndx++;
72,068,652✔
364
            if (m_results_ndx == m_results_end) {
72,068,652✔
365
                return not_found;
6,258✔
366
            }
6,258✔
367
            m_actual_key = get_internal(m_results_ndx);
72,062,394✔
368
        }
72,062,394✔
369

370
        // If actual_key is bigger than last key, it is not in this leaf
371
        ObjKey last_key = cluster->get_real_key(end - 1);
183,063✔
372
        if (m_actual_key > last_key)
183,063✔
373
            return not_found;
107,136✔
374

375
        // Now actual_key must be found in leaf keys
376
        REALM_ASSERT(uint64_t(m_actual_key.value) >= cluster->get_offset());
75,927✔
377
        return cluster->lower_bound_key(ClusterNode::RowKey(m_actual_key.value - cluster->get_offset()));
75,927✔
378
    }
183,063✔
379
    return not_found;
51,315✔
380
}
240,636✔
381

382
StringNode<Equal>::StringNode(ColKey col, const Mixed* begin, const Mixed* end)
383
    : StringNodeEqualBase(StringData(), col)
156✔
384
{
300✔
385
    // Don't use the search index if present since we're in a scenario where
386
    // it'd be slower
387
    m_index_evaluator.reset();
300✔
388

389
    for (const Mixed* it = begin; it != end; ++it) {
8,052✔
390
        if (it->is_null()) {
7,752✔
391
            m_needles.emplace();
66✔
392
        }
66✔
393
        else if (const StringData* str = it->get_if<StringData>()) {
7,686✔
394
            m_needle_storage.push_back(std::make_unique<char[]>(str->size() + 1));
7,650✔
395
            std::copy(str->data(), str->data() + str->size(), m_needle_storage.back().get());
7,650✔
396
            m_needle_storage.back()[str->size()] = '\0';
7,650✔
397
            m_needles.insert(StringData(m_needle_storage.back().get(), str->size()));
7,650✔
398
        }
7,650✔
399
    }
7,752✔
400
    if (m_needles.empty()) {
300✔
401
        throw InvalidArgument("No string arguments in query");
×
402
    }
×
403
}
300✔
404

405
void StringNode<Equal>::_search_index_init()
406
{
3,369✔
407
    if (!m_needles.empty()) {
3,369✔
408
        m_index_evaluator.reset();
60✔
409
        return;
60✔
410
    }
60✔
411
    REALM_ASSERT(bool(m_index_evaluator));
3,309✔
412
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,309✔
413
    m_index_evaluator->init(index, StringNodeBase::m_string_value);
3,309✔
414
}
3,309✔
415

416
bool StringNode<Equal>::do_consume_condition(ParentNode& node)
417
{
234✔
418
    // Don't use the search index if present since we're in a scenario where
419
    // it'd be slower
420
    m_index_evaluator.reset();
234✔
421

422
    auto& other = static_cast<StringNode<Equal>&>(node);
234✔
423
    REALM_ASSERT(m_condition_column_key == other.m_condition_column_key);
234✔
424

425
    if (m_needles.empty()) {
234✔
426
        m_needles.insert(m_string_value);
162✔
427
    }
162✔
428
    auto add_string = [&](const StringData& str) {
264✔
429
        if (m_needles.count(str) == 0) {
264✔
430
            if (str.size()) {
246✔
431
                m_needle_storage.push_back(std::make_unique<char[]>(str.size()));
204✔
432
                std::copy(str.data(), str.data() + str.size(), m_needle_storage.back().get());
204✔
433
                m_needles.insert(StringData(m_needle_storage.back().get(), str.size()));
204✔
434
            }
204✔
435
            else {
42✔
436
                // this code path is different because we need to
437
                // distinguish null from the empty string
438
                m_needles.insert(str);
42✔
439
            }
42✔
440
        }
246✔
441
    };
264✔
442
    if (!other.m_needles.empty()) {
234✔
443
        for (const auto& str : other.m_needles) {
78✔
444
            add_string(str);
78✔
445
        }
78✔
446
    }
48✔
447
    else {
186✔
448
        add_string(other.m_string_value);
186✔
449
    }
186✔
450
    return true;
234✔
451
}
234✔
452

453
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
454
{
372,120✔
455
    if (m_needles.empty()) {
372,120✔
456
        return m_leaf->find_first(m_string_value, start, end, m_interned_string_id);
363,876✔
457
    }
363,876✔
458
    else {
8,244✔
459
        if (end == npos)
8,244✔
460
            end = m_leaf->size();
×
461
        REALM_ASSERT_3(start, <=, end);
8,244✔
462
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
8,244✔
463
    }
8,244✔
464
}
372,120✔
465

466
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
467
{
6,633✔
468
    if (m_needles.empty()) {
6,633✔
469
        return StringNodeEqualBase::describe(state);
6,543✔
470
    }
6,543✔
471

472
    std::string list_contents;
90✔
473
    bool is_first = true;
90✔
474
    for (auto it : m_needles) {
222✔
475
        StringData sd(it.data(), it.size());
222✔
476
        list_contents += util::format("%1%2", is_first ? "" : ", ", util::serializer::print_value(sd));
222✔
477
        is_first = false;
222✔
478
    }
222✔
479
    std::string col_descr = state.describe_column(ParentNode::m_table, m_condition_column_key);
90✔
480
    std::string desc = util::format("%1 IN {%2}", col_descr, list_contents);
90✔
481
    return desc;
90✔
482
}
6,633✔
483

484

485
void StringNode<EqualIns>::_search_index_init()
486
{
126✔
487
    auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
126✔
488
    m_index_matches.clear();
126✔
489
    constexpr bool case_insensitive = true;
126✔
490
    index->find_all(m_index_matches, StringNodeBase::m_string_value, case_insensitive);
126✔
491
    m_index_evaluator->init(&m_index_matches);
126✔
492
}
126✔
493

494
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
495
{
1,428✔
496
    EqualIns cond;
1,428✔
497
    for (size_t s = start; s < end; ++s) {
26,316✔
498
        StringData t = get_string(s);
25,032✔
499

500
        if (cond(m_string_value, m_ucase.c_str(), m_lcase.c_str(), t))
25,032✔
501
            return s;
144✔
502
    }
25,032✔
503

504
    return not_found;
1,284✔
505
}
1,428✔
506

507
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
508
    : m_value(v)
171✔
509
    , m_col(column)
171✔
510
    , m_link_map(std::move(lm))
171✔
511
{
342✔
512
    if (!m_link_map)
342✔
513
        m_link_map = std::make_unique<LinkMap>();
330✔
514
}
342✔
515

516
void StringNodeFulltext::table_changed()
517
{
354✔
518
    m_link_map->set_base_table(m_table);
354✔
519
}
354✔
520

521
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
522
    : ParentNode(other)
234✔
523
    , m_value(other.m_value)
234✔
524
    , m_col(other.m_col)
234✔
525
    , m_link_map(std::make_unique<LinkMap>(*other.m_link_map))
234✔
526
{
468✔
527
}
468✔
528

529
void StringNodeFulltext::init(bool will_query_ranges)
530
{
378✔
531
    ParentNode::init(will_query_ranges);
378✔
532

533
    StringIndex* index = m_link_map->get_target_table()->get_string_index(m_col);
378✔
534
    REALM_ASSERT(index && index->is_fulltext_index());
378✔
535
    m_index_matches.clear();
378✔
536
    index->find_all_fulltext(m_index_matches, m_value);
378✔
537

538
    // If links exists, use backlinks to find the original objects
539
    if (m_link_map->links_exist()) {
378✔
540
        std::set<ObjKey> tmp;
12✔
541
        for (auto k : m_index_matches) {
24✔
542
            auto keys = m_link_map->get_origin_objkeys(k);
24✔
543
            tmp.insert(keys.begin(), keys.end());
24✔
544
        }
24✔
545
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
546
    }
12✔
547

548
    m_index_evaluator = IndexEvaluator{};
378✔
549
    m_index_evaluator.init(&m_index_matches);
378✔
550
}
378✔
551

552
std::unique_ptr<ArrayPayload> TwoColumnsNodeBase::update_cached_leaf_pointers_for_column(Allocator& alloc,
553
                                                                                         const ColKey& col_key)
554
{
58,248✔
555
    switch (col_key.get_type()) {
58,248✔
556
        case col_type_Int:
12,840✔
557
            if (col_key.is_nullable()) {
12,840✔
558
                return std::make_unique<ArrayIntNull>(alloc);
3,444✔
559
            }
3,444✔
560
            return std::make_unique<ArrayInteger>(alloc);
9,396✔
561
        case col_type_Bool:
3,216✔
562
            return std::make_unique<ArrayBoolNull>(alloc);
3,216✔
563
        case col_type_String:
3,456✔
564
            return std::make_unique<ArrayString>(alloc);
3,456✔
565
        case col_type_Binary:
✔
566
            return std::make_unique<ArrayBinary>(alloc);
×
567
        case col_type_Mixed:
3,468✔
568
            return std::make_unique<ArrayMixed>(alloc);
3,468✔
569
        case col_type_Timestamp:
6,552✔
570
            return std::make_unique<ArrayTimestamp>(alloc);
6,552✔
571
        case col_type_Float:
12,606✔
572
            return std::make_unique<ArrayFloatNull>(alloc);
12,606✔
573
        case col_type_Double:
12,654✔
574
            return std::make_unique<ArrayDoubleNull>(alloc);
12,654✔
575
        case col_type_Decimal:
3,456✔
576
            return std::make_unique<ArrayDecimal128>(alloc);
3,456✔
577
        case col_type_Link:
✔
578
            return std::make_unique<ArrayKey>(alloc);
×
579
        case col_type_ObjectId:
✔
580
            return std::make_unique<ArrayObjectIdNull>(alloc);
×
581
        case col_type_UUID:
✔
582
            return std::make_unique<ArrayUUIDNull>(alloc);
×
583
        case col_type_TypedLink:
✔
584
        case col_type_BackLink:
✔
585
            break;
×
586
    };
58,248✔
587
    REALM_UNREACHABLE();
588
    return {};
×
589
}
58,248✔
590

591
size_t size_of_list_from_ref(ref_type ref, Allocator& alloc, ColumnType col_type, bool is_nullable)
592
{
204✔
593
    switch (col_type) {
204✔
594
        case col_type_Int: {
84✔
595
            if (is_nullable) {
84✔
596
                BPlusTree<util::Optional<Int>> list(alloc);
6✔
597
                list.init_from_ref(ref);
6✔
598
                return list.size();
6✔
599
            }
6✔
600
            else {
78✔
601
                BPlusTree<Int> list(alloc);
78✔
602
                list.init_from_ref(ref);
78✔
603
                return list.size();
78✔
604
            }
78✔
605
        }
84✔
606
        case col_type_Bool: {
12✔
607
            BPlusTree<Bool> list(alloc);
12✔
608
            list.init_from_ref(ref);
12✔
609
            return list.size();
12✔
610
        }
84✔
611
        case col_type_String: {
12✔
612
            BPlusTree<String> list(alloc);
12✔
613
            list.init_from_ref(ref);
12✔
614
            return list.size();
12✔
615
        }
84✔
616
        case col_type_Binary: {
12✔
617
            BPlusTree<Binary> list(alloc);
12✔
618
            list.init_from_ref(ref);
12✔
619
            return list.size();
12✔
620
        }
84✔
621
        case col_type_Timestamp: {
12✔
622
            BPlusTree<Timestamp> list(alloc);
12✔
623
            list.init_from_ref(ref);
12✔
624
            return list.size();
12✔
625
        }
84✔
626
        case col_type_Float: {
12✔
627
            BPlusTree<Float> list(alloc);
12✔
628
            list.init_from_ref(ref);
12✔
629
            return list.size();
12✔
630
        }
84✔
631
        case col_type_Double: {
12✔
632
            BPlusTree<Double> list(alloc);
12✔
633
            list.init_from_ref(ref);
12✔
634
            return list.size();
12✔
635
        }
84✔
636
        case col_type_Decimal: {
12✔
637
            BPlusTree<Decimal128> list(alloc);
12✔
638
            list.init_from_ref(ref);
12✔
639
            return list.size();
12✔
640
        }
84✔
641
        case col_type_ObjectId: {
12✔
642
            BPlusTree<ObjectId> list(alloc);
12✔
643
            list.init_from_ref(ref);
12✔
644
            return list.size();
12✔
645
        }
84✔
646
        case col_type_UUID: {
12✔
647
            BPlusTree<UUID> list(alloc);
12✔
648
            list.init_from_ref(ref);
12✔
649
            return list.size();
12✔
650
        }
84✔
651
        case col_type_Mixed: {
✔
652
            BPlusTree<Mixed> list(alloc);
×
653
            list.init_from_ref(ref);
×
654
            return list.size();
×
655
        }
84✔
656
        case col_type_Link: {
12✔
657
            BPlusTree<ObjKey> list(alloc);
12✔
658
            list.init_from_ref(ref);
12✔
659
            return list.size();
12✔
660
        }
84✔
661
        case col_type_TypedLink: {
✔
662
            BPlusTree<ObjLink> list(alloc);
×
663
            list.init_from_ref(ref);
×
664
            return list.size();
×
665
        }
84✔
666
        case col_type_BackLink:
✔
667
            break;
×
668
    }
204✔
669
    REALM_TERMINATE("Unsupported column type.");
670
}
×
671

672
size_t NotNode::find_first_local(size_t start, size_t end)
673
{
18,216✔
674
    if (start <= m_known_range_start && end >= m_known_range_end) {
18,216✔
675
        return find_first_covers_known(start, end);
2,484✔
676
    }
2,484✔
677
    else if (start >= m_known_range_start && end <= m_known_range_end) {
15,732✔
678
        return find_first_covered_by_known(start, end);
15,228✔
679
    }
15,228✔
680
    else if (start < m_known_range_start && end >= m_known_range_start) {
504!
681
        return find_first_overlap_lower(start, end);
×
682
    }
×
683
    else if (start <= m_known_range_end && end > m_known_range_end) {
504✔
684
        return find_first_overlap_upper(start, end);
168✔
685
    }
168✔
686
    else { // start > m_known_range_end || end < m_known_range_start
336✔
687
        return find_first_no_overlap(start, end);
336✔
688
    }
336✔
689
}
18,216✔
690

691
bool NotNode::evaluate_at(size_t rowndx)
692
{
23,904✔
693
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
23,904✔
694
}
23,904✔
695

696
void NotNode::update_known(size_t start, size_t end, size_t first)
697
{
2,892✔
698
    m_known_range_start = start;
2,892✔
699
    m_known_range_end = end;
2,892✔
700
    m_first_in_known_range = first;
2,892✔
701
}
2,892✔
702

703
size_t NotNode::find_first_loop(size_t start, size_t end)
704
{
20,700✔
705
    for (size_t i = start; i < end; ++i) {
27,288✔
706
        if (evaluate_at(i)) {
23,904✔
707
            return i;
17,316✔
708
        }
17,316✔
709
    }
23,904✔
710
    return not_found;
3,384✔
711
}
20,700✔
712

713
size_t NotNode::find_first_covers_known(size_t start, size_t end)
714
{
2,484✔
715
    // CASE: start-end covers the known range
716
    // [    ######    ]
717
    REALM_ASSERT_DEBUG(start <= m_known_range_start && end >= m_known_range_end);
2,484✔
718
    size_t result = find_first_loop(start, m_known_range_start);
2,484✔
719
    if (result != not_found) {
2,484✔
720
        update_known(start, m_known_range_end, result);
×
721
    }
×
722
    else {
2,484✔
723
        if (m_first_in_known_range != not_found) {
2,484✔
724
            update_known(start, m_known_range_end, m_first_in_known_range);
×
725
            result = m_first_in_known_range;
×
726
        }
×
727
        else {
2,484✔
728
            result = find_first_loop(m_known_range_end, end);
2,484✔
729
            update_known(start, end, result);
2,484✔
730
        }
2,484✔
731
    }
2,484✔
732
    return result;
2,484✔
733
}
2,484✔
734

735
size_t NotNode::find_first_covered_by_known(size_t start, size_t end)
736
{
15,228✔
737
    REALM_ASSERT_DEBUG(start >= m_known_range_start && end <= m_known_range_end);
15,228✔
738
    // CASE: the known range covers start-end
739
    // ###[#####]###
740
    if (m_first_in_known_range != not_found) {
15,228✔
741
        if (m_first_in_known_range > end) {
15,228✔
742
            return not_found;
×
743
        }
×
744
        else if (m_first_in_known_range >= start) {
15,228✔
745
            return m_first_in_known_range;
×
746
        }
×
747
    }
15,228✔
748
    // The first known match is before start, so we can't use the results to improve
749
    // heuristics.
750
    return find_first_loop(start, end);
15,228✔
751
}
15,228✔
752

753
size_t NotNode::find_first_overlap_lower(size_t start, size_t end)
754
{
×
755
    REALM_ASSERT_DEBUG(start < m_known_range_start && end >= m_known_range_start && end <= m_known_range_end);
×
756
    static_cast<void>(end);
×
757
    // CASE: partial overlap, lower end
758
    // [   ###]#####
759
    size_t result;
×
760
    result = find_first_loop(start, m_known_range_start);
×
761
    if (result == not_found) {
×
762
        result = m_first_in_known_range;
×
763
    }
×
764
    update_known(start, m_known_range_end, result);
×
765
    return result < end ? result : not_found;
×
766
}
×
767

768
size_t NotNode::find_first_overlap_upper(size_t start, size_t end)
769
{
168✔
770
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
168✔
771
    // CASE: partial overlap, upper end
772
    // ####[###    ]
773
    size_t result;
168✔
774
    if (m_first_in_known_range != not_found) {
168✔
775
        if (m_first_in_known_range >= start) {
144✔
776
            result = m_first_in_known_range;
×
777
            update_known(m_known_range_start, end, result);
×
778
        }
×
779
        else {
144✔
780
            result = find_first_loop(start, end);
144✔
781
            update_known(m_known_range_start, end, m_first_in_known_range);
144✔
782
        }
144✔
783
    }
144✔
784
    else {
24✔
785
        result = find_first_loop(m_known_range_end, end);
24✔
786
        update_known(m_known_range_start, end, result);
24✔
787
    }
24✔
788
    return result;
168✔
789
}
168✔
790

791
size_t NotNode::find_first_no_overlap(size_t start, size_t end)
792
{
336✔
793
    REALM_ASSERT_DEBUG((start < m_known_range_start && end < m_known_range_start) ||
336!
794
                       (start > m_known_range_end && end > m_known_range_end));
336✔
795
    // CASE: no overlap
796
    // ### [    ]   or    [    ] ####
797
    // if input is a larger range, discard and replace with results.
798
    size_t result = find_first_loop(start, end);
336✔
799
    if (end - start > m_known_range_end - m_known_range_start) {
336✔
800
        update_known(start, end, result);
240✔
801
    }
240✔
802
    return result;
336✔
803
}
336✔
804

805
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
806
    : m_expression(std::move(expression))
35,655✔
807
{
71,310✔
808
    m_dT = 50.0;
71,310✔
809
}
71,310✔
810

811
void ExpressionNode::table_changed()
812
{
76,662✔
813
    m_expression->set_base_table(m_table);
76,662✔
814
}
76,662✔
815

816
void ExpressionNode::cluster_changed()
817
{
125,961✔
818
    m_expression->set_cluster(m_cluster);
125,961✔
819
}
125,961✔
820

821
void ExpressionNode::init(bool will_query_ranges)
822
{
86,781✔
823
    ParentNode::init(will_query_ranges);
86,781✔
824
    m_dT = m_expression->init();
86,781✔
825
}
86,781✔
826

827
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
828
{
21,366✔
829
    if (m_expression) {
21,366✔
830
        return m_expression->description(state);
21,366✔
831
    }
21,366✔
832
    else {
×
833
        return "empty expression";
×
834
    }
×
835
}
21,366✔
836

837
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
838
{
9,648✔
839
    m_expression->collect_dependencies(tables);
9,648✔
840
}
9,648✔
841

842
size_t ExpressionNode::find_first_local(size_t start, size_t end)
843
{
1,947,357✔
844
    return m_expression->find_first(start, end);
1,947,357✔
845
}
1,947,357✔
846

847
std::unique_ptr<ParentNode> ExpressionNode::clone() const
848
{
72,624✔
849
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
72,624✔
850
}
72,624✔
851

852
ExpressionNode::ExpressionNode(const ExpressionNode& from)
853
    : ParentNode(from)
36,312✔
854
    , m_expression(from.m_expression->clone())
36,312✔
855
{
72,624✔
856
}
72,624✔
857

858
template <>
859
size_t LinksToNode<Equal>::find_first_local(size_t start, size_t end)
860
{
6,050,004✔
861
    if (m_condition_column_key.is_collection()) {
6,050,004✔
862
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
6,013,281✔
863
        if (m_condition_column_key.is_dictionary()) {
6,013,281✔
864
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
120✔
865
            Array top(alloc);
120✔
866
            for (size_t i = start; i < end; i++) {
168✔
867
                if (ref_type ref = get_ref(i)) {
144✔
868
                    top.init_from_ref(ref);
144✔
869
                    BPlusTree<Mixed> values(alloc);
144✔
870
                    values.set_parent(&top, 1);
144✔
871
                    values.init_from_parent();
144✔
872
                    for (auto& key : m_target_keys) {
192✔
873
                        ObjLink link(target_table_key, key);
192✔
874
                        if (values.find_first(link) != not_found)
192✔
875
                            return i;
96✔
876
                    }
192✔
877
                }
144✔
878
            }
144✔
879
        }
120✔
880
        else {
6,013,161✔
881
            // LinkLists never contain null
882
            if (!m_target_keys[0] && m_target_keys.size() == 1 && start != end)
6,013,161✔
883
                return not_found;
6✔
884

885
            BPlusTree<ObjKey> links(alloc);
6,013,155✔
886
            for (size_t i = start; i < end; i++) {
6,021,432✔
887
                if (ref_type ref = get_ref(i)) {
6,020,901✔
888
                    links.init_from_ref(ref);
6,016,632✔
889
                    for (auto& key : m_target_keys) {
6,016,689✔
890
                        if (key) {
6,016,689✔
891
                            if (links.find_first(key) != not_found)
6,016,677✔
892
                                return i;
6,012,624✔
893
                        }
6,016,677✔
894
                    }
6,016,689✔
895
                }
6,016,632✔
896
            }
6,020,901✔
897
        }
6,013,155✔
898
    }
6,013,281✔
899
    else if (m_list) {
36,750✔
900
        for (auto& key : m_target_keys) {
36,750✔
901
            auto pos = m_list->find_first(key, start, end);
36,750✔
902
            if (pos != realm::npos) {
36,750✔
903
                return pos;
7,662✔
904
            }
7,662✔
905
        }
36,750✔
906
    }
36,750✔
907

908
    return not_found;
29,616✔
909
}
6,050,004✔
910

911
template <>
912
size_t LinksToNode<NotEqual>::find_first_local(size_t start, size_t end)
913
{
6,756✔
914
    // NotEqual only makes sense for a single value
915
    REALM_ASSERT(m_target_keys.size() == 1);
6,756✔
916
    ObjKey key = m_target_keys[0];
6,756✔
917

918
    if (m_condition_column_key.is_collection()) {
6,756✔
919
        Allocator& alloc = m_table.unchecked_ptr()->get_alloc();
2,418✔
920
        if (m_condition_column_key.is_dictionary()) {
2,418✔
921
            auto target_table_key = m_table->get_opposite_table(m_condition_column_key)->get_key();
36✔
922
            Array top(alloc);
36✔
923
            for (size_t i = start; i < end; i++) {
48✔
924
                if (ref_type ref = get_ref(i)) {
36✔
925
                    top.init_from_ref(ref);
36✔
926
                    BPlusTree<Mixed> values(alloc);
36✔
927
                    values.set_parent(&top, 1);
36✔
928
                    values.init_from_parent();
36✔
929

930
                    ObjLink link(target_table_key, key);
36✔
931
                    bool found = false;
36✔
932
                    values.for_all([&](const Mixed& val) {
48✔
933
                        if (val != link) {
48✔
934
                            found = true;
24✔
935
                        }
24✔
936
                        return !found;
48✔
937
                    });
48✔
938
                    if (found)
36✔
939
                        return i;
24✔
940
                }
36✔
941
            }
36✔
942
        }
36✔
943
        else {
2,382✔
944
            BPlusTree<ObjKey> links(alloc);
2,382✔
945
            for (size_t i = start; i < end; i++) {
4,746✔
946
                if (ref_type ref = get_ref(i)) {
4,512✔
947
                    links.init_from_ref(ref);
2,364✔
948
                    auto sz = links.size();
2,364✔
949
                    for (size_t j = 0; j < sz; j++) {
2,610✔
950
                        if (links.get(j) != key) {
2,394✔
951
                            return i;
2,148✔
952
                        }
2,148✔
953
                    }
2,394✔
954
                }
2,364✔
955
            }
4,512✔
956
        }
2,382✔
957
    }
2,418✔
958
    else if (m_list) {
4,338✔
959
        for (size_t i = start; i < end; i++) {
4,854✔
960
            if (m_list->get(i) != key) {
4,674✔
961
                return i;
4,158✔
962
            }
4,158✔
963
        }
4,674✔
964
    }
4,338✔
965

966
    return not_found;
426✔
967
}
6,756✔
968

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