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

realm / realm-core / jorgen.edelbo_337

03 Jul 2024 01:04PM UTC coverage: 90.864% (-0.1%) from 90.984%
jorgen.edelbo_337

Pull #7826

Evergreen

nicola-cab
Merge branch 'master' of github.com:realm/realm-core into next-major
Pull Request #7826: Merge Next major

102968 of 181176 branches covered (56.83%)

3131 of 3738 new or added lines in 54 files covered. (83.76%)

106 existing lines in 23 files now uncovered.

217725 of 239616 relevant lines covered (90.86%)

6844960.2 hits per line

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

87.37
/src/realm/list.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19

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

41
namespace realm {
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

292
    CascadeState state;
3,138✔
293

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

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

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

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

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

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

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

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

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

340
    m_tree->erase(ndx);
×
341

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

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

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

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

362
    return *this;
×
363
}
×
364

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

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

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

380

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

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

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

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

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

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

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

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

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

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

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

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

515
        m_tree->clear();
678✔
516

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

999
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc