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

realm / realm-core / jorgen.edelbo_334

01 Jul 2024 07:22AM UTC coverage: 90.829% (-0.04%) from 90.865%
jorgen.edelbo_334

Pull #7803

Evergreen

jedelbo
Merge branch 'next-major' into feature/string-compression
Pull Request #7803: Feature/string compression

102912 of 180568 branches covered (56.99%)

1141 of 1267 new or added lines in 33 files covered. (90.06%)

172 existing lines in 24 files now uncovered.

218291 of 240332 relevant lines covered (90.83%)

7818396.4 hits per line

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

90.52
/src/realm/node.hpp
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
#ifndef REALM_NODE_HPP
20
#define REALM_NODE_HPP
21

22
#include <realm/node_header.hpp>
23
#include <realm/alloc.hpp>
24

25
#include <iostream>
26

27
namespace realm {
28

29
class Mixed;
30

31
/// Special index value. It has various meanings depending on
32
/// context. It is returned by some search functions to indicate 'not
33
/// found'. It is similar in function to std::string::npos.
34
const size_t npos = size_t(-1);
35

36
/// Alias for realm::npos.
37
const size_t not_found = npos;
38

39
/// All accessor classes that logically contains other objects must inherit
40
/// this class.
41
///
42
/// A database node accessor contains information about the parent of the
43
/// referenced node. This 'reverse' reference is not explicitly present in the
44
/// underlying node hierarchy, but it is needed when modifying an array. A
45
/// modification may lead to relocation of the underlying array node, and the
46
/// parent must be updated accordingly. Since this applies recursivly all the
47
/// way to the root node, it is essential that the entire chain of parent
48
/// accessors is constructed and propperly maintained when a particular array is
49
/// modified.
50
class ArrayParent {
51
public:
52
    virtual ~ArrayParent() noexcept {}
835,936,437✔
53

54
    virtual ref_type get_child_ref(size_t child_ndx) const noexcept = 0;
55
    virtual void update_child_ref(size_t child_ndx, ref_type new_ref) = 0;
56
};
57

58
/// Provides access to individual array nodes of the database.
59
///
60
/// This class serves purely as an accessor, it assumes no ownership of the
61
/// referenced memory.
62
///
63
/// An node accessor can be in one of two states: attached or unattached. It is
64
/// in the attached state if, and only if is_attached() returns true. Most
65
/// non-static member functions of this class have undefined behaviour if the
66
/// accessor is in the unattached state. The exceptions are: is_attached(),
67
/// detach(), create(), init_from_ref(), init_from_mem(), init_from_parent(),
68
/// has_parent(), get_parent(), set_parent(), get_ndx_in_parent(),
69
/// set_ndx_in_parent(), adjust_ndx_in_parent(), and get_ref_from_parent().
70
///
71
/// An node accessor contains information about the parent of the referenced
72
/// node. This 'reverse' reference is not explicitly present in the
73
/// underlying node hierarchy, but it is needed when modifying a node. A
74
/// modification may lead to relocation of the underlying node, and the
75
/// parent must be updated accordingly. Since this applies recursively all the
76
/// way to the root node, it is essential that the entire chain of parent
77
/// accessors is constructed and properly maintained when a particular node is
78
/// modified.
79
///
80
/// The parent reference (`pointer to parent`, `index in parent`) is updated
81
/// independently from the state of attachment to an underlying node. In
82
/// particular, the parent reference remains valid and is unaffected by changes
83
/// in attachment. These two aspects of the state of the accessor is updated
84
/// independently, and it is entirely the responsibility of the caller to update
85
/// them such that they are consistent with the underlying node hierarchy before
86
/// calling any method that modifies the underlying node.
87
///
88
/// FIXME: This class currently has fragments of ownership, in particular the
89
/// constructors that allocate underlying memory. On the other hand, the
90
/// destructor never frees the memory. This is a problematic situation, because
91
/// it so easily becomes an obscure source of leaks. There are three options for
92
/// a fix of which the third is most attractive but hardest to implement: (1)
93
/// Remove all traces of ownership semantics, that is, remove the constructors
94
/// that allocate memory, but keep the trivial copy constructor. For this to
95
/// work, it is important that the constness of the accessor has nothing to do
96
/// with the constness of the underlying memory, otherwise constness can be
97
/// violated simply by copying the accessor. (2) Disallov copying but associate
98
/// the constness of the accessor with the constness of the underlying
99
/// memory. (3) Provide full ownership semantics like is done for Table
100
/// accessors, and provide a proper copy constructor that really produces a copy
101
/// of the node. For this to work, the class should assume ownership if, and
102
/// only if there is no parent. A copy produced by a copy constructor will not
103
/// have a parent. Even if the original was part of a database, the copy will be
104
/// free-standing, that is, not be part of any database. For intra, or inter
105
/// database copying, one would have to also specify the target allocator.
106
class Node : public NodeHeader {
107
public:
108
    // FIXME: Should not be public
109
    char* m_data = nullptr; // Points to first byte after header
110

111
    /*********************** Constructor / destructor ************************/
112

113
    // The object will not be fully initialized when using this constructor
114
    explicit Node(Allocator& allocator) noexcept
115
        : m_alloc(allocator)
715,344,849✔
116
    {
1,442,031,447✔
117
    }
1,442,031,447✔
118

119
    virtual ~Node() = default;
1,269,576,324✔
120

121
    /**************************** Initializers *******************************/
122

123
    /// Same as init_from_ref(ref_type) but avoid the mapping of 'ref' to memory
124
    /// pointer.
125
    char* init_from_mem(MemRef mem) noexcept
126
    {
1,073,379,987✔
127
        char* header = mem.get_addr();
1,073,379,987✔
128
        REALM_ASSERT_DEBUG(!wtype_is_extended(header));
1,073,379,987✔
129
        m_ref = mem.get_ref();
1,073,379,987✔
130
        m_data = get_data_from_header(header);
1,073,379,987✔
131
        m_size = get_size_from_header(header);
1,073,379,987✔
132
        return header;
1,073,379,987✔
133
    }
1,073,379,987✔
134

135
    /************************** access functions *****************************/
136

137
    bool is_attached() const noexcept
138
    {
4,294,967,294✔
139
        return m_data != nullptr;
4,294,967,294✔
140
    }
4,294,967,294✔
141

142
    inline bool is_read_only() const noexcept
143
    {
1,391,939,343✔
144
        REALM_ASSERT_DEBUG(is_attached());
1,391,939,343✔
145
        return m_alloc.is_read_only(m_ref);
1,391,939,343✔
146
    }
1,391,939,343✔
147

148
    size_t size() const noexcept
149
    {
881,163,408✔
150
        REALM_ASSERT_DEBUG(is_attached());
881,163,408✔
151
        return m_size;
881,163,408✔
152
    }
881,163,408✔
153

154
    bool is_empty() const noexcept
155
    {
4✔
156
        return size() == 0;
4✔
157
    }
4✔
158

159
    ref_type get_ref() const noexcept
160
    {
64,161,951✔
161
        return m_ref;
64,161,951✔
162
    }
64,161,951✔
163
    MemRef get_mem() const noexcept
164
    {
528,060✔
165
        return MemRef(get_header_from_data(m_data), m_ref, m_alloc);
528,060✔
166
    }
528,060✔
167
    Allocator& get_alloc() const noexcept
168
    {
49,798,440✔
169
        return m_alloc;
49,798,440✔
170
    }
49,798,440✔
171
    /// Get the address of the header of this array.
172
    char* get_header() const noexcept
173
    {
3,017,527,704✔
174
        return get_header_from_data(m_data);
3,017,527,704✔
175
    }
3,017,527,704✔
176

177
    bool has_parent() const noexcept
178
    {
×
179
        return m_parent != nullptr;
×
180
    }
×
181
    ArrayParent* get_parent() const noexcept
182
    {
192,875,451✔
183
        return m_parent;
192,875,451✔
184
    }
192,875,451✔
185
    size_t get_ndx_in_parent() const noexcept
186
    {
104,232,249✔
187
        return m_ndx_in_parent;
104,232,249✔
188
    }
104,232,249✔
189
    bool has_missing_parent_update() const noexcept
190
    {
40,204,101✔
191
        return m_missing_parent_update;
40,204,101✔
192
    }
40,204,101✔
193

194
    /// Get the ref of this array as known to the parent. The caller must ensure
195
    /// that the parent information ('pointer to parent' and 'index in parent')
196
    /// is correct before calling this function.
197
    ref_type get_ref_from_parent() const noexcept
198
    {
161,948,337✔
199
        REALM_ASSERT_DEBUG(m_parent);
161,948,337✔
200
        ref_type ref = m_parent->get_child_ref(m_ndx_in_parent);
161,948,337✔
201
        return ref;
161,948,337✔
202
    }
161,948,337✔
203

204
    /***************************** modifiers *********************************/
205

206
    /// Detach from the underlying array node. This method has no effect if the
207
    /// accessor is currently unattached (idempotency).
208
    void detach() noexcept
209
    {
317,896,116✔
210
        m_data = nullptr;
317,896,116✔
211
    }
317,896,116✔
212

213
    /// Destroy only the array that this accessor is attached to, not the
214
    /// children of that array. See non-static destroy_deep() for an
215
    /// alternative. If this accessor is already in the detached state, this
216
    /// function has no effect (idempotency).
217
    void destroy() noexcept;
218

219
    /// Shorthand for `destroy(MemRef(ref, alloc), alloc)`.
220
    static void destroy(ref_type ref, Allocator& alloc) noexcept
221
    {
84✔
222
        destroy(MemRef(ref, alloc), alloc);
84✔
223
    }
84✔
224

225
    /// Destroy only the specified array node, not its children. See also
226
    /// destroy_deep(MemRef, Allocator&).
227
    static void destroy(MemRef mem, Allocator& alloc) noexcept
228
    {
84✔
229
        alloc.free_(mem);
84✔
230
    }
84✔
231

232
    /// Setting a new parent affects ownership of the attached array node, if
233
    /// any. If a non-null parent is specified, and there was no parent
234
    /// originally, then the caller passes ownership to the parent, and vice
235
    /// versa. This assumes, of course, that the change in parentship reflects a
236
    /// corresponding change in the list of children in the affected parents.
237
    void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept
238
    {
1,256,555,448✔
239
        m_parent = parent;
1,256,555,448✔
240
        m_ndx_in_parent = ndx_in_parent;
1,256,555,448✔
241
    }
1,256,555,448✔
242

243
    void set_ndx_in_parent(size_t ndx) noexcept
244
    {
×
245
        m_ndx_in_parent = ndx;
×
246
    }
×
247

248
    void clear_missing_parent_update()
249
    {
103,221✔
250
        m_missing_parent_update = false;
103,221✔
251
    }
103,221✔
252

253
    /// Update the parents reference to this child. This requires, of course,
254
    /// that the parent information stored in this child is up to date. If the
255
    /// parent pointer is set to null, this function has no effect.
256
    void update_parent()
257
    {
22,813,053✔
258
        if (m_parent) {
22,813,053✔
259
            m_parent->update_child_ref(m_ndx_in_parent, m_ref);
18,809,232✔
260
        }
18,809,232✔
261
        else {
4,003,821✔
262
            m_missing_parent_update = true;
4,003,821✔
263
        }
4,003,821✔
264
    }
22,813,053✔
265

266
    void typed_print(int) const
267
    {
×
268
        std::cout << "Generic Node ERROR\n";
×
269
    }
×
270

271
protected:
272
    /// The total size in bytes (including the header) of a new empty
273
    /// array. Must be a multiple of 8 (i.e., 64-bit aligned).
274
    static const size_t initial_capacity = 128;
275

276
    size_t m_ref;
277
    Allocator& m_alloc;
278
    size_t m_size = 0; // Number of elements currently stored.
279

280
#if REALM_ENABLE_MEMDEBUG
281
    // If m_no_relocation is false, then copy_on_write() will always relocate this array, regardless if it's
282
    // required or not. If it's true, then it will never relocate, which is currently only expeted inside
283
    // GroupWriter::write_group() due to a unique chicken/egg problem (see description there).
284
    bool m_no_relocation = false;
285
#endif
286

287
    void alloc(size_t init_size, size_t new_width);
288
    void copy_on_write()
289
    {
109,765,344✔
290
#if REALM_ENABLE_MEMDEBUG
291
        // We want to relocate this array regardless if there is a need or not, in order to catch use-after-free bugs.
292
        // Only exception is inside GroupWriter::write_group() (see explanation at the definition of the
293
        // m_no_relocation
294
        // member)
295
        if (!m_no_relocation) {
296
#else
297
        if (is_read_only()) {
109,765,344✔
298
#endif
8,491,662✔
299
            do_copy_on_write();
8,491,662✔
300
        }
8,491,662✔
301
    }
109,765,344✔
302
    void copy_on_write(size_t min_size)
303
    {
222,390✔
304
#if REALM_ENABLE_MEMDEBUG
305
        // We want to relocate this array regardless if there is a need or not, in order to catch use-after-free bugs.
306
        // Only exception is inside GroupWriter::write_group() (see explanation at the definition of the
307
        // m_no_relocation
308
        // member)
309
        if (!m_no_relocation) {
310
#else
311
        if (is_read_only()) {
222,393✔
312
#endif
222,393✔
313
            do_copy_on_write(min_size);
222,393✔
314
        }
222,393✔
315
    }
222,390✔
316
    void ensure_size(size_t min_size)
317
    {
103,221✔
318
        char* header = get_header_from_data(m_data);
103,221✔
319
        size_t orig_capacity_bytes = get_capacity_from_header(header);
103,221✔
320
        if (orig_capacity_bytes < min_size) {
103,221✔
321
            do_copy_on_write(min_size);
762✔
322
        }
762✔
323
    }
103,221✔
324

325
    static MemRef create_node(size_t size, Allocator& alloc, bool context_flag = false, Type type = type_Normal,
326
                              WidthType width_type = wtype_Ignore, uint8_t width = 1);
327

328
    void set_header_size(size_t value) noexcept
329
    {
17,301,408✔
330
        set_size_in_header(value, get_header());
17,301,408✔
331
    }
17,301,408✔
332

333
    // Includes array header. Not necessarily 8-byte aligned.
334
    virtual size_t calc_byte_len(size_t num_items, size_t width) const;
335
    virtual size_t calc_item_count(size_t bytes, size_t width) const noexcept;
336

337
private:
338
    friend class NodeTree;
339
    ArrayParent* m_parent = nullptr;
340
    size_t m_ndx_in_parent = 0; // Ignored if m_parent is null.
341
    bool m_missing_parent_update = false;
342

343
    void do_copy_on_write(size_t minimum_size = 0);
344
};
345

346
class Spec;
347
class Mixed;
348

349
namespace _impl {
350
class ArrayWriterBase;
351
}
352

353
/// Base class for all nodes holding user data
354
class StringInterner;
355
class ArrayPayload {
356
public:
357
    virtual ~ArrayPayload();
358
    virtual void init_from_ref(ref_type) noexcept = 0;
359
    virtual void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept = 0;
360
    virtual Mixed get_any(size_t ndx) const = 0;
361
    virtual bool need_string_interner() const
362
    {
7,795,056✔
363
        return false;
7,795,056✔
364
    }
7,795,056✔
NEW
365
    virtual void set_string_interner(StringInterner*) const {}
×
366
    virtual bool need_spec() const
367
    {
8,312,388✔
368
        return false;
8,312,388✔
369
    }
8,312,388✔
370
    virtual void set_spec(Spec*, size_t) const {}
×
371
    static ref_type typed_write(ref_type ref, _impl::ArrayWriterBase& out, Allocator& alloc);
372
};
373

374
} // namespace realm
375

376
#endif /* REALM_NODE_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