added iOS source code
[wl-app.git] / iOS / Pods / Realm / include / core / 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 <string>
24 #include <vector>
25 #include <map>
26 #include <stdexcept>
27
28 #include <realm/util/features.h>
29 #include <realm/exceptions.hpp>
30 #include <realm/impl/input_stream.hpp>
31 #include <realm/impl/output_stream.hpp>
32 #include <realm/impl/cont_transact_hist.hpp>
33 #include <realm/metrics/metrics.hpp>
34 #include <realm/table.hpp>
35 #include <realm/alloc_slab.hpp>
36
37 namespace realm {
38
39 class SharedGroup;
40 namespace _impl {
41 class GroupFriend;
42 class TransactLogConvenientEncoder;
43 class TransactLogParser;
44 }
45
46
47 /// A group is a collection of named tables.
48 ///
49 /// Tables occur in the group in an unspecified order, but an order that
50 /// generally remains fixed. The order is guaranteed to remain fixed between two
51 /// points in time if no tables are added to, or removed from the group during
52 /// that time. When tables are added to, or removed from the group, the order
53 /// may change arbitrarily.
54 ///
55 /// If `table` is a table accessor attached to a group-level table, and `group`
56 /// is a group accessor attached to the group, then the following is guaranteed,
57 /// even after a change in the table order:
58 ///
59 /// \code{.cpp}
60 ///
61 ///     table == group.get_table(table.get_index_in_group())
62 ///
63 /// \endcode
64 ///
65 class Group : private Table::Parent {
66 public:
67     /// Construct a free-standing group. This group instance will be
68     /// in the attached state, but neither associated with a file, nor
69     /// with an external memory buffer.
70     Group();
71
72     enum OpenMode {
73         /// Open in read-only mode. Fail if the file does not already exist.
74         mode_ReadOnly,
75         /// Open in read/write mode. Create the file if it doesn't exist.
76         mode_ReadWrite,
77         /// Open in read/write mode. Fail if the file does not already exist.
78         mode_ReadWriteNoCreate
79     };
80
81     /// Equivalent to calling open(const std::string&, const char*, OpenMode)
82     /// on an unattached group accessor.
83     explicit Group(const std::string& file, const char* encryption_key = nullptr, OpenMode = mode_ReadOnly);
84
85     /// Equivalent to calling open(BinaryData, bool) on an unattached
86     /// group accessor. Note that if this constructor throws, the
87     /// ownership of the memory buffer will remain with the caller,
88     /// regardless of whether \a take_ownership is set to `true` or
89     /// `false`.
90     explicit Group(BinaryData, bool take_ownership = true);
91
92     struct unattached_tag {
93     };
94
95     /// Create a Group instance in its unattached state. It may then
96     /// be attached to a database file later by calling one of the
97     /// open() methods. You may test whether this instance is
98     /// currently in its attached state by calling
99     /// is_attached(). Calling any other method (except the
100     /// destructor) while in the unattached state has undefined
101     /// behavior.
102     Group(unattached_tag) noexcept;
103
104     // FIXME: Implement a proper copy constructor (fairly trivial).
105     Group(const Group&) = delete;
106     Group& operator=(const Group&) = delete;
107
108     ~Group() noexcept override;
109
110     /// Attach this Group instance to the specified database file.
111     ///
112     /// By default, the specified file is opened in read-only mode
113     /// (mode_ReadOnly). This allows opening a file even when the
114     /// caller lacks permission to write to that file. The opened
115     /// group may still be modified freely, but the changes cannot be
116     /// written back to the same file using the commit() function. An
117     /// attempt to do that, will cause an exception to be thrown. When
118     /// opening in read-only mode, it is an error if the specified
119     /// file does not already exist in the file system.
120     ///
121     /// Alternatively, the file can be opened in read/write mode
122     /// (mode_ReadWrite). This allows use of the commit() function,
123     /// but, of course, it also requires that the caller has
124     /// permission to write to the specified file. When opening in
125     /// read-write mode, an attempt to create the specified file will
126     /// be made, if it does not already exist in the file system.
127     ///
128     /// In any case, if the file already exists, it must contain a
129     /// valid Realm database. In many cases invalidity will be
130     /// detected and cause the InvalidDatabase exception to be thrown,
131     /// but you should not rely on it.
132     ///
133     /// Note that changes made to the database via a Group instance
134     /// are not automatically committed to the specified file. You
135     /// may, however, at any time, explicitly commit your changes by
136     /// calling the commit() method, provided that the specified
137     /// open-mode is not mode_ReadOnly. Alternatively, you may call
138     /// write() to write the entire database to a new file. Writing
139     /// the database to a new file does not end, or in any other way
140     /// change the association between the Group instance and the file
141     /// that was specified in the call to open().
142     ///
143     /// A Realm file that contains a history (see Replication::HistoryType) may
144     /// be opened via Group::open(), as long as the application can ensure that
145     /// there is no concurrent access to the file (see below for more on
146     /// concurrency), but if the file is modified via Group::commit() the
147     /// history will be discarded. To retain the history, the application must
148     /// instead access the file in shared mode, i.e., via SharedGroup, and
149     /// supply the right kind of replication plugin (see
150     /// Replication::get_history_type()).
151     ///
152     /// A file that is passed to Group::open(), may not be modified by
153     /// a third party until after the Group object is
154     /// destroyed. Behavior is undefined if a file is modified by a
155     /// third party while any Group object is associated with it.
156     ///
157     /// Calling open() on a Group instance that is already in the
158     /// attached state has undefined behavior.
159     ///
160     /// Accessing a Realm database file through manual construction
161     /// of a Group object does not offer any level of thread safety or
162     /// transaction safety. When any of those kinds of safety are a
163     /// concern, consider using a SharedGroup instead. When accessing
164     /// a database file in read/write mode through a manually
165     /// constructed Group object, it is entirely the responsibility of
166     /// the application that the file is not accessed in any way by a
167     /// third party during the life-time of that group object. It is,
168     /// on the other hand, safe to concurrently access a database file
169     /// by multiple manually created Group objects, as long as all of
170     /// them are opened in read-only mode, and there is no other party
171     /// that modifies the file concurrently.
172     ///
173     /// Do not call this function on a group instance that is managed
174     /// by a shared group. Doing so will result in undefined behavior.
175     ///
176     /// Even if this function throws, it may have the side-effect of
177     /// creating the specified file, and the file may get left behind
178     /// in an invalid state. Of course, this can only happen if
179     /// read/write mode (mode_ReadWrite) was requested, and the file
180     /// did not already exist.
181     ///
182     /// \param file File system path to a Realm database file.
183     ///
184     /// \param encryption_key 32-byte key used to encrypt and decrypt
185     /// the database file, or nullptr to disable encryption.
186     ///
187     /// \param mode Specifying a mode that is not mode_ReadOnly
188     /// requires that the specified file can be opened in read/write
189     /// mode. In general there is no reason to open a group in
190     /// read/write mode unless you want to be able to call
191     /// Group::commit().
192     ///
193     /// \throw util::File::AccessError If the file could not be
194     /// opened. If the reason corresponds to one of the exception
195     /// types that are derived from util::File::AccessError, the
196     /// derived exception type is thrown. Note that InvalidDatabase is
197     /// among these derived exception types.
198     void open(const std::string& file, const char* encryption_key = nullptr, OpenMode mode = mode_ReadOnly);
199
200     /// Attach this Group instance to the specified memory buffer.
201     ///
202     /// This is similar to constructing a group from a file except
203     /// that in this case the database is assumed to be stored in the
204     /// specified memory buffer.
205     ///
206     /// If \a take_ownership is `true`, you pass the ownership of the
207     /// specified buffer to the group. In this case the buffer will
208     /// eventually be freed using std::free(), so the buffer you pass,
209     /// must have been allocated using std::malloc().
210     ///
211     /// On the other hand, if \a take_ownership is set to `false`, it
212     /// is your responsibility to keep the memory buffer alive during
213     /// the lifetime of the group, and in case the buffer needs to be
214     /// deallocated afterwards, that is your responsibility too.
215     ///
216     /// If this function throws, the ownership of the memory buffer
217     /// will remain with the caller, regardless of whether \a
218     /// take_ownership is set to `true` or `false`.
219     ///
220     /// Calling open() on a Group instance that is already in the
221     /// attached state has undefined behavior.
222     ///
223     /// Do not call this function on a group instance that is managed
224     /// by a shared group. Doing so will result in undefined behavior.
225     ///
226     /// \throw InvalidDatabase If the specified buffer does not appear
227     /// to contain a valid database.
228     void open(BinaryData, bool take_ownership = true);
229
230     /// A group may be created in the unattached state, and then later
231     /// attached to a file with a call to open(). Calling any method
232     /// other than open(), and is_attached() on an unattached instance
233     /// results in undefined behavior.
234     bool is_attached() const noexcept;
235
236     /// Returns true if, and only if the number of tables in this
237     /// group is zero.
238     bool is_empty() const noexcept;
239
240     /// Returns the number of tables in this group.
241     size_t size() const noexcept;
242
243     /// \defgroup group_table_access Table Accessors
244     ///
245     /// has_table() returns true if, and only if this group contains a table
246     /// with the specified name.
247     ///
248     /// find_table() returns the index of the first table in this group with the
249     /// specified name, or `realm::not_found` if this group does not contain a
250     /// table with the specified name.
251     ///
252     /// get_table_name() returns the name of table at the specified index.
253     ///
254     /// The versions of get_table(), that accepts a \a name argument, return the
255     /// first table with the specified name, or null if no such table exists.
256     ///
257     /// add_table() adds a table with the specified name to this group. It
258     /// throws TableNameInUse if \a require_unique_name is true and \a name
259     /// clashes with the name of an existing table. If \a require_unique_name is
260     /// false, it is possible to add more than one table with the same
261     /// name. Whenever a table is added, the order of the preexisting tables may
262     /// change arbitrarily, and the new table may not end up as the last one
263     /// either. But know that you can always call Table::get_index_in_group() on
264     /// the returned table accessor to find out at which index it ends up.
265     ///
266     /// get_or_add_table() checks if a table exists in this group with the specified
267     /// name. If it doesn't exist, a table is created.
268     ///
269     /// get_or_insert_table() works slightly differently from get_or_add_table(),
270     /// in that it considers the position of the requested table as part of that
271     /// table's identifying "key", in addition to the name.
272     ///
273     /// remove_table() removes the specified table from this group. A table can
274     /// be removed only when it is not the target of a link column of a
275     /// different table. Whenever a table is removed, the order of the remaining
276     /// tables may change arbitrarily.
277     ///
278     /// rename_table() changes the name of a preexisting table. If \a
279     /// require_unique_name is false, it becomes possible to have more than one
280     /// table with a given name in a single group.
281     ///
282     /// The template functions work exactly like their non-template namesakes
283     /// except as follows: The template versions of get_table() and
284     /// get_or_add_table() throw DescriptorMismatch if the dynamic type of the
285     /// specified table does not match the statically specified custom table
286     /// type. The template versions of add_table() and get_or_add_table() set
287     /// the dynamic type (descriptor) to match the statically specified custom
288     /// table type.
289     ///
290     /// \param index Index of table in this group.
291     ///
292     /// \param name Name of table. All strings are valid table names as long as
293     /// they are valid UTF-8 encodings and the number of bytes does not exceed
294     /// `max_table_name_length`. A call to add_table() or get_or_add_table()
295     /// with a name that is longer than `max_table_name_length` will cause an
296     /// exception to be thrown.
297     ///
298     /// \param new_name New name for preexisting table.
299     ///
300     /// \param require_unique_name When set to true (the default), it becomes
301     /// impossible to add a table with a name that is already in use, or to
302     /// rename a table to a name that is already in use.
303     ///
304     /// \param was_added When specified, the boolean variable is set to true if
305     /// the table was added, and to false otherwise. If the function throws, the
306     /// boolean variable retains its original value.
307     ///
308     /// \return get_table(), add_table(), and get_or_add_table() return a table
309     /// accessor attached to the requested (or added) table. get_table() may
310     /// return null.
311     ///
312     /// \throw DescriptorMismatch Thrown by get_table() and get_or_add_table()
313     /// tf the dynamic table type does not match the statically specified custom
314     /// table type (\a T).
315     ///
316     /// \throw NoSuchTable Thrown by remove_table() and rename_table() if there
317     /// is no table with the specified \a name.
318     ///
319     /// \throw TableNameInUse Thrown by add_table() if \a require_unique_name is
320     /// true and \a name clashes with the name of a preexisting table. Thrown by
321     /// rename_table() if \a require_unique_name is true and \a new_name clashes
322     /// with the name of a preexisting table.
323     ///
324     /// \throw CrossTableLinkTarget Thrown by remove_table() if the specified
325     /// table is the target of a link column of a different table.
326     ///
327     //@{
328
329     static const size_t max_table_name_length = 63;
330
331     bool has_table(StringData name) const noexcept;
332     size_t find_table(StringData name) const noexcept;
333     StringData get_table_name(size_t table_ndx) const;
334
335     TableRef get_table(size_t index);
336     ConstTableRef get_table(size_t index) const;
337
338     TableRef get_table(StringData name);
339     ConstTableRef get_table(StringData name) const;
340
341     TableRef add_table(StringData name, bool require_unique_name = true);
342     TableRef insert_table(size_t index, StringData name, bool require_unique_name = true);
343     TableRef get_or_add_table(StringData name, bool* was_added = nullptr);
344     TableRef get_or_insert_table(size_t index, StringData name, bool* was_added = nullptr);
345
346     void remove_table(size_t index);
347     void remove_table(StringData name);
348
349     void rename_table(size_t index, StringData new_name, bool require_unique_name = true);
350     void rename_table(StringData name, StringData new_name, bool require_unique_name = true);
351
352     //@}
353
354     // Serialization
355
356     /// Write this database to the specified output stream.
357     ///
358     /// \param out The destination output stream to write to.
359     ///
360     /// \param pad If true, the file is padded to ensure the footer is aligned
361     /// to the end of a page
362     void write(std::ostream& out, bool pad = false) const;
363
364     /// Write this database to a new file. It is an error to specify a
365     /// file that already exists. This is to protect against
366     /// overwriting a database file that is currently open, which
367     /// would cause undefined behaviour.
368     ///
369     /// \param file A filesystem path.
370     ///
371     /// \param encryption_key 32-byte key used to encrypt the database file,
372     /// or nullptr to disable encryption.
373     ///
374     /// \throw util::File::AccessError If the file could not be
375     /// opened. If the reason corresponds to one of the exception
376     /// types that are derived from util::File::AccessError, the
377     /// derived exception type is thrown. In particular,
378     /// util::File::Exists will be thrown if the file exists already.
379     void write(const std::string& file, const char* encryption_key = nullptr) const;
380
381     /// Write this database to a memory buffer.
382     ///
383     /// Ownership of the returned buffer is transferred to the
384     /// caller. The memory will have been allocated using
385     /// std::malloc().
386     BinaryData write_to_mem() const;
387
388     /// Commit changes to the attached file. This requires that the
389     /// attached file is opened in read/write mode.
390     ///
391     /// Calling this function on an unattached group, a free-standing
392     /// group, a group whose attached file is opened in read-only
393     /// mode, a group that is attached to a memory buffer, or a group
394     /// that is managed by a shared group, is an error and will result
395     /// in undefined behavior.
396     ///
397     /// Table accesors will remain valid across the commit. Note that
398     /// this is not the case when working with proper transactions.
399     void commit();
400
401     //@{
402     /// Some operations on Tables in a Group can cause indirect changes to other
403     /// fields, including in other Tables in the same Group. Specifically,
404     /// removing a row will set any links to that row to null, and if it had the
405     /// last strong links to other rows, will remove those rows. When this
406     /// happens, The cascade notification handler will be called with a
407     /// CascadeNotification containing information about what indirect changes
408     /// will occur, before any changes are made.
409     ///
410     /// has_cascade_notification_handler() returns true if and only if there is
411     /// currently a non-null notification handler registered.
412     ///
413     /// set_cascade_notification_handler() replaces the current handler (if any)
414     /// with the passed in handler. Pass in nullptr to remove the current handler
415     /// without registering a new one.
416     ///
417     /// CascadeNotification contains a vector of rows which will be removed and
418     /// a vector of links which will be set to null (or removed, for entries in
419     /// LinkLists).
420     struct CascadeNotification {
421         struct row {
422             /// Non-zero iff the removal of this row is ordered
423             /// (Table::remove()), as opposed to ordered
424             /// (Table::move_last_over()). Implicit removals are always
425             /// unordered.
426             ///
427             /// This flag does not take part in comparisons (operator==() and
428             /// operator<()).
429             size_t is_ordered_removal : 1;
430
431             /// Index within group of a group-level table.
432             size_t table_ndx : std::numeric_limits<size_t>::digits - 1;
433
434             /// Row index which will be removed.
435             size_t row_ndx;
436
437             row()
438                 : is_ordered_removal(0)
439             {
440             }
441
442             bool operator==(const row&) const noexcept;
443             bool operator!=(const row&) const noexcept;
444
445             /// Trivial lexicographic order
446             bool operator<(const row&) const noexcept;
447         };
448
449         struct link {
450             const Table* origin_table; ///< A group-level table.
451             size_t origin_col_ndx;     ///< Link column being nullified.
452             size_t origin_row_ndx;     ///< Row in column being nullified.
453             /// The target row index which is being removed. Mostly relevant for
454             /// LinkList (to know which entries are being removed), but also
455             /// valid for Link.
456             size_t old_target_row_ndx;
457         };
458
459         /// A sorted list of rows which will be removed by the current operation.
460         std::vector<row> rows;
461
462         /// An unordered list of links which will be nullified by the current
463         /// operation.
464         std::vector<link> links;
465     };
466
467     bool has_cascade_notification_handler() const noexcept;
468     void set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept;
469
470     //@}
471
472     //@{
473     /// During sync operation, schema changes may happen at runtime as connected
474     /// clients update their schema as part of an app update. Since this is a
475     /// relatively rare event, no attempt is made at limiting the amount of work
476     /// the handler is required to do to update its information about table and
477     /// column indices (i.e., all table and column indices must be recalculated).
478     ///
479     /// At the time of writing, only additive schema changes may occur in that
480     /// scenario.
481     ///
482     /// has_schema_change_notification_handler() returns true iff there is currently
483     /// a non-null notification handler registered.
484     ///
485     /// set_schema_change_notification_handler() replaces the current handler (if any)
486     /// with the passed in handler. Pass in nullptr to remove the current handler
487     /// without registering a new one.
488
489     bool has_schema_change_notification_handler() const noexcept;
490     void set_schema_change_notification_handler(std::function<void()> new_handler) noexcept;
491
492     //@}
493
494     // Conversion
495     template <class S>
496     void to_json(S& out, size_t link_depth = 0, std::map<std::string, std::string>* renames = nullptr) const;
497     void to_string(std::ostream& out) const;
498
499     /// Compare two groups for equality. Two groups are equal if, and
500     /// only if, they contain the same tables in the same order, that
501     /// is, for each table T at index I in one of the groups, there is
502     /// a table at index I in the other group that is equal to T.
503     /// Tables are equal if they have the same content and the same table name.
504     bool operator==(const Group&) const;
505
506     /// Compare two groups for inequality. See operator==().
507     bool operator!=(const Group& g) const
508     {
509         return !(*this == g);
510     }
511
512     /// Compute the sum of the sizes in number of bytes of all the array nodes
513     /// that currently make up this group. When this group represents a snapshot
514     /// in a Realm file (such as during a read transaction via a SharedGroup
515     /// instance), this function computes the footprint of that snapshot within
516     /// the Realm file.
517     ///
518     /// If this group accessor is the detached state, this function returns
519     /// zero.
520     size_t compute_aggregated_byte_size() const noexcept;
521
522     void verify() const;
523 #ifdef REALM_DEBUG
524     void print() const;
525     void print_free() const;
526     MemStats stats();
527     void enable_mem_diagnostics(bool enable = true)
528     {
529         m_alloc.enable_debug(enable);
530     }
531     void to_dot(std::ostream&) const;
532     void to_dot() const; // To std::cerr (for GDB)
533     void to_dot(const char* file_path) const;
534 #endif
535
536 private:
537     SlabAlloc m_alloc;
538
539     int m_file_format_version;
540     /// `m_top` is the root node (or top array) of the Realm, and has the
541     /// following layout:
542     ///
543     /// <pre>
544     ///
545     ///                                                     Introduced in file
546     ///   Slot  Value                                       format version
547     ///   ---------------------------------------------------------------------
548     ///    1st   m_table_names
549     ///    2nd   m_tables
550     ///    3rd   Logical file size
551     ///    4th   GroupWriter::m_free_positions (optional)
552     ///    5th   GroupWriter::m_free_lengths   (optional)
553     ///    6th   GroupWriter::m_free_versions  (optional)
554     ///    7th   Transaction number / version  (optional)
555     ///    8th   History type         (optional)             4
556     ///    9th   History ref          (optional)             4
557     ///   10th   History version      (optional)             7
558     ///
559     /// </pre>
560     ///
561     /// The 'History type' slot stores a value of type
562     /// Replication::HistoryType. The 'History version' slot stores a history
563     /// schema version as returned by Replication::get_history_schema_version().
564     ///
565     /// The first three entries are mandatory. In files created by
566     /// Group::write(), none of the optional entries are present and the size of
567     /// `m_top` is 3. In files updated by Group::commit(), the 4th and 5th entry
568     /// are present, and the size of `m_top` is 5. In files updated by way of a
569     /// transaction (SharedGroup::commit()), the 4th, 5th, 6th, and 7th entry
570     /// are present, and the size of `m_top` is 7. In files that contain a
571     /// changeset history, the 8th, 9th, and 10th entry are present, except that
572     /// if the file was opened in nonshared mode (via Group::open()), and the
573     /// file format remains at 6 (not previously upgraded to 7 or later), then
574     /// the 10th entry will be absent.
575     ///
576     /// When a group accessor is attached to a newly created file or an empty
577     /// memory buffer where there is no top array yet, `m_top`, `m_tables`, and
578     /// `m_table_names` will be left in the detached state until the initiation
579     /// of the first write transaction. In particular, they will remain in the
580     /// detached state during read transactions that precede the first write
581     /// transaction.
582     Array m_top;
583     ArrayInteger m_tables;
584     ArrayString m_table_names;
585
586     typedef std::vector<Table*> table_accessors;
587     mutable table_accessors m_table_accessors;
588
589     bool m_attached = false;
590     const bool m_is_shared;
591
592     std::function<void(const CascadeNotification&)> m_notify_handler;
593     std::function<void()> m_schema_change_handler;
594     std::shared_ptr<metrics::Metrics> m_metrics;
595     size_t m_total_rows;
596
597     struct shared_tag {
598     };
599     Group(shared_tag) noexcept;
600
601     void init_array_parents() noexcept;
602
603     void open(ref_type top_ref, const std::string& file_path);
604
605     /// If `top_ref` is not zero, attach this group accessor to the specified
606     /// underlying node structure. If `top_ref` is zero and \a
607     /// create_group_when_missing is true, create a new node structure that
608     /// represents an empty group, and attach this group accessor to it. It is
609     /// an error to call this function on an already attached group accessor.
610     void attach(ref_type top_ref, bool create_group_when_missing);
611
612     /// Detach this group accessor from the underlying node structure. If this
613     /// group accessors is already in the detached state, this function does
614     /// nothing (idempotency).
615     void detach() noexcept;
616
617     /// \param writable Must be set to true when, and only when attaching for a
618     /// write transaction.
619     void attach_shared(ref_type new_top_ref, size_t new_file_size, bool writable);
620
621     void create_empty_group();
622
623     void reset_free_space_tracking();
624
625     void remap(size_t new_file_size);
626     void remap_and_update_refs(ref_type new_top_ref, size_t new_file_size);
627
628     /// Recursively update refs stored in all cached array
629     /// accessors. This includes cached array accessors in any
630     /// currently attached table accessors. This ensures that the
631     /// group instance itself, as well as any attached table accessor
632     /// that exists across Group::commit() will remain valid. This
633     /// function is not appropriate for use in conjunction with
634     /// commits via shared group.
635     void update_refs(ref_type top_ref, size_t old_baseline) noexcept;
636
637     // Overriding method in ArrayParent
638     void update_child_ref(size_t, ref_type) override;
639
640     // Overriding method in ArrayParent
641     ref_type get_child_ref(size_t) const noexcept override;
642
643     // Overriding method in Table::Parent
644     StringData get_child_name(size_t) const noexcept override;
645
646     // Overriding method in Table::Parent
647     void child_accessor_destroyed(Table*) noexcept override;
648
649     // Overriding method in Table::Parent
650     std::recursive_mutex* get_accessor_management_lock() noexcept override
651     { return nullptr; } // we don't need locking for group!
652
653     // Overriding method in Table::Parent
654     Group* get_parent_group() noexcept override;
655
656     class TableWriter;
657     class DefaultTableWriter;
658
659     static void write(std::ostream&, int file_format_version, TableWriter&, bool no_top_array,
660                       bool pad_for_encryption, uint_fast64_t version_number);
661
662     typedef void (*DescSetter)(Table&);
663     typedef bool (*DescMatcher)(const Spec&);
664
665     Table* do_get_table(size_t table_ndx, DescMatcher desc_matcher);
666     const Table* do_get_table(size_t table_ndx, DescMatcher desc_matcher) const;
667     Table* do_get_table(StringData name, DescMatcher desc_matcher);
668     const Table* do_get_table(StringData name, DescMatcher desc_matcher) const;
669     Table* do_insert_table(size_t, StringData name, DescSetter desc_setter, bool require_unique_name);
670     Table* do_insert_table(size_t, StringData name, DescSetter desc_setter);
671     Table* do_get_or_add_table(StringData name, DescMatcher desc_matcher, DescSetter setter, bool* was_added);
672     Table* do_get_or_insert_table(size_t, StringData name, DescMatcher desc_matcher, DescSetter desc_setter,
673                                   bool* was_added);
674
675     void create_and_insert_table(size_t new_table_ndx, StringData name);
676     Table* create_table_accessor(size_t table_ndx);
677
678     void detach_table_accessors() noexcept; // Idempotent
679
680     void mark_all_table_accessors() noexcept;
681
682     void write(const std::string& file, const char* encryption_key, uint_fast64_t version_number) const;
683     void write(util::File& file, const char* encryption_key, uint_fast64_t version_number) const;
684     void write(std::ostream&, bool pad, uint_fast64_t version_numer) const;
685
686     Replication* get_replication() const noexcept;
687     void set_replication(Replication*) noexcept;
688     std::shared_ptr<metrics::Metrics> get_metrics() const noexcept;
689     void set_metrics(std::shared_ptr<metrics::Metrics> other) noexcept;
690     void update_num_objects();
691     class TransactAdvancer;
692     void advance_transact(ref_type new_top_ref, size_t new_file_size, _impl::NoCopyInputStream&);
693     void refresh_dirty_accessors();
694     template <class F>
695     void update_table_indices(F&& map_function);
696
697     /// \brief The version of the format of the node structure (in file or in
698     /// memory) in use by Realm objects associated with this group.
699     ///
700     /// Every group contains a file format version field, which is returned
701     /// by this function. The file format version field is set to the file format
702     /// version specified by the attached file (or attached memory buffer) at the
703     /// time of attachment and the value is used to determine if a file format
704     /// upgrade is required.
705     ///
706     /// A value of zero means that the file format is not yet decided. This is
707     /// only possible for empty Realms where top-ref is zero. (When group is created
708     /// with the unattached_tag). The version number will then be determined in the
709     /// subsequent call to Group::open.
710     ///
711     /// In shared mode (when a Realm file is opened via a SharedGroup instance)
712     /// it can happen that the file format is upgraded asyncronously (via
713     /// another SharedGroup instance), and in that case the file format version
714     /// field can get out of date, but only for a short while. It is always
715     /// guaranteed to be, and remain up to date after the opening process completes
716     /// (when SharedGroup::do_open() returns).
717     ///
718     /// An empty Realm file (one whose top-ref is zero) may specify a file
719     /// format version of zero to indicate that the format is not yet
720     /// decided. In that case the file format version must be changed to a proper
721     /// before the opening process completes (Group::open() or SharedGroup::open()).
722     ///
723     /// File format versions:
724     ///
725     ///   1 Initial file format version
726     ///
727     ///   2 Various changes.
728     ///
729     ///   3 Supporting null on string columns broke the file format in following
730     ///     way: Index appends an 'X' character to all strings except the null
731     ///     string, to be able to distinguish between null and empty
732     ///     string. Bumped to 3 because of null support of String columns and
733     ///     because of new format of index.
734     ///
735     ///   4 Introduction of optional in-Realm history of changes (additional
736     ///     entries in Group::m_top). Since this change is not forward
737     ///     compatible, the file format version had to be bumped. This change is
738     ///     implemented in a way that achieves backwards compatibility with
739     ///     version 3 (and in turn with version 2).
740     ///
741     ///   5 Introduced the new Timestamp column type that replaces DateTime.
742     ///     When opening an older database file, all DateTime columns will be
743     ///     automatically upgraded Timestamp columns.
744     ///
745     ///   6 Introduced a new structure for the StringIndex. Moved the commit
746     ///     logs into the Realm file. Changes to the transaction log format
747     ///     including reshuffling instructions. This is the format used in
748     ///     milestone 2.0.0.
749     ///
750     ///   7 Introduced "history schema version" as 10th entry in top array.
751     ///
752     ///   8 Subtables can now have search index.
753     ///
754     ///   9 Replication instruction values shuffled, instr_MoveRow added.
755     ///
756     /// IMPORTANT: When introducing a new file format version, be sure to review
757     /// the file validity checks in Group::open() and SharedGroup::do_open, the file
758     /// format selection logic in
759     /// Group::get_target_file_format_version_for_session(), and the file format
760     /// upgrade logic in Group::upgrade_file_format().
761
762     int get_file_format_version() const noexcept;
763     void set_file_format_version(int) noexcept;
764     int get_committed_file_format_version() const noexcept;
765
766     /// The specified history type must be a value of Replication::HistoryType.
767     static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept;
768
769     /// Must be called from within a write transaction
770     void upgrade_file_format(int target_file_format_version);
771
772     std::pair<ref_type, size_t> get_to_dot_parent(size_t ndx_in_parent) const override;
773
774     void send_cascade_notification(const CascadeNotification& notification) const;
775     void send_schema_change_notification() const;
776
777     static void get_version_and_history_info(const Array& top, _impl::History::version_type& version,
778                                              int& history_type, int& history_schema_version) noexcept;
779     static ref_type get_history_ref(const Array& top) noexcept;
780     static int get_history_schema_version(const Array& top) noexcept;
781     void set_history_schema_version(int version);
782     void set_history_parent(Array& history_root) noexcept;
783     void prepare_history_parent(Array& history_root, int history_type, int history_schema_version);
784
785     friend class Table;
786     friend class GroupWriter;
787     friend class SharedGroup;
788     friend class _impl::GroupFriend;
789     friend class _impl::TransactLogConvenientEncoder;
790     friend class _impl::TransactLogParser;
791     friend class Replication;
792     friend class TrivialReplication;
793     friend class metrics::QueryInfo;
794     friend class metrics::Metrics;
795 };
796
797
798 // Implementation
799
800 inline Group::Group(const std::string& file, const char* key, OpenMode mode)
801     : m_alloc() // Throws
802     , m_top(m_alloc)
803     , m_tables(m_alloc)
804     , m_table_names(m_alloc)
805     , m_is_shared(false)
806     , m_total_rows(0)
807 {
808     init_array_parents();
809
810     open(file, key, mode); // Throws
811 }
812
813 inline Group::Group(BinaryData buffer, bool take_ownership)
814     : m_alloc() // Throws
815     , m_top(m_alloc)
816     , m_tables(m_alloc)
817     , m_table_names(m_alloc)
818     , m_is_shared(false)
819     , m_total_rows(0)
820 {
821     init_array_parents();
822     open(buffer, take_ownership); // Throws
823 }
824
825 inline Group::Group(unattached_tag) noexcept
826     : m_alloc()
827     , // Throws
828     m_top(m_alloc)
829     , m_tables(m_alloc)
830     , m_table_names(m_alloc)
831     , m_is_shared(false)
832     , m_total_rows(0)
833 {
834     init_array_parents();
835 }
836
837 inline Group* Group::get_parent_group() noexcept
838 {
839     return this;
840 }
841
842 inline Group::Group(shared_tag) noexcept
843     : m_alloc()
844     , // Throws
845     m_top(m_alloc)
846     , m_tables(m_alloc)
847     , m_table_names(m_alloc)
848     , m_is_shared(true)
849     , m_total_rows(0)
850 {
851     init_array_parents();
852 }
853
854 inline bool Group::is_attached() const noexcept
855 {
856     return m_attached;
857 }
858
859 inline bool Group::is_empty() const noexcept
860 {
861     if (!is_attached())
862         return false;
863     if (m_table_names.is_attached())
864         return m_table_names.is_empty();
865     return true;
866 }
867
868 inline size_t Group::size() const noexcept
869 {
870     if (!is_attached())
871         return 0;
872     if (m_table_names.is_attached())
873         return m_table_names.size();
874     return 0;
875 }
876
877 inline StringData Group::get_table_name(size_t table_ndx) const
878 {
879     if (table_ndx >= size())
880         throw LogicError(LogicError::table_index_out_of_range);
881     return m_table_names.get(table_ndx);
882 }
883
884 inline bool Group::has_table(StringData name) const noexcept
885 {
886     size_t ndx = find_table(name);
887     return ndx != not_found;
888 }
889
890 inline size_t Group::find_table(StringData name) const noexcept
891 {
892     if (!is_attached())
893         return 0;
894     if (m_table_names.is_attached())
895         return m_table_names.find_first(name);
896     return not_found;
897 }
898
899 inline TableRef Group::get_table(size_t table_ndx)
900 {
901     if (!is_attached())
902         throw LogicError(LogicError::detached_accessor);
903     DescMatcher desc_matcher = nullptr;                   // Do not check descriptor
904     Table* table = do_get_table(table_ndx, desc_matcher); // Throws
905     return TableRef(table);
906 }
907
908 inline ConstTableRef Group::get_table(size_t table_ndx) const
909 {
910     if (!is_attached())
911         throw LogicError(LogicError::detached_accessor);
912     DescMatcher desc_matcher = nullptr;                         // Do not check descriptor
913     const Table* table = do_get_table(table_ndx, desc_matcher); // Throws
914     return ConstTableRef(table);
915 }
916
917 inline TableRef Group::get_table(StringData name)
918 {
919     if (!is_attached())
920         throw LogicError(LogicError::detached_accessor);
921     DescMatcher desc_matcher = nullptr;              // Do not check descriptor
922     Table* table = do_get_table(name, desc_matcher); // Throws
923     return TableRef(table);
924 }
925
926 inline ConstTableRef Group::get_table(StringData name) const
927 {
928     if (!is_attached())
929         throw LogicError(LogicError::detached_accessor);
930     DescMatcher desc_matcher = nullptr;                    // Do not check descriptor
931     const Table* table = do_get_table(name, desc_matcher); // Throws
932     return ConstTableRef(table);
933 }
934
935 inline TableRef Group::insert_table(size_t table_ndx, StringData name, bool require_unique_name)
936 {
937     if (!is_attached())
938         throw LogicError(LogicError::detached_accessor);
939     DescSetter desc_setter = nullptr;                                                  // Do not add any columns
940     Table* table = do_insert_table(table_ndx, name, desc_setter, require_unique_name); // Throws
941     return TableRef(table);
942 }
943
944 inline TableRef Group::add_table(StringData name, bool require_unique_name)
945 {
946     return insert_table(size(), name, require_unique_name);
947 }
948
949 inline TableRef Group::get_or_insert_table(size_t table_ndx, StringData name, bool* was_added)
950 {
951     if (!is_attached())
952         throw LogicError(LogicError::detached_accessor);
953     DescMatcher desc_matcher = nullptr; // Do not check descriptor
954     DescSetter desc_setter = nullptr;   // Do not add any columns
955     Table* table = do_get_or_insert_table(table_ndx, name, desc_matcher, desc_setter, was_added); // Throws
956     return TableRef(table);
957 }
958
959 inline TableRef Group::get_or_add_table(StringData name, bool* was_added)
960 {
961     if (!is_attached())
962         throw LogicError(LogicError::detached_accessor);
963     DescMatcher desc_matcher = nullptr;                                             // Do not check descriptor
964     DescSetter desc_setter = nullptr;                                               // Do not add any columns
965     Table* table = do_get_or_add_table(name, desc_matcher, desc_setter, was_added); // Throws
966     return TableRef(table);
967 }
968
969 template <class S>
970 void Group::to_json(S& out, size_t link_depth, std::map<std::string, std::string>* renames) const
971 {
972     if (!is_attached())
973         throw LogicError(LogicError::detached_accessor);
974
975     std::map<std::string, std::string> renames2;
976     renames = renames ? renames : &renames2;
977
978     out << "{";
979
980     for (size_t i = 0; i < m_tables.size(); ++i) {
981         StringData name = m_table_names.get(i);
982         std::map<std::string, std::string>& m = *renames;
983         if (m[name] != "")
984             name = m[name];
985
986         ConstTableRef table = get_table(i);
987
988         if (i)
989             out << ",";
990         out << "\"" << name << "\"";
991         out << ":";
992         table->to_json(out, link_depth, renames);
993     }
994
995     out << "}";
996 }
997
998 inline void Group::init_array_parents() noexcept
999 {
1000     m_table_names.set_parent(&m_top, 0);
1001     m_tables.set_parent(&m_top, 1);
1002 }
1003
1004 inline void Group::update_child_ref(size_t child_ndx, ref_type new_ref)
1005 {
1006     m_tables.set(child_ndx, new_ref);
1007 }
1008
1009 inline ref_type Group::get_child_ref(size_t child_ndx) const noexcept
1010 {
1011     return m_tables.get_as_ref(child_ndx);
1012 }
1013
1014 inline StringData Group::get_child_name(size_t child_ndx) const noexcept
1015 {
1016     return m_table_names.get(child_ndx);
1017 }
1018
1019 inline void Group::child_accessor_destroyed(Table*) noexcept
1020 {
1021     // Ignore
1022 }
1023
1024 inline bool Group::has_cascade_notification_handler() const noexcept
1025 {
1026     return !!m_notify_handler;
1027 }
1028
1029 inline void
1030 Group::set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept
1031 {
1032     m_notify_handler = std::move(new_handler);
1033 }
1034
1035 inline void Group::send_cascade_notification(const CascadeNotification& notification) const
1036 {
1037     if (m_notify_handler)
1038         m_notify_handler(notification);
1039 }
1040
1041 inline bool Group::has_schema_change_notification_handler() const noexcept
1042 {
1043     return !!m_schema_change_handler;
1044 }
1045
1046 inline void Group::set_schema_change_notification_handler(std::function<void()> new_handler) noexcept
1047 {
1048     m_schema_change_handler = std::move(new_handler);
1049 }
1050
1051 inline void Group::send_schema_change_notification() const
1052 {
1053     if (m_schema_change_handler)
1054         m_schema_change_handler();
1055 }
1056
1057 inline void Group::get_version_and_history_info(const Array& top, _impl::History::version_type& version,
1058                                                 int& history_type, int& history_schema_version) noexcept
1059 {
1060     using version_type = _impl::History::version_type;
1061     version_type version_2 = 0;
1062     int history_type_2 = 0;
1063     int history_schema_version_2 = 0;
1064     if (top.is_attached()) {
1065         if (top.size() >= 6) {
1066             REALM_ASSERT(top.size() >= 7);
1067             version_2 = version_type(top.get_as_ref_or_tagged(6).get_as_int());
1068         }
1069         if (top.size() >= 8) {
1070             REALM_ASSERT(top.size() >= 9);
1071             history_type_2           = int(top.get_as_ref_or_tagged(7).get_as_int());
1072         }
1073         if (top.size() >= 10) {
1074             history_schema_version_2 = int(top.get_as_ref_or_tagged(9).get_as_int());
1075         }
1076     }
1077     // Version 0 is not a legal initial version, so it has to be set to 1
1078     // instead.
1079     if (version_2 == 0)
1080         version_2 = 1;
1081     version = version_2;
1082     history_type = history_type_2;
1083     history_schema_version = history_schema_version_2;
1084 }
1085
1086 inline ref_type Group::get_history_ref(const Array& top) noexcept
1087 {
1088     bool has_history = (top.is_attached() && top.size() >= 8);
1089     if (has_history) {
1090         // This function is only used is shared mode (from SharedGroup)
1091         REALM_ASSERT(top.size() >= 10);
1092         return top.get_as_ref(8);
1093     }
1094     return 0;
1095 }
1096
1097 inline int Group::get_history_schema_version(const Array& top) noexcept
1098 {
1099     bool has_history = (top.is_attached() && top.size() >= 8);
1100     if (has_history) {
1101         // This function is only used is shared mode (from SharedGroup)
1102         REALM_ASSERT(top.size() >= 10);
1103         return int(top.get_as_ref_or_tagged(9).get_as_int());
1104     }
1105     return 0;
1106 }
1107
1108 inline void Group::set_history_schema_version(int version)
1109 {
1110     // This function is only used is shared mode (from SharedGroup)
1111     REALM_ASSERT(m_top.size() >= 10);
1112     m_top.set(9, RefOrTagged::make_tagged(unsigned(version))); // Throws
1113 }
1114
1115 inline void Group::set_history_parent(Array& history_root) noexcept
1116 {
1117     history_root.set_parent(&m_top, 8);
1118 }
1119
1120 class Group::TableWriter {
1121 public:
1122     virtual ref_type write_names(_impl::OutputStream&) = 0;
1123     virtual ref_type write_tables(_impl::OutputStream&) = 0;
1124     virtual ~TableWriter() noexcept
1125     {
1126     }
1127 };
1128
1129 inline const Table* Group::do_get_table(size_t table_ndx, DescMatcher desc_matcher) const
1130 {
1131     return const_cast<Group*>(this)->do_get_table(table_ndx, desc_matcher); // Throws
1132 }
1133
1134 inline const Table* Group::do_get_table(StringData name, DescMatcher desc_matcher) const
1135 {
1136     return const_cast<Group*>(this)->do_get_table(name, desc_matcher); // Throws
1137 }
1138
1139 inline void Group::reset_free_space_tracking()
1140 {
1141     m_alloc.reset_free_space_tracking(); // Throws
1142 }
1143
1144 inline Replication* Group::get_replication() const noexcept
1145 {
1146     return m_alloc.get_replication();
1147 }
1148
1149 inline void Group::set_replication(Replication* repl) noexcept
1150 {
1151     m_alloc.set_replication(repl);
1152 }
1153
1154 inline std::shared_ptr<metrics::Metrics> Group::get_metrics() const noexcept
1155 {
1156     return m_metrics;
1157 }
1158
1159 inline void Group::set_metrics(std::shared_ptr<metrics::Metrics> shared) noexcept
1160 {
1161     m_metrics = shared;
1162 }
1163
1164 // The purpose of this class is to give internal access to some, but
1165 // not all of the non-public parts of the Group class.
1166 class _impl::GroupFriend {
1167 public:
1168     static Allocator& get_alloc(Group& group) noexcept
1169     {
1170         return group.m_alloc;
1171     }
1172
1173     static const Allocator& get_alloc(const Group& group) noexcept
1174     {
1175         return group.m_alloc;
1176     }
1177
1178     static ref_type get_top_ref(const Group& group) noexcept
1179     {
1180         return group.m_top.get_ref();
1181     }
1182
1183     static Table& get_table(Group& group, size_t ndx_in_group)
1184     {
1185         Group::DescMatcher desc_matcher = 0;                           // Do not check descriptor
1186         Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws
1187         return *table;
1188     }
1189
1190     static const Table& get_table(const Group& group, size_t ndx_in_group)
1191     {
1192         Group::DescMatcher desc_matcher = 0;                                 // Do not check descriptor
1193         const Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws
1194         return *table;
1195     }
1196
1197     static Table* get_table(Group& group, StringData name)
1198     {
1199         Group::DescMatcher desc_matcher = 0;                   // Do not check descriptor
1200         Table* table = group.do_get_table(name, desc_matcher); // Throws
1201         return table;
1202     }
1203
1204     static const Table* get_table(const Group& group, StringData name)
1205     {
1206         Group::DescMatcher desc_matcher = 0;                         // Do not check descriptor
1207         const Table* table = group.do_get_table(name, desc_matcher); // Throws
1208         return table;
1209     }
1210
1211     static Table& insert_table(Group& group, size_t table_ndx, StringData name, bool require_unique_name)
1212     {
1213         Group::DescSetter desc_setter = nullptr; // Do not add any columns
1214         return *group.do_insert_table(table_ndx, name, desc_setter, require_unique_name);
1215     }
1216
1217     static Table& add_table(Group& group, StringData name, bool require_unique_name)
1218     {
1219         return insert_table(group, group.size(), name, require_unique_name);
1220     }
1221
1222     static Table& get_or_insert_table(Group& group, size_t table_ndx, StringData name, bool* was_inserted)
1223     {
1224         Group::DescMatcher desc_matcher = nullptr; // Do not check descriptor
1225         Group::DescSetter desc_setter = nullptr;   // Do not add any columns
1226         return *group.do_get_or_insert_table(table_ndx, name, desc_matcher, desc_setter, was_inserted);
1227     }
1228
1229     static Table& get_or_add_table(Group& group, StringData name, bool* was_inserted)
1230     {
1231         Group::DescMatcher desc_matcher = nullptr; // Do not check descriptor
1232         Group::DescSetter desc_setter = nullptr;   // Do not add any columns
1233         return *group.do_get_or_add_table(name, desc_matcher, desc_setter, was_inserted);
1234     }
1235
1236     static void send_cascade_notification(const Group& group, const Group::CascadeNotification& notification)
1237     {
1238         group.send_cascade_notification(notification);
1239     }
1240
1241     static Replication* get_replication(const Group& group) noexcept
1242     {
1243         return group.get_replication();
1244     }
1245
1246     static void set_replication(Group& group, Replication* repl) noexcept
1247     {
1248         group.set_replication(repl);
1249     }
1250
1251     static void detach(Group& group) noexcept
1252     {
1253         group.detach();
1254     }
1255
1256     static void attach_shared(Group& group, ref_type new_top_ref, size_t new_file_size, bool writable)
1257     {
1258         group.attach_shared(new_top_ref, new_file_size, writable); // Throws
1259     }
1260
1261     static void reset_free_space_tracking(Group& group)
1262     {
1263         group.reset_free_space_tracking(); // Throws
1264     }
1265
1266     static void remap(Group& group, size_t new_file_size)
1267     {
1268         group.remap(new_file_size); // Throws
1269     }
1270
1271     static void remap_and_update_refs(Group& group, ref_type new_top_ref, size_t new_file_size)
1272     {
1273         group.remap_and_update_refs(new_top_ref, new_file_size); // Throws
1274     }
1275
1276     static void advance_transact(Group& group, ref_type new_top_ref, size_t new_file_size,
1277                                  _impl::NoCopyInputStream& in)
1278     {
1279         group.advance_transact(new_top_ref, new_file_size, in); // Throws
1280     }
1281
1282     static void create_empty_group_when_missing(Group& group)
1283     {
1284         if (!group.m_top.is_attached())
1285             group.create_empty_group(); // Throws
1286     }
1287
1288     static void get_version_and_history_info(const Allocator& alloc, ref_type top_ref,
1289                                              _impl::History::version_type& version,
1290                                              int& history_type,
1291                                              int& history_schema_version) noexcept
1292     {
1293         Array top{const_cast<Allocator&>(alloc)};
1294         if (top_ref != 0)
1295             top.init_from_ref(top_ref);
1296         Group::get_version_and_history_info(top, version, history_type, history_schema_version);
1297     }
1298
1299     static ref_type get_history_ref(const Group& group) noexcept
1300     {
1301         return Group::get_history_ref(group.m_top);
1302     }
1303
1304     static ref_type get_history_ref(Allocator& alloc, ref_type top_ref) noexcept
1305     {
1306         Array top(alloc);
1307         if (top_ref != 0)
1308             top.init_from_ref(top_ref);
1309         return Group::get_history_ref(top);
1310     }
1311
1312     static int get_history_schema_version(const Group& group) noexcept
1313     {
1314         return Group::get_history_schema_version(group.m_top);
1315     }
1316
1317     static int get_history_schema_version(Allocator& alloc, ref_type top_ref) noexcept
1318     {
1319         Array top{alloc};
1320         if (top_ref != 0)
1321             top.init_from_ref(top_ref);
1322         return Group::get_history_schema_version(top);
1323     }
1324
1325     static void set_history_schema_version(Group& group, int version)
1326     {
1327         group.set_history_schema_version(version); // Throws
1328     }
1329
1330     static void set_history_parent(Group& group, Array& history_root) noexcept
1331     {
1332         group.set_history_parent(history_root);
1333     }
1334
1335     static void prepare_history_parent(Group& group, Array& history_root, int history_type,
1336                                        int history_schema_version)
1337     {
1338         group.prepare_history_parent(history_root, history_type, history_schema_version); // Throws
1339     }
1340
1341     static int get_file_format_version(const Group& group) noexcept
1342     {
1343         return group.get_file_format_version();
1344     }
1345
1346     static void set_file_format_version(Group& group, int file_format_version) noexcept
1347     {
1348         group.set_file_format_version(file_format_version);
1349     }
1350
1351     static int get_committed_file_format_version(const Group& group) noexcept
1352     {
1353         return group.get_committed_file_format_version();
1354     }
1355
1356     static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept
1357     {
1358         return Group::get_target_file_format_version_for_session(current_file_format_version, history_type);
1359     }
1360
1361     static void upgrade_file_format(Group& group, int target_file_format_version)
1362     {
1363         group.upgrade_file_format(target_file_format_version); // Throws
1364     }
1365 };
1366
1367
1368 struct CascadeState : Group::CascadeNotification {
1369     /// If non-null, then no recursion will be performed for rows of that
1370     /// table. The effect is then exactly as if all the rows of that table were
1371     /// added to \a state.rows initially, and then removed again after the
1372     /// explicit invocations of Table::cascade_break_backlinks_to() (one for
1373     /// each initiating row). This is used by Table::clear() to avoid
1374     /// reentrance.
1375     ///
1376     /// Must never be set concurrently with stop_on_link_list_column.
1377     Table* stop_on_table = nullptr;
1378
1379     /// If non-null, then Table::cascade_break_backlinks_to() will skip the
1380     /// removal of reciprocal backlinks for the link list at
1381     /// stop_on_link_list_row_ndx in this column, and no recursion will happen
1382     /// on its behalf. This is used by LinkView::clear() to avoid reentrance.
1383     ///
1384     /// Must never be set concurrently with stop_on_table.
1385     LinkListColumn* stop_on_link_list_column = nullptr;
1386
1387     /// Is ignored if stop_on_link_list_column is null.
1388     size_t stop_on_link_list_row_ndx = 0;
1389
1390     /// If false, the links field is not needed, so any work done just for that
1391     /// can be skipped.
1392     bool track_link_nullifications = false;
1393
1394     /// If false, weak links are followed too
1395     bool only_strong_links = true;
1396 };
1397
1398 inline bool Group::CascadeNotification::row::operator==(const row& r) const noexcept
1399 {
1400     return table_ndx == r.table_ndx && row_ndx == r.row_ndx;
1401 }
1402
1403 inline bool Group::CascadeNotification::row::operator!=(const row& r) const noexcept
1404 {
1405     return !(*this == r);
1406 }
1407
1408 inline bool Group::CascadeNotification::row::operator<(const row& r) const noexcept
1409 {
1410     return table_ndx < r.table_ndx || (table_ndx == r.table_ndx && row_ndx < r.row_ndx);
1411 }
1412
1413 } // namespace realm
1414
1415 #endif // REALM_GROUP_HPP