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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

83.14
/src/realm/set.hpp
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
#ifndef REALM_SET_HPP
20
#define REALM_SET_HPP
21

22
#include <realm/collection.hpp>
23
#include <realm/bplustree.hpp>
24
#include <realm/array_key.hpp>
25

26
#include <numeric> // std::iota
27

28
namespace realm {
29

30
class SetBase : public CollectionBase {
31
public:
32
    using CollectionBase::CollectionBase;
33

34
    virtual ~SetBase() {}
198,207✔
35
    virtual SetBasePtr clone() const = 0;
36
    virtual std::pair<size_t, bool> insert_null() = 0;
37
    virtual std::pair<size_t, bool> erase_null() = 0;
38
    virtual std::pair<size_t, bool> insert_any(Mixed value) = 0;
39
    virtual std::pair<size_t, bool> erase_any(Mixed value) = 0;
40

41
protected:
42
    static constexpr CollectionType s_collection_type = CollectionType::Set;
43

44
    void insert_repl(Replication* repl, size_t index, Mixed value) const;
45
    void erase_repl(Replication* repl, size_t index, Mixed value) const;
46
    void clear_repl(Replication* repl) const;
47
    static std::vector<Mixed> convert_to_mixed_set(const CollectionBase& rhs);
48
};
49

50
template <class T>
51
class Set final : public CollectionBaseImpl<SetBase> {
52
public:
53
    using Base = CollectionBaseImpl<SetBase>;
54
    using value_type = T;
55
    using iterator = CollectionIterator<Set<T>>;
56

57
    Set() = default;
×
58
    Set(const Obj& owner, ColKey col_key)
59
        : Set<T>(col_key)
60
    {
28,566✔
61
        this->set_owner(owner, col_key);
28,566✔
62
    }
28,566✔
63

64
    Set(ColKey col_key)
65
        : Base(col_key)
66
    {
148,101✔
67
        if (!(col_key.is_set() || col_key.get_type() == col_type_Mixed)) {
148,101!
NEW
68
            throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a set");
×
NEW
69
        }
×
70

73,794✔
71
        check_column_type<value_type>(m_col_key);
148,101✔
72
    }
148,101✔
73
    Set(CollectionParent& parent, CollectionParent::Index index)
74
        : Base(parent, index)
75
    {
30✔
76
    }
30✔
77
    Set(const Set& other);
78
    Set(Set&& other) noexcept;
79
    Set& operator=(const Set& other);
80
    Set& operator=(Set&& other) noexcept;
81

82
    SetBasePtr clone() const final
83
    {
×
84
        return std::make_unique<Set<T>>(*this);
×
85
    }
×
86

87
    T get(size_t ndx) const
88
    {
331,524✔
89
        const auto current_size = size();
331,524✔
90
        CollectionBase::validate_index("get()", ndx, current_size);
331,524✔
91
        return m_tree->get(ndx);
331,524✔
92
    }
331,524✔
93

94
    iterator begin() const noexcept
95
    {
155,871✔
96
        return iterator{this, 0};
155,871✔
97
    }
155,871✔
98

99
    iterator end() const noexcept
100
    {
306,489✔
101
        return iterator{this, size()};
306,489✔
102
    }
306,489✔
103

104
    size_t find_first(const T& value) const
105
    {
2,238✔
106
        return find(value);
2,238✔
107
    }
2,238✔
108

109
    template <class Func>
110
    void find_all(T value, Func&& func) const
111
    {
6✔
112
        size_t found = find(value);
6✔
113
        if (found != not_found) {
6✔
114
            func(found);
6✔
115
        }
6✔
116
    }
6✔
117

118
    bool is_subset_of(const CollectionBase&) const;
119
    bool is_strict_subset_of(const CollectionBase& rhs) const;
120
    bool is_superset_of(const CollectionBase& rhs) const;
121
    bool is_strict_superset_of(const CollectionBase& rhs) const;
122
    bool intersects(const CollectionBase& rhs) const;
123
    bool set_equals(const CollectionBase& rhs) const;
124
    void assign_union(const CollectionBase&);
125
    void assign_intersection(const CollectionBase&);
126
    void assign_difference(const CollectionBase&);
127
    void assign_symmetric_difference(const CollectionBase&);
128

129
    /// Insert a value into the set if it does not already exist, returning the index of the inserted value,
130
    /// or the index of the already-existing value.
131
    std::pair<size_t, bool> insert(T value);
132

133
    /// Find the index of a value in the set, or `size_t(-1)` if it is not in the set.
134
    size_t find(T value) const;
135

136
    /// Erase an element from the set, returning true if the set contained the element.
137
    std::pair<size_t, bool> erase(T value);
138

139
    // Overriding members of CollectionBase:
140
    size_t size() const final;
141
    bool is_null(size_t ndx) const final;
142
    Mixed get_any(size_t ndx) const final
143
    {
35,148✔
144
        return get(ndx);
35,148✔
145
    }
35,148✔
146
    void clear() final;
147
    util::Optional<Mixed> min(size_t* return_ndx = nullptr) const final;
148
    util::Optional<Mixed> max(size_t* return_ndx = nullptr) const final;
149
    util::Optional<Mixed> sum(size_t* return_cnt = nullptr) const final;
150
    util::Optional<Mixed> avg(size_t* return_cnt = nullptr) const final;
151
    CollectionBasePtr clone_collection() const final
152
    {
×
153
        return std::make_unique<Set<T>>(*this);
×
154
    }
×
155
    void sort(std::vector<size_t>& indices, bool ascending = true) const final;
156
    void distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order = util::none) const final;
157

158
    // Overriding members of SetBase:
159
    size_t find_any(Mixed) const final;
160
    std::pair<size_t, bool> insert_null() final;
161
    std::pair<size_t, bool> erase_null() final;
162
    std::pair<size_t, bool> insert_any(Mixed value) final;
163
    std::pair<size_t, bool> erase_any(Mixed value) final;
164

165
    const BPlusTree<T>& get_tree() const
166
    {
2,094✔
167
        return *m_tree;
2,094✔
168
    }
2,094✔
169

170
    UpdateStatus update_if_needed_with_status() const final
171
    {
827,205✔
172
        auto status = Base::get_update_status();
827,205✔
173
        switch (status) {
827,205✔
174
            case UpdateStatus::Detached: {
✔
175
                m_tree.reset();
×
176
                return UpdateStatus::Detached;
×
177
            }
×
178
            case UpdateStatus::NoChange:
707,946✔
179
                if (m_tree && m_tree->is_attached()) {
707,946✔
180
                    return UpdateStatus::NoChange;
688,167✔
181
                }
688,167✔
182
                // The tree has not been initialized yet for this accessor, so
9,837✔
183
                // perform lazy initialization by treating it as an update.
9,837✔
184
                [[fallthrough]];
19,779✔
185
            case UpdateStatus::Updated: {
139,038✔
186
                bool attached = init_from_parent(false);
139,038✔
187
                Base::update_content_version();
139,038✔
188
                return attached ? UpdateStatus::Updated : UpdateStatus::Detached;
114,960✔
189
            }
×
190
        }
×
191
        REALM_UNREACHABLE();
×
192
    }
×
193

194
    void ensure_created()
195
    {
90,909✔
196
        if (Base::should_update() || !(m_tree && m_tree->is_attached())) {
90,909✔
197
            // When allow_create is true, init_from_parent will always succeed
19,719✔
198
            // In case of errors, an exception is thrown.
19,719✔
199
            constexpr bool allow_create = true;
39,507✔
200
            init_from_parent(allow_create); // Throws
39,507✔
201
            Base::update_content_version();
39,507✔
202
        }
39,507✔
203
    }
90,909✔
204

205
    void migrate();
206
    void migration_resort();
207

208
private:
209
    // Friend because it needs access to `m_tree` in the implementation of
210
    // `ObjCollectionBase::get_mutable_tree()`.
211
    friend class LnkSet;
212

213
    // BPlusTree must be wrapped in an `std::unique_ptr` because it is not
214
    // default-constructible, due to its `Allocator&` member.
215
    mutable std::unique_ptr<BPlusTree<T>> m_tree;
216

217
    using Base::bump_content_version;
218
    using Base::get_alloc;
219
    using Base::m_col_key;
220
    using Base::m_nullable;
221

222
    bool init_from_parent(bool allow_create) const
223
    {
178,545✔
224
        if (!m_tree) {
178,545✔
225
            m_tree.reset(new BPlusTree<T>(get_alloc()));
136,308✔
226
            const ArrayParent* parent = this;
136,308✔
227
            m_tree->set_parent(const_cast<ArrayParent*>(parent), 0);
136,308✔
228
        }
136,308✔
229
        try {
178,545✔
230
            auto ref = Base::get_collection_ref();
178,545✔
231
            if (ref) {
178,545✔
232
                m_tree->init_from_ref(ref);
107,079✔
233
            }
107,079✔
234
            else {
71,466✔
235
                if (m_tree->init_from_parent()) {
71,466✔
236
                    // All is well
NEW
237
                    return true;
×
NEW
238
                }
×
239
                if (!allow_create) {
71,466✔
240
                    return false;
47,724✔
241
                }
47,724✔
242
                // The ref in the column was NULL, create the tree in place.
11,835✔
243
                m_tree->create();
23,742✔
244
                REALM_ASSERT(m_tree->is_attached());
23,742✔
245
            }
23,742✔
246
        }
178,545✔
247
        catch (...) {
88,935✔
248
            m_tree->detach();
6✔
249
            throw;
6✔
250
        }
6✔
251
        return true;
130,815✔
252
    }
130,815✔
253

254
    /// Update the accessor and return true if it is attached after the update.
255
    inline bool update() const
256
    {
748,161✔
257
        return update_if_needed_with_status() != UpdateStatus::Detached;
748,161✔
258
    }
748,161✔
259

260
    // `do_` methods here perform the action after preconditions have been
261
    // checked (bounds check, writability, etc.).
262
    void do_insert(size_t ndx, T value);
263
    void do_erase(size_t ndx);
264
    void do_clear();
265
    void do_resort(size_t from, size_t to);
266

267
    iterator find_impl(const T& value) const;
268

269
    template <class It1, class It2>
270
    bool is_subset_of(It1, It2) const;
271

272
    template <class It1, class It2>
273
    bool is_superset_of(It1, It2) const;
274

275
    template <class It1, class It2>
276
    bool intersects(It1, It2) const;
277

278
    template <class It1, class It2>
279
    void assign_union(It1, It2);
280

281
    template <class It1, class It2>
282
    void assign_intersection(It1, It2);
283

284
    template <class It1, class It2>
285
    void assign_difference(It1, It2);
286

287
    template <class It1, class It2>
288
    void assign_symmetric_difference(It1, It2);
289

290
    static std::vector<T> convert_to_set(const CollectionBase& rhs, bool nullable);
291
};
292

293
class LnkSet final : public ObjCollectionBase<SetBase> {
294
public:
295
    using Base = ObjCollectionBase<SetBase>;
296
    using value_type = ObjKey;
297
    using iterator = CollectionIterator<LnkSet>;
298

299
    LnkSet() = default;
×
300
    LnkSet(const Obj& owner, ColKey col_key)
301
        : m_set(owner, col_key)
302
    {
7,500✔
303
    }
7,500✔
304
    LnkSet(ColKey col_key)
305
        : m_set(col_key)
306
    {
32,916✔
307
    }
32,916✔
308

309

310
    LnkSet(const LnkSet&) = default;
4,308✔
311
    LnkSet(LnkSet&&) = default;
312
    LnkSet& operator=(const LnkSet&) = default;
313
    LnkSet& operator=(LnkSet&&) = default;
2✔
314
    bool operator==(const LnkSet& other) const;
315
    bool operator!=(const LnkSet& other) const;
316

317
    ObjKey get(size_t ndx) const;
318
    size_t find(ObjKey) const;
319
    size_t find_first(ObjKey) const;
320
    std::pair<size_t, bool> insert(ObjKey);
321
    std::pair<size_t, bool> erase(ObjKey);
322

323
    bool is_subset_of(const CollectionBase&) const;
324
    bool is_strict_subset_of(const CollectionBase& rhs) const;
325
    bool is_superset_of(const CollectionBase& rhs) const;
326
    bool is_strict_superset_of(const CollectionBase& rhs) const;
327
    bool intersects(const CollectionBase& rhs) const;
328
    bool set_equals(const CollectionBase& rhs) const;
329
    void assign_union(const CollectionBase&);
330
    void assign_intersection(const CollectionBase&);
331
    void assign_difference(const CollectionBase&);
332
    void assign_symmetric_difference(const CollectionBase&);
333

334
    // Overriding members of CollectionBase:
335
    using CollectionBase::get_owner_key;
336
    CollectionBasePtr clone_collection() const override
337
    {
×
338
        return clone_linkset();
×
339
    }
×
340
    size_t size() const final;
341
    bool is_null(size_t ndx) const final;
342
    Mixed get_any(size_t ndx) const final;
343
    void clear() final;
344
    util::Optional<Mixed> min(size_t* return_ndx = nullptr) const final;
345
    util::Optional<Mixed> max(size_t* return_ndx = nullptr) const final;
346
    util::Optional<Mixed> sum(size_t* return_cnt = nullptr) const final;
347
    util::Optional<Mixed> avg(size_t* return_cnt = nullptr) const final;
348
    void sort(std::vector<size_t>& indices, bool ascending = true) const final;
349
    void distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order = util::none) const final;
350
    const Obj& get_obj() const noexcept final;
351
    bool is_attached() const noexcept final;
352
    bool has_changed() const noexcept final;
353
    ColKey get_col_key() const noexcept final;
354
    CollectionType get_collection_type() const noexcept override
355
    {
72✔
356
        return CollectionType::Set;
72✔
357
    }
72✔
358

359
    FullPath get_path() const noexcept final
NEW
360
    {
×
NEW
361
        return m_set.get_path();
×
NEW
362
    }
×
363

364
    Path get_short_path() const noexcept final
NEW
365
    {
×
NEW
366
        return m_set.get_short_path();
×
NEW
367
    }
×
368

369
    StablePath get_stable_path() const noexcept final
370
    {
810✔
371
        return m_set.get_stable_path();
810✔
372
    }
810✔
373

374
    // Overriding members of SetBase:
375
    SetBasePtr clone() const override
376
    {
×
377
        return clone_linkset();
×
378
    }
×
379
    // Overriding members of ObjList:
380
    LinkCollectionPtr clone_obj_list() const final
381
    {
4,302✔
382
        return clone_linkset();
4,302✔
383
    }
4,302✔
384
    size_t find_any(Mixed) const final;
385
    std::pair<size_t, bool> insert_null() final;
386
    std::pair<size_t, bool> erase_null() final;
387
    std::pair<size_t, bool> insert_any(Mixed value) final;
388
    std::pair<size_t, bool> erase_any(Mixed value) final;
389

390
    // Overriding members of ObjList:
391
    Obj get_object(size_t ndx) const final;
392
    ObjKey get_key(size_t ndx) const final;
393

394
    // LnkSet interface:
395

396
    std::unique_ptr<LnkSet> clone_linkset() const
397
    {
4,302✔
398
        // FIXME: The copy constructor requires this.
2,151✔
399
        update_if_needed();
4,302✔
400
        return std::make_unique<LnkSet>(*this);
4,302✔
401
    }
4,302✔
402

403
    template <class Func>
404
    void find_all(ObjKey value, Func&& func) const
405
    {
2✔
406
        if (value.is_unresolved()) {
2✔
407
            return;
×
408
        }
×
409

1✔
410
        m_set.find_all(value, [&](size_t ndx) {
2✔
411
            func(real2virtual(ndx));
2✔
412
        });
2✔
413
    }
2✔
414

415
    TableView get_sorted_view(SortDescriptor order) const;
416
    TableView get_sorted_view(ColKey column_key, bool ascending = true);
417
    void remove_target_row(size_t link_ndx);
418
    void remove_all_target_rows();
419

420
    iterator begin() const noexcept
421
    {
78✔
422
        return iterator{this, 0};
78✔
423
    }
78✔
424

425
    iterator end() const noexcept
426
    {
78✔
427
        return iterator{this, size()};
78✔
428
    }
78✔
429

430
    void set_owner(const Obj& obj, ColKey ck) override
431
    {
32,916✔
432
        m_set.set_owner(obj, ck);
32,916✔
433
    }
32,916✔
434

435
    void set_owner(std::shared_ptr<CollectionParent> parent, CollectionParent::Index index) override
NEW
436
    {
×
NEW
437
        m_set.set_owner(std::move(parent), index);
×
NEW
438
    }
×
439

440
    void to_json(std::ostream&, size_t, JSONOutputMode, util::FunctionRef<void(const Mixed&)>) const override;
441

442
private:
443
    Set<ObjKey> m_set;
444

445
    // Overriding members of ObjCollectionBase:
446
    UpdateStatus do_update_if_needed() const final
447
    {
76,584✔
448
        return m_set.update_if_needed_with_status();
76,584✔
449
    }
76,584✔
450

451
    BPlusTree<ObjKey>* get_mutable_tree() const final
452
    {
49,872✔
453
        return m_set.m_tree.get();
49,872✔
454
    }
49,872✔
455
};
456

457
template <>
458
void Set<ObjKey>::do_insert(size_t, ObjKey);
459
template <>
460
void Set<ObjKey>::do_erase(size_t);
461
template <>
462
void Set<ObjKey>::do_clear();
463

464
template <>
465
void Set<ObjLink>::do_insert(size_t, ObjLink);
466
template <>
467
void Set<ObjLink>::do_erase(size_t);
468

469
template <>
470
void Set<Mixed>::do_insert(size_t, Mixed);
471
template <>
472
void Set<Mixed>::do_erase(size_t);
473
template <>
474
void Set<Mixed>::do_clear();
475
template <>
476
void Set<Mixed>::migrate();
477
template <>
478
void Set<Mixed>::migration_resort();
479
template <>
480
void Set<StringData>::migration_resort();
481
template <>
482
void Set<BinaryData>::migration_resort();
483

484
/// Compare set elements.
485
///
486
/// We cannot use `std::less<>` directly, because the ordering of set elements
487
/// impacts the file format. For primitive types this is trivial (and can indeed
488
/// be just `std::less<>`), but for example `Mixed` has specialized comparison
489
/// that defines equality of numeric types.
490
template <class T>
491
struct SetElementLessThan {
492
    bool operator()(const T& a, const T& b) const noexcept
493
    {
152,376✔
494
        // CAUTION: This routine is technically part of the file format, because
76,113✔
495
        // it determines the storage order of Set elements.
76,113✔
496
        return a < b;
152,376✔
497
    }
152,376✔
498
};
499

500
template <class T>
501
struct SetElementEquals {
502
    bool operator()(const T& a, const T& b) const noexcept
503
    {
60,735✔
504
        // CAUTION: This routine is technically part of the file format, because
30,366✔
505
        // it determines the storage order of Set elements.
30,366✔
506
        return a == b;
60,735✔
507
    }
60,735✔
508
};
509

510
template <>
511
struct SetElementLessThan<Mixed> {
512
    bool operator()(const Mixed& a, const Mixed& b) const noexcept
513
    {
38,601✔
514
        // CAUTION: This routine is technically part of the file format, because
19,653✔
515
        // it determines the storage order of Set elements.
19,653✔
516

19,653✔
517
        // These are the rules for comparison of Mixed types in a Set<Mixed>:
19,653✔
518
        // - If both values are null they are equal
19,653✔
519
        // - If only one value is null, that value is lesser than the other
19,653✔
520
        // - All numeric types are compared as the corresponding real numbers
19,653✔
521
        //   would compare. So integer 3 equals double 3.
19,653✔
522
        // - String and binary types are compared using lexicographical comparison.
19,653✔
523
        // - All other types are compared using the comparison operators defined
19,653✔
524
        //   for the types.
19,653✔
525
        // - If two values have different types, the rank of the types are compared.
19,653✔
526
        //   the rank is as follows:
19,653✔
527
        //       boolean
19,653✔
528
        //       numeric
19,653✔
529
        //       string
19,653✔
530
        //       binary
19,653✔
531
        //       Timestamp
19,653✔
532
        //       ObjectId
19,653✔
533
        //       UUID
19,653✔
534
        //       TypedLink
19,653✔
535
        //       Link
19,653✔
536
        //
19,653✔
537
        // The current Mixed::compare function implements these rules except when comparing
19,653✔
538
        // string and binary. If that function is changed we should either implement the rules
19,653✔
539
        // here or upgrade all Set<Mixed> columns.
19,653✔
540
        if (a.is_type(type_String) && b.is_type(type_Binary)) {
38,601✔
541
            return true;
1,080✔
542
        }
1,080✔
543
        if (a.is_type(type_Binary) && b.is_type(type_String)) {
37,521✔
544
            return false;
216✔
545
        }
216✔
546
        return a.compare(b) < 0;
37,305✔
547
    }
37,305✔
548
};
549

550
template <>
551
struct SetElementEquals<Mixed> {
552
    bool operator()(const Mixed& a, const Mixed& b) const noexcept
553
    {
10,647✔
554
        // CAUTION: This routine is technically part of the file format, because
5,325✔
555
        // it determines the storage order of Set elements.
5,325✔
556

5,325✔
557
        // See comments above
5,325✔
558

5,325✔
559
        if (a.is_type(type_String) && b.is_type(type_Binary)) {
10,647✔
560
            return false;
×
561
        }
×
562
        if (a.is_type(type_Binary) && b.is_type(type_String)) {
10,647✔
563
            return false;
36✔
564
        }
36✔
565
        return a.compare(b) == 0;
10,611✔
566
    }
10,611✔
567
};
568

569
template <class T>
570
inline Set<T>::Set(const Set& other)
571
    : Base(static_cast<const Base&>(other))
572
{
5,352✔
573
    // Reset the content version so we can rely on init_from_parent() being
2,676✔
574
    // called lazily when the accessor is used.
2,676✔
575
    Base::reset_content_version();
5,352✔
576
}
5,352✔
577

578
template <class T>
579
inline Set<T>::Set(Set&& other) noexcept
580
    : Base(static_cast<Base&&>(other))
581
    , m_tree(std::exchange(other.m_tree, nullptr))
582
{
583
    if (m_tree) {
584
        m_tree->set_parent(this, 0);
585
    }
586
}
587

588
template <class T>
589
inline Set<T>& Set<T>::operator=(const Set& other)
590
{
591
    Base::operator=(static_cast<const Base&>(other));
592

593
    if (this != &other) {
594
        // Just reset the pointer and rely on init_from_parent() being called
595
        // when the accessor is actually used.
596
        m_tree.reset();
597
        Base::reset_content_version();
598
    }
599

600
    return *this;
601
}
602

603
template <class T>
604
inline Set<T>& Set<T>::operator=(Set&& other) noexcept
605
{
2✔
606
    Base::operator=(static_cast<Base&&>(other));
2✔
607

1✔
608
    if (this != &other) {
2✔
609
        m_tree = std::exchange(other.m_tree, nullptr);
2✔
610
        if (m_tree) {
2✔
611
            m_tree->set_parent(this, 0);
×
612
            // Note: We do not need to call reset_content_version(), because we
613
            // took both `m_tree` and `m_content_version` from `other`.
614
        }
×
615
    }
2✔
616

1✔
617
    return *this;
2✔
618
}
2✔
619

620
template <typename U>
621
Set<U> Obj::get_set(ColKey col_key) const
622
{
20,502✔
623
    return Set<U>(*this, col_key);
20,502✔
624
}
20,502✔
625

626
template <typename U>
627
inline SetPtr<U> Obj::get_set_ptr(ColKey col_key) const
628
{
308✔
629
    return std::make_unique<Set<U>>(*this, col_key);
308✔
630
}
308✔
631

632
inline LnkSet Obj::get_linkset(ColKey col_key) const
633
{
7,212✔
634
    return LnkSet{*this, col_key};
7,212✔
635
}
7,212✔
636

637
inline LnkSet Obj::get_linkset(StringData col_name) const
638
{
4✔
639
    return get_linkset(get_column_key(col_name));
4✔
640
}
4✔
641

642
inline LnkSetPtr Obj::get_linkset_ptr(ColKey col_key) const
643
{
288✔
644
    return std::make_unique<LnkSet>(*this, col_key);
288✔
645
}
288✔
646

647
template <class T>
648
size_t Set<T>::find(T value) const
649
{
45,552✔
650
    auto it = find_impl(value);
45,552✔
651
    if (it != end() && SetElementEquals<T>{}(*it, value)) {
45,552✔
652
        return it.index();
20,718✔
653
    }
20,718✔
654
    return npos;
24,834✔
655
}
24,834✔
656

657
template <class T>
658
size_t Set<T>::find_any(Mixed value) const
659
{
11,070✔
660
    if constexpr (std::is_same_v<T, Mixed>) {
11,070✔
661
        return find(value);
10,170✔
662
    }
10,170✔
663
    else {
10,170✔
664
        if (value.is_null()) {
10,170✔
665
            if (!m_nullable) {
258!
666
                return not_found;
×
667
            }
×
668
            return find(BPlusTree<T>::default_value(true));
258✔
669
        }
258✔
670
        else {
9,912✔
671
            return find(value.get<typename util::RemoveOptional<T>::type>());
9,912✔
672
        }
9,912✔
673
    }
10,170✔
674
}
11,070✔
675

676
template <class T>
677
REALM_NOINLINE auto Set<T>::find_impl(const T& value) const -> iterator
678
{
150,339✔
679
    auto b = this->begin();
150,339✔
680
    auto e = this->end(); // Note: This ends up calling `update_if_needed()`.
150,339✔
681
    return std::lower_bound(b, e, value, SetElementLessThan<T>{});
150,339✔
682
}
150,339✔
683

684
template <class T>
685
std::pair<size_t, bool> Set<T>::insert(T value)
686
{
90,909✔
687
    ensure_created();
90,909✔
688

45,342✔
689
    if (value_is_null(value) && !m_nullable)
90,909!
690
        throw InvalidArgument(ErrorCodes::PropertyNotNullable,
12✔
691
                              util::format("Set: %1", CollectionBase::get_property_name()));
12✔
692

45,336✔
693
    auto it = find_impl(value);
90,897✔
694

45,336✔
695
    if (it != this->end() && SetElementEquals<T>{}(*it, value)) {
90,897✔
696
        return {it.index(), false};
26,286✔
697
    }
26,286✔
698

32,190✔
699
    if (Replication* repl = Base::get_replication()) {
64,611✔
700
        // FIXME: We should emit an instruction regardless of element presence for the purposes of conflict
30,360✔
701
        // resolution in synchronized databases. The reason is that the new insertion may come at a later time
30,360✔
702
        // than an interleaving erase instruction, so emitting the instruction ensures that last "write" wins.
30,360✔
703
        this->insert_repl(repl, it.index(), value);
60,951✔
704
    }
60,951✔
705

32,190✔
706
    do_insert(it.index(), value);
64,611✔
707
    bump_content_version();
64,611✔
708
    return {it.index(), true};
64,611✔
709
}
64,611✔
710

711
template <class T>
712
std::pair<size_t, bool> Set<T>::insert_any(Mixed value)
713
{
14,409✔
714
    if constexpr (std::is_same_v<T, Mixed>) {
14,409✔
715
        return insert(value);
13,533✔
716
    }
13,533✔
717
    else {
13,533✔
718
        if (value.is_null()) {
13,533✔
719
            return insert_null();
132✔
720
        }
132✔
721
        else {
13,401✔
722
            return insert(value.get<typename util::RemoveOptional<T>::type>());
13,401✔
723
        }
13,401✔
724
    }
13,533✔
725
}
14,409✔
726

727
template <class T>
728
std::pair<size_t, bool> Set<T>::erase(T value)
729
{
13,896✔
730
    auto it = find_impl(value); // Note: This ends up calling `update_if_needed()`.
13,896✔
731

6,951✔
732
    if (it == end() || !SetElementEquals<T>{}(*it, value)) {
13,896✔
733
        return {npos, false};
4,086✔
734
    }
4,086✔
735

4,908✔
736
    if (Replication* repl = Base::get_replication()) {
9,810✔
737
        this->erase_repl(repl, it.index(), value);
9,456✔
738
    }
9,456✔
739
    do_erase(it.index());
9,810✔
740
    bump_content_version();
9,810✔
741
    return {it.index(), true};
9,810✔
742
}
9,810✔
743

744
template <class T>
745
std::pair<size_t, bool> Set<T>::erase_any(Mixed value)
746
{
6,174✔
747
    if constexpr (std::is_same_v<T, Mixed>) {
6,174✔
748
        return erase(value);
5,370✔
749
    }
5,370✔
750
    else {
5,370✔
751
        if (value.is_null()) {
5,370✔
752
            return erase_null();
240✔
753
        }
240✔
754
        else {
5,130✔
755
            return erase(value.get<typename util::RemoveOptional<T>::type>());
5,130✔
756
        }
5,130✔
757
    }
5,370✔
758
}
6,174✔
759

760
template <class T>
761
inline std::pair<size_t, bool> Set<T>::insert_null()
762
{
192✔
763
    return insert(BPlusTree<T>::default_value(this->m_nullable));
192✔
764
}
192✔
765

766
template <class T>
767
std::pair<size_t, bool> Set<T>::erase_null()
768
{
306✔
769
    return erase(BPlusTree<T>::default_value(this->m_nullable));
306✔
770
}
306✔
771

772
template <class T>
773
REALM_NOINLINE size_t Set<T>::size() const
774
{
744,387✔
775
    return update() ? m_tree->size() : 0;
724,596✔
776
}
744,387✔
777

778
template <class T>
779
inline bool Set<T>::is_null(size_t ndx) const
780
{
30✔
781
    return m_nullable && value_is_null(get(ndx));
30!
782
}
30✔
783

784
template <class T>
785
inline void Set<T>::clear()
786
{
2,586✔
787
    if (size() > 0) {
2,586✔
788
        if (Replication* repl = Base::get_replication()) {
2,574✔
789
            this->clear_repl(repl);
2,412✔
790
        }
2,412✔
791
        do_clear();
2,574✔
792
        bump_content_version();
2,574✔
793
    }
2,574✔
794
}
2,586✔
795

796
template <class T>
797
inline util::Optional<Mixed> Set<T>::min(size_t* return_ndx) const
798
{
840✔
799
    if (update()) {
840✔
800
        return MinHelper<T>::eval(*m_tree, return_ndx);
828✔
801
    }
828✔
802
    return MinHelper<T>::not_found(return_ndx);
12✔
803
}
12✔
804

805
template <class T>
806
inline util::Optional<Mixed> Set<T>::max(size_t* return_ndx) const
807
{
840✔
808
    if (update()) {
840✔
809
        return MaxHelper<T>::eval(*m_tree, return_ndx);
828✔
810
    }
828✔
811
    return MaxHelper<T>::not_found(return_ndx);
12✔
812
}
12✔
813

814
template <class T>
815
inline util::Optional<Mixed> Set<T>::sum(size_t* return_cnt) const
816
{
792✔
817
    if (update()) {
792!
818
        return SumHelper<T>::eval(*m_tree, return_cnt);
780✔
819
    }
780✔
820
    return SumHelper<T>::not_found(return_cnt);
12✔
821
}
12✔
822

823
template <class T>
824
inline util::Optional<Mixed> Set<T>::avg(size_t* return_cnt) const
825
{
792✔
826
    if (update()) {
792✔
827
        return AverageHelper<T>::eval(*m_tree, return_cnt);
780✔
828
    }
780✔
829
    return AverageHelper<T>::not_found(return_cnt);
12✔
830
}
12✔
831

832
void set_sorted_indices(size_t sz, std::vector<size_t>& indices, bool ascending);
833

834
template <class T>
835
inline void Set<T>::sort(std::vector<size_t>& indices, bool ascending) const
836
{
30,096✔
837
    auto sz = size();
30,096✔
838
    set_sorted_indices(sz, indices, ascending);
30,096✔
839
}
30,096✔
840

841
template <>
842
void Set<Mixed>::sort(std::vector<size_t>& indices, bool ascending) const;
843

844
template <class T>
845
inline void Set<T>::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
846
{
×
847
    auto ascending = !sort_order || *sort_order;
×
848
    sort(indices, ascending);
×
849
}
×
850

851
template <class T>
852
inline void Set<T>::do_insert(size_t ndx, T value)
853
{
38,907✔
854
    m_tree->insert(ndx, value);
38,907✔
855
}
38,907✔
856

857
template <class T>
858
inline void Set<T>::do_erase(size_t ndx)
859
{
6,468✔
860
    m_tree->erase(ndx);
6,468✔
861
}
6,468✔
862

863
template <class T>
864
inline void Set<T>::do_clear()
865
{
1,398✔
866
    m_tree->clear();
1,398✔
867
}
1,398✔
868

869
template <class T>
870
std::vector<T> Set<T>::convert_to_set(const CollectionBase& rhs, bool nullable)
871
{
380✔
872
    if constexpr (std::is_same_v<T, Mixed>) {
380✔
873
        return SetBase::convert_to_mixed_set(rhs);
40✔
874
    }
40✔
875

16✔
876
    std::vector<Mixed> mixed = SetBase::convert_to_mixed_set(rhs);
32✔
877
    std::vector<T> ret;
32✔
878
    ret.reserve(mixed.size());
32✔
879
    for (auto&& val : mixed) {
1,568!
880
        if constexpr (std::is_same_v<T, ObjKey>) {
1,568✔
881
            static_cast<void>(nullable);
1,208✔
882
            if (val.is_type(type_Link, type_TypedLink)) {
874✔
883
                ret.push_back(val.get<ObjKey>());
540✔
884
            }
540✔
885
        }
540✔
886
        else {
1,208✔
887
            if (val.is_type(ColumnTypeTraits<T>::id)) {
1,208!
888
                ret.push_back(val.get<T>());
1,140✔
889
            }
1,140✔
890
            else if (val.is_null() && nullable) {
248!
891
                ret.push_back(BPlusTree<T>::default_value(true));
248✔
892
            }
248✔
893
        }
1,208✔
894
    }
1,568✔
895
    return ret;
32✔
896
}
32✔
897

898
template <class T>
899
bool Set<T>::is_subset_of(const CollectionBase& rhs) const
900
{
124✔
901
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
124✔
902
        return is_subset_of(other_set->begin(), other_set->end());
16✔
903
    }
16✔
904
    auto other_set = convert_to_set(rhs, m_nullable);
108✔
905
    return is_subset_of(other_set.begin(), other_set.end());
108✔
906
}
108✔
907

908
template <class T>
909
template <class It1, class It2>
910
bool Set<T>::is_subset_of(It1 first, It2 last) const
911
{
228✔
912
    return std::includes(first, last, begin(), end(), SetElementLessThan<T>{});
228✔
913
}
228✔
914

915
template <class T>
916
bool Set<T>::is_strict_subset_of(const CollectionBase& rhs) const
917
{
16✔
918
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
16!
919
        return size() != rhs.size() && is_subset_of(other_set->begin(), other_set->end());
4!
920
    }
4✔
921
    auto other_set = convert_to_set(rhs, m_nullable);
12✔
922
    return size() != other_set.size() && is_subset_of(other_set.begin(), other_set.end());
12!
923
}
12✔
924

925
template <class T>
926
bool Set<T>::is_superset_of(const CollectionBase& rhs) const
927
{
24✔
928
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
24!
929
        return is_superset_of(other_set->begin(), other_set->end());
16✔
930
    }
16✔
931
    auto other_set = convert_to_set(rhs, m_nullable);
8✔
932
    return is_superset_of(other_set.begin(), other_set.end());
8✔
933
}
8✔
934

935
template <class T>
936
template <class It1, class It2>
937
bool Set<T>::is_superset_of(It1 first, It2 last) const
938
{
32✔
939
    return std::includes(begin(), end(), first, last, SetElementLessThan<T>{});
32✔
940
}
32✔
941

942
template <class T>
943
bool Set<T>::is_strict_superset_of(const CollectionBase& rhs) const
944
{
16✔
945
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
16!
946
        return size() != rhs.size() && is_superset_of(other_set->begin(), other_set->end());
4!
947
    }
4✔
948
    auto other_set = convert_to_set(rhs, m_nullable);
12✔
949
    return size() != other_set.size() && is_superset_of(other_set.begin(), other_set.end());
12!
950
}
12✔
951

952
template <class T>
953
bool Set<T>::intersects(const CollectionBase& rhs) const
954
{
56✔
955
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
56!
956
        return intersects(other_set->begin(), other_set->end());
16✔
957
    }
16✔
958
    auto other_set = convert_to_set(rhs, m_nullable);
40✔
959
    return intersects(other_set.begin(), other_set.end());
40✔
960
}
40✔
961

962
template <class T>
963
template <class It1, class It2>
964
bool Set<T>::intersects(It1 first, It2 last) const
965
{
56✔
966
    SetElementLessThan<T> less;
56✔
967
    auto it = begin();
56✔
968
    while (it != end() && first != last) {
160!
969
        if (less(*it, *first)) {
148!
970
            ++it;
16✔
971
        }
16✔
972
        else if (less(*first, *it)) {
132!
973
            ++first;
88✔
974
        }
88✔
975
        else {
44✔
976
            return true;
44✔
977
        }
44✔
978
    }
148✔
979
    return false;
34✔
980
}
56✔
981

982
template <class T>
983
bool Set<T>::set_equals(const CollectionBase& rhs) const
984
{
108✔
985
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
108!
986
        return size() == rhs.size() && is_subset_of(other_set->begin(), other_set->end());
4!
987
    }
4✔
988
    auto other_set = convert_to_set(rhs, m_nullable);
104✔
989
    return size() == other_set.size() && is_subset_of(other_set.begin(), other_set.end());
104✔
990
}
104✔
991

992
template <class T>
993
inline void Set<T>::assign_union(const CollectionBase& rhs)
994
{
32✔
995
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
32!
996
        return assign_union(other_set->begin(), other_set->end());
16✔
997
    }
16✔
998
    auto other_set = convert_to_set(rhs, m_nullable);
16✔
999
    return assign_union(other_set.begin(), other_set.end());
16✔
1000
}
16✔
1001

1002
template <class T>
1003
template <class It1, class It2>
1004
void Set<T>::assign_union(It1 first, It2 last)
1005
{
32✔
1006
    std::vector<T> the_diff;
32✔
1007
    std::set_difference(first, last, begin(), end(), std::back_inserter(the_diff), SetElementLessThan<T>{});
32✔
1008
    // 'the_diff' now contains all the elements that are in foreign set, but not in 'this'
16✔
1009
    // Now insert those elements
16✔
1010
    for (auto&& value : the_diff) {
64!
1011
        insert(value);
64✔
1012
    }
64✔
1013
}
32✔
1014

1015
template <class T>
1016
inline void Set<T>::assign_intersection(const CollectionBase& rhs)
1017
{
20✔
1018
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
20!
1019
        return assign_intersection(other_set->begin(), other_set->end());
12✔
1020
    }
12✔
1021
    auto other_set = convert_to_set(rhs, m_nullable);
8✔
1022
    return assign_intersection(other_set.begin(), other_set.end());
8✔
1023
}
8✔
1024

1025
template <class T>
1026
template <class It1, class It2>
1027
void Set<T>::assign_intersection(It1 first, It2 last)
1028
{
20✔
1029
    std::vector<T> intersection;
20✔
1030
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection), SetElementLessThan<T>{});
20✔
1031
    clear();
20✔
1032
    // Elements in intersection comes from foreign set, so ok to use here
10✔
1033
    for (auto&& value : intersection) {
40!
1034
        insert(value);
40✔
1035
    }
40✔
1036
}
20✔
1037

1038
template <class T>
1039
inline void Set<T>::assign_difference(const CollectionBase& rhs)
1040
{
16✔
1041
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
16!
1042
        return assign_difference(other_set->begin(), other_set->end());
4✔
1043
    }
4✔
1044
    auto other_set = convert_to_set(rhs, m_nullable);
12✔
1045
    return assign_difference(other_set.begin(), other_set.end());
12✔
1046
}
12✔
1047

1048
template <class T>
1049
template <class It1, class It2>
1050
void Set<T>::assign_difference(It1 first, It2 last)
1051
{
16✔
1052
    std::vector<T> intersection;
16✔
1053
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection), SetElementLessThan<T>{});
16✔
1054
    // 'intersection' now contains all the elements that are in both foreign set and 'this'.
8✔
1055
    // Remove those elements. The elements comes from the foreign set, so ok to refer to.
8✔
1056
    for (auto&& value : intersection) {
44!
1057
        erase(value);
44✔
1058
    }
44✔
1059
}
16✔
1060

1061
template <class T>
1062
inline void Set<T>::assign_symmetric_difference(const CollectionBase& rhs)
1063
{
16✔
1064
    if (auto other_set = dynamic_cast<const Set<T>*>(&rhs)) {
16!
1065
        return assign_symmetric_difference(other_set->begin(), other_set->end());
8✔
1066
    }
8✔
1067
    auto other_set = convert_to_set(rhs, m_nullable);
8✔
1068
    return assign_symmetric_difference(other_set.begin(), other_set.end());
8✔
1069
}
8✔
1070

1071
template <class T>
1072
template <class It1, class It2>
1073
void Set<T>::assign_symmetric_difference(It1 first, It2 last)
1074
{
16✔
1075
    std::vector<T> difference;
16✔
1076
    std::set_difference(first, last, begin(), end(), std::back_inserter(difference), SetElementLessThan<T>{});
16✔
1077
    std::vector<T> intersection;
16✔
1078
    std::set_intersection(first, last, begin(), end(), std::back_inserter(intersection), SetElementLessThan<T>{});
16✔
1079
    // Now remove the common elements and add the differences
8✔
1080
    for (auto&& value : intersection) {
28!
1081
        erase(value);
28✔
1082
    }
28✔
1083
    for (auto&& value : difference) {
24!
1084
        insert(value);
24✔
1085
    }
24✔
1086
}
16✔
1087

1088
inline bool LnkSet::operator==(const LnkSet& other) const
1089
{
×
1090
    return m_set == other.m_set;
×
1091
}
×
1092

1093
inline bool LnkSet::operator!=(const LnkSet& other) const
1094
{
×
1095
    return m_set != other.m_set;
×
1096
}
×
1097

1098
inline ObjKey LnkSet::get(size_t ndx) const
1099
{
10,977✔
1100
    const auto current_size = size();
10,977✔
1101
    if (ndx >= current_size) {
10,977✔
1102
        throw OutOfBounds(util::format("Invalid index into set: %1", CollectionBase::get_property_name()), ndx,
×
1103
                          current_size);
×
1104
    }
×
1105
    return m_set.m_tree->get(virtual2real(ndx));
10,977✔
1106
}
10,977✔
1107

1108
inline size_t LnkSet::find(ObjKey value) const
1109
{
5,100✔
1110
    if (value.is_unresolved()) {
5,100✔
1111
        return not_found;
18✔
1112
    }
18✔
1113

2,541✔
1114
    update_if_needed();
5,082✔
1115

2,541✔
1116
    size_t ndx = m_set.find(value);
5,082✔
1117
    if (ndx == not_found) {
5,082✔
1118
        return not_found;
3,888✔
1119
    }
3,888✔
1120
    return real2virtual(ndx);
1,194✔
1121
}
1,194✔
1122

1123
inline size_t LnkSet::find_first(ObjKey value) const
1124
{
12✔
1125
    return find(value);
12✔
1126
}
12✔
1127

1128
inline size_t LnkSet::size() const
1129
{
31,596✔
1130
    update_if_needed();
31,596✔
1131
    return m_set.size() - num_unresolved();
31,596✔
1132
}
31,596✔
1133

1134
inline std::pair<size_t, bool> LnkSet::insert(ObjKey value)
1135
{
27,120✔
1136
    REALM_ASSERT(!value.is_unresolved());
27,120✔
1137
    update_if_needed();
27,120✔
1138

13,470✔
1139
    auto [ndx, inserted] = m_set.insert(value);
27,120✔
1140
    if (inserted) {
27,120✔
1141
        update_unresolved(UpdateStatus::Updated);
16,398✔
1142
    }
16,398✔
1143
    return {real2virtual(ndx), inserted};
27,120✔
1144
}
27,120✔
1145

1146
inline std::pair<size_t, bool> LnkSet::erase(ObjKey value)
1147
{
794✔
1148
    REALM_ASSERT(!value.is_unresolved());
794✔
1149
    update_if_needed();
794✔
1150

397✔
1151
    auto [ndx, removed] = m_set.erase(value);
794✔
1152
    if (removed) {
794✔
1153
        update_unresolved(UpdateStatus::Updated);
470✔
1154
        ndx = real2virtual(ndx);
470✔
1155
    }
470✔
1156
    return {ndx, removed};
794✔
1157
}
794✔
1158

1159
inline bool LnkSet::is_null(size_t ndx) const
1160
{
×
1161
    update_if_needed();
×
1162
    return m_set.is_null(virtual2real(ndx));
×
1163
}
×
1164

1165
inline Mixed LnkSet::get_any(size_t ndx) const
1166
{
4,218✔
1167
    update_if_needed();
4,218✔
1168
    auto obj_key = m_set.get(virtual2real(ndx));
4,218✔
1169
    return ObjLink{get_target_table()->get_key(), obj_key};
4,218✔
1170
}
4,218✔
1171

1172
inline std::pair<size_t, bool> LnkSet::insert_null()
1173
{
×
1174
    update_if_needed();
×
1175
    auto [ndx, inserted] = m_set.insert_null();
×
1176
    if (inserted) {
×
1177
        update_unresolved(UpdateStatus::Updated);
×
1178
    }
×
1179
    return {real2virtual(ndx), inserted};
×
1180
}
×
1181

1182
inline std::pair<size_t, bool> LnkSet::erase_null()
1183
{
×
1184
    update_if_needed();
×
1185
    auto [ndx, erased] = m_set.erase_null();
×
1186
    if (erased) {
×
1187
        update_unresolved(UpdateStatus::Updated);
×
1188
        ndx = real2virtual(ndx);
×
1189
    }
×
1190
    return {ndx, erased};
×
1191
}
×
1192

1193
inline std::pair<size_t, bool> LnkSet::insert_any(Mixed value)
1194
{
252✔
1195
    update_if_needed();
252✔
1196
    auto [ndx, inserted] = m_set.insert_any(value);
252✔
1197
    if (inserted) {
252✔
1198
        update_unresolved(UpdateStatus::Updated);
228✔
1199
    }
228✔
1200
    return {real2virtual(ndx), inserted};
252✔
1201
}
252✔
1202

1203
inline std::pair<size_t, bool> LnkSet::erase_any(Mixed value)
1204
{
150✔
1205
    auto [ndx, erased] = m_set.erase_any(value);
150✔
1206
    if (erased) {
150✔
1207
        update_unresolved(UpdateStatus::Updated);
150✔
1208
        ndx = real2virtual(ndx);
150✔
1209
    }
150✔
1210
    return {ndx, erased};
150✔
1211
}
150✔
1212

1213
inline void LnkSet::clear()
1214
{
888✔
1215
    // Note: Explicit call to `ensure_writable()` not needed, because we
444✔
1216
    // explicitly call `clear_unresolved()`.
444✔
1217
    m_set.clear();
888✔
1218
    clear_unresolved();
888✔
1219
}
888✔
1220

1221
inline util::Optional<Mixed> LnkSet::min(size_t* return_ndx) const
1222
{
×
1223
    update_if_needed();
×
1224
    size_t found = not_found;
×
1225
    auto value = m_set.min(&found);
×
1226
    if (found != not_found && return_ndx) {
×
1227
        *return_ndx = real2virtual(found);
×
1228
    }
×
1229
    return value;
×
1230
}
×
1231

1232
inline util::Optional<Mixed> LnkSet::max(size_t* return_ndx) const
1233
{
×
1234
    update_if_needed();
×
1235
    size_t found = not_found;
×
1236
    auto value = m_set.max(&found);
×
1237
    if (found != not_found && return_ndx) {
×
1238
        *return_ndx = real2virtual(found);
×
1239
    }
×
1240
    return value;
×
1241
}
×
1242

1243
inline util::Optional<Mixed> LnkSet::sum(size_t* return_cnt) const
1244
{
×
1245
    static_cast<void>(return_cnt);
×
1246
    REALM_TERMINATE("Not implemented");
×
1247
}
×
1248

1249
inline util::Optional<Mixed> LnkSet::avg(size_t* return_cnt) const
1250
{
×
1251
    static_cast<void>(return_cnt);
×
1252
    REALM_TERMINATE("Not implemented");
×
1253
}
×
1254

1255
inline void LnkSet::sort(std::vector<size_t>& indices, bool ascending) const
1256
{
708✔
1257
    update_if_needed();
708✔
1258

354✔
1259
    // Map the input indices to real indices.
354✔
1260
    std::transform(indices.begin(), indices.end(), indices.begin(), [this](size_t ndx) {
354✔
1261
        return virtual2real(ndx);
×
1262
    });
×
1263

354✔
1264
    m_set.sort(indices, ascending);
708✔
1265

354✔
1266
    // Map the output indices to virtual indices.
354✔
1267
    std::transform(indices.begin(), indices.end(), indices.begin(), [this](size_t ndx) {
1,650✔
1268
        return real2virtual(ndx);
1,650✔
1269
    });
1,650✔
1270
}
708✔
1271

1272
inline void LnkSet::distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order) const
1273
{
×
1274
    update_if_needed();
×
1275

1276
    // Map the input indices to real indices.
1277
    std::transform(indices.begin(), indices.end(), indices.begin(), [this](size_t ndx) {
×
1278
        return virtual2real(ndx);
×
1279
    });
×
1280

1281
    m_set.distinct(indices, sort_order);
×
1282

1283
    // Map the output indices to virtual indices.
1284
    std::transform(indices.begin(), indices.end(), indices.begin(), [this](size_t ndx) {
×
1285
        return real2virtual(ndx);
×
1286
    });
×
1287
}
×
1288

1289
inline const Obj& LnkSet::get_obj() const noexcept
1290
{
64,350✔
1291
    return m_set.get_obj();
64,350✔
1292
}
64,350✔
1293

1294
inline bool LnkSet::is_attached() const noexcept
1295
{
53,994✔
1296
    return m_set.is_attached();
53,994✔
1297
}
53,994✔
1298

1299
inline bool LnkSet::has_changed() const noexcept
1300
{
×
1301
    return m_set.has_changed();
×
1302
}
×
1303

1304
inline ColKey LnkSet::get_col_key() const noexcept
1305
{
68,658✔
1306
    return m_set.get_col_key();
68,658✔
1307
}
68,658✔
1308

1309
inline size_t LnkSet::find_any(Mixed value) const
1310
{
78✔
1311
    if (value.is_null())
78✔
1312
        return not_found;
×
1313

39✔
1314
    const auto type = value.get_type();
78✔
1315
    if (type == type_Link) {
78✔
1316
        return find(value.get<ObjKey>());
78✔
1317
    }
78✔
1318
    if (type == type_TypedLink) {
×
1319
        auto link = value.get_link();
×
1320
        if (link.get_table_key() == get_target_table()->get_key()) {
×
1321
            return find(link.get_obj_key());
×
1322
        }
×
1323
    }
×
1324
    return not_found;
×
1325
}
×
1326

1327
inline Obj LnkSet::get_object(size_t ndx) const
1328
{
10,587✔
1329
    ObjKey key = get(ndx);
10,587✔
1330
    return get_target_table()->get_object(key);
10,587✔
1331
}
10,587✔
1332

1333
inline ObjKey LnkSet::get_key(size_t ndx) const
1334
{
324✔
1335
    return get(ndx);
324✔
1336
}
324✔
1337

1338
inline void LnkSet::assign_union(const CollectionBase& rhs)
1339
{
4✔
1340
    m_set.assign_union(rhs);
4✔
1341
    update_unresolved(UpdateStatus::Updated);
4✔
1342
}
4✔
1343

1344
inline void LnkSet::assign_intersection(const CollectionBase& rhs)
1345
{
4✔
1346
    m_set.assign_intersection(rhs);
4✔
1347
    update_unresolved(UpdateStatus::Updated);
4✔
1348
}
4✔
1349

1350
inline void LnkSet::assign_difference(const CollectionBase& rhs)
1351
{
4✔
1352
    m_set.assign_difference(rhs);
4✔
1353
    update_unresolved(UpdateStatus::Updated);
4✔
1354
}
4✔
1355

1356
inline void LnkSet::assign_symmetric_difference(const CollectionBase& rhs)
1357
{
×
1358
    m_set.assign_symmetric_difference(rhs);
×
1359
    update_unresolved(UpdateStatus::Updated);
×
1360
}
×
1361

1362
} // namespace realm
1363

1364
#endif // REALM_SET_HPP
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