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

realm / realm-core / 2093

02 Mar 2024 12:43AM UTC coverage: 90.92%. Remained the same
2093

push

Evergreen

web-flow
Use updated curl on evergreen windows hosts (#7409)

93896 of 173116 branches covered (54.24%)

238389 of 262196 relevant lines covered (90.92%)

5713411.51 hits per line

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

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

19

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

41
namespace realm {
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1,377✔
307
    tf::remove_recursive(*origin_table, state); // Throws
2,757✔
308
}
2,757✔
309

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

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

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

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

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

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

340
    m_tree->erase(ndx);
×
341

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

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

350
bool Lst<Mixed>::init_from_parent(bool allow_create) const
351
{
23,286✔
352
    if (!m_tree) {
23,286✔
353
        m_tree.reset(new BPlusTreeMixed(get_alloc()));
17,340✔
354
        const ArrayParent* parent = this;
17,340✔
355
        m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
17,340✔
356
    }
17,340✔
357
    try {
23,286✔
358
        auto ref = Base::get_collection_ref();
23,286✔
359
        if (ref) {
23,286✔
360
            m_tree->init_from_ref(ref);
14,490✔
361
        }
14,490✔
362
        else {
8,796✔
363
            if (!allow_create) {
8,796✔
364
                m_tree->detach();
5,010✔
365
                return false;
5,010✔
366
            }
5,010✔
367

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

9,159✔
378
    return true;
18,240✔
379
}
18,240✔
380

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

12✔
575
    check_level();
24✔
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2025 Coveralls, Inc