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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

85.52
/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
{
485,970✔
35
    if (key.is_type(type_String)) {
485,970✔
36
        auto str = key.get_string();
485,970✔
37
        if (str.size()) {
485,970✔
38
            if (str[0] == '$')
485,910✔
39
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not start with '$'");
12✔
40
            if (memchr(str.data(), '.', str.size()))
485,898✔
41
                throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: key must not contain '.'");
12✔
42
        }
485,898✔
43
    }
485,970✔
44
}
485,970✔
45

46
} // namespace
47

48

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

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

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

76
Dictionary::~Dictionary() = default;
781,890✔
77

78
Dictionary& Dictionary::operator=(const Dictionary& other)
79
{
101,736✔
80
    if (this != &other) {
101,736✔
81
        Base::operator=(other);
101,736✔
82
        CollectionParent::operator=(other);
101,736✔
83

84
        // Back to scratch
85
        m_dictionary_top.reset();
101,736✔
86
        reset_content_version();
101,736✔
87
    }
101,736✔
88

89
    return *this;
101,736✔
90
}
101,736✔
91

92
size_t Dictionary::size() const
93
{
1,583,877✔
94
    if (!update())
1,583,877✔
95
        return 0;
44,742✔
96

97
    return m_values->size();
1,539,135✔
98
}
1,583,877✔
99

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

105
DataType Dictionary::get_value_data_type() const
106
{
25,128✔
107
    return DataType(m_col_key.get_type());
25,128✔
108
}
25,128✔
109

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

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

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

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

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

143
size_t Dictionary::find_any_key(Mixed key) const noexcept
144
{
138,834✔
145
    if (update()) {
138,834✔
146
        return do_find_key(key);
130,806✔
147
    }
130,806✔
148

149
    return realm::npos;
8,028✔
150
}
138,834✔
151

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

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

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

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

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

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

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

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

252
namespace {
253
bool can_minmax(DataType type)
254
{
606✔
255
    switch (type) {
606✔
256
        case type_Int:
90✔
257
        case type_Float:
138✔
258
        case type_Double:
186✔
259
        case type_Decimal:
234✔
260
        case type_Mixed:
318✔
261
        case type_Timestamp:
366✔
262
            return true;
366✔
263
        default:
240✔
264
            return false;
240✔
265
    }
606✔
266
}
606✔
267
bool can_sum(DataType type)
268
{
600✔
269
    switch (type) {
600✔
270
        case type_Int:
84✔
271
        case type_Float:
132✔
272
        case type_Double:
180✔
273
        case type_Decimal:
228✔
274
        case type_Mixed:
312✔
275
            return true;
312✔
276
        default:
288✔
277
            return false;
288✔
278
    }
600✔
279
}
600✔
280
} // anonymous namespace
281

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

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

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

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

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

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

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

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

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

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

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

423

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

432
void Dictionary::insert_collection(const PathElement& path_elem, CollectionType dict_or_list)
433
{
50,994✔
434
    if (dict_or_list == CollectionType::Set) {
50,994✔
435
        throw IllegalOperation("Set nested in Dictionary is not supported");
×
436
    }
×
437
    check_level();
50,994✔
438
    insert(path_elem.get_key(), Mixed(0, dict_or_list));
50,994✔
439
}
50,994✔
440

441
template <class T>
442
inline std::shared_ptr<T> Dictionary::do_get_collection(const PathElement& path_elem)
443
{
100,338✔
444
    update();
100,338✔
445
    auto get_shared = [&]() -> std::shared_ptr<CollectionParent> {
100,338✔
446
        auto weak = weak_from_this();
100,338✔
447

448
        if (weak.expired()) {
100,338✔
449
            REALM_ASSERT_DEBUG(m_level == 1);
96,828✔
450
            return std::make_shared<Dictionary>(*this);
96,828✔
451
        }
96,828✔
452

453
        return weak.lock();
3,510✔
454
    };
100,338✔
455

456
    auto shared = get_shared();
100,338✔
457
    auto ret = std::make_shared<T>(m_col_key, get_level() + 1);
100,338✔
458
    ret->set_owner(shared, build_index(path_elem.get_key()));
100,338✔
459
    return ret;
100,338✔
460
}
100,338✔
461

462
DictionaryPtr Dictionary::get_dictionary(const PathElement& path_elem) const
463
{
98,184✔
464
    return const_cast<Dictionary*>(this)->do_get_collection<Dictionary>(path_elem);
98,184✔
465
}
98,184✔
466

467
std::shared_ptr<Lst<Mixed>> Dictionary::get_list(const PathElement& path_elem) const
468
{
2,154✔
469
    return const_cast<Dictionary*>(this)->do_get_collection<Lst<Mixed>>(path_elem);
2,154✔
470
}
2,154✔
471

472
Mixed Dictionary::get(Mixed key) const
473
{
15,651✔
474
    if (auto opt_val = try_get(key)) {
15,651✔
475
        return *opt_val;
15,627✔
476
    }
15,627✔
477
    throw KeyNotFound("Dictionary::get");
24✔
478
}
15,651✔
479

480
util::Optional<Mixed> Dictionary::try_get(Mixed key) const
481
{
843,975✔
482
    if (update()) {
843,975✔
483
        auto ndx = do_find_key(key);
842,763✔
484
        if (ndx != realm::npos) {
842,763✔
485
            return do_get(ndx);
840,429✔
486
        }
840,429✔
487
    }
842,763✔
488
    return {};
3,546✔
489
}
843,975✔
490

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

499
Dictionary::Iterator Dictionary::end() const
500
{
183,795✔
501
    return Iterator(this, size());
183,795✔
502
}
183,795✔
503

504
std::pair<Dictionary::Iterator, bool> Dictionary::insert(Mixed key, Mixed value)
505
{
473,826✔
506
    auto my_table = get_table_unchecked();
473,826✔
507
    if (key.get_type() != m_key_type) {
473,826✔
508
        throw InvalidArgument(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: Invalid key type");
×
509
    }
×
510
    if (m_col_key) {
473,826✔
511
        if (value.is_null()) {
473,817✔
512
            if (!m_col_key.is_nullable()) {
5,334✔
513
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue, "Dictionary::insert: Value cannot be null");
×
514
            }
×
515
        }
5,334✔
516
        else {
468,483✔
517
            if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) {
468,483✔
518
                if (my_table->get_opposite_table_key(m_col_key) != value.get<ObjLink>().get_table_key()) {
2,850✔
519
                    throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
6✔
520
                                          "Dictionary::insert: Wrong object type");
6✔
521
                }
6✔
522
            }
2,850✔
523
            else if (m_col_key.get_type() != col_type_Mixed && value.get_type() != DataType(m_col_key.get_type())) {
465,633✔
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) {
465,633✔
527
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
×
528
                                      "Dictionary::insert: No target table for link");
×
529
            }
×
530
        }
468,483✔
531
    }
473,817✔
532

533
    validate_key_value(key);
473,820✔
534
    ensure_created();
473,820✔
535

536
    ObjLink new_link;
473,820✔
537
    if (value.is_type(type_TypedLink)) {
473,820✔
538
        new_link = value.get<ObjLink>();
152,358✔
539
        if (!new_link.is_unresolved())
152,358✔
540
            my_table->get_parent_group()->validate(new_link);
152,115✔
541
    }
152,358✔
542
    else if (value.is_type(type_Link)) {
321,462✔
543
        auto target_table = my_table->get_opposite_table(m_col_key);
13,128✔
544
        auto key = value.get<ObjKey>();
13,128✔
545
        if (!key.is_unresolved() && !target_table->is_valid(key)) {
13,128✔
546
            throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
6✔
547
        }
6✔
548
        new_link = ObjLink(target_table->get_key(), key);
13,122✔
549
        value = Mixed(new_link);
13,122✔
550
    }
13,122✔
551

552
    if (!m_dictionary_top) {
473,814✔
553
        throw StaleAccessor("Stale dictionary");
×
554
    }
×
555

556
    bool set_nested_collection_key = value.is_type(type_Dictionary, type_List);
473,814✔
557
    bool old_entry = false;
473,814✔
558
    auto [ndx, actual_key] = find_impl(key);
473,814✔
559
    if (actual_key != key) {
473,814✔
560
        // key does not already exist
561
        switch (m_key_type) {
463,425✔
562
            case type_String:
463,425✔
563
                static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key.get_string());
463,425✔
564
                break;
463,425✔
565
            case type_Int:
✔
566
                static_cast<BPlusTree<Int>*>(m_keys.get())->insert(ndx, key.get_int());
×
567
                break;
×
568
            default:
✔
569
                break;
×
570
        }
463,425✔
571
        m_values->insert(ndx, value);
463,416✔
572
    }
463,416✔
573
    else {
10,389✔
574
        old_entry = true;
10,389✔
575
    }
10,389✔
576

577
    if (Replication* repl = get_replication()) {
473,805✔
578
        if (old_entry) {
99,510✔
579
            repl->dictionary_set(*this, ndx, key, value);
9,117✔
580
        }
9,117✔
581
        else {
90,393✔
582
            repl->dictionary_insert(*this, ndx, key, value);
90,393✔
583
        }
90,393✔
584
    }
99,510✔
585
    bump_content_version();
473,805✔
586

587
    ObjLink old_link;
473,805✔
588
    if (old_entry) {
473,805✔
589
        Mixed old_value = m_values->get(ndx);
10,329✔
590
        if (old_value.is_type(type_TypedLink)) {
10,329✔
591
            old_link = old_value.get<ObjLink>();
816✔
592
        }
816✔
593
        if (!value.is_same_type(old_value) || value != old_value) {
10,329✔
594
            m_values->set(ndx, value);
7,644✔
595
        }
7,644✔
596
        else {
2,685✔
597
            set_nested_collection_key = false;
2,685✔
598
        }
2,685✔
599
    }
10,329✔
600

601
    if (set_nested_collection_key) {
473,805✔
602
        m_values->ensure_keys();
50,700✔
603
        set_key(*m_values, ndx);
50,700✔
604
    }
50,700✔
605

606
    if (new_link != old_link) {
473,805✔
607
        CascadeState cascade_state(CascadeState::Mode::Strong);
165,480✔
608
        bool recurse = Base::replace_backlink(m_col_key, old_link, new_link, cascade_state);
165,480✔
609
        if (recurse)
165,480✔
610
            _impl::TableFriend::remove_recursive(*my_table, cascade_state); // Throws
294✔
611
    }
165,480✔
612

613
    return {Iterator(this, ndx), !old_entry};
473,805✔
614
}
473,814✔
615

616
const Mixed Dictionary::operator[](Mixed key)
617
{
812,802✔
618
    auto ret = try_get(key);
812,802✔
619
    if (!ret) {
812,802✔
620
        ret = Mixed{};
6✔
621
        insert(key, Mixed{});
6✔
622
    }
6✔
623

624
    return *ret;
812,802✔
625
}
812,802✔
626

627
Obj Dictionary::get_object(StringData key)
628
{
12,435✔
629
    if (auto val = try_get(key)) {
12,435✔
630
        if ((*val).is_type(type_TypedLink)) {
9,237✔
631
            return get_table()->get_parent_group()->get_object((*val).get_link());
9,117✔
632
        }
9,117✔
633
    }
9,237✔
634
    return {};
3,318✔
635
}
12,435✔
636

637
bool Dictionary::contains(Mixed key) const noexcept
638
{
4,050✔
639
    return find_any_key(key) != realm::npos;
4,050✔
640
}
4,050✔
641

642
Dictionary::Iterator Dictionary::find(Mixed key) const noexcept
643
{
133,692✔
644
    auto ndx = find_any_key(key);
133,692✔
645
    if (ndx != realm::npos) {
133,692✔
646
        return Iterator(this, ndx);
118,344✔
647
    }
118,344✔
648
    return end();
15,348✔
649
}
133,692✔
650

651
void Dictionary::add_index(Path& path, const Index& index) const
652
{
2,526✔
653
    auto ndx = m_values->find_key(index.get_salt());
2,526✔
654
    auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
2,526✔
655
    path.emplace_back(keys->get(ndx));
2,526✔
656
}
2,526✔
657

658
size_t Dictionary::find_index(const Index& index) const
659
{
306✔
660
    update();
306✔
661
    return m_values->find_key(index.get_salt());
306✔
662
}
306✔
663

664
UpdateStatus Dictionary::do_update_if_needed(bool allow_create) const
665
{
3,917,976✔
666
    switch (get_update_status()) {
3,917,976✔
667
        case UpdateStatus::Detached: {
6✔
668
            m_dictionary_top.reset();
6✔
669
            return UpdateStatus::Detached;
6✔
670
        }
×
671
        case UpdateStatus::NoChange: {
3,111,015✔
672
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
3,111,015✔
673
                return UpdateStatus::NoChange;
3,064,008✔
674
            }
3,064,008✔
675
            // The tree has not been initialized yet for this accessor, so
676
            // perform lazy initialization by treating it as an update.
677
            [[fallthrough]];
3,111,015✔
678
        }
47,007✔
679
        case UpdateStatus::Updated:
853,845✔
680
            return init_from_parent(allow_create);
853,845✔
681
    }
3,917,976✔
682
    REALM_UNREACHABLE();
683
}
×
684

685
UpdateStatus Dictionary::update_if_needed() const
686
{
3,444,201✔
687
    constexpr bool allow_create = false;
3,444,201✔
688
    return do_update_if_needed(allow_create);
3,444,201✔
689
}
3,444,201✔
690

691
void Dictionary::ensure_created()
692
{
473,805✔
693
    constexpr bool allow_create = true;
473,805✔
694
    if (do_update_if_needed(allow_create) == UpdateStatus::Detached) {
473,805✔
695
        throw StaleAccessor("Dictionary no longer exists");
×
696
    }
×
697
}
473,805✔
698

699
bool Dictionary::try_erase(Mixed key)
700
{
12,156✔
701
    validate_key_value(key);
12,156✔
702
    if (!update())
12,156✔
703
        return false;
×
704

705
    auto ndx = do_find_key(key);
12,156✔
706
    if (ndx == realm::npos) {
12,156✔
707
        return false;
1,248✔
708
    }
1,248✔
709

710
    do_erase(ndx, key);
10,908✔
711

712
    return true;
10,908✔
713
}
12,156✔
714

715
void Dictionary::erase(Mixed key)
716
{
10,686✔
717
    if (!try_erase(key)) {
10,686✔
718
        throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
618✔
719
    }
618✔
720
}
10,686✔
721

722
auto Dictionary::erase(Iterator it) -> Iterator
723
{
2,658✔
724
    auto pos = it.m_ndx;
2,658✔
725
    CollectionBase::validate_index("erase()", pos, size());
2,658✔
726

727
    do_erase(pos, do_get_key(pos));
2,658✔
728
    if (pos < size())
2,658✔
729
        pos++;
480✔
730
    return {this, pos};
2,658✔
731
}
2,658✔
732

733
void Dictionary::nullify(size_t ndx)
734
{
738✔
735
    REALM_ASSERT(m_dictionary_top);
738✔
736
    REALM_ASSERT(ndx != realm::npos);
738✔
737

738
    if (Replication* repl = get_replication()) {
738✔
739
        auto key = do_get_key(ndx);
720✔
740
        repl->dictionary_set(*this, ndx, key, Mixed());
720✔
741
    }
720✔
742

743
    m_values->set(ndx, Mixed());
738✔
744
}
738✔
745

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

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

807
bool Dictionary::remove_backlinks(CascadeState& state) const
808
{
159,522✔
809
    size_t sz = size();
159,522✔
810
    bool recurse = false;
159,522✔
811
    for (size_t ndx = 0; ndx < sz; ndx++) {
531,024✔
812
        if (clear_backlink(ndx, state)) {
371,502✔
813
            recurse = true;
24,369✔
814
        }
24,369✔
815
    }
371,502✔
816
    return recurse;
159,522✔
817
}
159,522✔
818

819
size_t Dictionary::find_first(Mixed value) const
820
{
50,082✔
821
    return update() ? m_values->find_first(value) : realm::not_found;
50,082✔
822
}
50,082✔
823

824
void Dictionary::clear()
825
{
3,126✔
826
    auto sz = size();
3,126✔
827
    Replication* repl = Base::get_replication();
3,126✔
828
    if (repl && (sz > 0 || !m_col_key.is_collection() || m_level > 1)) {
3,126✔
829
        repl->dictionary_clear(*this);
2,856✔
830
    }
2,856✔
831
    if (sz > 0) {
3,126✔
832
        CascadeState cascade_state(CascadeState::Mode::Strong);
2,682✔
833
        bool recurse = remove_backlinks(cascade_state);
2,682✔
834

835
        // Just destroy the whole cluster
836
        m_dictionary_top->destroy_deep();
2,682✔
837
        m_dictionary_top.reset();
2,682✔
838

839
        update_child_ref(0, 0);
2,682✔
840

841
        if (recurse)
2,682✔
842
            _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
6✔
843
    }
2,682✔
844
}
3,126✔
845

846
UpdateStatus Dictionary::init_from_parent(bool allow_create) const
847
{
853,896✔
848
    Base::update_content_version();
853,896✔
849
    try {
853,896✔
850
        auto ref = Base::get_collection_ref();
853,896✔
851
        if ((ref || allow_create) && !m_dictionary_top) {
853,896✔
852
            Allocator& alloc = get_alloc();
718,962✔
853
            m_dictionary_top.reset(new Array(alloc));
718,962✔
854
            m_dictionary_top->set_parent(const_cast<Dictionary*>(this), 0);
718,962✔
855
            StringInterner* interner = m_col_key ? get_table()->get_string_interner(m_col_key) : nullptr;
718,962✔
856
            switch (m_key_type) {
718,962✔
857
                case type_String: {
718,944✔
858
                    m_keys.reset(new BPlusTree<StringData>(alloc));
718,944✔
859
                    m_keys->set_interner(interner);
718,944✔
860
                    break;
718,944✔
861
                }
×
862
                case type_Int: {
✔
863
                    m_keys.reset(new BPlusTree<Int>(alloc));
×
864
                    break;
×
865
                }
×
866
                default:
✔
867
                    break;
×
868
            }
718,962✔
869
            m_keys->set_parent(m_dictionary_top.get(), 0);
718,956✔
870
            m_values.reset(new BPlusTreeMixed(alloc));
718,956✔
871
            m_values->set_parent(m_dictionary_top.get(), 1);
718,956✔
872
            m_values->set_interner(interner);
718,956✔
873
        }
718,956✔
874

875
        if (ref) {
853,890✔
876
            m_dictionary_top->init_from_ref(ref);
601,161✔
877
            m_keys->init_from_parent();
601,161✔
878
            m_values->init_from_parent();
601,161✔
879
        }
601,161✔
880
        else {
252,729✔
881
            // dictionary detached
882
            if (!allow_create) {
252,729✔
883
                m_dictionary_top.reset();
64,158✔
884
                return UpdateStatus::Detached;
64,158✔
885
            }
64,158✔
886

887
            // Create dictionary
888
            m_dictionary_top->create(Array::type_HasRefs, false, 2, 0);
188,571✔
889
            m_values->create();
188,571✔
890
            m_keys->create();
188,571✔
891
            m_dictionary_top->update_parent();
188,571✔
892
        }
188,571✔
893

894
        return UpdateStatus::Updated;
789,732✔
895
    }
853,890✔
896
    catch (...) {
853,896✔
897
        m_dictionary_top.reset();
30✔
898
        throw;
30✔
899
    }
30✔
900
}
853,896✔
901

902
size_t Dictionary::do_find_key(Mixed key) const noexcept
903
{
985,713✔
904
    auto [ndx, actual_key] = find_impl(key);
985,713✔
905
    if (actual_key == key) {
985,713✔
906
        return ndx;
973,167✔
907
    }
973,167✔
908
    return realm::npos;
12,546✔
909
}
985,713✔
910

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

944
    return {sz, actual};
189,918✔
945
}
1,459,512✔
946

947
Mixed Dictionary::do_get(size_t ndx) const
948
{
1,311,735✔
949
    Mixed val = m_values->get(ndx);
1,311,735✔
950

951
    // Filter out potential unresolved links
952
    if (val.is_type(type_TypedLink) && val.get<ObjKey>().is_unresolved()) {
1,311,735✔
953
        return {};
1,104✔
954
    }
1,104✔
955
    return val;
1,310,631✔
956
}
1,311,735✔
957

958
void Dictionary::do_erase(size_t ndx, Mixed key)
959
{
13,554✔
960
    CascadeState cascade_state(CascadeState::Mode::Strong);
13,554✔
961
    bool recurse = clear_backlink(ndx, cascade_state);
13,554✔
962

963
    if (recurse)
13,554✔
964
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
102✔
965

966
    if (Replication* repl = get_replication()) {
13,554✔
967
        repl->dictionary_erase(*this, ndx, key);
12,300✔
968
    }
12,300✔
969

970
    m_keys->erase(ndx);
13,554✔
971
    m_values->erase(ndx);
13,554✔
972
    bump_content_version();
13,554✔
973
}
13,554✔
974

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

988
    return {};
×
989
}
517,194✔
990

991
std::pair<Mixed, Mixed> Dictionary::do_get_pair(size_t ndx) const
992
{
454,308✔
993
    return {do_get_key(ndx), do_get(ndx)};
454,308✔
994
}
454,308✔
995

996
bool Dictionary::clear_backlink(size_t ndx, CascadeState& state) const
997
{
385,056✔
998
    auto value = m_values->get(ndx);
385,056✔
999
    if (value.is_type(type_TypedLink)) {
385,056✔
1000
        return Base::remove_backlink(m_col_key, value.get_link(), state);
151,851✔
1001
    }
151,851✔
1002
    if (value.is_type(type_Dictionary)) {
233,205✔
1003
        Dictionary dict{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
48,138✔
1004
        return dict.remove_backlinks(state);
48,138✔
1005
    }
48,138✔
1006
    if (value.is_type(type_List)) {
185,067✔
1007
        Lst<Mixed> list{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
186✔
1008
        return list.remove_backlinks(state);
186✔
1009
    }
186✔
1010
    return false;
184,881✔
1011
}
185,067✔
1012

1013
void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, size_t index2)
1014
{
×
1015
    std::string buf1, buf2;
×
1016

1017
    // Swap keys
1018
    REALM_ASSERT(m_key_type == type_String);
×
1019
    ArrayString keys(get_alloc());
×
1020
    keys.set_parent(&fields1, 1);
×
1021
    keys.init_from_parent();
×
1022
    buf1 = keys.get(index1);
×
1023

1024
    keys.set_parent(&fields2, 1);
×
1025
    keys.init_from_parent();
×
1026
    buf2 = keys.get(index2);
×
1027
    keys.set(index2, buf1);
×
1028

1029
    keys.set_parent(&fields1, 1);
×
1030
    keys.init_from_parent();
×
1031
    keys.set(index1, buf2);
×
1032

1033
    // Swap values
1034
    ArrayMixed values(get_alloc());
×
1035
    values.set_parent(&fields1, 2);
×
1036
    values.init_from_parent();
×
1037
    Mixed val1 = values.get(index1);
×
1038
    val1.use_buffer(buf1);
×
1039

1040
    values.set_parent(&fields2, 2);
×
1041
    values.init_from_parent();
×
1042
    Mixed val2 = values.get(index2);
×
1043
    val2.use_buffer(buf2);
×
1044
    values.set(index2, val1);
×
1045

1046
    values.set_parent(&fields1, 2);
×
1047
    values.init_from_parent();
×
1048
    values.set(index1, val2);
×
1049
}
×
1050

1051
Mixed Dictionary::find_value(Mixed value) const noexcept
1052
{
×
1053
    size_t ndx = update() ? m_values->find_first(value) : realm::npos;
×
1054
    return (ndx == realm::npos) ? Mixed{} : do_get_key(ndx);
×
1055
}
×
1056

1057
StableIndex Dictionary::build_index(Mixed key) const
1058
{
101,886✔
1059
    auto it = find(key);
101,886✔
1060
    int64_t index = (it != end()) ? m_values->get_key(it.index()) : 0;
101,886✔
1061
    return {index};
101,886✔
1062
}
101,886✔
1063

1064

1065
void Dictionary::verify() const
1066
{
12,522✔
1067
    m_keys->verify();
12,522✔
1068
    m_values->verify();
12,522✔
1069
    REALM_ASSERT(m_keys->size() == m_values->size());
12,522✔
1070
}
12,522✔
1071

1072
void Dictionary::get_key_type()
1073
{
602,280✔
1074
    m_key_type = get_table()->get_dictionary_key_type(m_col_key);
602,280✔
1075
    if (!(m_key_type == type_String || m_key_type == type_Int))
602,280!
1076
        throw Exception(ErrorCodes::InvalidDictionaryKey, "Dictionary keys can only be strings or integers");
×
1077
}
602,280✔
1078

1079
void Dictionary::migrate()
1080
{
6✔
1081
    // Dummy implementation of legacy dictionary cluster tree
1082
    class DictionaryClusterTree : public ClusterTree {
6✔
1083
    public:
6✔
1084
        DictionaryClusterTree(ArrayParent* owner, Allocator& alloc, size_t ndx)
6✔
1085
            : ClusterTree(nullptr, alloc, ndx)
6✔
1086
            , m_owner(owner)
6✔
1087
        {
6✔
1088
        }
6✔
1089

1090
        std::unique_ptr<ClusterNode> get_root_from_parent() final
6✔
1091
        {
6✔
1092
            return create_root_from_parent(m_owner, m_top_position_for_cluster_tree);
6✔
1093
        }
6✔
1094

1095
    private:
6✔
1096
        ArrayParent* m_owner;
6✔
1097
    };
6✔
1098

1099
    if (auto dict_ref = Base::get_collection_ref()) {
6✔
1100
        Allocator& alloc = get_alloc();
6✔
1101
        DictionaryClusterTree cluster_tree(this, alloc, 0);
6✔
1102
        if (cluster_tree.init_from_parent()) {
6✔
1103
            // Create an empty dictionary in the old ones place
1104
            Base::set_collection_ref(0);
6✔
1105
            ensure_created();
6✔
1106

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

1137
template <>
1138
void CollectionBaseImpl<DictionaryBase>::to_json(std::ostream&, JSONOutputMode,
1139
                                                 util::FunctionRef<void(const Mixed&)>) const
1140
{
×
1141
}
×
1142

1143
void Dictionary::to_json(std::ostream& out, JSONOutputMode output_mode,
1144
                         util::FunctionRef<void(const Mixed&)> fn) const
1145
{
438✔
1146
    if (output_mode == output_mode_xjson_plus) {
438✔
1147
        out << "{ \"$dictionary\": ";
156✔
1148
    }
156✔
1149
    out << "{";
438✔
1150

1151
    auto sz = size();
438✔
1152
    for (size_t i = 0; i < sz; i++) {
966✔
1153
        if (i > 0)
528✔
1154
            out << ",";
126✔
1155
        out << do_get_key(i) << ":";
528✔
1156
        Mixed val = do_get(i);
528✔
1157
        if (val.is_type(type_TypedLink)) {
528✔
1158
            fn(val);
144✔
1159
        }
144✔
1160
        else if (val.is_type(type_Dictionary)) {
384✔
1161
            DummyParent parent(this->get_table(), val.get_ref(), m_col_key);
12✔
1162
            Dictionary dict(parent, 0);
12✔
1163
            dict.to_json(out, output_mode, fn);
12✔
1164
        }
12✔
1165
        else if (val.is_type(type_List)) {
372✔
1166
            DummyParent parent(this->get_table(), val.get_ref(), m_col_key);
12✔
1167
            Lst<Mixed> list(parent, 0);
12✔
1168
            list.to_json(out, output_mode, fn);
12✔
1169
        }
12✔
1170
        else {
360✔
1171
            val.to_json(out, output_mode);
360✔
1172
        }
360✔
1173
    }
528✔
1174

1175
    out << "}";
438✔
1176
    if (output_mode == output_mode_xjson_plus) {
438✔
1177
        out << "}";
156✔
1178
    }
156✔
1179
}
438✔
1180

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

1194
bool Dictionary::check_collection_ref(Index index, CollectionType type) const noexcept
1195
{
2,670✔
1196
    auto ndx = m_values->find_key(index.get_salt());
2,670✔
1197
    if (ndx != realm::not_found) {
2,670✔
1198
        return m_values->get(ndx).is_type(DataType(int(type)));
2,652✔
1199
    }
2,652✔
1200
    return false;
18✔
1201
}
2,670✔
1202

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

1212
LinkCollectionPtr Dictionary::clone_as_obj_list() const
1213
{
1,650✔
1214
    if (get_value_data_type() == type_Link) {
1,650✔
1215
        return std::make_unique<DictionaryLinkValues>(*this);
1,524✔
1216
    }
1,524✔
1217
    return nullptr;
126✔
1218
}
1,650✔
1219

1220
ref_type Dictionary::typed_write(ref_type ref, _impl::ArrayWriterBase& out, Allocator& alloc)
1221
{
48,861✔
1222
    if (out.only_modified && alloc.is_read_only(ref))
48,861✔
1223
        return ref;
4,326✔
1224

1225
    ArrayRef dict_top(alloc);
44,535✔
1226
    dict_top.init_from_ref(ref);
44,535✔
1227
    REALM_ASSERT_DEBUG(dict_top.size() == 2);
44,535✔
1228
    TempArray written_dict_top(2);
44,535✔
1229

1230
    // We have to find out what kind of keys we are using - strings or ints
1231
    // Btw - ints is only used in tests. Can probably be removed at some point
1232
    auto key_ref = dict_top.get(0);
44,535✔
1233
    auto header = alloc.translate(key_ref);
44,535✔
1234
    if (!NodeHeader::get_hasrefs_from_header(header) &&
44,535✔
1235
        NodeHeader::get_wtype_from_header(header) != Array::wtype_Multiply) {
44,535✔
1236
        // Key type int.
1237
        REALM_ASSERT(!NodeHeader::get_is_inner_bptree_node_from_header(header));
22,479✔
1238
        written_dict_top.set_as_ref(0, BPlusTree<int64_t>::typed_write(key_ref, out, alloc));
22,479✔
1239
    }
22,479✔
1240
    else {
22,056✔
1241
        written_dict_top.set_as_ref(0, BPlusTree<StringData>::typed_write(key_ref, out, alloc));
22,056✔
1242
    }
22,056✔
1243

1244
    auto values_ref = dict_top.get_as_ref(1);
44,535✔
1245
    written_dict_top.set_as_ref(1, BPlusTree<Mixed>::typed_write(values_ref, out, alloc));
44,535✔
1246

1247
    return written_dict_top.write(out);
44,535✔
1248
}
48,861✔
1249

1250
/************************* DictionaryLinkValues *************************/
1251

1252
DictionaryLinkValues::DictionaryLinkValues(const Obj& obj, ColKey col_key)
1253
    : m_source(obj, col_key)
1254
{
×
1255
    REALM_ASSERT_EX(col_key.get_type() == col_type_Link, col_key.get_type());
×
1256
}
×
1257

1258
DictionaryLinkValues::DictionaryLinkValues(const Dictionary& source)
1259
    : m_source(source)
795✔
1260
{
1,590✔
1261
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
1,590✔
1262
}
1,590✔
1263

1264
ObjKey DictionaryLinkValues::get_key(size_t ndx) const
1265
{
72✔
1266
    Mixed val = m_source.get_any(ndx);
72✔
1267
    if (val.is_type(type_Link, type_TypedLink)) {
72✔
1268
        return val.get<ObjKey>();
48✔
1269
    }
48✔
1270
    return {};
24✔
1271
}
72✔
1272

1273
Obj DictionaryLinkValues::get_object(size_t row_ndx) const
1274
{
2,400✔
1275
    Mixed val = m_source.get_any(row_ndx);
2,400✔
1276
    if (val.is_type(type_TypedLink)) {
2,400✔
1277
        return get_table()->get_parent_group()->get_object(val.get_link());
2,382✔
1278
    }
2,382✔
1279
    return {};
18✔
1280
}
2,400✔
1281

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