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

realm / realm-core / github_pull_request_312964

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

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

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


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

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

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

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

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

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

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

46
} // namespace
47

48

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

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

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

76
Dictionary::~Dictionary() = default;
403,281✔
77

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

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

89
    return *this;
50,868✔
90
}
50,868✔
91

92
size_t Dictionary::size() const
93
{
804,327✔
94
    if (!update())
804,327✔
95
        return 0;
22,161✔
96

97
    return m_values->size();
782,166✔
98
}
804,327✔
99

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

105
DataType Dictionary::get_value_data_type() const
106
{
12,564✔
107
    return DataType(m_col_key.get_type());
12,564✔
108
}
12,564✔
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
{
8,175✔
117
    // Note: `size()` calls `update_if_needed()`.
118
    auto current_size = size();
8,175✔
119
    CollectionBase::validate_index("get_any()", ndx, current_size);
8,175✔
120
    return do_get(ndx);
8,175✔
121
}
8,175✔
122

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

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

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

143
size_t Dictionary::find_any_key(Mixed key) const noexcept
144
{
69,417✔
145
    if (update()) {
69,417✔
146
        return do_find_key(key);
65,403✔
147
    }
65,403✔
148

149
    return realm::npos;
4,014✔
150
}
69,417✔
151

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

157
    m_values->traverse([&](BPlusTreeNode* node, size_t offset) {
8,892✔
158
        auto leaf = static_cast<BPlusTree<Mixed>::LeafNode*>(node);
8,892✔
159
        size_t e = leaf->size();
8,892✔
160
        for (size_t i = 0; i < e; i++) {
34,230✔
161
            auto val = leaf->get(i);
25,338✔
162
            if (agg.accumulate(val)) {
25,338✔
163
                ndx = i + offset;
19,305✔
164
            }
19,305✔
165
        }
25,338✔
166
        // Continue
167
        return IteratorControl::AdvanceToNext;
8,892✔
168
    });
8,892✔
169

170
    if (return_ndx)
8,892✔
171
        *return_ndx = ndx;
6✔
172
}
8,892✔
173

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

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

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

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

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

252
namespace {
253
bool can_minmax(DataType type)
254
{
303✔
255
    switch (type) {
303✔
256
        case type_Int:
45✔
257
        case type_Float:
69✔
258
        case type_Double:
93✔
259
        case type_Decimal:
117✔
260
        case type_Mixed:
159✔
261
        case type_Timestamp:
183✔
262
            return true;
183✔
263
        default:
120✔
264
            return false;
120✔
265
    }
303✔
266
}
303✔
267
bool can_sum(DataType type)
268
{
300✔
269
    switch (type) {
300✔
270
        case type_Int:
42✔
271
        case type_Float:
66✔
272
        case type_Double:
90✔
273
        case type_Decimal:
114✔
274
        case type_Mixed:
156✔
275
            return true;
156✔
276
        default:
144✔
277
            return false;
144✔
278
    }
300✔
279
}
300✔
280
} // anonymous namespace
281

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

295
util::Optional<Mixed> Dictionary::max(size_t* return_ndx) const
296
{
153✔
297
    if (!can_minmax(get_value_data_type())) {
153✔
298
        return std::nullopt;
60✔
299
    }
60✔
300
    if (update()) {
93✔
301
        return do_max(return_ndx);
54✔
302
    }
54✔
303
    if (return_ndx)
39✔
304
        *return_ndx = realm::not_found;
3✔
305
    return Mixed{};
39✔
306
}
93✔
307

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

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

334
void Dictionary::align_indices(std::vector<size_t>& indices) const
335
{
2,523✔
336
    auto sz = size();
2,523✔
337
    auto sz2 = indices.size();
2,523✔
338
    indices.reserve(sz);
2,523✔
339
    if (sz < sz2) {
2,523✔
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++) {
11,679✔
345
        // If list size has increased, just add the missing indices
346
        indices.push_back(i);
9,156✔
347
    }
9,156✔
348
}
2,523✔
349

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

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

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

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

385
void Dictionary::sort_keys(std::vector<size_t>& indices, bool ascending) const
386
{
252✔
387
    align_indices(indices);
252✔
388
#ifdef REALM_DEBUG
252✔
389
    if (indices.size() > 1) {
252✔
390
        // We rely in the design that the keys are already sorted
391
        switch (m_key_type) {
252✔
392
            case type_String: {
252✔
393
                IteratorAdapter help(static_cast<BPlusTree<StringData>*>(m_keys.get()));
252✔
394
                auto is_sorted = std::is_sorted(help.begin(), help.end());
252✔
395
                REALM_ASSERT(is_sorted);
252✔
396
                break;
252✔
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
        }
252✔
407
    }
252✔
408
#endif
252✔
409
    if (ascending) {
252✔
410
        std::sort(indices.begin(), indices.end());
126✔
411
    }
126✔
412
    else {
126✔
413
        std::sort(indices.begin(), indices.end(), std::greater<size_t>());
126✔
414
    }
126✔
415
}
252✔
416

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

423

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

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

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

448
        if (weak.expired()) {
50,169✔
449
            REALM_ASSERT_DEBUG(m_level == 1);
48,414✔
450
            return std::make_shared<Dictionary>(*this);
48,414✔
451
        }
48,414✔
452

453
        return weak.lock();
1,755✔
454
    };
50,169✔
455

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

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

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

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

480
util::Optional<Mixed> Dictionary::try_get(Mixed key) const
481
{
422,010✔
482
    if (update()) {
422,010✔
483
        auto ndx = do_find_key(key);
421,404✔
484
        if (ndx != realm::npos) {
421,404✔
485
            return do_get(ndx);
420,237✔
486
        }
420,237✔
487
    }
421,404✔
488
    return {};
1,773✔
489
}
422,010✔
490

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

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

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

533
    validate_key_value(key);
236,586✔
534
    ensure_created();
236,586✔
535

536
    ObjLink new_link;
236,586✔
537
    if (value.is_type(type_TypedLink)) {
236,586✔
538
        new_link = value.get<ObjLink>();
76,179✔
539
        if (!new_link.is_unresolved())
76,179✔
540
            my_table->get_parent_group()->validate(new_link);
76,059✔
541
    }
76,179✔
542
    else if (value.is_type(type_Link)) {
160,407✔
543
        auto target_table = my_table->get_opposite_table(m_col_key);
6,471✔
544
        auto key = value.get<ObjKey>();
6,471✔
545
        if (!key.is_unresolved() && !target_table->is_valid(key)) {
6,471✔
546
            throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
3✔
547
        }
3✔
548
        new_link = ObjLink(target_table->get_key(), key);
6,468✔
549
        value = Mixed(new_link);
6,468✔
550
    }
6,468✔
551

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

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

577
    if (Replication* repl = get_replication()) {
236,583✔
578
        if (old_entry) {
49,461✔
579
            repl->dictionary_set(*this, ndx, key, value);
4,563✔
580
        }
4,563✔
581
        else {
44,898✔
582
            repl->dictionary_insert(*this, ndx, key, value);
44,898✔
583
        }
44,898✔
584
    }
49,461✔
585
    bump_content_version();
236,583✔
586

587
    ObjLink old_link;
236,583✔
588
    if (old_entry) {
236,583✔
589
        Mixed old_value = m_values->get(ndx);
5,169✔
590
        if (old_value.is_type(type_TypedLink)) {
5,169✔
591
            old_link = old_value.get<ObjLink>();
411✔
592
        }
411✔
593
        if (!value.is_same_type(old_value) || value != old_value) {
5,169✔
594
            m_values->set(ndx, value);
3,825✔
595
        }
3,825✔
596
        else {
1,344✔
597
            set_nested_collection_key = false;
1,344✔
598
        }
1,344✔
599
    }
5,169✔
600

601
    if (set_nested_collection_key) {
236,583✔
602
        m_values->ensure_keys();
25,350✔
603
        set_key(*m_values, ndx);
25,350✔
604
    }
25,350✔
605

606
    if (new_link != old_link) {
236,583✔
607
        CascadeState cascade_state(CascadeState::Mode::Strong);
82,647✔
608
        bool recurse = Base::replace_backlink(m_col_key, old_link, new_link, cascade_state);
82,647✔
609
        if (recurse)
82,647✔
610
            _impl::TableFriend::remove_recursive(*my_table, cascade_state); // Throws
147✔
611
    }
82,647✔
612

613
    return {Iterator(this, ndx), !old_entry};
236,583✔
614
}
236,583✔
615

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

624
    return *ret;
406,401✔
625
}
406,401✔
626

627
Obj Dictionary::get_object(StringData key)
628
{
6,216✔
629
    if (auto val = try_get(key)) {
6,216✔
630
        if ((*val).is_type(type_TypedLink)) {
4,617✔
631
            return get_table()->get_parent_group()->get_object((*val).get_link());
4,557✔
632
        }
4,557✔
633
    }
4,617✔
634
    return {};
1,659✔
635
}
6,216✔
636

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

642
Dictionary::Iterator Dictionary::find(Mixed key) const noexcept
643
{
66,846✔
644
    auto ndx = find_any_key(key);
66,846✔
645
    if (ndx != realm::npos) {
66,846✔
646
        return Iterator(this, ndx);
59,172✔
647
    }
59,172✔
648
    return end();
7,674✔
649
}
66,846✔
650

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

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

664
UpdateStatus Dictionary::do_update_if_needed(bool allow_create) const
665
{
1,983,924✔
666
    switch (get_update_status()) {
1,983,924✔
667
        case UpdateStatus::Detached: {
3✔
668
            m_dictionary_top.reset();
3✔
669
            return UpdateStatus::Detached;
3✔
670
        }
×
671
        case UpdateStatus::NoChange: {
1,568,106✔
672
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
1,568,106✔
673
                return UpdateStatus::NoChange;
1,544,664✔
674
            }
1,544,664✔
675
            // The tree has not been initialized yet for this accessor, so
676
            // perform lazy initialization by treating it as an update.
677
            [[fallthrough]];
1,568,106✔
678
        }
23,442✔
679
        case UpdateStatus::Updated:
439,227✔
680
            return init_from_parent(allow_create);
439,227✔
681
    }
1,983,924✔
682
    REALM_UNREACHABLE();
683
}
×
684

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

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

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

705
    auto ndx = do_find_key(key);
6,129✔
706
    if (ndx == realm::npos) {
6,129✔
707
        return false;
624✔
708
    }
624✔
709

710
    do_erase(ndx, key);
5,505✔
711

712
    return true;
5,505✔
713
}
6,129✔
714

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

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

727
    do_erase(pos, do_get_key(pos));
1,329✔
728
    if (pos < size())
1,329✔
729
        pos++;
240✔
730
    return {this, pos};
1,329✔
731
}
1,329✔
732

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

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

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

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

776
bool Dictionary::replace_link(ObjLink old_link, ObjLink replace_link)
777
{
132✔
778
    size_t ndx = find_first(old_link);
132✔
779
    if (ndx != realm::not_found) {
132✔
780
        auto key = do_get_key(ndx);
132✔
781
        insert(key, replace_link);
132✔
782
        return true;
132✔
783
    }
132✔
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
}
132✔
806

807
bool Dictionary::remove_backlinks(CascadeState& state) const
808
{
79,770✔
809
    size_t sz = size();
79,770✔
810
    bool recurse = false;
79,770✔
811
    for (size_t ndx = 0; ndx < sz; ndx++) {
265,527✔
812
        if (clear_backlink(ndx, state)) {
185,757✔
813
            recurse = true;
12,180✔
814
        }
12,180✔
815
    }
185,757✔
816
    return recurse;
79,770✔
817
}
79,770✔
818

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

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

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

839
        update_child_ref(0, 0);
1,347✔
840

841
        if (recurse)
1,347✔
842
            _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
3✔
843
    }
1,347✔
844
}
1,569✔
845

846
UpdateStatus Dictionary::init_from_parent(bool allow_create) const
847
{
439,236✔
848
    Base::update_content_version();
439,236✔
849
    try {
439,236✔
850
        auto ref = Base::get_collection_ref();
439,236✔
851
        if ((ref || allow_create) && !m_dictionary_top) {
439,236✔
852
            Allocator& alloc = get_alloc();
372,171✔
853
            m_dictionary_top.reset(new Array(alloc));
372,171✔
854
            m_dictionary_top->set_parent(const_cast<Dictionary*>(this), 0);
372,171✔
855
            switch (m_key_type) {
372,171✔
856
                case type_String: {
372,186✔
857
                    m_keys.reset(new BPlusTree<StringData>(alloc));
372,186✔
858
                    break;
372,186✔
859
                }
×
860
                case type_Int: {
✔
861
                    m_keys.reset(new BPlusTree<Int>(alloc));
×
862
                    break;
×
863
                }
×
864
                default:
✔
865
                    break;
×
866
            }
372,171✔
867
            m_keys->set_parent(m_dictionary_top.get(), 0);
372,180✔
868
            m_values.reset(new BPlusTreeMixed(alloc));
372,180✔
869
            m_values->set_parent(m_dictionary_top.get(), 1);
372,180✔
870
        }
372,180✔
871

872
        if (ref) {
439,245✔
873
            m_dictionary_top->init_from_ref(ref);
313,323✔
874
            m_keys->init_from_parent();
313,323✔
875
            m_values->init_from_parent();
313,323✔
876
        }
313,323✔
877
        else {
125,922✔
878
            // dictionary detached
879
            if (!allow_create) {
125,922✔
880
                m_dictionary_top.reset();
31,659✔
881
                return UpdateStatus::Detached;
31,659✔
882
            }
31,659✔
883

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

891
        return UpdateStatus::Updated;
407,586✔
892
    }
439,245✔
893
    catch (...) {
439,236✔
894
        m_dictionary_top.reset();
15✔
895
        throw;
15✔
896
    }
15✔
897
}
439,236✔
898

899
size_t Dictionary::do_find_key(Mixed key) const noexcept
900
{
492,930✔
901
    auto [ndx, actual_key] = find_impl(key);
492,930✔
902
    if (actual_key == key) {
492,930✔
903
        return ndx;
486,657✔
904
    }
486,657✔
905
    return realm::npos;
6,273✔
906
}
492,930✔
907

908
std::pair<size_t, Mixed> Dictionary::find_impl(Mixed key) const noexcept
909
{
729,510✔
910
    auto sz = m_keys->size();
729,510✔
911
    Mixed actual;
729,510✔
912
    if (sz && key.is_type(m_key_type)) {
729,510✔
913
        switch (m_key_type) {
634,587✔
914
            case type_String: {
634,587✔
915
                auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
634,587✔
916
                StringData val = key.get<StringData>();
634,587✔
917
                IteratorAdapter help(keys);
634,587✔
918
                auto it = std::lower_bound(help.begin(), help.end(), val);
634,587✔
919
                if (it.index() < sz) {
634,587✔
920
                    actual = *it;
506,661✔
921
                }
506,661✔
922
                return {it.index(), actual};
634,587✔
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
        }
634,587✔
939
    }
634,587✔
940

941
    return {sz, actual};
94,923✔
942
}
729,510✔
943

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

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

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

960
    if (recurse)
6,828✔
961
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
51✔
962

963
    if (Replication* repl = get_replication()) {
6,828✔
964
        repl->dictionary_erase(*this, ndx, key);
6,201✔
965
    }
6,201✔
966

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

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

985
    return {};
×
986
}
271,434✔
987

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

993
bool Dictionary::clear_backlink(size_t ndx, CascadeState& state) const
994
{
192,585✔
995
    auto value = m_values->get(ndx);
192,585✔
996
    if (value.is_type(type_TypedLink)) {
192,585✔
997
        return Base::remove_backlink(m_col_key, value.get_link(), state);
75,918✔
998
    }
75,918✔
999
    if (value.is_type(type_Dictionary)) {
116,667✔
1000
        Dictionary dict{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
24,069✔
1001
        return dict.remove_backlinks(state);
24,069✔
1002
    }
24,069✔
1003
    if (value.is_type(type_List)) {
92,598✔
1004
        Lst<Mixed> list{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
93✔
1005
        return list.remove_backlinks(state);
93✔
1006
    }
93✔
1007
    return false;
92,505✔
1008
}
92,598✔
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
{
50,943✔
1056
    auto it = find(key);
50,943✔
1057
    int64_t index = (it != end()) ? m_values->get_key(it.index()) : 0;
50,943✔
1058
    return {index};
50,943✔
1059
}
50,943✔
1060

1061

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

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

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

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

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

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

1104
            ArrayString keys(alloc); // We only support string type keys.
3✔
1105
            ArrayMixed values(alloc);
3✔
1106
            constexpr ColKey key_col(ColKey::Idx{0}, col_type_String, ColumnAttrMask(), 0);
3✔
1107
            constexpr ColKey value_col(ColKey::Idx{1}, col_type_Mixed, ColumnAttrMask(), 0);
3✔
1108
            size_t nb_elements = cluster_tree.size();
3✔
1109
            cluster_tree.traverse([&](const Cluster* cluster) {
3✔
1110
                cluster->init_leaf(key_col, &keys);
3✔
1111
                cluster->init_leaf(value_col, &values);
3✔
1112
                auto sz = cluster->node_size();
3✔
1113
                for (size_t i = 0; i < sz; i++) {
30✔
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);
27✔
1118
                    auto [ndx, actual_key] = find_impl(key);
27✔
1119
                    REALM_ASSERT(actual_key != key);
27✔
1120
                    static_cast<BPlusTree<StringData>*>(m_keys.get())->insert(ndx, key);
27✔
1121
                    m_values->insert(ndx, values.get(i));
27✔
1122
                }
27✔
1123
                return IteratorControl::AdvanceToNext;
3✔
1124
            });
3✔
1125
            REALM_ASSERT(size() == nb_elements);
3✔
1126
            Array::destroy_deep(to_ref(dict_ref), alloc);
3✔
1127
        }
3✔
1128
        else {
×
1129
            REALM_UNREACHABLE();
1130
        }
×
1131
    }
3✔
1132
}
3✔
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
{
219✔
1143
    if (output_mode == output_mode_xjson_plus) {
219✔
1144
        out << "{ \"$dictionary\": ";
78✔
1145
    }
78✔
1146
    out << "{";
219✔
1147

1148
    auto sz = size();
219✔
1149
    for (size_t i = 0; i < sz; i++) {
483✔
1150
        if (i > 0)
264✔
1151
            out << ",";
63✔
1152
        out << do_get_key(i) << ":";
264✔
1153
        Mixed val = do_get(i);
264✔
1154
        if (val.is_type(type_TypedLink)) {
264✔
1155
            fn(val);
72✔
1156
        }
72✔
1157
        else if (val.is_type(type_Dictionary)) {
192✔
1158
            DummyParent parent(this->get_table(), val.get_ref());
6✔
1159
            Dictionary dict(parent, 0);
6✔
1160
            dict.to_json(out, output_mode, fn);
6✔
1161
        }
6✔
1162
        else if (val.is_type(type_List)) {
186✔
1163
            DummyParent parent(this->get_table(), val.get_ref());
6✔
1164
            Lst<Mixed> list(parent, 0);
6✔
1165
            list.to_json(out, output_mode, fn);
6✔
1166
        }
6✔
1167
        else {
180✔
1168
            val.to_json(out, output_mode);
180✔
1169
        }
180✔
1170
    }
264✔
1171

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

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

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

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

1209
LinkCollectionPtr Dictionary::clone_as_obj_list() const
1210
{
825✔
1211
    if (get_value_data_type() == type_Link) {
825✔
1212
        return std::make_unique<DictionaryLinkValues>(*this);
762✔
1213
    }
762✔
1214
    return nullptr;
63✔
1215
}
825✔
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
{
795✔
1228
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
795✔
1229
}
795✔
1230

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

1240
Obj DictionaryLinkValues::get_object(size_t row_ndx) const
1241
{
1,200✔
1242
    Mixed val = m_source.get_any(row_ndx);
1,200✔
1243
    if (val.is_type(type_TypedLink)) {
1,200✔
1244
        return get_table()->get_parent_group()->get_object(val.get_link());
1,191✔
1245
    }
1,191✔
1246
    return {};
9✔
1247
}
1,200✔
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

© 2025 Coveralls, Inc