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

realm / realm-core / 2544

05 Aug 2024 04:04PM UTC coverage: 91.108%. Remained the same
2544

push

Evergreen

web-flow
Only track pending client resets done by the same core version (#7944)

If the previous attempt at performing a client reset was done with a different
core version then we should retry the client reset as the new version may have
fixed a bug that made the previous attempt fail (or may be a downgrade to a
version before when the bug was introduced). This also simplifies the tracking
as it means that we don't need to be able to read trackers created by different
versions.

This also means that we can freely change the schema of the table, which this
takes advantage of to drop the unused primary key and make the error required,
as we never actually stored null and the code reading it would have crashed if
it encountered a null error.

102728 of 181534 branches covered (56.59%)

138 of 153 new or added lines in 10 files covered. (90.2%)

59 existing lines in 11 files now uncovered.

216763 of 237918 relevant lines covered (91.11%)

5998814.86 hits per line

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

87.37
/src/realm/list.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

20
#include "realm/list.hpp"
21
#include "realm/cluster_tree.hpp"
22
#include "realm/array_basic.hpp"
23
#include "realm/array_integer.hpp"
24
#include "realm/array_bool.hpp"
25
#include "realm/array_string.hpp"
26
#include "realm/array_binary.hpp"
27
#include "realm/array_timestamp.hpp"
28
#include "realm/array_decimal128.hpp"
29
#include "realm/array_fixed_bytes.hpp"
30
#include "realm/array_typed_link.hpp"
31
#include "realm/array_mixed.hpp"
32
#include "realm/column_type_traits.hpp"
33
#include "realm/object_id.hpp"
34
#include "realm/table.hpp"
35
#include "realm/table_view.hpp"
36
#include "realm/group.hpp"
37
#include "realm/replication.hpp"
38
#include "realm/dictionary.hpp"
39
#include "realm/index_string.hpp"
40

41
namespace realm {
42

43
/****************************** Lst aggregates *******************************/
44

45
namespace {
46
void do_sort(std::vector<size_t>& indices, size_t size, util::FunctionRef<bool(size_t, size_t)> comp)
47
{
5,574✔
48
    auto old_size = indices.size();
5,574✔
49
    indices.reserve(size);
5,574✔
50
    if (size < old_size) {
5,574✔
51
        // If list size has decreased, we have to start all over
52
        indices.clear();
108✔
53
        old_size = 0;
108✔
54
    }
108✔
55
    for (size_t i = old_size; i < size; i++) {
34,428✔
56
        // If list size has increased, just add the missing indices
57
        indices.push_back(i);
28,854✔
58
    }
28,854✔
59

60
    auto b = indices.begin();
5,574✔
61
    auto e = indices.end();
5,574✔
62
    std::sort(b, e, comp);
5,574✔
63
}
5,574✔
64
} // anonymous namespace
65

66
template <class T>
67
void Lst<T>::sort(std::vector<size_t>& indices, bool ascending) const
68
{
5,340✔
69
    update();
5,340✔
70

71
    auto tree = m_tree.get();
5,340✔
72
    if (ascending) {
5,340!
73
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
57,174✔
74
            return tree->get(i1) < tree->get(i2);
57,174✔
75
        });
57,174✔
76
    }
4,500✔
77
    else {
840✔
78
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
16,380✔
79
            return tree->get(i1) > tree->get(i2);
16,380✔
80
        });
16,380✔
81
    }
840✔
82
}
5,340✔
83

84
// std::unique, but leaving the minimum value rather than the first found value
85
// for runs of duplicates. This makes distinct stable without relying on a
86
// stable sort, which makes it easier to write tests and avoids surprising results
87
// where distinct appears to change the order of elements
88
template <class Iterator, class Predicate>
89
static Iterator min_unique(Iterator first, Iterator last, Predicate pred)
90
{
858✔
91
    if (first == last) {
858✔
92
        return first;
×
93
    }
×
94

95
    Iterator result = first;
858✔
96
    while (++first != last) {
6,792!
97
        bool equal = pred(*result, *first);
5,934✔
98
        if ((equal && *result > *first) || (!equal && ++result != first))
5,934!
99
            *result = *first;
2,784✔
100
    }
5,934✔
101
    return ++result;
858✔
102
}
858✔
103

104
template <class T>
105
void Lst<T>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
106
{
828✔
107
    indices.clear();
828✔
108
    sort(indices, sort_order.value_or(true));
828✔
109
    if (indices.empty()) {
828✔
110
        return;
×
111
    }
×
112

113
    auto tree = m_tree.get();
828✔
114
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
5,268✔
115
        return tree->get(i1) == tree->get(i2);
5,268✔
116
    });
5,268✔
117

118
    // Erase the duplicates
119
    indices.erase(duplicates, indices.end());
828✔
120

121
    if (!sort_order) {
828✔
122
        // Restore original order
123
        std::sort(indices.begin(), indices.end());
588✔
124
    }
588✔
125
}
828✔
126

127
/********************************** LstBase *********************************/
128

129
template <>
130
void CollectionBaseImpl<LstBase>::to_json(std::ostream& out, JSONOutputMode output_mode,
131
                                          util::FunctionRef<void(const Mixed&)> fn) const
132
{
636✔
133
    auto sz = size();
636✔
134
    out << "[";
636✔
135
    for (size_t i = 0; i < sz; i++) {
1,800✔
136
        if (i > 0)
1,164✔
137
            out << ",";
678✔
138
        Mixed val = get_any(i);
1,164✔
139
        if (val.is_type(type_Link, type_TypedLink)) {
1,164✔
140
            fn(val);
84✔
141
        }
84✔
142
        else {
1,080✔
143
            val.to_json(out, output_mode);
1,080✔
144
        }
1,080✔
145
    }
1,164✔
146
    out << "]";
636✔
147
}
636✔
148

149
/***************************** Lst<Stringdata> ******************************/
150

151
template <>
152
void Lst<StringData>::do_insert(size_t ndx, StringData value)
153
{
733,431✔
154
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
733,431✔
155
        // Inserting a value already present is idempotent
156
        index->insert(get_owner_key(), value);
180✔
157
    }
180✔
158
    m_tree->insert(ndx, value);
733,431✔
159
}
733,431✔
160

161
template <>
162
void Lst<StringData>::do_set(size_t ndx, StringData value)
163
{
1,881✔
164
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
1,881✔
165
        auto old_value = m_tree->get(ndx);
30✔
166
        size_t nb_old = 0;
30✔
167
        m_tree->for_all([&](StringData val) {
120✔
168
            if (val == old_value) {
120✔
169
                nb_old++;
42✔
170
            }
42✔
171
            return !(nb_old > 1);
120✔
172
        });
120✔
173

174
        if (nb_old == 1) {
30✔
175
            // Remove last one
176
            index->erase_string(get_owner_key(), old_value);
18✔
177
        }
18✔
178
        // Inserting a value already present is idempotent
179
        index->insert(get_owner_key(), value);
30✔
180
    }
30✔
181
    m_tree->set(ndx, value);
1,881✔
182
}
1,881✔
183

184
template <>
185
inline void Lst<StringData>::do_remove(size_t ndx)
186
{
13,788✔
187
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
13,788✔
188
        auto old_value = m_tree->get(ndx);
6✔
189
        size_t nb_old = 0;
6✔
190
        m_tree->for_all([&](StringData val) {
30✔
191
            if (val == old_value) {
30✔
192
                nb_old++;
6✔
193
            }
6✔
194
            return !(nb_old > 1);
30✔
195
        });
30✔
196

197
        if (nb_old == 1) {
6✔
198
            index->erase_string(get_owner_key(), old_value);
6✔
199
        }
6✔
200
    }
6✔
201
    m_tree->erase(ndx);
13,788✔
202
}
13,788✔
203

204
template <>
205
inline void Lst<StringData>::do_clear()
206
{
162✔
207
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
162✔
208
        index->erase_list(get_owner_key(), *this);
6✔
209
    }
6✔
210
    m_tree->clear();
162✔
211
}
162✔
212

213
/********************************* Lst<Key> *********************************/
214

215
template <>
216
void Lst<ObjKey>::do_set(size_t ndx, ObjKey target_key)
217
{
41,478✔
218
    auto origin_table = get_table_unchecked();
41,478✔
219
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
41,478✔
220
    ObjKey old_key = this->get(ndx);
41,478✔
221
    CascadeState state(CascadeState::Mode::Strong);
41,478✔
222
    bool recurse = replace_backlink(m_col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
41,478✔
223

224
    m_tree->set(ndx, target_key);
41,478✔
225

226
    if (recurse) {
41,478✔
227
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
60✔
228
    }
60✔
229
    if (target_key.is_unresolved()) {
41,478✔
230
        if (!old_key.is_unresolved())
24✔
231
            m_tree->set_context_flag(true);
24✔
232
    }
24✔
233
    else if (old_key.is_unresolved()) {
41,454✔
234
        // We might have removed the last unresolved link - check it
235
        _impl::check_for_last_unresolved(m_tree.get());
12✔
236
    }
12✔
237
}
41,478✔
238

239
template <>
240
void Lst<ObjKey>::do_insert(size_t ndx, ObjKey target_key)
241
{
6,431,847✔
242
    auto origin_table = get_table_unchecked();
6,431,847✔
243
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
6,431,847✔
244
    set_backlink(m_col_key, {target_table_key, target_key});
6,431,847✔
245
    m_tree->insert(ndx, target_key);
6,431,847✔
246
    if (target_key.is_unresolved()) {
6,431,847✔
247
        m_tree->set_context_flag(true);
27,084✔
248
    }
27,084✔
249
}
6,431,847✔
250

251
template <>
252
void Lst<ObjKey>::do_remove(size_t ndx)
253
{
2,784✔
254
    auto origin_table = get_table_unchecked();
2,784✔
255
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
2,784✔
256
    ObjKey old_key = get(ndx);
2,784✔
257
    CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
2,784✔
258

259
    bool recurse = remove_backlink(m_col_key, {target_table_key, old_key}, state);
2,784✔
260

261
    m_tree->erase(ndx);
2,784✔
262

263
    if (recurse) {
2,784✔
264
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
1,182✔
265
    }
1,182✔
266
    if (old_key.is_unresolved()) {
2,784✔
267
        // We might have removed the last unresolved link - check it
268
        _impl::check_for_last_unresolved(m_tree.get());
18✔
269
    }
18✔
270
}
2,784✔
271

272
template <>
273
void Lst<ObjKey>::do_clear()
274
{
4,239✔
275
    auto origin_table = get_table_unchecked();
4,239✔
276
    TableRef target_table = get_obj().get_target_table(m_col_key);
4,239✔
277

278
    size_t sz = size();
4,239✔
279
    if (!target_table->is_embedded()) {
4,239✔
280
        size_t ndx = sz;
396✔
281
        while (ndx--) {
41,016✔
282
            do_set(ndx, null_key);
40,620✔
283
            m_tree->erase(ndx);
40,620✔
284
        }
40,620✔
285
        m_tree->set_context_flag(false);
396✔
286
        return;
396✔
287
    }
396✔
288

289
    TableKey target_table_key = target_table->get_key();
3,843✔
290
    ColKey backlink_col = origin_table->get_opposite_column(m_col_key);
3,843✔
291

292
    CascadeState state;
3,843✔
293

294
    typedef _impl::TableFriend tf;
3,843✔
295
    for (size_t ndx = 0; ndx < sz; ++ndx) {
10,614✔
296
        ObjKey target_key = m_tree->get(ndx);
6,771✔
297
        Obj target_obj = target_table->get_object(target_key);
6,771✔
298
        target_obj.remove_one_backlink(backlink_col, get_obj().get_key()); // Throws
6,771✔
299
        // embedded objects should only have one incoming link
300
        REALM_ASSERT_EX(target_obj.get_backlink_count() == 0, target_obj.get_backlink_count());
6,771✔
301
        state.m_to_be_deleted.emplace_back(target_table_key, target_key);
6,771✔
302
    }
6,771✔
303

304
    m_tree->clear();
3,843✔
305
    m_tree->set_context_flag(false);
3,843✔
306

307
    tf::remove_recursive(*origin_table, state); // Throws
3,843✔
308
}
3,843✔
309

310
template <>
311
void Lst<ObjLink>::do_set(size_t ndx, ObjLink target_link)
312
{
×
313
    ObjLink old_link = get(ndx);
×
314
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
315
    bool recurse = replace_backlink(m_col_key, old_link, target_link, state);
×
316

317
    m_tree->set(ndx, target_link);
×
318

319
    if (recurse) {
×
320
        auto origin_table = get_table_unchecked();
×
321
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
322
    }
×
323
}
×
324

325
template <>
326
void Lst<ObjLink>::do_insert(size_t ndx, ObjLink target_link)
327
{
×
328
    set_backlink(m_col_key, target_link);
×
329
    m_tree->insert(ndx, target_link);
×
330
}
×
331

332
template <>
333
void Lst<ObjLink>::do_remove(size_t ndx)
334
{
×
335
    ObjLink old_link = get(ndx);
×
336
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
337

338
    bool recurse = remove_backlink(m_col_key, old_link, state);
×
339

340
    m_tree->erase(ndx);
×
341

342
    if (recurse) {
×
343
        auto table = get_table_unchecked();
×
344
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
345
    }
×
346
}
×
347

348
/******************************** Lst<Mixed> *********************************/
349

350
Lst<Mixed>& Lst<Mixed>::operator=(const Lst<Mixed>& other)
351
{
×
352
    if (this != &other) {
×
353
        Base::operator=(other);
×
354
        CollectionParent::operator=(other);
×
355

356
        // Just reset the pointer and rely on init_from_parent() being called
357
        // when the accessor is actually used.
358
        m_tree.reset();
×
359
        Base::reset_content_version();
×
360
    }
×
361

362
    return *this;
×
363
}
×
364

365
Lst<Mixed>& Lst<Mixed>::operator=(Lst<Mixed>&& other) noexcept
366
{
18✔
367
    if (this != &other) {
18✔
368
        Base::operator=(std::move(other));
18✔
369
        CollectionParent::operator=(std::move(other));
18✔
370

371
        m_tree = std::exchange(other.m_tree, nullptr);
18✔
372
        if (m_tree) {
18✔
373
            m_tree->set_parent(this, 0);
×
374
        }
×
375
    }
18✔
376

377
    return *this;
18✔
378
}
18✔
379

380

381
UpdateStatus Lst<Mixed>::init_from_parent(bool allow_create) const
382
{
888,078✔
383
    Base::update_content_version();
888,078✔
384

385
    if (!m_tree) {
888,078✔
386
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
595,455✔
387
        const ArrayParent* parent = this;
595,455✔
388
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
595,455✔
389
    }
595,455✔
390
    try {
888,078✔
391
        return do_init_from_parent(m_tree.get(), Base::get_collection_ref(), allow_create);
888,078✔
392
    }
888,078✔
393
    catch (...) {
888,078✔
394
        m_tree->detach();
36✔
395
        throw;
36✔
396
    }
36✔
397
}
888,078✔
398

399
UpdateStatus Lst<Mixed>::update_if_needed() const
400
{
3,040,296✔
401
    switch (get_update_status()) {
3,040,296✔
402
        case UpdateStatus::Detached:
48✔
403
            m_tree.reset();
48✔
404
            return UpdateStatus::Detached;
48✔
405
        case UpdateStatus::NoChange:
2,318,595✔
406
            if (m_tree && m_tree->is_attached()) {
2,318,724✔
407
                return UpdateStatus::NoChange;
2,315,304✔
408
            }
2,315,304✔
409
            // The tree has not been initialized yet for this accessor, so
410
            // perform lazy initialization by treating it as an update.
411
            [[fallthrough]];
2,318,595✔
412
        case UpdateStatus::Updated:
724,713✔
413
            return init_from_parent(false);
724,713✔
414
    }
3,040,296✔
415
    REALM_UNREACHABLE();
416
}
×
417

418
size_t Lst<Mixed>::find_first(const Mixed& value) const
419
{
1,782✔
420
    if (!update())
1,782✔
421
        return not_found;
×
422

423
    if (value.is_null()) {
1,782✔
424
        auto ndx = m_tree->find_first(value);
72✔
425
        auto size = ndx == not_found ? m_tree->size() : ndx;
72✔
426
        for (size_t i = 0; i < size; ++i) {
108✔
427
            if (m_tree->get(i).is_unresolved_link())
60✔
428
                return i;
24✔
429
        }
60✔
430
        return ndx;
48✔
431
    }
72✔
432
    return m_tree->find_first(value);
1,710✔
433
}
1,782✔
434

435
Mixed Lst<Mixed>::set(size_t ndx, Mixed value)
436
{
1,764✔
437
    // get will check for ndx out of bounds
438
    Mixed old = do_get(ndx, "set()");
1,764✔
439
    if (Replication* repl = Base::get_replication()) {
1,764✔
440
        repl->list_set(*this, ndx, value);
1,686✔
441
    }
1,686✔
442
    if (!value.is_same_type(old) || value != old) {
1,764✔
443
        do_set(ndx, value);
1,482✔
444
        if (value.is_type(type_Dictionary, type_List)) {
1,482✔
445
            m_tree->ensure_keys();
180✔
446
            set_key(*m_tree, ndx);
180✔
447
        }
180✔
448
        bump_content_version();
1,482✔
449
    }
1,482✔
450
    return old;
1,764✔
451
}
1,764✔
452

453
void Lst<Mixed>::insert(size_t ndx, Mixed value)
454
{
380,175✔
455
    ensure_created();
380,175✔
456
    auto sz = size();
380,175✔
457
    CollectionBase::validate_index("insert()", ndx, sz + 1);
380,175✔
458
    if (value.is_type(type_TypedLink)) {
380,175✔
459
        get_table()->get_parent_group()->validate(value.get_link());
150,822✔
460
    }
150,822✔
461
    if (Replication* repl = Base::get_replication()) {
380,175✔
462
        repl->list_insert(*this, ndx, value, sz);
18,246✔
463
    }
18,246✔
464
    do_insert(ndx, value);
380,175✔
465
    if (value.is_type(type_Dictionary, type_List)) {
380,175✔
466
        m_tree->ensure_keys();
50,004✔
467
        set_key(*m_tree, ndx);
50,004✔
468
    }
50,004✔
469
    bump_content_version();
380,175✔
470
}
380,175✔
471

472
void Lst<Mixed>::resize(size_t new_size)
473
{
48✔
474
    size_t current_size = size();
48✔
475
    if (new_size != current_size) {
48✔
476
        while (new_size > current_size) {
48✔
477
            insert_null(current_size++);
×
478
        }
×
479
        remove(new_size, current_size);
48✔
480
        Base::bump_both_versions();
48✔
481
    }
48✔
482
}
48✔
483

484
Mixed Lst<Mixed>::remove(size_t ndx)
485
{
1,482✔
486
    // get will check for ndx out of bounds
487
    Mixed old = do_get(ndx, "remove()");
1,482✔
488
    if (Replication* repl = Base::get_replication()) {
1,482✔
489
        repl->list_erase(*this, ndx);
1,458✔
490
    }
1,458✔
491

492
    do_remove(ndx);
1,482✔
493
    bump_content_version();
1,482✔
494
    return old;
1,482✔
495
}
1,482✔
496

497
void Lst<Mixed>::remove(size_t from, size_t to)
498
{
318✔
499
    while (from < to) {
636✔
500
        remove(--to);
318✔
501
    }
318✔
502
}
318✔
503

504
void Lst<Mixed>::clear()
505
{
984✔
506
    auto sz = size();
984✔
507
    Replication* repl = Base::get_replication();
984✔
508
    if (repl && (sz > 0 || !m_col_key.is_collection() || m_level > 1)) {
984✔
509
        repl->list_clear(*this);
882✔
510
    }
882✔
511
    if (sz > 0) {
984✔
512
        CascadeState state;
678✔
513
        bool recurse = remove_backlinks(state);
678✔
514

515
        m_tree->clear();
678✔
516

517
        if (recurse) {
678✔
518
            auto table = get_table_unchecked();
×
519
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
520
        }
×
521
        bump_content_version();
678✔
522
    }
678✔
523
}
984✔
524

525
void Lst<Mixed>::move(size_t from, size_t to)
526
{
222✔
527
    auto sz = size();
222✔
528
    CollectionBase::validate_index("move()", from, sz);
222✔
529
    CollectionBase::validate_index("move()", to, sz);
222✔
530

531
    if (from != to) {
222✔
532
        if (Replication* repl = Base::get_replication()) {
222✔
533
            repl->list_move(*this, from, to);
216✔
534
        }
216✔
535
        if (to > from) {
222✔
536
            to++;
156✔
537
        }
156✔
538
        else {
66✔
539
            from++;
66✔
540
        }
66✔
541
        // We use swap here as it handles the special case for StringData where
542
        // 'to' and 'from' points into the same array. In this case you cannot
543
        // set an entry with the result of a get from another entry in the same
544
        // leaf.
545
        m_tree->insert(to, Mixed());
222✔
546
        m_tree->swap(from, to);
222✔
547
        m_tree->erase(from);
222✔
548

549
        bump_content_version();
222✔
550
    }
222✔
551
}
222✔
552

553
void Lst<Mixed>::swap(size_t ndx1, size_t ndx2)
554
{
18✔
555
    auto sz = size();
18✔
556
    CollectionBase::validate_index("swap()", ndx1, sz);
18✔
557
    CollectionBase::validate_index("swap()", ndx2, sz);
18✔
558

559
    if (ndx1 != ndx2) {
18✔
560
        if (Replication* repl = Base::get_replication()) {
18✔
561
            LstBase::swap_repl(repl, ndx1, ndx2);
12✔
562
        }
12✔
563
        m_tree->swap(ndx1, ndx2);
18✔
564
        bump_content_version();
18✔
565
    }
18✔
566
}
18✔
567

568
void Lst<Mixed>::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
569
{
49,992✔
570
    if (dict_or_list == CollectionType::Set) {
49,992✔
571
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
572
    }
×
573
    check_level();
49,992✔
574
    insert(path_elem.get_ndx(), Mixed(0, dict_or_list));
49,992✔
575
}
49,992✔
576

577
void Lst<Mixed>::set_collection(const PathElement& path_elem, CollectionType dict_or_list)
578
{
312✔
579
    if (dict_or_list == CollectionType::Set) {
312✔
580
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
581
    }
×
582
    check_level();
312✔
583
    set(path_elem.get_ndx(), Mixed(0, dict_or_list));
312✔
584
}
312✔
585

586
template <class T>
587
inline std::shared_ptr<T> Lst<Mixed>::do_get_collection(const PathElement& path_elem)
588
{
100,932✔
589
    update();
100,932✔
590
    auto get_shared = [&]() -> std::shared_ptr<CollectionParent> {
100,932✔
591
        auto weak = weak_from_this();
100,932✔
592

593
        if (weak.expired()) {
100,932✔
594
            REALM_ASSERT_DEBUG(m_level == 1);
97,860✔
595
            return std::make_shared<Lst<Mixed>>(*this);
97,860✔
596
        }
97,860✔
597

598
        return weak.lock();
3,072✔
599
    };
100,932✔
600

601
    auto shared = get_shared();
100,932✔
602
    auto ret = std::make_shared<T>(m_col_key, get_level() + 1);
100,932✔
603
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
100,932✔
604
    return ret;
100,932✔
605
}
100,932✔
606

607
DictionaryPtr Lst<Mixed>::get_dictionary(const PathElement& path_elem) const
608
{
1,626✔
609
    return const_cast<Lst<Mixed>*>(this)->do_get_collection<Dictionary>(path_elem);
1,626✔
610
}
1,626✔
611

612
std::shared_ptr<Lst<Mixed>> Lst<Mixed>::get_list(const PathElement& path_elem) const
613
{
99,306✔
614
    return const_cast<Lst<Mixed>*>(this)->do_get_collection<Lst<Mixed>>(path_elem);
99,306✔
615
}
99,306✔
616

617
void Lst<Mixed>::do_set(size_t ndx, Mixed value)
618
{
1,482✔
619
    ObjLink old_link;
1,482✔
620
    ObjLink target_link;
1,482✔
621
    Mixed old_value = m_tree->get(ndx);
1,482✔
622

623
    if (old_value.is_type(type_TypedLink)) {
1,482✔
624
        old_link = old_value.get<ObjLink>();
438✔
625
    }
438✔
626
    if (value.is_type(type_TypedLink)) {
1,482✔
627
        target_link = value.get<ObjLink>();
432✔
628
        get_table_unchecked()->get_parent_group()->validate(target_link);
432✔
629
    }
432✔
630

631
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
1,482✔
632
    bool recurse = Base::replace_backlink(m_col_key, old_link, target_link, state);
1,482✔
633

634
    m_tree->set(ndx, value);
1,482✔
635

636
    if (recurse) {
1,482✔
637
        auto origin_table = get_table_unchecked();
×
638
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
639
    }
×
640
}
1,482✔
641

642
void Lst<Mixed>::do_insert(size_t ndx, Mixed value)
643
{
380,145✔
644
    if (value.is_type(type_TypedLink)) {
380,145✔
645
        Base::set_backlink(m_col_key, value.get<ObjLink>());
150,789✔
646
    }
150,789✔
647

648
    m_tree->insert(ndx, value);
380,145✔
649
}
380,145✔
650

651
void Lst<Mixed>::do_remove(size_t ndx)
652
{
1,482✔
653
    CascadeState state;
1,482✔
654
    bool recurse = clear_backlink(ndx, state);
1,482✔
655

656
    m_tree->erase(ndx);
1,482✔
657

658
    if (recurse) {
1,482✔
659
        auto table = get_table_unchecked();
×
660
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
661
    }
×
662
}
1,482✔
663

664
void Lst<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const
665
{
234✔
666
    update();
234✔
667

668
    auto tree = m_tree.get();
234✔
669
    if (ascending) {
234✔
670
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
13,683✔
671
            return unresolved_to_null(tree->get(i1)) < unresolved_to_null(tree->get(i2));
13,683✔
672
        });
13,683✔
673
    }
210✔
674
    else {
24✔
675
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
2,451✔
676
            return unresolved_to_null(tree->get(i1)) > unresolved_to_null(tree->get(i2));
2,451✔
677
        });
2,451✔
678
    }
24✔
679
}
234✔
680

681
void Lst<Mixed>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
682
{
30✔
683
    indices.clear();
30✔
684
    sort(indices, sort_order.value_or(true));
30✔
685
    if (indices.empty()) {
30✔
686
        return;
×
687
    }
×
688

689
    auto tree = m_tree.get();
30✔
690
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
666✔
691
        return unresolved_to_null(tree->get(i1)) == unresolved_to_null(tree->get(i2));
666✔
692
    });
666✔
693

694
    // Erase the duplicates
695
    indices.erase(duplicates, indices.end());
30✔
696

697
    if (!sort_order) {
30✔
698
        // Restore original order
699
        std::sort(indices.begin(), indices.end());
30✔
700
    }
30✔
701
}
30✔
702

703
util::Optional<Mixed> Lst<Mixed>::min(size_t* return_ndx) const
704
{
96✔
705
    if (update()) {
96✔
706
        return MinHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
707
    }
84✔
708
    return MinHelper<Mixed>::not_found(return_ndx);
12✔
709
}
96✔
710

711
util::Optional<Mixed> Lst<Mixed>::max(size_t* return_ndx) const
712
{
96✔
713
    if (update()) {
96✔
714
        return MaxHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
715
    }
84✔
716
    return MaxHelper<Mixed>::not_found(return_ndx);
12✔
717
}
96✔
718

719
util::Optional<Mixed> Lst<Mixed>::sum(size_t* return_cnt) const
720
{
96✔
721
    if (update()) {
96✔
722
        return SumHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
723
    }
84✔
724
    return SumHelper<Mixed>::not_found(return_cnt);
12✔
725
}
96✔
726

727
util::Optional<Mixed> Lst<Mixed>::avg(size_t* return_cnt) const
728
{
96✔
729
    if (update()) {
96✔
730
        return AverageHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
731
    }
84✔
732
    return AverageHelper<Mixed>::not_found(return_cnt);
12✔
733
}
96✔
734

735
void Lst<Mixed>::to_json(std::ostream& out, JSONOutputMode output_mode,
736
                         util::FunctionRef<void(const Mixed&)> fn) const
737
{
54✔
738
    out << "[";
54✔
739

740
    auto sz = size();
54✔
741
    for (size_t i = 0; i < sz; i++) {
180✔
742
        if (i > 0)
126✔
743
            out << ",";
72✔
744
        Mixed val = m_tree->get(i);
126✔
745
        if (val.is_type(type_TypedLink)) {
126✔
746
            fn(val);
36✔
747
        }
36✔
748
        else if (val.is_type(type_Dictionary)) {
90✔
749
            DummyParent parent(this->get_table(), val.get_ref());
18✔
750
            Dictionary dict(parent, i);
18✔
751
            dict.to_json(out, output_mode, fn);
18✔
752
        }
18✔
753
        else if (val.is_type(type_List)) {
72✔
754
            DummyParent parent(this->get_table(), val.get_ref());
12✔
755
            Lst<Mixed> list(parent, i);
12✔
756
            list.to_json(out, output_mode, fn);
12✔
757
        }
12✔
758
        else {
60✔
759
            val.to_json(out, output_mode);
60✔
760
        }
60✔
761
    }
126✔
762

763
    out << "]";
54✔
764
}
54✔
765

766
ref_type Lst<Mixed>::get_collection_ref(Index index, CollectionType type) const
767
{
200,430✔
768
    auto ndx = m_tree->find_key(index.get_salt());
200,430✔
769
    if (ndx != realm::not_found) {
200,430✔
770
        auto val = get(ndx);
200,430✔
771
        if (val.is_type(DataType(int(type)))) {
200,430✔
772
            return val.get_ref();
200,430✔
773
        }
200,430✔
UNCOV
774
        throw realm::IllegalOperation(util::format("Not a %1", type));
×
775
    }
200,430✔
UNCOV
776
    throw StaleAccessor("This collection is no more");
×
777
    return 0;
×
778
}
200,430✔
779

780
bool Lst<Mixed>::check_collection_ref(Index index, CollectionType type) const noexcept
781
{
3,504✔
782
    auto ndx = m_tree->find_key(index.get_salt());
3,504✔
783
    if (ndx != realm::not_found) {
3,504✔
784
        return get(ndx).is_type(DataType(int(type)));
3,450✔
785
    }
3,450✔
786
    return false;
54✔
787
}
3,504✔
788

789
void Lst<Mixed>::set_collection_ref(Index index, ref_type ref, CollectionType type)
790
{
50,856✔
791
    auto ndx = m_tree->find_key(index.get_salt());
50,856✔
792
    if (ndx == realm::not_found) {
50,856✔
793
        throw StaleAccessor("Collection has been deleted");
×
794
    }
×
795
    m_tree->set(ndx, Mixed(ref, type));
50,856✔
796
}
50,856✔
797

798
void Lst<Mixed>::add_index(Path& path, const Index& index) const
799
{
2,256✔
800
    auto ndx = m_tree->find_key(index.get_salt());
2,256✔
801
    REALM_ASSERT(ndx != realm::not_found);
2,256✔
802
    path.emplace_back(ndx);
2,256✔
803
}
2,256✔
804

805
size_t Lst<Mixed>::find_index(const Index& index) const
806
{
468✔
807
    update();
468✔
808
    return m_tree->find_key(index.get_salt());
468✔
809
}
468✔
810

811
bool Lst<Mixed>::nullify(ObjLink link)
812
{
480✔
813
    size_t ndx = find_first(link);
480✔
814
    if (ndx != realm::not_found) {
480✔
815
        if (Replication* repl = Base::get_replication()) {
474✔
816
            repl->list_erase(*this, ndx); // Throws
468✔
817
        }
468✔
818

819
        m_tree->erase(ndx);
474✔
820
        return true;
474✔
821
    }
474✔
822
    else {
6✔
823
        // There must be a link in a nested collection
824
        size_t sz = size();
6✔
825
        for (size_t ndx = 0; ndx < sz; ndx++) {
12✔
826
            Mixed val = m_tree->get(ndx);
12✔
827
            if (val.is_type(type_Dictionary)) {
12✔
828
                auto dict = get_dictionary(ndx);
12✔
829
                if (dict->nullify(link)) {
12✔
830
                    return true;
6✔
831
                }
6✔
832
            }
12✔
833
            if (val.is_type(type_List)) {
6✔
834
                auto list = get_list(ndx);
×
835
                if (list->nullify(link)) {
×
836
                    return true;
×
837
                }
×
838
            }
×
839
        }
6✔
840
    }
6✔
841
    return false;
×
842
}
480✔
843

844
bool Lst<Mixed>::replace_link(ObjLink old_link, ObjLink replace_link)
845
{
138✔
846
    size_t ndx = find_first(old_link);
138✔
847
    if (ndx != realm::not_found) {
138✔
848
        set(ndx, replace_link);
138✔
849
        return true;
138✔
850
    }
138✔
851
    else {
×
852
        // There must be a link in a nested collection
853
        size_t sz = size();
×
854
        for (size_t ndx = 0; ndx < sz; ndx++) {
×
855
            Mixed val = m_tree->get(ndx);
×
856
            if (val.is_type(type_Dictionary)) {
×
857
                auto dict = get_dictionary(ndx);
×
858
                if (dict->replace_link(old_link, replace_link)) {
×
859
                    return true;
×
860
                }
×
861
            }
×
862
            if (val.is_type(type_List)) {
×
863
                auto list = get_list(ndx);
×
864
                if (list->replace_link(old_link, replace_link)) {
×
865
                    return true;
×
866
                }
×
867
            }
×
868
        }
×
869
    }
×
870
    return false;
×
871
}
138✔
872

873
bool Lst<Mixed>::clear_backlink(size_t ndx, CascadeState& state) const
874
{
365,481✔
875
    Mixed value = m_tree->get(ndx);
365,481✔
876
    if (value.is_type(type_TypedLink, type_Dictionary, type_List)) {
365,481✔
877
        if (value.is_type(type_TypedLink)) {
194,298✔
878
            auto link = value.get<ObjLink>();
146,025✔
879
            return Base::remove_backlink(m_col_key, link, state);
146,025✔
880
        }
146,025✔
881
        else if (value.is_type(type_List)) {
48,273✔
882
            Lst<Mixed> list{*const_cast<Lst<Mixed>*>(this), m_tree->get_key(ndx)};
48,192✔
883
            return list.remove_backlinks(state);
48,192✔
884
        }
48,192✔
885
        else if (value.is_type(type_Dictionary)) {
81✔
886
            Dictionary dict{*const_cast<Lst<Mixed>*>(this), m_tree->get_key(ndx)};
78✔
887
            return dict.remove_backlinks(state);
78✔
888
        }
78✔
889
    }
194,298✔
890
    return false;
171,186✔
891
}
365,481✔
892

893
bool Lst<Mixed>::remove_backlinks(CascadeState& state) const
894
{
157,758✔
895
    size_t sz = size();
157,758✔
896
    bool recurse = false;
157,758✔
897
    for (size_t ndx = 0; ndx < sz; ndx++) {
521,757✔
898
        if (clear_backlink(ndx, state)) {
363,999✔
899
            recurse = true;
24,000✔
900
        }
24,000✔
901
    }
363,999✔
902
    return recurse;
157,758✔
903
}
157,758✔
904

905
/********************************** LnkLst ***********************************/
906

907
Obj LnkLst::create_and_insert_linked_object(size_t ndx)
908
{
28,665✔
909
    Table& t = *get_target_table();
28,665✔
910
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
28,665✔
911
    m_list.insert(ndx, o.get_key());
28,665✔
912
    return o;
28,665✔
913
}
28,665✔
914

915
Obj LnkLst::create_and_set_linked_object(size_t ndx)
916
{
78✔
917
    Table& t = *get_target_table();
78✔
918
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
78✔
919
    m_list.set(ndx, o.get_key());
78✔
920
    return o;
78✔
921
}
78✔
922

923
TableView LnkLst::get_sorted_view(SortDescriptor order) const
924
{
90✔
925
    TableView tv(clone_linklist());
90✔
926
    tv.do_sync();
90✔
927
    tv.sort(std::move(order));
90✔
928
    return tv;
90✔
929
}
90✔
930

931
TableView LnkLst::get_sorted_view(ColKey column_key, bool ascending) const
932
{
72✔
933
    TableView v = get_sorted_view(SortDescriptor({{column_key}}, {ascending}));
72✔
934
    return v;
72✔
935
}
72✔
936

937
void LnkLst::remove_target_row(size_t link_ndx)
938
{
30✔
939
    // Deleting the object will automatically remove all links
940
    // to it. So we do not have to manually remove the deleted link
941
    ObjKey k = get(link_ndx);
30✔
942
    get_target_table()->remove_object(k);
30✔
943
}
30✔
944

945
void LnkLst::remove_all_target_rows()
946
{
48✔
947
    if (is_attached()) {
48✔
948
        update_if_needed();
48✔
949
        _impl::TableFriend::batch_erase_rows(*get_target_table(), *m_list.m_tree);
48✔
950
    }
48✔
951
}
48✔
952

953
void LnkLst::to_json(std::ostream& out, JSONOutputMode mode, util::FunctionRef<void(const Mixed&)> fn) const
954
{
96✔
955
    m_list.to_json(out, mode, fn);
96✔
956
}
96✔
957

958
void LnkLst::replace_link(ObjKey old_val, ObjKey new_val)
959
{
27,414✔
960
    update_if_needed();
27,414✔
961
    auto tree = m_list.m_tree.get();
27,414✔
962
    auto n = tree->find_first(old_val);
27,414✔
963
    REALM_ASSERT(n != realm::npos);
27,414✔
964
    if (Replication* repl = get_obj().get_replication()) {
27,414✔
965
        repl->list_set(m_list, n, new_val);
27,342✔
966
    }
27,342✔
967
    tree->set(n, new_val);
27,414✔
968
    m_list.bump_content_version();
27,414✔
969
    if (new_val.is_unresolved()) {
27,414✔
970
        if (!old_val.is_unresolved()) {
366✔
971
            tree->set_context_flag(true);
366✔
972
        }
366✔
973
    }
366✔
974
    else {
27,048✔
975
        _impl::check_for_last_unresolved(tree);
27,048✔
976
    }
27,048✔
977
}
27,414✔
978

979
// Force instantiation:
980
template class Lst<ObjKey>;
981
template class Lst<ObjLink>;
982
template class Lst<int64_t>;
983
template class Lst<bool>;
984
template class Lst<StringData>;
985
template class Lst<BinaryData>;
986
template class Lst<Timestamp>;
987
template class Lst<float>;
988
template class Lst<double>;
989
template class Lst<Decimal128>;
990
template class Lst<ObjectId>;
991
template class Lst<UUID>;
992
template class Lst<util::Optional<int64_t>>;
993
template class Lst<util::Optional<bool>>;
994
template class Lst<util::Optional<float>>;
995
template class Lst<util::Optional<double>>;
996
template class Lst<util::Optional<ObjectId>>;
997
template class Lst<util::Optional<UUID>>;
998

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