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

realm / realm-core / nicola.cabiddu_1040

26 Sep 2023 05:08PM UTC coverage: 91.056% (-1.9%) from 92.915%
nicola.cabiddu_1040

Pull #6766

Evergreen

nicola-cab
several fixes and final client reset algo for collection in mixed
Pull Request #6766: Client Reset for collections in mixed / nested collections

97128 of 178458 branches covered (0.0%)

1524 of 1603 new or added lines in 5 files covered. (95.07%)

4511 existing lines in 109 files now uncovered.

236619 of 259862 relevant lines covered (91.06%)

7169640.31 hits per line

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

85.39
/src/realm/dictionary.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2019 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
#include <realm/dictionary.hpp>
20
#include <realm/aggregate_ops.hpp>
21
#include <realm/array_mixed.hpp>
22
#include <realm/array_ref.hpp>
23
#include <realm/group.hpp>
24
#include <realm/list.hpp>
25
#include <realm/set.hpp>
26
#include <realm/replication.hpp>
27

28
#include <algorithm>
29

30
namespace realm {
31

32
namespace {
33
void validate_key_value(const Mixed& key)
34
{
116,712✔
35
    if (key.is_type(type_String)) {
116,712✔
36
        auto str = key.get_string();
116,712✔
37
        if (str.size()) {
116,712✔
38
            if (str[0] == '$')
116,682✔
39
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not start with '$'");
12✔
40
            if (memchr(str.data(), '.', str.size()))
116,670✔
41
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not contain '.'");
12✔
42
        }
116,670✔
43
    }
116,712✔
44
}
116,712✔
45

46
} // namespace
47

48

49
/******************************** Dictionary *********************************/
50

51
Dictionary::Dictionary(ColKey col_key, size_t level)
52
    : Base(col_key)
53
    , CollectionParent(level)
54
{
161,088✔
55
    if (!(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed)) {
161,088✔
UNCOV
56
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a dictionary");
×
UNCOV
57
    }
×
58
}
161,088✔
59

60
Dictionary::Dictionary(Allocator& alloc, ColKey col_key, ref_type ref)
61
    : Base(Obj{}, col_key)
62
    , m_key_type(type_String)
63
{
27,900✔
64
    set_alloc(alloc);
27,900✔
65
    REALM_ASSERT(ref);
27,900✔
66
    m_dictionary_top.reset(new Array(alloc));
27,900✔
67
    m_dictionary_top->init_from_ref(ref);
27,900✔
68
    m_keys.reset(new BPlusTree<StringData>(alloc));
27,900✔
69
    m_values.reset(new BPlusTreeMixed(alloc));
27,900✔
70
    m_keys->set_parent(m_dictionary_top.get(), 0);
27,900✔
71
    m_values->set_parent(m_dictionary_top.get(), 1);
27,900✔
72
    m_keys->init_from_parent();
27,900✔
73
    m_values->init_from_parent();
27,900✔
74
}
27,900✔
75

76
Dictionary::~Dictionary() = default;
195,024✔
77

78
Dictionary& Dictionary::operator=(const Dictionary& other)
79
{
5,802✔
80
    Base::operator=(static_cast<const Base&>(other));
5,802✔
81

2,901✔
82
    if (this != &other) {
5,802✔
83
        // Back to scratch
2,901✔
84
        m_dictionary_top.reset();
5,802✔
85
        reset_content_version();
5,802✔
86
    }
5,802✔
87

2,901✔
88
    return *this;
5,802✔
89
}
5,802✔
90

91
size_t Dictionary::size() const
92
{
383,712✔
93
    if (!update())
383,712✔
94
        return 0;
41,148✔
95

171,129✔
96
    return m_values->size();
342,564✔
97
}
342,564✔
98

99
DataType Dictionary::get_key_data_type() const
100
{
17,880✔
101
    return m_key_type;
17,880✔
102
}
17,880✔
103

104
DataType Dictionary::get_value_data_type() const
105
{
14,238✔
106
    return DataType(m_col_key.get_type());
14,238✔
107
}
14,238✔
108

109
bool Dictionary::is_null(size_t ndx) const
UNCOV
110
{
×
UNCOV
111
    return get_any(ndx).is_null();
×
UNCOV
112
}
×
113

114
Mixed Dictionary::get_any(size_t ndx) const
115
{
14,205✔
116
    // Note: `size()` calls `update_if_needed()`.
7,101✔
117
    auto current_size = size();
14,205✔
118
    CollectionBase::validate_index("get_any()", ndx, current_size);
14,205✔
119
    return do_get(ndx);
14,205✔
120
}
14,205✔
121

122
std::pair<Mixed, Mixed> Dictionary::get_pair(size_t ndx) const
123
{
99,222✔
124
    // Note: `size()` calls `update_if_needed()`.
49,464✔
125
    auto current_size = size();
99,222✔
126
    CollectionBase::validate_index("get_pair()", ndx, current_size);
99,222✔
127
    return do_get_pair(ndx);
99,222✔
128
}
99,222✔
129

130
Mixed Dictionary::get_key(size_t ndx) const
131
{
53,010✔
132
    // Note: `size()` calls `update_if_needed()`.
26,469✔
133
    CollectionBase::validate_index("get_key()", ndx, size());
53,010✔
134
    return do_get_key(ndx);
53,010✔
135
}
53,010✔
136

137
size_t Dictionary::find_any(Mixed value) const
138
{
3,198✔
139
    return size() ? m_values->find_first(value) : realm::not_found;
2,952✔
140
}
3,198✔
141

142
size_t Dictionary::find_any_key(Mixed key) const noexcept
143
{
36,000✔
144
    if (update()) {
36,000✔
145
        return do_find_key(key);
28,164✔
146
    }
28,164✔
147

3,918✔
148
    return realm::npos;
7,836✔
149
}
7,836✔
150

151
template <typename AggregateType>
152
void Dictionary::do_accumulate(size_t* return_ndx, AggregateType& agg) const
153
{
17,784✔
154
    size_t ndx = realm::npos;
17,784✔
155

8,892✔
156
    m_values->traverse([&](BPlusTreeNode* node, size_t offset) {
17,784✔
157
        auto leaf = static_cast<BPlusTree<Mixed>::LeafNode*>(node);
17,784✔
158
        size_t e = leaf->size();
17,784✔
159
        for (size_t i = 0; i < e; i++) {
68,460✔
160
            auto val = leaf->get(i);
50,676✔
161
            if (agg.accumulate(val)) {
50,676✔
162
                ndx = i + offset;
38,598✔
163
            }
38,598✔
164
        }
50,676✔
165
        // Continue
8,892✔
166
        return IteratorControl::AdvanceToNext;
17,784✔
167
    });
17,784✔
168

8,892✔
169
    if (return_ndx)
17,784✔
170
        *return_ndx = ndx;
12✔
171
}
17,784✔
172

173
util::Optional<Mixed> Dictionary::do_min(size_t* return_ndx) const
174
{
4,104✔
175
    aggregate_operations::Minimum<Mixed> agg;
4,104✔
176
    do_accumulate(return_ndx, agg);
4,104✔
177
    return agg.is_null() ? Mixed{} : agg.result();
4,104✔
178
}
4,104✔
179

180
util::Optional<Mixed> Dictionary::do_max(size_t* return_ndx) const
181
{
5,424✔
182
    aggregate_operations::Maximum<Mixed> agg;
5,424✔
183
    do_accumulate(return_ndx, agg);
5,424✔
184
    return agg.is_null() ? Mixed{} : agg.result();
5,424✔
185
}
5,424✔
186

187
util::Optional<Mixed> Dictionary::do_sum(size_t* return_cnt) const
188
{
4,164✔
189
    auto type = get_value_data_type();
4,164✔
190
    if (type == type_Int) {
4,164✔
191
        aggregate_operations::Sum<Int> agg;
42✔
192
        do_accumulate(nullptr, agg);
42✔
193
        if (return_cnt)
42✔
194
            *return_cnt = agg.items_counted();
18✔
195
        return Mixed{agg.result()};
42✔
196
    }
42✔
197
    else if (type == type_Double) {
4,122✔
198
        aggregate_operations::Sum<Double> agg;
84✔
199
        do_accumulate(nullptr, agg);
84✔
200
        if (return_cnt)
84✔
UNCOV
201
            *return_cnt = agg.items_counted();
×
202
        return Mixed{agg.result()};
84✔
203
    }
84✔
204
    else if (type == type_Float) {
4,038✔
205
        aggregate_operations::Sum<Float> agg;
84✔
206
        do_accumulate(nullptr, agg);
84✔
207
        if (return_cnt)
84✔
UNCOV
208
            *return_cnt = agg.items_counted();
×
209
        return Mixed{agg.result()};
84✔
210
    }
84✔
211

1,977✔
212
    aggregate_operations::Sum<Mixed> agg;
3,954✔
213
    do_accumulate(nullptr, agg);
3,954✔
214
    if (return_cnt)
3,954✔
UNCOV
215
        *return_cnt = agg.items_counted();
×
216
    return Mixed{agg.result()};
3,954✔
217
}
3,954✔
218

219
util::Optional<Mixed> Dictionary::do_avg(size_t* return_cnt) const
220
{
4,092✔
221
    auto type = get_value_data_type();
4,092✔
222
    if (type == type_Int) {
4,092✔
223
        aggregate_operations::Average<Int> agg;
42✔
224
        do_accumulate(nullptr, agg);
42✔
225
        if (return_cnt)
42✔
226
            *return_cnt = agg.items_counted();
18✔
227
        return agg.is_null() ? Mixed{} : agg.result();
42✔
228
    }
42✔
229
    else if (type == type_Double) {
4,050✔
230
        aggregate_operations::Average<Double> agg;
60✔
231
        do_accumulate(nullptr, agg);
60✔
232
        if (return_cnt)
60✔
UNCOV
233
            *return_cnt = agg.items_counted();
×
234
        return agg.is_null() ? Mixed{} : agg.result();
60✔
235
    }
60✔
236
    else if (type == type_Float) {
3,990✔
237
        aggregate_operations::Average<Float> agg;
60✔
238
        do_accumulate(nullptr, agg);
60✔
239
        if (return_cnt)
60✔
UNCOV
240
            *return_cnt = agg.items_counted();
×
241
        return agg.is_null() ? Mixed{} : agg.result();
60✔
242
    }
60✔
243
    // Decimal128 is covered with mixed as well.
1,965✔
244
    aggregate_operations::Average<Mixed> agg;
3,930✔
245
    do_accumulate(nullptr, agg);
3,930✔
246
    if (return_cnt)
3,930✔
UNCOV
247
        *return_cnt = agg.items_counted();
×
248
    return agg.is_null() ? Mixed{} : agg.result();
3,930✔
249
}
3,930✔
250

251
namespace {
252
bool can_minmax(DataType type)
253
{
606✔
254
    switch (type) {
606✔
255
        case type_Int:
228✔
256
        case type_Float:
252✔
257
        case type_Double:
276✔
258
        case type_Decimal:
300✔
259
        case type_Mixed:
342✔
260
        case type_Timestamp:
366✔
261
            return true;
366✔
262
        default:
303✔
263
            return false;
240✔
264
    }
606✔
265
}
606✔
266
bool can_sum(DataType type)
267
{
600✔
268
    switch (type) {
600✔
269
        case type_Int:
198✔
270
        case type_Float:
222✔
271
        case type_Double:
246✔
272
        case type_Decimal:
270✔
273
        case type_Mixed:
312✔
274
            return true;
312✔
275
        default:
300✔
276
            return false;
288✔
277
    }
600✔
278
}
600✔
279
} // anonymous namespace
280

281
util::Optional<Mixed> Dictionary::min(size_t* return_ndx) const
282
{
300✔
283
    if (!can_minmax(get_value_data_type())) {
300✔
284
        return std::nullopt;
120✔
285
    }
120✔
286
    if (update()) {
180✔
287
        return do_min(return_ndx);
108✔
288
    }
108✔
289
    if (return_ndx)
72✔
UNCOV
290
        *return_ndx = realm::not_found;
×
291
    return Mixed{};
72✔
292
}
72✔
293

294
util::Optional<Mixed> Dictionary::max(size_t* return_ndx) const
295
{
306✔
296
    if (!can_minmax(get_value_data_type())) {
306✔
297
        return std::nullopt;
120✔
298
    }
120✔
299
    if (update()) {
186✔
300
        return do_max(return_ndx);
108✔
301
    }
108✔
302
    if (return_ndx)
78✔
303
        *return_ndx = realm::not_found;
6✔
304
    return Mixed{};
78✔
305
}
78✔
306

307
util::Optional<Mixed> Dictionary::sum(size_t* return_cnt) const
308
{
300✔
309
    if (!can_sum(get_value_data_type())) {
300✔
310
        return std::nullopt;
144✔
311
    }
144✔
312
    if (update()) {
156✔
313
        return do_sum(return_cnt);
96✔
314
    }
96✔
315
    if (return_cnt)
60✔
UNCOV
316
        *return_cnt = 0;
×
317
    return Mixed{0};
60✔
318
}
60✔
319

320
util::Optional<Mixed> Dictionary::avg(size_t* return_cnt) const
321
{
300✔
322
    if (!can_sum(get_value_data_type())) {
300✔
323
        return std::nullopt;
144✔
324
    }
144✔
325
    if (update()) {
156✔
326
        return do_avg(return_cnt);
96✔
327
    }
96✔
328
    if (return_cnt)
60✔
UNCOV
329
        *return_cnt = 0;
×
330
    return Mixed{};
60✔
331
}
60✔
332

333
void Dictionary::align_indices(std::vector<size_t>& indices) const
334
{
5,046✔
335
    auto sz = size();
5,046✔
336
    auto sz2 = indices.size();
5,046✔
337
    indices.reserve(sz);
5,046✔
338
    if (sz < sz2) {
5,046✔
339
        // If list size has decreased, we have to start all over
UNCOV
340
        indices.clear();
×
UNCOV
341
        sz2 = 0;
×
UNCOV
342
    }
×
343
    for (size_t i = sz2; i < sz; i++) {
23,358✔
344
        // If list size has increased, just add the missing indices
9,156✔
345
        indices.push_back(i);
18,312✔
346
    }
18,312✔
347
}
5,046✔
348

349
namespace {
350
template <class T>
351
void do_sort(std::vector<size_t>& indices, bool ascending, const std::vector<T>& values)
352
{
4,416✔
353
    auto b = indices.begin();
4,416✔
354
    auto e = indices.end();
4,416✔
355
    std::sort(b, e, [ascending, &values](size_t i1, size_t i2) {
36,156✔
356
        return ascending ? values[i1] < values[i2] : values[i2] < values[i1];
35,514✔
357
    });
36,156✔
358
}
4,416✔
359
} // anonymous namespace
360

361
void Dictionary::sort(std::vector<size_t>& indices, bool ascending) const
362
{
3,786✔
363
    align_indices(indices);
3,786✔
364
    do_sort(indices, ascending, m_values->get_all());
3,786✔
365
}
3,786✔
366

367
void Dictionary::distinct(std::vector<size_t>& indices, util::Optional<bool> ascending) const
368
{
630✔
369
    align_indices(indices);
630✔
370
    auto values = m_values->get_all();
630✔
371
    do_sort(indices, ascending.value_or(true), values);
630✔
372
    indices.erase(std::unique(indices.begin(), indices.end(),
630✔
373
                              [&values](size_t i1, size_t i2) {
1,800✔
374
                                  return values[i1] == values[i2];
1,800✔
375
                              }),
1,800✔
376
                  indices.end());
630✔
377

315✔
378
    if (!ascending) {
630✔
379
        // need to return indices in original ordering
63✔
380
        std::sort(indices.begin(), indices.end(), std::less<size_t>());
126✔
381
    }
126✔
382
}
630✔
383

384
void Dictionary::sort_keys(std::vector<size_t>& indices, bool ascending) const
385
{
504✔
386
    align_indices(indices);
504✔
387
#ifdef REALM_DEBUG
504✔
388
    if (indices.size() > 1) {
504✔
389
        // We rely in the design that the keys are already sorted
252✔
390
        switch (m_key_type) {
504✔
391
            case type_String: {
504✔
392
                IteratorAdapter help(static_cast<BPlusTree<StringData>*>(m_keys.get()));
504✔
393
                auto is_sorted = std::is_sorted(help.begin(), help.end());
504✔
394
                REALM_ASSERT(is_sorted);
504✔
395
                break;
504✔
UNCOV
396
            }
×
UNCOV
397
            case type_Int: {
✔
UNCOV
398
                IteratorAdapter help(static_cast<BPlusTree<Int>*>(m_keys.get()));
×
UNCOV
399
                auto is_sorted = std::is_sorted(help.begin(), help.end());
×
UNCOV
400
                REALM_ASSERT(is_sorted);
×
UNCOV
401
                break;
×
UNCOV
402
            }
×
UNCOV
403
            default:
✔
UNCOV
404
                break;
×
405
        }
504✔
406
    }
504✔
407
#endif
504✔
408
    if (ascending) {
504✔
409
        std::sort(indices.begin(), indices.end());
252✔
410
    }
252✔
411
    else {
252✔
412
        std::sort(indices.begin(), indices.end(), std::greater<size_t>());
252✔
413
    }
252✔
414
}
504✔
415

416
void Dictionary::distinct_keys(std::vector<size_t>& indices, util::Optional<bool>) const
417
{
126✔
418
    // we rely on the design of dictionary to assume that the keys are unique
63✔
419
    align_indices(indices);
126✔
420
}
126✔
421

422

423
Obj Dictionary::create_and_insert_linked_object(Mixed key)
424
{
4,230✔
425
    Table& t = *get_target_table();
4,230✔
426
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
4,230✔
427
    insert(key, o.get_key());
4,230✔
428
    return o;
4,230✔
429
}
4,230✔
430

431
void Dictionary::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
432
{
486✔
433
    check_level();
486✔
434
    ensure_created();
486✔
435
    m_values->ensure_keys();
486✔
436
    auto [it, inserted] = insert(path_elem.get_key(), Mixed(0, dict_or_list));
486✔
437
    int64_t key = generate_key(size());
486✔
438
    while (m_values->find_key(key) != realm::not_found) {
489✔
439
        key++;
3✔
440
    }
3✔
441
    m_values->set_key(it.index(), key);
486✔
442
}
486✔
443

444
DictionaryPtr Dictionary::get_dictionary(const PathElement& path_elem) const
445
{
228✔
446
    update();
228✔
447
    auto weak = const_cast<Dictionary*>(this)->weak_from_this();
228✔
448
    auto shared = weak.expired() ? std::make_shared<Dictionary>(*this) : weak.lock();
198✔
449
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
228✔
450
    ret->set_owner(shared, build_index(path_elem.get_key()));
228✔
451
    return ret;
228✔
452
}
228✔
453

454
SetMixedPtr Dictionary::get_set(const PathElement& path_elem) const
455
{
84✔
456
    update();
84✔
457
    auto weak = const_cast<Dictionary*>(this)->weak_from_this();
84✔
458
    auto shared = weak.expired() ? std::make_shared<Dictionary>(*this) : weak.lock();
72✔
459
    auto ret = std::make_shared<Set<Mixed>>(m_obj_mem, m_col_key);
84✔
460
    ret->set_owner(shared, build_index(path_elem.get_key()));
84✔
461
    return ret;
84✔
462
}
84✔
463

464
std::shared_ptr<Lst<Mixed>> Dictionary::get_list(const PathElement& path_elem) const
465
{
402✔
466
    update();
402✔
467
    auto weak = const_cast<Dictionary*>(this)->weak_from_this();
402✔
468
    auto shared = weak.expired() ? std::make_shared<Dictionary>(*this) : weak.lock();
351✔
469
    std::shared_ptr<Lst<Mixed>> ret = std::make_shared<Lst<Mixed>>(m_col_key, get_level() + 1);
402✔
470
    ret->set_owner(shared, build_index(path_elem.get_key()));
402✔
471
    return ret;
402✔
472
}
402✔
473

474
Mixed Dictionary::get(Mixed key) const
475
{
13,263✔
476
    if (auto opt_val = try_get(key)) {
13,263✔
477
        return *opt_val;
13,239✔
478
    }
13,239✔
479
    throw KeyNotFound("Dictionary::get");
24✔
480
}
24✔
481

482
util::Optional<Mixed> Dictionary::try_get(Mixed key) const
483
{
839,265✔
484
    if (update()) {
839,265✔
485
        auto ndx = do_find_key(key);
838,293✔
486
        if (ndx != realm::npos) {
838,293✔
487
            return do_get(ndx);
836,121✔
488
        }
836,121✔
489
    }
3,144✔
490
    return {};
3,144✔
491
}
3,144✔
492

493
Dictionary::Iterator Dictionary::begin() const
494
{
25,005✔
495
    // Need an update because the `Dictionary::Iterator` constructor relies on
12,246✔
496
    // `m_clusters` to determine if it was already at end.
12,246✔
497
    update();
25,005✔
498
    return Iterator(this, 0);
25,005✔
499
}
25,005✔
500

501
Dictionary::Iterator Dictionary::end() const
502
{
82,869✔
503
    return Iterator(this, size());
82,869✔
504
}
82,869✔
505

506
std::pair<Dictionary::Iterator, bool> Dictionary::insert(Mixed key, Mixed value)
507
{
104,688✔
508
    auto my_table = get_table_unchecked();
104,688✔
509
    if (key.get_type() != m_key_type) {
104,688✔
UNCOV
510
        throw InvalidArgument(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: Invalid key type");
×
UNCOV
511
    }
×
512
    if (m_col_key) {
104,688✔
513
        if (value.is_null()) {
104,610✔
514
            if (!m_col_key.is_nullable()) {
5,220✔
UNCOV
515
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Value cannot be null");
×
UNCOV
516
            }
×
517
        }
99,390✔
518
        else {
99,390✔
519
            if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) {
99,390✔
520
                if (my_table->get_opposite_table_key(m_col_key) != value.get<ObjLink>().get_table_key()) {
2,712✔
521
                    throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
6✔
522
                                          "Dictionary::insert: Wrong object type");
6✔
523
                }
6✔
524
            }
96,678✔
525
            else if (m_col_key.get_type() != col_type_Mixed && value.get_type() != DataType(m_col_key.get_type())) {
96,678✔
UNCOV
526
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Wrong value type");
×
UNCOV
527
            }
×
528
            else if (value.is_type(type_Link) && m_col_key.get_type() != col_type_Link) {
96,678✔
UNCOV
529
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
×
UNCOV
530
                                      "Dictionary::insert: No target table for link");
×
531
            }
×
532
        }
104,682✔
533
    }
104,610✔
534

52,287✔
535
    validate_key_value(key);
104,682✔
536
    ensure_created();
104,682✔
537

52,287✔
538
    ObjLink new_link;
104,682✔
539
    if (value.is_type(type_TypedLink)) {
104,682✔
540
        new_link = value.get<ObjLink>();
7,164✔
541
        if (!new_link.is_unresolved())
7,164✔
542
            my_table->get_parent_group()->validate(new_link);
7,068✔
543
    }
7,164✔
544
    else if (value.is_type(type_Link)) {
97,518✔
545
        auto target_table = my_table->get_opposite_table(m_col_key);
12,132✔
546
        auto key = value.get<ObjKey>();
12,132✔
547
        if (!key.is_unresolved() && !target_table->is_valid(key)) {
12,132✔
548
            throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
6✔
549
        }
6✔
550
        new_link = ObjLink(target_table->get_key(), key);
12,126✔
551
        value = Mixed(new_link);
12,126✔
552
    }
12,126✔
553

52,287✔
554
    if (!m_dictionary_top) {
104,679✔
UNCOV
555
        throw StaleAccessor("Stale dictionary");
×
UNCOV
556
    }
×
557

52,284✔
558
    bool old_entry = false;
104,676✔
559
    auto [ndx, actual_key] = find_impl(key);
104,676✔
560
    if (actual_key != key) {
104,676✔
561
        // key does not already exist
47,634✔
562
        switch (m_key_type) {
95,376✔
563
            case type_String:
95,376✔
564
                static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key.get_string());
95,376✔
565
                break;
95,376✔
UNCOV
566
            case type_Int:
✔
UNCOV
567
                static_cast<BPlusTree<Int>*>(m_keys.get())->insert(ndx, key.get_int());
×
UNCOV
568
                break;
×
UNCOV
569
            default:
✔
UNCOV
570
                break;
×
571
        }
95,373✔
572
        m_values->insert(ndx, value);
95,373✔
573
    }
95,373✔
574
    else {
9,300✔
575
        old_entry = true;
9,300✔
576
    }
9,300✔
577

52,284✔
578
    if (Replication* repl = get_replication()) {
104,676✔
579
        if (old_entry) {
92,310✔
580
            repl->dictionary_set(*this, ndx, key, value);
8,052✔
581
        }
8,052✔
582
        else {
84,258✔
583
            repl->dictionary_insert(*this, ndx, key, value);
84,258✔
584
        }
84,258✔
585
    }
92,310✔
586

52,281✔
587
    bump_content_version();
104,673✔
588

52,281✔
589
    ObjLink old_link;
104,673✔
590
    if (old_entry) {
104,673✔
591
        Mixed old_value = m_values->get(ndx);
9,252✔
592
        if (old_value.is_type(type_TypedLink)) {
9,252✔
593
            old_link = old_value.get<ObjLink>();
594✔
594
        }
594✔
595
        m_values->set(ndx, value);
9,252✔
596
    }
9,252✔
597

52,281✔
598
    if (new_link != old_link) {
104,673✔
599
        CascadeState cascade_state(CascadeState::Mode::Strong);
19,326✔
600
        bool recurse = Base::replace_backlink(m_col_key, old_link, new_link, cascade_state);
19,326✔
601
        if (recurse)
19,326✔
602
            _impl::TableFriend::remove_recursive(*my_table, cascade_state); // Throws
288✔
603
    }
19,326✔
604

52,281✔
605
    return {Iterator(this, ndx), !old_entry};
104,673✔
606
}
104,676✔
607

608
const Mixed Dictionary::operator[](Mixed key)
609
{
812,802✔
610
    auto ret = try_get(key);
812,802✔
611
    if (!ret) {
812,802✔
612
        ret = Mixed{};
6✔
613
        insert(key, Mixed{});
6✔
614
    }
6✔
615

406,401✔
616
    return *ret;
812,802✔
617
}
812,802✔
618

619
Obj Dictionary::get_object(StringData key)
620
{
12,069✔
621
    if (auto val = try_get(key)) {
12,069✔
622
        if ((*val).is_type(type_TypedLink)) {
8,967✔
623
            return get_table()->get_parent_group()->get_object((*val).get_link());
8,847✔
624
        }
8,847✔
625
    }
3,222✔
626
    return {};
3,222✔
627
}
3,222✔
628

629
bool Dictionary::contains(Mixed key) const noexcept
630
{
3,072✔
631
    return find_any_key(key) != realm::npos;
3,072✔
632
}
3,072✔
633

634
Dictionary::Iterator Dictionary::find(Mixed key) const noexcept
635
{
31,836✔
636
    auto ndx = find_any_key(key);
31,836✔
637
    if (ndx != realm::npos) {
31,836✔
638
        return Iterator(this, ndx);
16,878✔
639
    }
16,878✔
640
    return end();
14,958✔
641
}
14,958✔
642

643
void Dictionary::add_index(Path& path, const Index& index) const
644
{
168✔
645
    auto ndx = m_values->find_key(index.get_salt());
168✔
646
    auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
168✔
647
    path.emplace_back(keys->get(ndx));
168✔
648
}
168✔
649

650
size_t Dictionary::find_index(const Index& index) const
651
{
108✔
652
    update();
108✔
653
    return m_values->find_key(index.get_salt());
108✔
654
}
108✔
655

656
UpdateStatus Dictionary::update_if_needed_with_status() const
657
{
1,363,746✔
658
    auto status = Base::get_update_status();
1,363,746✔
659
    switch (status) {
1,363,746✔
660
        case UpdateStatus::Detached: {
6✔
661
            m_dictionary_top.reset();
6✔
662
            return UpdateStatus::Detached;
6✔
UNCOV
663
        }
×
664
        case UpdateStatus::NoChange: {
1,224,357✔
665
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
1,224,357✔
666
                return UpdateStatus::NoChange;
1,189,323✔
667
            }
1,189,323✔
668
            // The tree has not been initialized yet for this accessor, so
17,331✔
669
            // perform lazy initialization by treating it as an update.
17,331✔
670
            [[fallthrough]];
35,034✔
671
        }
35,034✔
672
        case UpdateStatus::Updated: {
174,417✔
673
            // Try to initialize. If the dictionary is not initialized
86,694✔
674
            // the function will return false;
86,694✔
675
            bool attached = init_from_parent(false);
174,417✔
676
            Base::update_content_version();
174,417✔
677
            return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
144,030✔
UNCOV
678
        }
×
UNCOV
679
    }
×
UNCOV
680
    REALM_UNREACHABLE();
×
UNCOV
681
}
×
682

683
void Dictionary::ensure_created()
684
{
105,150✔
685
    if (Base::should_update() || !(m_dictionary_top && m_dictionary_top->is_attached())) {
105,150✔
686
        // When allow_create is true, init_from_parent will always succeed
24,609✔
687
        // In case of errors, an exception is thrown.
24,609✔
688
        constexpr bool allow_create = true;
49,146✔
689
        init_from_parent(allow_create); // Throws
49,146✔
690
        Base::update_content_version();
49,146✔
691
    }
49,146✔
692
}
105,150✔
693

694
bool Dictionary::try_erase(Mixed key)
695
{
12,036✔
696
    validate_key_value(key);
12,036✔
697
    if (!update())
12,036✔
UNCOV
698
        return false;
×
699

5,934✔
700
    auto ndx = do_find_key(key);
12,036✔
701
    if (ndx == realm::npos) {
12,036✔
702
        return false;
1,230✔
703
    }
1,230✔
704

5,319✔
705
    do_erase(ndx, key);
10,806✔
706

5,319✔
707
    return true;
10,806✔
708
}
10,806✔
709

710

711
void Dictionary::erase(Mixed key)
712
{
10,926✔
713
    if (!try_erase(key)) {
10,926✔
714
        throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
618✔
715
    }
618✔
716
}
10,926✔
717

718
auto Dictionary::erase(Iterator it) -> Iterator
719
{
2,382✔
720
    auto pos = it.m_ndx;
2,382✔
721
    CollectionBase::validate_index("erase()", pos, size());
2,382✔
722

1,191✔
723
    do_erase(pos, do_get_key(pos));
2,382✔
724
    if (pos < size())
2,382✔
725
        pos++;
402✔
726
    return {this, pos};
2,382✔
727
}
2,382✔
728

729
void Dictionary::nullify(size_t ndx)
730
{
294✔
731
    REALM_ASSERT(m_dictionary_top);
294✔
732
    REALM_ASSERT(ndx != realm::npos);
294✔
733

147✔
734
    if (Replication* repl = get_replication()) {
294✔
735
        auto key = do_get_key(ndx);
276✔
736
        repl->dictionary_set(*this, ndx, key, Mixed());
276✔
737
    }
276✔
738

147✔
739
    m_values->set(ndx, Mixed());
294✔
740
}
294✔
741

742
bool Dictionary::nullify(ObjLink target_link)
743
{
306✔
744
    size_t ndx = find_first(target_link);
306✔
745
    if (ndx != realm::not_found) {
306✔
746
        nullify(ndx);
294✔
747
        return true;
294✔
748
    }
294✔
749
    else {
12✔
750
        // There must be a link in a nested collection
6✔
751
        size_t sz = size();
12✔
752
        for (size_t ndx = 0; ndx < sz; ndx++) {
18✔
753
            auto val = m_values->get(ndx);
12✔
754
            auto key = do_get_key(ndx);
12✔
755
            if (val.is_type(type_Dictionary)) {
12✔
756
                auto dict = get_dictionary(key.get_string());
×
757
                if (dict->nullify(target_link)) {
×
UNCOV
758
                    return true;
×
UNCOV
759
                }
×
760
            }
12✔
761
            if (val.is_type(type_List)) {
12✔
762
                auto list = get_list(key.get_string());
6✔
763
                if (list->nullify(target_link)) {
6✔
764
                    return true;
6✔
765
                }
6✔
766
            }
6✔
767
        }
12✔
768
    }
12✔
769
    return false;
156✔
770
}
306✔
771

772
bool Dictionary::replace_link(ObjLink old_link, ObjLink replace_link)
773
{
120✔
774
    size_t ndx = find_first(old_link);
120✔
775
    if (ndx != realm::not_found) {
120✔
776
        auto key = do_get_key(ndx);
120✔
777
        insert(key, replace_link);
120✔
778
        return true;
120✔
779
    }
120✔
UNCOV
780
    else {
×
781
        // There must be a link in a nested collection
UNCOV
782
        size_t sz = size();
×
UNCOV
783
        for (size_t ndx = 0; ndx < sz; ndx++) {
×
UNCOV
784
            auto val = m_values->get(ndx);
×
UNCOV
785
            auto key = do_get_key(ndx);
×
UNCOV
786
            if (val.is_type(type_Dictionary)) {
×
UNCOV
787
                auto dict = get_dictionary(key.get_string());
×
UNCOV
788
                if (dict->replace_link(old_link, replace_link)) {
×
UNCOV
789
                    return true;
×
UNCOV
790
                }
×
UNCOV
791
            }
×
UNCOV
792
            if (val.is_type(type_List)) {
×
UNCOV
793
                auto list = get_list(key.get_string());
×
UNCOV
794
                if (list->replace_link(old_link, replace_link)) {
×
UNCOV
795
                    return true;
×
UNCOV
796
                }
×
UNCOV
797
            }
×
UNCOV
798
        }
×
UNCOV
799
    }
×
800
    return false;
60✔
801
}
120✔
802

803
void Dictionary::remove_backlinks(CascadeState& state) const
804
{
450✔
805
    size_t sz = size();
450✔
806
    for (size_t ndx = 0; ndx < sz; ndx++) {
1,908✔
807
        auto val = m_values->get(ndx);
1,458✔
808
        if (val.is_type(type_TypedLink)) {
1,458✔
809
            Base::remove_backlink(m_col_key, val.get_link(), state);
912✔
810
        }
912✔
811
        else if (val.is_type(type_Dictionary)) {
546✔
812
            auto key = do_get_key(ndx);
12✔
813
            get_dictionary(key.get_string())->remove_backlinks(state);
12✔
814
        }
12✔
815
        else if (val.is_type(type_List)) {
534✔
816
            auto key = do_get_key(ndx);
18✔
817
            get_list(key.get_string())->remove_backlinks(state);
18✔
818
        }
18✔
819
    }
1,458✔
820
}
450✔
821

822
size_t Dictionary::find_first(Mixed value) const
823
{
44,700✔
824
    return update() ? m_values->find_first(value) : realm::not_found;
44,700✔
825
}
44,700✔
826

827
void Dictionary::clear()
828
{
2,586✔
829
    if (size() > 0) {
2,586✔
830
        Replication* repl = get_replication();
2,364✔
831
        bool recurse = false;
2,364✔
832
        CascadeState cascade_state(CascadeState::Mode::Strong);
2,364✔
833
        if (repl) {
2,364✔
834
            repl->dictionary_clear(*this);
2,352✔
835
        }
2,352✔
836
        for (auto&& elem : *this) {
9,138✔
837
            if (clear_backlink(elem.second, cascade_state))
9,138✔
838
                recurse = true;
18✔
839
        }
9,138✔
840
        // Just destroy the whole cluster
1,182✔
841
        m_dictionary_top->destroy_deep();
2,364✔
842
        m_dictionary_top.reset();
2,364✔
843

1,182✔
844
        update_child_ref(0, 0);
2,364✔
845

1,182✔
846
        if (recurse)
2,364✔
847
            _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
6✔
848
    }
2,364✔
849
}
2,586✔
850

851
bool Dictionary::init_from_parent(bool allow_create) const
852
{
223,563✔
853
    try {
223,563✔
854
        auto ref = Base::get_collection_ref();
223,563✔
855
        if ((ref || allow_create) && !m_dictionary_top) {
223,563✔
856
            Allocator& alloc = get_alloc();
133,617✔
857
            m_dictionary_top.reset(new Array(alloc));
133,617✔
858
            m_dictionary_top->set_parent(const_cast<Dictionary*>(this), 0);
133,617✔
859
            switch (m_key_type) {
133,617✔
860
                case type_String: {
133,617✔
861
                    m_keys.reset(new BPlusTree<StringData>(alloc));
133,617✔
862
                    break;
133,617✔
UNCOV
863
                }
×
UNCOV
864
                case type_Int: {
✔
UNCOV
865
                    m_keys.reset(new BPlusTree<Int>(alloc));
×
866
                    break;
×
867
                }
×
868
                default:
✔
869
                    break;
×
870
            }
133,617✔
871
            m_keys->set_parent(m_dictionary_top.get(), 0);
133,617✔
872
            m_values.reset(new BPlusTreeMixed(alloc));
133,617✔
873
            m_values->set_parent(m_dictionary_top.get(), 1);
133,617✔
874
        }
133,617✔
875

111,303✔
876
        if (ref) {
223,563✔
877
            m_dictionary_top->init_from_ref(ref);
136,197✔
878
            m_keys->init_from_parent();
136,197✔
879
            m_values->init_from_parent();
136,197✔
880
        }
136,197✔
881
        else {
87,366✔
882
            // dictionary detached
43,221✔
883
            if (!allow_create) {
87,366✔
884
                m_dictionary_top.reset();
59,916✔
885
                return false;
59,916✔
886
            }
59,916✔
887

13,683✔
888
            // Create dictionary
13,683✔
889
            m_dictionary_top->create(Array::type_HasRefs, false, 2, 0);
27,450✔
890
            m_values->create();
27,450✔
891
            m_keys->create();
27,450✔
892
            m_dictionary_top->update_parent();
27,450✔
893
        }
27,450✔
894

111,303✔
895
        return true;
193,185✔
896
    }
36✔
897
    catch (...) {
36✔
898
        m_dictionary_top.reset();
36✔
899
        throw;
36✔
900
    }
36✔
901
}
223,563✔
902

903
size_t Dictionary::do_find_key(Mixed key) const noexcept
904
{
878,481✔
905
    auto [ndx, actual_key] = find_impl(key);
878,481✔
906
    if (actual_key == key) {
878,481✔
907
        return ndx;
866,319✔
908
    }
866,319✔
909
    return realm::npos;
12,162✔
910
}
12,162✔
911

912
std::pair<size_t, Mixed> Dictionary::find_impl(Mixed key) const noexcept
913
{
983,163✔
914
    auto sz = m_keys->size();
983,163✔
915
    Mixed actual;
983,163✔
916
    if (sz && key.is_type(m_key_type)) {
983,163✔
917
        switch (m_key_type) {
954,573✔
918
            case type_String: {
954,573✔
919
                auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
954,573✔
920
                StringData val = key.get<StringData>();
954,573✔
921
                IteratorAdapter help(keys);
954,573✔
922
                auto it = std::lower_bound(help.begin(), help.end(), val);
954,573✔
923
                if (it.index() < sz) {
954,573✔
924
                    actual = *it;
905,037✔
925
                }
905,037✔
926
                return {it.index(), actual};
954,573✔
UNCOV
927
                break;
×
UNCOV
928
            }
×
UNCOV
929
            case type_Int: {
✔
UNCOV
930
                auto keys = static_cast<BPlusTree<Int>*>(m_keys.get());
×
UNCOV
931
                Int val = key.get<Int>();
×
UNCOV
932
                IteratorAdapter help(keys);
×
UNCOV
933
                auto it = std::lower_bound(help.begin(), help.end(), val);
×
UNCOV
934
                if (it.index() < sz) {
×
UNCOV
935
                    actual = *it;
×
UNCOV
936
                }
×
UNCOV
937
                return {it.index(), actual};
×
UNCOV
938
                break;
×
UNCOV
939
            }
×
UNCOV
940
            default:
✔
UNCOV
941
                break;
×
942
        }
28,590✔
943
    }
28,590✔
944

14,250✔
945
    return {sz, actual};
28,590✔
946
}
28,590✔
947

948
Mixed Dictionary::do_get(size_t ndx) const
949
{
950,190✔
950
    Mixed val = m_values->get(ndx);
950,190✔
951

474,858✔
952
    // Filter out potential unresolved links
474,858✔
953
    if (val.is_type(type_TypedLink) && val.get<ObjKey>().is_unresolved()) {
950,190✔
954
        return {};
690✔
955
    }
690✔
956
    return val;
949,500✔
957
}
949,500✔
958

959
void Dictionary::do_erase(size_t ndx, Mixed key)
960
{
13,176✔
961
    auto old_value = m_values->get(ndx);
13,176✔
962

6,504✔
963
    CascadeState cascade_state(CascadeState::Mode::Strong);
13,176✔
964
    bool recurse = clear_backlink(old_value, cascade_state);
13,176✔
965
    if (recurse)
13,176✔
966
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
102✔
967

6,504✔
968
    if (Replication* repl = get_replication()) {
13,176✔
969
        repl->dictionary_erase(*this, ndx, key);
11,952✔
970
    }
11,952✔
971

6,504✔
972
    m_keys->erase(ndx);
13,176✔
973
    m_values->erase(ndx);
13,176✔
974

6,504✔
975
    bump_content_version();
13,176✔
976
}
13,176✔
977

978
Mixed Dictionary::do_get_key(size_t ndx) const
979
{
155,694✔
980
    switch (m_key_type) {
155,694✔
981
        case type_String: {
155,694✔
982
            return static_cast<BPlusTree<StringData>*>(m_keys.get())->get(ndx);
155,694✔
UNCOV
983
        }
×
UNCOV
984
        case type_Int: {
✔
UNCOV
985
            return static_cast<BPlusTree<Int>*>(m_keys.get())->get(ndx);
×
UNCOV
986
        }
×
UNCOV
987
        default:
✔
UNCOV
988
            break;
×
UNCOV
989
    }
×
990

UNCOV
991
    return {};
×
992
}
×
993

994
std::pair<Mixed, Mixed> Dictionary::do_get_pair(size_t ndx) const
995
{
99,216✔
996
    return {do_get_key(ndx), do_get(ndx)};
99,216✔
997
}
99,216✔
998

999
bool Dictionary::clear_backlink(Mixed value, CascadeState& state) const
1000
{
22,314✔
1001
    if (value.is_type(type_TypedLink)) {
22,314✔
1002
        return Base::remove_backlink(m_col_key, value.get_link(), state);
6,756✔
1003
    }
6,756✔
1004
    return false;
15,558✔
1005
}
15,558✔
1006

1007
void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, size_t index2)
UNCOV
1008
{
×
UNCOV
1009
    std::string buf1, buf2;
×
1010

1011
    // Swap keys
UNCOV
1012
    REALM_ASSERT(m_key_type == type_String);
×
UNCOV
1013
    ArrayString keys(get_alloc());
×
UNCOV
1014
    keys.set_parent(&fields1, 1);
×
UNCOV
1015
    keys.init_from_parent();
×
UNCOV
1016
    buf1 = keys.get(index1);
×
1017

UNCOV
1018
    keys.set_parent(&fields2, 1);
×
UNCOV
1019
    keys.init_from_parent();
×
UNCOV
1020
    buf2 = keys.get(index2);
×
UNCOV
1021
    keys.set(index2, buf1);
×
1022

UNCOV
1023
    keys.set_parent(&fields1, 1);
×
UNCOV
1024
    keys.init_from_parent();
×
UNCOV
1025
    keys.set(index1, buf2);
×
1026

1027
    // Swap values
UNCOV
1028
    ArrayMixed values(get_alloc());
×
UNCOV
1029
    values.set_parent(&fields1, 2);
×
UNCOV
1030
    values.init_from_parent();
×
UNCOV
1031
    Mixed val1 = values.get(index1);
×
UNCOV
1032
    val1.use_buffer(buf1);
×
1033

UNCOV
1034
    values.set_parent(&fields2, 2);
×
UNCOV
1035
    values.init_from_parent();
×
UNCOV
1036
    Mixed val2 = values.get(index2);
×
UNCOV
1037
    val2.use_buffer(buf2);
×
UNCOV
1038
    values.set(index2, val1);
×
1039

UNCOV
1040
    values.set_parent(&fields1, 2);
×
UNCOV
1041
    values.init_from_parent();
×
UNCOV
1042
    values.set(index1, val2);
×
UNCOV
1043
}
×
1044

1045
Mixed Dictionary::find_value(Mixed value) const noexcept
UNCOV
1046
{
×
UNCOV
1047
    size_t ndx = update() ? m_values->find_first(value) : realm::npos;
×
UNCOV
1048
    return (ndx == realm::npos) ? Mixed{} : do_get_key(ndx);
×
UNCOV
1049
}
×
1050

1051
StableIndex Dictionary::build_index(Mixed key) const
1052
{
768✔
1053
    auto it = find(key);
768✔
1054
    int64_t index = (it != end()) ? m_values->get_key(it.index()) : 0;
768✔
1055
    return {index};
768✔
1056
}
768✔
1057

1058

1059
void Dictionary::verify() const
1060
{
12,168✔
1061
    m_keys->verify();
12,168✔
1062
    m_values->verify();
12,168✔
1063
    REALM_ASSERT(m_keys->size() == m_values->size());
12,168✔
1064
}
12,168✔
1065

1066
void Dictionary::get_key_type()
1067
{
161,088✔
1068
    m_key_type = get_table()->get_dictionary_key_type(m_col_key);
161,088✔
1069
    if (!(m_key_type == type_String || m_key_type == type_Int))
161,088!
UNCOV
1070
        throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary keys can only be strings or integers");
×
1071
}
161,088✔
1072

1073
void Dictionary::migrate()
1074
{
6✔
1075
    // Dummy implementation of legacy dictionary cluster tree
3✔
1076
    class DictionaryClusterTree : public ClusterTree {
6✔
1077
    public:
6✔
1078
        DictionaryClusterTree(ArrayParent* owner, Allocator& alloc, size_t ndx)
6✔
1079
            : ClusterTree(nullptr, alloc, ndx)
6✔
1080
            , m_owner(owner)
6✔
1081
        {
6✔
1082
        }
6✔
1083

3✔
1084
        std::unique_ptr<ClusterNode> get_root_from_parent() final
6✔
1085
        {
6✔
1086
            return create_root_from_parent(m_owner, m_top_position_for_cluster_tree);
6✔
1087
        }
6✔
1088

3✔
1089
    private:
6✔
1090
        ArrayParent* m_owner;
6✔
1091
    };
6✔
1092

3✔
1093
    if (auto dict_ref = Base::get_collection_ref()) {
6✔
1094
        Allocator& alloc = get_alloc();
6✔
1095
        DictionaryClusterTree cluster_tree(this, alloc, 0);
6✔
1096
        if (cluster_tree.init_from_parent()) {
6✔
1097
            // Create an empty dictionary in the old ones place
3✔
1098
            Base::set_collection_ref(0);
6✔
1099
            ensure_created();
6✔
1100

3✔
1101
            ArrayString keys(alloc); // We only support string type keys.
6✔
1102
            ArrayMixed values(alloc);
6✔
1103
            constexpr ColKey key_col(ColKey::Idx{0}, col_type_String, ColumnAttrMask(), 0);
6✔
1104
            constexpr ColKey value_col(ColKey::Idx{1}, col_type_Mixed, ColumnAttrMask(), 0);
6✔
1105
            size_t nb_elements = cluster_tree.size();
6✔
1106
            cluster_tree.traverse([&](const Cluster* cluster) {
6✔
1107
                cluster->init_leaf(key_col, &keys);
6✔
1108
                cluster->init_leaf(value_col, &values);
6✔
1109
                auto sz = cluster->node_size();
6✔
1110
                for (size_t i = 0; i < sz; i++) {
60✔
1111
                    // Just use low level functions to insert elements. All keys must be legal and
27✔
1112
                    // unique and all values must match expected type. Links should just be preserved
27✔
1113
                    // so no need to worry about backlinks.
27✔
1114
                    StringData key = keys.get(i);
54✔
1115
                    auto [ndx, actual_key] = find_impl(key);
54✔
1116
                    REALM_ASSERT(actual_key != key);
54✔
1117
                    static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key);
54✔
1118
                    m_values->insert(ndx, values.get(i));
54✔
1119
                }
54✔
1120
                return IteratorControl::AdvanceToNext;
6✔
1121
            });
6✔
1122
            REALM_ASSERT(size() == nb_elements);
6✔
1123
            Array::destroy_deep(to_ref(dict_ref), alloc);
6✔
1124
        }
6✔
UNCOV
1125
        else {
×
UNCOV
1126
            REALM_UNREACHABLE();
×
UNCOV
1127
        }
×
1128
    }
6✔
1129
}
6✔
1130

1131
template <>
1132
void CollectionBaseImpl<DictionaryBase>::to_json(std::ostream&, size_t, JSONOutputMode,
1133
                                                 util::FunctionRef<void(const Mixed&)>) const
UNCOV
1134
{
×
UNCOV
1135
}
×
1136

1137
void Dictionary::to_json(std::ostream& out, size_t link_depth, JSONOutputMode output_mode,
1138
                         util::FunctionRef<void(const Mixed&)> fn) const
1139
{
504✔
1140
    auto [open_str, close_str] = get_open_close_strings(link_depth, output_mode);
504✔
1141

252✔
1142
    out << open_str;
504✔
1143
    out << "{";
504✔
1144

252✔
1145
    auto sz = size();
504✔
1146
    for (size_t i = 0; i < sz; i++) {
1,152✔
1147
        if (i > 0)
648✔
1148
            out << ",";
198✔
1149
        out << do_get_key(i) << ":";
648✔
1150
        Mixed val = do_get(i);
648✔
1151
        if (val.is_type(type_TypedLink)) {
648✔
1152
            fn(val);
210✔
1153
        }
210✔
1154
        else if (val.is_type(type_Dictionary)) {
438✔
1155
            DummyParent parent(this->get_table(), val.get_ref());
12✔
1156
            Dictionary dict(parent, 0);
12✔
1157
            dict.to_json(out, link_depth, output_mode, fn);
12✔
1158
        }
12✔
1159
        else if (val.is_type(type_List)) {
426✔
1160
            DummyParent parent(this->get_table(), val.get_ref());
12✔
1161
            Lst<Mixed> list(parent, 0);
12✔
1162
            list.to_json(out, link_depth, output_mode, fn);
12✔
1163
        }
12✔
1164
        else if (val.is_type(type_Set)) {
414✔
1165
            DummyParent parent(this->get_table(), val.get_ref());
12✔
1166
            Set<Mixed> set(parent, 0);
12✔
1167
            set.to_json(out, link_depth, output_mode, fn);
12✔
1168
        }
12✔
1169
        else {
402✔
1170
            val.to_json(out, output_mode);
402✔
1171
        }
402✔
1172
    }
648✔
1173

252✔
1174
    out << "}";
504✔
1175
    out << close_str;
504✔
1176
}
504✔
1177

1178
ref_type Dictionary::get_collection_ref(Index index, CollectionType type) const
1179
{
1,704✔
1180
    auto ndx = m_values->find_key(index.get_salt());
1,704✔
1181
    if (ndx != realm::not_found) {
1,704✔
1182
        auto val = m_values->get(ndx);
1,692✔
1183
        if (val.is_type(DataType(int(type)))) {
1,692✔
1184
            return val.get_ref();
1,692✔
1185
        }
1,692✔
UNCOV
1186
        throw realm::IllegalOperation(util::format("Not a %1", type));
×
UNCOV
1187
    }
×
1188
    throw StaleAccessor("This collection is no more");
12✔
1189
    return 0;
6✔
1190
}
12✔
1191

1192
bool Dictionary::check_collection_ref(Index index, CollectionType type) const noexcept
1193
{
726✔
1194
    auto ndx = m_values->find_key(index.get_salt());
726✔
1195
    if (ndx != realm::not_found) {
726✔
1196
        return m_values->get(ndx).is_type(DataType(int(type)));
696✔
1197
    }
696✔
1198
    return false;
30✔
1199
}
30✔
1200

1201
void Dictionary::set_collection_ref(Index index, ref_type ref, CollectionType type)
1202
{
390✔
1203
    auto ndx = m_values->find_key(index.get_salt());
390✔
1204
    if (ndx == realm::not_found) {
390✔
UNCOV
1205
        throw StaleAccessor("Collection has been deleted");
×
UNCOV
1206
    }
×
1207
    m_values->set(ndx, Mixed(ref, type));
390✔
1208
}
390✔
1209

1210
bool Dictionary::update_if_needed() const
1211
{
762✔
1212
    auto status = update_if_needed_with_status();
762✔
1213
    if (status == UpdateStatus::Detached) {
762✔
UNCOV
1214
        throw StaleAccessor("CollectionList no longer exists");
×
UNCOV
1215
    }
×
1216
    return status == UpdateStatus::Updated;
762✔
1217
}
762✔
1218

1219
/************************* DictionaryLinkValues *************************/
1220

1221
DictionaryLinkValues::DictionaryLinkValues(const Obj& obj, ColKey col_key)
1222
    : m_source(obj, col_key)
UNCOV
1223
{
×
UNCOV
1224
    REALM_ASSERT_EX(col_key.get_type() == col_type_Link, col_key.get_type());
×
UNCOV
1225
}
×
1226

1227
DictionaryLinkValues::DictionaryLinkValues(const Dictionary& source)
1228
    : m_source(source)
1229
{
3,114✔
1230
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
3,114✔
1231
}
3,114✔
1232

1233
ObjKey DictionaryLinkValues::get_key(size_t ndx) const
1234
{
72✔
1235
    Mixed val = m_source.get_any(ndx);
72✔
1236
    if (val.is_type(type_Link, type_TypedLink)) {
72✔
1237
        return val.get<ObjKey>();
48✔
1238
    }
48✔
1239
    return {};
24✔
1240
}
24✔
1241

1242
Obj DictionaryLinkValues::get_object(size_t row_ndx) const
1243
{
2,400✔
1244
    Mixed val = m_source.get_any(row_ndx);
2,400✔
1245
    if (val.is_type(type_TypedLink)) {
2,400✔
1246
        return get_table()->get_parent_group()->get_object(val.get_link());
2,382✔
1247
    }
2,382✔
1248
    return {};
18✔
1249
}
18✔
1250

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