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

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 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
{
2,787✔
48
    auto old_size = indices.size();
2,787✔
49
    indices.reserve(size);
2,787✔
50
    if (size < old_size) {
2,787✔
51
        // If list size has decreased, we have to start all over
52
        indices.clear();
54✔
53
        old_size = 0;
54✔
54
    }
54✔
55
    for (size_t i = old_size; i < size; i++) {
17,214✔
56
        // If list size has increased, just add the missing indices
57
        indices.push_back(i);
14,427✔
58
    }
14,427✔
59

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

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

71
    auto tree = m_tree.get();
2,670✔
72
    if (ascending) {
2,670!
73
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
28,632✔
74
            return tree->get(i1) < tree->get(i2);
28,632✔
75
        });
28,632✔
76
    }
2,250✔
77
    else {
420✔
78
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
6,936✔
79
            return tree->get(i1) > tree->get(i2);
6,936✔
80
        });
6,936✔
81
    }
420✔
82
}
2,670✔
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
{
429✔
91
    if (first == last) {
429!
92
        return first;
×
93
    }
×
94

95
    Iterator result = first;
429✔
96
    while (++first != last) {
3,396!
97
        bool equal = pred(*result, *first);
2,967✔
98
        if ((equal && *result > *first) || (!equal && ++result != first))
2,967!
99
            *result = *first;
1,413✔
100
    }
2,967✔
101
    return ++result;
429✔
102
}
429✔
103

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

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

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

121
    if (!sort_order) {
414!
122
        // Restore original order
123
        std::sort(indices.begin(), indices.end());
294✔
124
    }
294✔
125
}
414✔
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
{
318✔
133
    auto sz = size();
318✔
134
    out << "[";
318✔
135
    for (size_t i = 0; i < sz; i++) {
900✔
136
        if (i > 0)
582✔
137
            out << ",";
339✔
138
        Mixed val = get_any(i);
582✔
139
        if (val.is_type(type_Link, type_TypedLink)) {
582✔
140
            fn(val);
42✔
141
        }
42✔
142
        else {
540✔
143
            val.to_json(out, output_mode);
540✔
144
        }
540✔
145
    }
582✔
146
    out << "]";
318✔
147
}
318✔
148

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

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

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

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

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

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

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

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

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

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

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

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

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

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

261
    m_tree->erase(ndx);
1,392✔
262

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

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

278
    size_t sz = size();
2,127✔
279
    if (!target_table->is_embedded()) {
2,127✔
280
        size_t ndx = sz;
198✔
281
        while (ndx--) {
20,508✔
282
            do_set(ndx, null_key);
20,310✔
283
            m_tree->erase(ndx);
20,310✔
284
        }
20,310✔
285
        m_tree->set_context_flag(false);
198✔
286
        return;
198✔
287
    }
198✔
288

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

292
    CascadeState state;
1,929✔
293

294
    typedef _impl::TableFriend tf;
1,929✔
295
    for (size_t ndx = 0; ndx < sz; ++ndx) {
5,322✔
296
        ObjKey target_key = m_tree->get(ndx);
3,393✔
297
        Obj target_obj = target_table->get_object(target_key);
3,393✔
298
        target_obj.remove_one_backlink(backlink_col, get_obj().get_key()); // Throws
3,393✔
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,393✔
301
        state.m_to_be_deleted.emplace_back(target_table_key, target_key);
3,393✔
302
    }
3,393✔
303

304
    m_tree->clear();
1,929✔
305
    m_tree->set_context_flag(false);
1,929✔
306

307
    tf::remove_recursive(*origin_table, state); // Throws
1,929✔
308
}
1,929✔
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
{
9✔
367
    if (this != &other) {
9✔
368
        Base::operator=(std::move(other));
9✔
369
        CollectionParent::operator=(std::move(other));
9✔
370

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

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

380

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

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

399
UpdateStatus Lst<Mixed>::update_if_needed() const
400
{
1,520,103✔
401
    switch (get_update_status()) {
1,520,103✔
402
        case UpdateStatus::Detached:
24✔
403
            m_tree.reset();
24✔
404
            return UpdateStatus::Detached;
24✔
405
        case UpdateStatus::NoChange:
1,159,158✔
406
            if (m_tree && m_tree->is_attached()) {
1,159,323✔
407
                return UpdateStatus::NoChange;
1,157,631✔
408
            }
1,157,631✔
409
            // The tree has not been initialized yet for this accessor, so
410
            // perform lazy initialization by treating it as an update.
411
            [[fallthrough]];
1,159,158✔
412
        case UpdateStatus::Updated:
362,250✔
413
            return init_from_parent(false);
362,250✔
414
    }
1,520,103✔
415
    REALM_UNREACHABLE();
416
}
×
417

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

423
    if (value.is_null()) {
891✔
424
        auto ndx = m_tree->find_first(value);
36✔
425
        auto size = ndx == not_found ? m_tree->size() : ndx;
36✔
426
        for (size_t i = 0; i < size; ++i) {
54✔
427
            if (m_tree->get(i).is_unresolved_link())
30✔
428
                return i;
12✔
429
        }
30✔
430
        return ndx;
24✔
431
    }
36✔
432
    return m_tree->find_first(value);
855✔
433
}
891✔
434

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

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

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

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

492
    do_remove(ndx);
741✔
493
    bump_content_version();
741✔
494
    return old;
741✔
495
}
741✔
496

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

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

515
        m_tree->clear();
339✔
516

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

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

531
    if (from != to) {
111✔
532
        if (Replication* repl = Base::get_replication()) {
111✔
533
            repl->list_move(*this, from, to);
108✔
534
        }
108✔
535
        if (to > from) {
111✔
536
            to++;
78✔
537
        }
78✔
538
        else {
33✔
539
            from++;
33✔
540
        }
33✔
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());
111✔
546
        m_tree->swap(from, to);
111✔
547
        m_tree->erase(from);
111✔
548

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

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

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

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

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

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

593
        if (weak.expired()) {
50,466✔
594
            REALM_ASSERT_DEBUG(m_level == 1);
48,930✔
595
            return std::make_shared<Lst<Mixed>>(*this);
48,930✔
596
        }
48,930✔
597

598
        return weak.lock();
1,536✔
599
    };
50,466✔
600

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

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

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

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

623
    if (old_value.is_type(type_TypedLink)) {
741✔
624
        old_link = old_value.get<ObjLink>();
219✔
625
    }
219✔
626
    if (value.is_type(type_TypedLink)) {
741✔
627
        target_link = value.get<ObjLink>();
216✔
628
        get_table_unchecked()->get_parent_group()->validate(target_link);
216✔
629
    }
216✔
630

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

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

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

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

648
    m_tree->insert(ndx, value);
190,074✔
649
}
190,074✔
650

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

656
    m_tree->erase(ndx);
741✔
657

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

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

668
    auto tree = m_tree.get();
117✔
669
    if (ascending) {
117✔
670
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
7,518✔
671
            return unresolved_to_null(tree->get(i1)) < unresolved_to_null(tree->get(i2));
7,518✔
672
        });
7,518✔
673
    }
105✔
674
    else {
12✔
675
        do_sort(indices, size(), [tree](size_t i1, size_t i2) {
1,140✔
676
            return unresolved_to_null(tree->get(i1)) > unresolved_to_null(tree->get(i2));
1,140✔
677
        });
1,140✔
678
    }
12✔
679
}
117✔
680

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

844
bool Lst<Mixed>::replace_link(ObjLink old_link, ObjLink replace_link)
845
{
69✔
846
    size_t ndx = find_first(old_link);
69✔
847
    if (ndx != realm::not_found) {
69✔
848
        set(ndx, replace_link);
69✔
849
        return true;
69✔
850
    }
69✔
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
}
69✔
872

873
bool Lst<Mixed>::clear_backlink(size_t ndx, CascadeState& state) const
874
{
182,748✔
875
    Mixed value = m_tree->get(ndx);
182,748✔
876
    if (value.is_type(type_TypedLink, type_Dictionary, type_List)) {
182,748✔
877
        if (value.is_type(type_TypedLink)) {
97,155✔
878
            auto link = value.get<ObjLink>();
73,020✔
879
            return Base::remove_backlink(m_col_key, link, state);
73,020✔
880
        }
73,020✔
881
        else if (value.is_type(type_List)) {
24,135✔
882
            Lst<Mixed> list{*const_cast<Lst<Mixed>*>(this), m_tree->get_key(ndx)};
24,096✔
883
            return list.remove_backlinks(state);
24,096✔
884
        }
24,096✔
885
        else if (value.is_type(type_Dictionary)) {
39✔
886
            Dictionary dict{*const_cast<Lst<Mixed>*>(this), m_tree->get_key(ndx)};
39✔
887
            return dict.remove_backlinks(state);
39✔
888
        }
39✔
889
    }
97,155✔
890
    return false;
85,593✔
891
}
182,748✔
892

893
bool Lst<Mixed>::remove_backlinks(CascadeState& state) const
894
{
78,885✔
895
    size_t sz = size();
78,885✔
896
    bool recurse = false;
78,885✔
897
    for (size_t ndx = 0; ndx < sz; ndx++) {
260,892✔
898
        if (clear_backlink(ndx, state)) {
182,007✔
899
            recurse = true;
12,000✔
900
        }
12,000✔
901
    }
182,007✔
902
    return recurse;
78,885✔
903
}
78,885✔
904

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

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

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

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

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

937
void LnkLst::remove_target_row(size_t link_ndx)
938
{
15✔
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);
15✔
942
    get_target_table()->remove_object(k);
15✔
943
}
15✔
944

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

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

958
void LnkLst::replace_link(ObjKey old_val, ObjKey new_val)
959
{
13,707✔
960
    update_if_needed();
13,707✔
961
    auto tree = m_list.m_tree.get();
13,707✔
962
    auto n = tree->find_first(old_val);
13,707✔
963
    REALM_ASSERT(n != realm::npos);
13,707✔
964
    if (Replication* repl = get_obj().get_replication()) {
13,707✔
965
        repl->list_set(m_list, n, new_val);
13,671✔
966
    }
13,671✔
967
    tree->set(n, new_val);
13,707✔
968
    m_list.bump_content_version();
13,707✔
969
    if (new_val.is_unresolved()) {
13,707✔
970
        if (!old_val.is_unresolved()) {
183✔
971
            tree->set_context_flag(true);
183✔
972
        }
183✔
973
    }
183✔
974
    else {
13,524✔
975
        _impl::check_for_last_unresolved(tree);
13,524✔
976
    }
13,524✔
977
}
13,707✔
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