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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

88.11
/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!
91
        return first;
×
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;
×
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✔
NEW
139
            fn(val);
×
NEW
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,407,292✔
177
    auto origin_table = get_table_unchecked();
6,407,292✔
178
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
6,407,292✔
179
    set_backlink(m_col_key, {target_table_key, target_key});
6,407,292✔
180
    m_tree->insert(ndx, target_key);
6,407,292✔
181
    if (target_key.is_unresolved()) {
6,407,292✔
182
        m_tree->set_context_flag(true);
9,084✔
183
    }
9,084✔
184
}
6,407,292✔
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,631✔
210
    auto origin_table = get_table_unchecked();
2,631✔
211
    TableRef target_table = get_obj().get_target_table(m_col_key);
2,631✔
212

1,317✔
213
    size_t sz = size();
2,631✔
214
    if (!target_table->is_embedded()) {
2,631✔
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,119✔
224
    TableKey target_table_key = target_table->get_key();
2,235✔
225
    ColKey backlink_col = origin_table->get_opposite_column(m_col_key);
2,235✔
226

1,119✔
227
    CascadeState state;
2,235✔
228

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

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

1,119✔
242
    tf::remove_recursive(*origin_table, state); // Throws
2,235✔
243
}
2,235✔
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);
×
NEW
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) {
×
NEW
255
        auto origin_table = get_table_unchecked();
×
256
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
257
    }
×
UNCOV
258
}
×
259

260
template <>
261
void Lst<ObjLink>::do_insert(size_t ndx, ObjLink target_link)
UNCOV
262
{
×
NEW
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

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

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

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

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

285
bool Lst<Mixed>::init_from_parent(bool allow_create) const
286
{
19,032✔
287
    if (!m_tree) {
19,032✔
288
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
13,566✔
289
        const ArrayParent* parent = this;
13,566✔
290
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
13,566✔
291
    }
13,566✔
292
    try {
19,032✔
293
        auto ref = Base::get_collection_ref();
19,032✔
294
        if (ref) {
19,032✔
295
            m_tree->init_from_ref(ref);
10,626✔
296
        }
10,626✔
297
        else {
8,406✔
298
            if (!allow_create) {
8,406✔
299
                m_tree->detach();
5,106✔
300
                return false;
5,106✔
301
            }
5,106✔
302

1,650✔
303
            // The ref in the column was NULL, create the tree in place.
1,650✔
304
            m_tree->create();
3,300✔
305
            REALM_ASSERT(m_tree->is_attached());
3,300✔
306
        }
3,300✔
307
    }
19,032✔
308
    catch (...) {
9,588✔
309
        m_tree->detach();
30✔
310
        throw;
30✔
311
    }
30✔
312

7,005✔
313
    return true;
13,896✔
314
}
13,896✔
315

316
UpdateStatus Lst<Mixed>::update_if_needed_with_status() const
317
{
78,888✔
318
    auto status = Base::get_update_status();
78,888✔
319
    switch (status) {
78,888✔
320
        case UpdateStatus::Detached: {
6✔
321
            m_tree.reset();
6✔
322
            return UpdateStatus::Detached;
6✔
NEW
323
        }
×
324
        case UpdateStatus::NoChange:
64,056✔
325
            if (m_tree && m_tree->is_attached()) {
64,056✔
326
                return UpdateStatus::NoChange;
63,252✔
327
            }
63,252✔
328
            // The tree has not been initialized yet for this accessor, so
405✔
329
            // perform lazy initialization by treating it as an update.
405✔
330
            [[fallthrough]];
804✔
331
        case UpdateStatus::Updated: {
15,630✔
332
            bool attached = init_from_parent(false);
15,630✔
333
            Base::update_content_version();
15,630✔
334
            return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
13,065✔
NEW
335
        }
×
NEW
336
    }
×
NEW
337
    REALM_UNREACHABLE();
×
NEW
338
}
×
339

340
size_t Lst<Mixed>::find_first(const Mixed& value) const
341
{
1,254✔
342
    if (!update())
1,254✔
NEW
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
{
846✔
359
    // get will check for ndx out of bounds
423✔
360
    Mixed old = do_get(ndx, "set()");
846✔
361
    if (Replication* repl = Base::get_replication()) {
846✔
362
        repl->list_set(*this, ndx, value);
786✔
363
    }
786✔
364
    if (!(old.is_same_type(value) && old == value)) {
846✔
365
        do_set(ndx, value);
702✔
366
        bump_content_version();
702✔
367
    }
702✔
368
    return old;
846✔
369
}
846✔
370

371
void Lst<Mixed>::insert(size_t ndx, Mixed value)
372
{
12,372✔
373
    ensure_created();
12,372✔
374
    auto sz = size();
12,372✔
375
    CollectionBase::validate_index("insert()", ndx, sz + 1);
12,372✔
376
    if (Replication* repl = Base::get_replication()) {
12,372✔
377
        repl->list_insert(*this, ndx, value, sz);
10,860✔
378
    }
10,860✔
379
    do_insert(ndx, value);
12,372✔
380
    bump_content_version();
12,372✔
381
}
12,372✔
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✔
NEW
388
            insert_null(current_size++);
×
NEW
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,116✔
397
    // get will check for ndx out of bounds
558✔
398
    Mixed old = do_get(ndx, "remove()");
1,116✔
399
    if (Replication* repl = Base::get_replication()) {
1,116✔
400
        repl->list_erase(*this, ndx);
1,092✔
401
    }
1,092✔
402

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

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

415
void Lst<Mixed>::clear()
416
{
282✔
417
    if (size() > 0) {
282✔
418
        if (Replication* repl = Base::get_replication()) {
234✔
419
            repl->list_clear(*this);
222✔
420
        }
222✔
421
        CascadeState state;
234✔
422
        bool recurse = remove_backlinks(state);
234✔
423

117✔
424
        m_tree->clear();
234✔
425

117✔
426
        if (recurse) {
234✔
NEW
427
            auto table = get_table_unchecked();
×
NEW
428
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
NEW
429
        }
×
430
        bump_content_version();
234✔
431
    }
234✔
432
}
282✔
433

434
void Lst<Mixed>::move(size_t from, size_t to)
435
{
162✔
436
    auto sz = size();
162✔
437
    CollectionBase::validate_index("move()", from, sz);
162✔
438
    CollectionBase::validate_index("move()", to, sz);
162✔
439

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

81✔
458
        bump_content_version();
162✔
459
    }
162✔
460
}
162✔
461

462
void Lst<Mixed>::swap(size_t ndx1, size_t ndx2)
463
{
18✔
464
    auto sz = size();
18✔
465
    CollectionBase::validate_index("swap()", ndx1, sz);
18✔
466
    CollectionBase::validate_index("swap()", ndx2, sz);
18✔
467

9✔
468
    if (ndx1 != ndx2) {
18✔
469
        if (Replication* repl = Base::get_replication()) {
18✔
470
            LstBase::swap_repl(repl, ndx1, ndx2);
12✔
471
        }
12✔
472
        m_tree->swap(ndx1, ndx2);
18✔
473
        bump_content_version();
18✔
474
    }
18✔
475
}
18✔
476

477
void Lst<Mixed>::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
478
{
348✔
479
    ensure_created();
348✔
480
    check_level();
348✔
481
    m_tree->ensure_keys();
348✔
482
    insert(path_elem.get_ndx(), Mixed(0, dict_or_list));
348✔
483
    int64_t key = generate_key(size());
348✔
484
    while (m_tree->find_key(key) != realm::not_found) {
348✔
NEW
485
        key++;
×
NEW
486
    }
×
487
    m_tree->set_key(path_elem.get_ndx(), key);
348✔
488
    bump_content_version();
348✔
489
}
348✔
490

491
void Lst<Mixed>::set_collection(const PathElement& path_elem, CollectionType type)
492
{
30✔
493
    auto ndx = path_elem.get_ndx();
30✔
494
    // get will check for ndx out of bounds
15✔
495
    Mixed old_val = do_get(ndx, "set_collection()");
30✔
496
    Mixed new_val(0, type);
30✔
497

15✔
498
    check_level();
30✔
499

15✔
500
    if (old_val != new_val) {
30✔
501
        m_tree->ensure_keys();
24✔
502
        set(ndx, Mixed(0, type));
24✔
503
        int64_t key = m_tree->get_key(ndx);
24✔
504
        if (key == 0) {
24✔
505
            key = generate_key(size());
24✔
506
            while (m_tree->find_key(key) != realm::not_found) {
24✔
NEW
507
                key++;
×
NEW
508
            }
×
509
            m_tree->set_key(ndx, key);
24✔
510
        }
24✔
511
        bump_content_version();
24✔
512
    }
24✔
513
}
30✔
514

515
DictionaryPtr Lst<Mixed>::get_dictionary(const PathElement& path_elem) const
516
{
228✔
517
    update();
228✔
518
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
228✔
519
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
189✔
520
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
228✔
521
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
228✔
522
    return ret;
228✔
523
}
228✔
524

525
SetMixedPtr Lst<Mixed>::get_set(const PathElement& path_elem) const
526
{
42✔
527
    update();
42✔
528
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
42✔
529
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
42✔
530
    auto ret = std::make_shared<Set<Mixed>>(m_obj_mem, m_col_key);
42✔
531
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
42✔
532
    return ret;
42✔
533
}
42✔
534

535
std::shared_ptr<Lst<Mixed>> Lst<Mixed>::get_list(const PathElement& path_elem) const
536
{
312✔
537
    update();
312✔
538
    auto weak = const_cast<Lst<Mixed>*>(this)->weak_from_this();
312✔
539
    auto shared = weak.expired() ? std::make_shared<Lst<Mixed>>(*this) : weak.lock();
300✔
540
    std::shared_ptr<Lst<Mixed>> ret = std::make_shared<Lst<Mixed>>(m_col_key, get_level() + 1);
312✔
541
    ret->set_owner(shared, m_tree->get_key(path_elem.get_ndx()));
312✔
542
    return ret;
312✔
543
}
312✔
544

545
void Lst<Mixed>::do_set(size_t ndx, Mixed value)
546
{
702✔
547
    ObjLink old_link;
702✔
548
    ObjLink target_link;
702✔
549
    Mixed old_value = m_tree->get(ndx);
702✔
550

351✔
551
    if (old_value.is_type(type_TypedLink)) {
702✔
552
        old_link = old_value.get<ObjLink>();
210✔
553
    }
210✔
554
    if (value.is_type(type_TypedLink)) {
702✔
555
        target_link = value.get<ObjLink>();
204✔
556
        get_table_unchecked()->get_parent_group()->validate(target_link);
204✔
557
    }
204✔
558

351✔
559
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
699✔
560
    bool recurse = Base::replace_backlink(m_col_key, old_link, target_link, state);
702✔
561

351✔
562
    m_tree->set(ndx, value);
702✔
563

351✔
564
    if (recurse) {
702✔
NEW
565
        auto origin_table = get_table_unchecked();
×
566
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
567
    }
×
568
}
702✔
569

570
void Lst<Mixed>::do_insert(size_t ndx, Mixed value)
571
{
12,366✔
572
    if (value.is_type(type_TypedLink)) {
12,366✔
573
        Base::set_backlink(m_col_key, value.get<ObjLink>());
5,244✔
574
    }
5,244✔
575

6,183✔
576
    m_tree->insert(ndx, value);
12,366✔
577
}
12,366✔
578

579
void Lst<Mixed>::do_remove(size_t ndx)
580
{
1,116✔
581
    CascadeState state;
1,116✔
582
    bool recurse = clear_backlink(ndx, state);
1,116✔
583

558✔
584
    m_tree->erase(ndx);
1,116✔
585

558✔
586
    if (recurse) {
1,116✔
NEW
587
        auto table = get_table_unchecked();
×
NEW
588
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
NEW
589
    }
×
590
}
1,116✔
591

592
void Lst<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const
593
{
234✔
594
    update();
234✔
595

117✔
596
    auto tree = m_tree.get();
234✔
597
    if (ascending) {
234✔
598
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
13,683✔
599
            return unresolved_to_null(tree->get(i1)) < unresolved_to_null(tree->get(i2));
13,683✔
600
        });
13,683✔
601
    }
210✔
602
    else {
24✔
603
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
2,451✔
604
            return unresolved_to_null(tree->get(i1)) > unresolved_to_null(tree->get(i2));
2,451✔
605
        });
2,451✔
606
    }
24✔
607
}
234✔
608

609
void Lst<Mixed>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
610
{
30✔
611
    indices.clear();
30✔
612
    sort(indices, sort_order.value_or(true));
30✔
613
    if (indices.empty()) {
30✔
NEW
614
        return;
×
NEW
615
    }
×
616

15✔
617
    auto tree = m_tree.get();
30✔
618
    auto duplicates = min_unique(indices.begin(), indices.end(), [tree](size_t i1, size_t i2) noexcept {
666✔
619
        return unresolved_to_null(tree->get(i1)) == unresolved_to_null(tree->get(i2));
666✔
620
    });
666✔
621

15✔
622
    // Erase the duplicates
15✔
623
    indices.erase(duplicates, indices.end());
30✔
624

15✔
625
    if (!sort_order) {
30✔
626
        // Restore original order
15✔
627
        std::sort(indices.begin(), indices.end());
30✔
628
    }
30✔
629
}
30✔
630

631
util::Optional<Mixed> Lst<Mixed>::min(size_t* return_ndx) const
632
{
96✔
633
    if (update()) {
96✔
634
        return MinHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
635
    }
84✔
636
    return MinHelper<Mixed>::not_found(return_ndx);
12✔
637
}
12✔
638

639
util::Optional<Mixed> Lst<Mixed>::max(size_t* return_ndx) const
640
{
96✔
641
    if (update()) {
96✔
642
        return MaxHelper<Mixed>::eval(*m_tree, return_ndx);
84✔
643
    }
84✔
644
    return MaxHelper<Mixed>::not_found(return_ndx);
12✔
645
}
12✔
646

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

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

663
void Lst<Mixed>::to_json(std::ostream& out, size_t link_depth, JSONOutputMode output_mode,
664
                         util::FunctionRef<void(const Mixed&)> fn) const
665
{
78✔
666
    auto [open_str, close_str] = get_open_close_strings(link_depth, output_mode);
78✔
667

39✔
668
    out << open_str;
78✔
669
    out << "[";
78✔
670

39✔
671
    auto sz = size();
78✔
672
    for (size_t i = 0; i < sz; i++) {
276✔
673
        if (i > 0)
198✔
674
            out << ",";
120✔
675
        Mixed val = m_tree->get(i);
198✔
676
        if (val.is_type(type_TypedLink)) {
198✔
677
            fn(val);
48✔
678
        }
48✔
679
        else if (val.is_type(type_Dictionary)) {
150✔
680
            DummyParent parent(this->get_table(), val.get_ref());
24✔
681
            Dictionary dict(parent, i);
24✔
682
            dict.to_json(out, link_depth, output_mode, fn);
24✔
683
        }
24✔
684
        else if (val.is_type(type_List)) {
126✔
685
            DummyParent parent(this->get_table(), val.get_ref());
24✔
686
            Lst<Mixed> list(parent, i);
24✔
687
            list.to_json(out, link_depth, output_mode, fn);
24✔
688
        }
24✔
689
        else if (val.is_type(type_Set)) {
102✔
690
            DummyParent parent(this->get_table(), val.get_ref());
6✔
691
            Set<Mixed> set(parent, 0);
6✔
692
            set.to_json(out, link_depth, output_mode, fn);
6✔
693
        }
6✔
694
        else {
96✔
695
            val.to_json(out, output_mode);
96✔
696
        }
96✔
697
    }
198✔
698

39✔
699
    out << "]";
78✔
700
    out << close_str;
78✔
701
}
78✔
702

703
ref_type Lst<Mixed>::get_collection_ref(Index index, CollectionType type) const
704
{
1,002✔
705
    auto ndx = m_tree->find_key(index.get_salt());
1,002✔
706
    if (ndx != realm::not_found) {
1,002✔
707
        auto val = get(ndx);
1,002✔
708
        if (val.is_type(DataType(int(type)))) {
1,002✔
709
            return val.get_ref();
1,002✔
710
        }
1,002✔
NEW
711
        throw realm::IllegalOperation(util::format("Not a %1", type));
×
NEW
712
    }
×
NEW
713
    throw StaleAccessor("This collection is no more");
×
NEW
714
    return 0;
×
NEW
715
}
×
716

717
bool Lst<Mixed>::check_collection_ref(Index index, CollectionType type) const noexcept
718
{
582✔
719
    auto ndx = m_tree->find_key(index.get_salt());
582✔
720
    if (ndx != realm::not_found) {
582✔
721
        return get(ndx).is_type(DataType(int(type)));
546✔
722
    }
546✔
723
    return false;
36✔
724
}
36✔
725

726
void Lst<Mixed>::set_collection_ref(Index index, ref_type ref, CollectionType type)
727
{
276✔
728
    auto ndx = m_tree->find_key(index.get_salt());
276✔
729
    if (ndx == realm::not_found) {
276✔
NEW
730
        throw StaleAccessor("Collection has been deleted");
×
NEW
731
    }
×
732
    m_tree->set(ndx, Mixed(ref, type));
276✔
733
}
276✔
734

735
void Lst<Mixed>::add_index(Path& path, const Index& index) const
736
{
12✔
737
    auto ndx = m_tree->find_key(index.get_salt());
12✔
738
    REALM_ASSERT(ndx != realm::not_found);
12✔
739
    path.emplace_back(ndx);
12✔
740
}
12✔
741

742
size_t Lst<Mixed>::find_index(const Index& index) const
743
{
144✔
744
    update();
144✔
745
    return m_tree->find_key(index.get_salt());
144✔
746
}
144✔
747

748
bool Lst<Mixed>::nullify(ObjLink link)
749
{
84✔
750
    size_t ndx = find_first(link);
84✔
751
    if (ndx != realm::not_found) {
84✔
752
        if (Replication* repl = Base::get_replication()) {
78✔
753
            repl->list_erase(*this, ndx); // Throws
72✔
754
        }
72✔
755

39✔
756
        m_tree->erase(ndx);
78✔
757
        return true;
78✔
758
    }
78✔
759
    else {
6✔
760
        // There must be a link in a nested collection
3✔
761
        size_t sz = size();
6✔
762
        for (size_t ndx = 0; ndx < sz; ndx++) {
12✔
763
            Mixed val = m_tree->get(ndx);
12✔
764
            if (val.is_type(type_Dictionary)) {
12✔
765
                auto dict = get_dictionary(ndx);
12✔
766
                if (dict->nullify(link)) {
12✔
767
                    return true;
6✔
768
                }
6✔
769
            }
6✔
770
            if (val.is_type(type_List)) {
6✔
NEW
771
                auto list = get_list(ndx);
×
NEW
772
                if (list->nullify(link)) {
×
NEW
773
                    return true;
×
NEW
774
                }
×
NEW
775
            }
×
776
        }
6✔
777
    }
6✔
778
    return false;
42✔
779
}
84✔
780

781
bool Lst<Mixed>::replace_link(ObjLink old_link, ObjLink replace_link)
782
{
54✔
783
    size_t ndx = find_first(old_link);
54✔
784
    if (ndx != realm::not_found) {
54✔
785
        set(ndx, replace_link);
54✔
786
        return true;
54✔
787
    }
54✔
NEW
788
    else {
×
789
        // There must be a link in a nested collection
NEW
790
        size_t sz = size();
×
NEW
791
        for (size_t ndx = 0; ndx < sz; ndx++) {
×
NEW
792
            Mixed val = m_tree->get(ndx);
×
NEW
793
            if (val.is_type(type_Dictionary)) {
×
NEW
794
                auto dict = get_dictionary(ndx);
×
NEW
795
                if (dict->replace_link(old_link, replace_link)) {
×
NEW
796
                    return true;
×
NEW
797
                }
×
NEW
798
            }
×
NEW
799
            if (val.is_type(type_List)) {
×
NEW
800
                auto list = get_list(ndx);
×
NEW
801
                if (list->replace_link(old_link, replace_link)) {
×
NEW
802
                    return true;
×
NEW
803
                }
×
NEW
804
            }
×
NEW
805
        }
×
NEW
806
    }
×
807
    return false;
27✔
808
}
54✔
809

810
bool Lst<Mixed>::clear_backlink(size_t ndx, CascadeState& state) const
811
{
3,756✔
812
    Mixed value = m_tree->get(ndx);
3,756✔
813
    if (value.is_type(type_TypedLink, type_Dictionary, type_List)) {
3,756✔
814
        if (value.is_type(type_TypedLink)) {
1,824✔
815
            auto link = value.get<ObjLink>();
1,782✔
816
            if (link.get_obj_key().is_unresolved()) {
1,782✔
817
                state.m_mode = CascadeState::Mode::All;
6✔
818
            }
6✔
819
            return Base::remove_backlink(m_col_key, link, state);
1,782✔
820
        }
1,782✔
821
        else if (value.is_type(type_List)) {
42✔
822
            return get_list(ndx)->remove_backlinks(state);
12✔
823
        }
12✔
824
        else if (value.is_type(type_Dictionary)) {
30✔
825
            return get_dictionary(ndx)->remove_backlinks(state);
30✔
826
        }
30✔
827
    }
1,932✔
828
    return false;
1,932✔
829
}
1,932✔
830

831
bool Lst<Mixed>::remove_backlinks(CascadeState& state) const
832
{
504✔
833
    size_t sz = size();
504✔
834
    bool recurse = false;
504✔
835
    for (size_t ndx = 0; ndx < sz; ndx++) {
3,144✔
836
        if (clear_backlink(ndx, state)) {
2,640✔
NEW
837
            recurse = true;
×
NEW
838
        }
×
839
    }
2,640✔
840
    return recurse;
504✔
841
}
504✔
842

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

852
/********************************** LnkLst ***********************************/
853

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

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

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

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

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

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

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

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

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

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

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

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