1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 **************************************************************************/
19 #ifndef REALM_GROUP_HPP
20 #define REALM_GROUP_HPP
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>
42 class TransactLogConvenientEncoder;
43 class TransactLogParser;
47 /// A group is a collection of named tables.
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.
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:
61 /// table == group.get_table(table.get_index_in_group())
65 class Group : private Table::Parent {
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.
73 /// Open in read-only mode. Fail if the file does not already exist.
75 /// Open in read/write mode. Create the file if it doesn't exist.
77 /// Open in read/write mode. Fail if the file does not already exist.
78 mode_ReadWriteNoCreate
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);
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
90 explicit Group(BinaryData, bool take_ownership = true);
92 struct unattached_tag {
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
102 Group(unattached_tag) noexcept;
104 // FIXME: Implement a proper copy constructor (fairly trivial).
105 Group(const Group&) = delete;
106 Group& operator=(const Group&) = delete;
108 ~Group() noexcept override;
110 /// Attach this Group instance to the specified database file.
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.
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.
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.
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().
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()).
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.
157 /// Calling open() on a Group instance that is already in the
158 /// attached state has undefined behavior.
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.
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.
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.
182 /// \param file File system path to a Realm database file.
184 /// \param encryption_key 32-byte key used to encrypt and decrypt
185 /// the database file, or nullptr to disable encryption.
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
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);
200 /// Attach this Group instance to the specified memory buffer.
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.
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().
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.
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`.
220 /// Calling open() on a Group instance that is already in the
221 /// attached state has undefined behavior.
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.
226 /// \throw InvalidDatabase If the specified buffer does not appear
227 /// to contain a valid database.
228 void open(BinaryData, bool take_ownership = true);
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;
236 /// Returns true if, and only if the number of tables in this
238 bool is_empty() const noexcept;
240 /// Returns the number of tables in this group.
241 size_t size() const noexcept;
243 /// \defgroup group_table_access Table Accessors
245 /// has_table() returns true if, and only if this group contains a table
246 /// with the specified name.
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.
252 /// get_table_name() returns the name of table at the specified index.
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.
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.
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.
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.
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.
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.
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
290 /// \param index Index of table in this group.
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.
298 /// \param new_name New name for preexisting table.
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.
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.
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
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).
316 /// \throw NoSuchTable Thrown by remove_table() and rename_table() if there
317 /// is no table with the specified \a name.
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.
324 /// \throw CrossTableLinkTarget Thrown by remove_table() if the specified
325 /// table is the target of a link column of a different table.
329 static const size_t max_table_name_length = 63;
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;
335 TableRef get_table(size_t index);
336 ConstTableRef get_table(size_t index) const;
338 TableRef get_table(StringData name);
339 ConstTableRef get_table(StringData name) const;
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);
346 void remove_table(size_t index);
347 void remove_table(StringData name);
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);
356 /// Write this database to the specified output stream.
358 /// \param out The destination output stream to write to.
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;
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.
369 /// \param file A filesystem path.
371 /// \param encryption_key 32-byte key used to encrypt the database file,
372 /// or nullptr to disable encryption.
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;
381 /// Write this database to a memory buffer.
383 /// Ownership of the returned buffer is transferred to the
384 /// caller. The memory will have been allocated using
386 BinaryData write_to_mem() const;
388 /// Commit changes to the attached file. This requires that the
389 /// attached file is opened in read/write mode.
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.
397 /// Table accesors will remain valid across the commit. Note that
398 /// this is not the case when working with proper transactions.
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.
410 /// has_cascade_notification_handler() returns true if and only if there is
411 /// currently a non-null notification handler registered.
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.
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
420 struct CascadeNotification {
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
427 /// This flag does not take part in comparisons (operator==() and
429 size_t is_ordered_removal : 1;
431 /// Index within group of a group-level table.
432 size_t table_ndx : std::numeric_limits<size_t>::digits - 1;
434 /// Row index which will be removed.
438 : is_ordered_removal(0)
442 bool operator==(const row&) const noexcept;
443 bool operator!=(const row&) const noexcept;
445 /// Trivial lexicographic order
446 bool operator<(const row&) const noexcept;
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
456 size_t old_target_row_ndx;
459 /// A sorted list of rows which will be removed by the current operation.
460 std::vector<row> rows;
462 /// An unordered list of links which will be nullified by the current
464 std::vector<link> links;
467 bool has_cascade_notification_handler() const noexcept;
468 void set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept;
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).
479 /// At the time of writing, only additive schema changes may occur in that
482 /// has_schema_change_notification_handler() returns true iff there is currently
483 /// a non-null notification handler registered.
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.
489 bool has_schema_change_notification_handler() const noexcept;
490 void set_schema_change_notification_handler(std::function<void()> new_handler) noexcept;
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;
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;
506 /// Compare two groups for inequality. See operator==().
507 bool operator!=(const Group& g) const
509 return !(*this == g);
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
518 /// If this group accessor is the detached state, this function returns
520 size_t compute_aggregated_byte_size() const noexcept;
525 void print_free() const;
527 void enable_mem_diagnostics(bool enable = true)
529 m_alloc.enable_debug(enable);
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;
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:
545 /// Introduced in file
546 /// Slot Value format version
547 /// ---------------------------------------------------------------------
548 /// 1st m_table_names
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
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().
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.
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
583 ArrayInteger m_tables;
584 ArrayString m_table_names;
586 typedef std::vector<Table*> table_accessors;
587 mutable table_accessors m_table_accessors;
589 bool m_attached = false;
590 const bool m_is_shared;
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;
599 Group(shared_tag) noexcept;
601 void init_array_parents() noexcept;
603 void open(ref_type top_ref, const std::string& file_path);
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);
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;
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);
621 void create_empty_group();
623 void reset_free_space_tracking();
625 void remap(size_t new_file_size);
626 void remap_and_update_refs(ref_type new_top_ref, size_t new_file_size);
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;
637 // Overriding method in ArrayParent
638 void update_child_ref(size_t, ref_type) override;
640 // Overriding method in ArrayParent
641 ref_type get_child_ref(size_t) const noexcept override;
643 // Overriding method in Table::Parent
644 StringData get_child_name(size_t) const noexcept override;
646 // Overriding method in Table::Parent
647 void child_accessor_destroyed(Table*) noexcept override;
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!
653 // Overriding method in Table::Parent
654 Group* get_parent_group() noexcept override;
657 class DefaultTableWriter;
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);
662 typedef void (*DescSetter)(Table&);
663 typedef bool (*DescMatcher)(const Spec&);
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,
675 void create_and_insert_table(size_t new_table_ndx, StringData name);
676 Table* create_table_accessor(size_t table_ndx);
678 void detach_table_accessors() noexcept; // Idempotent
680 void mark_all_table_accessors() noexcept;
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;
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();
695 void update_table_indices(F&& map_function);
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.
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.
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.
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).
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()).
723 /// File format versions:
725 /// 1 Initial file format version
727 /// 2 Various changes.
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.
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).
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.
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
750 /// 7 Introduced "history schema version" as 10th entry in top array.
752 /// 8 Subtables can now have search index.
754 /// 9 Replication instruction values shuffled, instr_MoveRow added.
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().
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;
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;
769 /// Must be called from within a write transaction
770 void upgrade_file_format(int target_file_format_version);
772 std::pair<ref_type, size_t> get_to_dot_parent(size_t ndx_in_parent) const override;
774 void send_cascade_notification(const CascadeNotification& notification) const;
775 void send_schema_change_notification() const;
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);
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;
800 inline Group::Group(const std::string& file, const char* key, OpenMode mode)
801 : m_alloc() // Throws
804 , m_table_names(m_alloc)
808 init_array_parents();
810 open(file, key, mode); // Throws
813 inline Group::Group(BinaryData buffer, bool take_ownership)
814 : m_alloc() // Throws
817 , m_table_names(m_alloc)
821 init_array_parents();
822 open(buffer, take_ownership); // Throws
825 inline Group::Group(unattached_tag) noexcept
830 , m_table_names(m_alloc)
834 init_array_parents();
837 inline Group* Group::get_parent_group() noexcept
842 inline Group::Group(shared_tag) noexcept
847 , m_table_names(m_alloc)
851 init_array_parents();
854 inline bool Group::is_attached() const noexcept
859 inline bool Group::is_empty() const noexcept
863 if (m_table_names.is_attached())
864 return m_table_names.is_empty();
868 inline size_t Group::size() const noexcept
872 if (m_table_names.is_attached())
873 return m_table_names.size();
877 inline StringData Group::get_table_name(size_t table_ndx) const
879 if (table_ndx >= size())
880 throw LogicError(LogicError::table_index_out_of_range);
881 return m_table_names.get(table_ndx);
884 inline bool Group::has_table(StringData name) const noexcept
886 size_t ndx = find_table(name);
887 return ndx != not_found;
890 inline size_t Group::find_table(StringData name) const noexcept
894 if (m_table_names.is_attached())
895 return m_table_names.find_first(name);
899 inline TableRef Group::get_table(size_t table_ndx)
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);
908 inline ConstTableRef Group::get_table(size_t table_ndx) const
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);
917 inline TableRef Group::get_table(StringData name)
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);
926 inline ConstTableRef Group::get_table(StringData name) const
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);
935 inline TableRef Group::insert_table(size_t table_ndx, StringData name, bool require_unique_name)
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);
944 inline TableRef Group::add_table(StringData name, bool require_unique_name)
946 return insert_table(size(), name, require_unique_name);
949 inline TableRef Group::get_or_insert_table(size_t table_ndx, StringData name, bool* was_added)
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);
959 inline TableRef Group::get_or_add_table(StringData name, bool* was_added)
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);
970 void Group::to_json(S& out, size_t link_depth, std::map<std::string, std::string>* renames) const
973 throw LogicError(LogicError::detached_accessor);
975 std::map<std::string, std::string> renames2;
976 renames = renames ? renames : &renames2;
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;
986 ConstTableRef table = get_table(i);
990 out << "\"" << name << "\"";
992 table->to_json(out, link_depth, renames);
998 inline void Group::init_array_parents() noexcept
1000 m_table_names.set_parent(&m_top, 0);
1001 m_tables.set_parent(&m_top, 1);
1004 inline void Group::update_child_ref(size_t child_ndx, ref_type new_ref)
1006 m_tables.set(child_ndx, new_ref);
1009 inline ref_type Group::get_child_ref(size_t child_ndx) const noexcept
1011 return m_tables.get_as_ref(child_ndx);
1014 inline StringData Group::get_child_name(size_t child_ndx) const noexcept
1016 return m_table_names.get(child_ndx);
1019 inline void Group::child_accessor_destroyed(Table*) noexcept
1024 inline bool Group::has_cascade_notification_handler() const noexcept
1026 return !!m_notify_handler;
1030 Group::set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept
1032 m_notify_handler = std::move(new_handler);
1035 inline void Group::send_cascade_notification(const CascadeNotification& notification) const
1037 if (m_notify_handler)
1038 m_notify_handler(notification);
1041 inline bool Group::has_schema_change_notification_handler() const noexcept
1043 return !!m_schema_change_handler;
1046 inline void Group::set_schema_change_notification_handler(std::function<void()> new_handler) noexcept
1048 m_schema_change_handler = std::move(new_handler);
1051 inline void Group::send_schema_change_notification() const
1053 if (m_schema_change_handler)
1054 m_schema_change_handler();
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
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());
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());
1073 if (top.size() >= 10) {
1074 history_schema_version_2 = int(top.get_as_ref_or_tagged(9).get_as_int());
1077 // Version 0 is not a legal initial version, so it has to be set to 1
1081 version = version_2;
1082 history_type = history_type_2;
1083 history_schema_version = history_schema_version_2;
1086 inline ref_type Group::get_history_ref(const Array& top) noexcept
1088 bool has_history = (top.is_attached() && top.size() >= 8);
1090 // This function is only used is shared mode (from SharedGroup)
1091 REALM_ASSERT(top.size() >= 10);
1092 return top.get_as_ref(8);
1097 inline int Group::get_history_schema_version(const Array& top) noexcept
1099 bool has_history = (top.is_attached() && top.size() >= 8);
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());
1108 inline void Group::set_history_schema_version(int version)
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
1115 inline void Group::set_history_parent(Array& history_root) noexcept
1117 history_root.set_parent(&m_top, 8);
1120 class Group::TableWriter {
1122 virtual ref_type write_names(_impl::OutputStream&) = 0;
1123 virtual ref_type write_tables(_impl::OutputStream&) = 0;
1124 virtual ~TableWriter() noexcept
1129 inline const Table* Group::do_get_table(size_t table_ndx, DescMatcher desc_matcher) const
1131 return const_cast<Group*>(this)->do_get_table(table_ndx, desc_matcher); // Throws
1134 inline const Table* Group::do_get_table(StringData name, DescMatcher desc_matcher) const
1136 return const_cast<Group*>(this)->do_get_table(name, desc_matcher); // Throws
1139 inline void Group::reset_free_space_tracking()
1141 m_alloc.reset_free_space_tracking(); // Throws
1144 inline Replication* Group::get_replication() const noexcept
1146 return m_alloc.get_replication();
1149 inline void Group::set_replication(Replication* repl) noexcept
1151 m_alloc.set_replication(repl);
1154 inline std::shared_ptr<metrics::Metrics> Group::get_metrics() const noexcept
1159 inline void Group::set_metrics(std::shared_ptr<metrics::Metrics> shared) noexcept
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 {
1168 static Allocator& get_alloc(Group& group) noexcept
1170 return group.m_alloc;
1173 static const Allocator& get_alloc(const Group& group) noexcept
1175 return group.m_alloc;
1178 static ref_type get_top_ref(const Group& group) noexcept
1180 return group.m_top.get_ref();
1183 static Table& get_table(Group& group, size_t ndx_in_group)
1185 Group::DescMatcher desc_matcher = 0; // Do not check descriptor
1186 Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws
1190 static const Table& get_table(const Group& group, size_t ndx_in_group)
1192 Group::DescMatcher desc_matcher = 0; // Do not check descriptor
1193 const Table* table = group.do_get_table(ndx_in_group, desc_matcher); // Throws
1197 static Table* get_table(Group& group, StringData name)
1199 Group::DescMatcher desc_matcher = 0; // Do not check descriptor
1200 Table* table = group.do_get_table(name, desc_matcher); // Throws
1204 static const Table* get_table(const Group& group, StringData name)
1206 Group::DescMatcher desc_matcher = 0; // Do not check descriptor
1207 const Table* table = group.do_get_table(name, desc_matcher); // Throws
1211 static Table& insert_table(Group& group, size_t table_ndx, StringData name, bool require_unique_name)
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);
1217 static Table& add_table(Group& group, StringData name, bool require_unique_name)
1219 return insert_table(group, group.size(), name, require_unique_name);
1222 static Table& get_or_insert_table(Group& group, size_t table_ndx, StringData name, bool* was_inserted)
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);
1229 static Table& get_or_add_table(Group& group, StringData name, bool* was_inserted)
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);
1236 static void send_cascade_notification(const Group& group, const Group::CascadeNotification& notification)
1238 group.send_cascade_notification(notification);
1241 static Replication* get_replication(const Group& group) noexcept
1243 return group.get_replication();
1246 static void set_replication(Group& group, Replication* repl) noexcept
1248 group.set_replication(repl);
1251 static void detach(Group& group) noexcept
1256 static void attach_shared(Group& group, ref_type new_top_ref, size_t new_file_size, bool writable)
1258 group.attach_shared(new_top_ref, new_file_size, writable); // Throws
1261 static void reset_free_space_tracking(Group& group)
1263 group.reset_free_space_tracking(); // Throws
1266 static void remap(Group& group, size_t new_file_size)
1268 group.remap(new_file_size); // Throws
1271 static void remap_and_update_refs(Group& group, ref_type new_top_ref, size_t new_file_size)
1273 group.remap_and_update_refs(new_top_ref, new_file_size); // Throws
1276 static void advance_transact(Group& group, ref_type new_top_ref, size_t new_file_size,
1277 _impl::NoCopyInputStream& in)
1279 group.advance_transact(new_top_ref, new_file_size, in); // Throws
1282 static void create_empty_group_when_missing(Group& group)
1284 if (!group.m_top.is_attached())
1285 group.create_empty_group(); // Throws
1288 static void get_version_and_history_info(const Allocator& alloc, ref_type top_ref,
1289 _impl::History::version_type& version,
1291 int& history_schema_version) noexcept
1293 Array top{const_cast<Allocator&>(alloc)};
1295 top.init_from_ref(top_ref);
1296 Group::get_version_and_history_info(top, version, history_type, history_schema_version);
1299 static ref_type get_history_ref(const Group& group) noexcept
1301 return Group::get_history_ref(group.m_top);
1304 static ref_type get_history_ref(Allocator& alloc, ref_type top_ref) noexcept
1308 top.init_from_ref(top_ref);
1309 return Group::get_history_ref(top);
1312 static int get_history_schema_version(const Group& group) noexcept
1314 return Group::get_history_schema_version(group.m_top);
1317 static int get_history_schema_version(Allocator& alloc, ref_type top_ref) noexcept
1321 top.init_from_ref(top_ref);
1322 return Group::get_history_schema_version(top);
1325 static void set_history_schema_version(Group& group, int version)
1327 group.set_history_schema_version(version); // Throws
1330 static void set_history_parent(Group& group, Array& history_root) noexcept
1332 group.set_history_parent(history_root);
1335 static void prepare_history_parent(Group& group, Array& history_root, int history_type,
1336 int history_schema_version)
1338 group.prepare_history_parent(history_root, history_type, history_schema_version); // Throws
1341 static int get_file_format_version(const Group& group) noexcept
1343 return group.get_file_format_version();
1346 static void set_file_format_version(Group& group, int file_format_version) noexcept
1348 group.set_file_format_version(file_format_version);
1351 static int get_committed_file_format_version(const Group& group) noexcept
1353 return group.get_committed_file_format_version();
1356 static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept
1358 return Group::get_target_file_format_version_for_session(current_file_format_version, history_type);
1361 static void upgrade_file_format(Group& group, int target_file_format_version)
1363 group.upgrade_file_format(target_file_format_version); // Throws
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
1376 /// Must never be set concurrently with stop_on_link_list_column.
1377 Table* stop_on_table = nullptr;
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.
1384 /// Must never be set concurrently with stop_on_table.
1385 LinkListColumn* stop_on_link_list_column = nullptr;
1387 /// Is ignored if stop_on_link_list_column is null.
1388 size_t stop_on_link_list_row_ndx = 0;
1390 /// If false, the links field is not needed, so any work done just for that
1392 bool track_link_nullifications = false;
1394 /// If false, weak links are followed too
1395 bool only_strong_links = true;
1398 inline bool Group::CascadeNotification::row::operator==(const row& r) const noexcept
1400 return table_ndx == r.table_ndx && row_ndx == r.row_ndx;
1403 inline bool Group::CascadeNotification::row::operator!=(const row& r) const noexcept
1405 return !(*this == r);
1408 inline bool Group::CascadeNotification::row::operator<(const row& r) const noexcept
1410 return table_ndx < r.table_ndx || (table_ndx == r.table_ndx && row_ndx < r.row_ndx);
1413 } // namespace realm
1415 #endif // REALM_GROUP_HPP