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

realm / realm-core / github_pull_request_301264

30 Jul 2024 07:11PM UTC coverage: 91.111% (+0.009%) from 91.102%
github_pull_request_301264

Pull #7936

Evergreen

web-flow
Add support for multi-process subscription state change notifications (#7862)

As with the other multi-process notifications, the core idea here is to
eliminate the in-memory state and produce notifications based entirely on the
current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for
the specific legal state transitions, which also take a write transaction as a
parameter. These functions are called by PendingBootstrapStore inside the same
write transaction as the bootstrap updates which changed the subscription
state. This is both a minor performance optimization (due to fewer writes) and
eliminates a brief window between the two writes where the Realm file was in an
inconsistent state.

There's a minor functional change here: previously old subscription sets were
superseded when the new one reached the Completed state, and now they are
superseded on AwaitingMark. This aligns it with when the new subscription set
becomes the one which is returned by get_active().
Pull Request #7936: Fix connection callback crashes when reloading with React Native

102800 of 181570 branches covered (56.62%)

216840 of 237996 relevant lines covered (91.11%)

5918493.47 hits per line

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

72.55
/src/realm/bplustree.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2018 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/bplustree.hpp>
20
#include <realm/impl/destroy_guard.hpp>
21
#include <realm/array_unsigned.hpp>
22
#include <realm/array_integer.hpp>
23

24
using namespace realm;
25

26
namespace realm {
27

28
bool BPlusTreeNode::get_context_flag() const noexcept
29
{
6,629,529✔
30
    auto ref = get_ref();
6,629,529✔
31
    MemRef mem(ref, m_tree->get_alloc());
6,629,529✔
32
    return Array::get_context_flag_from_header(mem.get_addr());
6,629,529✔
33
}
6,629,529✔
34

35
void BPlusTreeNode::set_context_flag(bool cf) noexcept
36
{
44,913✔
37
    auto ref = get_ref();
44,913✔
38
    MemRef mem(ref, m_tree->get_alloc());
44,913✔
39
    if (Array::get_context_flag_from_header(mem.get_addr()) != cf) {
44,913✔
40
        Array arr(m_tree->get_alloc());
12,666✔
41
        arr.init_from_mem(mem);
12,666✔
42
        arr.set_context_flag(cf);
12,666✔
43
        if (auto new_ref = arr.get_ref(); new_ref != ref) {
12,666✔
44
            init_from_ref(new_ref);
6✔
45
            update_parent();
6✔
46
        }
6✔
47
    }
12,666✔
48
}
44,913✔
49

50

51
/*****************************************************************************/
52
/* BPlusTreeInner                                                            */
53
/* All interior nodes is of this class                                       */
54
/*****************************************************************************/
55
class BPlusTreeInner : public BPlusTreeNode, private Array {
56
public:
57
    using Array::destroy_deep;
58

59
    BPlusTreeInner(BPlusTreeBase* tree);
60
    ~BPlusTreeInner() override;
61

62
    void init_from_mem(MemRef mem);
63
    void create(size_t elems_per_child);
64

65
    /******** Implementation of abstract interface *******/
66
    bool is_leaf() const override
67
    {
461,286✔
68
        return false;
461,286✔
69
    }
461,286✔
70
    bool is_compact() const override
71
    {
×
72
        return (Array::get(0) & 1) != 0;
×
73
    }
×
74
    ref_type get_ref() const override
75
    {
69,582✔
76
        return Array::get_ref();
69,582✔
77
    }
69,582✔
78

79
    void init_from_ref(ref_type ref) noexcept override
80
    {
7,610,490✔
81
        char* header = m_alloc.translate(ref);
7,610,490✔
82
        init_from_mem(MemRef(header, ref, m_alloc));
7,610,490✔
83
    }
7,610,490✔
84

85
    void bp_set_parent(ArrayParent* p, size_t n) override
86
    {
8,802,297✔
87
        Array::set_parent(p, n);
8,802,297✔
88
    }
8,802,297✔
89
    void update_parent() override
90
    {
13,392✔
91
        Array::update_parent();
13,392✔
92
    }
13,392✔
93

94
    size_t get_node_size() const override
95
    {
8,288,532✔
96
        return Array::size() - 2;
8,288,532✔
97
    }
8,288,532✔
98
    size_t get_tree_size() const override
99
    {
9,010,791✔
100
        return size_t(back()) >> 1;
9,010,791✔
101
    }
9,010,791✔
102

103
    ref_type bptree_insert(size_t n, State& state, InsertFunc) override;
104
    void bptree_access(size_t n, AccessFunc) override;
105
    size_t bptree_erase(size_t n, EraseFunc) override;
106
    bool bptree_traverse(TraverseFunc) override;
107
    void verify() const override;
108

109
    // Other modifiers
110

111
    void append_tree_size(size_t sz)
112
    {
13,392✔
113
        Array::add((sz << 1) + 1);
13,392✔
114
    }
13,392✔
115
    void add_bp_node_ref(ref_type ref, int64_t offset = 0)
116
    {
26,832✔
117
        Array::add(from_ref(ref));
26,832✔
118
        REALM_ASSERT_DEBUG(offset >= 0);
26,832✔
119
        if (offset && m_offsets.is_attached()) {
26,832✔
120
            m_offsets.add(offset);
126✔
121
        }
126✔
122
    }
26,832✔
123
    // Reset first (and only!) child ref and return the previous value
124
    ref_type clear_first_bp_node_ref()
125
    {
186✔
126
        REALM_ASSERT(get_node_size() == 1);
186✔
127
        ref_type old_ref = Array::get_as_ref(1);
186✔
128
        Array::set(1, 0);
186✔
129

130
        return old_ref;
186✔
131
    }
186✔
132
    void ensure_offsets();
133

134
    std::unique_ptr<BPlusTreeInner> split_root();
135

136
private:
137
    ArrayUnsigned m_offsets;
138
    size_t m_my_offset;
139

140
    void move(BPlusTreeNode* new_node, size_t ndx, int64_t offset_adj) override;
141
    void set_offset(size_t offs)
142
    {
×
143
        m_my_offset = offs;
×
144
    }
×
145
    void set_tree_size(size_t sz)
146
    {
×
147
        Array::set(m_size - 1, (sz << 1) + 1);
×
148
    }
×
149
    size_t get_elems_per_child() const
150
    {
12,137,466✔
151
        // Only relevant when in compact form
152
        REALM_ASSERT_DEBUG(!m_offsets.is_attached());
12,137,466✔
153
        return size_t(Array::get(0)) >> 1;
12,137,466✔
154
    }
12,137,466✔
155
    // Should not be mixed up with Array::get_child_ref
156
    ref_type get_bp_node_ref(size_t ndx) const noexcept
157
    {
12,873,363✔
158
        return Array::get_as_ref(ndx + 1);
12,873,363✔
159
    }
12,873,363✔
160
    void insert_bp_node_ref(size_t ndx, ref_type ref)
161
    {
1,044✔
162
        Array::insert(ndx + 1, from_ref(ref));
1,044✔
163
    }
1,044✔
164
    BPlusTreeLeaf* cache_leaf(MemRef mem, size_t ndx, size_t offset);
165
    void erase_and_destroy_bp_node(size_t ndx);
166
    ref_type insert_bp_node(size_t child_ndx, ref_type new_sibling_ref, State& state);
167
    size_t get_bp_node_offset(size_t child_ndx) const
168
    {
706,434✔
169
        return (child_ndx) > 0 ? size_t(m_offsets.get(child_ndx - 1)) : 0;
706,434✔
170
    }
706,434✔
171
};
172
} // namespace realm
173

174
/****************************** BPlusTreeNode ********************************/
175

176
BPlusTreeNode::~BPlusTreeNode() {}
64,082,817✔
177

178
/****************************** BPlusTreeLeaf ********************************/
179

180
ref_type BPlusTreeLeaf::bptree_insert(size_t ndx, State& state, InsertFunc func)
181
{
20,516,646✔
182
    size_t leaf_size = get_node_size();
20,516,646✔
183
    REALM_ASSERT_DEBUG(leaf_size <= REALM_MAX_BPNODE_SIZE);
20,516,646✔
184
    if (ndx > leaf_size)
20,516,646✔
185
        ndx = leaf_size;
20,379,378✔
186
    if (REALM_LIKELY(leaf_size < REALM_MAX_BPNODE_SIZE)) {
20,516,646✔
187
        func(this, ndx);
20,498,448✔
188
        m_tree->adjust_leaf_bounds(1);
20,498,448✔
189
        return 0; // Leaf was not split
20,498,448✔
190
    }
20,498,448✔
191

192
    // Split leaf node
193
    auto new_leaf = m_tree->create_leaf_node();
18,198✔
194
    if (ndx == leaf_size) {
18,351✔
195
        func(new_leaf.get(), 0);
14,280✔
196
        state.split_offset = ndx;
14,280✔
197
    }
14,280✔
198
    else {
2,147,487,718✔
199
        move(new_leaf.get(), ndx, 0);
2,147,487,718✔
200
        func(this, ndx);
2,147,487,718✔
201
        state.split_offset = ndx + 1;
2,147,487,718✔
202
        // Invalidate cached leaf
203
        m_tree->invalidate_leaf_cache();
2,147,487,718✔
204
    }
2,147,487,718✔
205
    state.split_size = leaf_size + 1;
18,198✔
206

207
    return new_leaf->get_ref();
18,198✔
208
}
20,516,646✔
209

210
void BPlusTreeLeaf::bptree_access(size_t ndx, AccessFunc func)
211
{
50,454,195✔
212
    func(this, ndx);
50,454,195✔
213
}
50,454,195✔
214

215
size_t BPlusTreeLeaf::bptree_erase(size_t ndx, EraseFunc func)
216
{
1,611,720✔
217
    return func(this, ndx);
1,611,720✔
218
}
1,611,720✔
219

220
bool BPlusTreeLeaf::bptree_traverse(TraverseFunc func)
221
{
2,439,159✔
222
    return func(this, 0) == IteratorControl::Stop;
2,439,159✔
223
}
2,439,159✔
224

225
template <>
226
void BPlusTree<int64_t>::split_root()
227
{
6✔
228
    if (m_root->is_leaf()) {
6✔
229
        auto sz = m_root->get_node_size();
6✔
230

231
        LeafNode* leaf = static_cast<LeafNode*>(m_root.get());
6✔
232
        ref_type orig_root_ref = leaf->get_ref();
6✔
233
        auto new_root = std::make_unique<BPlusTreeInner>(this);
6✔
234

235
        new_root->create(REALM_MAX_BPNODE_SIZE);
6✔
236

237
        size_t ndx = 0;
6✔
238
        while (ndx < sz) {
66✔
239
            LeafNode new_leaf(this);
60✔
240
            new_leaf.create();
60✔
241
            size_t to_move = std::min(size_t(REALM_MAX_BPNODE_SIZE), sz - ndx);
60✔
242
            for (size_t i = 0; i < to_move; i++, ndx++) {
60,060✔
243
                new_leaf.insert(i, leaf->get(ndx));
60,000✔
244
            }
60,000✔
245
            new_root->add_bp_node_ref(new_leaf.get_ref()); // Throws
60✔
246
        }
60✔
247
        new_root->append_tree_size(sz);
6✔
248
        replace_root(std::move(new_root));
6✔
249
        // destroy after replace_root in case we need a valid context flag lookup
250
        Array::destroy(orig_root_ref, m_alloc);
6✔
251
    }
6✔
252
    else {
×
253
        BPlusTreeInner* inner = static_cast<BPlusTreeInner*>(m_root.get());
×
254
        replace_root(inner->split_root());
×
255
    }
×
256
}
6✔
257

258
/****************************** BPlusTreeInner *******************************/
259

260
BPlusTreeInner::BPlusTreeInner(BPlusTreeBase* tree)
261
    : BPlusTreeNode(tree)
3,773,235✔
262
    , Array(tree->get_alloc())
3,773,235✔
263
    , m_offsets(tree->get_alloc())
3,773,235✔
264
    , m_my_offset(0)
3,773,235✔
265
{
7,548,147✔
266
    m_offsets.set_parent(this, 0);
7,548,147✔
267
}
7,548,147✔
268

269
void BPlusTreeInner::create(size_t elems_per_child)
270
{
13,392✔
271
    // Born only with room for number of elements per child
272
    uint64_t tagged = (elems_per_child << 1) + 1;
13,392✔
273
    Array::create(Array::type_InnerBptreeNode, false, 1, tagged);
13,392✔
274
}
13,392✔
275

276
BPlusTreeInner::~BPlusTreeInner() {}
7,548,168✔
277

278
void BPlusTreeInner::init_from_mem(MemRef mem)
279
{
7,610,448✔
280
    Array::init_from_mem(mem);
7,610,448✔
281
    auto rot = Array::get(0);
7,610,448✔
282
    if ((rot & 1) == 0) {
7,610,448✔
283
        // rot is a ref
284
        m_offsets.init_from_ref(to_ref(rot));
117,108✔
285
    }
117,108✔
286
    else {
7,493,340✔
287
        m_offsets.detach();
7,493,340✔
288
    }
7,493,340✔
289
}
7,610,448✔
290

291
void BPlusTreeInner::bptree_access(size_t n, AccessFunc func)
292
{
4,522,044✔
293
    size_t child_ndx;
4,522,044✔
294
    size_t child_offset;
4,522,044✔
295
    if (m_offsets.is_attached()) {
4,522,044✔
296
        child_ndx = m_offsets.upper_bound(n);
157,302✔
297
        child_offset = get_bp_node_offset(child_ndx);
157,302✔
298
        REALM_ASSERT_3(child_ndx, <, get_node_size());
157,302✔
299
    }
157,302✔
300
    else {
4,364,742✔
301
        auto elems_per_child = get_elems_per_child();
4,364,742✔
302
        child_ndx = n / elems_per_child;
4,364,742✔
303
        child_offset = child_ndx * elems_per_child;
4,364,742✔
304
    }
4,364,742✔
305

306
    ref_type child_ref = get_bp_node_ref(child_ndx);
4,522,044✔
307
    char* child_header = m_alloc.translate(child_ref);
4,522,044✔
308
    MemRef mem(child_header, child_ref, m_alloc);
4,522,044✔
309
    bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
4,522,044✔
310
    if (child_is_leaf) {
4,522,065✔
311
        auto leaf = cache_leaf(mem, child_ndx, child_offset + m_my_offset);
4,521,015✔
312
        func(leaf, n - child_offset);
4,521,015✔
313
    }
4,521,015✔
314
    else {
2,147,484,697✔
315
        BPlusTreeInner node(m_tree);
2,147,484,697✔
316
        node.set_parent(this, child_ndx + 1);
2,147,484,697✔
317
        node.init_from_mem(mem);
2,147,484,697✔
318
        node.set_offset(child_offset + m_my_offset);
2,147,484,697✔
319
        node.bptree_access(n - child_offset, func);
2,147,484,697✔
320
    }
2,147,484,697✔
321
}
4,522,044✔
322

323
ref_type BPlusTreeInner::bptree_insert(size_t ndx, State& state, InsertFunc func)
324
{
1,400,361✔
325
    size_t child_ndx;
1,400,361✔
326
    size_t child_offset;
1,400,361✔
327
    if (ndx != npos) {
1,400,361✔
328
        ensure_offsets();
17,898✔
329
        child_ndx = m_offsets.upper_bound(ndx);
17,898✔
330
        child_offset = get_bp_node_offset(child_ndx);
17,898✔
331
        ndx -= child_offset;
17,898✔
332
        REALM_ASSERT_3(child_ndx, <, get_node_size());
17,898✔
333
    }
17,898✔
334
    else {
1,382,463✔
335
        child_ndx = get_node_size() - 1;
1,382,463✔
336
        if (m_offsets.is_attached()) {
1,382,463✔
337
            child_offset = get_bp_node_offset(child_ndx);
×
338
            REALM_ASSERT_3(child_ndx, <, get_node_size());
×
339
        }
×
340
        else {
1,382,463✔
341
            auto elems_per_child = get_elems_per_child();
1,382,463✔
342
            child_offset = child_ndx * elems_per_child;
1,382,463✔
343
        }
1,382,463✔
344
    }
1,382,463✔
345

346
    ref_type child_ref = get_bp_node_ref(child_ndx);
1,400,361✔
347
    char* child_header = m_alloc.translate(child_ref);
1,400,361✔
348
    MemRef mem(child_header, child_ref, m_alloc);
1,400,361✔
349
    bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
1,400,361✔
350
    ref_type new_sibling_ref;
1,400,361✔
351
    if (child_is_leaf) {
1,400,364✔
352
        auto leaf = cache_leaf(mem, child_ndx, child_offset + m_my_offset);
1,400,205✔
353
        new_sibling_ref = leaf->bptree_insert(ndx, state, func);
1,400,205✔
354
    }
1,400,205✔
355
    else {
2,147,483,806✔
356
        BPlusTreeInner node(m_tree);
2,147,483,806✔
357
        node.set_parent(this, child_ndx + 1);
2,147,483,806✔
358
        node.init_from_mem(mem);
2,147,483,806✔
359
        node.set_offset(child_offset + m_my_offset);
2,147,483,806✔
360
        new_sibling_ref = node.bptree_insert(ndx, state, func);
2,147,483,806✔
361
    }
2,147,483,806✔
362

363
    if (!new_sibling_ref) {
1,400,361✔
364
        adjust(size() - 1, +2); // Throws
1,398,981✔
365
        if (m_offsets.is_attached()) {
1,398,981✔
366
            m_offsets.adjust(child_ndx, m_offsets.size(), 1);
17,874✔
367
        }
17,874✔
368
        return 0;
1,398,981✔
369
    }
1,398,981✔
370

371
    return insert_bp_node(child_ndx, new_sibling_ref, state);
1,380✔
372
}
1,400,361✔
373

374
size_t BPlusTreeInner::bptree_erase(size_t n, EraseFunc func)
375
{
177,186✔
376
    ensure_offsets();
177,186✔
377

378
    size_t child_ndx = m_offsets.upper_bound(n);
177,186✔
379
    size_t child_offset = get_bp_node_offset(child_ndx);
177,186✔
380
    REALM_ASSERT_3(child_ndx, <, get_node_size());
177,186✔
381

382
    // Call bptree_erase recursively and ultimately call 'func' on leaf
383
    size_t erase_node_size;
177,186✔
384
    ref_type child_ref = get_bp_node_ref(child_ndx);
177,186✔
385
    char* child_header = m_alloc.translate(child_ref);
177,186✔
386
    MemRef mem(child_header, child_ref, m_alloc);
177,186✔
387
    BPlusTreeLeaf* leaf = nullptr;
177,186✔
388
    BPlusTreeInner node(m_tree);
177,186✔
389
    bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
177,186✔
390
    if (child_is_leaf) {
177,186✔
391
        leaf = cache_leaf(mem, child_ndx, child_offset + m_my_offset);
177,186✔
392
        erase_node_size = func(leaf, n - child_offset);
177,186✔
393
        if (erase_node_size == 0) {
177,186✔
394
            m_tree->invalidate_leaf_cache();
168✔
395
        }
168✔
396
        else {
177,018✔
397
            m_tree->adjust_leaf_bounds(-1);
177,018✔
398
        }
177,018✔
399
    }
177,186✔
400
    else {
×
401
        node.set_parent(this, child_ndx + 1);
×
402
        node.init_from_mem(mem);
×
403
        node.set_offset(child_offset + m_my_offset);
×
404
        erase_node_size = node.bptree_erase(n - child_offset, func);
×
405
    }
×
406

407
    adjust(size() - 1, -2);
177,186✔
408
    m_offsets.adjust(child_ndx, m_offsets.size(), -1);
177,186✔
409

410
    // Check if some nodes could be merged
411
    size_t num_children = get_node_size();
177,186✔
412
    if (erase_node_size == 0) {
177,186✔
413
        if (num_children == 1) {
168✔
414
            // Only child empty - delete this one too
415
            return 0;
×
416
        }
×
417
        // Child leaf is empty - destroy it!
418
        erase_and_destroy_bp_node(child_ndx);
168✔
419
        return num_children - 1;
168✔
420
    }
168✔
421
    else if (erase_node_size < REALM_MAX_BPNODE_SIZE / 2 && child_ndx < (num_children - 1)) {
177,018✔
422
        // Candidate for merge. First calculate if the combined size of current and
423
        // next sibling is small enough.
424
        size_t sibling_ndx = child_ndx + 1;
30,972✔
425
        ref_type sibling_ref = get_bp_node_ref(sibling_ndx);
30,972✔
426
        std::unique_ptr<BPlusTreeLeaf> sibling_leaf;
30,972✔
427
        BPlusTreeInner node2(m_tree);
30,972✔
428
        BPlusTreeNode* sibling_node;
30,972✔
429
        if (child_is_leaf) {
30,972✔
430
            sibling_leaf = m_tree->init_leaf_node(sibling_ref);
30,972✔
431
            sibling_node = sibling_leaf.get();
30,972✔
432
        }
30,972✔
433
        else {
×
434
            node2.init_from_ref(sibling_ref);
×
435
            sibling_node = &node2;
×
436
        }
×
437
        sibling_node->bp_set_parent(this, sibling_ndx + 1);
30,972✔
438

439
        size_t combined_size = sibling_node->get_node_size() + erase_node_size;
30,972✔
440

441
        if (combined_size < REALM_MAX_BPNODE_SIZE * 3 / 4) {
30,972✔
442
            // Combined size is small enough for the nodes to be merged
443
            // Move all content from the next sibling into current node
444
            int64_t offs_adj = 0;
132✔
445
            if (child_is_leaf) {
132✔
446
                REALM_ASSERT(leaf);
132✔
447
                if (sibling_ndx < m_offsets.size())
132✔
448
                    m_offsets.set(sibling_ndx - 1, m_offsets.get(sibling_ndx));
×
449
                sibling_node->move(leaf, 0, 0);
132✔
450
                // Invalidate cached leaf
451
                m_tree->invalidate_leaf_cache();
132✔
452
            }
132✔
453
            else {
×
454
                node.ensure_offsets();
×
455
                node2.ensure_offsets();
×
456
                // Offset adjustment is negative as the sibling has bigger offsets
457
                auto sibling_offs = m_offsets.get(sibling_ndx - 1);
×
458
                auto erase_node_offs = get_bp_node_offset(child_ndx);
×
459
                offs_adj = erase_node_offs - sibling_offs;
×
460
                if (sibling_ndx < m_offsets.size())
×
461
                    m_offsets.set(sibling_ndx - 1, m_offsets.get(sibling_ndx));
×
462

463
                size_t orig_size = node.get_tree_size();
×
464
                size_t size_of_moved = node2.get_tree_size();
×
465
                // Remove size field from node as the move operation will just add
466
                node.erase(node.size() - 1);
×
467
                node2.move(&node, 0, offs_adj);
×
468
                node.append_tree_size(orig_size + size_of_moved);
×
469
            }
×
470

471
            // Destroy sibling
472
            erase_and_destroy_bp_node(sibling_ndx);
132✔
473
            num_children--;
132✔
474
        }
132✔
475
    }
30,972✔
476

477
    return num_children;
177,018✔
478
}
177,186✔
479

480
bool BPlusTreeInner::bptree_traverse(TraverseFunc func)
481
{
6,213,291✔
482
    size_t sz = get_node_size();
6,213,291✔
483
    for (size_t i = 0; i < sz; i++) {
6,753,906✔
484
        size_t child_offset;
6,744,729✔
485
        if (m_offsets.is_attached()) {
6,744,729✔
486
            child_offset = get_bp_node_offset(i);
354,024✔
487
        }
354,024✔
488
        else {
6,390,705✔
489
            auto elems_per_child = get_elems_per_child();
6,390,705✔
490
            child_offset = i * elems_per_child;
6,390,705✔
491
        }
6,390,705✔
492

493
        bool done;
6,744,729✔
494
        ref_type child_ref = get_bp_node_ref(i);
6,744,729✔
495
        char* child_header = m_alloc.translate(child_ref);
6,744,729✔
496
        MemRef mem(child_header, child_ref, m_alloc);
6,744,729✔
497
        bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
6,744,729✔
498
        if (child_is_leaf) {
6,744,729✔
499
            auto leaf = cache_leaf(mem, i, child_offset + m_my_offset);
6,744,687✔
500
            done = (func(leaf, child_offset + m_my_offset) == IteratorControl::Stop);
6,744,687✔
501
        }
6,744,687✔
502
        else {
42✔
503
            BPlusTreeInner node(m_tree);
42✔
504
            node.set_parent(this, i + 1);
42✔
505
            node.init_from_mem(mem);
42✔
506
            node.set_offset(child_offset + m_my_offset);
42✔
507
            // std::cout << "BPlusTreeInner offset: " << child_offset << std::endl;
508
            done = node.bptree_traverse(func);
42✔
509
        }
42✔
510
        if (done) {
6,744,729✔
511
            return true;
6,204,114✔
512
        }
6,204,114✔
513
    }
6,744,729✔
514
    return false;
9,177✔
515
}
6,213,291✔
516

517
void BPlusTreeInner::move(BPlusTreeNode* new_node, size_t ndx, int64_t adj)
518
{
×
519
    BPlusTreeInner* dst(static_cast<BPlusTreeInner*>(new_node));
×
520
    size_t sz = get_node_size();
×
521

522
    // Copy refs
523
    for (size_t i = ndx; i < sz; i++) {
×
524
        size_t offs = get_bp_node_offset(i);
×
525
        dst->add_bp_node_ref(get_bp_node_ref(i), offs - adj);
×
526
    }
×
527
    truncate(ndx + 1);
×
528
    if (ndx > 0)
×
529
        m_offsets.truncate(ndx - 1);
×
530
}
×
531

532
void BPlusTreeInner::ensure_offsets()
533
{
195,210✔
534
    if (!m_offsets.is_attached()) {
195,210✔
535
        size_t elems_per_child = get_elems_per_child();
216✔
536
        size_t sz = size();
216✔
537
        size_t num_offsets = (sz > 2) ? sz - 3 : 0;
216✔
538
        size_t offs = 0;
216✔
539

540
        m_offsets.create(num_offsets, num_offsets * elems_per_child);
216✔
541
        for (size_t i = 0; i != num_offsets; ++i) {
420✔
542
            offs += elems_per_child;
204✔
543
            m_offsets.set(i, offs);
204✔
544
        }
204✔
545
        Array::set_as_ref(0, m_offsets.get_ref());
216✔
546
    }
216✔
547
}
195,210✔
548

549
std::unique_ptr<BPlusTreeInner> BPlusTreeInner::split_root()
550
{
×
551
    auto new_root = std::make_unique<BPlusTreeInner>(m_tree);
×
552
    auto sz = get_node_size();
×
553
    size_t elems_per_child = get_elems_per_child();
×
554
    new_root->create(REALM_MAX_BPNODE_SIZE * elems_per_child);
×
555
    new_root->Array::set_context_flag(this->Array::get_context_flag());
×
556
    size_t ndx = 0;
×
557
    size_t tree_size = get_tree_size();
×
558
    size_t accumulated_size = 0;
×
559
    while (ndx < sz) {
×
560
        BPlusTreeInner new_inner(m_tree);
×
561
        size_t to_move = std::min(size_t(REALM_MAX_BPNODE_SIZE), sz - ndx);
×
562
        new_inner.create(elems_per_child);
×
563
        for (size_t i = 0; i < to_move; i++, ndx++) {
×
564
            new_inner.add_bp_node_ref(get_bp_node_ref(ndx));
×
565
        }
×
566
        size_t this_size = to_move * elems_per_child;
×
567
        if (accumulated_size + this_size > tree_size) {
×
568
            this_size = tree_size - accumulated_size;
×
569
        }
×
570
        accumulated_size += this_size;
×
571
        new_inner.append_tree_size(this_size);
×
572
        new_root->add_bp_node_ref(new_inner.get_ref()); // Throws
×
573
    }
×
574
    REALM_ASSERT(accumulated_size == tree_size);
×
575
    new_root->append_tree_size(tree_size);
×
576
    destroy();
×
577
    return new_root;
×
578
}
×
579

580
inline BPlusTreeLeaf* BPlusTreeInner::cache_leaf(MemRef mem, size_t ndx, size_t offset)
581
{
12,841,458✔
582
    BPlusTreeLeaf* leaf = m_tree->cache_leaf(mem);
12,841,458✔
583
    leaf->bp_set_parent(this, ndx + 1);
12,841,458✔
584
    size_t sz = leaf->get_node_size();
12,841,458✔
585
    m_tree->set_leaf_bounds(offset, offset + sz);
12,841,458✔
586

587
    return leaf;
12,841,458✔
588
}
12,841,458✔
589

590
void BPlusTreeInner::erase_and_destroy_bp_node(size_t ndx)
591
{
300✔
592
    ref_type ref = get_bp_node_ref(ndx);
300✔
593
    erase(ndx + 1);
300✔
594
    Array::destroy_deep(ref, m_tree->get_alloc());
300✔
595
    REALM_ASSERT_DEBUG(m_offsets.is_attached());
300✔
596
    size_t sz = m_offsets.size();
300✔
597
    if (sz) {
300✔
598
        // in this case there will always be an offset to erase
599
        if (ndx < sz) {
300✔
600
            m_offsets.erase(ndx);
×
601
        }
×
602
        else {
300✔
603
            m_offsets.erase(sz - 1);
300✔
604
        }
300✔
605
    }
300✔
606
    REALM_ASSERT_DEBUG(m_offsets.size() == get_node_size() - 1);
300✔
607
}
300✔
608

609
ref_type BPlusTreeInner::insert_bp_node(size_t child_ndx, ref_type new_sibling_ref, State& state)
610
{
1,044✔
611
    size_t new_ref_ndx = child_ndx + 1;
1,044✔
612

613
    size_t sz = get_node_size();
1,044✔
614
    if (sz < REALM_MAX_BPNODE_SIZE) {
1,044✔
615
        // Room in current node for the new child
616
        adjust(size() - 1, +2); // Throws
1,044✔
617
        if (m_offsets.is_attached()) {
1,044✔
618
            size_t elem_ndx_offset = get_bp_node_offset(child_ndx);
24✔
619
            m_offsets.insert(child_ndx, elem_ndx_offset + state.split_offset); // Throws
24✔
620
            m_offsets.adjust(child_ndx + 1, m_offsets.size(), +1);             // Throws
24✔
621
        }
24✔
622
        insert_bp_node_ref(new_ref_ndx, new_sibling_ref);
1,044✔
623
        return ref_type(0);
1,044✔
624
    }
1,044✔
625

626
    // This node has to be split
627
    BPlusTreeInner new_sibling(m_tree);
×
628

629
    size_t elem_ndx_offset = 0;
×
630
    if (m_offsets.is_attached()) {
×
631
        new_sibling.create(0);
×
632
        new_sibling.ensure_offsets();
×
633
        elem_ndx_offset = get_bp_node_offset(child_ndx);
×
634
    }
×
635
    else {
×
636
        size_t elems_per_child = get_elems_per_child();
×
637
        elem_ndx_offset = child_ndx * elems_per_child;
×
638
        new_sibling.create(elems_per_child);
×
639
    }
×
640

641
    size_t new_split_offset;
×
642
    size_t new_split_size;
×
643
    if (new_ref_ndx == sz) {
×
644
        // Case 1/2: The split child was the last child of the parent
645
        // to be split. In this case the parent may or may not be on
646
        // the compact form.
647
        new_split_offset = size_t(elem_ndx_offset + state.split_offset);
×
648
        new_split_size = elem_ndx_offset + state.split_size;
×
649
        new_sibling.add_bp_node_ref(new_sibling_ref); // Throws
×
650
        set_tree_size(new_split_offset);              // Throws
×
651
    }
×
652
    else {
×
653
        // Case 2/2: The split child was not the last child of the
654
        // parent to be split. Since this is not possible during
655
        // 'append', we can safely assume that the parent node is on
656
        // the general form.
657
        new_split_offset = size_t(elem_ndx_offset + state.split_size);
×
658
        new_split_size = get_tree_size() + 1;
×
659

660
        move(&new_sibling, new_ref_ndx, (new_split_offset - 1));                // Strips off tree size
×
661
        add_bp_node_ref(new_sibling_ref, elem_ndx_offset + state.split_offset); // Throws
×
662
        append_tree_size(new_split_offset);                                     // Throws
×
663
    }
×
664

665
    new_sibling.append_tree_size(new_split_size - new_split_offset); // Throws
×
666

667
    state.split_offset = new_split_offset;
×
668
    state.split_size = new_split_size;
×
669

670
    return new_sibling.get_ref();
×
671
}
1,044✔
672

673
void BPlusTreeInner::verify() const
674
{
×
675
#ifdef REALM_DEBUG
×
676
    Array::verify();
×
677

678
    // This node must not be a leaf
679
    REALM_ASSERT_3(Array::get_type(), ==, Array::type_InnerBptreeNode);
×
680

681
    REALM_ASSERT_3(Array::size(), >=, 2);
×
682
    size_t num_children = get_node_size();
×
683

684
    // Verify invar:bptree-nonempty-inner
685
    REALM_ASSERT_3(num_children, >=, 1);
×
686

687
    size_t elems_per_child = 0;
×
688
    if (m_offsets.is_attached()) {
×
689
        REALM_ASSERT_DEBUG(m_offsets.size() == num_children - 1);
×
690
    }
×
691
    else {
×
692
        elems_per_child = get_elems_per_child();
×
693
    }
×
694

695
    size_t num_elems = 0;
×
696
    for (size_t i = 0; i < num_children; i++) {
×
697
        ref_type child_ref = get_bp_node_ref(i);
×
698
        char* child_header = m_alloc.translate(child_ref);
×
699
        MemRef mem(child_header, child_ref, m_alloc);
×
700
        bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
×
701
        size_t elems_in_child;
×
702
        if (child_is_leaf) {
×
703
            auto leaf = const_cast<BPlusTreeInner*>(this)->cache_leaf(mem, i, 0);
×
704
            elems_in_child = leaf->get_node_size();
×
705
            leaf->verify();
×
706
        }
×
707
        else {
×
708
            BPlusTreeInner node(m_tree);
×
709
            node.set_parent(const_cast<BPlusTreeInner*>(this), i + 1);
×
710
            node.init_from_mem(mem);
×
711
            node.verify();
×
712
            elems_in_child = node.get_tree_size();
×
713
        }
×
714
        num_elems += elems_in_child;
×
715
        if (m_offsets.is_attached()) {
×
716
            if (i < num_children - 1) {
×
717
                REALM_ASSERT_DEBUG(num_elems == m_offsets.get(i));
×
718
            }
×
719
        }
×
720
        else {
×
721
            if (i < num_children - 1) {
×
722
                REALM_ASSERT_DEBUG(elems_in_child == elems_per_child);
×
723
            }
×
724
            else {
×
725
                REALM_ASSERT_DEBUG(elems_in_child <= elems_per_child);
×
726
            }
×
727
        }
×
728
    }
×
729
    REALM_ASSERT_DEBUG(get_tree_size() == num_elems);
×
730
    m_tree->invalidate_leaf_cache();
×
731
#endif
×
732
}
×
733

734
/****************************** BPlusTreeBase ********************************/
735

736
BPlusTreeBase::~BPlusTreeBase() {}
32,028,705✔
737

738
void BPlusTreeBase::create()
739
{
1,178,310✔
740
    if (!m_root) {                   // Idempotent
1,178,322✔
741
        m_root = create_leaf_node(); // Throws
1,178,310✔
742
        if (m_parent) {
1,178,310✔
743
            ref_type ref = get_ref();
1,114,443✔
744
            _impl::DeepArrayRefDestroyGuard destroy_guard{ref, get_alloc()};
1,114,443✔
745
            m_parent->update_child_ref(m_ndx_in_parent, ref); // Throws
1,114,443✔
746
            destroy_guard.release();
1,114,443✔
747
        }
1,114,443✔
748
        m_root->bp_set_parent(m_parent, m_ndx_in_parent);
1,178,310✔
749
    }
1,178,310✔
750
}
1,178,310✔
751

752
void BPlusTreeBase::destroy()
753
{
48,213✔
754
    if (is_attached()) {
48,213✔
755
        ref_type ref = m_root->get_ref();
48,213✔
756
        Array::destroy_deep(ref, m_alloc);
48,213✔
757
        m_root = nullptr;
48,213✔
758
    }
48,213✔
759
    invalidate_leaf_cache();
48,213✔
760
}
48,213✔
761

762
void BPlusTreeBase::replace_root(std::unique_ptr<BPlusTreeNode> new_root)
763
{
13,578✔
764
    // Maintain parent.
765
    new_root->bp_set_parent(m_parent, m_ndx_in_parent);
13,578✔
766
    new_root->update_parent(); // Throws
13,578✔
767

768
    m_root = std::move(new_root);
13,578✔
769
}
13,578✔
770

771
void BPlusTreeBase::bptree_insert(size_t n, BPlusTreeNode::InsertFunc func)
772
{
20,531,994✔
773
    size_t bptree_size = m_root->get_tree_size();
20,531,994✔
774
    if (n == bptree_size) {
20,531,994✔
775
        n = npos;
11,471,136✔
776
    }
11,471,136✔
777
    BPlusTreeNode::State state;
20,531,994✔
778
    ref_type new_sibling_ref = m_root->bptree_insert(n, state, func);
20,531,994✔
779
    if (REALM_UNLIKELY(new_sibling_ref)) {
20,531,994✔
780
        bool compact_form = (n == npos) && m_root->is_compact();
13,386✔
781
        auto new_root = std::make_unique<BPlusTreeInner>(this);
13,386✔
782
        if (!compact_form) {
13,386✔
783
            new_root->create(0);
126✔
784
            new_root->ensure_offsets();
126✔
785
        }
126✔
786
        else {
13,260✔
787
            new_root->create(size_t(state.split_offset));
13,260✔
788
        }
13,260✔
789

790
        new_root->add_bp_node_ref(m_root->get_ref());                   // Throws
13,386✔
791
        new_root->add_bp_node_ref(new_sibling_ref, state.split_offset); // Throws
13,386✔
792
        new_root->append_tree_size(state.split_size);
13,386✔
793
        replace_root(std::move(new_root));
13,386✔
794
    }
13,386✔
795
}
20,531,994✔
796

797
void BPlusTreeBase::bptree_erase(size_t n, BPlusTreeNode::EraseFunc func)
798
{
1,788,867✔
799
    size_t root_size = m_root->bptree_erase(n, func);
1,788,867✔
800
    while (!m_root->is_leaf() && root_size == 1) {
1,789,053✔
801
        BPlusTreeInner* node = static_cast<BPlusTreeInner*>(m_root.get());
186✔
802
        ref_type orig_root_ref = node->get_ref();
186✔
803
        ref_type new_root_ref = node->clear_first_bp_node_ref();
186✔
804
        auto new_root = create_root_from_ref(new_root_ref);
186✔
805

806
        replace_root(std::move(new_root));
186✔
807
        root_size = m_root->get_node_size();
186✔
808
        // destroy after replace_root for valid context flag lookup
809
        Array::destroy_deep(orig_root_ref, m_alloc);
186✔
810
    }
186✔
811
}
1,788,867✔
812

813
std::unique_ptr<BPlusTreeNode> BPlusTreeBase::create_root_from_ref(ref_type ref)
814
{
34,984,902✔
815
    char* header = m_alloc.translate(ref);
34,984,902✔
816
    bool is_leaf = !Array::get_is_inner_bptree_node_from_header(header);
34,984,902✔
817
    bool reuse_root = m_root && m_root->is_leaf() == is_leaf;
34,984,902✔
818

819
    if (reuse_root) {
34,984,902✔
820
        m_root->init_from_ref(ref);
4,181,466✔
821
        return std::move(m_root);
4,181,466✔
822
    }
4,181,466✔
823

824
    if (is_leaf) {
30,803,436✔
825
        return init_leaf_node(ref);
23,438,295✔
826
    }
23,438,295✔
827
    else {
7,365,141✔
828
        std::unique_ptr<BPlusTreeNode> new_root = std::make_unique<BPlusTreeInner>(this);
7,365,141✔
829
        new_root->init_from_ref(ref);
7,365,141✔
830
        return new_root;
7,365,141✔
831
    }
7,365,141✔
832
}
30,803,436✔
833

834
size_t BPlusTreeBase::size_from_header(const char* header)
835
{
232,326✔
836
    auto node_size = Array::get_size_from_header(header);
232,326✔
837
    if (Array::get_is_inner_bptree_node_from_header(header)) {
232,326✔
838
        auto data = Array::get_data_from_header(header);
6✔
839
        auto width = Array::get_width_from_header(header);
6✔
840
        node_size = size_t(get_direct(data, width, node_size - 1)) >> 1;
6✔
841
    }
6✔
842
    return node_size;
232,326✔
843
}
232,326✔
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