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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

90.49
/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,165,081✔
37
}
3,165,081✔
38

39

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

2,534,769✔
46
    while (REALM_LIKELY(start < end)) {
4,770,066✔
47
        size_t m = m_children[current_cond]->find_first_local(start, end);
4,744,713✔
48

2,699,805✔
49
        if (m != start) {
4,744,713✔
50
            // Pointer advanced - we will have to check all other conditions
1,363,368✔
51
            nb_cond_to_test = sz;
2,561,457✔
52
            start = m;
2,561,457✔
53
        }
2,561,457✔
54

2,699,805✔
55
        nb_cond_to_test--;
4,744,713✔
56

2,699,805✔
57
        // Optimized for one condition where this will be true first time
2,699,805✔
58
        if (REALM_LIKELY(nb_cond_to_test == 0))
4,744,713✔
59
            return m;
4,575,207✔
60

178,737✔
61
        current_cond++;
348,243✔
62

178,737✔
63
        if (current_cond == sz)
348,243✔
64
            current_cond = 0;
115,509✔
65
    }
348,243✔
66
    return not_found;
2,546,421✔
67
}
4,421,823✔
68

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

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

2,329,239✔
99
    m_state = st;
5,678,445✔
100
    m_source_column = source_column;
5,678,445✔
101
    size_t local_matches = 0;
5,678,445✔
102

2,329,239✔
103
    if (m_children.size() == 1) {
6,297,435✔
104
        return find_all_local(start, end);
6,290,643✔
105
    }
6,290,643✔
106

2,147,483,647✔
107
    size_t r = start - 1;
2,147,490,439✔
108
    for (;;) {
2,147,900,746✔
109
        if (local_matches == local_limit) {
964,284✔
110
            m_dD = double(r - start) / (local_matches + 1.1);
15,768✔
111
            return r + 1;
15,768✔
112
        }
15,768✔
113

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

518,457✔
122
        local_matches++;
911,295✔
123

518,457✔
124
        // Find first match in remaining condition nodes
518,457✔
125
        size_t m = r;
911,295✔
126

518,457✔
127
        for (size_t c = 1; c < m_children.size(); c++) {
1,426,467✔
128
            m = m_children[c]->find_first_local(r, r + 1);
977,061✔
129
            if (m != r) {
977,061✔
130
                break;
461,889✔
131
            }
461,889✔
132
        }
977,061✔
133

518,457✔
134
        // If index of first match in this node equals index of first match in all remaining nodes, we have a final
518,457✔
135
        // match
518,457✔
136
        if (m == r) {
911,295✔
137
            Mixed val;
449,475✔
138
            if (source_column) {
449,475✔
139
                val = source_column->get_any(r);
408✔
140
            }
408✔
141
            bool cont = st->match(r, val);
449,475✔
142
            if (!cont) {
449,475✔
143
                return static_cast<size_t>(-1);
21✔
144
            }
21✔
145
        }
449,475✔
146
    }
911,295✔
147
}
2,147,490,439✔
148

149
size_t ParentNode::find_all_local(size_t start, size_t end)
150
{
308,742✔
151
    while (start < end) {
10,519,647✔
152
        start = find_first_local(start, end);
10,210,905✔
153
        if (start != not_found) {
10,210,905✔
154
            Mixed val;
10,038,888✔
155
            if (m_source_column) {
10,038,888✔
156
                val = m_source_column->get_any(start);
1,014✔
157
            }
1,014✔
158
            bool cont = m_state->match(start, val);
10,038,888✔
159
            if (!cont) {
10,038,888✔
160
                return static_cast<size_t>(-1);
×
161
            }
×
162
            start++;
10,038,888✔
163
        }
10,038,888✔
164
    }
10,210,905✔
165
    return end;
308,742✔
166
}
308,742✔
167

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

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

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

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

195
    return not_found;
×
196
}
×
197

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

48✔
202
    StringData val_as_string;
96✔
203
    if (m_value.is_type(type_String)) {
96✔
204
        val_as_string = m_value.get<StringData>();
36✔
205
    }
36✔
206
    else if (m_value.is_type(type_Binary)) {
60✔
207
        BinaryData bin = m_value.get<BinaryData>();
24✔
208
        val_as_string = StringData(bin.data(), bin.size());
24✔
209
    }
24✔
210
    REALM_ASSERT(bool(m_index_evaluator) ==
96✔
211
                 (m_table.unchecked_ptr()->search_index_type(m_condition_column_key) == IndexType::General));
96✔
212
    if (m_index_evaluator) {
96✔
213
        auto index = ParentNode::m_table->get_search_index(ParentNode::m_condition_column_key);
30✔
214
        if (!val_as_string.is_null()) {
30✔
215
            m_index_matches.clear();
12✔
216
            constexpr bool case_insensitive = true;
12✔
217
            index->find_all(m_index_matches, val_as_string, case_insensitive);
12✔
218
            // It is unfortunate but necessary to check the type due to Binary and String
6✔
219
            // having the same StringIndex hash values
6✔
220
            m_index_matches.erase(std::remove_if(m_index_matches.begin(), m_index_matches.end(),
12✔
221
                                                 [this](const ObjKey& obj_key) {
12✔
222
                                                     Mixed to_check =
12✔
223
                                                         m_table->get_object(obj_key).get_any(m_condition_column_key);
12✔
224
                                                     return (!Mixed::types_are_comparable(to_check, m_value));
12✔
225
                                                 }),
12✔
226
                                  m_index_matches.end());
12✔
227
            m_index_evaluator->init(&m_index_matches);
12✔
228
        }
12✔
229
        else {
18✔
230
            // search for non string can use exact match
9✔
231
            m_index_evaluator->init(index, m_value);
18✔
232
        }
18✔
233
        m_dT = 0.0;
30✔
234
    }
30✔
235
    else {
66✔
236
        m_dT = 10.0;
66✔
237
        if (!val_as_string.is_null()) {
66✔
238
            auto upper = case_map(val_as_string, true);
48✔
239
            auto lower = case_map(val_as_string, false);
48✔
240
            if (!upper || !lower) {
48✔
UNCOV
241
                throw query_parser::InvalidQueryError(util::format("Malformed UTF-8: %1", val_as_string));
×
UNCOV
242
            }
×
243
            else {
48✔
244
                m_ucase = std::move(*upper);
48✔
245
                m_lcase = std::move(*lower);
48✔
246
            }
48✔
247
        }
48✔
248
    }
66✔
249
}
96✔
250

251
size_t MixedNode<EqualIns>::find_first_local(size_t start, size_t end)
252
{
114✔
253
    REALM_ASSERT(m_table);
114✔
254

57✔
255
    EqualIns cond;
114✔
256
    if (m_value.is_type(type_String, type_Binary)) {
114✔
257
        for (size_t i = start; i < end; i++) {
4,848✔
258
            QueryValue val(m_leaf->get(i));
4,800✔
259
            if (!Mixed::types_are_comparable(m_value, val)) {
4,800✔
260
                continue;
4,062✔
261
            }
4,062✔
262
            StringData val_as_str = val.export_to_type<StringData>();
738✔
263
            if (cond(m_value.export_to_type<StringData>(), m_ucase.c_str(), m_lcase.c_str(), val_as_str))
738✔
264
                return i;
30✔
265
        }
738✔
266
    }
78✔
267
    else {
36✔
268
        for (size_t i = start; i < end; i++) {
1,818✔
269
            QueryValue val(m_leaf->get(i));
1,800✔
270
            if (cond(val, m_value))
1,800✔
271
                return i;
18✔
272
        }
1,800✔
273
    }
36✔
274

57✔
275
    return not_found;
90✔
276
}
114✔
277

278
void StringNodeEqualBase::init(bool will_query_ranges)
279
{
78,984✔
280
    StringNodeBase::init(will_query_ranges);
78,984✔
281

39,177✔
282
    const bool uses_index = has_search_index();
78,984✔
283
    if (m_is_string_enum) {
78,984✔
284
        m_dT = 1.0;
2,658✔
285
    }
2,658✔
286
    else if (uses_index) {
76,326✔
287
        m_dT = 0.0;
3,204✔
288
    }
3,204✔
289
    else {
73,122✔
290
        m_dT = 10.0;
73,122✔
291
    }
73,122✔
292

39,177✔
293
    if (uses_index) {
78,984✔
294
        _search_index_init();
3,666✔
295
    }
3,666✔
296
}
78,984✔
297

298
size_t StringNodeEqualBase::find_first_local(size_t start, size_t end)
299
{
1,099,068✔
300
    REALM_ASSERT(m_table);
1,099,068✔
301

573,663✔
302
    if (m_index_evaluator) {
1,099,068✔
303
        return m_index_evaluator->do_search_index(m_cluster, start, end);
241,620✔
304
    }
241,620✔
305

447,372✔
306
    return _find_first_local(start, end);
857,448✔
307
}
857,448✔
308

309

310
void IndexEvaluator::init(SearchIndex* index, Mixed value)
311
{
657,696✔
312
    REALM_ASSERT(index);
657,696✔
313
    m_matching_keys = nullptr;
657,696✔
314
    FindRes fr;
657,696✔
315
    InternalFindResult res;
657,696✔
316

328,848✔
317
    m_last_start_key = ObjKey();
657,696✔
318
    m_results_start = 0;
657,696✔
319
    fr = index->find_all_no_copy(value, res);
657,696✔
320

328,848✔
321
    m_index_matches.reset();
657,696✔
322
    switch (fr) {
657,696✔
323
        case FindRes_single:
13,662✔
324
            m_actual_key = ObjKey(res.payload);
13,662✔
325
            m_results_end = 1;
13,662✔
326
            break;
13,662✔
327
        case FindRes_column:
1,551✔
328
            m_index_matches.reset(new IntegerColumn(index->get_alloc(), ref_type(res.payload))); // Throws
1,551✔
329
            m_results_start = res.start_ndx;
1,551✔
330
            m_results_end = res.end_ndx;
1,551✔
331
            m_actual_key = ObjKey(m_index_matches->get(m_results_start));
1,551✔
332
            break;
1,551✔
333
        case FindRes_not_found:
642,483✔
334
            m_results_end = 0;
642,483✔
335
            break;
642,483✔
336
    }
657,693✔
337
    m_results_ndx = m_results_start;
657,693✔
338
}
657,693✔
339

340
void IndexEvaluator::init(std::vector<ObjKey>* storage)
341
{
444✔
342
    REALM_ASSERT(storage);
444✔
343
    m_matching_keys = storage;
444✔
344
    m_actual_key = ObjKey();
444✔
345
    m_last_start_key = ObjKey();
444✔
346
    m_results_start = 0;
444✔
347
    m_results_end = m_matching_keys->size();
444✔
348
    m_results_ndx = 0;
444✔
349
    if (m_results_start != m_results_end) {
444✔
350
        m_actual_key = m_matching_keys->at(0);
378✔
351
    }
378✔
352
}
444✔
353

354
size_t IndexEvaluator::do_search_index(const Cluster* cluster, size_t start, size_t end)
355
{
885,579✔
356
    if (start >= end) {
885,579✔
357
        return not_found;
9✔
358
    }
9✔
359

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

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

102,699✔
380
        // If actual_key is bigger than last key, it is not in this leaf
102,699✔
381
        ObjKey last_key = cluster->get_real_key(end - 1);
189,885✔
382
        if (m_actual_key > last_key)
189,165✔
383
            return not_found;
116,442✔
384

39,600✔
385
        // Now actual_key must be found in leaf keys
39,600✔
386
        return cluster->lower_bound_key(ObjKey(m_actual_key.value - cluster->get_offset()));
72,723✔
387
    }
72,723✔
388
    return not_found;
694,932✔
389
}
694,932✔
390

391
void StringNode<Equal>::_search_index_init()
392
{
3,180✔
393
    REALM_ASSERT(bool(m_index_evaluator));
3,180✔
394
    auto index = ParentNode::m_table.unchecked_ptr()->get_search_index(ParentNode::m_condition_column_key);
3,180✔
395
    m_index_evaluator->init(index, StringData(StringNodeBase::m_value));
3,180✔
396
}
3,180✔
397

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

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

421
size_t StringNode<Equal>::_find_first_local(size_t start, size_t end)
422
{
856,044✔
423
    if (m_needles.empty()) {
856,044✔
424
        return m_leaf->find_first(m_value, start, end);
855,363✔
425
    }
855,363✔
426
    else {
681✔
427
        if (end == npos)
681✔
UNCOV
428
            end = m_leaf->size();
×
429
        REALM_ASSERT_3(start, <=, end);
681✔
430
        return find_first_haystack<20>(*m_leaf, m_needles, start, end);
681✔
431
    }
681✔
432
}
856,044✔
433

434
std::string StringNode<Equal>::describe(util::serializer::SerialisationState& state) const
435
{
59,196✔
436
    if (m_needles.empty()) {
59,196✔
437
        return StringNodeEqualBase::describe(state);
59,130✔
438
    }
59,130✔
439

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

452

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

462
size_t StringNode<EqualIns>::_find_first_local(size_t start, size_t end)
463
{
1,398✔
464
    EqualIns cond;
1,398✔
465
    for (size_t s = start; s < end; ++s) {
26,214✔
466
        StringData t = get_string(s);
24,936✔
467

12,468✔
468
        if (cond(StringData(m_value), m_ucase.c_str(), m_lcase.c_str(), t))
24,936✔
469
            return s;
120✔
470
    }
24,936✔
471

699✔
472
    return not_found;
1,338✔
473
}
1,398✔
474

475
StringNodeFulltext::StringNodeFulltext(StringData v, ColKey column, std::unique_ptr<LinkMap> lm)
476
    : StringNodeEqualBase(v, column)
477
    , m_link_map(std::move(lm))
478
{
324✔
479
    if (!m_link_map)
324✔
480
        m_link_map = std::make_unique<LinkMap>();
312✔
481
}
324✔
482

483
void StringNodeFulltext::table_changed()
484
{
336✔
485
    StringNodeEqualBase::table_changed();
336✔
486
    m_link_map->set_base_table(m_table);
336✔
487
}
336✔
488

489
StringNodeFulltext::StringNodeFulltext(const StringNodeFulltext& other)
490
    : StringNodeEqualBase(other)
491
{
432✔
492
    m_link_map = std::make_unique<LinkMap>(*other.m_link_map);
432✔
493
}
432✔
494

495
void StringNodeFulltext::_search_index_init()
496
{
360✔
497
    StringIndex* index = m_link_map->get_target_table()->get_string_index(ParentNode::m_condition_column_key);
360✔
498
    REALM_ASSERT(index && index->is_fulltext_index());
360✔
499
    m_index_matches.clear();
360✔
500
    index->find_all_fulltext(m_index_matches, StringData(StringNodeBase::m_value));
360✔
501

180✔
502
    // If links exists, use backlinks to find the original objects
180✔
503
    if (m_link_map->links_exist()) {
360✔
504
        std::set<ObjKey> tmp;
12✔
505
        for (auto k : m_index_matches) {
24✔
506
            auto keys = m_link_map->get_origin_objkeys(k);
24✔
507
            tmp.insert(keys.begin(), keys.end());
24✔
508
        }
24✔
509
        m_index_matches.assign(tmp.begin(), tmp.end());
12✔
510
    }
12✔
511

180✔
512
    m_index_evaluator = IndexEvaluator{};
360✔
513
    m_index_evaluator->init(&m_index_matches);
360✔
514
}
360✔
515

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

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

638
size_t NotNode::find_first_local(size_t start, size_t end)
639
{
13,776✔
640
    if (start <= m_known_range_start && end >= m_known_range_end) {
13,776✔
641
        return find_first_covers_known(start, end);
2,004✔
642
    }
2,004✔
643
    else if (start >= m_known_range_start && end <= m_known_range_end) {
11,772✔
644
        return find_first_covered_by_known(start, end);
11,268✔
645
    }
11,268✔
646
    else if (start < m_known_range_start && end >= m_known_range_start) {
504!
UNCOV
647
        return find_first_overlap_lower(start, end);
×
UNCOV
648
    }
×
649
    else if (start <= m_known_range_end && end > m_known_range_end) {
504✔
650
        return find_first_overlap_upper(start, end);
168✔
651
    }
168✔
652
    else { // start > m_known_range_end || end < m_known_range_start
336✔
653
        return find_first_no_overlap(start, end);
336✔
654
    }
336✔
655
}
13,776✔
656

657
bool NotNode::evaluate_at(size_t rowndx)
658
{
17,664✔
659
    return m_condition->find_first(rowndx, rowndx + 1) == not_found;
17,664✔
660
}
17,664✔
661

662
void NotNode::update_known(size_t start, size_t end, size_t first)
663
{
2,412✔
664
    m_known_range_start = start;
2,412✔
665
    m_known_range_end = end;
2,412✔
666
    m_first_in_known_range = first;
2,412✔
667
}
2,412✔
668

669
size_t NotNode::find_first_loop(size_t start, size_t end)
670
{
15,780✔
671
    for (size_t i = start; i < end; ++i) {
20,448✔
672
        if (evaluate_at(i)) {
17,664✔
673
            return i;
12,996✔
674
        }
12,996✔
675
    }
17,664✔
676
    return not_found;
9,282✔
677
}
15,780✔
678

679
size_t NotNode::find_first_covers_known(size_t start, size_t end)
680
{
2,004✔
681
    // CASE: start-end covers the known range
1,002✔
682
    // [    ######    ]
1,002✔
683
    REALM_ASSERT_DEBUG(start <= m_known_range_start && end >= m_known_range_end);
2,004✔
684
    size_t result = find_first_loop(start, m_known_range_start);
2,004✔
685
    if (result != not_found) {
2,004✔
686
        update_known(start, m_known_range_end, result);
×
687
    }
×
688
    else {
2,004✔
689
        if (m_first_in_known_range != not_found) {
2,004✔
UNCOV
690
            update_known(start, m_known_range_end, m_first_in_known_range);
×
UNCOV
691
            result = m_first_in_known_range;
×
UNCOV
692
        }
×
693
        else {
2,004✔
694
            result = find_first_loop(m_known_range_end, end);
2,004✔
695
            update_known(start, end, result);
2,004✔
696
        }
2,004✔
697
    }
2,004✔
698
    return result;
2,004✔
699
}
2,004✔
700

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

719
size_t NotNode::find_first_overlap_lower(size_t start, size_t end)
720
{
×
721
    REALM_ASSERT_DEBUG(start < m_known_range_start && end >= m_known_range_start && end <= m_known_range_end);
×
722
    static_cast<void>(end);
×
723
    // CASE: partial overlap, lower end
724
    // [   ###]#####
725
    size_t result;
×
726
    result = find_first_loop(start, m_known_range_start);
×
727
    if (result == not_found) {
×
UNCOV
728
        result = m_first_in_known_range;
×
UNCOV
729
    }
×
UNCOV
730
    update_known(start, m_known_range_end, result);
×
UNCOV
731
    return result < end ? result : not_found;
×
UNCOV
732
}
×
733

734
size_t NotNode::find_first_overlap_upper(size_t start, size_t end)
735
{
168✔
736
    REALM_ASSERT_DEBUG(start <= m_known_range_end && start >= m_known_range_start && end > m_known_range_end);
168✔
737
    // CASE: partial overlap, upper end
84✔
738
    // ####[###    ]
84✔
739
    size_t result;
168✔
740
    if (m_first_in_known_range != not_found) {
168✔
741
        if (m_first_in_known_range >= start) {
144✔
UNCOV
742
            result = m_first_in_known_range;
×
UNCOV
743
            update_known(m_known_range_start, end, result);
×
UNCOV
744
        }
×
745
        else {
144✔
746
            result = find_first_loop(start, end);
144✔
747
            update_known(m_known_range_start, end, m_first_in_known_range);
144✔
748
        }
144✔
749
    }
144✔
750
    else {
24✔
751
        result = find_first_loop(m_known_range_end, end);
24✔
752
        update_known(m_known_range_start, end, result);
24✔
753
    }
24✔
754
    return result;
168✔
755
}
168✔
756

757
size_t NotNode::find_first_no_overlap(size_t start, size_t end)
758
{
336✔
759
    REALM_ASSERT_DEBUG((start < m_known_range_start && end < m_known_range_start) ||
336!
760
                       (start > m_known_range_end && end > m_known_range_end));
336✔
761
    // CASE: no overlap
168✔
762
    // ### [    ]   or    [    ] ####
168✔
763
    // if input is a larger range, discard and replace with results.
168✔
764
    size_t result = find_first_loop(start, end);
336✔
765
    if (end - start > m_known_range_end - m_known_range_start) {
336✔
766
        update_known(start, end, result);
240✔
767
    }
240✔
768
    return result;
336✔
769
}
336✔
770

771
ExpressionNode::ExpressionNode(std::unique_ptr<Expression> expression)
772
    : m_expression(std::move(expression))
773
{
65,637✔
774
    m_dT = 50.0;
65,637✔
775
}
65,637✔
776

777
void ExpressionNode::table_changed()
778
{
70,998✔
779
    m_expression->set_base_table(m_table);
70,998✔
780
}
70,998✔
781

782
void ExpressionNode::cluster_changed()
783
{
92,796✔
784
    m_expression->set_cluster(m_cluster);
92,796✔
785
}
92,796✔
786

787
void ExpressionNode::init(bool will_query_ranges)
788
{
75,186✔
789
    ParentNode::init(will_query_ranges);
75,186✔
790
    m_dT = m_expression->init();
75,186✔
791
}
75,186✔
792

793
std::string ExpressionNode::describe(util::serializer::SerialisationState& state) const
794
{
18,768✔
795
    if (m_expression) {
18,768✔
796
        return m_expression->description(state);
18,768✔
797
    }
18,768✔
UNCOV
798
    else {
×
UNCOV
799
        return "empty expression";
×
UNCOV
800
    }
×
801
}
18,768✔
802

803
void ExpressionNode::collect_dependencies(std::vector<TableKey>& tables) const
804
{
3,576✔
805
    m_expression->collect_dependencies(tables);
3,576✔
806
}
3,576✔
807

808
size_t ExpressionNode::find_first_local(size_t start, size_t end)
809
{
1,673,553✔
810
    return m_expression->find_first(start, end);
1,673,553✔
811
}
1,673,553✔
812

813
std::unique_ptr<ParentNode> ExpressionNode::clone() const
814
{
60,966✔
815
    return std::unique_ptr<ParentNode>(new ExpressionNode(*this));
60,966✔
816
}
60,966✔
817

818
ExpressionNode::ExpressionNode(const ExpressionNode& from)
819
    : ParentNode(from)
820
    , m_expression(from.m_expression->clone())
821
{
60,969✔
822
}
60,969✔
823

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

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

3,024,999✔
874
    return not_found;
3,039,822✔
875
}
6,049,998✔
876

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

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

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

3,348✔
932
    return not_found;
3,555✔
933
}
6,696✔
934

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