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

realm / realm-core / 2074

27 Feb 2024 04:10PM UTC coverage: 90.925% (+0.02%) from 90.908%
2074

push

Evergreen

web-flow
Don't update backlinks in Mixed self-assignment (#7384)

Setting a Mixed field to ObjLink equal to the current value removed the
existing backlink and then exited before adding the new one, leaving things in
an invalid state.

93892 of 173104 branches covered (54.24%)

26 of 26 new or added lines in 2 files covered. (100.0%)

50 existing lines in 11 files now uncovered.

238379 of 262172 relevant lines covered (90.92%)

5993155.02 hits per line

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

88.62
/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
{
671,556✔
154
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
671,556✔
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);
671,556✔
159
}
671,556✔
160

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

184
template <>
185
inline void Lst<StringData>::do_remove(size_t ndx)
186
{
13,659✔
187
    if (auto index = get_table_unchecked()->get_string_index(m_col_key)) {
13,659✔
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,659✔
202
}
13,659✔
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,427,923✔
242
    auto origin_table = get_table_unchecked();
6,427,923✔
243
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
6,427,923✔
244
    set_backlink(m_col_key, {target_table_key, target_key});
6,427,923✔
245
    m_tree->insert(ndx, target_key);
6,427,923✔
246
    if (target_key.is_unresolved()) {
6,427,923✔
247
        m_tree->set_context_flag(true);
27,084✔
248
    }
27,084✔
249
}
6,427,923✔
250

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

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

1,263✔
261
    m_tree->erase(ndx);
2,526✔
262

1,263✔
263
    if (recurse) {
2,526✔
264
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
948✔
265
    }
948✔
266
    if (old_key.is_unresolved()) {
2,526✔
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,526✔
271

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

1,575✔
278
    size_t sz = size();
3,153✔
279
    if (!target_table->is_embedded()) {
3,153✔
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,377✔
289
    TableKey target_table_key = target_table->get_key();
2,757✔
290
    ColKey backlink_col = origin_table->get_opposite_column(m_col_key);
2,757✔
291

1,377✔
292
    CascadeState state;
2,757✔
293

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

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

1,377✔
307
    tf::remove_recursive(*origin_table, state); // Throws
2,757✔
308
}
2,757✔
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
bool Lst<Mixed>::init_from_parent(bool allow_create) const
351
{
23,163✔
352
    if (!m_tree) {
23,163✔
353
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
17,217✔
354
        const ArrayParent* parent = this;
17,217✔
355
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
17,217✔
356
    }
17,217✔
357
    try {
23,163✔
358
        auto ref = Base::get_collection_ref();
23,163✔
359
        if (ref) {
23,163✔
360
            m_tree->init_from_ref(ref);
14,367✔
361
        }
14,367✔
362
        else {
8,796✔
363
            if (!allow_create) {
8,796✔
364
                m_tree->detach();
5,010✔
365
                return false;
5,010✔
366
            }
5,010✔
367

1,893✔
368
            // The ref in the column was NULL, create the tree in place.
1,893✔
369
            m_tree->create();
3,786✔
370
            REALM_ASSERT(m_tree->is_attached());
3,786✔
371
        }
3,786✔
372
    }
23,163✔
373
    catch (...) {
11,580✔
374
        m_tree->detach();
36✔
375
        throw;
36✔
376
    }
36✔
377

9,039✔
378
    return true;
18,117✔
379
}
18,117✔
380

381
UpdateStatus Lst<Mixed>::update_if_needed_with_status() const
382
{
96,795✔
383
    auto status = Base::get_update_status();
96,795✔
384
    switch (status) {
96,795✔
385
        case UpdateStatus::Detached: {
6✔
386
            m_tree.reset();
6✔
387
            return UpdateStatus::Detached;
6✔
388
        }
×
389
        case UpdateStatus::NoChange:
77,928✔
390
            if (m_tree && m_tree->is_attached()) {
77,928✔
391
                return UpdateStatus::NoChange;
77,550✔
392
            }
77,550✔
393
            // The tree has not been initialized yet for this accessor, so
189✔
394
            // perform lazy initialization by treating it as an update.
189✔
395
            [[fallthrough]];
378✔
396
        case UpdateStatus::Updated: {
19,239✔
397
            bool attached = init_from_parent(false);
19,239✔
398
            Base::update_content_version();
19,239✔
399
            CollectionParent::m_parent_version++;
19,239✔
400
            return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
16,722✔
401
        }
×
402
    }
×
403
    REALM_UNREACHABLE();
404
}
×
405

406
size_t Lst<Mixed>::find_first(const Mixed& value) const
407
{
1,758✔
408
    if (!update())
1,758✔
409
        return not_found;
×
410

879✔
411
    if (value.is_null()) {
1,758✔
412
        auto ndx = m_tree->find_first(value);
72✔
413
        auto size = ndx == not_found ? m_tree->size() : ndx;
63✔
414
        for (size_t i = 0; i < size; ++i) {
108✔
415
            if (m_tree->get(i).is_unresolved_link())
60✔
416
                return i;
24✔
417
        }
60✔
418
        return ndx;
60✔
419
    }
1,686✔
420
    return m_tree->find_first(value);
1,686✔
421
}
1,686✔
422

423
Mixed Lst<Mixed>::set(size_t ndx, Mixed value)
424
{
1,068✔
425
    // get will check for ndx out of bounds
534✔
426
    Mixed old = do_get(ndx, "set()");
1,068✔
427
    if (Replication* repl = Base::get_replication()) {
1,068✔
428
        repl->list_set(*this, ndx, value);
1,008✔
429
    }
1,008✔
430
    if (!(old.is_same_type(value) && old == value)) {
1,068✔
431
        do_set(ndx, value);
924✔
432
        bump_content_version();
924✔
433
    }
924✔
434
    return old;
1,068✔
435
}
1,068✔
436

437
void Lst<Mixed>::insert(size_t ndx, Mixed value)
438
{
14,142✔
439
    ensure_created();
14,142✔
440
    auto sz = size();
14,142✔
441
    CollectionBase::validate_index("insert()", ndx, sz + 1);
14,142✔
442
    if (value.is_type(type_TypedLink)) {
14,142✔
443
        get_table()->get_parent_group()->validate(value.get_link());
6,714✔
444
    }
6,714✔
445
    if (Replication* repl = Base::get_replication()) {
14,142✔
446
        repl->list_insert(*this, ndx, value, sz);
12,582✔
447
    }
12,582✔
448
    do_insert(ndx, value);
14,142✔
449
    bump_content_version();
14,142✔
450
}
14,142✔
451

452
void Lst<Mixed>::resize(size_t new_size)
453
{
48✔
454
    size_t current_size = size();
48✔
455
    if (new_size != current_size) {
48✔
456
        while (new_size > current_size) {
48✔
457
            insert_null(current_size++);
×
458
        }
×
459
        remove(new_size, current_size);
48✔
460
        Base::bump_both_versions();
48✔
461
    }
48✔
462
}
48✔
463

464
Mixed Lst<Mixed>::remove(size_t ndx)
465
{
1,266✔
466
    // get will check for ndx out of bounds
633✔
467
    Mixed old = do_get(ndx, "remove()");
1,266✔
468
    if (Replication* repl = Base::get_replication()) {
1,266✔
469
        repl->list_erase(*this, ndx);
1,242✔
470
    }
1,242✔
471

633✔
472
    do_remove(ndx);
1,266✔
473
    bump_content_version();
1,266✔
474
    return old;
1,266✔
475
}
1,266✔
476

477
void Lst<Mixed>::remove(size_t from, size_t to)
478
{
492✔
479
    while (from < to) {
1,200✔
480
        remove(--to);
708✔
481
    }
708✔
482
}
492✔
483

484
void Lst<Mixed>::clear()
485
{
288✔
486
    if (size() > 0) {
288✔
487
        if (Replication* repl = Base::get_replication()) {
240✔
488
            repl->list_clear(*this);
228✔
489
        }
228✔
490
        CascadeState state;
240✔
491
        bool recurse = remove_backlinks(state);
240✔
492

120✔
493
        m_tree->clear();
240✔
494

120✔
495
        if (recurse) {
240✔
496
            auto table = get_table_unchecked();
×
497
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
498
        }
×
499
        bump_content_version();
240✔
500
    }
240✔
501
}
288✔
502

503
void Lst<Mixed>::move(size_t from, size_t to)
504
{
210✔
505
    auto sz = size();
210✔
506
    CollectionBase::validate_index("move()", from, sz);
210✔
507
    CollectionBase::validate_index("move()", to, sz);
210✔
508

105✔
509
    if (from != to) {
210✔
510
        if (Replication* repl = Base::get_replication()) {
210✔
511
            repl->list_move(*this, from, to);
204✔
512
        }
204✔
513
        if (to > from) {
210✔
514
            to++;
144✔
515
        }
144✔
516
        else {
66✔
517
            from++;
66✔
518
        }
66✔
519
        // We use swap here as it handles the special case for StringData where
105✔
520
        // 'to' and 'from' points into the same array. In this case you cannot
105✔
521
        // set an entry with the result of a get from another entry in the same
105✔
522
        // leaf.
105✔
523
        m_tree->insert(to, Mixed());
210✔
524
        m_tree->swap(from, to);
210✔
525
        m_tree->erase(from);
210✔
526

105✔
527
        bump_content_version();
210✔
528
    }
210✔
529
}
210✔
530

531
void Lst<Mixed>::swap(size_t ndx1, size_t ndx2)
532
{
18✔
533
    auto sz = size();
18✔
534
    CollectionBase::validate_index("swap()", ndx1, sz);
18✔
535
    CollectionBase::validate_index("swap()", ndx2, sz);
18✔
536

9✔
537
    if (ndx1 != ndx2) {
18✔
538
        if (Replication* repl = Base::get_replication()) {
18✔
539
            LstBase::swap_repl(repl, ndx1, ndx2);
12✔
540
        }
12✔
541
        m_tree->swap(ndx1, ndx2);
18✔
542
        bump_content_version();
18✔
543
    }
18✔
544
}
18✔
545

546
void Lst<Mixed>::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
547
{
492✔
548
    if (dict_or_list == CollectionType::Set) {
492✔
549
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
550
    }
×
551

246✔
552
    ensure_created();
492✔
553
    check_level();
492✔
554
    m_tree->ensure_keys();
492✔
555
    insert(path_elem.get_ndx(), Mixed(0, dict_or_list));
492✔
556
    int64_t key = generate_key(size());
492✔
557
    while (m_tree->find_key(key) != realm::not_found) {
492✔
UNCOV
558
        key++;
×
UNCOV
559
    }
×
560
    m_tree->set_key(path_elem.get_ndx(), key);
492✔
561
    bump_content_version();
492✔
562
}
492✔
563

564
void Lst<Mixed>::set_collection(const PathElement& path_elem, CollectionType dict_or_list)
565
{
24✔
566
    if (dict_or_list == CollectionType::Set) {
24✔
567
        throw IllegalOperation("Set nested in List<Mixed> is not supported");
×
568
    }
×
569

12✔
570
    auto ndx = path_elem.get_ndx();
24✔
571
    // get will check for ndx out of bounds
12✔
572
    Mixed old_val = do_get(ndx, "set_collection()");
24✔
573
    Mixed new_val(0, dict_or_list);
24✔
574

12✔
575
    check_level();
24✔
576

12✔
577
    if (old_val != new_val) {
24✔
578
        m_tree->ensure_keys();
18✔
579
        set(ndx, new_val);
18✔
580
        int64_t key = m_tree->get_key(ndx);
18✔
581
        if (key == 0) {
18✔
582
            key = generate_key(size());
18✔
583
            while (m_tree->find_key(key) != realm::not_found) {
18✔
584
                key++;
×
585
            }
×
586
            m_tree->set_key(ndx, key);
18✔
587
        }
18✔
588
        bump_content_version();
18✔
589
    }
18✔
590
}
24✔
591

592
DictionaryPtr Lst<Mixed>::get_dictionary(const PathElement& path_elem) const
593
{
330✔
594
    update();
330✔
595
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
330✔
596
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
291✔
597
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
330✔
598
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
330✔
599
    return ret;
330✔
600
}
330✔
601

602
std::shared_ptr<Lst<Mixed>> Lst<Mixed>::get_list(const PathElement& path_elem) const
603
{
474✔
604
    update();
474✔
605
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
474✔
606
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
462✔
607
    std::shared_ptr<Lst<Mixed>> ret = std::make_shared<Lst<Mixed>>(m_col_key, get_level() + 1);
474✔
608
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
474✔
609
    return ret;
474✔
610
}
474✔
611

612
void Lst<Mixed>::do_set(size_t ndx, Mixed value)
613
{
924✔
614
    ObjLink old_link;
924✔
615
    ObjLink target_link;
924✔
616
    Mixed old_value = m_tree->get(ndx);
924✔
617

462✔
618
    if (old_value.is_type(type_TypedLink)) {
924✔
619
        old_link = old_value.get<ObjLink>();
426✔
620
    }
426✔
621
    if (value.is_type(type_TypedLink)) {
924✔
622
        target_link = value.get<ObjLink>();
420✔
623
        get_table_unchecked()->get_parent_group()->validate(target_link);
420✔
624
    }
420✔
625

462✔
626
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
903✔
627
    bool recurse = Base::replace_backlink(m_col_key, old_link, target_link, state);
924✔
628

462✔
629
    m_tree->set(ndx, value);
924✔
630

462✔
631
    if (recurse) {
924✔
632
        auto origin_table = get_table_unchecked();
×
633
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
634
    }
×
635
}
924✔
636

637
void Lst<Mixed>::do_insert(size_t ndx, Mixed value)
638
{
14,106✔
639
    if (value.is_type(type_TypedLink)) {
14,106✔
640
        Base::set_backlink(m_col_key, value.get<ObjLink>());
6,690✔
641
    }
6,690✔
642

7,053✔
643
    m_tree->insert(ndx, value);
14,106✔
644
}
14,106✔
645

646
void Lst<Mixed>::do_remove(size_t ndx)
647
{
1,266✔
648
    CascadeState state;
1,266✔
649
    bool recurse = clear_backlink(ndx, state);
1,266✔
650

633✔
651
    m_tree->erase(ndx);
1,266✔
652

633✔
653
    if (recurse) {
1,266✔
654
        auto table = get_table_unchecked();
×
655
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
656
    }
×
657
}
1,266✔
658

659
void Lst<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const
660
{
234✔
661
    update();
234✔
662

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

676
void Lst<Mixed>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
677
{
30✔
678
    indices.clear();
30✔
679
    sort(indices, sort_order.value_or(true));
30✔
680
    if (indices.empty()) {
30✔
681
        return;
×
682
    }
×
683

15✔
684
    auto tree = m_tree.get();
30✔
685
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
666✔
686
        return unresolved_to_null(tree->get(i1)) == unresolved_to_null(tree->get(i2));
666✔
687
    });
666✔
688

15✔
689
    // Erase the duplicates
15✔
690
    indices.erase(duplicates, indices.end());
30✔
691

15✔
692
    if (!sort_order) {
30✔
693
        // Restore original order
15✔
694
        std::sort(indices.begin(), indices.end());
30✔
695
    }
30✔
696
}
30✔
697

698
util::Optional<Mixed> Lst<Mixed>::min(size_t* return_ndx) const
699
{
96✔
700
    if (update()) {
96✔
701
        return MinHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
702
    }
84✔
703
    return MinHelper<Mixed>::not_found(return_ndx);
12✔
704
}
12✔
705

706
util::Optional<Mixed> Lst<Mixed>::max(size_t* return_ndx) const
707
{
96✔
708
    if (update()) {
96✔
709
        return MaxHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
710
    }
84✔
711
    return MaxHelper<Mixed>::not_found(return_ndx);
12✔
712
}
12✔
713

714
util::Optional<Mixed> Lst<Mixed>::sum(size_t* return_cnt) const
715
{
96✔
716
    if (update()) {
96✔
717
        return SumHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
718
    }
84✔
719
    return SumHelper<Mixed>::not_found(return_cnt);
12✔
720
}
12✔
721

722
util::Optional<Mixed> Lst<Mixed>::avg(size_t* return_cnt) const
723
{
96✔
724
    if (update()) {
96✔
725
        return AverageHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
726
    }
84✔
727
    return AverageHelper<Mixed>::not_found(return_cnt);
12✔
728
}
12✔
729

730
void Lst<Mixed>::to_json(std::ostream& out, JSONOutputMode output_mode,
731
                         util::FunctionRef<void(const Mixed&)> fn) const
732
{
54✔
733
    out << "[";
54✔
734

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

27✔
758
    out << "]";
54✔
759
}
54✔
760

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

775
bool Lst<Mixed>::check_collection_ref(Index index, CollectionType type) const noexcept
776
{
684✔
777
    auto ndx = m_tree->find_key(index.get_salt());
684✔
778
    if (ndx != realm::not_found) {
684✔
779
        return get(ndx).is_type(DataType(int(type)));
654✔
780
    }
654✔
781
    return false;
30✔
782
}
30✔
783

784
void Lst<Mixed>::set_collection_ref(Index index, ref_type ref, CollectionType type)
785
{
456✔
786
    auto ndx = m_tree->find_key(index.get_salt());
456✔
787
    if (ndx == realm::not_found) {
456✔
788
        throw StaleAccessor("Collection has been deleted");
×
789
    }
×
790
    m_tree->set(ndx, Mixed(ref, type));
456✔
791
}
456✔
792

793
void Lst<Mixed>::add_index(Path& path, const Index& index) const
794
{
48✔
795
    auto ndx = m_tree->find_key(index.get_salt());
48✔
796
    REALM_ASSERT(ndx != realm::not_found);
48✔
797
    path.emplace_back(ndx);
48✔
798
}
48✔
799

800
size_t Lst<Mixed>::find_index(const Index& index) const
801
{
270✔
802
    update();
270✔
803
    return m_tree->find_key(index.get_salt());
270✔
804
}
270✔
805

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

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

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

868
bool Lst<Mixed>::clear_backlink(size_t ndx, CascadeState& state) const
869
{
3,912✔
870
    Mixed value = m_tree->get(ndx);
3,912✔
871
    if (value.is_type(type_TypedLink, type_Dictionary, type_List)) {
3,912✔
872
        if (value.is_type(type_TypedLink)) {
1,974✔
873
            auto link = value.get<ObjLink>();
1,932✔
874
            if (link.get_obj_key().is_unresolved()) {
1,932✔
875
                state.m_mode = CascadeState::Mode::All;
42✔
876
            }
42✔
877
            return Base::remove_backlink(m_col_key, link, state);
1,932✔
878
        }
1,932✔
879
        else if (value.is_type(type_List)) {
42✔
880
            return get_list(ndx)->remove_backlinks(state);
12✔
881
        }
12✔
882
        else if (value.is_type(type_Dictionary)) {
30✔
883
            return get_dictionary(ndx)->remove_backlinks(state);
30✔
884
        }
30✔
885
    }
1,938✔
886
    return false;
1,938✔
887
}
1,938✔
888

889
bool Lst<Mixed>::remove_backlinks(CascadeState& state) const
890
{
516✔
891
    size_t sz = size();
516✔
892
    bool recurse = false;
516✔
893
    for (size_t ndx = 0; ndx < sz; ndx++) {
3,162✔
894
        if (clear_backlink(ndx, state)) {
2,646✔
895
            recurse = true;
×
896
        }
×
897
    }
2,646✔
898
    return recurse;
516✔
899
}
516✔
900

901
bool Lst<Mixed>::update_if_needed() const
902
{
834✔
903
    auto status = update_if_needed_with_status();
834✔
904
    if (status == UpdateStatus::Detached) {
834✔
905
        throw StaleAccessor("CollectionList no longer exists");
×
906
    }
×
907
    return status == UpdateStatus::Updated;
834✔
908
}
834✔
909

910
/********************************** LnkLst ***********************************/
911

912
Obj LnkLst::create_and_insert_linked_object(size_t ndx)
913
{
23,892✔
914
    Table& t = *get_target_table();
23,892✔
915
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
23,892✔
916
    m_list.insert(ndx, o.get_key());
23,892✔
917
    return o;
23,892✔
918
}
23,892✔
919

920
Obj LnkLst::create_and_set_linked_object(size_t ndx)
921
{
78✔
922
    Table& t = *get_target_table();
78✔
923
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
78✔
924
    m_list.set(ndx, o.get_key());
78✔
925
    return o;
78✔
926
}
78✔
927

928
TableView LnkLst::get_sorted_view(SortDescriptor order) const
929
{
90✔
930
    TableView tv(clone_linklist());
90✔
931
    tv.do_sync();
90✔
932
    tv.sort(std::move(order));
90✔
933
    return tv;
90✔
934
}
90✔
935

936
TableView LnkLst::get_sorted_view(ColKey column_key, bool ascending) const
937
{
72✔
938
    TableView v = get_sorted_view(SortDescriptor({{column_key}}, {ascending}));
72✔
939
    return v;
72✔
940
}
72✔
941

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

950
void LnkLst::remove_all_target_rows()
951
{
48✔
952
    if (is_attached()) {
48✔
953
        update_if_needed();
48✔
954
        _impl::TableFriend::batch_erase_rows(*get_target_table(), *m_list.m_tree);
48✔
955
    }
48✔
956
}
48✔
957

958
void LnkLst::to_json(std::ostream& out, JSONOutputMode, util::FunctionRef<void(const Mixed&)> fn) const
959
{
96✔
960
    out << "[";
96✔
961

48✔
962
    auto sz = m_list.size();
96✔
963
    for (size_t i = 0; i < sz; i++) {
180✔
964
        if (i > 0)
84✔
965
            out << ",";
30✔
966
        Mixed val(m_list.get(i));
84✔
967
        fn(val);
84✔
968
    }
84✔
969

48✔
970
    out << "]";
96✔
971
}
96✔
972

973
void LnkLst::replace_link(ObjKey old_val, ObjKey new_val)
974
{
27,414✔
975
    update_if_needed();
27,414✔
976
    auto tree = m_list.m_tree.get();
27,414✔
977
    auto n = tree->find_first(old_val);
27,414✔
978
    REALM_ASSERT(n != realm::npos);
27,414✔
979
    if (Replication* repl = get_obj().get_replication()) {
27,414✔
980
        repl->list_set(m_list, n, new_val);
27,342✔
981
    }
27,342✔
982
    tree->set(n, new_val);
27,414✔
983
    m_list.bump_content_version();
27,414✔
984
    if (new_val.is_unresolved()) {
27,414✔
985
        if (!old_val.is_unresolved()) {
366✔
986
            tree->set_context_flag(true);
366✔
987
        }
366✔
988
    }
366✔
989
    else {
27,048✔
990
        _impl::check_for_last_unresolved(tree);
27,048✔
991
    }
27,048✔
992
}
27,414✔
993

994
// Force instantiation:
995
template class Lst<ObjKey>;
996
template class Lst<ObjLink>;
997
template class Lst<int64_t>;
998
template class Lst<bool>;
999
template class Lst<StringData>;
1000
template class Lst<BinaryData>;
1001
template class Lst<Timestamp>;
1002
template class Lst<float>;
1003
template class Lst<double>;
1004
template class Lst<Decimal128>;
1005
template class Lst<ObjectId>;
1006
template class Lst<UUID>;
1007
template class Lst<util::Optional<int64_t>>;
1008
template class Lst<util::Optional<bool>>;
1009
template class Lst<util::Optional<float>>;
1010
template class Lst<util::Optional<double>>;
1011
template class Lst<util::Optional<ObjectId>>;
1012
template class Lst<util::Optional<UUID>>;
1013

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

© 2026 Coveralls, Inc