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

realm / realm-core / thomas.goyne_232

13 Mar 2024 01:00AM UTC coverage: 91.787% (+0.9%) from 90.924%
thomas.goyne_232

Pull #7402

Evergreen

tgoyne
Add more UpdateIfNeeded tests
Pull Request #7402: Make Obj trivial and add a separate ObjCollectionParent type

94460 of 174600 branches covered (54.1%)

496 of 559 new or added lines in 21 files covered. (88.73%)

848 existing lines in 34 files now uncovered.

242761 of 264484 relevant lines covered (91.79%)

6342666.36 hits per line

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

87.28
/src/realm/collection.hpp
1
#ifndef REALM_COLLECTION_HPP
2
#define REALM_COLLECTION_HPP
3

4
#include <realm/obj.hpp>
5
#include <realm/bplustree.hpp>
6
#include <realm/obj_list.hpp>
7
#include <realm/table.hpp>
8

9
#include <iosfwd>      // std::ostream
10
#include <type_traits> // std::void_t
11
#include <set>
12

13
namespace realm {
14

15
template <class L>
16
struct CollectionIterator;
17

18
// Used in Cluster when removing owning object
19
class DummyParent : public CollectionParent {
20
public:
21
    DummyParent(TableRef t, ref_type ref)
22
        : m_obj(t, MemRef(), ObjKey(), 0)
23
        , m_ref(ref)
24
    {
150✔
25
    }
150✔
26
    FullPath get_path() const noexcept final
27
    {
×
28
        return {};
×
29
    }
×
30
    Path get_short_path() const noexcept final
31
    {
×
32
        return {};
×
33
    }
×
34
    StablePath get_stable_path() const noexcept final
35
    {
×
36
        return {};
×
37
    }
×
38
    ColKey get_col_key() const noexcept final
39
    {
150✔
40
        return {};
150✔
41
    }
150✔
42
    void add_index(Path&, const Index&) const noexcept final {}
×
43
    size_t find_index(const Index&) const noexcept final
44
    {
×
45
        return realm::npos;
×
46
    }
×
47

48
    TableRef get_table() const noexcept final
49
    {
×
50
        return m_obj.get_table();
×
51
    }
×
52
    const Obj& get_object() const noexcept final
53
    {
150✔
54
        return m_obj;
150✔
55
    }
150✔
56
    uint32_t parent_version() const noexcept final
57
    {
174✔
58
        return 0;
174✔
59
    }
174✔
60

61
protected:
62
    Obj m_obj;
63
    ref_type m_ref;
64
    UpdateStatus update_if_needed() const final
65
    {
174✔
66
        return UpdateStatus::Updated;
174✔
67
    }
174✔
68
    ref_type get_collection_ref(Index, CollectionType) const final
69
    {
174✔
70
        return m_ref;
174✔
71
    }
174✔
72
    void set_collection_ref(Index, ref_type, CollectionType) {}
×
73
};
74

75
class Collection {
76
public:
77
    virtual ~Collection();
78
    /// The size of the collection.
79
    virtual size_t size() const = 0;
80
    /// Get element at @a ndx as a `Mixed`.
81
    virtual Mixed get_any(size_t ndx) const = 0;
82
    /// True if `size()` returns 0.
83
    bool is_empty() const
84
    {
7,692✔
85
        return size() == 0;
7,692✔
86
    }
7,692✔
87
    virtual void to_json(std::ostream&, JSONOutputMode, util::FunctionRef<void(const Mixed&)>) const {}
×
88
    /// Get collection type (set, list, dictionary)
89
    virtual CollectionType get_collection_type() const noexcept = 0;
90

91
    virtual void insert_collection(const PathElement&, CollectionType)
92
    {
×
93
        throw IllegalOperation("insert_collection is not legal on this collection type");
×
94
    }
×
95
    virtual void set_collection(const PathElement&, CollectionType)
96
    {
×
97
        throw IllegalOperation("set_collection is not legal on this collection type");
×
98
    }
×
99
    // Returns the path to the collection. Uniquely identifies the collection within the Group.
100
    virtual FullPath get_path() const = 0;
101
    // Returns the path from the owning object. Starting with the column key. Identifies
102
    // the collection within the object
103
    virtual Path get_short_path() const = 0;
104
    // Return a path based on keys instead of indices
105
    virtual StablePath get_stable_path() const = 0;
106

107
    struct QueryCtrlBlock {
108
        Path path;
109
        std::vector<std::vector<Mixed>> matches;
110
        bool path_only_unary_keys = false; // Not from list
111
        Allocator* alloc = nullptr;
112
        Group* group = nullptr;
113
    };
114
    static void get_any(QueryCtrlBlock&, Mixed, size_t);
115
};
116

117
using CollectionPtr = std::shared_ptr<Collection>;
118

119
/// Base class for all collection accessors.
120
///
121
/// Collections are bound to particular properties of an object. In a
122
/// collection's public interface, the implementation must take care to keep the
123
/// object consistent with the persisted state, mindful of the fact that the
124
/// state may have changed as a consequence of modifications from other instances
125
/// referencing the same persisted state.
126
class CollectionBase : public Collection {
127
public:
128
    /// True if the element at @a ndx is NULL.
129
    virtual bool is_null(size_t ndx) const = 0;
130

131
    /// Clear the collection.
132
    virtual void clear() = 0;
133

134
    /// Get the min element, according to whatever comparison function is
135
    /// meaningful for the collection, or none if min is not supported for this type.
136
    virtual util::Optional<Mixed> min(size_t* return_ndx = nullptr) const = 0;
137

138
    /// Get the max element, according to whatever comparison function is
139
    /// meaningful for the collection, or none if max is not supported for this type.
140
    virtual util::Optional<Mixed> max(size_t* return_ndx = nullptr) const = 0;
141

142
    /// For collections of arithmetic types, return the sum of all elements.
143
    /// For non arithmetic types, returns none.
144
    virtual util::Optional<Mixed> sum(size_t* return_cnt = nullptr) const = 0;
145

146
    /// For collections of arithmetic types, return the average of all elements.
147
    /// For non arithmetic types, returns none.
148
    virtual util::Optional<Mixed> avg(size_t* return_cnt = nullptr) const = 0;
149

150
    /// Produce a clone of the collection accessor referring to the same
151
    /// underlying memory.
152
    virtual CollectionBasePtr clone_collection() const = 0;
153

154
    /// Modifies a vector of indices so that they refer to values sorted
155
    /// according to the specified sort order.
156
    virtual void sort(std::vector<size_t>& indices, bool ascending = true) const = 0;
157

158
    /// Modifies a vector of indices so that they refer to distinct values. If
159
    /// @a sort_order is supplied, the indices will refer to values in sort
160
    /// order, otherwise the indices will be in the same order as they appear in
161
    /// the collection.
162
    virtual void distinct(std::vector<size_t>& indices, util::Optional<bool> sort_order = util::none) const = 0;
163

164
    // Return index of the first occurrence of 'value'
165
    virtual size_t find_any(Mixed value) const = 0;
166

167
    /// Get the object that owns this collection.
168
    virtual const Obj& get_obj() const noexcept = 0;
169

170
    /// Get the column key for this collection.
171
    virtual ColKey get_col_key() const noexcept = 0;
172

173
    virtual PathElement get_path_element(size_t ndx) const
174
    {
12✔
175
        return PathElement(ndx);
12✔
176
    }
12✔
177

178
    /// Return true if the collection has changed since the last call to
179
    /// `has_changed()`. Note that this function is not idempotent and updates
180
    /// the internal state of the accessor if it has changed.
181
    virtual bool has_changed() const noexcept = 0;
182

183
    /// Returns true if the accessor is in the attached state. By default, this
184
    /// checks if the owning object is still valid.
185
    virtual bool is_attached() const noexcept
186
    {
16,542✔
187
        return get_obj().is_valid();
16,542✔
188
    }
16,542✔
189

190
    // Note: virtual..final prevents static override.
191

192
    /// Get the key of the object that owns this collection.
193
    virtual ObjKey get_owner_key() const noexcept final
194
    {
2,902,344✔
195
        return get_obj().get_key();
2,902,344✔
196
    }
2,902,344✔
197

198
    /// Get the table of the object that owns this collection.
199
    ConstTableRef get_table() const noexcept
200
    {
3,785,277✔
201
        return get_obj().get_table();
3,785,277✔
202
    }
3,785,277✔
203

204
    /// If this is a collection of links, get the target table.
205
    virtual TableRef get_target_table() const final
206
    {
6,796,446✔
207
        return get_obj().get_target_table(get_col_key());
6,796,446✔
208
    }
6,796,446✔
209

210
    virtual size_t translate_index(size_t ndx) const noexcept
211
    {
19,284✔
212
        return ndx;
19,284✔
213
    }
19,284✔
214

215
    virtual DictionaryPtr get_dictionary(const PathElement&) const
216
    {
×
217
        throw IllegalOperation("get_dictionary for this collection is not allowed");
×
218
    }
×
219
    virtual ListMixedPtr get_list(const PathElement&) const
220
    {
×
221
        throw IllegalOperation("get_list for this collection is not allowed");
×
222
    }
×
223

224
    virtual void set_owner(const Obj& obj, ColKey) = 0;
225
    virtual void set_owner(std::shared_ptr<CollectionParent> parent, CollectionParent::Index index) = 0;
226

227

228
    StringData get_property_name() const
229
    {
492✔
230
        return get_table()->get_column_name(get_col_key());
492✔
231
    }
492✔
232

233
    bool operator==(const CollectionBase& other) const noexcept
234
    {
2,310✔
235
        return get_table() == other.get_table() && get_owner_key() == other.get_owner_key() &&
2,310✔
236
               get_col_key() == other.get_col_key();
1,986✔
237
    }
2,310✔
238

239
    bool operator!=(const CollectionBase& other) const noexcept
240
    {
×
241
        return !(*this == other);
×
242
    }
×
243

244
    // These are shadowed by typed versions in subclasses
245
    using value_type = Mixed;
246
    CollectionIterator<CollectionBase> begin() const;
247
    CollectionIterator<CollectionBase> end() const;
248

249
protected:
250
    friend class Transaction;
251
    CollectionBase() noexcept = default;
3,036,600✔
252
    CollectionBase(const CollectionBase&) noexcept = default;
24,594✔
253
    CollectionBase(CollectionBase&&) noexcept = default;
254
    CollectionBase& operator=(const CollectionBase&) noexcept = default;
7,104✔
255
    CollectionBase& operator=(CollectionBase&&) noexcept = default;
256

257
    void validate_index(const char* msg, size_t index, size_t size) const;
258
    static UpdateStatus do_init_from_parent(BPlusTreeBase* tree, ref_type ref, bool allow_create);
259
};
260

261
inline std::string_view collection_type_name(CollectionType col_type, bool uppercase = false)
262
{
2,670✔
263
    switch (col_type) {
2,670✔
264
        case CollectionType::List:
1,074✔
265
            return uppercase ? "List" : "list";
1,074✔
266
        case CollectionType::Set:
1,062✔
267
            return uppercase ? "Set" : "set";
1,062✔
268
        case CollectionType::Dictionary:
534✔
269
            return uppercase ? "Dictionary" : "dictionary";
534✔
270
    }
×
271
    return "";
×
272
}
×
273

274
inline void CollectionBase::validate_index(const char* msg, size_t index, size_t size) const
275
{
10,623,609✔
276
    if (index >= size) {
10,623,609✔
277
        throw OutOfBounds(util::format("%1 on %2 '%3.%4'", msg, collection_type_name(get_collection_type()),
438✔
278
                                       get_table()->get_class_name(), get_property_name()),
438✔
279
                          index, size);
438✔
280
    }
438✔
281
}
10,623,609✔
282

283

284
template <class T>
285
inline void check_column_type(ColKey col)
286
{
1,321,959✔
287
    if (col && col.get_type() != ColumnTypeTraits<T>::column_id) {
1,321,959✔
288
        throw InvalidColumnKey();
×
289
    }
×
290
}
1,321,959✔
291

292
template <>
293
inline void check_column_type<Int>(ColKey col)
294
{
47,730✔
295
    if (col && (col.get_type() != col_type_Int || col.get_attrs().test(col_attr_Nullable))) {
47,730✔
296
        throw InvalidColumnKey();
×
297
    }
×
298
}
47,730✔
299

300
template <>
301
inline void check_column_type<util::Optional<Int>>(ColKey col)
302
{
159,420✔
303
    if (col && (col.get_type() != col_type_Int || !col.get_attrs().test(col_attr_Nullable))) {
159,420✔
304
        throw InvalidColumnKey();
6✔
305
    }
6✔
306
}
159,420✔
307

308
template <>
309
inline void check_column_type<ObjKey>(ColKey col)
310
{
699,720✔
311
    if (col) {
699,720✔
312
        if (!((col.is_list() || col.is_set()) && col.get_type() == col_type_Link))
699,714✔
313
            throw InvalidArgument(ErrorCodes::TypeMismatch, "Property not a list or set");
×
314
    }
699,714✔
315
}
699,720✔
316

317
template <class T, class = void>
318
struct MinHelper {
319
    template <class U>
320
    static util::Optional<Mixed> eval(U&, size_t*) noexcept
321
    {
366✔
322
        return util::none;
366✔
323
    }
366✔
324
    static util::Optional<Mixed> not_found(size_t*) noexcept
325
    {
×
326
        return util::none;
×
327
    }
×
328
};
329

330
template <class T>
331
struct MinHelper<T, std::void_t<ColumnMinMaxType<T>>> {
332
    template <class U>
333
    static util::Optional<Mixed> eval(U& tree, size_t* return_ndx)
334
    {
427,470✔
335
        auto optional_min = bptree_minimum<T>(tree, return_ndx);
427,470✔
336
        if (optional_min) {
427,470✔
337
            return Mixed{*optional_min};
426,576✔
338
        }
426,576✔
339
        return Mixed{};
894✔
340
    }
894✔
341
    static util::Optional<Mixed> not_found(size_t* return_ndx) noexcept
342
    {
30✔
343
        if (return_ndx)
30!
344
            *return_ndx = realm::not_found;
6✔
345
        return Mixed{};
30✔
346
    }
30✔
347
};
348

349
template <class T, class Enable = void>
350
struct MaxHelper {
351
    template <class U>
352
    static util::Optional<Mixed> eval(U&, size_t*) noexcept
353
    {
360✔
354
        return util::none;
360✔
355
    }
360✔
356
    static util::Optional<Mixed> not_found(size_t*) noexcept
357
    {
×
358
        return util::none;
×
359
    }
×
360
};
361

362
template <class T>
363
struct MaxHelper<T, std::void_t<ColumnMinMaxType<T>>> {
364
    template <class U>
365
    static util::Optional<Mixed> eval(U& tree, size_t* return_ndx)
366
    {
427,470✔
367
        auto optional_max = bptree_maximum<T>(tree, return_ndx);
427,470✔
368
        if (optional_max) {
427,470✔
369
            return Mixed{*optional_max};
426,576✔
370
        }
426,576✔
371
        return Mixed{};
894✔
372
    }
894✔
373
    static util::Optional<Mixed> not_found(size_t* return_ndx) noexcept
374
    {
30✔
375
        if (return_ndx)
30!
376
            *return_ndx = realm::not_found;
6✔
377
        return Mixed{};
30✔
378
    }
30✔
379
};
380

381
template <class T, class Enable = void>
382
class SumHelper {
383
public:
384
    template <class U>
385
    static util::Optional<Mixed> eval(U&, size_t* return_cnt) noexcept
386
    {
432✔
387
        if (return_cnt)
432✔
388
            *return_cnt = 0;
×
389
        return util::none;
432✔
390
    }
432✔
391
    static util::Optional<Mixed> not_found(size_t*) noexcept
392
    {
×
393
        return util::none;
×
394
    }
×
395
};
396

397
template <class T>
398
class SumHelper<T, std::void_t<ColumnSumType<T>>> {
399
public:
400
    template <class U>
401
    static util::Optional<Mixed> eval(U& tree, size_t* return_cnt)
402
    {
942✔
403
        return Mixed{bptree_sum<T>(tree, return_cnt)};
942✔
404
    }
942✔
405
    static util::Optional<Mixed> not_found(size_t* return_cnt) noexcept
406
    {
30✔
407
        if (return_cnt)
30!
408
            *return_cnt = 0;
6✔
409
        using ResultType = typename aggregate_operations::Sum<typename util::RemoveOptional<T>::type>::ResultType;
30✔
410
        return Mixed{ResultType{}};
30✔
411
    }
30✔
412
};
413

414
template <class T, class = void>
415
struct AverageHelper {
416
    template <class U>
417
    static util::Optional<Mixed> eval(U&, size_t* return_cnt) noexcept
418
    {
432✔
419
        if (return_cnt)
432✔
420
            *return_cnt = 0;
×
421
        return util::none;
432✔
422
    }
432✔
423
    static util::Optional<Mixed> not_found(size_t*) noexcept
424
    {
×
425
        return util::none;
×
426
    }
×
427
};
428

429
template <class T>
430
struct AverageHelper<T, std::void_t<ColumnSumType<T>>> {
431
    template <class U>
432
    static util::Optional<Mixed> eval(U& tree, size_t* return_cnt)
433
    {
942✔
434
        size_t count = 0;
942✔
435
        auto result = Mixed{bptree_average<T>(tree, &count)};
942✔
436
        if (return_cnt) {
942✔
437
            *return_cnt = count;
144✔
438
        }
144✔
439
        return count == 0 ? util::none : result;
747✔
440
    }
942✔
441
    static util::Optional<Mixed> not_found(size_t* return_cnt) noexcept
442
    {
30✔
443
        if (return_cnt)
30!
444
            *return_cnt = 0;
6✔
445
        return Mixed{};
30✔
446
    }
30✔
447
};
448

449
/// Convenience base class for collections, which implements most of the
450
/// relevant interfaces for a collection that is bound to an object accessor and
451
/// representable as a BPlusTree<T>.
452
template <class Interface>
453
class CollectionBaseImpl : public Interface, protected ArrayParent {
454
public:
455
    static_assert(std::is_base_of_v<CollectionBase, Interface>);
456

457
    FullPath get_path() const override
458
    {
103,734✔
459
        auto path = m_parent->get_path();
103,734✔
460
        m_parent->add_index(path.path_from_top, m_index);
103,734✔
461
        return path;
103,734✔
462
    }
103,734✔
463

464
    Path get_short_path() const override
465
    {
369,411✔
466
        Path ret = m_parent->get_short_path();
369,411✔
467
        m_parent->add_index(ret, m_index);
369,411✔
468
        return ret;
369,411✔
469
    }
369,411✔
470

471
    StablePath get_stable_path() const override
472
    {
1,923,318✔
473
        auto ret = m_parent->get_stable_path();
1,923,318✔
474
        ret.push_back(m_index);
1,923,318✔
475
        return ret;
1,923,318✔
476
    }
1,923,318✔
477

478
    // Overriding members of CollectionBase:
479
    ColKey get_col_key() const noexcept override
480
    {
7,517,178✔
481
        return m_col_key;
7,517,178✔
482
    }
7,517,178✔
483

484
    const Obj& get_obj() const noexcept final
485
    {
13,779,987✔
486
        return m_obj_mem;
13,779,987✔
487
    }
13,779,987✔
488

489
    // The tricky thing here is that we should return true, even if the
490
    // collection has not yet been created.
491
    bool is_attached() const noexcept final
492
    {
861,591✔
493
        if (m_parent) {
861,591✔
494
            try {
861,423✔
495
                // Update the parent. Will throw if parent is not existing.
430,698✔
496
                switch (m_parent->update_if_needed()) {
861,423✔
497
                    case UpdateStatus::Updated:
93,195✔
498
                        // Make sure to update next time around
46,590✔
499
                        m_content_version = 0;
93,195✔
500
                        [[fallthrough]];
93,195✔
501
                    case UpdateStatus::NoChange:
856,239✔
502
                        // Check if it would be legal to try and get a ref from parent
428,106✔
503
                        // Will return true even if the current ref is 0.
428,106✔
504
                        return m_parent->check_collection_ref(m_index, Interface::s_collection_type);
856,239✔
505
                    case UpdateStatus::Detached:
49,179✔
506
                        break;
5,178✔
507
                }
6✔
508
            }
6✔
509
            catch (...) {
6✔
510
            }
6✔
511
        }
861,423✔
512
        return false;
433,458✔
513
    }
861,591✔
514

515
    /// Returns true if the accessor has changed since the last time
516
    /// `has_changed()` was called.
517
    ///
518
    /// Note: This method is not idempotent.
519
    ///
520
    /// Note: This involves a call to `update_if_needed()`.
521
    ///
522
    /// Note: This function returns false for an accessor that became
523
    /// detached since the last call
524
    bool has_changed() const noexcept final
525
    {
43,314✔
526
        try {
43,314✔
527
            // `has_changed()` sneakily modifies internal state.
21,657✔
528
            update_if_needed();
43,314✔
529
            if (m_last_content_version != m_content_version) {
43,314✔
530
                m_last_content_version = m_content_version;
9,822✔
531
                return true;
9,822✔
532
            }
9,822✔
533
        }
×
534
        catch (...) {
×
535
        }
×
536
        return false;
38,403✔
537
    }
43,314✔
538

539
    CollectionType get_collection_type() const noexcept override
540
    {
2,088✔
541
        return Interface::s_collection_type;
2,088✔
542
    }
2,088✔
543

544
    void set_owner(const Obj& obj, ColKey ck) override
545
    {
2,393,781✔
546
        m_obj_mem = obj;
2,393,781✔
547
        m_parent = &m_obj_mem;
2,393,781✔
548
        m_index = obj.build_index(ck);
2,393,781✔
549
        if (obj) {
2,394,561✔
550
            m_alloc = &obj.get_alloc();
2,394,561✔
551
        }
2,394,561✔
552
    }
2,393,781✔
553

554
    void set_owner(std::shared_ptr<CollectionParent> parent, CollectionParent::Index index) override
555
    {
5,604✔
556
        m_obj_mem = parent->get_object();
5,604✔
557
        m_col_parent = std::move(parent);
5,604✔
558
        m_parent = m_col_parent.get();
5,604✔
559
        m_index = index;
5,604✔
560
        if (m_obj_mem) {
5,604!
561
            m_alloc = &m_obj_mem.get_alloc();
5,604✔
562
        }
5,604✔
563
        // Force update on next access
2,802✔
564
        m_content_version = 0;
5,604✔
565
    }
5,604✔
566

567
    CollectionParent* get_owner() const noexcept
568
    {
18✔
569
        return m_parent;
18✔
570
    }
18✔
571

572
    void to_json(std::ostream&, JSONOutputMode, util::FunctionRef<void(const Mixed&)>) const override;
573

574
    using Interface::get_owner_key;
575
    using Interface::get_table;
576
    using Interface::get_target_table;
577

578
protected:
579
    ObjCollectionParent m_obj_mem;
580
    std::shared_ptr<CollectionParent> m_col_parent;
581
    CollectionParent::Index m_index;
582
    ColKey m_col_key;
583
    mutable uint_fast64_t m_content_version = 0;
584
    // Content version used by `has_changed()`.
585
    mutable uint_fast64_t m_last_content_version = 0;
586
    mutable uint32_t m_parent_version = 0;
587
    bool m_nullable = false;
588

589
    CollectionBaseImpl() = default;
298✔
590
    CollectionBaseImpl(const CollectionBaseImpl& other)
591
        : Interface(static_cast<const Interface&>(other))
592
        , m_obj_mem(other.m_obj_mem)
593
        , m_col_parent(other.m_col_parent)
594
        , m_index(other.m_index)
595
        , m_col_key(other.m_col_key)
596
        , m_nullable(other.m_nullable)
597
        , m_parent(m_col_parent ? m_col_parent.get() : &m_obj_mem)
598
        , m_alloc(other.m_alloc)
599
    {
18,105✔
600
    }
18,105✔
601

602
    CollectionBaseImpl(const Obj& obj, ColKey col_key) noexcept
603
        : m_obj_mem(obj)
604
        , m_index(obj.build_index(col_key))
605
        , m_col_key(col_key)
606
        , m_nullable(col_key.is_nullable())
607
        , m_parent(&m_obj_mem)
608
    {
27,909✔
609
        if (obj) {
27,909✔
610
            m_alloc = &m_obj_mem.get_alloc();
×
611
        }
×
612
    }
27,909✔
613

614
    CollectionBaseImpl(ColKey col_key) noexcept
615
        : m_col_key(col_key)
616
        , m_nullable(col_key.is_nullable())
617
    {
2,400,546✔
618
    }
2,400,546✔
619

620
    CollectionBaseImpl(CollectionParent& parent, CollectionParent::Index index) noexcept
621
        : m_obj_mem(parent.get_object())
622
        , m_index(index)
623
        , m_col_key(parent.get_col_key())
624
        , m_parent(&parent)
625
        , m_alloc(&m_obj_mem.get_alloc())
626
    {
2,304✔
627
    }
2,304✔
628

629
    CollectionBaseImpl& operator=(const CollectionBaseImpl& other)
630
    {
7,008✔
631
        Interface::operator=(static_cast<const Interface&>(other));
7,008✔
632
        if (this != &other) {
7,008✔
633
            m_obj_mem = other.m_obj_mem;
7,008✔
634
            m_col_parent = other.m_col_parent;
7,008✔
635
            m_parent = m_col_parent ? m_col_parent.get() : &m_obj_mem;
7,008✔
636
            m_alloc = other.m_alloc;
7,008✔
637
            m_index = other.m_index;
7,008✔
638
            m_col_key = other.m_col_key;
7,008✔
639
            m_nullable = other.m_nullable;
7,008✔
640
        }
7,008✔
641

3,504✔
642
        return *this;
7,008✔
643
    }
7,008✔
644

645
    ref_type get_collection_ref() const
646
    {
3,260,310✔
647
        return m_parent->get_collection_ref(m_index, Interface::s_collection_type);
3,260,310✔
648
    }
3,260,310✔
649

650
    void set_collection_ref(ref_type ref)
651
    {
514,800✔
652
        m_parent->set_collection_ref(m_index, ref, Interface::s_collection_type);
514,800✔
653
    }
514,800✔
654

655
    UpdateStatus get_update_status() const
656
    {
43,570,743✔
657
        UpdateStatus status = m_parent ? m_parent->update_if_needed() : UpdateStatus::Detached;
4,294,967,294✔
658

21,807,336✔
659
        if (status != UpdateStatus::Detached) {
43,570,743✔
660
            auto content_version = m_alloc->get_content_version();
43,513,632✔
661
            auto parent_version = m_parent->parent_version();
43,513,632✔
662
            if (content_version != m_content_version || m_parent_version != parent_version) {
43,513,632✔
663
                m_content_version = content_version;
2,170,167✔
664
                m_parent_version = parent_version;
2,170,167✔
665
                status = UpdateStatus::Updated;
2,170,167✔
666
            }
2,170,167✔
667
        }
43,513,632✔
668

21,807,336✔
669
        return status;
43,570,743✔
670
    }
43,570,743✔
671

672
    /// Refresh the parent object (if needed) and compare version numbers.
673
    /// Return true if the collection should initialize from parent
674
    /// Throws if the owning object no longer exists.
675
    bool should_update() const
676
    {
8,206,896✔
677
        check_parent();
8,206,896✔
678
        auto status = get_update_status();
8,206,896✔
679
        if (status == UpdateStatus::Detached) {
8,206,896✔
NEW
680
            throw StaleAccessor("Parent no longer exists");
×
UNCOV
681
        }
×
682
        return status == UpdateStatus::Updated;
8,206,896✔
683
    }
8,206,896✔
684

685
    void bump_content_version()
686
    {
8,573,712✔
687
        REALM_ASSERT(m_alloc);
8,573,712✔
688
        m_content_version = m_alloc->bump_content_version();
8,573,712✔
689
    }
8,573,712✔
690

691
    void update_content_version() const
692
    {
3,173,538✔
693
        REALM_ASSERT(m_alloc);
3,173,538✔
694
        m_content_version = m_alloc->get_content_version();
3,173,538✔
695
    }
3,173,538✔
696

697
    void bump_both_versions()
698
    {
1,230✔
699
        REALM_ASSERT(m_alloc);
1,230✔
700
        m_alloc->bump_content_version();
1,230✔
701
        m_alloc->bump_storage_version();
1,230✔
702
    }
1,230✔
703

704
    Replication* get_replication() const
705
    {
8,554,902✔
706
        check_parent();
8,554,902✔
707
        return m_parent->get_table()->get_repl();
8,554,902✔
708
    }
8,554,902✔
709

710
    Table* get_table_unchecked() const
711
    {
7,303,341✔
712
        check_parent();
7,303,341✔
713
        auto t = m_parent->get_table();
7,303,341✔
714
        REALM_ASSERT(t);
7,303,341✔
715
        return t.unchecked_ptr();
7,303,341✔
716
    }
7,303,341✔
717

718
    Allocator& get_alloc() const
719
    {
2,037,063✔
720
        check_alloc();
2,037,063✔
721
        return *m_alloc;
2,037,063✔
722
    }
2,037,063✔
723

724
    void set_alloc(Allocator& alloc)
725
    {
27,909✔
726
        m_alloc = &alloc;
27,909✔
727
    }
27,909✔
728

729
    void set_backlink(ColKey col_key, ObjLink new_link) const
730
    {
6,458,316✔
731
        check_parent();
6,458,316✔
732
        m_parent->get_object().set_backlink(col_key, new_link);
6,458,316✔
733
    }
6,458,316✔
734
    // Used when replacing a link, return true if CascadeState contains objects to remove
735
    bool replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
736
    {
63,864✔
737
        check_parent();
63,864✔
738
        return m_parent->get_object().replace_backlink(col_key, old_link, new_link, state);
63,864✔
739
    }
63,864✔
740
    // Used when removing a backlink, return true if CascadeState contains objects to remove
741
    bool remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
742
    {
19,446✔
743
        check_parent();
19,446✔
744
        return m_parent->get_object().remove_backlink(col_key, old_link, state);
19,446✔
745
    }
19,446✔
746

747
    /// Reset the accessor's tracking of the content version. Derived classes
748
    /// may choose to call this to force the accessor to become out of date,
749
    /// such that `update_if_needed()` returns `UpdateStatus::Updated` the next
750
    /// time it is called (or `UpdateStatus::Detached` if the data vanished in
751
    /// the meantime).
752
    void reset_content_version()
753
    {
12,666✔
754
        m_content_version = 0;
12,666✔
755
    }
12,666✔
756

757
    // Overriding ArrayParent interface:
758
    ref_type get_child_ref(size_t child_ndx) const noexcept final
759
    {
2,774,733✔
760
        static_cast<void>(child_ndx);
2,774,733✔
761
        return get_collection_ref();
2,774,733✔
762
    }
2,774,733✔
763

764
    void update_child_ref(size_t child_ndx, ref_type new_ref) final
765
    {
514,791✔
766
        static_cast<void>(child_ndx);
514,791✔
767
        set_collection_ref(new_ref);
514,791✔
768
    }
514,791✔
769

770
private:
771
    CollectionParent* m_parent = nullptr;
772
    Allocator* m_alloc = nullptr;
773

774
    void check_parent() const
775
    {
30,586,449✔
776
        if (!m_parent) {
30,586,449✔
777
            throw StaleAccessor("Collection has no owner");
6✔
778
        }
6✔
779
    }
30,586,449✔
780
    void check_alloc() const
781
    {
2,036,988✔
782
        if (!m_alloc) {
2,036,988✔
783
            throw StaleAccessor("Allocator not set");
×
784
        }
×
785
    }
2,036,988✔
786
    /// Refresh the associated `Obj` (if needed), and update the internal
787
    /// content version number. This is meant to be called from a derived class
788
    /// before accessing its data.
789
    ///
790
    /// If the `Obj` changed since the last call, or the content version was
791
    /// bumped, this returns `UpdateStatus::Updated`. In response, the caller
792
    /// must invoke `init_from_parent()` or similar on its internal state
793
    /// accessors to refresh its view of the data.
794
    ///
795
    /// If the owning object (or parent container) was deleted, an exception will
796
    /// be thrown and the caller should enter a degenerate state.
797
    ///
798
    /// If no change has happened to the data, this function returns
799
    /// `UpdateStatus::NoChange`, and the caller is allowed to not do anything.
800
    virtual UpdateStatus update_if_needed() const = 0;
801
};
802

803
namespace _impl {
804
/// Translate from condensed index to uncondensed index in collections that hide
805
/// tombstones.
806
size_t virtual2real(const std::vector<size_t>& vec, size_t ndx) noexcept;
807
size_t virtual2real(const BPlusTree<ObjKey>* tree, size_t ndx) noexcept;
808

809
/// Translate from uncondensed index to condensed into in collections that hide
810
/// tombstones.
811
size_t real2virtual(const std::vector<size_t>& vec, size_t ndx) noexcept;
812

813
/// Rebuild the list of unresolved keys for tombstone handling.
814
void update_unresolved(std::vector<size_t>& vec, const BPlusTree<ObjKey>* tree);
815

816
/// Clear the context flag on the tree if there are no more unresolved links.
817
void check_for_last_unresolved(BPlusTree<ObjKey>* tree);
818

819
/// Proxy class needed because the ObjList interface clobbers method names from
820
/// CollectionBase.
821
struct ObjListProxy : ObjList {
822
    virtual TableRef proxy_get_target_table() const = 0;
823

824
    TableRef get_target_table() const final
825
    {
6,279✔
826
        return proxy_get_target_table();
6,279✔
827
    }
6,279✔
828
};
829

830
} // namespace _impl
831

832
/// Base class for collections of objects, where unresolved links (tombstones)
833
/// can occur.
834
template <class Interface>
835
class ObjCollectionBase : public Interface, public _impl::ObjListProxy {
836
public:
837
    static_assert(std::is_base_of_v<CollectionBase, Interface>);
838

839
    using Interface::get_col_key;
840
    using Interface::get_obj;
841
    using Interface::get_table;
842
    using Interface::is_attached;
843
    using Interface::size;
844

845
    // Overriding methods in ObjList:
846

847
    void get_dependencies(TableVersions& versions) const final
848
    {
4,308✔
849
        if (is_attached()) {
4,308✔
850
            auto table = this->get_table();
4,212✔
851
            versions.emplace_back(table->get_key(), table->get_content_version());
4,212✔
852
        }
4,212✔
853
    }
4,308✔
854

855
    void sync_if_needed() const final
856
    {
1,788✔
857
        update_if_needed();
1,788✔
858
    }
1,788✔
859

860
    bool is_in_sync() const noexcept final
861
    {
12✔
862
        return true;
12✔
863
    }
12✔
864

865
    bool has_unresolved() const noexcept
866
    {
1,016✔
867
        update_if_needed();
1,016✔
868
        return m_unresolved.size() != 0;
1,016✔
869
    }
1,016✔
870

871
    using Interface::get_target_table;
872

873
protected:
874
    ObjCollectionBase() = default;
599,076✔
875
    ObjCollectionBase(const ObjCollectionBase&) = default;
6,489✔
876
    ObjCollectionBase(ObjCollectionBase&&) = default;
877
    ObjCollectionBase& operator=(const ObjCollectionBase&) = default;
8✔
878
    ObjCollectionBase& operator=(ObjCollectionBase&&) = default;
54✔
879

880
    /// Implementations should call `update_if_needed()` on their inner accessor
881
    /// (without `update_unresolved()`).
882
    virtual UpdateStatus do_update_if_needed() const = 0;
883

884
    /// Implementations should return a non-const reference to their internal
885
    /// `BPlusTree<T>`.
886
    virtual BPlusTree<ObjKey>* get_mutable_tree() const = 0;
887

888
    /// Implements `update_if_needed()` in a way that ensures the consistency of
889
    /// the unresolved list. Derived classes should call this instead of calling
890
    /// `update_if_needed()` on their inner accessor.
891
    UpdateStatus update_if_needed() const
892
    {
13,295,316✔
893
        auto status = do_update_if_needed();
13,295,316✔
894
        update_unresolved(status);
13,295,316✔
895
        return status;
13,295,316✔
896
    }
13,295,316✔
897

898
    /// Translate from condensed index to uncondensed.
899
    size_t virtual2real(size_t ndx) const noexcept
900
    {
6,666,831✔
901
        return _impl::virtual2real(m_unresolved, ndx);
6,666,831✔
902
    }
6,666,831✔
903

904
    /// Translate from uncondensed index to condensed.
905
    size_t real2virtual(size_t ndx) const noexcept
906
    {
34,710✔
907
        return _impl::real2virtual(m_unresolved, ndx);
34,710✔
908
    }
34,710✔
909

910
    bool real_is_unresolved(size_t ndx) const noexcept
911
    {
444✔
912
        return std::find(m_unresolved.begin(), m_unresolved.end(), ndx) != m_unresolved.end();
444✔
913
    }
444✔
914

915
    /// Rebuild the list of tombstones if there is a possibility that it has
916
    /// changed.
917
    ///
918
    /// If the accessor became detached, this clears the unresolved list.
919
    void update_unresolved(UpdateStatus status) const
920
    {
19,686,825✔
921
        switch (status) {
19,686,825✔
922
            case UpdateStatus::Detached: {
247,257✔
923
                clear_unresolved();
247,257✔
924
                break;
247,257✔
925
            }
×
926
            case UpdateStatus::Updated: {
6,531,300✔
927
                _impl::update_unresolved(m_unresolved, get_mutable_tree());
6,531,300✔
928
                break;
6,531,300✔
929
            }
×
930
            case UpdateStatus::NoChange:
12,912,066✔
931
                break;
12,912,066✔
932
        }
19,686,825✔
933
    }
19,686,825✔
934

935
    /// When a tombstone is removed from a list, call this to update internal
936
    /// flags that indicate the presence of tombstones.
937
    void check_for_last_unresolved()
938
    {
939
        _impl::check_for_last_unresolved(get_mutable_tree());
940
    }
941

942
    /// Clear the list of tombstones. It will be rebuilt the next time
943
    /// `update_if_needed()` is called.
944
    void clear_unresolved() const noexcept
945
    {
257,034✔
946
        m_unresolved.clear();
257,034✔
947
    }
257,034✔
948

949
    /// Return the number of tombstones.
950
    size_t num_unresolved() const noexcept
951
    {
6,825,213✔
952
        return m_unresolved.size();
6,825,213✔
953
    }
6,825,213✔
954

955
private:
956
    // Sorted set of indices containing unresolved links.
957
    mutable std::vector<size_t> m_unresolved;
958

959
    TableRef proxy_get_target_table() const final
960
    {
6,279✔
961
        return Interface::get_target_table();
6,279✔
962
    }
6,279✔
963
    bool matches(const ObjList& other) const final
964
    {
6✔
965
        return get_owning_obj().get_key() == other.get_owning_obj().get_key() &&
6!
966
               get_owning_col_key() == other.get_owning_col_key();
6!
967
    }
6✔
968
    Obj get_owning_obj() const final
969
    {
780✔
970
        return get_obj();
780✔
971
    }
780✔
972
    ColKey get_owning_col_key() const final
973
    {
666✔
974
        return get_col_key();
666✔
975
    }
666✔
976
};
977

978
/// Random-access iterator over elements of a collection.
979
///
980
/// Values are cached into a member variable in order to support `operator->`
981
/// and `operator*` returning a pointer and a reference, respectively.
982
template <class L>
983
struct CollectionIterator {
984
    using iterator_category = std::random_access_iterator_tag;
985
    using value_type = typename L::value_type;
986
    using difference_type = ptrdiff_t;
987
    using pointer = const value_type*;
988
    using reference = const value_type&;
989

990
    CollectionIterator() noexcept = default;
6✔
991
    CollectionIterator(const L* l, size_t ndx) noexcept
992
        : m_list(l)
993
        , m_ndx(ndx)
994
    {
2,456,826✔
995
    }
2,456,826✔
996

997
    pointer operator->() const
998
    {
8,430,149✔
999
        if constexpr (std::is_same_v<L, CollectionBase>) {
8,430,149✔
1000
            m_val = m_list->get_any(m_ndx);
8,421,653✔
1001
        }
8,421,653✔
1002
        else {
8,421,653✔
1003
            m_val = m_list->get(m_ndx);
8,421,653✔
1004
        }
8,421,653✔
1005
        return &m_val;
8,430,149✔
1006
    }
8,430,149✔
1007

1008
    reference operator*() const
1009
    {
8,430,143✔
1010
        return *operator->();
8,430,143✔
1011
    }
8,430,143✔
1012

1013
    CollectionIterator& operator++() noexcept
1014
    {
3,614,888✔
1015
        ++m_ndx;
3,614,888✔
1016
        return *this;
3,614,888✔
1017
    }
3,614,888✔
1018

1019
    CollectionIterator operator++(int) noexcept
1020
    {
2✔
1021
        auto tmp = *this;
2✔
1022
        operator++();
2✔
1023
        return tmp;
2✔
1024
    }
2✔
1025

1026
    CollectionIterator& operator--() noexcept
1027
    {
1028
        --m_ndx;
1029
        return *this;
1030
    }
1031

1032
    CollectionIterator operator--(int) noexcept
1033
    {
1034
        auto tmp = *this;
1035
        operator--();
1036
        return tmp;
1037
    }
1038

1039
    CollectionIterator& operator+=(ptrdiff_t n) noexcept
1040
    {
7,311,654✔
1041
        m_ndx += n;
7,311,654✔
1042
        return *this;
7,311,654✔
1043
    }
7,311,654✔
1044

1045
    CollectionIterator& operator-=(ptrdiff_t n) noexcept
1046
    {
1047
        m_ndx -= n;
1048
        return *this;
1049
    }
1050

1051
    friend ptrdiff_t operator-(const CollectionIterator& lhs, const CollectionIterator& rhs) noexcept
1052
    {
1,119,473✔
1053
        return ptrdiff_t(lhs.m_ndx) - ptrdiff_t(rhs.m_ndx);
1,119,473✔
1054
    }
1,119,473✔
1055

1056
    friend CollectionIterator operator+(CollectionIterator lhs, ptrdiff_t rhs) noexcept
1057
    {
1058
        lhs.m_ndx += rhs;
1059
        return lhs;
1060
    }
1061

1062
    friend CollectionIterator operator+(ptrdiff_t lhs, CollectionIterator rhs) noexcept
1063
    {
1064
        return rhs + lhs;
1065
    }
1066

1067
    bool operator!=(const CollectionIterator& rhs) const noexcept
1068
    {
299,464✔
1069
        REALM_ASSERT_DEBUG(m_list == rhs.m_list);
299,464✔
1070
        return m_ndx != rhs.m_ndx;
299,464✔
1071
    }
299,464✔
1072

1073
    bool operator==(const CollectionIterator& rhs) const noexcept
1074
    {
15,786✔
1075
        REALM_ASSERT_DEBUG(m_list == rhs.m_list);
15,786!
1076
        return m_ndx == rhs.m_ndx;
15,786✔
1077
    }
15,786✔
1078

1079
    size_t index() const noexcept
1080
    {
2,204,607✔
1081
        return m_ndx;
2,204,607✔
1082
    }
2,204,607✔
1083

1084
private:
1085
    mutable value_type m_val;
1086
    const L* m_list = nullptr;
1087
    size_t m_ndx = size_t(-1);
1088
};
1089

1090

1091
inline CollectionIterator<CollectionBase> CollectionBase::begin() const
1092
{
1,386✔
1093
    return CollectionIterator<CollectionBase>(this, 0);
1,386✔
1094
}
1,386✔
1095
inline CollectionIterator<CollectionBase> CollectionBase::end() const
1096
{
1,506✔
1097
    return CollectionIterator<CollectionBase>(this, size());
1,506✔
1098
}
1,506✔
1099
template <class T>
1100
class IteratorAdapter {
1101
public:
1102
    IteratorAdapter(T* keys)
1103
        : m_list(keys)
1104
    {
963,309✔
1105
    }
963,309✔
1106
    CollectionIterator<T> begin() const
1107
    {
963,309✔
1108
        return CollectionIterator<T>(m_list, 0);
963,309✔
1109
    }
963,309✔
1110
    CollectionIterator<T> end() const
1111
    {
963,309✔
1112
        return CollectionIterator<T>(m_list, m_list->size());
963,309✔
1113
    }
963,309✔
1114

1115
private:
1116
    T* m_list;
1117
};
1118

1119
namespace _impl {
1120
size_t get_collection_size_from_ref(ref_type, Allocator& alloc);
1121
}
1122

1123
} // namespace realm
1124

1125
#endif // REALM_COLLECTION_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

© 2025 Coveralls, Inc