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

realm / realm-core / jorgen.edelbo_138

13 Mar 2024 08:41AM UTC coverage: 91.77% (-0.3%) from 92.078%
jorgen.edelbo_138

Pull #7356

Evergreen

jedelbo
Add ability to get path to modified collections in object notifications
Pull Request #7356: Add ability to get path to modified collections in object notifications

94532 of 174642 branches covered (54.13%)

118 of 163 new or added lines in 16 files covered. (72.39%)

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

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

91.69
/src/realm/set.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2020 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/set.hpp"
20

21
#include "realm/array_basic.hpp"
22
#include "realm/array_binary.hpp"
23
#include "realm/array_bool.hpp"
24
#include "realm/array_decimal128.hpp"
25
#include "realm/array_fixed_bytes.hpp"
26
#include "realm/array_integer.hpp"
27
#include "realm/array_mixed.hpp"
28
#include "realm/array_string.hpp"
29
#include "realm/array_timestamp.hpp"
30
#include "realm/array_typed_link.hpp"
31
#include "realm/replication.hpp"
32

33
#include <numeric> // std::iota
34

35
namespace realm {
36

37

38
/********************************** SetBase *********************************/
39

40
void SetBase::insert_repl(Replication* repl, size_t index, Mixed value) const
41
{
63,378✔
42
    repl->set_insert(*this, index, value);
63,378✔
43
}
63,378✔
44

45
void SetBase::erase_repl(Replication* repl, size_t index, Mixed value) const
46
{
9,954✔
47
    repl->set_erase(*this, index, value);
9,954✔
48
}
9,954✔
49

50
void SetBase::clear_repl(Replication* repl) const
51
{
2,418✔
52
    repl->set_clear(*this);
2,418✔
53
}
2,418✔
54

55
static std::vector<Mixed> convert_to_set(const CollectionBase& rhs)
56
{
426✔
57
    std::vector<Mixed> mixed(rhs.begin(), rhs.end());
426✔
58
    std::sort(mixed.begin(), mixed.end());
426✔
59
    mixed.erase(std::unique(mixed.begin(), mixed.end()), mixed.end());
426✔
60
    return mixed;
426✔
61
}
426✔
62

63
bool SetBase::is_subset_of(const CollectionBase& rhs) const
64
{
174✔
65
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
174✔
66
        return is_subset_of(other_set->begin(), other_set->end());
48✔
67
    }
48✔
68
    auto other_set = convert_to_set(rhs);
126✔
69
    return is_subset_of(other_set.begin(), other_set.end());
126✔
70
}
126✔
71

72
template <class It1, class It2>
73
bool SetBase::is_subset_of(It1 first, It2 last) const
74
{
330✔
75
    return std::includes(first, last, begin(), end());
330✔
76
}
330✔
77

78
bool SetBase::is_strict_subset_of(const CollectionBase& rhs) const
79
{
30✔
80
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
30✔
81
        return size() != rhs.size() && is_subset_of(other_set->begin(), other_set->end());
6!
82
    }
6✔
83
    auto other_set = convert_to_set(rhs);
24✔
84
    return size() != other_set.size() && is_subset_of(other_set.begin(), other_set.end());
24✔
85
}
24✔
86

87
bool SetBase::is_superset_of(const CollectionBase& rhs) const
88
{
60✔
89
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
60✔
90
        return is_superset_of(other_set->begin(), other_set->end());
48✔
91
    }
48✔
92
    auto other_set = convert_to_set(rhs);
12✔
93
    return is_superset_of(other_set.begin(), other_set.end());
12✔
94
}
12✔
95

96
template <class It1, class It2>
97
bool SetBase::is_superset_of(It1 first, It2 last) const
98
{
78✔
99
    return std::includes(begin(), end(), first, last);
78✔
100
}
78✔
101

102
bool SetBase::is_strict_superset_of(const CollectionBase& rhs) const
103
{
30✔
104
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
30✔
105
        return size() != rhs.size() && is_superset_of(other_set->begin(), other_set->end());
6!
106
    }
6✔
107
    auto other_set = convert_to_set(rhs);
24✔
108
    return size() != other_set.size() && is_superset_of(other_set.begin(), other_set.end());
24✔
109
}
24✔
110

111
bool SetBase::intersects(const CollectionBase& rhs) const
112
{
72✔
113
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
72✔
114
        return intersects(other_set->begin(), other_set->end());
60✔
115
    }
60✔
116
    auto other_set = convert_to_set(rhs);
12✔
117
    return intersects(other_set.begin(), other_set.end());
12✔
118
}
12✔
119

120
template <class It1, class It2>
121
bool SetBase::intersects(It1 first, It2 last) const
122
{
72✔
123
    auto it = begin();
72✔
124
    while (it != end() && first != last) {
192✔
125
        if (*it < *first) {
180✔
126
            ++it;
24✔
127
        }
24✔
128
        else if (*first < *it) {
156✔
129
            ++first;
96✔
130
        }
96✔
131
        else {
60✔
132
            return true;
60✔
133
        }
60✔
134
    }
180✔
135
    return false;
42✔
136
}
72✔
137

138
bool SetBase::set_equals(const CollectionBase& rhs) const
139
{
162✔
140
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
162✔
141
        return size() == rhs.size() && is_subset_of(other_set->begin(), other_set->end());
6✔
142
    }
6✔
143
    auto other_set = convert_to_set(rhs);
156✔
144
    return size() == other_set.size() && is_subset_of(other_set.begin(), other_set.end());
156✔
145
}
156✔
146

147
void SetBase::assign_union(const CollectionBase& rhs)
148
{
174✔
149
    if (*this == rhs) {
174✔
150
        return;
114✔
151
    }
114✔
152
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
60✔
153
        return assign_union(other_set->begin(), other_set->end());
36✔
154
    }
36✔
155
    auto other_set = convert_to_set(rhs);
24✔
156
    return assign_union(other_set.begin(), other_set.end());
24✔
157
}
24✔
158

159
template <class It1, class It2>
160
void SetBase::assign_union(It1 first, It2 last)
161
{
60✔
162
    std::vector<Mixed> the_diff;
60✔
163
    std::set_difference(first, last, begin(), end(), std::back_inserter(the_diff));
60✔
164
    // 'the_diff' now contains all the elements that are in foreign set, but not in 'this'
30✔
165
    // Now insert those elements
30✔
166
    for (auto&& value : the_diff) {
120✔
167
        insert_any(value);
120✔
168
    }
120✔
169
}
60✔
170

171
void SetBase::assign_intersection(const CollectionBase& rhs)
172
{
156✔
173
    if (*this == rhs) {
156✔
174
        return;
114✔
175
    }
114✔
176
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
42✔
177
        return assign_intersection(other_set->begin(), other_set->end());
30✔
178
    }
30✔
179
    auto other_set = convert_to_set(rhs);
12✔
180
    return assign_intersection(other_set.begin(), other_set.end());
12✔
181
}
12✔
182

183
template <class It1, class It2>
184
void SetBase::assign_intersection(It1 first, It2 last)
185
{
42✔
186
    std::vector<Mixed> intersection;
42✔
187
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection));
42✔
188
    clear();
42✔
189
    // Elements in intersection comes from foreign set, so ok to use here
21✔
190
    for (auto&& value : intersection) {
90✔
191
        insert_any(value);
90✔
192
    }
90✔
193
}
42✔
194

195
void SetBase::assign_difference(const CollectionBase& rhs)
196
{
162✔
197
    if (*this == rhs) {
162✔
198
        clear();
114✔
199
        return;
114✔
200
    }
114✔
201
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
48✔
202
        return assign_difference(other_set->begin(), other_set->end());
30✔
203
    }
30✔
204
    auto other_set = convert_to_set(rhs);
18✔
205
    return assign_difference(other_set.begin(), other_set.end());
18✔
206
}
18✔
207

208
template <class It1, class It2>
209
void SetBase::assign_difference(It1 first, It2 last)
210
{
48✔
211
    std::vector<Mixed> intersection;
48✔
212
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection));
48✔
213
    // 'intersection' now contains all the elements that are in both foreign set and 'this'.
24✔
214
    // Remove those elements. The elements comes from the foreign set, so ok to refer to.
24✔
215
    for (auto&& value : intersection) {
126✔
216
        erase_any(value);
126✔
217
    }
126✔
218
}
48✔
219

220
void SetBase::assign_symmetric_difference(const CollectionBase& rhs)
221
{
144✔
222
    if (*this == rhs) {
144✔
223
        clear();
114✔
224
        return;
114✔
225
    }
114✔
226
    if (auto other_set = dynamic_cast<const SetBase*>(&rhs)) {
30✔
227
        return assign_symmetric_difference(other_set->begin(), other_set->end());
12✔
228
    }
12✔
229
    auto other_set = convert_to_set(rhs);
18✔
230
    return assign_symmetric_difference(other_set.begin(), other_set.end());
18✔
231
}
18✔
232

233
template <class It1, class It2>
234
void SetBase::assign_symmetric_difference(It1 first, It2 last)
235
{
30✔
236
    std::vector<Mixed> difference;
30✔
237
    std::set_difference(first, last, begin(), end(), std::back_inserter(difference));
30✔
238
    std::vector<Mixed> intersection;
30✔
239
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection));
30✔
240
    // Now remove the common elements and add the differences
15✔
241
    for (auto&& value : intersection) {
48✔
242
        erase_any(value);
48✔
243
    }
48✔
244
    for (auto&& value : difference) {
48✔
245
        insert_any(value);
48✔
246
    }
48✔
247
}
30✔
248

249
template <>
250
void CollectionBaseImpl<SetBase>::to_json(std::ostream& out, JSONOutputMode output_mode,
251
                                          util::FunctionRef<void(const Mixed&)> fn) const
252
{
270✔
253
    if (output_mode == output_mode_xjson_plus) {
270✔
254
        out << "{ \"$set\": ";
90✔
255
    }
90✔
256

135✔
257
    out << "[";
270✔
258
    auto sz = size();
270✔
259
    for (size_t i = 0; i < sz; i++) {
540✔
260
        if (i > 0)
270✔
261
            out << ",";
×
262
        Mixed val = get_any(i);
270✔
263
        if (val.is_type(type_Link, type_TypedLink)) {
270✔
264
            fn(val);
×
265
        }
×
266
        else {
270✔
267
            val.to_json(out, output_mode);
270✔
268
        }
270✔
269
    }
270✔
270
    out << "]";
270✔
271
    if (output_mode == output_mode_xjson_plus) {
270✔
272
        out << "}";
90✔
273
    }
90✔
274
}
270✔
275

276
void SetBase::resort_range(size_t start, size_t end)
277
{
102✔
278
    if (end > size()) {
102✔
UNCOV
279
        end = size();
×
UNCOV
280
    }
×
281
    if (start >= end) {
102✔
282
        return;
6✔
283
    }
6✔
284
    std::vector<size_t> indices;
96✔
285
    indices.resize(end - start);
96✔
286
    std::iota(indices.begin(), indices.end(), 0);
96✔
287
    std::sort(indices.begin(), indices.end(), [&](auto a, auto b) {
1,272✔
288
        return get_any(a + start) < get_any(b + start);
1,272✔
289
    });
1,272✔
290
    for (size_t i = 0; i < indices.size(); ++i) {
594✔
291
        if (indices[i] != i) {
498✔
292
            m_tree->swap(i + start, start + indices[i]);
174✔
293
            auto it = std::find(indices.begin() + i, indices.end(), i);
174✔
294
            REALM_ASSERT(it != indices.end());
174✔
295
            *it = indices[i];
174✔
296
            indices[i] = i;
174✔
297
        }
174✔
298
    }
498✔
299
}
96✔
300

301
/********************************* Set<Key> *********************************/
302

303
template <>
304
void Set<ObjKey>::do_insert(size_t ndx, ObjKey target_key)
305
{
18,048✔
306
    auto origin_table = get_table_unchecked();
18,048✔
307
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
18,048✔
308
    set_backlink(m_col_key, {target_table_key, target_key});
18,048✔
309
    tree().insert(ndx, target_key);
18,048✔
310
    if (target_key.is_unresolved()) {
18,048✔
311
        tree().set_context_flag(true);
126✔
312
    }
126✔
313
}
18,048✔
314

315
template <>
316
void Set<ObjKey>::do_erase(size_t ndx)
317
{
5,820✔
318
    auto origin_table = get_table_unchecked();
5,820✔
319
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
5,820✔
320
    ObjKey old_key = get(ndx);
5,820✔
321
    CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
5,814✔
322

2,910✔
323
    bool recurse = remove_backlink(m_col_key, {target_table_key, old_key}, state);
5,820✔
324

2,910✔
325
    tree().erase(ndx);
5,820✔
326

2,910✔
327
    if (recurse) {
5,820✔
UNCOV
328
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
UNCOV
329
    }
×
330
    if (old_key.is_unresolved()) {
5,820✔
331
        // We might have removed the last unresolved link - check it
6✔
332

6✔
333
        // FIXME: Exploit the fact that the values are sorted and unresolved
6✔
334
        // keys have a negative value.
6✔
335
        _impl::check_for_last_unresolved(&tree());
12✔
336
    }
12✔
337
}
5,820✔
338

339
template <>
340
void Set<ObjKey>::do_clear()
341
{
924✔
342
    size_t ndx = size();
924✔
343
    while (ndx--) {
4,752✔
344
        do_erase(ndx);
3,828✔
345
    }
3,828✔
346

462✔
347
    tree().set_context_flag(false);
924✔
348
}
924✔
349

350
template <>
351
void Set<ObjKey>::migrate()
UNCOV
352
{
×
UNCOV
353
}
×
354

355
template class Set<ObjKey>;
356

357
template <>
358
void Set<ObjLink>::do_insert(size_t ndx, ObjLink target_link)
UNCOV
359
{
×
UNCOV
360
    set_backlink(m_col_key, target_link);
×
UNCOV
361
    tree().insert(ndx, target_link);
×
UNCOV
362
}
×
363

364
template <>
365
void Set<ObjLink>::do_erase(size_t ndx)
UNCOV
366
{
×
UNCOV
367
    ObjLink old_link = get(ndx);
×
UNCOV
368
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
369

UNCOV
370
    bool recurse = remove_backlink(m_col_key, old_link, state);
×
371

UNCOV
372
    tree().erase(ndx);
×
373

UNCOV
374
    if (recurse) {
×
UNCOV
375
        auto table = get_table_unchecked();
×
UNCOV
376
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
UNCOV
377
    }
×
378
}
×
379

380
template <>
381
void Set<Mixed>::do_insert(size_t ndx, Mixed value)
382
{
9,744✔
383
    REALM_ASSERT(!value.is_type(type_Link));
9,744✔
384
    if (value.is_type(type_TypedLink)) {
9,744✔
385
        auto target_link = value.get<ObjLink>();
5,418✔
386
        get_table_unchecked()->get_parent_group()->validate(target_link);
5,418✔
387
        set_backlink(m_col_key, target_link);
5,418✔
388
    }
5,418✔
389
    tree().insert(ndx, value);
9,744✔
390
}
9,744✔
391

392
template <>
393
void Set<Mixed>::do_erase(size_t ndx)
394
{
3,966✔
395
    if (Mixed old_value = get(ndx); old_value.is_type(type_TypedLink)) {
3,966✔
396
        auto old_link = old_value.get<ObjLink>();
1,308✔
397

654✔
398
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
690✔
399
                                                                  : CascadeState::Mode::Strong);
1,272✔
400
        bool recurse = remove_backlink(m_col_key, old_link, state);
1,308✔
401

654✔
402
        tree().erase(ndx);
1,308✔
403

654✔
404
        if (recurse) {
1,308✔
UNCOV
405
            auto table = get_table_unchecked();
×
UNCOV
406
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
UNCOV
407
        }
×
408
    }
1,308✔
409
    else {
2,658✔
410
        tree().erase(ndx);
2,658✔
411
    }
2,658✔
412
}
3,966✔
413

414
template <>
415
void Set<Mixed>::do_clear()
416
{
270✔
417
    size_t ndx = size();
270✔
418
    while (ndx--) {
2,382✔
419
        do_erase(ndx);
2,112✔
420
    }
2,112✔
421
}
270✔
422

423
template <>
424
void Set<Mixed>::migrate()
425
{
6✔
426
    // We should just move all string values to be before the binary values
3✔
427
    size_t first_binary = size();
6✔
428
    BPlusTree<Mixed>& my_tree(tree());
6✔
429
    for (size_t n = 0; n < size(); n++) {
24✔
430
        if (my_tree.get(n).is_type(type_Binary)) {
24✔
431
            first_binary = n;
6✔
432
            break;
6✔
433
        }
6✔
434
    }
24✔
435

3✔
436
    for (size_t n = first_binary; n < size(); n++) {
36✔
437
        if (my_tree.get(n).is_type(type_String)) {
30✔
438
            my_tree.insert(first_binary, Mixed());
12✔
439
            my_tree.swap(n + 1, first_binary);
12✔
440
            my_tree.erase(n + 1);
12✔
441
            first_binary++;
12✔
442
        }
12✔
443
    }
30✔
444
}
6✔
445

446
template <>
447
void Set<Mixed>::migration_resort()
448
{
12✔
449
    // sort order of strings and binaries changed
6✔
450
    auto first_string = std::lower_bound(begin(), end(), StringData(""));
12✔
451
    auto last_binary = std::partition_point(first_string, end(), [](const Mixed& item) {
48✔
452
        return item.is_type(type_String, type_Binary);
48✔
453
    });
48✔
454
    resort_range(first_string.index(), last_binary.index());
12✔
455
}
12✔
456

457
template <>
458
void Set<StringData>::migration_resort()
459
{
84✔
460
    // sort order of strings changed
42✔
461
    resort_range(0, size());
84✔
462
}
84✔
463

464
template <>
465
void Set<BinaryData>::migration_resort()
466
{
6✔
467
    // sort order of binaries changed
3✔
468
    resort_range(0, size());
6✔
469
}
6✔
470

471
void LnkSet::remove_target_row(size_t link_ndx)
UNCOV
472
{
×
473
    // Deleting the object will automatically remove all links
474
    // to it. So we do not have to manually remove the deleted link
UNCOV
475
    ObjKey k = get(link_ndx);
×
UNCOV
476
    get_target_table()->remove_object(k);
×
UNCOV
477
}
×
478

479
void LnkSet::remove_all_target_rows()
480
{
510✔
481
    if (m_set.update()) {
510✔
482
        _impl::TableFriend::batch_erase_rows(*get_target_table(), m_set.tree());
510✔
483
    }
510✔
484
}
510✔
485

486
void LnkSet::to_json(std::ostream& out, JSONOutputMode, util::FunctionRef<void(const Mixed&)> fn) const
487
{
36✔
488
    out << "[";
36✔
489

18✔
490
    auto sz = m_set.size();
36✔
491
    for (size_t i = 0; i < sz; i++) {
72✔
492
        if (i > 0)
36✔
493
            out << ",";
12✔
494
        Mixed val(m_set.get(i));
36✔
495
        fn(val);
36✔
496
    }
36✔
497

18✔
498
    out << "]";
36✔
499
}
36✔
500

501
void set_sorted_indices(size_t sz, std::vector<size_t>& indices, bool ascending)
502
{
32,946✔
503
    indices.resize(sz);
32,946✔
504
    if (ascending) {
32,946✔
505
        std::iota(indices.begin(), indices.end(), 0);
32,658✔
506
    }
32,658✔
507
    else {
288✔
508
        std::iota(indices.rbegin(), indices.rend(), 0);
288✔
509
    }
288✔
510
}
32,946✔
511
} // 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