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

realm / realm-core / 2109

07 Mar 2024 01:56PM UTC coverage: 90.918% (+0.01%) from 90.908%
2109

push

Evergreen

web-flow
Fix querying with a path into nested collections with wildcards (#7404)

Comparing a collection with a list could fail if there was wildcards
in the path and therefore multiple collections to compare with right
hand list.

Linklist is implicitly having wildcard in the path, so if linklists is
in the path there will be a similar problem.  Do not merge values
from different objects into a common list in queries.

93972 of 173176 branches covered (54.26%)

323 of 332 new or added lines in 6 files covered. (97.29%)

91 existing lines in 18 files now uncovered.

238503 of 262328 relevant lines covered (90.92%)

6065347.74 hits per line

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

74.28
/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,680,751✔
30
    auto ref = get_ref();
6,680,751✔
31
    MemRef mem(ref, m_tree->get_alloc());
6,680,751✔
32
    return Array::get_context_flag_from_header(mem.get_addr());
6,680,751✔
33
}
6,680,751✔
34

35
void BPlusTreeNode::set_context_flag(bool cf) noexcept
36
{
43,827✔
37
    auto ref = get_ref();
43,827✔
38
    MemRef mem(ref, m_tree->get_alloc());
43,827✔
39
    if (Array::get_context_flag_from_header(mem.get_addr()) != cf) {
43,827✔
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
}
43,827✔
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
    {
451,665✔
68
        return false;
451,665✔
69
    }
451,665✔
70
    bool is_compact() const override
71
    {
×
72
        return (Array::get(0) & 1) != 0;
×
73
    }
×
74
    ref_type get_ref() const override
75
    {
72,576✔
76
        return Array::get_ref();
72,576✔
77
    }
72,576✔
78

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

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

94
    size_t get_node_size() const override
95
    {
8,289,285✔
96
        return Array::size() - 2;
8,289,285✔
97
    }
8,289,285✔
98
    size_t get_tree_size() const override
99
    {
8,997,969✔
100
        return size_t(back()) >> 1;
8,997,969✔
101
    }
8,997,969✔
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,332✔
113
        Array::add((sz << 1) + 1);
13,332✔
114
    }
13,332✔
115
    void add_bp_node_ref(ref_type ref, int64_t offset = 0)
116
    {
26,712✔
117
        Array::add(from_ref(ref));
26,712✔
118
        REALM_ASSERT_DEBUG(offset >= 0);
26,712✔
119
        if (offset && m_offsets.is_attached()) {
26,712✔
120
            m_offsets.add(offset);
126✔
121
        }
126✔
122
    }
26,712✔
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

93✔
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,131,763✔
151
        // Only relevant when in compact form
6,071,757✔
152
        REALM_ASSERT_DEBUG(!m_offsets.is_attached());
12,131,763✔
153
        return size_t(Array::get(0)) >> 1;
12,131,763✔
154
    }
12,131,763✔
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,868,386✔
158
        return Array::get_as_ref(ndx + 1);
12,868,386✔
159
    }
12,868,386✔
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,389✔
169
        return (child_ndx) > 0 ? size_t(m_offsets.get(child_ndx - 1)) : 0;
604,245✔
170
    }
706,389✔
171
};
172
}
173

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

176
BPlusTreeNode::~BPlusTreeNode()
177
{
59,470,350✔
178
}
59,470,350✔
179

180
/****************************** BPlusTreeLeaf ********************************/
181

182
ref_type BPlusTreeLeaf::bptree_insert(size_t ndx, State& state, InsertFunc func)
183
{
19,020,261✔
184
    size_t leaf_size = get_node_size();
19,020,261✔
185
    REALM_ASSERT_DEBUG(leaf_size <= REALM_MAX_BPNODE_SIZE);
19,020,261✔
186
    if (ndx > leaf_size)
19,020,261✔
187
        ndx = leaf_size;
18,882,546✔
188
    if (REALM_LIKELY(leaf_size < REALM_MAX_BPNODE_SIZE)) {
19,020,261✔
189
        func(this, ndx);
19,000,356✔
190
        m_tree->adjust_leaf_bounds(1);
19,000,356✔
191
        return 0; // Leaf was not split
19,000,356✔
192
    }
19,000,356✔
193

6,195✔
194
    // Split leaf node
6,195✔
195
    auto new_leaf = m_tree->create_leaf_node();
19,905✔
196
    if (ndx == leaf_size) {
20,820✔
197
        func(new_leaf.get(), 0);
14,220✔
198
        state.split_offset = ndx;
14,220✔
199
    }
14,220✔
200
    else {
2,147,490,247✔
201
        move(new_leaf.get(), ndx, 0);
2,147,490,247✔
202
        func(this, ndx);
2,147,490,247✔
203
        state.split_offset = ndx + 1;
2,147,490,247✔
204
        // Invalidate cached leaf
2,147,483,647✔
205
        m_tree->invalidate_leaf_cache();
2,147,490,247✔
206
    }
2,147,490,247✔
207
    state.split_size = leaf_size + 1;
19,905✔
208

6,195✔
209
    return new_leaf->get_ref();
19,905✔
210
}
19,905✔
211

212
void BPlusTreeLeaf::bptree_access(size_t ndx, AccessFunc func)
213
{
44,937,273✔
214
    func(this, ndx);
44,937,273✔
215
}
44,937,273✔
216

217
size_t BPlusTreeLeaf::bptree_erase(size_t ndx, EraseFunc func)
218
{
1,427,811✔
219
    return func(this, ndx);
1,427,811✔
220
}
1,427,811✔
221

222
bool BPlusTreeLeaf::bptree_traverse(TraverseFunc func)
223
{
1,617,183✔
224
    return func(this, 0) == IteratorControl::Stop;
1,617,183✔
225
}
1,617,183✔
226

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

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

3✔
237
        new_root->create(REALM_MAX_BPNODE_SIZE);
6✔
238

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

260
/****************************** BPlusTreeInner *******************************/
261

262
BPlusTreeInner::BPlusTreeInner(BPlusTreeBase* tree)
263
    : BPlusTreeNode(tree)
264
    , Array(tree->get_alloc())
265
    , m_offsets(tree->get_alloc())
266
    , m_my_offset(0)
267
{
7,545,243✔
268
    m_offsets.set_parent(this, 0);
7,545,243✔
269
}
7,545,243✔
270

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

278
BPlusTreeInner::~BPlusTreeInner()
279
{
7,546,800✔
280
}
7,546,800✔
281

282
void BPlusTreeInner::init_from_mem(MemRef mem)
283
{
7,599,345✔
284
    Array::init_from_mem(mem);
7,599,345✔
285
    auto rot = Array::get(0);
7,599,345✔
286
    if ((rot & 1) == 0) {
7,599,345✔
287
        // rot is a ref
58,554✔
288
        m_offsets.init_from_ref(to_ref(rot));
117,108✔
289
    }
117,108✔
290
    else {
7,482,237✔
291
        m_offsets.detach();
7,482,237✔
292
    }
7,482,237✔
293
}
7,599,345✔
294

295
void BPlusTreeInner::bptree_access(size_t n, AccessFunc func)
296
{
4,513,719✔
297
    size_t child_ndx;
4,513,719✔
298
    size_t child_offset;
4,513,719✔
299
    if (m_offsets.is_attached()) {
4,513,719✔
300
        child_ndx = m_offsets.upper_bound(n);
157,263✔
301
        child_offset = get_bp_node_offset(child_ndx);
157,263✔
302
        REALM_ASSERT_3(child_ndx, <, get_node_size());
157,263✔
303
    }
157,263✔
304
    else {
4,356,456✔
305
        auto elems_per_child = get_elems_per_child();
4,356,456✔
306
        child_ndx = n / elems_per_child;
4,356,456✔
307
        child_offset = child_ndx * elems_per_child;
4,356,456✔
308
    }
4,356,456✔
309

2,263,395✔
310
    ref_type child_ref = get_bp_node_ref(child_ndx);
4,513,719✔
311
    char* child_header = m_alloc.translate(child_ref);
4,513,719✔
312
    MemRef mem(child_header, child_ref, m_alloc);
4,513,719✔
313
    bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
4,513,719✔
314
    if (child_is_leaf) {
4,513,719✔
315
        auto leaf = cache_leaf(mem, child_ndx, child_offset + m_my_offset);
4,513,644✔
316
        func(leaf, n - child_offset);
4,513,644✔
317
    }
4,513,644✔
318
    else {
75✔
319
        BPlusTreeInner node(m_tree);
75✔
320
        node.set_parent(this, child_ndx + 1);
75✔
321
        node.init_from_mem(mem);
75✔
322
        node.set_offset(child_offset + m_my_offset);
75✔
323
        node.bptree_access(n - child_offset, func);
75✔
324
    }
75✔
325
}
4,513,719✔
326

327
ref_type BPlusTreeInner::bptree_insert(size_t ndx, State& state, InsertFunc func)
328
{
1,399,053✔
329
    size_t child_ndx;
1,399,053✔
330
    size_t child_offset;
1,399,053✔
331
    if (ndx != npos) {
1,399,053✔
332
        ensure_offsets();
17,898✔
333
        child_ndx = m_offsets.upper_bound(ndx);
17,898✔
334
        child_offset = get_bp_node_offset(child_ndx);
17,898✔
335
        ndx -= child_offset;
17,898✔
336
        REALM_ASSERT_3(child_ndx, <, get_node_size());
17,898✔
337
    }
17,898✔
338
    else {
1,381,155✔
339
        child_ndx = get_node_size() - 1;
1,381,155✔
340
        if (m_offsets.is_attached()) {
1,381,155✔
341
            child_offset = get_bp_node_offset(child_ndx);
×
342
            REALM_ASSERT_3(child_ndx, <, get_node_size());
×
343
        }
×
344
        else {
1,381,155✔
345
            auto elems_per_child = get_elems_per_child();
1,381,155✔
346
            child_offset = child_ndx * elems_per_child;
1,381,155✔
347
        }
1,381,155✔
348
    }
1,381,155✔
349

698,703✔
350
    ref_type child_ref = get_bp_node_ref(child_ndx);
1,399,053✔
351
    char* child_header = m_alloc.translate(child_ref);
1,399,053✔
352
    MemRef mem(child_header, child_ref, m_alloc);
1,399,053✔
353
    bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
1,399,053✔
354
    ref_type new_sibling_ref;
1,399,053✔
355
    if (child_is_leaf) {
1,399,053✔
356
        auto leaf = cache_leaf(mem, child_ndx, child_offset + m_my_offset);
1,398,906✔
357
        new_sibling_ref = leaf->bptree_insert(ndx, state, func);
1,398,906✔
358
    }
1,398,906✔
359
    else {
147✔
360
        BPlusTreeInner node(m_tree);
147✔
361
        node.set_parent(this, child_ndx + 1);
147✔
362
        node.init_from_mem(mem);
147✔
363
        node.set_offset(child_offset + m_my_offset);
147✔
364
        new_sibling_ref = node.bptree_insert(ndx, state, func);
147✔
365
    }
147✔
366

698,703✔
367
    if (!new_sibling_ref) {
1,399,053✔
368
        adjust(size() - 1, +2); // Throws
1,397,829✔
369
        if (m_offsets.is_attached()) {
1,397,829✔
370
            m_offsets.adjust(child_ndx, m_offsets.size(), 1);
17,874✔
371
        }
17,874✔
372
        return 0;
1,397,829✔
373
    }
1,397,829✔
374

699✔
375
    return insert_bp_node(child_ndx, new_sibling_ref, state);
1,224✔
376
}
1,224✔
377

378
size_t BPlusTreeInner::bptree_erase(size_t n, EraseFunc func)
379
{
177,186✔
380
    ensure_offsets();
177,186✔
381

88,593✔
382
    size_t child_ndx = m_offsets.upper_bound(n);
177,186✔
383
    size_t child_offset = get_bp_node_offset(child_ndx);
177,186✔
384
    REALM_ASSERT_3(child_ndx, <, get_node_size());
177,186✔
385

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

88,593✔
411
    adjust(size() - 1, -2);
177,186✔
412
    m_offsets.adjust(child_ndx, m_offsets.size(), -1);
177,186✔
413

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

15,486✔
443
        size_t combined_size = sibling_node->get_node_size() + erase_node_size;
30,972✔
444

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

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

66✔
475
            // Destroy sibling
66✔
476
            erase_and_destroy_bp_node(sibling_ndx);
132✔
477
            num_children--;
132✔
478
        }
132✔
479
    }
30,972✔
480

88,593✔
481
    return num_children;
177,102✔
482
}
177,186✔
483

484
bool BPlusTreeInner::bptree_traverse(TraverseFunc func)
485
{
6,216,189✔
486
    size_t sz = get_node_size();
6,216,189✔
487
    for (size_t i = 0; i < sz; i++) {
6,762,339✔
488
        size_t child_offset;
6,750,171✔
489
        if (m_offsets.is_attached()) {
6,750,171✔
490
            child_offset = get_bp_node_offset(i);
354,024✔
491
        }
354,024✔
492
        else {
6,396,147✔
493
            auto elems_per_child = get_elems_per_child();
6,396,147✔
494
            child_offset = i * elems_per_child;
6,396,147✔
495
        }
6,396,147✔
496

3,375,405✔
497
        bool done;
6,750,171✔
498
        ref_type child_ref = get_bp_node_ref(i);
6,750,171✔
499
        char* child_header = m_alloc.translate(child_ref);
6,750,171✔
500
        MemRef mem(child_header, child_ref, m_alloc);
6,750,171✔
501
        bool child_is_leaf = !Array::get_is_inner_bptree_node_from_header(child_header);
6,750,171✔
502
        if (child_is_leaf) {
6,750,171✔
503
            auto leaf = cache_leaf(mem, i, child_offset + m_my_offset);
6,749,961✔
504
            done = (func(leaf, child_offset + m_my_offset) == IteratorControl::Stop);
6,749,961✔
505
        }
6,749,961✔
506
        else {
210✔
507
            BPlusTreeInner node(m_tree);
210✔
508
            node.set_parent(this, i + 1);
210✔
509
            node.init_from_mem(mem);
210✔
510
            node.set_offset(child_offset + m_my_offset);
210✔
511
            // std::cout << "BPlusTreeInner offset: " << child_offset << std::endl;
512
            done = node.bptree_traverse(func);
210✔
513
        }
210✔
514
        if (done) {
6,750,171✔
515
            return true;
6,204,021✔
516
        }
6,204,021✔
517
    }
6,750,171✔
518
    return false;
3,114,225✔
519
}
6,216,189✔
520

521
void BPlusTreeInner::move(BPlusTreeNode* new_node, size_t ndx, int64_t adj)
522
{
×
523
    BPlusTreeInner* dst(static_cast<BPlusTreeInner*>(new_node));
×
524
    size_t sz = get_node_size();
×
525

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

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

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

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

584
inline BPlusTreeLeaf* BPlusTreeInner::cache_leaf(MemRef mem, size_t ndx, size_t offset)
585
{
12,835,554✔
586
    BPlusTreeLeaf* leaf = m_tree->cache_leaf(mem);
12,835,554✔
587
    leaf->bp_set_parent(this, ndx + 1);
12,835,554✔
588
    size_t sz = leaf->get_node_size();
12,835,554✔
589
    m_tree->set_leaf_bounds(offset, offset + sz);
12,835,554✔
590

6,424,398✔
591
    return leaf;
12,835,554✔
592
}
12,835,554✔
593

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

613
ref_type BPlusTreeInner::insert_bp_node(size_t child_ndx, ref_type new_sibling_ref, State& state)
614
{
1,044✔
615
    size_t new_ref_ndx = child_ndx + 1;
1,044✔
616

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

630
    // This node has to be split
631
    BPlusTreeInner new_sibling(m_tree);
×
632

633
    size_t elem_ndx_offset = 0;
×
634
    if (m_offsets.is_attached()) {
×
635
        new_sibling.create(0);
×
636
        new_sibling.ensure_offsets();
×
637
        elem_ndx_offset = get_bp_node_offset(child_ndx);
×
638
    }
×
639
    else {
×
640
        size_t elems_per_child = get_elems_per_child();
×
641
        elem_ndx_offset = child_ndx * elems_per_child;
×
642
        new_sibling.create(elems_per_child);
×
643
    }
×
644

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

664
        move(&new_sibling, new_ref_ndx, (new_split_offset - 1));               // Strips off tree size
×
665
        add_bp_node_ref(new_sibling_ref, elem_ndx_offset + state.split_offset); // Throws
×
666
        append_tree_size(new_split_offset);                                    // Throws
×
667
    }
×
668

669
    new_sibling.append_tree_size(new_split_size - new_split_offset); // Throws
×
670

671
    state.split_offset = new_split_offset;
×
672
    state.split_size = new_split_size;
×
673

674
    return new_sibling.get_ref();
×
675
}
×
676

677
void BPlusTreeInner::verify() const
678
{
×
679
#ifdef REALM_DEBUG
×
680
    Array::verify();
×
681

682
    // This node must not be a leaf
683
    REALM_ASSERT_3(Array::get_type(), ==, Array::type_InnerBptreeNode);
×
684

685
    REALM_ASSERT_3(Array::size(), >=, 2);
×
686
    size_t num_children = get_node_size();
×
687

688
    // Verify invar:bptree-nonempty-inner
689
    REALM_ASSERT_3(num_children, >=, 1);
×
690

691
    size_t elems_per_child = 0;
×
692
    if (m_offsets.is_attached()) {
×
693
        REALM_ASSERT_DEBUG(m_offsets.size() == num_children - 1);
×
694
    }
×
695
    else {
×
696
        elems_per_child = get_elems_per_child();
×
697
    }
×
698

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

738
/****************************** BPlusTreeBase ********************************/
739

740
BPlusTreeBase::~BPlusTreeBase()
741
{
29,707,890✔
742
}
29,707,890✔
743

744
void BPlusTreeBase::create()
745
{
645,297✔
746
    if (!m_root) {                   // Idempotent
645,303✔
747
        m_root = create_leaf_node(); // Throws
645,303✔
748
        if (m_parent) {
645,303✔
749
            ref_type ref = get_ref();
623,499✔
750
            _impl::DeepArrayRefDestroyGuard destroy_guard{ref, get_alloc()};
623,499✔
751
            m_parent->update_child_ref(m_ndx_in_parent, ref); // Throws
623,499✔
752
            destroy_guard.release();
623,499✔
753
        }
623,499✔
754
        m_root->bp_set_parent(m_parent, m_ndx_in_parent);
645,303✔
755
    }
645,303✔
756
}
645,297✔
757

758
void BPlusTreeBase::destroy()
759
{
5,877✔
760
    if (is_attached()) {
5,877✔
761
        ref_type ref = m_root->get_ref();
5,877✔
762
        Array::destroy_deep(ref, m_alloc);
5,877✔
763
        m_root = nullptr;
5,877✔
764
    }
5,877✔
765
    invalidate_leaf_cache();
5,877✔
766
}
5,877✔
767

768
void BPlusTreeBase::replace_root(std::unique_ptr<BPlusTreeNode> new_root)
769
{
13,518✔
770
    // Maintain parent.
6,759✔
771
    new_root->bp_set_parent(m_parent, m_ndx_in_parent);
13,518✔
772
    new_root->update_parent(); // Throws
13,518✔
773

6,759✔
774
    m_root = std::move(new_root);
13,518✔
775
}
13,518✔
776

777
void BPlusTreeBase::bptree_insert(size_t n, BPlusTreeNode::InsertFunc func)
778
{
19,034,727✔
779
    size_t bptree_size = m_root->get_tree_size();
19,034,727✔
780
    if (n == bptree_size) {
19,034,727✔
781
        n = npos;
10,310,358✔
782
    }
10,310,358✔
783
    BPlusTreeNode::State state;
19,034,727✔
784
    ref_type new_sibling_ref = m_root->bptree_insert(n, state, func);
19,034,727✔
785
    if (REALM_UNLIKELY(new_sibling_ref)) {
19,034,727✔
786
        bool compact_form = (n == npos) && m_root->is_compact();
13,326✔
787
        auto new_root = std::make_unique<BPlusTreeInner>(this);
13,326✔
788
        if (!compact_form) {
13,326✔
789
            new_root->create(0);
126✔
790
            new_root->ensure_offsets();
126✔
791
        }
126✔
792
        else {
13,200✔
793
            new_root->create(size_t(state.split_offset));
13,200✔
794
        }
13,200✔
795

6,663✔
796
        new_root->add_bp_node_ref(m_root->get_ref());                   // Throws
13,326✔
797
        new_root->add_bp_node_ref(new_sibling_ref, state.split_offset); // Throws
13,326✔
798
        new_root->append_tree_size(state.split_size);
13,326✔
799
        replace_root(std::move(new_root));
13,326✔
800
    }
13,326✔
801
}
19,034,727✔
802

803
void BPlusTreeBase::bptree_erase(size_t n, BPlusTreeNode::EraseFunc func)
804
{
1,604,970✔
805
    size_t root_size = m_root->bptree_erase(n, func);
1,604,970✔
806
    while (!m_root->is_leaf() && root_size == 1) {
1,605,156✔
807
        BPlusTreeInner* node = static_cast<BPlusTreeInner*>(m_root.get());
186✔
808
        ref_type orig_root_ref = node->get_ref();
186✔
809
        ref_type new_root_ref = node->clear_first_bp_node_ref();
186✔
810
        auto new_root = create_root_from_ref(new_root_ref);
186✔
811

93✔
812
        replace_root(std::move(new_root));
186✔
813
        root_size = m_root->get_node_size();
186✔
814
        // destroy after replace_root for valid context flag lookup
93✔
815
        Array::destroy_deep(orig_root_ref, m_alloc);
186✔
816
    }
186✔
817
}
1,604,970✔
818

819
std::unique_ptr<BPlusTreeNode> BPlusTreeBase::create_root_from_ref(ref_type ref)
820
{
32,763,345✔
821
    char* header = m_alloc.translate(ref);
32,763,345✔
822
    bool is_leaf = !Array::get_is_inner_bptree_node_from_header(header);
32,763,345✔
823
    bool reuse_root = m_root && m_root->is_leaf() == is_leaf;
32,763,345✔
824

16,508,616✔
825
    if (reuse_root) {
32,763,345✔
826
        m_root->init_from_ref(ref);
3,724,986✔
827
        return std::move(m_root);
3,724,986✔
828
    }
3,724,986✔
829

14,527,998✔
830
    if (is_leaf) {
29,038,359✔
831
        return init_leaf_node(ref);
21,726,570✔
832
    }
21,726,570✔
833
    else {
7,311,789✔
834
        std::unique_ptr<BPlusTreeNode> new_root = std::make_unique<BPlusTreeInner>(this);
7,311,789✔
835
        new_root->init_from_ref(ref);
7,311,789✔
836
        return new_root;
7,311,789✔
837
    }
7,311,789✔
838
}
29,038,359✔
839

840
size_t BPlusTreeBase::size_from_header(const char* header)
841
{
22,290✔
842
    auto node_size = Array::get_size_from_header(header);
22,290✔
843
    if (Array::get_is_inner_bptree_node_from_header(header)) {
22,290✔
844
        auto data = Array::get_data_from_header(header);
6✔
845
        auto width = Array::get_width_from_header(header);
6✔
846
        node_size = size_t(get_direct(data, width, node_size - 1)) >> 1;
6✔
847
    }
6✔
848
    return node_size;
22,290✔
849
}
22,290✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc