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

realm / realm-core / thomas.goyne_232

13 Mar 2024 01:00AM UTC coverage: 91.787% (+0.9%) from 90.924%
thomas.goyne_232

Pull #7402

Evergreen

tgoyne
Add more UpdateIfNeeded tests
Pull Request #7402: Make Obj trivial and add a separate ObjCollectionParent type

94460 of 174600 branches covered (54.1%)

496 of 559 new or added lines in 21 files covered. (88.73%)

848 existing lines in 34 files now uncovered.

242761 of 264484 relevant lines covered (91.79%)

6342666.36 hits per line

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

89.17
/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
54✔
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
14,427✔
57
        indices.push_back(i);
28,854✔
58
    }
28,854✔
59

2,787✔
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

2,670✔
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,162✔
74
            return tree->get(i1) < tree->get(i2);
57,162✔
75
        });
57,162✔
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

429✔
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

414✔
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

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

414✔
121
    if (!sort_order) {
828!
122
        // Restore original order
294✔
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
{
540✔
133
    auto sz = size();
540✔
134
    out << "[";
540✔
135
    for (size_t i = 0; i < sz; i++) {
1,620✔
136
        if (i > 0)
1,080✔
137
            out << ",";
648✔
138
        Mixed val = get_any(i);
1,080✔
139
        if (val.is_type(type_TypedLink)) {
1,080✔
140
            fn(val);
×
141
        }
×
142
        else {
1,080✔
143
            val.to_json(out, output_mode);
1,080✔
144
        }
1,080✔
145
    }
1,080✔
146
    out << "]";
540✔
147
}
540✔
148

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

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

161
template <>
162
void Lst<StringData>::do_set(size_t ndx, StringData value)
163
{
1,791✔
164
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
1,791✔
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

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

184
template <>
185
inline void Lst<StringData>::do_remove(size_t ndx)
186
{
13,839✔
187
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
13,839✔
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

3✔
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,839✔
202
}
13,839✔
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

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

20,739✔
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
6✔
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,428,265✔
242
    auto origin_table = get_table_unchecked();
6,428,265✔
243
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
6,428,265✔
244
    set_backlink(m_col_key, {target_table_key, target_key});
6,428,265✔
245
    m_tree->insert(ndx, target_key);
6,428,265✔
246
    if (target_key.is_unresolved()) {
6,428,265✔
247
        m_tree->set_context_flag(true);
27,084✔
248
    }
27,084✔
249
}
6,428,265✔
250

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

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

1,275✔
261
    m_tree->erase(ndx);
2,550✔
262

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

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

1,578✔
278
    size_t sz = size();
3,156✔
279
    if (!target_table->is_embedded()) {
3,156✔
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

1,380✔
289
    TableKey target_table_key = target_table->get_key();
2,760✔
290
    ColKey backlink_col = origin_table->get_opposite_column(m_col_key);
2,760✔
291

1,380✔
292
    CascadeState state;
2,760✔
293

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

1,380✔
304
    m_tree->clear();
2,760✔
305
    m_tree->set_context_flag(false);
2,760✔
306

1,380✔
307
    tf::remove_recursive(*origin_table, state); // Throws
2,760✔
308
}
2,760✔
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
UpdateStatus Lst<Mixed>::init_from_parent(bool allow_create) const
351
{
39,123✔
352
    Base::update_content_version();
39,123✔
353

19,545✔
354
    if (!m_tree) {
39,123✔
355
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
25,611✔
356
        const ArrayParent* parent = this;
25,611✔
357
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
25,611✔
358
    }
25,611✔
359
    try {
39,123✔
360
        return do_init_from_parent(m_tree.get(), Base::get_collection_ref(), allow_create);
39,123✔
361
    }
39,123✔
362
    catch (...) {
36✔
363
        m_tree->detach();
36✔
364
        throw;
36✔
365
    }
36✔
366
}
39,123✔
367

368
UpdateStatus Lst<Mixed>::update_if_needed() const
369
{
141,597✔
370
    switch (get_update_status()) {
141,597✔
371
        case UpdateStatus::Detached:
48✔
372
            m_tree.reset();
48✔
373
            return UpdateStatus::Detached;
48✔
374
        case UpdateStatus::NoChange:
110,079✔
375
            if (m_tree && m_tree->is_attached()) {
110,079✔
376
                return UpdateStatus::NoChange;
108,948✔
377
            }
108,948✔
378
            // The tree has not been initialized yet for this accessor, so
558✔
379
            // perform lazy initialization by treating it as an update.
558✔
380
            [[fallthrough]];
1,131✔
381
        case UpdateStatus::Updated:
32,601✔
382
            return init_from_parent(false);
32,601✔
UNCOV
383
    }
×
384
    REALM_UNREACHABLE();
385
}
×
386

387
size_t Lst<Mixed>::find_first(const Mixed& value) const
388
{
1,758✔
389
    if (!update())
1,758✔
390
        return not_found;
×
391

879✔
392
    if (value.is_null()) {
1,758✔
393
        auto ndx = m_tree->find_first(value);
72✔
394
        auto size = ndx == not_found ? m_tree->size() : ndx;
63✔
395
        for (size_t i = 0; i < size; ++i) {
108✔
396
            if (m_tree->get(i).is_unresolved_link())
60✔
397
                return i;
24✔
398
        }
60✔
399
        return ndx;
60✔
400
    }
1,686✔
401
    return m_tree->find_first(value);
1,686✔
402
}
1,686✔
403

404
Mixed Lst<Mixed>::set(size_t ndx, Mixed value)
405
{
1,452✔
406
    // get will check for ndx out of bounds
726✔
407
    Mixed old = do_get(ndx, "set()");
1,452✔
408
    if (Replication* repl = Base::get_replication()) {
1,452✔
409
        repl->list_set(*this, ndx, value);
1,392✔
410
    }
1,392✔
411
    if (!(old.is_same_type(value) && old == value)) {
1,452✔
412
        do_set(ndx, value);
1,194✔
413
        bump_content_version();
1,194✔
414
    }
1,194✔
415
    return old;
1,452✔
416
}
1,452✔
417

418
void Lst<Mixed>::insert(size_t ndx, Mixed value)
419
{
17,430✔
420
    ensure_created();
17,430✔
421
    auto sz = size();
17,430✔
422
    CollectionBase::validate_index("insert()", ndx, sz + 1);
17,430✔
423
    if (value.is_type(type_TypedLink)) {
17,430✔
424
        get_table()->get_parent_group()->validate(value.get_link());
6,714✔
425
    }
6,714✔
426
    if (Replication* repl = Base::get_replication()) {
17,430✔
427
        repl->list_insert(*this, ndx, value, sz);
15,564✔
428
    }
15,564✔
429
    do_insert(ndx, value);
17,430✔
430
    bump_content_version();
17,430✔
431
}
17,430✔
432

433
void Lst<Mixed>::resize(size_t new_size)
434
{
48✔
435
    size_t current_size = size();
48✔
436
    if (new_size != current_size) {
48✔
437
        while (new_size > current_size) {
48✔
438
            insert_null(current_size++);
×
439
        }
×
440
        remove(new_size, current_size);
48✔
441
        Base::bump_both_versions();
48✔
442
    }
48✔
443
}
48✔
444

445
Mixed Lst<Mixed>::remove(size_t ndx)
446
{
1,374✔
447
    // get will check for ndx out of bounds
687✔
448
    Mixed old = do_get(ndx, "remove()");
1,374✔
449
    if (Replication* repl = Base::get_replication()) {
1,374✔
450
        repl->list_erase(*this, ndx);
1,350✔
451
    }
1,350✔
452

687✔
453
    do_remove(ndx);
1,374✔
454
    bump_content_version();
1,374✔
455
    return old;
1,374✔
456
}
1,374✔
457

458
void Lst<Mixed>::remove(size_t from, size_t to)
459
{
540✔
460
    while (from < to) {
1,296✔
461
        remove(--to);
756✔
462
    }
756✔
463
}
540✔
464

465
void Lst<Mixed>::clear()
466
{
486✔
467
    if (size() > 0) {
486✔
468
        if (Replication* repl = Base::get_replication()) {
402✔
469
            repl->list_clear(*this);
390✔
470
        }
390✔
471
        CascadeState state;
402✔
472
        bool recurse = remove_backlinks(state);
402✔
473

201✔
474
        m_tree->clear();
402✔
475

201✔
476
        if (recurse) {
402✔
477
            auto table = get_table_unchecked();
×
478
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
479
        }
×
480
        bump_content_version();
402✔
481
    }
402✔
482
}
486✔
483

484
void Lst<Mixed>::move(size_t from, size_t to)
485
{
222✔
486
    auto sz = size();
222✔
487
    CollectionBase::validate_index("move()", from, sz);
222✔
488
    CollectionBase::validate_index("move()", to, sz);
222✔
489

111✔
490
    if (from != to) {
222✔
491
        if (Replication* repl = Base::get_replication()) {
222✔
492
            repl->list_move(*this, from, to);
216✔
493
        }
216✔
494
        if (to > from) {
222✔
495
            to++;
156✔
496
        }
156✔
497
        else {
66✔
498
            from++;
66✔
499
        }
66✔
500
        // We use swap here as it handles the special case for StringData where
111✔
501
        // 'to' and 'from' points into the same array. In this case you cannot
111✔
502
        // set an entry with the result of a get from another entry in the same
111✔
503
        // leaf.
111✔
504
        m_tree->insert(to, Mixed());
222✔
505
        m_tree->swap(from, to);
222✔
506
        m_tree->erase(from);
222✔
507

111✔
508
        bump_content_version();
222✔
509
    }
222✔
510
}
222✔
511

512
void Lst<Mixed>::swap(size_t ndx1, size_t ndx2)
513
{
18✔
514
    auto sz = size();
18✔
515
    CollectionBase::validate_index("swap()", ndx1, sz);
18✔
516
    CollectionBase::validate_index("swap()", ndx2, sz);
18✔
517

9✔
518
    if (ndx1 != ndx2) {
18✔
519
        if (Replication* repl = Base::get_replication()) {
18✔
520
            LstBase::swap_repl(repl, ndx1, ndx2);
12✔
521
        }
12✔
522
        m_tree->swap(ndx1, ndx2);
18✔
523
        bump_content_version();
18✔
524
    }
18✔
525
}
18✔
526

527
void Lst<Mixed>::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
528
{
1,524✔
529
    if (dict_or_list == CollectionType::Set) {
1,524✔
530
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
531
    }
×
532

762✔
533
    ensure_created();
1,524✔
534
    check_level();
1,524✔
535
    m_tree->ensure_keys();
1,524✔
536
    insert(path_elem.get_ndx(), Mixed(0, dict_or_list));
1,524✔
537
    set_key(*m_tree, path_elem.get_ndx());
1,524✔
538
    bump_content_version();
1,524✔
539
}
1,524✔
540

541
void Lst<Mixed>::set_collection(const PathElement& path_elem, CollectionType dict_or_list)
542
{
204✔
543
    if (dict_or_list == CollectionType::Set) {
204✔
544
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
545
    }
×
546

102✔
547
    auto ndx = path_elem.get_ndx();
204✔
548
    // get will check for ndx out of bounds
102✔
549
    Mixed old_val = do_get(ndx, "set_collection()");
204✔
550
    Mixed new_val(0, dict_or_list);
204✔
551

102✔
552
    check_level();
204✔
553

102✔
554
    if (old_val != new_val) {
204✔
555
        m_tree->ensure_keys();
162✔
556
        set(ndx, new_val);
162✔
557
        int64_t key = m_tree->get_key(ndx);
162✔
558
        if (key == 0) {
162✔
559
            set_key(*m_tree, path_elem.get_ndx());
162✔
560
        }
162✔
561
        bump_content_version();
162✔
562
    }
162✔
563
}
204✔
564

565
DictionaryPtr Lst<Mixed>::get_dictionary(const PathElement& path_elem) const
566
{
1,398✔
567
    update();
1,398✔
568
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
1,398✔
569
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
1,140✔
570
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
1,398✔
571
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
1,398✔
572
    return ret;
1,398✔
573
}
1,398✔
574

575
std::shared_ptr<Lst<Mixed>> Lst<Mixed>::get_list(const PathElement& path_elem) const
576
{
2,352✔
577
    update();
2,352✔
578
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
2,352✔
579
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
1,842✔
580
    std::shared_ptr<Lst<Mixed>> ret = std::make_shared<Lst<Mixed>>(m_col_key, get_level() + 1);
2,352✔
581
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
2,352✔
582
    return ret;
2,352✔
583
}
2,352✔
584

585
void Lst<Mixed>::do_set(size_t ndx, Mixed value)
586
{
1,194✔
587
    ObjLink old_link;
1,194✔
588
    ObjLink target_link;
1,194✔
589
    Mixed old_value = m_tree->get(ndx);
1,194✔
590

597✔
591
    if (old_value.is_type(type_TypedLink)) {
1,194✔
592
        old_link = old_value.get<ObjLink>();
426✔
593
    }
426✔
594
    if (value.is_type(type_TypedLink)) {
1,194✔
595
        target_link = value.get<ObjLink>();
420✔
596
        get_table_unchecked()->get_parent_group()->validate(target_link);
420✔
597
    }
420✔
598

597✔
599
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
1,173✔
600
    bool recurse = Base::replace_backlink(m_col_key, old_link, target_link, state);
1,194✔
601

597✔
602
    m_tree->set(ndx, value);
1,194✔
603

597✔
604
    if (recurse) {
1,194✔
605
        auto origin_table = get_table_unchecked();
×
606
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
607
    }
×
608
}
1,194✔
609

610
void Lst<Mixed>::do_insert(size_t ndx, Mixed value)
611
{
17,394✔
612
    if (value.is_type(type_TypedLink)) {
17,394✔
613
        Base::set_backlink(m_col_key, value.get<ObjLink>());
6,690✔
614
    }
6,690✔
615

8,697✔
616
    m_tree->insert(ndx, value);
17,394✔
617
}
17,394✔
618

619
void Lst<Mixed>::do_remove(size_t ndx)
620
{
1,374✔
621
    CascadeState state;
1,374✔
622
    bool recurse = clear_backlink(ndx, state);
1,374✔
623

687✔
624
    m_tree->erase(ndx);
1,374✔
625

687✔
626
    if (recurse) {
1,374✔
627
        auto table = get_table_unchecked();
×
628
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
629
    }
×
630
}
1,374✔
631

632
void Lst<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const
633
{
234✔
634
    update();
234✔
635

117✔
636
    auto tree = m_tree.get();
234✔
637
    if (ascending) {
234✔
638
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
13,683✔
639
            return unresolved_to_null(tree->get(i1)) < unresolved_to_null(tree->get(i2));
13,683✔
640
        });
13,683✔
641
    }
210✔
642
    else {
24✔
643
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
2,451✔
644
            return unresolved_to_null(tree->get(i1)) > unresolved_to_null(tree->get(i2));
2,451✔
645
        });
2,451✔
646
    }
24✔
647
}
234✔
648

649
void Lst<Mixed>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
650
{
30✔
651
    indices.clear();
30✔
652
    sort(indices, sort_order.value_or(true));
30✔
653
    if (indices.empty()) {
30✔
654
        return;
×
655
    }
×
656

15✔
657
    auto tree = m_tree.get();
30✔
658
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
666✔
659
        return unresolved_to_null(tree->get(i1)) == unresolved_to_null(tree->get(i2));
666✔
660
    });
666✔
661

15✔
662
    // Erase the duplicates
15✔
663
    indices.erase(duplicates, indices.end());
30✔
664

15✔
665
    if (!sort_order) {
30✔
666
        // Restore original order
15✔
667
        std::sort(indices.begin(), indices.end());
30✔
668
    }
30✔
669
}
30✔
670

671
util::Optional<Mixed> Lst<Mixed>::min(size_t* return_ndx) const
672
{
96✔
673
    if (update()) {
96✔
674
        return MinHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
675
    }
84✔
676
    return MinHelper<Mixed>::not_found(return_ndx);
12✔
677
}
12✔
678

679
util::Optional<Mixed> Lst<Mixed>::max(size_t* return_ndx) const
680
{
96✔
681
    if (update()) {
96✔
682
        return MaxHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
683
    }
84✔
684
    return MaxHelper<Mixed>::not_found(return_ndx);
12✔
685
}
12✔
686

687
util::Optional<Mixed> Lst<Mixed>::sum(size_t* return_cnt) const
688
{
96✔
689
    if (update()) {
96✔
690
        return SumHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
691
    }
84✔
692
    return SumHelper<Mixed>::not_found(return_cnt);
12✔
693
}
12✔
694

695
util::Optional<Mixed> Lst<Mixed>::avg(size_t* return_cnt) const
696
{
96✔
697
    if (update()) {
96✔
698
        return AverageHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
699
    }
84✔
700
    return AverageHelper<Mixed>::not_found(return_cnt);
12✔
701
}
12✔
702

703
void Lst<Mixed>::to_json(std::ostream& out, JSONOutputMode output_mode,
704
                         util::FunctionRef<void(const Mixed&)> fn) const
705
{
54✔
706
    out << "[";
54✔
707

27✔
708
    auto sz = size();
54✔
709
    for (size_t i = 0; i < sz; i++) {
180✔
710
        if (i > 0)
126✔
711
            out << ",";
72✔
712
        Mixed val = m_tree->get(i);
126✔
713
        if (val.is_type(type_TypedLink)) {
126✔
714
            fn(val);
36✔
715
        }
36✔
716
        else if (val.is_type(type_Dictionary)) {
90✔
717
            DummyParent parent(this->get_table(), val.get_ref());
18✔
718
            Dictionary dict(parent, i);
18✔
719
            dict.to_json(out, output_mode, fn);
18✔
720
        }
18✔
721
        else if (val.is_type(type_List)) {
72✔
722
            DummyParent parent(this->get_table(), val.get_ref());
12✔
723
            Lst<Mixed> list(parent, i);
12✔
724
            list.to_json(out, output_mode, fn);
12✔
725
        }
12✔
726
        else {
60✔
727
            val.to_json(out, output_mode);
60✔
728
        }
60✔
729
    }
126✔
730

27✔
731
    out << "]";
54✔
732
}
54✔
733

734
ref_type Lst<Mixed>::get_collection_ref(Index index, CollectionType type) const
735
{
7,932✔
736
    auto ndx = m_tree->find_key(index.get_salt());
7,932✔
737
    if (ndx != realm::not_found) {
7,932✔
738
        auto val = get(ndx);
7,932✔
739
        if (val.is_type(DataType(int(type)))) {
7,932✔
740
            return val.get_ref();
7,932✔
741
        }
7,932✔
742
        throw realm::IllegalOperation(util::format("Not a %1", type));
×
743
    }
×
744
    throw StaleAccessor("This collection is no more");
×
745
    return 0;
×
746
}
×
747

748
bool Lst<Mixed>::check_collection_ref(Index index, CollectionType type) const noexcept
749
{
2,262✔
750
    auto ndx = m_tree->find_key(index.get_salt());
2,262✔
751
    if (ndx != realm::not_found) {
2,262✔
752
        return get(ndx).is_type(DataType(int(type)));
2,232✔
753
    }
2,232✔
754
    return false;
30✔
755
}
30✔
756

757
void Lst<Mixed>::set_collection_ref(Index index, ref_type ref, CollectionType type)
758
{
2,160✔
759
    auto ndx = m_tree->find_key(index.get_salt());
2,160✔
760
    if (ndx == realm::not_found) {
2,160✔
761
        throw StaleAccessor("Collection has been deleted");
×
762
    }
×
763
    m_tree->set(ndx, Mixed(ref, type));
2,160✔
764
}
2,160✔
765

766
void Lst<Mixed>::add_index(Path& path, const Index& index) const
767
{
1,362✔
768
    auto ndx = m_tree->find_key(index.get_salt());
1,362✔
769
    REALM_ASSERT(ndx != realm::not_found);
1,362✔
770
    path.emplace_back(ndx);
1,362✔
771
}
1,362✔
772

773
size_t Lst<Mixed>::find_index(const Index& index) const
774
{
450✔
775
    update();
450✔
776
    return m_tree->find_key(index.get_salt());
450✔
777
}
450✔
778

779
bool Lst<Mixed>::nullify(ObjLink link)
780
{
480✔
781
    size_t ndx = find_first(link);
480✔
782
    if (ndx != realm::not_found) {
480✔
783
        if (Replication* repl = Base::get_replication()) {
474✔
784
            repl->list_erase(*this, ndx); // Throws
468✔
785
        }
468✔
786

237✔
787
        m_tree->erase(ndx);
474✔
788
        return true;
474✔
789
    }
474✔
790
    else {
6✔
791
        // There must be a link in a nested collection
3✔
792
        size_t sz = size();
6✔
793
        for (size_t ndx = 0; ndx < sz; ndx++) {
12✔
794
            Mixed val = m_tree->get(ndx);
12✔
795
            if (val.is_type(type_Dictionary)) {
12✔
796
                auto dict = get_dictionary(ndx);
12✔
797
                if (dict->nullify(link)) {
12✔
798
                    return true;
6✔
799
                }
6✔
800
            }
6✔
801
            if (val.is_type(type_List)) {
6✔
802
                auto list = get_list(ndx);
×
803
                if (list->nullify(link)) {
×
804
                    return true;
×
805
                }
×
806
            }
×
807
        }
6✔
808
    }
6✔
809
    return false;
240✔
810
}
480✔
811

812
bool Lst<Mixed>::replace_link(ObjLink old_link, ObjLink replace_link)
813
{
126✔
814
    size_t ndx = find_first(old_link);
126✔
815
    if (ndx != realm::not_found) {
126✔
816
        set(ndx, replace_link);
126✔
817
        return true;
126✔
818
    }
126✔
819
    else {
×
820
        // There must be a link in a nested collection
821
        size_t sz = size();
×
822
        for (size_t ndx = 0; ndx < sz; ndx++) {
×
823
            Mixed val = m_tree->get(ndx);
×
824
            if (val.is_type(type_Dictionary)) {
×
825
                auto dict = get_dictionary(ndx);
×
826
                if (dict->replace_link(old_link, replace_link)) {
×
827
                    return true;
×
828
                }
×
829
            }
×
830
            if (val.is_type(type_List)) {
×
831
                auto list = get_list(ndx);
×
832
                if (list->replace_link(old_link, replace_link)) {
×
833
                    return true;
×
834
                }
×
835
            }
×
836
        }
×
837
    }
×
838
    return false;
63✔
839
}
126✔
840

841
bool Lst<Mixed>::clear_backlink(size_t ndx, CascadeState& state) const
842
{
4,524✔
843
    Mixed value = m_tree->get(ndx);
4,524✔
844
    if (value.is_type(type_TypedLink, type_Dictionary, type_List)) {
4,524✔
845
        if (value.is_type(type_TypedLink)) {
2,076✔
846
            auto link = value.get<ObjLink>();
1,932✔
847
            if (link.get_obj_key().is_unresolved()) {
1,932✔
848
                state.m_mode = CascadeState::Mode::All;
42✔
849
            }
42✔
850
            return Base::remove_backlink(m_col_key, link, state);
1,932✔
851
        }
1,932✔
852
        else if (value.is_type(type_List)) {
144✔
853
            return get_list(ndx)->remove_backlinks(state);
114✔
854
        }
114✔
855
        else if (value.is_type(type_Dictionary)) {
30✔
856
            return get_dictionary(ndx)->remove_backlinks(state);
30✔
857
        }
30✔
858
    }
2,448✔
859
    return false;
2,448✔
860
}
2,448✔
861

862
bool Lst<Mixed>::remove_backlinks(CascadeState& state) const
863
{
990✔
864
    size_t sz = size();
990✔
865
    bool recurse = false;
990✔
866
    for (size_t ndx = 0; ndx < sz; ndx++) {
4,140✔
867
        if (clear_backlink(ndx, state)) {
3,150✔
868
            recurse = true;
×
869
        }
×
870
    }
3,150✔
871
    return recurse;
990✔
872
}
990✔
873

874
/********************************** LnkLst ***********************************/
875

876
Obj LnkLst::create_and_insert_linked_object(size_t ndx)
877
{
23,901✔
878
    Table& t = *get_target_table();
23,901✔
879
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
23,901✔
880
    m_list.insert(ndx, o.get_key());
23,901✔
881
    return o;
23,901✔
882
}
23,901✔
883

884
Obj LnkLst::create_and_set_linked_object(size_t ndx)
885
{
78✔
886
    Table& t = *get_target_table();
78✔
887
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
78✔
888
    m_list.set(ndx, o.get_key());
78✔
889
    return o;
78✔
890
}
78✔
891

892
TableView LnkLst::get_sorted_view(SortDescriptor order) const
893
{
90✔
894
    TableView tv(clone_linklist());
90✔
895
    tv.do_sync();
90✔
896
    tv.sort(std::move(order));
90✔
897
    return tv;
90✔
898
}
90✔
899

900
TableView LnkLst::get_sorted_view(ColKey column_key, bool ascending) const
901
{
72✔
902
    TableView v = get_sorted_view(SortDescriptor({{column_key}}, {ascending}));
72✔
903
    return v;
72✔
904
}
72✔
905

906
void LnkLst::remove_target_row(size_t link_ndx)
907
{
30✔
908
    // Deleting the object will automatically remove all links
15✔
909
    // to it. So we do not have to manually remove the deleted link
15✔
910
    ObjKey k = get(link_ndx);
30✔
911
    get_target_table()->remove_object(k);
30✔
912
}
30✔
913

914
void LnkLst::remove_all_target_rows()
915
{
48✔
916
    if (is_attached()) {
48✔
917
        update_if_needed();
48✔
918
        _impl::TableFriend::batch_erase_rows(*get_target_table(), *m_list.m_tree);
48✔
919
    }
48✔
920
}
48✔
921

922
void LnkLst::to_json(std::ostream& out, JSONOutputMode, util::FunctionRef<void(const Mixed&)> fn) const
923
{
96✔
924
    out << "[";
96✔
925

48✔
926
    auto sz = m_list.size();
96✔
927
    for (size_t i = 0; i < sz; i++) {
180✔
928
        if (i > 0)
84✔
929
            out << ",";
30✔
930
        Mixed val(m_list.get(i));
84✔
931
        fn(val);
84✔
932
    }
84✔
933

48✔
934
    out << "]";
96✔
935
}
96✔
936

937
void LnkLst::replace_link(ObjKey old_val, ObjKey new_val)
938
{
27,414✔
939
    update_if_needed();
27,414✔
940
    auto tree = m_list.m_tree.get();
27,414✔
941
    auto n = tree->find_first(old_val);
27,414✔
942
    REALM_ASSERT(n != realm::npos);
27,414✔
943
    if (Replication* repl = get_obj().get_replication()) {
27,414✔
944
        repl->list_set(m_list, n, new_val);
27,342✔
945
    }
27,342✔
946
    tree->set(n, new_val);
27,414✔
947
    m_list.bump_content_version();
27,414✔
948
    if (new_val.is_unresolved()) {
27,414✔
949
        if (!old_val.is_unresolved()) {
366✔
950
            tree->set_context_flag(true);
366✔
951
        }
366✔
952
    }
366✔
953
    else {
27,048✔
954
        _impl::check_for_last_unresolved(tree);
27,048✔
955
    }
27,048✔
956
}
27,414✔
957

958
// Force instantiation:
959
template class Lst<ObjKey>;
960
template class Lst<ObjLink>;
961
template class Lst<int64_t>;
962
template class Lst<bool>;
963
template class Lst<StringData>;
964
template class Lst<BinaryData>;
965
template class Lst<Timestamp>;
966
template class Lst<float>;
967
template class Lst<double>;
968
template class Lst<Decimal128>;
969
template class Lst<ObjectId>;
970
template class Lst<UUID>;
971
template class Lst<util::Optional<int64_t>>;
972
template class Lst<util::Optional<bool>>;
973
template class Lst<util::Optional<float>>;
974
template class Lst<util::Optional<double>>;
975
template class Lst<util::Optional<ObjectId>>;
976
template class Lst<util::Optional<UUID>>;
977

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