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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

93.58
/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 {}
868,468,236✔
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)
786,918,513✔
116
    {
1,583,325,003✔
117
    }
1,583,325,003✔
118

119
    virtual ~Node() = default;
1,305,131,235✔
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,134,875,973✔
127
        char* header = mem.get_addr();
1,134,875,973✔
128
        REALM_ASSERT_DEBUG(!wtype_is_extended(header));
1,134,875,973✔
129
        m_ref = mem.get_ref();
1,134,875,973✔
130
        m_data = get_data_from_header(header);
1,134,875,973✔
131
        m_size = get_size_from_header(header);
1,134,875,973✔
132
        return header;
1,134,875,973✔
133
    }
1,134,875,973✔
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,411,437,024✔
144
        REALM_ASSERT_DEBUG(is_attached());
1,411,437,024✔
145
        return m_alloc.is_read_only(m_ref);
1,411,437,024✔
146
    }
1,411,437,024✔
147

148
    size_t size() const noexcept
149
    {
1,004,570,355✔
150
        REALM_ASSERT_DEBUG(is_attached());
1,004,570,355✔
151
        return m_size;
1,004,570,355✔
152
    }
1,004,570,355✔
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
    {
65,271,618✔
161
        return m_ref;
65,271,618✔
162
    }
65,271,618✔
163
    MemRef get_mem() const noexcept
164
    {
540,699✔
165
        return MemRef(get_header_from_data(m_data), m_ref, m_alloc);
540,699✔
166
    }
540,699✔
167
    Allocator& get_alloc() const noexcept
168
    {
46,945,185✔
169
        return m_alloc;
46,945,185✔
170
    }
46,945,185✔
171
    /// Get the address of the header of this array.
172
    char* get_header() const noexcept
173
    {
3,101,337,984✔
174
        return get_header_from_data(m_data);
3,101,337,984✔
175
    }
3,101,337,984✔
176

177
    bool has_parent() const noexcept
178
    {
×
179
        return m_parent != nullptr;
×
180
    }
×
181
    ArrayParent* get_parent() const noexcept
182
    {
245,636,976✔
183
        return m_parent;
245,636,976✔
184
    }
245,636,976✔
185
    size_t get_ndx_in_parent() const noexcept
186
    {
158,575,431✔
187
        return m_ndx_in_parent;
158,575,431✔
188
    }
158,575,431✔
189
    bool has_missing_parent_update() const noexcept
190
    {
39,283,449✔
191
        return m_missing_parent_update;
39,283,449✔
192
    }
39,283,449✔
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
    {
162,613,128✔
199
        REALM_ASSERT_DEBUG(m_parent);
162,613,128✔
200
        ref_type ref = m_parent->get_child_ref(m_ndx_in_parent);
162,613,128✔
201
        return ref;
162,613,128✔
202
    }
162,613,128✔
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
    {
334,833,882✔
210
        m_data = nullptr;
334,833,882✔
211
    }
334,833,882✔
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
    {
90✔
222
        destroy(MemRef(ref, alloc), alloc);
90✔
223
    }
90✔
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
    {
90✔
229
        alloc.free_(mem);
90✔
230
    }
90✔
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,326,053,847✔
239
        m_parent = parent;
1,326,053,847✔
240
        m_ndx_in_parent = ndx_in_parent;
1,326,053,847✔
241
    }
1,326,053,847✔
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
    {
100,353✔
250
        m_missing_parent_update = false;
100,353✔
251
    }
100,353✔
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
    {
24,036,555✔
258
        if (m_parent) {
24,036,555✔
259
            m_parent->update_child_ref(m_ndx_in_parent, m_ref);
19,994,385✔
260
        }
19,994,385✔
261
        else {
4,042,170✔
262
            m_missing_parent_update = true;
4,042,170✔
263
        }
4,042,170✔
264
    }
24,036,555✔
265

266
protected:
267
    /// The total size in bytes (including the header) of a new empty
268
    /// array. Must be a multiple of 8 (i.e., 64-bit aligned).
269
    static const size_t initial_capacity = 128;
270

271
    size_t m_ref;
272
    Allocator& m_alloc;
273
    size_t m_size = 0; // Number of elements currently stored.
274

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

282
    void alloc(size_t init_size, size_t new_width);
283
    void copy_on_write()
284
    {
110,955,840✔
285
#if REALM_ENABLE_MEMDEBUG
286
        // We want to relocate this array regardless if there is a need or not, in order to catch use-after-free bugs.
287
        // Only exception is inside GroupWriter::write_group() (see explanation at the definition of the
288
        // m_no_relocation
289
        // member)
290
        if (!m_no_relocation) {
291
#else
292
        if (is_read_only()) {
110,955,840✔
293
#endif
8,618,241✔
294
            do_copy_on_write();
8,618,241✔
295
        }
8,618,241✔
296
    }
110,955,840✔
297
    void copy_on_write(size_t min_size)
298
    {
227,721✔
299
#if REALM_ENABLE_MEMDEBUG
300
        // We want to relocate this array regardless if there is a need or not, in order to catch use-after-free bugs.
301
        // Only exception is inside GroupWriter::write_group() (see explanation at the definition of the
302
        // m_no_relocation
303
        // member)
304
        if (!m_no_relocation) {
305
#else
306
        if (is_read_only()) {
227,724✔
307
#endif
227,724✔
308
            do_copy_on_write(min_size);
227,724✔
309
        }
227,724✔
310
    }
227,721✔
311
    void ensure_size(size_t min_size)
312
    {
100,353✔
313
        char* header = get_header_from_data(m_data);
100,353✔
314
        size_t orig_capacity_bytes = get_capacity_from_header(header);
100,353✔
315
        if (orig_capacity_bytes < min_size) {
100,353✔
316
            do_copy_on_write(min_size);
762✔
317
        }
762✔
318
    }
100,353✔
319

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

323
    void set_header_size(size_t value) noexcept
324
    {
19,353,912✔
325
        set_size_in_header(value, get_header());
19,353,912✔
326
    }
19,353,912✔
327

328
    // Includes array header. Not necessarily 8-byte aligned.
329
    virtual size_t calc_byte_len(size_t num_items, size_t width) const;
330
    virtual size_t calc_item_count(size_t bytes, size_t width) const noexcept;
331

332
private:
333
    friend class NodeTree;
334
    ArrayParent* m_parent = nullptr;
335
    size_t m_ndx_in_parent = 0; // Ignored if m_parent is null.
336
    bool m_missing_parent_update = false;
337

338
    void do_copy_on_write(size_t minimum_size = 0);
339
};
340

341
class Spec;
342
class Mixed;
343

344
namespace _impl {
345
class ArrayWriterBase;
346
}
347

348
/// Base class for all nodes holding user data
349
class StringInterner;
350
class ArrayPayload {
351
public:
352
    virtual ~ArrayPayload();
353
    virtual void init_from_ref(ref_type) noexcept = 0;
354
    virtual void set_parent(ArrayParent* parent, size_t ndx_in_parent) noexcept = 0;
355
    virtual Mixed get_any(size_t ndx) const = 0;
356
    virtual bool need_string_interner() const
357
    {
8,055,744✔
358
        return false;
8,055,744✔
359
    }
8,055,744✔
NEW
360
    virtual void set_string_interner(StringInterner*) const {}
×
361
    static ref_type typed_write(ref_type ref, _impl::ArrayWriterBase& out, Allocator& alloc);
362
};
363

364
} // namespace realm
365

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