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

realm / realm-core / jorgen.edelbo_322

20 Jun 2024 09:43AM UTC coverage: 84.879% (-6.1%) from 90.966%
jorgen.edelbo_322

Pull #7826

Evergreen

jedelbo
remove set_direct methods from integer compressors
Pull Request #7826: Merge Next major

66056 of 81292 branches covered (81.26%)

3131 of 3738 new or added lines in 54 files covered. (83.76%)

566 existing lines in 29 files now uncovered.

84791 of 99896 relevant lines covered (84.88%)

15351931.14 hits per line

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

88.95
/src/realm/group.hpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 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_GROUP_HPP
20
#define REALM_GROUP_HPP
21

22
#include <functional>
23
#include <map>
24
#include <optional>
25
#include <stdexcept>
26
#include <string>
27
#include <set>
28
#include <chrono>
29

30
#include <realm/alloc_slab.hpp>
31
#include <realm/exceptions.hpp>
32
#include <realm/impl/cont_transact_hist.hpp>
33
#include <realm/impl/output_stream.hpp>
34
#include <realm/table.hpp>
35
#include <realm/util/features.h>
36
#include <realm/util/input_stream.hpp>
37

38
namespace realm {
39

40
class DB;
41
class TableKeys;
42

43
namespace _impl {
44
class GroupFriend;
45
} // namespace _impl
46

47
/// A group is a collection of named tables.
48
///
49
class Group : public ArrayParent {
50
    static constexpr StringData g_class_name_prefix = "class_";
51

52
public:
53
    /// Construct a free-standing group. This group instance will be
54
    /// in the attached state, but neither associated with a file, nor
55
    /// with an external memory buffer.
56
    Group();
57

58
    /// Attach this Group instance to the specified database file.
59
    ///
60
    /// The specified file is opened in read-only mode. This allows opening
61
    /// a file even when the caller lacks permission to write to that file.
62
    /// The opened group may still be modified freely, but the changes cannot be
63
    /// written back to the same file. Tt is an error if the specified
64
    /// file does not already exist in the file system.
65
    ///
66
    /// The file must contain a valid Realm database. In many cases invalidity
67
    /// will be detected and cause the InvalidDatabase exception to be thrown,
68
    /// but you should not rely on it.
69
    ///
70
    /// You may call write() to write the entire database to a new file. Writing
71
    /// the database to a new file does not end, or in any other way
72
    /// change the association between the Group instance and the file
73
    /// that was specified in the call to open().
74
    ///
75
    /// A Realm file that contains a history (see Replication::HistoryType) may
76
    /// be opened via Group::open(), as long as the application can ensure that
77
    /// there is no concurrent access to the file (see below for more on
78
    /// concurrency).
79
    ///
80
    /// A file that is passed to Group::open(), may not be modified by
81
    /// a third party until after the Group object is
82
    /// destroyed. Behavior is undefined if a file is modified by a
83
    /// third party while any Group object is associated with it.
84
    ///
85
    /// Calling open() on a Group instance that is already in the
86
    /// attached state has undefined behavior.
87
    ///
88
    /// Accessing a Realm database file through manual construction
89
    /// of a Group object does not offer any level of thread safety or
90
    /// transaction safety. When any of those kinds of safety are a
91
    /// concern, consider using a DB instead. When accessing
92
    /// a database file in read/write mode through a manually
93
    /// constructed Group object, it is entirely the responsibility of
94
    /// the application that the file is not accessed in any way by a
95
    /// third party during the life-time of that group object. It is,
96
    /// on the other hand, safe to concurrently access a database file
97
    /// by multiple manually created Group objects, as long as all of
98
    /// them are opened in read-only mode, and there is no other party
99
    /// that modifies the file concurrently.
100
    ///
101
    /// Do not call this function on a group instance that is managed
102
    /// by a shared group. Doing so will result in undefined behavior.
103
    ///
104
    /// Even if this function throws, it may have the side-effect of
105
    /// creating the specified file, and the file may get left behind
106
    /// in an invalid state. Of course, this can only happen if
107
    /// read/write mode (mode_ReadWrite) was requested, and the file
108
    /// did not already exist.
109
    ///
110
    /// \param file File system path to a Realm database file.
111
    ///
112
    /// \param encryption_key 32-byte key used to encrypt and decrypt
113
    /// the database file, or nullptr to disable encryption.
114
    ///
115
    /// \throw FileAccessError If the file could not be
116
    /// opened. If the reason corresponds to one of the exception
117
    /// types that are derived from FileAccessError, the
118
    /// derived exception type is thrown. Note that InvalidDatabase is
119
    /// among these derived exception types.
120
    explicit Group(const std::string& file, const char* encryption_key = nullptr);
121

122
    /// Attach this Group instance to the specified memory buffer.
123
    ///
124
    /// This is similar to constructing a group from a file except
125
    /// that in this case the database is assumed to be stored in the
126
    /// specified memory buffer.
127
    ///
128
    /// If \a take_ownership is `true`, you pass the ownership of the
129
    /// specified buffer to the group. In this case the buffer will
130
    /// eventually be freed using std::free(), so the buffer you pass,
131
    /// must have been allocated using std::malloc().
132
    ///
133
    /// On the other hand, if \a take_ownership is set to `false`, it
134
    /// is your responsibility to keep the memory buffer alive during
135
    /// the lifetime of the group, and in case the buffer needs to be
136
    /// deallocated afterwards, that is your responsibility too.
137
    ///
138
    /// If this function throws, the ownership of the memory buffer
139
    /// will remain with the caller, regardless of whether \a
140
    /// take_ownership is set to `true` or `false`.
141
    ///
142
    /// Calling open() on a Group instance that is already in the
143
    /// attached state has undefined behavior.
144
    ///
145
    /// Do not call this function on a group instance that is managed
146
    /// by a shared group. Doing so will result in undefined behavior.
147
    ///
148
    /// \throw InvalidDatabase If the specified buffer does not appear
149
    /// to contain a valid database.
150
    /// Note that if this constructor throws, the
151
    /// ownership of the memory buffer will remain with the caller,
152
    /// regardless of whether \a take_ownership is set to `true` or
153
    /// `false`.
154
    explicit Group(BinaryData, bool take_ownership = true);
155

156
    Group(const Group&) = delete;
157
    Group& operator=(const Group&) = delete;
158

159
    ~Group() noexcept override;
160

161
    /// A group may be created in the unattached state, and then later
162
    /// attached to a file with a call to open(). Calling any method
163
    /// other than open(), and is_attached() on an unattached instance
164
    /// results in undefined behavior.
165
    bool is_attached() const noexcept;
166
    /// A group is frozen only if it is actually a frozen transaction.
167
    virtual bool is_frozen() const noexcept
168
    {
6,990✔
169
        return false;
6,990✔
170
    }
6,990✔
171
    /// Returns true if, and only if the number of tables in this
172
    /// group is zero.
173
    bool is_empty() const noexcept;
174

175
    size_t size() const noexcept;
176

177
    static int get_current_file_format_version()
178
    {
4✔
179
        return g_current_file_format_version;
4✔
180
    }
4✔
181

182
    int get_history_schema_version() noexcept;
183

184
    Replication* get_replication() const
185
    {
7,632,180✔
186
        return *get_repl();
7,632,180✔
187
    }
7,632,180✔
188

189
    /// The sync file id is set when a client synchronizes with the server for the
190
    /// first time. It is used when generating GlobalKeys for tables without a primary
191
    /// key, where it is used as the "hi" part. This ensures global uniqueness of
192
    /// GlobalKeys.
193
    uint64_t get_sync_file_id() const noexcept;
194
    void set_sync_file_id(uint64_t id);
195

196
    /// Returns the keys for all tables in this group.
197
    TableKeys get_table_keys() const;
198

199
    /// \defgroup group_table_access Table Accessors
200
    ///
201
    /// has_table() returns true if, and only if this group contains a table
202
    /// with the specified name.
203
    ///
204
    /// find_table() returns the key of the first table in this group with the
205
    /// specified name, or `realm::not_found` if this group does not contain a
206
    /// table with the specified name.
207
    ///
208
    /// get_table_name() returns the name of table with the specified key.
209
    ///
210
    /// The versions of get_table(), that accepts a \a name argument, return a
211
    /// table with the specified name, or null if no such table exists.
212
    ///
213
    /// add_table() adds a table with the specified name to this group. It
214
    /// throws TableNameInUse if \a require_unique_name is true and \a name
215
    /// clashes with the name of an existing table. If \a require_unique_name is
216
    /// false, it is possible to add more than one table with the same
217
    /// name. Whenever a table is added the key assigned to it is returned.
218
    ///
219
    /// get_or_add_table() checks if a table exists in this group with the specified
220
    /// name. If it doesn't exist, a table is created.
221
    ///
222
    /// remove_table() removes the specified table from this group. A table can
223
    /// be removed only when it is not the target of a link column of a
224
    /// different table.
225
    ///
226
    /// rename_table() changes the name of a preexisting table. If \a
227
    /// require_unique_name is false, it becomes possible to have more than one
228
    /// table with a given name in a single group.
229
    ///
230
    /// The template functions work exactly like their non-template namesakes
231
    /// except as follows: The template versions of get_table() and
232
    /// get_or_add_table() throw DescriptorMismatch if the dynamic type of the
233
    /// specified table does not match the statically specified custom table
234
    /// type. The template versions of add_table() and get_or_add_table() set
235
    /// the dynamic type (descriptor) to match the statically specified custom
236
    /// table type.
237
    ///
238
    /// \param key Key of table in this group.
239
    ///
240
    /// \param name Name of table. All strings are valid table names as long as
241
    /// they are valid UTF-8 encodings and the number of bytes does not exceed
242
    /// `max_table_name_length`. A call to add_table() or get_or_add_table()
243
    /// with a name that is longer than `max_table_name_length` will cause an
244
    /// exception to be thrown.
245
    ///
246
    /// \param new_name New name for preexisting table.
247
    ///
248
    /// \param require_unique_name When set to true (the default), it becomes
249
    /// impossible to add a table with a name that is already in use, or to
250
    /// rename a table to a name that is already in use.
251
    ///
252
    /// \param was_added When specified, the boolean variable is set to true if
253
    /// the table was added, and to false otherwise. If the function throws, the
254
    /// boolean variable retains its original value.
255
    ///
256
    /// \return get_table(), add_table(), and get_or_add_table() return a table
257
    /// accessor attached to the requested (or added) table. get_table() may
258
    /// return null.
259
    ///
260
    /// \throw DescriptorMismatch Thrown by get_table() and get_or_add_table()
261
    /// tf the dynamic table type does not match the statically specified custom
262
    /// table type (\a T).
263
    ///
264
    /// \throw NoSuchTable Thrown by remove_table() and rename_table() if there
265
    /// is no table with the specified \a name.
266
    ///
267
    /// \throw TableNameInUse Thrown by add_table() if \a require_unique_name is
268
    /// true and \a name clashes with the name of a preexisting table. Thrown by
269
    /// rename_table() if \a require_unique_name is true and \a new_name clashes
270
    /// with the name of a preexisting table.
271
    ///
272
    /// \throw CrossTableLinkTarget Thrown by remove_table() if the specified
273
    /// table is the target of a link column of a different table.
274
    ///
275
    //@{
276

277
    static constexpr size_t max_table_name_length = 63;
278
    static constexpr size_t max_class_name_length = max_table_name_length - g_class_name_prefix.size();
279

280
    bool has_table(StringData name) const noexcept;
281
    TableKey find_table(StringData name) const noexcept;
282
    StringData get_table_name(TableKey key) const;
283
    StringData get_class_name(TableKey key) const
284
    {
4✔
285
        return table_name_to_class_name(get_table_name(key));
4✔
286
    }
4✔
287
    bool table_is_public(TableKey key) const;
288
    static StringData table_name_to_class_name(StringData table_name)
289
    {
1,277,976✔
290
        if (table_name.begins_with(g_class_name_prefix)) {
1,277,976✔
291
            return table_name.substr(g_class_name_prefix.size());
1,096,071✔
292
        }
1,096,071✔
293
        return table_name;
181,905✔
294
    }
1,277,976✔
295

296
    using TableNameBuffer = std::array<char, max_table_name_length>;
297
    static StringData class_name_to_table_name(StringData class_name, TableNameBuffer& buffer)
298
    {
171,339✔
299
        REALM_ASSERT(class_name.size() <= max_class_name_length);
171,339✔
300
        char* p = std::copy_n(g_class_name_prefix.data(), g_class_name_prefix.size(), buffer.data());
171,339✔
301
        size_t len = std::min(class_name.size(), buffer.size() - g_class_name_prefix.size());
171,339✔
302
        std::copy_n(class_name.data(), len, p);
171,339✔
303
        return StringData(buffer.data(), g_class_name_prefix.size() + len);
171,339✔
304
    }
171,339✔
305

306
    TableRef get_table(TableKey key);
307
    ConstTableRef get_table(TableKey key) const;
308

309
    // Catch some implicit conversions
310
    TableRef get_table(int) = delete;
311
    ConstTableRef get_table(int) const = delete;
312

313
    TableRef get_table(StringData name);
314
    ConstTableRef get_table(StringData name) const;
315

316
    TableRef add_table(StringData name, Table::Type table_type = Table::Type::TopLevel);
317
    TableRef add_table_with_primary_key(StringData name, DataType pk_type, StringData pk_name, bool nullable = false,
318
                                        Table::Type table_type = Table::Type::TopLevel);
319
    TableRef get_or_add_table(StringData name, Table::Type table_type = Table::Type::TopLevel,
320
                              bool* was_added = nullptr);
321
    TableRef get_or_add_table_with_primary_key(StringData name, DataType pk_type, StringData pk_name,
322
                                               bool nullable = false, Table::Type table_type = Table::Type::TopLevel);
323

324
    void remove_table(TableKey key);
325
    void remove_table(StringData name);
326

327
    void rename_table(TableKey key, StringData new_name, bool require_unique_name = true);
328
    void rename_table(StringData name, StringData new_name, bool require_unique_name = true);
329

330
    Obj get_object(ObjLink link);
331
    Obj try_get_object(ObjLink link) noexcept;
332
    void validate(ObjLink link) const;
333

334
    //@}
335

336
    // Serialization
337

338
    /// Write this database to the specified output stream.
339
    ///
340
    /// \param out The destination output stream to write to.
341
    ///
342
    /// \param pad If true, the file is padded to ensure the footer is aligned
343
    /// to the end of a page
344
    void write(std::ostream& out, bool pad = false) const;
345

346
    /// Write this database to a new file. It is an error to specify a
347
    /// file that already exists. This is to protect against
348
    /// overwriting a database file that is currently open, which
349
    /// would cause undefined behaviour.
350
    ///
351
    /// \param path A filesystem path to the file you want to write to.
352
    ///
353
    /// \param encryption_key 32-byte key used to encrypt the database file,
354
    /// or nullptr to disable encryption.
355
    ///
356
    /// \param version If different from 0, the new file will be a full fledged
357
    /// realm file with free list and history info. The version of the commit
358
    /// will be set to the value given here.
359
    ///
360
    /// \param write_history Indicates if you want the Sync Client History to
361
    /// be written to the file (only relevant for synchronized files).
362
    /// \throw FileAccessError If the file could not be
363
    /// opened. If the reason corresponds to one of the exception
364
    /// types that are derived from FileAccessError, the
365
    /// derived exception type is thrown. In particular,
366
    /// util::File::Exists will be thrown if the file exists already.
367
    void write(const std::string& path, const char* encryption_key = nullptr, uint64_t version = 0,
368
               bool write_history = true) const;
369

370
    /// Write this database to a memory buffer.
371
    ///
372
    /// Ownership of the returned buffer is transferred to the
373
    /// caller. The memory will have been allocated using
374
    /// std::malloc().
375
    BinaryData write_to_mem() const;
376

377
    //@{
378
    /// Some operations on Tables in a Group can cause indirect changes to other
379
    /// fields, including in other Tables in the same Group. Specifically,
380
    /// removing a row will set any links to that row to null, and if it had the
381
    /// last strong links to other rows, will remove those rows. When this
382
    /// happens, The cascade notification handler will be called with a
383
    /// CascadeNotification containing information about what indirect changes
384
    /// will occur, before any changes are made.
385
    ///
386
    /// has_cascade_notification_handler() returns true if and only if there is
387
    /// currently a non-null notification handler registered.
388
    ///
389
    /// set_cascade_notification_handler() replaces the current handler (if any)
390
    /// with the passed in handler. Pass in nullptr to remove the current handler
391
    /// without registering a new one.
392
    ///
393
    /// CascadeNotification contains a vector of rows which will be removed and
394
    /// a vector of links which will be set to null (or removed, for entries in
395
    /// LinkLists).
396
    struct CascadeNotification {
397
        struct row {
398
            /// Key identifying a group-level table.
399
            TableKey table_key;
400

401
            /// Key identifying object to be removed.
402
            ObjKey key;
403

404
            row() = default;
405

406
            row(TableKey tk, ObjKey k)
407
                : table_key(tk)
213✔
408
                , key(k)
213✔
409
            {
426✔
410
            }
426✔
411
            bool operator==(const row& r) const noexcept
412
            {
×
413
                return table_key == r.table_key && key == r.key;
×
414
            }
×
415
            bool operator!=(const row& r) const noexcept
416
            {
×
417
                return !(*this == r);
×
418
            }
×
419
            /// Trivial lexicographic order
420
            bool operator<(const row& r) const noexcept
421
            {
×
422
                return table_key < r.table_key || (table_key == r.table_key && key < r.key);
×
423
            }
×
424
        };
425

426
        struct link {
427
            link() = default;
428
            link(TableKey tk, ColKey ck, ObjKey k, ObjKey otk)
429
                : origin_table(tk)
24✔
430
                , origin_col_key(ck)
24✔
431
                , origin_key(k)
24✔
432
                , old_target_key(otk)
24✔
433
            {
48✔
434
            }
48✔
435
            TableKey origin_table; ///< A group-level table.
436
            ColKey origin_col_key; ///< Link column being nullified.
437
            ObjKey origin_key;     ///< Row in column being nullified.
438
            /// The target row index which is being removed. Mostly relevant for
439
            /// LinkList (to know which entries are being removed), but also
440
            /// valid for Link.
441
            ObjKey old_target_key;
442
        };
443

444
        /// A sorted list of rows which will be removed by the current operation.
445
        std::vector<row> rows;
446

447
        /// An unordered list of links which will be nullified by the current
448
        /// operation.
449
        std::vector<link> links;
450
    };
451

452
    bool has_cascade_notification_handler() const noexcept;
453
    void
454
    set_cascade_notification_handler(util::UniqueFunction<void(const CascadeNotification&)> new_handler) noexcept;
455

456
    //@}
457

458
    //@{
459
    /// During sync operation, schema changes may happen at runtime as connected
460
    /// clients update their schema as part of an app update. Since this is a
461
    /// relatively rare event, no attempt is made at limiting the amount of work
462
    /// the handler is required to do to update its information about table and
463
    /// column indices (i.e., all table and column indices must be recalculated).
464
    ///
465
    /// At the time of writing, only additive schema changes may occur in that
466
    /// scenario.
467
    ///
468
    /// has_schema_change_notification_handler() returns true iff there is currently
469
    /// a non-null notification handler registered.
470
    ///
471
    /// set_schema_change_notification_handler() replaces the current handler (if any)
472
    /// with the passed in handler. Pass in nullptr to remove the current handler
473
    /// without registering a new one.
474

475
    bool has_schema_change_notification_handler() const noexcept;
476
    void set_schema_change_notification_handler(util::UniqueFunction<void()> new_handler) noexcept;
477

478
    //@}
479

480
    // Conversion
481
    void schema_to_json(std::ostream& out) const;
482
    void to_json(std::ostream& out, JSONOutputMode output_mode = output_mode_json) const;
483

484
    /// Compare two groups for equality. Two groups are equal if, and
485
    /// only if, they contain the same tables in the same order, that
486
    /// is, for each table T at index I in one of the groups, there is
487
    /// a table at index I in the other group that is equal to T.
488
    /// Tables are equal if they have the same content and the same table name.
489
    bool operator==(const Group&) const;
490

491
    /// Compare two groups for inequality. See operator==().
492
    bool operator!=(const Group& g) const
493
    {
4✔
494
        return !(*this == g);
4✔
495
    }
4✔
496

497
    /// Return the size taken up by the current snapshot. This is in contrast to
498
    /// the number returned by DB::get_stats() which will return the
499
    /// size of the last snapshot done in that DB. If the snapshots are
500
    /// identical, the numbers will of course be equal.
501
    size_t get_used_space() const noexcept;
502

503
    /// check that an already attached realm file is valid for read only access.
504
    /// if not detach the file and throw a FileFormatUpgradeRequired.
505
    /// return the file format version.
506
    static int read_only_version_check(SlabAlloc& alloc, ref_type top_ref, const std::string& path);
507
    void verify() const;
508
    void validate_primary_columns();
509
#ifdef REALM_DEBUG
510
    void print() const;
511
    void print_free() const;
512
    MemStats get_stats();
513
    void enable_mem_diagnostics(bool enable = true)
514
    {
×
515
        m_alloc.enable_debug(enable);
×
516
    }
×
517
#endif
518
    ref_type typed_write_tables(_impl::ArrayWriterBase& out) const;
519
    void table_typed_print(std::string prefix, ref_type ref) const;
520
    void typed_print(std::string prefix) const;
521

522
protected:
523
    static constexpr size_t s_table_name_ndx = 0;
524
    static constexpr size_t s_table_refs_ndx = 1;
525
    static constexpr size_t s_file_size_ndx = 2;
526
    static constexpr size_t s_free_pos_ndx = 3;
527
    static constexpr size_t s_free_size_ndx = 4;
528
    static constexpr size_t s_free_version_ndx = 5;
529
    static constexpr size_t s_version_ndx = 6;
530
    static constexpr size_t s_hist_type_ndx = 7;
531
    static constexpr size_t s_hist_ref_ndx = 8;
532
    static constexpr size_t s_hist_version_ndx = 9;
533
    static constexpr size_t s_sync_file_id_ndx = 10;
534
    static constexpr size_t s_evacuation_point_ndx = 11;
535

536
    static constexpr size_t s_group_max_size = 12;
537

538
    virtual Replication* const* get_repl() const
539
    {
4,116,711✔
540
        return &Table::g_dummy_replication;
4,116,711✔
541
    }
4,116,711✔
542

543
private:
544
    // nullptr, if we're sharing an allocator provided during initialization
545
    std::unique_ptr<SlabAlloc> m_local_alloc;
546
    // in-use allocator. If local, then equal to m_local_alloc.
547
    SlabAlloc& m_alloc;
548

549
    int m_file_format_version;
550
    /// `m_top` is the root node (or top array) of the Realm, and has the
551
    /// following layout:
552
    ///
553
    /// <pre>
554
    ///
555
    ///                                                     Introduced in file
556
    ///   Slot  Value                                       format version
557
    ///   ---------------------------------------------------------------------
558
    ///    1st   m_table_names
559
    ///    2nd   m_tables
560
    ///    3rd   Logical file size
561
    ///    4th   GroupWriter::m_free_positions (optional)
562
    ///    5th   GroupWriter::m_free_lengths   (optional)
563
    ///    6th   GroupWriter::m_free_versions  (optional)
564
    ///    7th   Transaction number / version  (optional)
565
    ///    8th   History type         (optional)             4
566
    ///    9th   History ref          (optional)             4
567
    ///   10th   History version      (optional)             7
568
    ///   11th   Sync File Id         (optional)            10
569
    ///   12th   Evacuation point     (optional)            22
570
    ///
571
    /// </pre>
572
    ///
573
    /// The 'History type' slot stores a value of type
574
    /// Replication::HistoryType. The 'History version' slot stores a history
575
    /// schema version as returned by Replication::get_history_schema_version().
576
    ///
577
    /// The first three entries are mandatory. In files created by
578
    /// Group::write(), none of the optional entries are present and the size of
579
    /// `m_top` is 3. In files updated by Group::commit(), the 4th and 5th entry
580
    /// are present, and the size of `m_top` is 5. In files updated by way of a
581
    /// transaction (Transaction::commit()), the 4th, 5th, 6th, and 7th entry
582
    /// are present, and the size of `m_top` is 7. In files that contain a
583
    /// changeset history, the 8th, 9th, and 10th entry are present. The 11th entry
584
    /// will be present if the file is syncked and the client has received a client
585
    /// file id from the server.
586
    ///
587
    /// When a group accessor is attached to a newly created file or an empty
588
    /// memory buffer where there is no top array yet, `m_top`, `m_tables`, and
589
    /// `m_table_names` will be left in the detached state until the initiation
590
    /// of the first write transaction. In particular, they will remain in the
591
    /// detached state during read transactions that precede the first write
592
    /// transaction.
593
    Array m_top;
594
    Array m_tables;
595
    ArrayStringShort m_table_names;
596
    uint64_t m_last_seen_mapping_version = 0;
597

598
    typedef std::vector<Table*> TableAccessors;
599
    mutable TableAccessors m_table_accessors;
600
    mutable std::mutex m_accessor_mutex;
601
    mutable int m_num_tables = 0;
602
    bool m_attached = false;
603
    bool m_is_writable = true;
604
    static std::optional<int> fake_target_file_format;
605

606
    util::UniqueFunction<void(const CascadeNotification&)> m_notify_handler;
607
    util::UniqueFunction<void()> m_schema_change_handler;
608
    std::set<TableKey> m_tables_to_clear;
609

610
    Group(SlabAlloc* alloc) noexcept;
611
    void init_array_parents() noexcept;
612

613
    void open(ref_type top_ref, const std::string& file_path);
614

615
    // If the underlying memory mappings have been extended, this method is used
616
    // to update all the tables' allocator wrappers. The allocator wrappers are
617
    // configure to either allow or deny changes.
618
    void update_allocator_wrappers(bool writable);
619

620
    /// If `top_ref` is not zero, attach this group accessor to the specified
621
    /// underlying node structure. If `top_ref` is zero and \a
622
    /// create_group_when_missing is true, create a new node structure that
623
    /// represents an empty group, and attach this group accessor to it.
624
    void attach(ref_type top_ref, bool writable, bool create_group_when_missing, size_t file_size = -1,
625
                uint_fast64_t version = -1);
626

627
    /// Detach this group accessor from the underlying node structure. If this
628
    /// group accessors is already in the detached state, this function does
629
    /// nothing (idempotency).
630
    void detach() noexcept;
631

632
    /// \param writable Must be set to true when, and only when attaching for a
633
    /// write transaction.
634
    void attach_shared(ref_type new_top_ref, size_t new_file_size, bool writable, VersionID version);
635

636
    void create_empty_group();
637
    void remove_table(size_t table_ndx, TableKey key);
638

639
    void reset_free_space_tracking();
640

641
    void remap_and_update_refs(ref_type new_top_ref, size_t new_file_size, bool writable);
642
    void update_table_accessors();
643
    /// Recursively update refs stored in all cached array
644
    /// accessors. This includes cached array accessors in any
645
    /// currently attached table accessors. This ensures that the
646
    /// group instance itself, as well as any attached table accessor
647
    /// that exists across Transaction::commit() will remain valid. This
648
    /// function is not appropriate for use in conjunction with
649
    /// commits via shared group.
650
    void update_refs(ref_type top_ref) noexcept;
651

652
    // Overriding method in ArrayParent
653
    void update_child_ref(size_t, ref_type) override;
654

655
    // Overriding method in ArrayParent
656
    ref_type get_child_ref(size_t) const noexcept override;
657

658
    class TableWriter;
659
    class DefaultTableWriter;
660

661
    static void write(std::ostream&, int file_format_version, TableWriter&, bool no_top_array,
662
                      bool pad_for_encryption, uint_fast64_t version_number);
663

664
    Table* do_get_table(size_t ndx);
665
    const Table* do_get_table(size_t ndx) const;
666
    Table* do_get_table(StringData name);
667
    const Table* do_get_table(StringData name) const;
668
    Table* do_add_table(StringData name, Table::Type table_type, bool do_repl = true);
669

670
    void create_and_insert_table(TableKey key, StringData name);
671
    Table* create_table_accessor(size_t table_ndx);
672
    void recycle_table_accessor(Table*);
673

674
    void detach_table_accessors() noexcept; // Idempotent
675

676
    void mark_all_table_accessors() noexcept;
677

678
    void write(util::File& file, const char* encryption_key, uint_fast64_t version_number, TableWriter& writer) const;
679
    void write(std::ostream&, bool pad, uint_fast64_t version_numer, TableWriter& writer) const;
680

681
    /// Memory mappings must have been updated to reflect any growth in filesize before
682
    /// calling advance_transact()
683
    void advance_transact(ref_type new_top_ref, util::InputStream*, bool writable);
684
    void refresh_dirty_accessors();
685
    void flush_accessors_for_commit();
686

687
    /// \brief The version of the format of the node structure (in file or in
688
    /// memory) in use by Realm objects associated with this group.
689
    ///
690
    /// Every group contains a file format version field, which is returned
691
    /// by this function. The file format version field is set to the file format
692
    /// version specified by the attached file (or attached memory buffer) at the
693
    /// time of attachment and the value is used to determine if a file format
694
    /// upgrade is required.
695
    ///
696
    /// A value of zero means that the file format is not yet decided. This is
697
    /// only possible for empty Realms where top-ref is zero. (When group is created
698
    /// with the unattached_tag). The version number will then be determined in the
699
    /// subsequent call to Group::open.
700
    ///
701
    /// In shared mode (when a Realm file is opened via a DB instance)
702
    /// it can happen that the file format is upgraded asyncronously (via
703
    /// another DB instance), and in that case the file format version
704
    /// field can get out of date, but only for a short while. It is always
705
    /// guaranteed to be, and remain up to date after the opening process completes
706
    /// (when DB::do_open() returns).
707
    ///
708
    /// An empty Realm file (one whose top-ref is zero) may specify a file
709
    /// format version of zero to indicate that the format is not yet
710
    /// decided. In that case the file format version must be changed to a proper
711
    /// before the opening process completes (Group::open() or DB::open()).
712
    ///
713
    /// File format versions:
714
    ///
715
    ///   1 Initial file format version
716
    ///
717
    ///   2 Various changes.
718
    ///
719
    ///   3 Supporting null on string columns broke the file format in following
720
    ///     way: Index appends an 'X' character to all strings except the null
721
    ///     string, to be able to distinguish between null and empty
722
    ///     string. Bumped to 3 because of null support of String columns and
723
    ///     because of new format of index.
724
    ///
725
    ///   4 Introduction of optional in-Realm history of changes (additional
726
    ///     entries in Group::m_top). Since this change is not forward
727
    ///     compatible, the file format version had to be bumped. This change is
728
    ///     implemented in a way that achieves backwards compatibility with
729
    ///     version 3 (and in turn with version 2).
730
    ///
731
    ///   5 Introduced the new Timestamp column type that replaces DateTime.
732
    ///     When opening an older database file, all DateTime columns will be
733
    ///     automatically upgraded Timestamp columns.
734
    ///
735
    ///   6 Introduced a new structure for the StringIndex. Moved the commit
736
    ///     logs into the Realm file. Changes to the transaction log format
737
    ///     including reshuffling instructions. This is the format used in
738
    ///     milestone 2.0.0.
739
    ///
740
    ///   7 Introduced "history schema version" as 10th entry in top array.
741
    ///
742
    ///   8 Subtables can now have search index.
743
    ///
744
    ///   9 Replication instruction values shuffled, instr_MoveRow added.
745
    ///
746
    ///  10 Cluster based table layout. Memory mapping changes which require
747
    ///     special treatment of large files of preceding versions.
748
    ///
749
    ///  11 Same as 10, but version 11 files will have search index added on
750
    ///     string primary key columns.
751
    ///
752
    ///  12 - 19 Room for new file formats in legacy code.
753
    ///
754
    ///  20 New data types: Decimal128 and ObjectId. Embedded tables. Search index
755
    ///     is removed from primary key columns.
756
    ///
757
    ///  21 New data types: UUID, Mixed, Set and Dictionary.
758
    ///
759
    ///  22 Object keys are no longer generated from primary key values. Search index
760
    ///     reintroduced.
761
    ///
762
    ///  23 Layout of Set and Dictionary changed.
763
    ///
764
    ///  24 Variable sized arrays for Decimal128.
765
    ///     Nested collections
766
    ///     Backlinks in BPlusTree
767
    ///     Sort order of Strings changed (affects sets and the string index)
768
    ///
769
    /// IMPORTANT: When introducing a new file format version, be sure to review
770
    /// the file validity checks in Group::open() and DB::do_open, the file
771
    /// format selection logic in
772
    /// Group::get_target_file_format_version_for_session(), and the file format
773
    /// upgrade logic in Group::upgrade_file_format(), AND the lists of accepted
774
    /// file formats and the version deletion list residing in "backup_restore.cpp"
775

776
    static constexpr int g_current_file_format_version = 24;
777

778
    int get_file_format_version() const noexcept;
779
    void set_file_format_version(int) noexcept;
780
    int get_committed_file_format_version() const noexcept;
781

782
    /// The specified history type must be a value of Replication::HistoryType.
783
    static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept;
784

785
    void send_cascade_notification(const CascadeNotification& notification) const;
786
    void send_schema_change_notification() const;
787

788
    static void get_version_and_history_info(const Array& top, _impl::History::version_type& version,
789
                                             int& history_type, int& history_schema_version) noexcept;
790
    static ref_type get_history_ref(const Array& top) noexcept;
791
    static size_t get_logical_file_size(const Array& top) noexcept;
792
    static size_t get_free_space_size(const Array& top) noexcept;
793
    static size_t get_history_size(const Array& top) noexcept;
794
    size_t get_logical_file_size() const noexcept
795
    {
156✔
796
        return get_logical_file_size(m_top);
156✔
797
    }
156✔
798
    void clear_history();
799
    void set_history_schema_version(int version);
800
    template <class Accessor>
801
    void set_history_parent(Accessor& history_root) noexcept;
802
    void prepare_top_for_history(int history_type, int history_schema_version, uint64_t file_ident);
803
    template <class Accessor>
804
    void prepare_history_parent(Accessor& history_root, int history_type, int history_schema_version,
805
                                uint64_t file_ident);
806
    static void validate_top_array(const Array& arr, const SlabAlloc& alloc,
807
                                   std::optional<size_t> read_lock_file_size = util::none,
808
                                   std::optional<uint_fast64_t> read_lock_version = util::none);
809

810
    Table* get_table_unchecked(TableKey);
811
    size_t find_table_index(StringData name) const noexcept;
812
    TableKey ndx2key(size_t ndx) const;
813
    static size_t key2ndx(TableKey key);
814
    size_t key2ndx_checked(TableKey key) const;
815
    void set_size() const noexcept;
816
    std::map<TableRef, ColKey> get_primary_key_columns_from_pk_table(TableRef pk_table);
817
    void check_table_name_uniqueness(StringData name)
818
    {
165,396✔
819
        if (m_table_names.find_first(name) != not_found)
165,396✔
820
            throw TableNameInUse();
6✔
821
    }
165,396✔
822
    void check_attached() const
823
    {
33,138,552✔
824
        if (!is_attached())
33,138,552✔
825
            throw StaleAccessor("Stale transaction");
×
826
    }
33,138,552✔
827

828
    friend class CascadeState;
829
    friend class DB;
830
    friend class GroupCommitter;
831
    friend class GroupWriter;
832
    friend class SlabAlloc;
833
    friend class Table;
834
    friend class TableKeyIterator;
835
    friend class Transaction;
836
    friend class _impl::DeepChangeChecker;
837
    friend class _impl::GroupFriend;
838
};
839

840
class TableKeyIterator {
841
public:
842
    bool operator!=(const TableKeyIterator& other)
843
    {
1,848,303✔
844
        return m_pos != other.m_pos;
1,848,303✔
845
    }
1,848,303✔
846
    TableKeyIterator& operator++();
847
    TableKey operator*();
848

849
private:
850
    friend class TableKeys;
851
    const Group* m_group;
852
    size_t m_pos;
853
    size_t m_index_in_group = 0;
854
    TableKey m_table_key;
855

856
    TableKeyIterator(const Group* g, size_t p)
857
        : m_group(g)
779,376✔
858
        , m_pos(p)
779,376✔
859
    {
1,565,265✔
860
    }
1,565,265✔
861
    void load_key();
862
};
863

864
class TableKeys {
865
public:
866
    TableKeys(const Group* g)
867
        : m_iter(g, 0)
259,881✔
868
    {
522,090✔
869
    }
522,090✔
870
    size_t size() const
871
    {
521,682✔
872
        return m_iter.m_group->size();
521,682✔
873
    }
521,682✔
874
    bool empty() const
875
    {
×
876
        return size() == 0;
×
877
    }
×
878
    TableKey operator[](size_t p) const;
879
    TableKeyIterator begin() const
880
    {
521,589✔
881
        return TableKeyIterator(m_iter.m_group, 0);
521,589✔
882
    }
521,589✔
883
    TableKeyIterator end() const
884
    {
521,598✔
885
        return TableKeyIterator(m_iter.m_group, size());
521,598✔
886
    }
521,598✔
887

888
private:
889
    mutable TableKeyIterator m_iter;
890
};
891

892
// Implementation
893

894
inline TableKeys Group::get_table_keys() const
895
{
522,093✔
896
    return TableKeys(this);
522,093✔
897
}
522,093✔
898

899
inline bool Group::is_attached() const noexcept
900
{
37,752,051✔
901
    return m_attached;
37,752,051✔
902
}
37,752,051✔
903

904
inline bool Group::is_empty() const noexcept
905
{
24✔
906
    if (!is_attached())
24✔
907
        return false;
×
908
    return size() == 0;
24✔
909
}
24✔
910

911
inline size_t Group::key2ndx(TableKey key)
912
{
28,565,019✔
913
    size_t idx = key.value & 0xFFFF;
28,565,019✔
914
    return idx;
28,565,019✔
915
}
28,565,019✔
916

917
inline StringData Group::get_table_name(TableKey key) const
918
{
5,058,177✔
919
    size_t table_ndx = key2ndx_checked(key);
5,058,177✔
920
    return m_table_names.get(table_ndx);
5,058,177✔
921
}
5,058,177✔
922

923
inline bool Group::table_is_public(TableKey key) const
924
{
1,406,220✔
925
    return get_table_name(key).begins_with(g_class_name_prefix);
1,406,220✔
926
}
1,406,220✔
927

928
inline bool Group::has_table(StringData name) const noexcept
929
{
211,656✔
930
    size_t ndx = find_table_index(name);
211,656✔
931
    return ndx != not_found;
211,656✔
932
}
211,656✔
933

934
inline size_t Group::find_table_index(StringData name) const noexcept
935
{
211,806✔
936
    if (m_table_names.is_attached())
211,806✔
937
        return m_table_names.find_first(name);
179,817✔
938
    return not_found;
31,989✔
939
}
211,806✔
940

941
inline TableKey Group::find_table(StringData name) const noexcept
942
{
100✔
943
    if (!is_attached())
100✔
944
        return TableKey();
×
945
    size_t ndx = find_table_index(name);
100✔
946
    return (ndx != npos) ? ndx2key(ndx) : TableKey{};
100✔
947
}
100✔
948

949
inline Table* Group::get_table_unchecked(TableKey key)
950
{
22,619,583✔
951
    auto ndx = key2ndx_checked(key);
22,619,583✔
952
    return do_get_table(ndx); // Throws
22,619,583✔
953
}
22,619,583✔
954

955
inline TableRef Group::get_table(TableKey key)
956
{
22,865,175✔
957
    check_attached();
22,865,175✔
958
    Table* table = get_table_unchecked(key);
22,865,175✔
959
    return TableRef(table, table ? table->m_alloc.get_instance_version() : 0);
22,865,175✔
960
}
22,865,175✔
961

962
inline ConstTableRef Group::get_table(TableKey key) const
963
{
807,864✔
964
    check_attached();
807,864✔
965
    auto ndx = key2ndx_checked(key);
807,864✔
966
    const Table* table = do_get_table(ndx); // Throws
807,864✔
967
    return ConstTableRef(table, table ? table->m_alloc.get_instance_version() : 0);
2,147,887,798✔
968
}
807,864✔
969

970
inline TableRef Group::get_table(StringData name)
971
{
8,632,221✔
972
    check_attached();
8,632,221✔
973
    Table* table = do_get_table(name); // Throws
8,632,221✔
974
    return TableRef(table, table ? table->m_alloc.get_instance_version() : 0);
8,632,221✔
975
}
8,632,221✔
976

977
inline ConstTableRef Group::get_table(StringData name) const
978
{
328,668✔
979
    check_attached();
328,668✔
980
    const Table* table = do_get_table(name); // Throws
328,668✔
981
    return ConstTableRef(table, table ? table->m_alloc.get_instance_version() : 0);
328,668✔
982
}
328,668✔
983

984
inline TableRef Group::add_table(StringData name, Table::Type table_type)
985
{
56,544✔
986
    check_attached();
56,544✔
987
    check_table_name_uniqueness(name);
56,544✔
988
    Table* table = do_add_table(name, table_type); // Throws
56,544✔
989
    return TableRef(table, table->m_alloc.get_instance_version());
56,544✔
990
}
56,544✔
991

992
inline TableRef Group::get_or_add_table(StringData name, Table::Type table_type, bool* was_added)
993
{
174,591✔
994
    REALM_ASSERT(table_type != Table::Type::Embedded);
174,591✔
995
    check_attached();
174,591✔
996
    auto table = do_get_table(name);
174,591✔
997
    if (was_added)
174,591✔
998
        *was_added = !table;
120✔
999
    if (!table) {
174,591✔
1000
        table = do_add_table(name, table_type);
113,760✔
1001
    }
113,760✔
1002
    return TableRef(table, table->m_alloc.get_instance_version());
174,591✔
1003
}
174,591✔
1004

1005
inline TableRef Group::get_or_add_table_with_primary_key(StringData name, DataType pk_type, StringData pk_name,
1006
                                                         bool nullable, Table::Type table_type)
1007
{
4,274✔
1008
    REALM_ASSERT(table_type != Table::Type::Embedded);
4,274✔
1009
    if (TableRef table = get_table(name)) {
4,274✔
1010
        if (!table->get_primary_key_column() || table->get_column_name(table->get_primary_key_column()) != pk_name ||
120✔
1011
            table->is_nullable(table->get_primary_key_column()) != nullable ||
120✔
1012
            table->get_table_type() != table_type) {
120✔
1013
            return {};
×
1014
        }
×
1015
        return table;
120✔
1016
    }
120✔
1017
    else {
4,154✔
1018
        return add_table_with_primary_key(name, pk_type, pk_name, nullable, table_type);
4,154✔
1019
    }
4,154✔
1020
}
4,274✔
1021

1022

1023
inline void Group::init_array_parents() noexcept
1024
{
1,894,425✔
1025
    m_table_names.set_parent(&m_top, 0);
1,894,425✔
1026
    m_tables.set_parent(&m_top, 1);
1,894,425✔
1027
}
1,894,425✔
1028

1029
inline void Group::update_child_ref(size_t child_ndx, ref_type new_ref)
1030
{
553,359✔
1031
    m_tables.set(child_ndx, new_ref);
553,359✔
1032
}
553,359✔
1033

1034
inline ref_type Group::get_child_ref(size_t child_ndx) const noexcept
1035
{
1,724,040✔
1036
    return m_tables.get_as_ref(child_ndx);
1,724,040✔
1037
}
1,724,040✔
1038

1039
inline bool Group::has_cascade_notification_handler() const noexcept
1040
{
2,410,494✔
1041
    return !!m_notify_handler;
2,410,494✔
1042
}
2,410,494✔
1043

1044
inline void
1045
Group::set_cascade_notification_handler(util::UniqueFunction<void(const CascadeNotification&)> new_handler) noexcept
1046
{
30✔
1047
    m_notify_handler = std::move(new_handler);
30✔
1048
}
30✔
1049

1050
inline void Group::send_cascade_notification(const CascadeNotification& notification) const
1051
{
150✔
1052
    REALM_ASSERT_DEBUG(m_notify_handler);
150✔
1053
    m_notify_handler(notification);
150✔
1054
}
150✔
1055

1056
inline bool Group::has_schema_change_notification_handler() const noexcept
1057
{
249,402✔
1058
    return !!m_schema_change_handler;
249,402✔
1059
}
249,402✔
1060

1061
inline void Group::set_schema_change_notification_handler(util::UniqueFunction<void()> new_handler) noexcept
1062
{
130,910✔
1063
    m_schema_change_handler = std::move(new_handler);
130,910✔
1064
}
130,910✔
1065

1066
inline void Group::send_schema_change_notification() const
1067
{
11,790✔
1068
    if (m_schema_change_handler)
11,790✔
1069
        m_schema_change_handler();
11,790✔
1070
}
11,790✔
1071

1072
inline ref_type Group::get_history_ref(const Array& top) noexcept
1073
{
1,083,885✔
1074
    bool has_history = (top.is_attached() && top.size() > s_hist_type_ndx);
1,083,885✔
1075
    if (has_history) {
1,083,885✔
1076
        // This function is only used is shared mode (from DB)
1077
        REALM_ASSERT(top.size() > s_hist_version_ndx);
1,001,748✔
1078
        return top.get_as_ref(s_hist_ref_ndx);
1,001,748✔
1079
    }
1,001,748✔
1080
    return 0;
82,137✔
1081
}
1,083,885✔
1082

1083
inline size_t Group::get_logical_file_size(const Array& top) noexcept
1084
{
87,024✔
1085
    if (top.is_attached() && top.size() > s_file_size_ndx) {
87,024✔
1086
        return (size_t)top.get_as_ref_or_tagged(s_file_size_ndx).get_as_int();
87,018✔
1087
    }
87,018✔
1088
    return 0;
6✔
1089
}
87,024✔
1090

1091

1092
inline void Group::set_sync_file_id(uint64_t id)
1093
{
56,892✔
1094
    while (m_top.size() < s_sync_file_id_ndx + 1)
79,788✔
1095
        m_top.add(0);
22,896✔
1096
    m_top.set(s_sync_file_id_ndx, RefOrTagged::make_tagged(id));
56,892✔
1097
}
56,892✔
1098

1099
inline void Group::set_history_schema_version(int version)
1100
{
138✔
1101
    while (m_top.size() < s_hist_version_ndx + 1)
870✔
1102
        m_top.add(0);
732✔
1103
    m_top.set(s_hist_version_ndx, RefOrTagged::make_tagged(unsigned(version))); // Throws
138✔
1104
}
138✔
1105

1106
template <class Accessor>
1107
inline void Group::set_history_parent(Accessor& history_root) noexcept
1108
{
684,661✔
1109
    history_root.set_parent(&m_top, 8);
684,661✔
1110
}
684,661✔
1111

1112
template <class Accessor>
1113
void Group::prepare_history_parent(Accessor& history_root, int history_type, int history_schema_version,
1114
                                   uint64_t file_ident)
1115
{
51,854✔
1116
    prepare_top_for_history(history_type, history_schema_version, file_ident);
51,854✔
1117
    set_history_parent(history_root);
51,854✔
1118
}
51,854✔
1119

1120
class Group::TableWriter {
1121
public:
1122
    struct HistoryInfo {
1123
        ref_type ref = 0;
1124
        int type = 0;
1125
        int version = 0;
1126
        uint64_t sync_file_id = 0;
1127
    };
1128

1129
    virtual ref_type write_names(_impl::OutputStream&) = 0;
1130
    virtual ref_type write_tables(_impl::OutputStream&) = 0;
1131
    virtual HistoryInfo write_history(_impl::OutputStream&) = 0;
1132
    void typed_print(std::string prefix)
NEW
1133
    {
×
NEW
1134
        m_group->typed_print(prefix);
×
NEW
1135
    }
×
1136

1137
    virtual ~TableWriter() noexcept {}
666✔
1138

1139
    void set_group(const Group* g)
1140
    {
660✔
1141
        m_group = g;
660✔
1142
    }
660✔
1143

1144
protected:
1145
    const Group* m_group = nullptr;
1146
};
1147

1148
class Group::DefaultTableWriter : public Group::TableWriter {
1149
public:
1150
    DefaultTableWriter(bool should_write_history = true)
1151
        : m_should_write_history(should_write_history)
333✔
1152
    {
666✔
1153
    }
666✔
1154
    ref_type write_names(_impl::OutputStream& out) override;
1155
    ref_type write_tables(_impl::OutputStream& out) override;
1156
    HistoryInfo write_history(_impl::OutputStream& out) override;
1157

1158
private:
1159
    bool m_should_write_history;
1160
};
1161

1162
inline const Table* Group::do_get_table(size_t ndx) const
1163
{
2,173,251✔
1164
    return const_cast<Group*>(this)->do_get_table(ndx); // Throws
2,173,251✔
1165
}
2,173,251✔
1166

1167
inline const Table* Group::do_get_table(StringData name) const
1168
{
328,668✔
1169
    return const_cast<Group*>(this)->do_get_table(name); // Throws
328,668✔
1170
}
328,668✔
1171

1172
inline void Group::reset_free_space_tracking()
1173
{
834✔
1174
    // if used whith a shared allocator, free space should never be reset through
1175
    // Group, but rather through the proper owner of the allocator, which is the DB object.
1176
    REALM_ASSERT(m_local_alloc);
834✔
1177
    m_alloc.reset_free_space_tracking(); // Throws
834✔
1178
}
834✔
1179

1180
// The purpose of this class is to give internal access to some, but
1181
// not all of the non-public parts of the Group class.
1182
class _impl::GroupFriend {
1183
public:
1184
    static Allocator& get_alloc(const Group& group) noexcept
1185
    {
42,684✔
1186
        return group.m_alloc;
42,684✔
1187
    }
42,684✔
1188

1189
    static ref_type get_top_ref(const Group& group) noexcept
1190
    {
×
1191
        return group.m_top.get_ref();
×
1192
    }
×
1193

1194
    static ref_type get_history_ref(Allocator& alloc, ref_type top_ref) noexcept
1195
    {
249,618✔
1196
        Array top(alloc);
249,618✔
1197
        if (top_ref != 0)
249,618✔
1198
            top.init_from_ref(top_ref);
249,606✔
1199
        return Group::get_history_ref(top);
249,618✔
1200
    }
249,618✔
1201

1202
    static ref_type get_history_ref(const Group& group) noexcept
1203
    {
765,489✔
1204
        return Group::get_history_ref(group.m_top);
765,489✔
1205
    }
765,489✔
1206

1207
    static int get_file_format_version(const Group& group) noexcept
1208
    {
×
1209
        return group.get_file_format_version();
×
1210
    }
×
1211

1212
    static void get_version_and_history_info(const Allocator& alloc, ref_type top_ref,
1213
                                             _impl::History::version_type& version, int& history_type,
1214
                                             int& history_schema_version) noexcept
1215
    {
71,226✔
1216
        Array top{const_cast<Allocator&>(alloc)};
71,226✔
1217
        if (top_ref != 0)
71,226✔
1218
            top.init_from_ref(top_ref);
31,086✔
1219
        Group::get_version_and_history_info(top, version, history_type, history_schema_version);
71,226✔
1220
    }
71,226✔
1221

1222
    static void set_history_schema_version(Group& group, int version)
1223
    {
×
1224
        group.set_history_schema_version(version); // Throws
×
1225
    }
×
1226

1227
    template <class Accessor>
1228
    static void set_history_parent(Group& group, Accessor& history_root) noexcept
1229
    {
632,809✔
1230
        group.set_history_parent(history_root);
632,809✔
1231
    }
632,809✔
1232

1233
    template <class Accessor>
1234
    static void prepare_history_parent(Group& group, Accessor& history_root, int history_type,
1235
                                       int history_schema_version, uint64_t file_ident = 0)
1236
    {
51,854✔
1237
        group.prepare_history_parent(history_root, history_type, history_schema_version, file_ident); // Throws
51,854✔
1238
    }
51,854✔
1239

1240
    // This is used by upgrade functions in Sync
1241
    static Table* get_table_by_ndx(Group& group, size_t ndx)
1242
    {
×
1243
        return group.do_get_table(ndx);
×
1244
    }
×
1245

1246
    static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept
1247
    {
×
1248
        return Group::get_target_file_format_version_for_session(current_file_format_version, history_type);
×
1249
    }
×
1250

1251
    static void fake_target_file_format(const std::optional<int> format) noexcept;
1252
};
1253

1254

1255
class CascadeState {
1256
public:
1257
    enum class Mode {
1258
        /// If we remove the last link to an object, delete that object, even if
1259
        /// the link we removed was not a strong link
1260
        All,
1261
        /// If we remove the last link to an object, delete that object only if
1262
        /// the link we removed was a strong link
1263
        Strong,
1264
        /// Never delete objects due to removing links
1265
        None
1266
    };
1267

1268
    struct Link {
1269
        TableKey origin_table; ///< A group-level table.
1270
        ColKey origin_col_key; ///< Link column being nullified.
1271
        ObjKey origin_key;     ///< Row in column being nullified.
1272
        /// The target row index which is being removed. Mostly relevant for
1273
        /// LinkList (to know which entries are being removed), but also
1274
        /// valid for Link.
1275
        ObjLink old_target_link;
1276
    };
1277

1278
    CascadeState(Mode mode = Mode::Strong, Group* g = nullptr) noexcept
1279
        : m_mode(mode)
1,434,045✔
1280
        , m_group(g)
1,434,045✔
1281
    {
2,869,881✔
1282
    }
2,869,881✔
1283

1284
    /// Indicate which links to take action on. Either all, strong or none.
1285
    Mode m_mode;
1286

1287
    std::vector<std::pair<TableKey, ObjKey>> m_to_be_deleted;
1288
    std::vector<Link> m_to_be_nullified;
1289
    Group* m_group = nullptr;
1290

1291
    bool notification_handler() const noexcept
1292
    {
31,167✔
1293
        return m_group && m_group->has_cascade_notification_handler();
31,167✔
1294
    }
31,167✔
1295

1296
    void send_notifications(Group::CascadeNotification& notifications) const
1297
    {
150✔
1298
        REALM_ASSERT_DEBUG(notification_handler());
150✔
1299
        m_group->send_cascade_notification(notifications);
150✔
1300
    }
150✔
1301

1302
    bool enqueue_for_cascade(const Obj& target_obj, bool link_is_strong, bool last_removed)
1303
    {
194,079✔
1304
        // Check if the object should be cascade deleted
1305
        if (m_mode == Mode::None || !last_removed) {
194,079✔
1306
            return false;
131,310✔
1307
        }
131,310✔
1308
        if (m_mode == Mode::All || link_is_strong) {
62,769✔
1309
            bool has_backlinks = target_obj.has_backlinks(m_mode == Mode::Strong);
8,715✔
1310
            if (!has_backlinks) {
8,715✔
1311
                // Object has no more backlinks - add to list for deletion
1312
                m_to_be_deleted.emplace_back(target_obj.get_table()->get_key(), target_obj.get_key());
8,715✔
1313
                return true;
8,715✔
1314
            }
8,715✔
1315
        }
8,715✔
1316
        return false;
54,054✔
1317
    }
62,769✔
1318

1319
    void enqueue_for_nullification(Table& src_table, ColKey src_col_key, ObjKey origin_key, ObjLink target_link)
1320
    {
6,999✔
1321
        // Nullify immediately if we don't need to send cascade notifications
1322
        if (!notification_handler()) {
6,999✔
1323
            if (Obj obj = src_table.try_get_object(origin_key)) {
6,951✔
1324
                std::move(obj).nullify_link(src_col_key, target_link);
6,951✔
1325
            }
6,951✔
1326
            return;
6,951✔
1327
        }
6,951✔
1328

1329
        // Otherwise enqueue it
1330
        m_to_be_nullified.push_back({src_table.get_key(), src_col_key, origin_key, target_link});
48✔
1331
    }
48✔
1332

1333
    void send_notifications()
1334
    {
24,018✔
1335
        if (!notification_handler()) {
24,018✔
1336
            return;
23,868✔
1337
        }
23,868✔
1338
        Group::CascadeNotification notification;
150✔
1339
        for (auto& o : m_to_be_deleted)
150✔
1340
            notification.rows.emplace_back(o.first, o.second);
426✔
1341
        for (auto& l : m_to_be_nullified)
150✔
1342
            notification.links.emplace_back(l.origin_table, l.origin_col_key, l.origin_key,
48✔
1343
                                            l.old_target_link.get_obj_key());
48✔
1344
        send_notifications(notification);
150✔
1345
    }
150✔
1346
};
1347

1348
} // namespace realm
1349

1350
#endif // REALM_GROUP_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