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

realm / realm-core / 2493

15 Jul 2024 06:38PM UTC coverage: 90.982% (+0.002%) from 90.98%
2493

push

Evergreen

web-flow
RCORE-2192 RCORE-2193 Fix FLX download progress reporting (#7870)

* Fix FLX download progress reporting

We need to store the download progress for each batch of a bootstrap and not
just at the end for it to be useful in any way.

The server will sometimes send us DOWNLOAD messages with a non-one estimate
followed by a one estimate where the byte-level information is the same (as the
final message is empty). When this happens we need to report the download
completion to the user, so add the estimate to the fields checked for changes.

A subscription change which doesn't actually change what set of objects is in
view can result in an empty DOWNLOAD message with no changes other than the
query version, and we should report that too.

* Fix a comment

* Pass the DownloadMessage to process_flx_bootstrap_message()

* Report steady-state download progress

102352 of 180586 branches covered (56.68%)

247 of 257 new or added lines in 10 files covered. (96.11%)

88 existing lines in 17 files now uncovered.

215381 of 236730 relevant lines covered (90.98%)

5726853.58 hits per line

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

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

46
} // namespace
47

48

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

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

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

76
Dictionary::~Dictionary() = default;
780,891✔
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,202✔
94
    if (!update())
1,583,202✔
95
        return 0;
44,742✔
96

97
    return m_values->size();
1,538,460✔
98
}
1,583,202✔
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,353✔
117
    // Note: `size()` calls `update_if_needed()`.
118
    auto current_size = size();
16,353✔
119
    CollectionBase::validate_index("get_any()", ndx, current_size);
16,353✔
120
    return do_get(ndx);
16,353✔
121
}
16,353✔
122

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

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

138
size_t Dictionary::find_any(Mixed value) const
139
{
3,222✔
140
    return size() ? m_values->find_first(value) : realm::not_found;
3,222✔
141
}
3,222✔
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,595✔
164
            }
38,595✔
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) {
36,036✔
357
        return ascending ? values[i1] < values[i2] : values[i2] < values[i1];
36,036✔
358
    });
36,036✔
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,702✔
474
    if (auto opt_val = try_get(key)) {
15,702✔
475
        return *opt_val;
15,678✔
476
    }
15,678✔
477
    throw KeyNotFound("Dictionary::get");
24✔
478
}
15,702✔
479

480
util::Optional<Mixed> Dictionary::try_get(Mixed key) const
481
{
843,966✔
482
    if (update()) {
843,966✔
483
        auto ndx = do_find_key(key);
842,754✔
484
        if (ndx != realm::npos) {
842,754✔
485
            return do_get(ndx);
840,420✔
486
        }
840,420✔
487
    }
842,754✔
488
    return {};
3,546✔
489
}
843,966✔
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,424✔
506
    auto my_table = get_table_unchecked();
473,424✔
507
    if (key.get_type() != m_key_type) {
473,424✔
508
        throw InvalidArgument(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: Invalid key type");
×
509
    }
×
510
    if (m_col_key) {
473,424✔
511
        if (value.is_null()) {
473,418✔
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,084✔
517
            if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) {
468,084✔
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,234✔
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,234✔
527
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
×
528
                                      "Dictionary::insert: No target table for link");
×
529
            }
×
530
        }
468,084✔
531
    }
473,418✔
532

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

536
    ObjLink new_link;
473,418✔
537
    if (value.is_type(type_TypedLink)) {
473,418✔
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,118✔
541
    }
152,358✔
542
    else if (value.is_type(type_Link)) {
321,060✔
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,412✔
553
        throw StaleAccessor("Stale dictionary");
×
554
    }
×
555

556
    bool set_nested_collection_key = value.is_type(type_Dictionary, type_List);
473,412✔
557
    bool old_entry = false;
473,412✔
558
    auto [ndx, actual_key] = find_impl(key);
473,412✔
559
    if (actual_key != key) {
473,412✔
560
        // key does not already exist
561
        switch (m_key_type) {
463,026✔
562
            case type_String:
463,026✔
563
                static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key.get_string());
463,026✔
564
                break;
463,026✔
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,026✔
571
        m_values->insert(ndx, value);
463,026✔
572
    }
463,026✔
573
    else {
10,386✔
574
        old_entry = true;
10,386✔
575
    }
10,386✔
576

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

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

601
    if (set_nested_collection_key) {
473,412✔
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,412✔
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,412✔
614
}
473,412✔
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,916,935✔
666
    switch (get_update_status()) {
3,916,935✔
667
        case UpdateStatus::Detached: {
6✔
668
            m_dictionary_top.reset();
6✔
669
            return UpdateStatus::Detached;
6✔
670
        }
×
671
        case UpdateStatus::NoChange: {
3,110,787✔
672
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
3,110,787✔
673
                return UpdateStatus::NoChange;
3,063,657✔
674
            }
3,063,657✔
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,110,787✔
678
        }
47,130✔
679
        case UpdateStatus::Updated:
853,254✔
680
            return init_from_parent(allow_create);
853,254✔
681
    }
3,916,935✔
682
    REALM_UNREACHABLE();
683
}
×
684

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

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

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

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

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

712
    return true;
10,959✔
713
}
12,207✔
714

715
void Dictionary::erase(Mixed key)
716
{
10,737✔
717
    if (!try_erase(key)) {
10,737✔
718
        throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
618✔
719
    }
618✔
720
}
10,737✔
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,534✔
809
    size_t sz = size();
159,534✔
810
    bool recurse = false;
159,534✔
811
    for (size_t ndx = 0; ndx < sz; ndx++) {
531,042✔
812
        if (clear_backlink(ndx, state)) {
371,508✔
813
            recurse = true;
24,369✔
814
        }
24,369✔
815
    }
371,508✔
816
    return recurse;
159,534✔
817
}
159,534✔
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,278✔
848
    Base::update_content_version();
853,278✔
849
    try {
853,278✔
850
        auto ref = Base::get_collection_ref();
853,278✔
851
        if ((ref || allow_create) && !m_dictionary_top) {
853,278✔
852
            Allocator& alloc = get_alloc();
718,272✔
853
            m_dictionary_top.reset(new Array(alloc));
718,272✔
854
            m_dictionary_top->set_parent(const_cast<Dictionary*>(this), 0);
718,272✔
855
            switch (m_key_type) {
718,272✔
856
                case type_String: {
718,275✔
857
                    m_keys.reset(new BPlusTree<StringData>(alloc));
718,275✔
858
                    break;
718,275✔
859
                }
×
860
                case type_Int: {
✔
861
                    m_keys.reset(new BPlusTree<Int>(alloc));
×
862
                    break;
×
863
                }
×
864
                default:
✔
865
                    break;
×
866
            }
718,272✔
867
            m_keys->set_parent(m_dictionary_top.get(), 0);
718,278✔
868
            m_values.reset(new BPlusTreeMixed(alloc));
718,278✔
869
            m_values->set_parent(m_dictionary_top.get(), 1);
718,278✔
870
        }
718,278✔
871

872
        if (ref) {
853,284✔
873
            m_dictionary_top->init_from_ref(ref);
600,480✔
874
            m_keys->init_from_parent();
600,480✔
875
            m_values->init_from_parent();
600,480✔
876
        }
600,480✔
877
        else {
252,804✔
878
            // dictionary detached
879
            if (!allow_create) {
252,804✔
880
                m_dictionary_top.reset();
64,158✔
881
                return UpdateStatus::Detached;
64,158✔
882
            }
64,158✔
883

884
            // Create dictionary
885
            m_dictionary_top->create(Array::type_HasRefs, false, 2, 0);
188,646✔
886
            m_values->create();
188,646✔
887
            m_keys->create();
188,646✔
888
            m_dictionary_top->update_parent();
188,646✔
889
        }
188,646✔
890

891
        return UpdateStatus::Updated;
789,126✔
892
    }
853,284✔
893
    catch (...) {
853,278✔
894
        m_dictionary_top.reset();
30✔
895
        throw;
30✔
896
    }
30✔
897
}
853,278✔
898

899
size_t Dictionary::do_find_key(Mixed key) const noexcept
900
{
985,755✔
901
    auto [ndx, actual_key] = find_impl(key);
985,755✔
902
    if (actual_key == key) {
985,755✔
903
        return ndx;
973,209✔
904
    }
973,209✔
905
    return realm::npos;
12,546✔
906
}
985,755✔
907

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

941
    return {sz, actual};
189,894✔
942
}
1,459,155✔
943

944
Mixed Dictionary::do_get(size_t ndx) const
945
{
1,311,555✔
946
    Mixed val = m_values->get(ndx);
1,311,555✔
947

948
    // Filter out potential unresolved links
949
    if (val.is_type(type_TypedLink) && val.get<ObjKey>().is_unresolved()) {
1,311,555✔
950
        return {};
1,104✔
951
    }
1,104✔
952
    return val;
1,310,451✔
953
}
1,311,555✔
954

955
void Dictionary::do_erase(size_t ndx, Mixed key)
956
{
13,605✔
957
    CascadeState cascade_state(CascadeState::Mode::Strong);
13,605✔
958
    bool recurse = clear_backlink(ndx, cascade_state);
13,605✔
959

960
    if (recurse)
13,605✔
961
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
102✔
962

963
    if (Replication* repl = get_replication()) {
13,605✔
964
        repl->dictionary_erase(*this, ndx, key);
12,351✔
965
    }
12,351✔
966

967
    m_keys->erase(ndx);
13,605✔
968
    m_values->erase(ndx);
13,605✔
969
    bump_content_version();
13,605✔
970
}
13,605✔
971

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

985
    return {};
×
986
}
517,020✔
987

988
std::pair<Mixed, Mixed> Dictionary::do_get_pair(size_t ndx) const
989
{
454,254✔
990
    return {do_get_key(ndx), do_get(ndx)};
454,254✔
991
}
454,254✔
992

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

1010
void Dictionary::swap_content(Array& fields1, Array& fields2, size_t index1, size_t index2)
1011
{
×
1012
    std::string buf1, buf2;
×
1013

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

1021
    keys.set_parent(&fields2, 1);
×
1022
    keys.init_from_parent();
×
1023
    buf2 = keys.get(index2);
×
1024
    keys.set(index2, buf1);
×
1025

1026
    keys.set_parent(&fields1, 1);
×
1027
    keys.init_from_parent();
×
1028
    keys.set(index1, buf2);
×
1029

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

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

1043
    values.set_parent(&fields1, 2);
×
1044
    values.init_from_parent();
×
1045
    values.set(index1, val2);
×
1046
}
×
1047

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

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

1061

1062
void Dictionary::verify() const
1063
{
12,216✔
1064
    m_keys->verify();
12,216✔
1065
    m_values->verify();
12,216✔
1066
    REALM_ASSERT(m_keys->size() == m_values->size());
12,216✔
1067
}
12,216✔
1068

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

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

1087
        std::unique_ptr<ClusterNode> get_root_from_parent() final
6✔
1088
        {
6✔
1089
            return create_root_from_parent(m_owner, m_top_position_for_cluster_tree);
6✔
1090
        }
6✔
1091

1092
    private:
6✔
1093
        ArrayParent* m_owner;
6✔
1094
    };
6✔
1095

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

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

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

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

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

1172
    out << "}";
438✔
1173
    if (output_mode == output_mode_xjson_plus) {
438✔
1174
        out << "}";
156✔
1175
    }
156✔
1176
}
438✔
1177

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

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

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

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

1217
/************************* DictionaryLinkValues *************************/
1218

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

1225
DictionaryLinkValues::DictionaryLinkValues(const Dictionary& source)
1226
    : m_source(source)
795✔
1227
{
1,590✔
1228
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
1,590✔
1229
}
1,590✔
1230

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

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

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

© 2026 Coveralls, Inc