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

realm / realm-core / jorgen.edelbo_322

20 Jun 2024 09:43AM UTC coverage: 84.879% (-6.1%) from 90.966%
jorgen.edelbo_322

Pull #7826

Evergreen

jedelbo
remove set_direct methods from integer compressors
Pull Request #7826: Merge Next major

66056 of 81292 branches covered (81.26%)

3131 of 3738 new or added lines in 54 files covered. (83.76%)

566 existing lines in 29 files now uncovered.

84791 of 99896 relevant lines covered (84.88%)

15351931.14 hits per line

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

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

46
} // namespace
47

48

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

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

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

76
Dictionary::~Dictionary() = default;
216,708✔
77

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

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

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

92
size_t Dictionary::size() const
93
{
406,977✔
94
    if (!update())
406,977✔
95
        return 0;
44,658✔
96

97
    return m_values->size();
362,319✔
98
}
406,977✔
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
{
94,260✔
125
    // Note: `size()` calls `update_if_needed()`.
126
    auto current_size = size();
94,260✔
127
    CollectionBase::validate_index("get_pair()", ndx, current_size);
94,260✔
128
    return do_get_pair(ndx);
94,260✔
129
}
94,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
{
42,834✔
145
    if (update()) {
42,834✔
146
        return do_find_key(key);
34,806✔
147
    }
34,806✔
148

149
    return realm::npos;
8,028✔
150
}
42,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) {
35,907✔
357
        return ascending ? values[i1] < values[i2] : values[i2] < values[i1];
35,907✔
358
    });
35,907✔
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
{
2,994✔
434
    if (dict_or_list == CollectionType::Set) {
2,994✔
435
        throw IllegalOperation("Set nested in Dictionary is not supported");
×
436
    }
×
437
    check_level();
2,994✔
438
    insert(path_elem.get_key(), Mixed(0, dict_or_list));
2,994✔
439
}
2,994✔
440

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

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

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

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

462
DictionaryPtr Dictionary::get_dictionary(const PathElement& path_elem) const
463
{
2,184✔
464
    return const_cast<Dictionary*>(this)->do_get_collection<Dictionary>(path_elem);
2,184✔
465
}
2,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,711✔
474
    if (auto opt_val = try_get(key)) {
15,711✔
475
        return *opt_val;
15,687✔
476
    }
15,687✔
477
    throw KeyNotFound("Dictionary::get");
24✔
478
}
15,711✔
479

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

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

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

504
std::pair<Dictionary::Iterator, bool> Dictionary::insert(Mixed key, Mixed value)
505
{
113,322✔
506
    auto my_table = get_table_unchecked();
113,322✔
507
    if (key.get_type() != m_key_type) {
113,322✔
508
        throw InvalidArgument(ErrorCodes::InvalidDictionaryKey, "Dictionary::insert: Invalid key type");
×
509
    }
×
510
    if (m_col_key) {
113,322✔
511
        if (value.is_null()) {
113,316✔
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 {
107,982✔
517
            if (m_col_key.get_type() == col_type_Link && value.get_type() == type_TypedLink) {
107,982✔
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())) {
105,132✔
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) {
105,132✔
527
                throw InvalidArgument(ErrorCodes::InvalidDictionaryValue,
×
528
                                      "Dictionary::insert: No target table for link");
×
529
            }
×
530
        }
107,982✔
531
    }
113,316✔
532

533
    validate_key_value(key);
113,316✔
534
    ensure_created();
113,316✔
535

536
    ObjLink new_link;
113,316✔
537
    if (value.is_type(type_TypedLink)) {
113,316✔
538
        new_link = value.get<ObjLink>();
8,358✔
539
        if (!new_link.is_unresolved())
8,358✔
540
            my_table->get_parent_group()->validate(new_link);
8,118✔
541
    }
8,358✔
542
    else if (value.is_type(type_Link)) {
104,958✔
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) {
113,310✔
553
        throw StaleAccessor("Stale dictionary");
×
554
    }
×
555

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

577
    if (Replication* repl = get_replication()) {
113,310✔
578
        if (old_entry) {
99,066✔
579
            repl->dictionary_set(*this, ndx, key, value);
9,114✔
580
        }
9,114✔
581
        else {
89,952✔
582
            repl->dictionary_insert(*this, ndx, key, value);
89,952✔
583
        }
89,952✔
584
    }
99,066✔
585
    bump_content_version();
113,310✔
586

587
    ObjLink old_link;
113,310✔
588
    if (old_entry) {
113,310✔
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) {
113,310✔
602
        m_values->ensure_keys();
2,700✔
603
        set_key(*m_values, ndx);
2,700✔
604
    }
2,700✔
605

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

613
    return {Iterator(this, ndx), !old_entry};
113,310✔
614
}
113,310✔
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
{
37,692✔
644
    auto ndx = find_any_key(key);
37,692✔
645
    if (ndx != realm::npos) {
37,692✔
646
        return Iterator(this, ndx);
22,344✔
647
    }
22,344✔
648
    return end();
15,348✔
649
}
37,692✔
650

651
void Dictionary::add_index(Path& path, const Index& index) const
652
{
2,520✔
653
    auto ndx = m_values->find_key(index.get_salt());
2,520✔
654
    auto keys = static_cast<BPlusTree<StringData>*>(m_keys.get());
2,520✔
655
    path.emplace_back(keys->get(ndx));
2,520✔
656
}
2,520✔
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
{
1,660,632✔
666
    switch (get_update_status()) {
1,660,632✔
667
        case UpdateStatus::Detached: {
6✔
668
            m_dictionary_top.reset();
6✔
669
            return UpdateStatus::Detached;
6✔
670
        }
×
671
        case UpdateStatus::NoChange: {
1,430,703✔
672
            if (m_dictionary_top && m_dictionary_top->is_attached()) {
1,430,703✔
673
                return UpdateStatus::NoChange;
1,383,603✔
674
            }
1,383,603✔
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,430,703✔
678
        }
47,100✔
679
        case UpdateStatus::Updated:
277,026✔
680
            return init_from_parent(allow_create);
277,026✔
681
    }
1,660,632✔
682
    REALM_UNREACHABLE();
683
}
×
684

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

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

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

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

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

712
    return true;
10,968✔
713
}
12,216✔
714

715
void Dictionary::erase(Mixed key)
716
{
10,746✔
717
    if (!try_erase(key)) {
10,746✔
718
        throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
618✔
719
    }
618✔
720
}
10,746✔
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
{
3,510✔
809
    size_t sz = size();
3,510✔
810
    bool recurse = false;
3,510✔
811
    for (size_t ndx = 0; ndx < sz; ndx++) {
14,982✔
812
        if (clear_backlink(ndx, state)) {
11,472✔
813
            recurse = true;
369✔
814
        }
369✔
815
    }
11,472✔
816
    return recurse;
3,510✔
817
}
3,510✔
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,066✔
826
    if (size() > 0) {
3,066✔
827
        if (Replication* repl = get_replication()) {
2,658✔
828
            repl->dictionary_clear(*this);
2,646✔
829
        }
2,646✔
830
        CascadeState cascade_state(CascadeState::Mode::Strong);
2,658✔
831
        bool recurse = remove_backlinks(cascade_state);
2,658✔
832

833
        // Just destroy the whole cluster
834
        m_dictionary_top->destroy_deep();
2,658✔
835
        m_dictionary_top.reset();
2,658✔
836

837
        update_child_ref(0, 0);
2,658✔
838

839
        if (recurse)
2,658✔
840
            _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
6✔
841
    }
2,658✔
842
}
3,066✔
843

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

870
        if (ref) {
277,026✔
871
            m_dictionary_top->init_from_ref(ref);
180,366✔
872
            m_keys->init_from_parent();
180,366✔
873
            m_values->init_from_parent();
180,366✔
874
        }
180,366✔
875
        else {
96,660✔
876
            // dictionary detached
877
            if (!allow_create) {
96,660✔
878
                m_dictionary_top.reset();
64,074✔
879
                return UpdateStatus::Detached;
64,074✔
880
            }
64,074✔
881

882
            // Create dictionary
883
            m_dictionary_top->create(Array::type_HasRefs, false, 2, 0);
32,586✔
884
            m_values->create();
32,586✔
885
            m_keys->create();
32,586✔
886
            m_dictionary_top->update_parent();
32,586✔
887
        }
32,586✔
888

889
        return UpdateStatus::Updated;
212,952✔
890
    }
277,026✔
891
    catch (...) {
277,026✔
892
        m_dictionary_top.reset();
30✔
893
        throw;
30✔
894
    }
30✔
895
}
277,026✔
896

897
size_t Dictionary::do_find_key(Mixed key) const noexcept
898
{
889,773✔
899
    auto [ndx, actual_key] = find_impl(key);
889,773✔
900
    if (actual_key == key) {
889,773✔
901
        return ndx;
877,227✔
902
    }
877,227✔
903
    return realm::npos;
12,546✔
904
}
889,773✔
905

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

939
    return {sz, actual};
33,852✔
940
}
1,003,077✔
941

942
Mixed Dictionary::do_get(size_t ndx) const
943
{
951,564✔
944
    Mixed val = m_values->get(ndx);
951,564✔
945

946
    // Filter out potential unresolved links
947
    if (val.is_type(type_TypedLink) && val.get<ObjKey>().is_unresolved()) {
951,564✔
948
        return {};
1,104✔
949
    }
1,104✔
950
    return val;
950,460✔
951
}
951,564✔
952

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

958
    if (recurse)
13,614✔
959
        _impl::TableFriend::remove_recursive(*get_table_unchecked(), cascade_state); // Throws
102✔
960

961
    if (Replication* repl = get_replication()) {
13,614✔
962
        repl->dictionary_erase(*this, ndx, key);
12,360✔
963
    }
12,360✔
964

965
    m_keys->erase(ndx);
13,614✔
966
    m_values->erase(ndx);
13,614✔
967
    bump_content_version();
13,614✔
968
}
13,614✔
969

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

983
    return {};
×
984
}
157,020✔
985

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

991
bool Dictionary::clear_backlink(size_t ndx, CascadeState& state) const
992
{
25,086✔
993
    auto value = m_values->get(ndx);
25,086✔
994
    if (value.is_type(type_TypedLink)) {
25,086✔
995
        return Base::remove_backlink(m_col_key, value.get_link(), state);
7,854✔
996
    }
7,854✔
997
    if (value.is_type(type_Dictionary)) {
17,232✔
998
        Dictionary dict{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
138✔
999
        return dict.remove_backlinks(state);
138✔
1000
    }
138✔
1001
    if (value.is_type(type_List)) {
17,094✔
1002
        Lst<Mixed> list{*const_cast<Dictionary*>(this), m_values->get_key(ndx)};
186✔
1003
        return list.remove_backlinks(state);
186✔
1004
    }
186✔
1005
    return false;
16,908✔
1006
}
17,094✔
1007

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

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

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

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

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

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

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

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

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

1059

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1215
ref_type Dictionary::typed_write(ref_type ref, _impl::ArrayWriterBase& out, Allocator& alloc)
1216
{
48,504✔
1217
    if (out.only_modified && alloc.is_read_only(ref))
48,504✔
1218
        return ref;
4,326✔
1219

1220
    ArrayRef dict_top(alloc);
44,178✔
1221
    dict_top.init_from_ref(ref);
44,178✔
1222
    REALM_ASSERT_DEBUG(dict_top.size() == 2);
44,178✔
1223
    TempArray written_dict_top(2);
44,178✔
1224

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

1239
    auto values_ref = dict_top.get_as_ref(1);
44,178✔
1240
    written_dict_top.set_as_ref(1, BPlusTree<Mixed>::typed_write(values_ref, out, alloc));
44,178✔
1241

1242
    return written_dict_top.write(out);
44,178✔
1243
}
48,504✔
1244

1245
/************************* DictionaryLinkValues *************************/
1246

1247
DictionaryLinkValues::DictionaryLinkValues(const Obj& obj, ColKey col_key)
1248
    : m_source(obj, col_key)
1249
{
×
1250
    REALM_ASSERT_EX(col_key.get_type() == col_type_Link, col_key.get_type());
×
1251
}
×
1252

1253
DictionaryLinkValues::DictionaryLinkValues(const Dictionary& source)
1254
    : m_source(source)
795✔
1255
{
1,590✔
1256
    REALM_ASSERT_EX(source.get_value_data_type() == type_Link, source.get_value_data_type());
1,590✔
1257
}
1,590✔
1258

1259
ObjKey DictionaryLinkValues::get_key(size_t ndx) const
1260
{
72✔
1261
    Mixed val = m_source.get_any(ndx);
72✔
1262
    if (val.is_type(type_Link, type_TypedLink)) {
72✔
1263
        return val.get<ObjKey>();
48✔
1264
    }
48✔
1265
    return {};
24✔
1266
}
72✔
1267

1268
Obj DictionaryLinkValues::get_object(size_t row_ndx) const
1269
{
2,400✔
1270
    Mixed val = m_source.get_any(row_ndx);
2,400✔
1271
    if (val.is_type(type_TypedLink)) {
2,400✔
1272
        return get_table()->get_parent_group()->get_object(val.get_link());
2,382✔
1273
    }
2,382✔
1274
    return {};
18✔
1275
}
2,400✔
1276

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