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

realm / realm-core / jorgen.edelbo_138

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

Pull #7356

Evergreen

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

94532 of 174642 branches covered (54.13%)

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

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

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

87.12
/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✔
NEW
42
    void translate_path(const StablePath&, Path&) const final {}
×
UNCOV
43
    void add_index(Path&, const Index&) const noexcept final {}
×
44
    size_t find_index(const Index&) const noexcept final
45
    {
×
46
        return realm::npos;
×
47
    }
×
48

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

228

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

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

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

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

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

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

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

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

284

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

485
    const Obj& get_obj() const noexcept final
486
    {
13,783,182✔
487
        return m_obj_mem;
13,783,182✔
488
    }
13,783,182✔
489

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

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

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

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

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

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

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

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

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

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

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

615
    CollectionBaseImpl(ColKey col_key) noexcept
616
        : m_col_key(col_key)
617
        , m_nullable(col_key.is_nullable())
618
    {
2,402,199✔
619
    }
2,402,199✔
620

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

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

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

646
    ref_type get_collection_ref() const
647
    {
3,264,252✔
648
        return m_parent->get_collection_ref(m_index, Interface::s_collection_type);
3,264,252✔
649
    }
3,264,252✔
650

651
    void set_collection_ref(ref_type ref)
652
    {
515,505✔
653
        m_parent->set_collection_ref(m_index, ref, Interface::s_collection_type);
515,505✔
654
    }
515,505✔
655

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

21,775,686✔
660
        if (status != UpdateStatus::Detached) {
43,564,602✔
661
            auto content_version = m_alloc->get_content_version();
43,472,274✔
662
            auto parent_version = m_parent->parent_version();
43,472,274✔
663
            if (content_version != m_content_version || m_parent_version != parent_version) {
43,472,274✔
664
                m_content_version = content_version;
2,173,128✔
665
                m_parent_version = parent_version;
2,173,128✔
666
                status = UpdateStatus::Updated;
2,173,128✔
667
            }
2,173,128✔
668
        }
43,472,274✔
669

21,775,686✔
670
        return status;
43,564,602✔
671
    }
43,564,602✔
672

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

686
    void bump_content_version()
687
    {
8,571,477✔
688
        REALM_ASSERT(m_alloc);
8,571,477✔
689
        m_content_version = m_alloc->bump_content_version();
8,571,477✔
690
    }
8,571,477✔
691

692
    void update_content_version() const
693
    {
3,178,656✔
694
        REALM_ASSERT(m_alloc);
3,178,656✔
695
        m_content_version = m_alloc->get_content_version();
3,178,656✔
696
    }
3,178,656✔
697

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

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

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

719
    Allocator& get_alloc() const
720
    {
2,038,938✔
721
        check_alloc();
2,038,938✔
722
        return *m_alloc;
2,038,938✔
723
    }
2,038,938✔
724

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

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

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

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

765
    void update_child_ref(size_t child_ndx, ref_type new_ref) final
766
    {
515,499✔
767
        static_cast<void>(child_ndx);
515,499✔
768
        set_collection_ref(new_ref);
515,499✔
769
    }
515,499✔
770

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

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

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

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

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

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

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

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

831
} // namespace _impl
832

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

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

846
    // Overriding methods in ObjList:
847

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

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

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

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

872
    using Interface::get_target_table;
873

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1009
    reference operator*() const
1010
    {
8,429,126✔
1011
        return *operator->();
8,429,126✔
1012
    }
8,429,126✔
1013

1014
    CollectionIterator& operator++() noexcept
1015
    {
3,615,854✔
1016
        ++m_ndx;
3,615,854✔
1017
        return *this;
3,615,854✔
1018
    }
3,615,854✔
1019

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

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

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

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

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

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

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

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

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

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

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

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

1091

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

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

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

1124
} // namespace realm
1125

1126
#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