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

realm / realm-core / 2100

05 Mar 2024 07:31PM UTC coverage: 90.931% (-0.005%) from 90.936%
2100

push

Evergreen

web-flow
Merge pull request #7410 from realm/tg/set-cleanup

Delete leftover code for Set's order differing from Mixed

93886 of 173056 branches covered (54.25%)

39 of 41 new or added lines in 3 files covered. (95.12%)

143 existing lines in 12 files now uncovered.

238325 of 262094 relevant lines covered (90.93%)

6055791.07 hits per line

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

90.89
/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
bool SetBase::do_init_from_parent(ref_type ref, bool allow_create) const
277
{
184,035✔
278
    try {
184,035✔
279
        if (ref) {
184,035✔
280
            m_tree->init_from_ref(ref);
111,051✔
281
        }
111,051✔
282
        else {
72,984✔
283
            if (m_tree->init_from_parent()) {
72,984✔
284
                // All is well
285
                return true;
×
286
            }
×
287
            if (!allow_create) {
72,984✔
288
                return false;
48,636✔
289
            }
48,636✔
290
            // The ref in the column was NULL, create the tree in place.
12,138✔
291
            m_tree->create();
24,348✔
292
            REALM_ASSERT(m_tree->is_attached());
24,348✔
293
        }
24,348✔
294
    }
184,035✔
295
    catch (...) {
91,752✔
296
        m_tree->detach();
×
297
        throw;
×
298
    }
×
299
    return true;
135,399✔
300
}
135,399✔
301

302
void SetBase::resort_range(size_t start, size_t end)
303
{
102✔
304
    if (end > size()) {
102✔
NEW
305
        end = size();
×
NEW
306
    }
×
307
    if (start >= end) {
102✔
308
        return;
6✔
309
    }
6✔
310
    std::vector<size_t> indices;
96✔
311
    indices.resize(end - start);
96✔
312
    std::iota(indices.begin(), indices.end(), 0);
96✔
313
    std::sort(indices.begin(), indices.end(), [&](auto a, auto b) {
1,272✔
314
        return get_any(a + start) < get_any(b + start);
1,272✔
315
    });
1,272✔
316
    for (size_t i = 0; i < indices.size(); ++i) {
594✔
317
        if (indices[i] != i) {
498✔
318
            m_tree->swap(i + start, start + indices[i]);
174✔
319
            auto it = std::find(indices.begin() + i, indices.end(), i);
174✔
320
            REALM_ASSERT(it != indices.end());
174✔
321
            *it = indices[i];
174✔
322
            indices[i] = i;
174✔
323
        }
174✔
324
    }
498✔
325
}
96✔
326

327
/********************************* Set<Key> *********************************/
328

329
template <>
330
void Set<ObjKey>::do_insert(size_t ndx, ObjKey target_key)
331
{
18,048✔
332
    auto origin_table = get_table_unchecked();
18,048✔
333
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
18,048✔
334
    set_backlink(m_col_key, {target_table_key, target_key});
18,048✔
335
    tree().insert(ndx, target_key);
18,048✔
336
    if (target_key.is_unresolved()) {
18,048✔
337
        tree().set_context_flag(true);
126✔
338
    }
126✔
339
}
18,048✔
340

341
template <>
342
void Set<ObjKey>::do_erase(size_t ndx)
343
{
5,796✔
344
    auto origin_table = get_table_unchecked();
5,796✔
345
    auto target_table_key = origin_table->get_opposite_table_key(m_col_key);
5,796✔
346
    ObjKey old_key = get(ndx);
5,796✔
347
    CascadeState state(old_key.is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
5,790✔
348

2,898✔
349
    bool recurse = remove_backlink(m_col_key, {target_table_key, old_key}, state);
5,796✔
350

2,898✔
351
    tree().erase(ndx);
5,796✔
352

2,898✔
353
    if (recurse) {
5,796✔
354
        _impl::TableFriend::remove_recursive(*origin_table, state); // Throws
×
355
    }
×
356
    if (old_key.is_unresolved()) {
5,796✔
357
        // We might have removed the last unresolved link - check it
6✔
358

6✔
359
        // FIXME: Exploit the fact that the values are sorted and unresolved
6✔
360
        // keys have a negative value.
6✔
361
        _impl::check_for_last_unresolved(&tree());
12✔
362
    }
12✔
363
}
5,796✔
364

365
template <>
366
void Set<ObjKey>::do_clear()
367
{
924✔
368
    size_t ndx = size();
924✔
369
    while (ndx--) {
4,752✔
370
        do_erase(ndx);
3,828✔
371
    }
3,828✔
372

462✔
373
    tree().set_context_flag(false);
924✔
374
}
924✔
375

376
template <>
377
void Set<ObjKey>::migrate()
378
{
×
379
}
×
380

381
template class Set<ObjKey>;
382

383
template <>
384
void Set<ObjLink>::do_insert(size_t ndx, ObjLink target_link)
385
{
×
386
    set_backlink(m_col_key, target_link);
×
387
    tree().insert(ndx, target_link);
×
388
}
×
389

390
template <>
391
void Set<ObjLink>::do_erase(size_t ndx)
392
{
×
393
    ObjLink old_link = get(ndx);
×
394
    CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All : CascadeState::Mode::Strong);
×
395

396
    bool recurse = remove_backlink(m_col_key, old_link, state);
×
397

398
    tree().erase(ndx);
×
399

400
    if (recurse) {
×
401
        auto table = get_table_unchecked();
×
402
        _impl::TableFriend::remove_recursive(*table, state); // Throws
×
403
    }
×
404
}
×
405

406
template <>
407
void Set<Mixed>::do_insert(size_t ndx, Mixed value)
408
{
9,744✔
409
    REALM_ASSERT(!value.is_type(type_Link));
9,744✔
410
    if (value.is_type(type_TypedLink)) {
9,744✔
411
        auto target_link = value.get<ObjLink>();
5,418✔
412
        get_table_unchecked()->get_parent_group()->validate(target_link);
5,418✔
413
        set_backlink(m_col_key, target_link);
5,418✔
414
    }
5,418✔
415
    tree().insert(ndx, value);
9,744✔
416
}
9,744✔
417

418
template <>
419
void Set<Mixed>::do_erase(size_t ndx)
420
{
3,966✔
421
    if (Mixed old_value = get(ndx); old_value.is_type(type_TypedLink)) {
3,966✔
422
        auto old_link = old_value.get<ObjLink>();
1,308✔
423

654✔
424
        CascadeState state(old_link.get_obj_key().is_unresolved() ? CascadeState::Mode::All
690✔
425
                                                                  : CascadeState::Mode::Strong);
1,272✔
426
        bool recurse = remove_backlink(m_col_key, old_link, state);
1,308✔
427

654✔
428
        tree().erase(ndx);
1,308✔
429

654✔
430
        if (recurse) {
1,308✔
431
            auto table = get_table_unchecked();
×
432
            _impl::TableFriend::remove_recursive(*table, state); // Throws
×
433
        }
×
434
    }
1,308✔
435
    else {
2,658✔
436
        tree().erase(ndx);
2,658✔
437
    }
2,658✔
438
}
3,966✔
439

440
template <>
441
void Set<Mixed>::do_clear()
442
{
270✔
443
    size_t ndx = size();
270✔
444
    while (ndx--) {
2,382✔
445
        do_erase(ndx);
2,112✔
446
    }
2,112✔
447
}
270✔
448

449
template <>
450
void Set<Mixed>::migrate()
451
{
6✔
452
    // We should just move all string values to be before the binary values
3✔
453
    size_t first_binary = size();
6✔
454
    BPlusTree<Mixed>& my_tree(tree());
6✔
455
    for (size_t n = 0; n < size(); n++) {
24✔
456
        if (my_tree.get(n).is_type(type_Binary)) {
24✔
457
            first_binary = n;
6✔
458
            break;
6✔
459
        }
6✔
460
    }
24✔
461

3✔
462
    for (size_t n = first_binary; n < size(); n++) {
36✔
463
        if (my_tree.get(n).is_type(type_String)) {
30✔
464
            my_tree.insert(first_binary, Mixed());
12✔
465
            my_tree.swap(n + 1, first_binary);
12✔
466
            my_tree.erase(n + 1);
12✔
467
            first_binary++;
12✔
468
        }
12✔
469
    }
30✔
470
}
6✔
471

472
template <>
473
void Set<Mixed>::migration_resort()
474
{
12✔
475
    // sort order of strings and binaries changed
6✔
476
    auto first_string = std::lower_bound(begin(), end(), StringData(""));
12✔
477
    auto last_binary = std::partition_point(first_string, end(), [](const Mixed& item) {
48✔
478
        return item.is_type(type_String, type_Binary);
48✔
479
    });
48✔
480
    resort_range(first_string.index(), last_binary.index());
12✔
481
}
12✔
482

483
template <>
484
void Set<StringData>::migration_resort()
485
{
84✔
486
    // sort order of strings changed
42✔
487
    resort_range(0, size());
84✔
488
}
84✔
489

490
template <>
491
void Set<BinaryData>::migration_resort()
492
{
6✔
493
    // sort order of binaries changed
3✔
494
    resort_range(0, size());
6✔
495
}
6✔
496

497
void LnkSet::remove_target_row(size_t link_ndx)
498
{
×
499
    // Deleting the object will automatically remove all links
500
    // to it. So we do not have to manually remove the deleted link
501
    ObjKey k = get(link_ndx);
×
502
    get_target_table()->remove_object(k);
×
503
}
×
504

505
void LnkSet::remove_all_target_rows()
506
{
510✔
507
    if (m_set.update()) {
510✔
508
        _impl::TableFriend::batch_erase_rows(*get_target_table(), m_set.tree());
510✔
509
    }
510✔
510
}
510✔
511

512
void LnkSet::to_json(std::ostream& out, JSONOutputMode, util::FunctionRef<void(const Mixed&)> fn) const
513
{
36✔
514
    out << "[";
36✔
515

18✔
516
    auto sz = m_set.size();
36✔
517
    for (size_t i = 0; i < sz; i++) {
72✔
518
        if (i > 0)
36✔
519
            out << ",";
12✔
520
        Mixed val(m_set.get(i));
36✔
521
        fn(val);
36✔
522
    }
36✔
523

18✔
524
    out << "]";
36✔
525
}
36✔
526

527
void set_sorted_indices(size_t sz, std::vector<size_t>& indices, bool ascending)
528
{
32,898✔
529
    indices.resize(sz);
32,898✔
530
    if (ascending) {
32,898✔
531
        std::iota(indices.begin(), indices.end(), 0);
32,610✔
532
    }
32,610✔
533
    else {
288✔
534
        std::iota(indices.rbegin(), indices.rend(), 0);
288✔
535
    }
288✔
536
}
32,898✔
537
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc