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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM UTC coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

88.81
/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

40
namespace realm {
41

42
/****************************** Lst aggregates *******************************/
43

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

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

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

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

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

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

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

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

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

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

126
/********************************** LstBase *********************************/
127

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

148
/********************************* Lst<Key> *********************************/
149

150
template <>
151
void Lst<ObjKey>::do_set(size_t ndx, ObjKey target_key)
152
{
50,724✔
153
    auto origin_table = get_table_unchecked();
50,724✔
154
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
50,724✔
155
    ObjKey old_key = this->get(ndx);
50,724✔
156
    CascadeState state(CascadeState::Mode::Strong);
50,724✔
157
    bool recurse = replace_backlink(m_col_key, {target_table_key, old_key}, {target_table_key, target_key}, state);
50,724✔
158

25,362✔
159
    m_tree->set(ndx, target_key);
50,724✔
160

25,362✔
161
    if (recurse) {
50,724✔
162
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
60✔
163
    }
60✔
164
    if (target_key.is_unresolved()) {
50,724✔
165
        if (!old_key.is_unresolved())
318✔
166
            m_tree->set_context_flag(true);
318✔
167
    }
318✔
168
    else if (old_key.is_unresolved()) {
50,406✔
169
        // We might have removed the last unresolved link - check it
4,530✔
170
        _impl::check_for_last_unresolved(m_tree.get());
9,060✔
171
    }
9,060✔
172
}
50,724✔
173

174
template <>
175
void Lst<ObjKey>::do_insert(size_t ndx, ObjKey target_key)
176
{
6,406,947✔
177
    auto origin_table = get_table_unchecked();
6,406,947✔
178
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
6,406,947✔
179
    set_backlink(m_col_key, {target_table_key, target_key});
6,406,947✔
180
    m_tree->insert(ndx, target_key);
6,406,947✔
181
    if (target_key.is_unresolved()) {
6,406,947✔
182
        m_tree->set_context_flag(true);
9,084✔
183
    }
9,084✔
184
}
6,406,947✔
185

186
template <>
187
void Lst<ObjKey>::do_remove(size_t ndx)
188
{
2,394✔
189
    auto origin_table = get_table_unchecked();
2,394✔
190
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
2,394✔
191
    ObjKey old_key = get(ndx);
2,394✔
192
    CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
2,385✔
193

1,197✔
194
    bool recurse = remove_backlink(m_col_key, {target_table_key, old_key}, state);
2,394✔
195

1,197✔
196
    m_tree->erase(ndx);
2,394✔
197

1,197✔
198
    if (recurse) {
2,394✔
199
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
918✔
200
    }
918✔
201
    if (old_key.is_unresolved()) {
2,394✔
202
        // We might have removed the last unresolved link - check it
9✔
203
        _impl::check_for_last_unresolved(m_tree.get());
18✔
204
    }
18✔
205
}
2,394✔
206

207
template <>
208
void Lst<ObjKey>::do_clear()
209
{
2,619✔
210
    auto origin_table = get_table_unchecked();
2,619✔
211
    TableRef target_table = get_obj().get_target_table(m_col_key);
2,619✔
212

1,311✔
213
    size_t sz = size();
2,619✔
214
    if (!target_table->is_embedded()) {
2,619✔
215
        size_t ndx = sz;
396✔
216
        while (ndx--) {
41,016✔
217
            do_set(ndx, null_key);
40,620✔
218
            m_tree->erase(ndx);
40,620✔
219
        }
40,620✔
220
        m_tree->set_context_flag(false);
396✔
221
        return;
396✔
222
    }
396✔
223

1,113✔
224
    TableKey target_table_key = target_table->get_key();
2,223✔
225
    ColKey backlink_col = origin_table->get_opposite_column(m_col_key);
2,223✔
226

1,113✔
227
    CascadeState state;
2,223✔
228

1,113✔
229
    typedef _impl::TableFriend tf;
2,223✔
230
    for (size_t ndx = 0; ndx < sz; ++ndx) {
4,572✔
231
        ObjKey target_key = m_tree->get(ndx);
2,349✔
232
        Obj target_obj = target_table->get_object(target_key);
2,349✔
233
        target_obj.remove_one_backlink(backlink_col, get_obj().get_key()); // Throws
2,349✔
234
        // embedded objects should only have one incoming link
1,176✔
235
        REALM_ASSERT_EX(target_obj.get_backlink_count() == 0, target_obj.get_backlink_count());
2,349✔
236
        state.m_to_be_deleted.emplace_back(target_table_key, target_key);
2,349✔
237
    }
2,349✔
238

1,113✔
239
    m_tree->clear();
2,223✔
240
    m_tree->set_context_flag(false);
2,223✔
241

1,113✔
242
    tf::remove_recursive(*origin_table, state); // Throws
2,223✔
243
}
2,223✔
244

245
template <>
246
void Lst<ObjLink>::do_set(size_t ndx, ObjLink target_link)
UNCOV
247
{
×
UNCOV
248
    ObjLink old_link = get(ndx);
×
UNCOV
249
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
UNCOV
250
    bool recurse = replace_backlink(m_col_key, old_link, target_link, state);
×
251

UNCOV
252
    m_tree->set(ndx, target_link);
×
253

UNCOV
254
    if (recurse) {
×
UNCOV
255
        auto origin_table = get_table_unchecked();
×
UNCOV
256
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
UNCOV
257
    }
×
UNCOV
258
}
×
259

260
template <>
261
void Lst<ObjLink>::do_insert(size_t ndx, ObjLink target_link)
UNCOV
262
{
×
UNCOV
263
    set_backlink(m_col_key, target_link);
×
UNCOV
264
    m_tree->insert(ndx, target_link);
×
UNCOV
265
}
×
266

267
template <>
268
void Lst<ObjLink>::do_remove(size_t ndx)
UNCOV
269
{
×
UNCOV
270
    ObjLink old_link = get(ndx);
×
UNCOV
271
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
272

UNCOV
273
    bool recurse = remove_backlink(m_col_key, old_link, state);
×
274

UNCOV
275
    m_tree->erase(ndx);
×
276

UNCOV
277
    if (recurse) {
×
UNCOV
278
        auto table = get_table_unchecked();
×
UNCOV
279
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
UNCOV
280
    }
×
UNCOV
281
}
×
282

283
/******************************** Lst<Mixed> *********************************/
284

285
bool Lst<Mixed>::init_from_parent(bool allow_create) const
286
{
26,037✔
287
    if (!m_tree) {
26,037✔
288
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
18,777✔
289
        const ArrayParent* parent = this;
18,777✔
290
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
18,777✔
291
    }
18,777✔
292
    try {
26,037✔
293
        auto ref = Base::get_collection_ref();
26,037✔
294
        if (ref) {
26,037✔
295
            m_tree->init_from_ref(ref);
15,687✔
296
        }
15,687✔
297
        else {
10,350✔
298
            if (!allow_create) {
10,350✔
299
                m_tree->detach();
6,048✔
300
                return false;
6,048✔
301
            }
6,048✔
302

2,151✔
303
            // The ref in the column was NULL, create the tree in place.
2,151✔
304
            m_tree->create();
4,302✔
305
            REALM_ASSERT(m_tree->is_attached());
4,302✔
306
        }
4,302✔
307
    }
26,037✔
308
    catch (...) {
13,020✔
309
        m_tree->detach();
30✔
310
        throw;
30✔
311
    }
30✔
312

9,966✔
313
    return true;
19,959✔
314
}
19,959✔
315

316
UpdateStatus Lst<Mixed>::update_if_needed_with_status() const
317
{
111,561✔
318
    auto status = Base::get_update_status();
111,561✔
319
    switch (status) {
111,561✔
320
        case UpdateStatus::Detached: {
6✔
321
            m_tree.reset();
6✔
322
            return UpdateStatus::Detached;
6✔
UNCOV
323
        }
×
324
        case UpdateStatus::NoChange:
91,425✔
325
            if (m_tree && m_tree->is_attached()) {
91,425✔
326
                return UpdateStatus::NoChange;
90,234✔
327
            }
90,234✔
328
            // The tree has not been initialized yet for this accessor, so
597✔
329
            // perform lazy initialization by treating it as an update.
597✔
330
            [[fallthrough]];
1,191✔
331
        case UpdateStatus::Updated: {
21,321✔
332
            bool attached = init_from_parent(false);
21,321✔
333
            Base::update_content_version();
21,321✔
334
            return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
18,285✔
UNCOV
335
        }
×
UNCOV
336
    }
×
UNCOV
337
    REALM_UNREACHABLE();
×
UNCOV
338
}
×
339

340
size_t Lst<Mixed>::find_first(const Mixed& value) const
341
{
1,254✔
342
    if (!update())
1,254✔
UNCOV
343
        return not_found;
×
344

627✔
345
    if (value.is_null()) {
1,254✔
346
        auto ndx = m_tree->find_first(value);
72✔
347
        auto size = ndx == not_found ? m_tree->size() : ndx;
63✔
348
        for (size_t i = 0; i < size; ++i) {
108✔
349
            if (m_tree->get(i).is_unresolved_link())
60✔
350
                return i;
24✔
351
        }
60✔
352
        return ndx;
60✔
353
    }
1,182✔
354
    return m_tree->find_first(value);
1,182✔
355
}
1,182✔
356

357
Mixed Lst<Mixed>::set(size_t ndx, Mixed value)
358
{
1,116✔
359
    // get will check for ndx out of bounds
558✔
360
    Mixed old = do_get(ndx, "set()");
1,116✔
361
    if (Replication* repl = Base::get_replication()) {
1,116✔
362
        repl->list_set(*this, ndx, value);
1,056✔
363
    }
1,056✔
364
    if (!(old.is_same_type(value) && old == value)) {
1,116✔
365
        do_set(ndx, value);
858✔
366
        bump_content_version();
858✔
367
    }
858✔
368
    return old;
1,116✔
369
}
1,116✔
370

371
void Lst<Mixed>::insert(size_t ndx, Mixed value)
372
{
14,202✔
373
    ensure_created();
14,202✔
374
    auto sz = size();
14,202✔
375
    CollectionBase::validate_index("insert()", ndx, sz + 1);
14,202✔
376
    if (Replication* repl = Base::get_replication()) {
14,202✔
377
        repl->list_insert(*this, ndx, value, sz);
12,690✔
378
    }
12,690✔
379
    do_insert(ndx, value);
14,202✔
380
    bump_content_version();
14,202✔
381
}
14,202✔
382

383
void Lst<Mixed>::resize(size_t new_size)
384
{
48✔
385
    size_t current_size = size();
48✔
386
    if (new_size != current_size) {
48✔
387
        while (new_size > current_size) {
48✔
UNCOV
388
            insert_null(current_size++);
×
UNCOV
389
        }
×
390
        remove(new_size, current_size);
48✔
391
        Base::bump_both_versions();
48✔
392
    }
48✔
393
}
48✔
394

395
Mixed Lst<Mixed>::remove(size_t ndx)
396
{
1,242✔
397
    // get will check for ndx out of bounds
621✔
398
    Mixed old = do_get(ndx, "remove()");
1,242✔
399
    if (Replication* repl = Base::get_replication()) {
1,242✔
400
        repl->list_erase(*this, ndx);
1,218✔
401
    }
1,218✔
402

621✔
403
    do_remove(ndx);
1,242✔
404
    bump_content_version();
1,242✔
405
    return old;
1,242✔
406
}
1,242✔
407

408
void Lst<Mixed>::remove(size_t from, size_t to)
409
{
474✔
410
    while (from < to) {
1,134✔
411
        remove(--to);
660✔
412
    }
660✔
413
}
474✔
414

415
void Lst<Mixed>::clear()
416
{
300✔
417
    if (size() > 0) {
300✔
418
        if (Replication* repl = Base::get_replication()) {
252✔
419
            repl->list_clear(*this);
240✔
420
        }
240✔
421
        size_t ndx = size();
252✔
422
        while (ndx--) {
2,028✔
423
            do_remove(ndx);
1,776✔
424
        }
1,776✔
425
        bump_content_version();
252✔
426
    }
252✔
427
}
300✔
428

429
void Lst<Mixed>::move(size_t from, size_t to)
430
{
174✔
431
    auto sz = size();
174✔
432
    CollectionBase::validate_index("move()", from, sz);
174✔
433
    CollectionBase::validate_index("move()", to, sz);
174✔
434

87✔
435
    if (from != to) {
174✔
436
        if (Replication* repl = Base::get_replication()) {
174✔
437
            repl->list_move(*this, from, to);
168✔
438
        }
168✔
439
        if (to > from) {
174✔
440
            to++;
120✔
441
        }
120✔
442
        else {
54✔
443
            from++;
54✔
444
        }
54✔
445
        // We use swap here as it handles the special case for StringData where
87✔
446
        // 'to' and 'from' points into the same array. In this case you cannot
87✔
447
        // set an entry with the result of a get from another entry in the same
87✔
448
        // leaf.
87✔
449
        m_tree->insert(to, Mixed());
174✔
450
        m_tree->swap(from, to);
174✔
451
        m_tree->erase(from);
174✔
452

87✔
453
        bump_content_version();
174✔
454
    }
174✔
455
}
174✔
456

457
void Lst<Mixed>::swap(size_t ndx1, size_t ndx2)
458
{
18✔
459
    auto sz = size();
18✔
460
    CollectionBase::validate_index("swap()", ndx1, sz);
18✔
461
    CollectionBase::validate_index("swap()", ndx2, sz);
18✔
462

9✔
463
    if (ndx1 != ndx2) {
18✔
464
        if (Replication* repl = Base::get_replication()) {
18✔
465
            LstBase::swap_repl(repl, ndx1, ndx2);
12✔
466
        }
12✔
467
        m_tree->swap(ndx1, ndx2);
18✔
468
        bump_content_version();
18✔
469
    }
18✔
470
}
18✔
471

472
void Lst<Mixed>::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
473
{
1,056✔
474
    ensure_created();
1,056✔
475
    check_level();
1,056✔
476
    m_tree->ensure_keys();
1,056✔
477
    insert(path_elem.get_ndx(), Mixed(0, dict_or_list));
1,056✔
478
    int64_t key = generate_key(size());
1,056✔
479
    while (m_tree->find_key(key) != realm::not_found) {
1,056✔
UNCOV
480
        key++;
×
UNCOV
481
    }
×
482
    m_tree->set_key(path_elem.get_ndx(), key);
1,056✔
483
    bump_content_version();
1,056✔
484
}
1,056✔
485

486
void Lst<Mixed>::set_collection(const PathElement& path_elem, CollectionType type)
487
{
72✔
488
    auto ndx = path_elem.get_ndx();
72✔
489
    // get will check for ndx out of bounds
36✔
490
    Mixed old_val = do_get(ndx, "set_collection()");
72✔
491
    Mixed new_val(0, type);
72✔
492

36✔
493
    check_level();
72✔
494

36✔
495
    if (old_val != new_val) {
72✔
496
        m_tree->ensure_keys();
66✔
497
        set(ndx, Mixed(0, type));
66✔
498
        int64_t key = m_tree->get_key(ndx);
66✔
499
        if (key == 0) {
66✔
500
            key = generate_key(size());
66✔
501
            while (m_tree->find_key(key) != realm::not_found) {
66✔
UNCOV
502
                key++;
×
UNCOV
503
            }
×
504
            m_tree->set_key(ndx, key);
66✔
505
        }
66✔
506
        bump_content_version();
66✔
507
    }
66✔
508
}
72✔
509

510
DictionaryPtr Lst<Mixed>::get_dictionary(const PathElement& path_elem) const
511
{
1,002✔
512
    update();
1,002✔
513
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
1,002✔
514
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
753✔
515
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
1,002✔
516
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
1,002✔
517
    return ret;
1,002✔
518
}
1,002✔
519

520
SetMixedPtr Lst<Mixed>::get_set(const PathElement& path_elem) const
521
{
90✔
522
    update();
90✔
523
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
90✔
524
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
78✔
525
    auto ret = std::make_shared<Set<Mixed>>(m_obj_mem, m_col_key);
90✔
526
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
90✔
527
    return ret;
90✔
528
}
90✔
529

530
std::shared_ptr<Lst<Mixed>> Lst<Mixed>::get_list(const PathElement& path_elem) const
531
{
1,956✔
532
    update();
1,956✔
533
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
1,956✔
534
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
1,494✔
535
    std::shared_ptr<Lst<Mixed>> ret = std::make_shared<Lst<Mixed>>(m_col_key, get_level() + 1);
1,956✔
536
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
1,956✔
537
    return ret;
1,956✔
538
}
1,956✔
539

540
void Lst<Mixed>::do_set(size_t ndx, Mixed value)
541
{
858✔
542
    ObjLink old_link;
858✔
543
    ObjLink target_link;
858✔
544
    Mixed old_value = m_tree->get(ndx);
858✔
545

429✔
546
    if (old_value.is_type(type_TypedLink)) {
858✔
547
        old_link = old_value.get<ObjLink>();
210✔
548
    }
210✔
549
    if (value.is_type(type_TypedLink)) {
858✔
550
        target_link = value.get<ObjLink>();
204✔
551
        get_table_unchecked()->get_parent_group()->validate(target_link);
204✔
552
    }
204✔
553

429✔
554
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
855✔
555
    bool recurse = Base::replace_backlink(m_col_key, old_link, target_link, state);
858✔
556

429✔
557
    m_tree->set(ndx, value);
858✔
558

429✔
559
    if (recurse) {
858✔
UNCOV
560
        auto origin_table = get_table_unchecked();
×
UNCOV
561
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
UNCOV
562
    }
×
563
}
858✔
564

565
void Lst<Mixed>::do_insert(size_t ndx, Mixed value)
566
{
14,196✔
567
    if (value.is_type(type_TypedLink)) {
14,196✔
568
        Base::set_backlink(m_col_key, value.get<ObjLink>());
5,202✔
569
    }
5,202✔
570

7,098✔
571
    m_tree->insert(ndx, value);
14,196✔
572
}
14,196✔
573

574
void Lst<Mixed>::do_remove(size_t ndx)
575
{
3,018✔
576
    Mixed old_value = m_tree->get(ndx);
3,018✔
577
    if (old_value.is_type(type_TypedLink, type_Dictionary, type_List)) {
3,018✔
578

654✔
579
        bool recurse = false;
1,308✔
580
        CascadeState state;
1,308✔
581
        if (old_value.is_type(type_TypedLink)) {
1,308✔
582
            auto old_link = old_value.get<ObjLink>();
1,224✔
583
            if (old_link.get_obj_key().is_unresolved()) {
1,224✔
584
                state.m_mode = CascadeState::Mode::All;
6✔
585
            }
6✔
586
            recurse = Base::remove_backlink(m_col_key, old_link, state);
1,224✔
587
        }
1,224✔
588
        else if (old_value.is_type(type_List)) {
84✔
589
            get_list(ndx)->remove_backlinks(state);
78✔
590
        }
78✔
591
        else if (old_value.is_type(type_Dictionary)) {
6✔
592
            get_dictionary(ndx)->remove_backlinks(state);
6✔
593
        }
6✔
594

654✔
595
        m_tree->erase(ndx);
1,308✔
596

654✔
597
        if (recurse) {
1,308✔
UNCOV
598
            auto table = get_table_unchecked();
×
UNCOV
599
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
UNCOV
600
        }
×
601
    }
1,308✔
602
    else {
1,710✔
603
        m_tree->erase(ndx);
1,710✔
604
    }
1,710✔
605
}
3,018✔
606

607
void Lst<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const
608
{
234✔
609
    update();
234✔
610

117✔
611
    auto tree = m_tree.get();
234✔
612
    if (ascending) {
234✔
613
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
13,683✔
614
            return unresolved_to_null(tree->get(i1)) < unresolved_to_null(tree->get(i2));
13,683✔
615
        });
13,683✔
616
    }
210✔
617
    else {
24✔
618
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
2,451✔
619
            return unresolved_to_null(tree->get(i1)) > unresolved_to_null(tree->get(i2));
2,451✔
620
        });
2,451✔
621
    }
24✔
622
}
234✔
623

624
void Lst<Mixed>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
625
{
30✔
626
    indices.clear();
30✔
627
    sort(indices, sort_order.value_or(true));
30✔
628
    if (indices.empty()) {
30✔
UNCOV
629
        return;
×
UNCOV
630
    }
×
631

15✔
632
    auto tree = m_tree.get();
30✔
633
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
666✔
634
        return unresolved_to_null(tree->get(i1)) == unresolved_to_null(tree->get(i2));
666✔
635
    });
666✔
636

15✔
637
    // Erase the duplicates
15✔
638
    indices.erase(duplicates, indices.end());
30✔
639

15✔
640
    if (!sort_order) {
30✔
641
        // Restore original order
15✔
642
        std::sort(indices.begin(), indices.end());
30✔
643
    }
30✔
644
}
30✔
645

646
util::Optional<Mixed> Lst<Mixed>::min(size_t* return_ndx) const
647
{
96✔
648
    if (update()) {
96✔
649
        return MinHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
650
    }
84✔
651
    return MinHelper<Mixed>::not_found(return_ndx);
12✔
652
}
12✔
653

654
util::Optional<Mixed> Lst<Mixed>::max(size_t* return_ndx) const
655
{
96✔
656
    if (update()) {
96✔
657
        return MaxHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
658
    }
84✔
659
    return MaxHelper<Mixed>::not_found(return_ndx);
12✔
660
}
12✔
661

662
util::Optional<Mixed> Lst<Mixed>::sum(size_t* return_cnt) const
663
{
96✔
664
    if (update()) {
96✔
665
        return SumHelper<Mixed>::eval(*m_tree, return_cnt);
84✔
666
    }
84✔
667
    return SumHelper<Mixed>::not_found(return_cnt);
12✔
668
}
12✔
669

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

678
void Lst<Mixed>::to_json(std::ostream& out, size_t link_depth, JSONOutputMode output_mode,
679
                         util::FunctionRef<void(const Mixed&)> fn) const
680
{
78✔
681
    auto [open_str, close_str] = get_open_close_strings(link_depth, output_mode);
78✔
682

39✔
683
    out << open_str;
78✔
684
    out << "[";
78✔
685

39✔
686
    auto sz = size();
78✔
687
    for (size_t i = 0; i < sz; i++) {
276✔
688
        if (i > 0)
198✔
689
            out << ",";
120✔
690
        Mixed val = m_tree->get(i);
198✔
691
        if (val.is_type(type_TypedLink)) {
198✔
692
            fn(val);
48✔
693
        }
48✔
694
        else if (val.is_type(type_Dictionary)) {
150✔
695
            DummyParent parent(this->get_table(), val.get_ref());
24✔
696
            Dictionary dict(parent, i);
24✔
697
            dict.to_json(out, link_depth, output_mode, fn);
24✔
698
        }
24✔
699
        else if (val.is_type(type_List)) {
126✔
700
            DummyParent parent(this->get_table(), val.get_ref());
24✔
701
            Lst<Mixed> list(parent, i);
24✔
702
            list.to_json(out, link_depth, output_mode, fn);
24✔
703
        }
24✔
704
        else if (val.is_type(type_Set)) {
102✔
705
            DummyParent parent(this->get_table(), val.get_ref());
6✔
706
            Set<Mixed> set(parent, 0);
6✔
707
            set.to_json(out, link_depth, output_mode, fn);
6✔
708
        }
6✔
709
        else {
96✔
710
            val.to_json(out, output_mode);
96✔
711
        }
96✔
712
    }
198✔
713

39✔
714
    out << "]";
78✔
715
    out << close_str;
78✔
716
}
78✔
717

718
ref_type Lst<Mixed>::get_collection_ref(Index index, CollectionType type) const
719
{
4,482✔
720
    auto ndx = m_tree->find_key(index.get_salt());
4,482✔
721
    if (ndx != realm::not_found) {
4,482✔
722
        auto val = get(ndx);
4,482✔
723
        if (val.is_type(DataType(int(type)))) {
4,482✔
724
            return val.get_ref();
4,482✔
725
        }
4,482✔
UNCOV
726
        throw realm::IllegalOperation(util::format("Not a %1", type));
×
UNCOV
727
    }
×
UNCOV
728
    throw StaleAccessor("This collection is no more");
×
UNCOV
729
    return 0;
×
UNCOV
730
}
×
731

732
bool Lst<Mixed>::check_collection_ref(Index index, CollectionType type) const noexcept
733
{
2,166✔
734
    auto ndx = m_tree->find_key(index.get_salt());
2,166✔
735
    if (ndx != realm::not_found) {
2,166✔
736
        return get(ndx).is_type(DataType(int(type)));
2,130✔
737
    }
2,130✔
738
    return false;
36✔
739
}
36✔
740

741
void Lst<Mixed>::set_collection_ref(Index index, ref_type ref, CollectionType type)
742
{
1,182✔
743
    auto ndx = m_tree->find_key(index.get_salt());
1,182✔
744
    if (ndx == realm::not_found) {
1,182✔
UNCOV
745
        throw StaleAccessor("Collection has been deleted");
×
UNCOV
746
    }
×
747
    m_tree->set(ndx, Mixed(ref, type));
1,182✔
748
}
1,182✔
749

750
void Lst<Mixed>::add_index(Path& path, const Index& index) const
751
{
960✔
752
    auto ndx = m_tree->find_key(index.get_salt());
960✔
753
    REALM_ASSERT(ndx != realm::not_found);
960✔
754
    path.emplace_back(ndx);
960✔
755
}
960✔
756

757
size_t Lst<Mixed>::find_index(const Index& index) const
758
{
288✔
759
    update();
288✔
760
    return m_tree->find_key(index.get_salt());
288✔
761
}
288✔
762

763
bool Lst<Mixed>::nullify(ObjLink link)
764
{
84✔
765
    size_t ndx = find_first(link);
84✔
766
    if (ndx != realm::not_found) {
84✔
767
        if (Replication* repl = Base::get_replication()) {
78✔
768
            repl->list_erase(*this, ndx); // Throws
72✔
769
        }
72✔
770

39✔
771
        m_tree->erase(ndx);
78✔
772
        return true;
78✔
773
    }
78✔
774
    else {
6✔
775
        // There must be a link in a nested collection
3✔
776
        size_t sz = size();
6✔
777
        for (size_t ndx = 0; ndx < sz; ndx++) {
12✔
778
            Mixed val = m_tree->get(ndx);
12✔
779
            if (val.is_type(type_Dictionary)) {
12✔
780
                auto dict = get_dictionary(ndx);
12✔
781
                if (dict->nullify(link)) {
12✔
782
                    return true;
6✔
783
                }
6✔
784
            }
6✔
785
            if (val.is_type(type_List)) {
6✔
UNCOV
786
                auto list = get_list(ndx);
×
UNCOV
787
                if (list->nullify(link)) {
×
UNCOV
788
                    return true;
×
UNCOV
789
                }
×
UNCOV
790
            }
×
791
        }
6✔
792
    }
6✔
793
    return false;
42✔
794
}
84✔
795

796
bool Lst<Mixed>::replace_link(ObjLink old_link, ObjLink replace_link)
797
{
54✔
798
    size_t ndx = find_first(old_link);
54✔
799
    if (ndx != realm::not_found) {
54✔
800
        set(ndx, replace_link);
54✔
801
        return true;
54✔
802
    }
54✔
UNCOV
803
    else {
×
804
        // There must be a link in a nested collection
UNCOV
805
        size_t sz = size();
×
UNCOV
806
        for (size_t ndx = 0; ndx < sz; ndx++) {
×
UNCOV
807
            Mixed val = m_tree->get(ndx);
×
UNCOV
808
            if (val.is_type(type_Dictionary)) {
×
UNCOV
809
                auto dict = get_dictionary(ndx);
×
UNCOV
810
                if (dict->replace_link(old_link, replace_link)) {
×
UNCOV
811
                    return true;
×
UNCOV
812
                }
×
UNCOV
813
            }
×
UNCOV
814
            if (val.is_type(type_List)) {
×
UNCOV
815
                auto list = get_list(ndx);
×
UNCOV
816
                if (list->replace_link(old_link, replace_link)) {
×
UNCOV
817
                    return true;
×
UNCOV
818
                }
×
UNCOV
819
            }
×
UNCOV
820
        }
×
UNCOV
821
    }
×
822
    return false;
27✔
823
}
54✔
824

825
void Lst<Mixed>::remove_backlinks(CascadeState& state) const
826
{
390✔
827
    size_t sz = size();
390✔
828
    for (size_t ndx = 0; ndx < sz; ndx++) {
1,380✔
829
        Mixed val = m_tree->get(ndx);
990✔
830
        if (val.is_type(type_TypedLink)) {
990✔
831
            Base::remove_backlink(m_col_key, val.get_link(), state);
546✔
832
        }
546✔
833
        else if (val.is_type(type_List)) {
444✔
834
            get_list(ndx)->remove_backlinks(state);
18✔
835
        }
18✔
836
        else if (val.is_type(type_Dictionary)) {
426✔
837
            get_dictionary(ndx)->remove_backlinks(state);
24✔
838
        }
24✔
839
    }
990✔
840
}
390✔
841

842
bool Lst<Mixed>::update_if_needed() const
843
{
1,650✔
844
    auto status = update_if_needed_with_status();
1,650✔
845
    if (status == UpdateStatus::Detached) {
1,650✔
UNCOV
846
        throw StaleAccessor("CollectionList no longer exists");
×
UNCOV
847
    }
×
848
    return status == UpdateStatus::Updated;
1,650✔
849
}
1,650✔
850

851
/********************************** LnkLst ***********************************/
852

853
Obj LnkLst::create_and_insert_linked_object(size_t ndx)
854
{
22,566✔
855
    Table& t = *get_target_table();
22,566✔
856
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
22,566✔
857
    m_list.insert(ndx, o.get_key());
22,566✔
858
    return o;
22,566✔
859
}
22,566✔
860

861
Obj LnkLst::create_and_set_linked_object(size_t ndx)
862
{
78✔
863
    Table& t = *get_target_table();
78✔
864
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
78✔
865
    m_list.set(ndx, o.get_key());
78✔
866
    return o;
78✔
867
}
78✔
868

869
TableView LnkLst::get_sorted_view(SortDescriptor order) const
870
{
90✔
871
    TableView tv(clone_linklist());
90✔
872
    tv.do_sync();
90✔
873
    tv.sort(std::move(order));
90✔
874
    return tv;
90✔
875
}
90✔
876

877
TableView LnkLst::get_sorted_view(ColKey column_key, bool ascending) const
878
{
72✔
879
    TableView v = get_sorted_view(SortDescriptor({{column_key}}, {ascending}));
72✔
880
    return v;
72✔
881
}
72✔
882

883
void LnkLst::remove_target_row(size_t link_ndx)
884
{
30✔
885
    // Deleting the object will automatically remove all links
15✔
886
    // to it. So we do not have to manually remove the deleted link
15✔
887
    ObjKey k = get(link_ndx);
30✔
888
    get_target_table()->remove_object(k);
30✔
889
}
30✔
890

891
void LnkLst::remove_all_target_rows()
892
{
48✔
893
    if (is_attached()) {
48✔
894
        update_if_needed();
48✔
895
        _impl::TableFriend::batch_erase_rows(*get_target_table(), *m_list.m_tree);
48✔
896
    }
48✔
897
}
48✔
898

899
void LnkLst::to_json(std::ostream& out, size_t link_depth, JSONOutputMode output_mode,
900
                     util::FunctionRef<void(const Mixed&)> fn) const
901
{
408✔
902
    auto [open_str, close_str] = get_open_close_strings(link_depth, output_mode);
408✔
903

204✔
904
    out << open_str;
408✔
905
    out << "[";
408✔
906

204✔
907
    auto sz = m_list.size();
408✔
908
    for (size_t i = 0; i < sz; i++) {
762✔
909
        if (i > 0)
354✔
910
            out << ",";
126✔
911
        Mixed val(m_list.get(i));
354✔
912
        fn(val);
354✔
913
    }
354✔
914

204✔
915
    out << "]";
408✔
916
    out << close_str;
408✔
917
}
408✔
918

919
// Force instantiation:
920
template class Lst<ObjKey>;
921
template class Lst<ObjLink>;
922
template class Lst<int64_t>;
923
template class Lst<bool>;
924
template class Lst<StringData>;
925
template class Lst<BinaryData>;
926
template class Lst<Timestamp>;
927
template class Lst<float>;
928
template class Lst<double>;
929
template class Lst<Decimal128>;
930
template class Lst<ObjectId>;
931
template class Lst<UUID>;
932
template class Lst<util::Optional<int64_t>>;
933
template class Lst<util::Optional<bool>>;
934
template class Lst<util::Optional<float>>;
935
template class Lst<util::Optional<double>>;
936
template class Lst<util::Optional<ObjectId>>;
937
template class Lst<util::Optional<UUID>>;
938

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