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

realm / realm-core / 2130

13 Mar 2024 04:51AM UTC coverage: 92.078% (+0.2%) from 91.833%
2130

push

Evergreen

web-flow
Merge pull request #7402 from realm/tg/obj-perf

Make Obj trivial and add a separate ObjCollectionParent type

94732 of 174812 branches covered (54.19%)

531 of 559 new or added lines in 22 files covered. (94.99%)

45 existing lines in 13 files now uncovered.

244506 of 265543 relevant lines covered (92.08%)

5982312.84 hits per line

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

92.64
/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
{
122,298✔
35
    if (key.is_type(type_String)) {
122,298✔
36
        auto str = key.get_string();
122,298✔
37
        if (str.size()) {
122,298✔
38
            if (str[0] == '$')
122,238✔
39
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not start with '$'");
12✔
40
            if (memchr(str.data(), '.', str.size()))
122,226✔
41
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not contain '.'");
12✔
42
        }
122,226✔
43
    }
122,298✔
44
}
122,298✔
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
{
172,614✔
55
    if (!(col_key.is_dictionary() || col_key.get_type() == col_type_Mixed)) {
172,614✔
56
        throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a dictionary");
×
57
    }
×
58
}
172,614✔
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,912✔
64
    set_alloc(alloc);
27,912✔
65
    REALM_ASSERT(ref);
27,912✔
66
    m_dictionary_top.reset(new Array(alloc));
27,912✔
67
    m_dictionary_top->init_from_ref(ref);
27,912✔
68
    m_keys.reset(new BPlusTree<StringData>(alloc));
27,912✔
69
    m_values.reset(new BPlusTreeMixed(alloc));
27,912✔
70
    m_keys->set_parent(m_dictionary_top.get(), 0);
27,912✔
71
    m_values->set_parent(m_dictionary_top.get(), 1);
27,912✔
72
    m_keys->init_from_parent();
27,912✔
73
    m_values->init_from_parent();
27,912✔
74
}
27,912✔
75

76
Dictionary::~Dictionary() = default;
208,368✔
77

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

3,366✔
82
    if (this != &other) {
6,732✔
83
        // Back to scratch
3,366✔
84
        m_dictionary_top.reset();
6,732✔
85
        reset_content_version();
6,732✔
86
    }
6,732✔
87

3,366✔
88
    return *this;
6,732✔
89
}
6,732✔
90

91
size_t Dictionary::size() const
92
{
395,592✔
93
    if (!update())
395,592✔
94
        return 0;
41,808✔
95

176,235✔
96
    return m_values->size();
353,784✔
97
}
353,784✔
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
110
{
×
111
    return get_any(ndx).is_null();
×
112
}
×
113

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

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

130
Mixed Dictionary::get_key(size_t ndx) const
131
{
54,450✔
132
    // Note: `size()` calls `update_if_needed()`.
27,189✔
133
    CollectionBase::validate_index("get_key()", ndx, size());
54,450✔
134
    return do_get_key(ndx);
54,450✔
135
}
54,450✔
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
{
39,018✔
144
    if (update()) {
39,018✔
145
        return do_find_key(key);
30,990✔
146
    }
30,990✔
147

4,014✔
148
    return realm::npos;
8,028✔
149
}
8,028✔
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,580✔
163
            }
38,580✔
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✔
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✔
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✔
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✔
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✔
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✔
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✔
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✔
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✔
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
340
        indices.clear();
×
341
        sz2 = 0;
×
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,219✔
356
        return ascending ? values[i1] < values[i2] : values[i2] < values[i1];
35,577✔
357
    });
36,219✔
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✔
396
            }
×
397
            case type_Int: {
✔
398
                IteratorAdapter help(static_cast<BPlusTree<Int>*>(m_keys.get()));
×
399
                auto is_sorted = std::is_sorted(help.begin(), help.end());
×
400
                REALM_ASSERT(is_sorted);
×
401
                break;
×
402
            }
×
403
            default:
✔
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,344✔
425
    Table& t = *get_target_table();
4,344✔
426
    auto o = t.is_embedded() ? t.create_linked_object() : t.create_object();
4,344✔
427
    insert(key, o.get_key());
4,344✔
428
    return o;
4,344✔
429
}
4,344✔
430

431
void Dictionary::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
432
{
1,530✔
433
    if (dict_or_list == CollectionType::Set) {
1,530✔
434
        throw IllegalOperation("Set nested in Dictionary is not supported");
×
435
    }
×
436

765✔
437
    check_level();
1,530✔
438
    ensure_created();
1,530✔
439
    Mixed new_val(0, dict_or_list);
1,530✔
440
    auto old_val = try_get(path_elem.get_key());
1,530✔
441
    if (!old_val || *old_val != new_val) {
1,530✔
442
        m_values->ensure_keys();
1,392✔
443
        auto [it, inserted] = insert(path_elem.get_key(), new_val);
1,392✔
444
        int64_t key = generate_key(size());
1,392✔
445
        while (m_values->find_key(key) != realm::not_found) {
1,392✔
446
            key++;
765✔
447
        }
448
        m_values->set_key(it.index(), key);
696✔
449
    }
1,026✔
450
}
1,095✔
451

330✔
452
DictionaryPtr Dictionary::get_dictionary(const PathElement& path_elem) const
330✔
453
{
660✔
454
    update();
660✔
455
    auto weak = const_cast<Dictionary*>(this)->weak_from_this();
660✔
456
    auto shared = weak.expired() ? std::make_shared<Dictionary>(*this) : weak.lock();
582✔
457
    DictionaryPtr ret = std::make_shared<Dictionary>(m_col_key, get_level() + 1);
330✔
458
    ret->set_owner(shared, build_index(path_elem.get_key()));
330✔
459
    return ret;
927✔
460
}
927✔
461

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

7,560✔
472
Mixed Dictionary::get(Mixed key) const
7,560✔
473
{
7,554✔
474
    if (auto opt_val = try_get(key)) {
7,554✔
475
        return *opt_val;
7,530✔
476
    }
7,530✔
477
    throw KeyNotFound("Dictionary::get");
421,518✔
478
}
421,518✔
479

421,008✔
480
util::Optional<Mixed> Dictionary::try_get(Mixed key) const
421,008✔
481
{
840,822✔
482
    if (update()) {
840,822✔
483
        auto ndx = do_find_key(key);
423,150✔
484
        if (ndx != realm::npos) {
423,150✔
485
            return do_get(ndx);
421,482✔
486
        }
419,316✔
487
    }
2,166✔
488
    return {};
13,605✔
489
}
13,605✔
490

11,439✔
491
Dictionary::Iterator Dictionary::begin() const
11,439✔
492
{
23,391✔
493
    // Need an update because the `Dictionary::Iterator` constructor relies on
11,439✔
494
    // `m_clusters` to determine if it was already at end.
495
    update();
11,952✔
496
    return Iterator(this, 0);
54,171✔
497
}
54,171✔
498

42,219✔
499
Dictionary::Iterator Dictionary::end() const
500
{
42,732✔
501
    return Iterator(this, size());
97,692✔
502
}
97,692✔
503

54,960✔
504
std::pair<Dictionary::Iterator, bool> Dictionary::insert(Mixed key, Mixed value)
505
{
55,257✔
506
    auto my_table = get_table_unchecked();
110,217✔
507
    if (key.get_type() != m_key_type) {
110,214✔
508
        throw InvalidArgument(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: Invalid key type");
2,634✔
509
    }
×
510
    if (m_col_key) {
55,257✔
511
        if (value.is_null()) {
107,577✔
512
            if (!m_col_key.is_nullable()) {
54,969✔
513
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Value cannot be null");
52,323✔
514
            }
1,413✔
515
        }
52,611✔
516
        else {
52,611✔
517
            if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) {
52,611✔
518
                if (my_table->get_opposite_table_key(m_col_key) != value.get<ObjLink>().get_table_key()) {
52,323✔
519
                    throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
50,913✔
520
                                          "Dictionary::insert: Wrong object type");
3✔
521
                }
3✔
522
            }
102,105✔
523
            else if (m_col_key.get_type() != col_type_Mixed && value.get_type() != DataType(m_col_key.get_type())) {
51,195✔
524
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Wrong value type");
×
525
            }
×
526
            else if (value.is_type(type_Link) && m_col_key.get_type() != col_type_Link) {
106,152✔
527
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
54,957✔
528
                                      "Dictionary::insert: No target table for link");
54,957✔
529
            }
54,957✔
530
        }
110,211✔
531
    }
110,211✔
532

54,957✔
533
    validate_key_value(key);
110,211✔
534
    ensure_created();
59,334✔
535

4,080✔
536
    ObjLink new_link;
59,214✔
537
    if (value.is_type(type_TypedLink)) {
59,334✔
538
        new_link = value.get<ObjLink>();
54,957✔
539
        if (!new_link.is_unresolved())
10,467✔
540
            my_table->get_parent_group()->validate(new_link);
10,347✔
541
    }
10,467✔
542
    else if (value.is_type(type_Link)) {
51,177✔
543
        auto target_table = my_table->get_opposite_table(m_col_key);
6,606✔
544
        auto key = value.get<ObjKey>();
12,987✔
545
        if (!key.is_unresolved() && !target_table->is_valid(key)) {
12,987✔
546
            throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
6,387✔
547
        }
54,960✔
548
        new_link = ObjLink(target_table->get_key(), key);
61,557✔
549
        value = Mixed(new_link);
6,600✔
550
    }
6,600✔
551

54,954✔
552
    if (!m_dictionary_top) {
110,205✔
553
        throw StaleAccessor("Stale dictionary");
54,954✔
554
    }
54,954✔
555

50,031✔
556
    bool old_entry = false;
105,282✔
557
    auto [ndx, actual_key] = find_impl(key);
105,282✔
558
    if (actual_key != key) {
105,282✔
559
        // key does not already exist
50,031✔
560
        switch (m_key_type) {
50,328✔
561
            case type_String:
50,328✔
562
                static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key.get_string());
50,328✔
563
                break;
50,328✔
564
            case type_Int:
✔
565
                static_cast<BPlusTree<Int>*>(m_keys.get())->insert(ndx, key.get_int());
50,031✔
566
                break;
50,031✔
567
            default:
50,031✔
568
                break;
4,923✔
569
        }
55,251✔
570
        m_values->insert(ndx, value);
55,251✔
571
    }
105,282✔
572
    else {
59,877✔
573
        old_entry = true;
53,055✔
574
    }
9,219✔
575

4,296✔
576
    if (Replication* repl = get_replication()) {
99,087✔
577
        if (old_entry) {
92,265✔
578
            repl->dictionary_set(*this, ndx, key, value);
48,132✔
579
        }
52,428✔
580
        else {
99,087✔
581
            repl->dictionary_insert(*this, ndx, key, value);
99,087✔
582
        }
99,087✔
583
    }
103,383✔
584

54,954✔
585
    bump_content_version();
60,147✔
586

4,896✔
587
    ObjLink old_link;
55,635✔
588
    if (old_entry) {
55,635✔
589
        Mixed old_value = m_values->get(ndx);
9,792✔
590
        if (old_value.is_type(type_TypedLink)) {
9,792✔
591
            old_link = old_value.get<ObjLink>();
55,338✔
592
        }
55,338✔
593
        m_values->set(ndx, value);
15,384✔
594
    }
15,384✔
595

10,488✔
596
    if (new_link != old_link) {
55,398✔
597
        CascadeState cascade_state(CascadeState::Mode::Strong);
21,192✔
598
        bool recurse = Base::replace_backlink(m_col_key, old_link, new_link, cascade_state);
65,658✔
599
        if (recurse)
65,658✔
600
            _impl::TableFriend::remove_recursive(*my_table, cascade_state); // Throws
55,101✔
601
    }
10,704✔
602

603
    return {Iterator(this, ndx), !old_entry};
461,652✔
604
}
461,652✔
605

406,401✔
606
const Mixed Dictionary::operator[](Mixed key)
3✔
607
{
406,404✔
608
    auto ret = try_get(key);
406,404✔
609
    if (!ret) {
812,802✔
610
        ret = Mixed{};
406,404✔
611
        insert(key, Mixed{});
406,404✔
612
    }
3✔
613

614
    return *ret;
412,608✔
615
}
412,608✔
616

4,608✔
617
Obj Dictionary::get_object(StringData key)
4,548✔
618
{
10,758✔
619
    if (auto val = try_get(key)) {
7,869✔
620
        if ((*val).is_type(type_TypedLink)) {
6,270✔
621
            return get_table()->get_parent_group()->get_object((*val).get_link());
6,210✔
622
        }
4,551✔
623
    }
1,659✔
624
    return {};
3,195✔
625
}
3,195✔
626

1,536✔
627
bool Dictionary::contains(Mixed key) const noexcept
628
{
1,536✔
629
    return find_any_key(key) != realm::npos;
18,963✔
630
}
18,963✔
631

17,427✔
632
Dictionary::Iterator Dictionary::find(Mixed key) const noexcept
9,756✔
633
{
27,183✔
634
    auto ndx = find_any_key(key);
25,098✔
635
    if (ndx != realm::npos) {
25,098✔
636
        return Iterator(this, ndx);
9,756✔
637
    }
9,756✔
638
    return end();
8,175✔
639
}
8,175✔
640

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

153✔
648
size_t Dictionary::find_index(const Index& index) const
153✔
649
{
153✔
650
    update();
153✔
651
    return m_values->find_key(index.get_salt());
752,391✔
652
}
752,391✔
653

3✔
654
UpdateStatus Dictionary::update_if_needed_with_status() const
3✔
655
{
699,213✔
656
    auto status = Base::get_update_status();
699,210✔
657
    switch (status) {
1,355,559✔
658
        case UpdateStatus::Detached: {
656,352✔
659
            m_dictionary_top.reset();
633,876✔
660
            return UpdateStatus::Detached;
633,876✔
661
        }
22,476✔
662
        case UpdateStatus::NoChange: {
647,061✔
663
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
647,061✔
664
                return UpdateStatus::NoChange;
628,938✔
665
            }
724,824✔
666
            // The tree has not been initialized yet for this accessor, so
118,362✔
667
            // perform lazy initialization by treating it as an update.
668
            [[fallthrough]];
18,123✔
669
        }
18,123✔
670
        case UpdateStatus::Updated: {
92,745✔
671
            // Try to initialize. If the dictionary is not initialized
672
            // the function will return false;
696,525✔
673
            bool attached = init_from_parent(false);
789,270✔
674
            Base::update_content_version();
789,270✔
675
            CollectionParent::m_parent_version++;
789,270✔
676
            return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
61,812✔
677
        }
678
    }
55,713✔
679
    REALM_UNREACHABLE();
55,713✔
680
}
55,713✔
681

682
void Dictionary::ensure_created()
683
{
111,723✔
684
    if (Base::should_update() || !(m_dictionary_top && m_dictionary_top->is_attached())) {
56,010✔
685
        // When allow_create is true, init_from_parent will always succeed
686
        // In case of errors, an exception is thrown.
6,063✔
687
        constexpr bool allow_create = true;
33,195✔
688
        init_from_parent(allow_create); // Throws
33,195✔
689
        CollectionParent::m_parent_version++;
27,132✔
690
        Base::update_content_version();
33,195✔
691
    }
33,195✔
692
}
62,073✔
693

615✔
694
bool Dictionary::try_erase(Mixed key)
615✔
695
{
11,478✔
696
    validate_key_value(key);
11,478✔
697
    if (!update())
11,478✔
698
        return false;
5,448✔
699

5,448✔
700
    auto ndx = do_find_key(key);
6,030✔
701
    if (ndx == realm::npos) {
6,030✔
702
        return false;
6,123✔
703
    }
6,123✔
704

309✔
705
    do_erase(ndx, key);
5,724✔
706

5,508✔
707
    return true;
5,415✔
708
}
5,415✔
709

1,308✔
710
void Dictionary::erase(Mixed key)
1,308✔
711
{
6,783✔
712
    if (!try_erase(key)) {
6,783✔
713
        throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
1,617✔
714
    }
1,617✔
715
}
5,715✔
716

1,308✔
717
auto Dictionary::erase(Iterator it) -> Iterator
1,308✔
718
{
1,308✔
719
    auto pos = it.m_ndx;
1,308✔
720
    CollectionBase::validate_index("erase()", pos, size());
1,671✔
721

363✔
722
    do_erase(pos, do_get_key(pos));
1,671✔
723
    if (pos < size())
1,671✔
724
        pos++;
603✔
725
    return {this, pos};
1,662✔
726
}
1,662✔
727

354✔
728
void Dictionary::nullify(size_t ndx)
363✔
729
{
726✔
730
    REALM_ASSERT(m_dictionary_top);
726✔
731
    REALM_ASSERT(ndx != realm::npos);
363✔
732

733
    if (Replication* repl = get_replication()) {
732✔
734
        auto key = do_get_key(ndx);
723✔
735
        repl->dictionary_set(*this, ndx, key, Mixed());
723✔
736
    }
717✔
737

363✔
738
    m_values->set(ndx, Mixed());
726✔
739
}
369✔
740

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

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

5,448✔
802
bool Dictionary::remove_backlinks(CascadeState& state) const
1,524✔
803
{
3,054✔
804
    size_t sz = size();
1,530✔
805
    bool recurse = false;
1,530✔
806
    for (size_t ndx = 0; ndx < sz; ndx++) {
30,258✔
807
        if (clear_backlink(ndx, state)) {
28,728✔
808
            recurse = true;
23,451✔
809
        }
189✔
810
    }
5,466✔
811
    return recurse;
2,892✔
812
}
2,892✔
813

1,245✔
814
size_t Dictionary::find_first(Mixed value) const
1,239✔
815
{
24,573✔
816
    return update() ? m_values->find_first(value) : realm::not_found;
24,579✔
817
}
24,579✔
818

1,245✔
819
void Dictionary::clear()
1,245✔
820
{
2,607✔
821
    if (size() > 0) {
2,607✔
822
        if (Replication* repl = get_replication()) {
2,490✔
823
            repl->dictionary_clear(*this);
2,484✔
824
        }
2,484✔
825
        CascadeState cascade_state(CascadeState::Mode::Strong);
2,490✔
826
        bool recurse = remove_backlinks(cascade_state);
1,248✔
827

1,245✔
828
        // Just destroy the whole cluster
1,362✔
829
        m_dictionary_top->destroy_deep();
1,245✔
830
        m_dictionary_top.reset();
1,245✔
831

118,362✔
832
        update_child_ref(0, 0);
119,607✔
833

118,362✔
834
        if (recurse)
119,607✔
835
            _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
118,365✔
836
    }
73,947✔
837
}
74,064✔
838

72,702✔
839
bool Dictionary::init_from_parent(bool allow_create) const
72,702✔
840
{
192,579✔
841
    try {
192,579✔
842
        auto ref = Base::get_collection_ref();
192,579✔
843
        if ((ref || allow_create) && !m_dictionary_top) {
119,877✔
844
            Allocator& alloc = get_alloc();
73,119✔
845
            m_dictionary_top.reset(new Array(alloc));
73,119✔
846
            m_dictionary_top->set_parent(const_cast<Dictionary*>(this), 0);
73,119✔
847
            switch (m_key_type) {
73,119✔
848
                case type_String: {
73,119✔
849
                    m_keys.reset(new BPlusTree<StringData>(alloc));
73,119✔
850
                    break;
145,821✔
851
                }
72,702✔
852
                case type_Int: {
72,702✔
853
                    m_keys.reset(new BPlusTree<Int>(alloc));
72,702✔
854
                    break;
72,702✔
855
                }
118,362✔
856
                default:
118,362✔
857
                    break;
73,032✔
858
            }
146,151✔
859
            m_keys->set_parent(m_dictionary_top.get(), 0);
146,151✔
860
            m_values.reset(new BPlusTreeMixed(alloc));
146,151✔
861
            m_values->set_parent(m_dictionary_top.get(), 1);
118,449✔
862
        }
118,449✔
863

45,330✔
864
        if (ref) {
149,961✔
865
            m_dictionary_top->init_from_ref(ref);
103,707✔
866
            m_keys->init_from_parent();
103,707✔
867
            m_values->init_from_parent();
88,869✔
868
        }
88,869✔
869
        else {
61,500✔
870
            // dictionary detached
15,246✔
871
            if (!allow_create) {
61,500✔
872
                m_dictionary_top.reset();
46,170✔
873
                return false;
46,170✔
874
            }
149,286✔
875

118,362✔
876
            // Create dictionary
15✔
877
            m_dictionary_top->create(Array::type_HasRefs, false, 2, 0);
15,345✔
878
            m_values->create();
15,345✔
879
            m_keys->create();
15,345✔
880
            m_dictionary_top->update_parent();
15,345✔
881
        }
133,692✔
882

883
        return true;
88,953✔
884
    }
442,575✔
885
    catch (...) {
442,575✔
886
        m_dictionary_top.reset();
442,575✔
887
        throw;
435,816✔
888
    }
435,816✔
889
}
126,636✔
890

6,759✔
891
size_t Dictionary::do_find_key(Mixed key) const noexcept
892
{
442,503✔
893
    auto [ndx, actual_key] = find_impl(key);
940,017✔
894
    if (actual_key == key) {
940,017✔
895
        return ndx;
933,258✔
896
    }
933,258✔
897
    return realm::npos;
488,076✔
898
}
488,076✔
899

481,317✔
900
std::pair<size_t, Mixed> Dictionary::find_impl(Mixed key) const noexcept
481,317✔
901
{
979,071✔
902
    auto sz = m_keys->size();
979,071✔
903
    Mixed actual;
979,071✔
904
    if (sz && key.is_type(m_key_type)) {
953,406✔
905
        switch (m_key_type) {
937,125✔
906
            case type_String: {
962,790✔
907
                auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
481,473✔
908
                StringData val = key.get<StringData>();
481,473✔
909
                IteratorAdapter help(keys);
481,473✔
910
                auto it = std::lower_bound(help.begin(), help.end(), val);
481,473✔
911
                if (it.index() < sz) {
481,473✔
912
                    actual = *it;
455,628✔
913
                }
455,628✔
914
                return {it.index(), actual};
481,473✔
915
                break;
×
916
            }
×
917
            case type_Int: {
✔
918
                auto keys = static_cast<BPlusTree<Int>*>(m_keys.get());
×
919
                Int val = key.get<Int>();
×
920
                IteratorAdapter help(keys);
×
921
                auto it = std::lower_bound(help.begin(), help.end(), val);
×
922
                if (it.index() < sz) {
16,197!
923
                    actual = *it;
16,197✔
924
                }
16,197✔
925
                return {it.index(), actual};
16,197✔
926
                break;
16,197✔
927
            }
928
            default:
2✔
929
                break;
474,456✔
930
        }
490,737✔
931
    }
490,737✔
932

474,456✔
933
    return {sz, actual};
490,737✔
934
}
16,833✔
935

552✔
936
Mixed Dictionary::do_get(size_t ndx) const
473,904✔
937
{
948,633✔
938
    Mixed val = m_values->get(ndx);
474,729✔
939

940
    // Filter out potential unresolved links
6,750✔
941
    if (val.is_type(type_TypedLink) && val.get<ObjKey>().is_unresolved()) {
481,479✔
942
        return {};
7,302✔
943
    }
7,302✔
944
    return val;
480,927✔
945
}
474,228✔
946

6,750✔
947
void Dictionary::do_erase(size_t ndx, Mixed key)
6,750✔
948
{
12,840✔
949
    CascadeState cascade_state(CascadeState::Mode::Strong);
12,840✔
950
    bool recurse = clear_backlink(ndx, cascade_state);
13,467✔
951

6,750✔
952
    if (recurse)
13,467✔
953
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
6,801✔
954

6,750✔
955
    if (Replication* repl = get_replication()) {
6,717✔
956
        repl->dictionary_erase(*this, ndx, key);
6,090✔
957
    }
82,071✔
958

75,981✔
959
    m_keys->erase(ndx);
82,698✔
960
    m_values->erase(ndx);
82,698✔
961
    bump_content_version();
6,717✔
962
}
6,717✔
963

964
Mixed Dictionary::do_get_key(size_t ndx) const
965
{
76,347✔
966
    switch (m_key_type) {
76,347✔
967
        case type_String: {
76,347✔
968
            return static_cast<BPlusTree<StringData>*>(m_keys.get())->get(ndx);
76,347✔
969
        }
×
970
        case type_Int: {
✔
971
            return static_cast<BPlusTree<Int>*>(m_keys.get())->get(ndx);
972
        }
973
        default:
46,683✔
974
            break;
46,683✔
975
    }
46,683✔
976

977
    return {};
978
}
12,198✔
979

12,198✔
980
std::pair<Mixed, Mixed> Dictionary::do_get_pair(size_t ndx) const
12,198✔
981
{
50,886✔
982
    return {do_get_key(ndx), do_get(ndx)};
50,886✔
983
}
55,266✔
984

15✔
985
bool Dictionary::clear_backlink(size_t ndx, CascadeState& state) const
15✔
986
{
12,198✔
987
    auto value = m_values->get(ndx);
20,457✔
988
    if (value.is_type(type_TypedLink)) {
12,213✔
989
        return Base::remove_backlink(m_col_key, value.get_link(), state);
3,957✔
990
    }
3,957✔
991
    if (value.is_type(type_Dictionary)) {
16,500✔
992
        auto key = do_get_key(ndx);
8,259✔
993
        return get_dictionary(key.get_string())->remove_backlinks(state);
15✔
994
    }
15✔
995
    if (value.is_type(type_List)) {
8,241✔
996
        auto key = do_get_key(ndx);
30✔
997
        return get_list(key.get_string())->remove_backlinks(state);
30✔
998
    }
30✔
999
    return false;
8,211✔
1000
}
8,211✔
1001

1002
void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, size_t index2)
1003
{
×
UNCOV
1004
    std::string buf1, buf2;
×
1005

1006
    // Swap keys
1007
    REALM_ASSERT(m_key_type == type_String);
×
1008
    ArrayString keys(get_alloc());
×
UNCOV
1009
    keys.set_parent(&fields1, 1);
×
1010
    keys.init_from_parent();
×
1011
    buf1 = keys.get(index1);
×
1012

UNCOV
1013
    keys.set_parent(&fields2, 1);
×
UNCOV
1014
    keys.init_from_parent();
×
1015
    buf2 = keys.get(index2);
×
1016
    keys.set(index2, buf1);
×
1017

1018
    keys.set_parent(&fields1, 1);
×
1019
    keys.init_from_parent();
×
UNCOV
1020
    keys.set(index1, buf2);
×
1021

1022
    // Swap values
1023
    ArrayMixed values(get_alloc());
×
1024
    values.set_parent(&fields1, 2);
×
1025
    values.init_from_parent();
×
UNCOV
1026
    Mixed val1 = values.get(index1);
×
1027
    val1.use_buffer(buf1);
×
1028

1029
    values.set_parent(&fields2, 2);
×
1030
    values.init_from_parent();
×
1031
    Mixed val2 = values.get(index2);
1032
    val2.use_buffer(buf2);
1033
    values.set(index2, val1);
×
1034

1035
    values.set_parent(&fields1, 2);
×
1036
    values.init_from_parent();
×
1037
    values.set(index1, val2);
1038
}
1039

1,533✔
1040
Mixed Dictionary::find_value(Mixed value) const noexcept
1,533✔
1041
{
1,533✔
1042
    size_t ndx = update() ? m_values->find_first(value) : realm::npos;
1,533!
1043
    return (ndx == realm::npos) ? Mixed{} : do_get_key(ndx);
1,533!
1044
}
1045

1046
StableIndex Dictionary::build_index(Mixed key) const
1047
{
7,623✔
1048
    auto it = find(key);
7,623✔
1049
    int64_t index = (it != end()) ? m_values->get_key(it.index()) : 0;
7,623✔
1050
    return {index};
7,623✔
1051
}
7,623✔
1052

1053

1054
void Dictionary::verify() const
85,887✔
1055
{
91,977✔
1056
    m_keys->verify();
91,977✔
1057
    m_values->verify();
6,090✔
1058
    REALM_ASSERT(m_keys->size() == m_values->size());
91,977✔
1059
}
6,090✔
1060

1061
void Dictionary::get_key_type()
3✔
1062
{
86,730✔
1063
    m_key_type = get_table()->get_dictionary_key_type(m_col_key);
86,730✔
1064
    if (!(m_key_type == type_String || m_key_type == type_Int))
86,730!
1065
        throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary keys can only be strings or integers");
3✔
1066
}
86,730✔
1067

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

3✔
1079
        std::unique_ptr<ClusterNode> get_root_from_parent() final
6✔
1080
        {
6✔
1081
            return create_root_from_parent(m_owner, m_top_position_for_cluster_tree);
6✔
1082
        }
6✔
1083

3✔
1084
    private:
6✔
1085
        ArrayParent* m_owner;
6✔
1086
    };
6✔
1087

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

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

1126
template <>
219✔
1127
void CollectionBaseImpl<DictionaryBase>::to_json(std::ostream&, JSONOutputMode,
219✔
1128
                                                 util::FunctionRef<void(const Mixed&)>) const
78✔
1129
{
78✔
1130
}
219✔
1131

219✔
1132
void Dictionary::to_json(std::ostream& out, JSONOutputMode output_mode,
219✔
1133
                         util::FunctionRef<void(const Mixed&)> fn) const
483✔
1134
{
483✔
1135
    if (output_mode == output_mode_xjson_plus) {
282✔
1136
        out << "{ \"$dictionary\": ";
342✔
1137
    }
342✔
1138
    out << "{";
483✔
1139

72✔
1140
    auto sz = size();
291✔
1141
    for (size_t i = 0; i < sz; i++) {
675✔
1142
        if (i > 0)
270✔
1143
            out << ",";
69✔
1144
        out << do_get_key(i) << ":";
270✔
1145
        Mixed val = do_get(i);
270✔
1146
        if (val.is_type(type_TypedLink)) {
450✔
1147
            fn(val);
78✔
1148
        }
78✔
1149
        else if (val.is_type(type_Dictionary)) {
198✔
1150
            DummyParent parent(this->get_table(), val.get_ref());
12✔
1151
            Dictionary dict(parent, 0);
186✔
1152
            dict.to_json(out, output_mode, fn);
186✔
1153
        }
186✔
1154
        else if (val.is_type(type_List)) {
450✔
1155
            DummyParent parent(this->get_table(), val.get_ref());
225✔
1156
            Lst<Mixed> list(parent, 0);
225✔
1157
            list.to_json(out, output_mode, fn);
225✔
1158
        }
84✔
1159
        else {
258✔
1160
            val.to_json(out, output_mode);
399✔
1161
        }
180✔
1162
    }
264✔
1163

3,261✔
1164
    out << "}";
3,480✔
1165
    if (output_mode == output_mode_xjson_plus) {
3,480✔
1166
        out << "}";
3,336✔
1167
    }
3,336✔
1168
}
3,477✔
1169

3,258✔
1170
ref_type Dictionary::get_collection_ref(Index index, CollectionType type) const
1171
{
2,751✔
1172
    auto ndx = m_values->find_key(index.get_salt());
2,754✔
1173
    if (ndx != realm::not_found) {
2,754✔
1174
        auto val = m_values->get(ndx);
2,748✔
1175
        if (val.is_type(DataType(int(type)))) {
2,748✔
1176
            return val.get_ref();
3,255✔
1177
        }
3,255✔
1178
        throw realm::IllegalOperation(util::format("Not a %1", type));
507✔
1179
    }
498✔
1180
    throw StaleAccessor("This collection is no more");
501✔
1181
    return 0;
9✔
1182
}
12✔
1183

1184
bool Dictionary::check_collection_ref(Index index, CollectionType type) const noexcept
1185
{
1,332✔
1186
    auto ndx = m_values->find_key(index.get_salt());
1,332✔
1187
    if (ndx != realm::not_found) {
1,332✔
1188
        return m_values->get(ndx).is_type(DataType(int(type)));
498✔
1189
    }
498✔
1190
    return false;
834✔
1191
}
834✔
1192

1193
void Dictionary::set_collection_ref(Index index, ref_type ref, CollectionType type)
1194
{
825✔
1195
    auto ndx = m_values->find_key(index.get_salt());
825✔
1196
    if (ndx == realm::not_found) {
825✔
1197
        throw StaleAccessor("Collection has been deleted");
×
1198
    }
×
1199
    m_values->set(ndx, Mixed(ref, type));
825✔
1200
}
825✔
1201

1202
bool Dictionary::update_if_needed() const
1203
{
2,679✔
1204
    auto status = update_if_needed_with_status();
2,679✔
1205
    if (status == UpdateStatus::Detached) {
2,679✔
1206
        throw StaleAccessor("CollectionList no longer exists");
1207
    }
1208
    return status == UpdateStatus::Updated;
1,158✔
1209
}
1,158✔
1210

36✔
1211
/************************* DictionaryLinkValues *************************/
24✔
1212

24✔
1213
DictionaryLinkValues::DictionaryLinkValues(const Obj& obj, ColKey col_key)
12✔
1214
    : m_source(obj, col_key)
12✔
1215
{
1216
    REALM_ASSERT_EX(col_key.get_type() == col_type_Link, col_key.get_type());
×
1217
}
1,200✔
1218

1,200✔
1219
DictionaryLinkValues::DictionaryLinkValues(const Dictionary& source)
1,200✔
1220
    : m_source(source)
1,191✔
1221
{
2,748✔
1222
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
1,566✔
1223
}
1,566✔
1224

1225
ObjKey DictionaryLinkValues::get_key(size_t ndx) const
1226
{
36✔
1227
    Mixed val = m_source.get_any(ndx);
36✔
1228
    if (val.is_type(type_Link, type_TypedLink)) {
36✔
1229
        return val.get<ObjKey>();
24✔
1230
    }
24✔
1231
    return {};
12✔
1232
}
12✔
1233

1234
Obj DictionaryLinkValues::get_object(size_t row_ndx) const
1235
{
1,200✔
1236
    Mixed val = m_source.get_any(row_ndx);
1,200✔
1237
    if (val.is_type(type_TypedLink)) {
1,200✔
1238
        return get_table()->get_parent_group()->get_object(val.get_link());
1,191✔
1239
    }
1,191✔
1240
    return {};
9✔
1241
}
9✔
1242

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