group.hpp 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339
  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. #ifndef REALM_GROUP_HPP
  19. #define REALM_GROUP_HPP
  20. #include <functional>
  21. #include <string>
  22. #include <vector>
  23. #include <map>
  24. #include <stdexcept>
  25. #include <optional>
  26. #include <realm/util/features.h>
  27. #include <realm/exceptions.hpp>
  28. #include <realm/impl/input_stream.hpp>
  29. #include <realm/impl/output_stream.hpp>
  30. #include <realm/impl/cont_transact_hist.hpp>
  31. #include <realm/metrics/metrics.hpp>
  32. #include <realm/table.hpp>
  33. #include <realm/alloc_slab.hpp>
  34. namespace realm {
  35. class DB;
  36. class TableKeys;
  37. namespace _impl {
  38. class GroupFriend;
  39. class TransactLogConvenientEncoder;
  40. class TransactLogParser;
  41. } // namespace _impl
  42. /// A group is a collection of named tables.
  43. ///
  44. class Group : public ArrayParent {
  45. public:
  46. /// Construct a free-standing group. This group instance will be
  47. /// in the attached state, but neither associated with a file, nor
  48. /// with an external memory buffer.
  49. Group();
  50. enum OpenMode {
  51. /// Open in read-only mode. Fail if the file does not already exist.
  52. mode_ReadOnly,
  53. /// Open in read/write mode. Create the file if it doesn't exist.
  54. mode_ReadWrite,
  55. /// Open in read/write mode. Fail if the file does not already exist.
  56. mode_ReadWriteNoCreate
  57. };
  58. /// Equivalent to calling open(const std::string&, const char*, OpenMode)
  59. /// on an unattached group accessor.
  60. explicit Group(const std::string& file, const char* encryption_key = nullptr, OpenMode = mode_ReadOnly);
  61. /// Equivalent to calling open(BinaryData, bool) on an unattached
  62. /// group accessor. Note that if this constructor throws, the
  63. /// ownership of the memory buffer will remain with the caller,
  64. /// regardless of whether \a take_ownership is set to `true` or
  65. /// `false`.
  66. explicit Group(BinaryData, bool take_ownership = true);
  67. struct unattached_tag {
  68. };
  69. /// Create a Group instance in its unattached state. It may then
  70. /// be attached to a database file later by calling one of the
  71. /// open() methods. You may test whether this instance is
  72. /// currently in its attached state by calling
  73. /// is_attached(). Calling any other method (except the
  74. /// destructor) while in the unattached state has undefined
  75. /// behavior.
  76. Group(unattached_tag) noexcept;
  77. Group(const Group&) = delete;
  78. Group& operator=(const Group&) = delete;
  79. ~Group() noexcept override;
  80. /// Attach this Group instance to the specified database file.
  81. ///
  82. /// By default, the specified file is opened in read-only mode
  83. /// (mode_ReadOnly). This allows opening a file even when the
  84. /// caller lacks permission to write to that file. The opened
  85. /// group may still be modified freely, but the changes cannot be
  86. /// written back to the same file using the commit() function. An
  87. /// attempt to do that, will cause an exception to be thrown. When
  88. /// opening in read-only mode, it is an error if the specified
  89. /// file does not already exist in the file system.
  90. ///
  91. /// Alternatively, the file can be opened in read/write mode
  92. /// (mode_ReadWrite). This allows use of the commit() function,
  93. /// but, of course, it also requires that the caller has
  94. /// permission to write to the specified file. When opening in
  95. /// read-write mode, an attempt to create the specified file will
  96. /// be made, if it does not already exist in the file system.
  97. ///
  98. /// In any case, if the file already exists, it must contain a
  99. /// valid Realm database. In many cases invalidity will be
  100. /// detected and cause the InvalidDatabase exception to be thrown,
  101. /// but you should not rely on it.
  102. ///
  103. /// Note that changes made to the database via a Group instance
  104. /// are not automatically committed to the specified file. You
  105. /// may, however, at any time, explicitly commit your changes by
  106. /// calling the commit() method, provided that the specified
  107. /// open-mode is not mode_ReadOnly. Alternatively, you may call
  108. /// write() to write the entire database to a new file. Writing
  109. /// the database to a new file does not end, or in any other way
  110. /// change the association between the Group instance and the file
  111. /// that was specified in the call to open().
  112. ///
  113. /// A Realm file that contains a history (see Replication::HistoryType) may
  114. /// be opened via Group::open(), as long as the application can ensure that
  115. /// there is no concurrent access to the file (see below for more on
  116. /// concurrency), but if the file is modified via Group::commit() the
  117. /// history will be discarded. To retain the history, the application must
  118. /// instead access the file in shared mode, i.e., via DB, and
  119. /// supply the right kind of replication plugin (see
  120. /// Replication::get_history_type()).
  121. ///
  122. /// A file that is passed to Group::open(), may not be modified by
  123. /// a third party until after the Group object is
  124. /// destroyed. Behavior is undefined if a file is modified by a
  125. /// third party while any Group object is associated with it.
  126. ///
  127. /// Calling open() on a Group instance that is already in the
  128. /// attached state has undefined behavior.
  129. ///
  130. /// Accessing a Realm database file through manual construction
  131. /// of a Group object does not offer any level of thread safety or
  132. /// transaction safety. When any of those kinds of safety are a
  133. /// concern, consider using a DB instead. When accessing
  134. /// a database file in read/write mode through a manually
  135. /// constructed Group object, it is entirely the responsibility of
  136. /// the application that the file is not accessed in any way by a
  137. /// third party during the life-time of that group object. It is,
  138. /// on the other hand, safe to concurrently access a database file
  139. /// by multiple manually created Group objects, as long as all of
  140. /// them are opened in read-only mode, and there is no other party
  141. /// that modifies the file concurrently.
  142. ///
  143. /// Do not call this function on a group instance that is managed
  144. /// by a shared group. Doing so will result in undefined behavior.
  145. ///
  146. /// Even if this function throws, it may have the side-effect of
  147. /// creating the specified file, and the file may get left behind
  148. /// in an invalid state. Of course, this can only happen if
  149. /// read/write mode (mode_ReadWrite) was requested, and the file
  150. /// did not already exist.
  151. ///
  152. /// \param file File system path to a Realm database file.
  153. ///
  154. /// \param encryption_key 32-byte key used to encrypt and decrypt
  155. /// the database file, or nullptr to disable encryption.
  156. ///
  157. /// \param mode Specifying a mode that is not mode_ReadOnly
  158. /// requires that the specified file can be opened in read/write
  159. /// mode. In general there is no reason to open a group in
  160. /// read/write mode unless you want to be able to call
  161. /// Group::commit().
  162. ///
  163. /// \throw util::File::AccessError If the file could not be
  164. /// opened. If the reason corresponds to one of the exception
  165. /// types that are derived from util::File::AccessError, the
  166. /// derived exception type is thrown. Note that InvalidDatabase is
  167. /// among these derived exception types.
  168. void open(const std::string& file, const char* encryption_key = nullptr, OpenMode mode = mode_ReadOnly);
  169. /// Attach this Group instance to the specified memory buffer.
  170. ///
  171. /// This is similar to constructing a group from a file except
  172. /// that in this case the database is assumed to be stored in the
  173. /// specified memory buffer.
  174. ///
  175. /// If \a take_ownership is `true`, you pass the ownership of the
  176. /// specified buffer to the group. In this case the buffer will
  177. /// eventually be freed using std::free(), so the buffer you pass,
  178. /// must have been allocated using std::malloc().
  179. ///
  180. /// On the other hand, if \a take_ownership is set to `false`, it
  181. /// is your responsibility to keep the memory buffer alive during
  182. /// the lifetime of the group, and in case the buffer needs to be
  183. /// deallocated afterwards, that is your responsibility too.
  184. ///
  185. /// If this function throws, the ownership of the memory buffer
  186. /// will remain with the caller, regardless of whether \a
  187. /// take_ownership is set to `true` or `false`.
  188. ///
  189. /// Calling open() on a Group instance that is already in the
  190. /// attached state has undefined behavior.
  191. ///
  192. /// Do not call this function on a group instance that is managed
  193. /// by a shared group. Doing so will result in undefined behavior.
  194. ///
  195. /// \throw InvalidDatabase If the specified buffer does not appear
  196. /// to contain a valid database.
  197. void open(BinaryData, bool take_ownership = true);
  198. /// A group may be created in the unattached state, and then later
  199. /// attached to a file with a call to open(). Calling any method
  200. /// other than open(), and is_attached() on an unattached instance
  201. /// results in undefined behavior.
  202. bool is_attached() const noexcept;
  203. /// A group is frozen only if it is actually a frozen transaction.
  204. virtual bool is_frozen() const noexcept
  205. {
  206. return false;
  207. }
  208. /// Returns true if, and only if the number of tables in this
  209. /// group is zero.
  210. bool is_empty() const noexcept;
  211. size_t size() const noexcept;
  212. int get_history_schema_version() noexcept;
  213. Replication* get_replication() const
  214. {
  215. return *get_repl();
  216. }
  217. /// The sync file id is set when a client synchronizes with the server for the
  218. /// first time. It is used when generating GlobalKeys for tables without a primary
  219. /// key, where it is used as the "hi" part. This ensures global uniqueness of
  220. /// GlobalKeys.
  221. uint64_t get_sync_file_id() const noexcept;
  222. void set_sync_file_id(uint64_t id);
  223. void remove_sync_file_id();
  224. /// Returns the keys for all tables in this group.
  225. TableKeys get_table_keys() const;
  226. /// \defgroup group_table_access Table Accessors
  227. ///
  228. /// has_table() returns true if, and only if this group contains a table
  229. /// with the specified name.
  230. ///
  231. /// find_table() returns the key of the first table in this group with the
  232. /// specified name, or `realm::not_found` if this group does not contain a
  233. /// table with the specified name.
  234. ///
  235. /// get_table_name() returns the name of table with the specified key.
  236. ///
  237. /// The versions of get_table(), that accepts a \a name argument, return a
  238. /// table with the specified name, or null if no such table exists.
  239. ///
  240. /// add_table() adds a table with the specified name to this group. It
  241. /// throws TableNameInUse if \a require_unique_name is true and \a name
  242. /// clashes with the name of an existing table. If \a require_unique_name is
  243. /// false, it is possible to add more than one table with the same
  244. /// name. Whenever a table is added the key assigned to it is returned.
  245. ///
  246. /// get_or_add_table() checks if a table exists in this group with the specified
  247. /// name. If it doesn't exist, a table is created.
  248. ///
  249. /// remove_table() removes the specified table from this group. A table can
  250. /// be removed only when it is not the target of a link column of a
  251. /// different table.
  252. ///
  253. /// rename_table() changes the name of a preexisting table. If \a
  254. /// require_unique_name is false, it becomes possible to have more than one
  255. /// table with a given name in a single group.
  256. ///
  257. /// The template functions work exactly like their non-template namesakes
  258. /// except as follows: The template versions of get_table() and
  259. /// get_or_add_table() throw DescriptorMismatch if the dynamic type of the
  260. /// specified table does not match the statically specified custom table
  261. /// type. The template versions of add_table() and get_or_add_table() set
  262. /// the dynamic type (descriptor) to match the statically specified custom
  263. /// table type.
  264. ///
  265. /// \param key Key of table in this group.
  266. ///
  267. /// \param name Name of table. All strings are valid table names as long as
  268. /// they are valid UTF-8 encodings and the number of bytes does not exceed
  269. /// `max_table_name_length`. A call to add_table() or get_or_add_table()
  270. /// with a name that is longer than `max_table_name_length` will cause an
  271. /// exception to be thrown.
  272. ///
  273. /// \param new_name New name for preexisting table.
  274. ///
  275. /// \param require_unique_name When set to true (the default), it becomes
  276. /// impossible to add a table with a name that is already in use, or to
  277. /// rename a table to a name that is already in use.
  278. ///
  279. /// \param was_added When specified, the boolean variable is set to true if
  280. /// the table was added, and to false otherwise. If the function throws, the
  281. /// boolean variable retains its original value.
  282. ///
  283. /// \return get_table(), add_table(), and get_or_add_table() return a table
  284. /// accessor attached to the requested (or added) table. get_table() may
  285. /// return null.
  286. ///
  287. /// \throw DescriptorMismatch Thrown by get_table() and get_or_add_table()
  288. /// tf the dynamic table type does not match the statically specified custom
  289. /// table type (\a T).
  290. ///
  291. /// \throw NoSuchTable Thrown by remove_table() and rename_table() if there
  292. /// is no table with the specified \a name.
  293. ///
  294. /// \throw TableNameInUse Thrown by add_table() if \a require_unique_name is
  295. /// true and \a name clashes with the name of a preexisting table. Thrown by
  296. /// rename_table() if \a require_unique_name is true and \a new_name clashes
  297. /// with the name of a preexisting table.
  298. ///
  299. /// \throw CrossTableLinkTarget Thrown by remove_table() if the specified
  300. /// table is the target of a link column of a different table.
  301. ///
  302. //@{
  303. static const size_t max_table_name_length = 63;
  304. bool has_table(StringData name) const noexcept;
  305. TableKey find_table(StringData name) const noexcept;
  306. StringData get_table_name(TableKey key) const;
  307. TableRef get_table(TableKey key);
  308. ConstTableRef get_table(TableKey key) const;
  309. // Catch some implicit conversions
  310. TableRef get_table(int) = delete;
  311. ConstTableRef get_table(int) const = delete;
  312. TableRef get_table(StringData name);
  313. ConstTableRef get_table(StringData name) const;
  314. TableRef add_table(StringData name);
  315. TableRef add_embedded_table(StringData name);
  316. TableRef add_table_with_primary_key(StringData name, DataType pk_type, StringData pk_name, bool nullable = false);
  317. TableRef get_or_add_table(StringData name, bool* was_added = nullptr);
  318. void remove_table(TableKey key);
  319. void remove_table(StringData name);
  320. void rename_table(TableKey key, StringData new_name, bool require_unique_name = true);
  321. void rename_table(StringData name, StringData new_name, bool require_unique_name = true);
  322. Obj get_object(ObjLink link);
  323. void validate(ObjLink link) const;
  324. //@}
  325. // Serialization
  326. /// Write this database to the specified output stream.
  327. ///
  328. /// \param out The destination output stream to write to.
  329. ///
  330. /// \param pad If true, the file is padded to ensure the footer is aligned
  331. /// to the end of a page
  332. void write(std::ostream& out, bool pad = false) const;
  333. /// Write this database to a new file. It is an error to specify a
  334. /// file that already exists. This is to protect against
  335. /// overwriting a database file that is currently open, which
  336. /// would cause undefined behaviour.
  337. ///
  338. /// \param file A filesystem path.
  339. ///
  340. /// \param encryption_key 32-byte key used to encrypt the database file,
  341. /// or nullptr to disable encryption.
  342. ///
  343. /// \param version If different from 0, the new file will be a full fledged
  344. /// realm file with free list and history info. The version of the commit
  345. /// will be set to the value given here.
  346. ///
  347. /// \throw util::File::AccessError If the file could not be
  348. /// opened. If the reason corresponds to one of the exception
  349. /// types that are derived from util::File::AccessError, the
  350. /// derived exception type is thrown. In particular,
  351. /// util::File::Exists will be thrown if the file exists already.
  352. void write(const std::string& file, const char* encryption_key = nullptr, uint64_t version = 0,
  353. bool write_history = true) const;
  354. /// Write this database to a memory buffer.
  355. ///
  356. /// Ownership of the returned buffer is transferred to the
  357. /// caller. The memory will have been allocated using
  358. /// std::malloc().
  359. BinaryData write_to_mem() const;
  360. /// Commit changes to the attached file. This requires that the
  361. /// attached file is opened in read/write mode.
  362. ///
  363. /// Calling this function on an unattached group, a free-standing
  364. /// group, a group whose attached file is opened in read-only
  365. /// mode, a group that is attached to a memory buffer, or a group
  366. /// that is managed by a shared group, is an error and will result
  367. /// in undefined behavior.
  368. ///
  369. /// Table accesors will remain valid across the commit. Note that
  370. /// this is not the case when working with proper transactions.
  371. void commit();
  372. //@{
  373. /// Some operations on Tables in a Group can cause indirect changes to other
  374. /// fields, including in other Tables in the same Group. Specifically,
  375. /// removing a row will set any links to that row to null, and if it had the
  376. /// last strong links to other rows, will remove those rows. When this
  377. /// happens, The cascade notification handler will be called with a
  378. /// CascadeNotification containing information about what indirect changes
  379. /// will occur, before any changes are made.
  380. ///
  381. /// has_cascade_notification_handler() returns true if and only if there is
  382. /// currently a non-null notification handler registered.
  383. ///
  384. /// set_cascade_notification_handler() replaces the current handler (if any)
  385. /// with the passed in handler. Pass in nullptr to remove the current handler
  386. /// without registering a new one.
  387. ///
  388. /// CascadeNotification contains a vector of rows which will be removed and
  389. /// a vector of links which will be set to null (or removed, for entries in
  390. /// LinkLists).
  391. struct CascadeNotification {
  392. struct row {
  393. /// Key identifying a group-level table.
  394. TableKey table_key;
  395. /// Key identifying object to be removed.
  396. ObjKey key;
  397. row() = default;
  398. row(TableKey tk, ObjKey k)
  399. : table_key(tk)
  400. , key(k)
  401. {
  402. }
  403. bool operator==(const row& r) const noexcept
  404. {
  405. return table_key == r.table_key && key == r.key;
  406. }
  407. bool operator!=(const row& r) const noexcept
  408. {
  409. return !(*this == r);
  410. }
  411. /// Trivial lexicographic order
  412. bool operator<(const row& r) const noexcept
  413. {
  414. return table_key < r.table_key || (table_key == r.table_key && key < r.key);
  415. }
  416. };
  417. struct link {
  418. link() = default;
  419. link(TableKey tk, ColKey ck, ObjKey k, ObjKey otk)
  420. : origin_table(tk)
  421. , origin_col_key(ck)
  422. , origin_key(k)
  423. , old_target_key(otk)
  424. {
  425. }
  426. TableKey origin_table; ///< A group-level table.
  427. ColKey origin_col_key; ///< Link column being nullified.
  428. ObjKey origin_key; ///< Row in column being nullified.
  429. /// The target row index which is being removed. Mostly relevant for
  430. /// LinkList (to know which entries are being removed), but also
  431. /// valid for Link.
  432. ObjKey old_target_key;
  433. };
  434. /// A sorted list of rows which will be removed by the current operation.
  435. std::vector<row> rows;
  436. /// An unordered list of links which will be nullified by the current
  437. /// operation.
  438. std::vector<link> links;
  439. };
  440. bool has_cascade_notification_handler() const noexcept;
  441. void set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept;
  442. //@}
  443. //@{
  444. /// During sync operation, schema changes may happen at runtime as connected
  445. /// clients update their schema as part of an app update. Since this is a
  446. /// relatively rare event, no attempt is made at limiting the amount of work
  447. /// the handler is required to do to update its information about table and
  448. /// column indices (i.e., all table and column indices must be recalculated).
  449. ///
  450. /// At the time of writing, only additive schema changes may occur in that
  451. /// scenario.
  452. ///
  453. /// has_schema_change_notification_handler() returns true iff there is currently
  454. /// a non-null notification handler registered.
  455. ///
  456. /// set_schema_change_notification_handler() replaces the current handler (if any)
  457. /// with the passed in handler. Pass in nullptr to remove the current handler
  458. /// without registering a new one.
  459. bool has_schema_change_notification_handler() const noexcept;
  460. void set_schema_change_notification_handler(std::function<void()> new_handler) noexcept;
  461. //@}
  462. // Conversion
  463. void schema_to_json(std::ostream& out, std::map<std::string, std::string>* renames = nullptr) const;
  464. void to_json(std::ostream& out, size_t link_depth = 0, std::map<std::string, std::string>* renames = nullptr,
  465. JSONOutputMode output_mode = output_mode_json) const;
  466. /// Compare two groups for equality. Two groups are equal if, and
  467. /// only if, they contain the same tables in the same order, that
  468. /// is, for each table T at index I in one of the groups, there is
  469. /// a table at index I in the other group that is equal to T.
  470. /// Tables are equal if they have the same content and the same table name.
  471. bool operator==(const Group&) const;
  472. /// Compare two groups for inequality. See operator==().
  473. bool operator!=(const Group& g) const
  474. {
  475. return !(*this == g);
  476. }
  477. /// Control of what to include when computing memory usage
  478. enum SizeAggregateControl {
  479. size_of_state = 1, ///< size of tables, indexes, toplevel array
  480. size_of_history = 2, ///< size of the in-file history compartment
  481. size_of_freelists = 4, ///< size of the freelists
  482. size_of_all = 7
  483. };
  484. /// Compute the sum of the sizes in number of bytes of all the array nodes
  485. /// that currently make up this group. When this group represents a snapshot
  486. /// in a Realm file (such as during a read transaction via a Transaction
  487. /// instance), this function computes the footprint of that snapshot within
  488. /// the Realm file.
  489. ///
  490. /// If this group accessor is the detached state, this function returns
  491. /// zero.
  492. size_t compute_aggregated_byte_size(SizeAggregateControl ctrl = SizeAggregateControl::size_of_all) const noexcept;
  493. /// Return the size taken up by the current snapshot. This is in contrast to
  494. /// the number returned by DB::get_stats() which will return the
  495. /// size of the last snapshot done in that DB. If the snapshots are
  496. /// identical, the numbers will of course be equal.
  497. size_t get_used_space() const noexcept;
  498. /// check that an already attached realm file is valid for read only access.
  499. /// if not detach the file and throw a FileFormatUpgradeRequired.
  500. /// return the file format version.
  501. static int read_only_version_check(SlabAlloc& alloc, ref_type top_ref, const std::string& path);
  502. void verify() const;
  503. void validate_primary_columns();
  504. #ifdef REALM_DEBUG
  505. void print() const;
  506. void print_free() const;
  507. MemStats get_stats();
  508. void enable_mem_diagnostics(bool enable = true)
  509. {
  510. m_alloc.enable_debug(enable);
  511. }
  512. #endif
  513. protected:
  514. virtual Replication* const* get_repl() const
  515. {
  516. return &Table::g_dummy_replication;
  517. }
  518. private:
  519. static constexpr char g_class_name_prefix[] = "class_";
  520. static constexpr size_t g_class_name_prefix_len = 6;
  521. // nullptr, if we're sharing an allocator provided during initialization
  522. std::unique_ptr<SlabAlloc> m_local_alloc;
  523. // in-use allocator. If local, then equal to m_local_alloc.
  524. SlabAlloc& m_alloc;
  525. int m_file_format_version;
  526. /// `m_top` is the root node (or top array) of the Realm, and has the
  527. /// following layout:
  528. ///
  529. /// <pre>
  530. ///
  531. /// Introduced in file
  532. /// Slot Value format version
  533. /// ---------------------------------------------------------------------
  534. /// 1st m_table_names
  535. /// 2nd m_tables
  536. /// 3rd Logical file size
  537. /// 4th GroupWriter::m_free_positions (optional)
  538. /// 5th GroupWriter::m_free_lengths (optional)
  539. /// 6th GroupWriter::m_free_versions (optional)
  540. /// 7th Transaction number / version (optional)
  541. /// 8th History type (optional) 4
  542. /// 9th History ref (optional) 4
  543. /// 10th History version (optional) 7
  544. /// 11th Sync File Id (optional) 10
  545. ///
  546. /// </pre>
  547. ///
  548. /// The 'History type' slot stores a value of type
  549. /// Replication::HistoryType. The 'History version' slot stores a history
  550. /// schema version as returned by Replication::get_history_schema_version().
  551. ///
  552. /// The first three entries are mandatory. In files created by
  553. /// Group::write(), none of the optional entries are present and the size of
  554. /// `m_top` is 3. In files updated by Group::commit(), the 4th and 5th entry
  555. /// are present, and the size of `m_top` is 5. In files updated by way of a
  556. /// transaction (Transaction::commit()), the 4th, 5th, 6th, and 7th entry
  557. /// are present, and the size of `m_top` is 7. In files that contain a
  558. /// changeset history, the 8th, 9th, and 10th entry are present. The 11th entry
  559. /// will be present if the file is syncked and the client has received a client
  560. /// file id from the server.
  561. ///
  562. /// When a group accessor is attached to a newly created file or an empty
  563. /// memory buffer where there is no top array yet, `m_top`, `m_tables`, and
  564. /// `m_table_names` will be left in the detached state until the initiation
  565. /// of the first write transaction. In particular, they will remain in the
  566. /// detached state during read transactions that precede the first write
  567. /// transaction.
  568. Array m_top;
  569. Array m_tables;
  570. ArrayStringShort m_table_names;
  571. uint64_t m_last_seen_mapping_version = 0;
  572. typedef std::vector<Table*> TableAccessors;
  573. mutable TableAccessors m_table_accessors;
  574. mutable std::mutex m_accessor_mutex;
  575. mutable int m_num_tables = 0;
  576. bool m_attached = false;
  577. bool m_is_writable = true;
  578. const bool m_is_shared;
  579. static std::optional<int> fake_target_file_format;
  580. std::function<void(const CascadeNotification&)> m_notify_handler;
  581. std::function<void()> m_schema_change_handler;
  582. std::shared_ptr<metrics::Metrics> m_metrics;
  583. size_t m_total_rows;
  584. static constexpr size_t s_table_name_ndx = 0;
  585. static constexpr size_t s_table_refs_ndx = 1;
  586. static constexpr size_t s_file_size_ndx = 2;
  587. static constexpr size_t s_free_pos_ndx = 3;
  588. static constexpr size_t s_free_size_ndx = 4;
  589. static constexpr size_t s_free_version_ndx = 5;
  590. static constexpr size_t s_version_ndx = 6;
  591. static constexpr size_t s_hist_type_ndx = 7;
  592. static constexpr size_t s_hist_ref_ndx = 8;
  593. static constexpr size_t s_hist_version_ndx = 9;
  594. static constexpr size_t s_sync_file_id_ndx = 10;
  595. static constexpr size_t s_group_max_size = 11;
  596. struct shared_tag {
  597. };
  598. Group(shared_tag) noexcept;
  599. Group(SlabAlloc* alloc) noexcept;
  600. void init_array_parents() noexcept;
  601. void open(ref_type top_ref, const std::string& file_path);
  602. // If the underlying memory mappings have been extended, this method is used
  603. // to update all the tables' allocator wrappers. The allocator wrappers are
  604. // configure to either allow or deny changes.
  605. void update_allocator_wrappers(bool writable);
  606. /// If `top_ref` is not zero, attach this group accessor to the specified
  607. /// underlying node structure. If `top_ref` is zero and \a
  608. /// create_group_when_missing is true, create a new node structure that
  609. /// represents an empty group, and attach this group accessor to it.
  610. void attach(ref_type top_ref, bool writable, bool create_group_when_missing);
  611. /// Detach this group accessor from the underlying node structure. If this
  612. /// group accessors is already in the detached state, this function does
  613. /// nothing (idempotency).
  614. void detach() noexcept;
  615. /// \param writable Must be set to true when, and only when attaching for a
  616. /// write transaction.
  617. void attach_shared(ref_type new_top_ref, size_t new_file_size, bool writable);
  618. void create_empty_group();
  619. void remove_table(size_t table_ndx, TableKey key);
  620. void reset_free_space_tracking();
  621. void remap(size_t new_file_size);
  622. void remap_and_update_refs(ref_type new_top_ref, size_t new_file_size, bool writable);
  623. /// Recursively update refs stored in all cached array
  624. /// accessors. This includes cached array accessors in any
  625. /// currently attached table accessors. This ensures that the
  626. /// group instance itself, as well as any attached table accessor
  627. /// that exists across Group::commit() will remain valid. This
  628. /// function is not appropriate for use in conjunction with
  629. /// commits via shared group.
  630. void update_refs(ref_type top_ref) noexcept;
  631. // Overriding method in ArrayParent
  632. void update_child_ref(size_t, ref_type) override;
  633. // Overriding method in ArrayParent
  634. ref_type get_child_ref(size_t) const noexcept override;
  635. class TableWriter;
  636. class DefaultTableWriter;
  637. static void write(std::ostream&, int file_format_version, TableWriter&, bool no_top_array,
  638. bool pad_for_encryption, uint_fast64_t version_number);
  639. Table* do_get_table(size_t ndx);
  640. const Table* do_get_table(size_t ndx) const;
  641. Table* do_get_table(StringData name);
  642. const Table* do_get_table(StringData name) const;
  643. Table* do_add_table(StringData name, bool is_embedded, bool do_repl = true);
  644. void create_and_insert_table(TableKey key, StringData name);
  645. Table* create_table_accessor(size_t table_ndx);
  646. void recycle_table_accessor(Table*);
  647. void detach_table_accessors() noexcept; // Idempotent
  648. void mark_all_table_accessors() noexcept;
  649. void write(util::File& file, const char* encryption_key, uint_fast64_t version_number, bool write_history) const;
  650. void write(std::ostream&, bool pad, uint_fast64_t version_numer, bool write_history) const;
  651. std::shared_ptr<metrics::Metrics> get_metrics() const noexcept;
  652. void set_metrics(std::shared_ptr<metrics::Metrics> other) noexcept;
  653. void update_num_objects();
  654. class TransactAdvancer;
  655. /// Memory mappings must have been updated to reflect any growth in filesize before
  656. /// calling advance_transact()
  657. void advance_transact(ref_type new_top_ref, _impl::NoCopyInputStream&, bool writable);
  658. void refresh_dirty_accessors();
  659. void flush_accessors_for_commit();
  660. /// \brief The version of the format of the node structure (in file or in
  661. /// memory) in use by Realm objects associated with this group.
  662. ///
  663. /// Every group contains a file format version field, which is returned
  664. /// by this function. The file format version field is set to the file format
  665. /// version specified by the attached file (or attached memory buffer) at the
  666. /// time of attachment and the value is used to determine if a file format
  667. /// upgrade is required.
  668. ///
  669. /// A value of zero means that the file format is not yet decided. This is
  670. /// only possible for empty Realms where top-ref is zero. (When group is created
  671. /// with the unattached_tag). The version number will then be determined in the
  672. /// subsequent call to Group::open.
  673. ///
  674. /// In shared mode (when a Realm file is opened via a DB instance)
  675. /// it can happen that the file format is upgraded asyncronously (via
  676. /// another DB instance), and in that case the file format version
  677. /// field can get out of date, but only for a short while. It is always
  678. /// guaranteed to be, and remain up to date after the opening process completes
  679. /// (when DB::do_open() returns).
  680. ///
  681. /// An empty Realm file (one whose top-ref is zero) may specify a file
  682. /// format version of zero to indicate that the format is not yet
  683. /// decided. In that case the file format version must be changed to a proper
  684. /// before the opening process completes (Group::open() or DB::open()).
  685. ///
  686. /// File format versions:
  687. ///
  688. /// 1 Initial file format version
  689. ///
  690. /// 2 Various changes.
  691. ///
  692. /// 3 Supporting null on string columns broke the file format in following
  693. /// way: Index appends an 'X' character to all strings except the null
  694. /// string, to be able to distinguish between null and empty
  695. /// string. Bumped to 3 because of null support of String columns and
  696. /// because of new format of index.
  697. ///
  698. /// 4 Introduction of optional in-Realm history of changes (additional
  699. /// entries in Group::m_top). Since this change is not forward
  700. /// compatible, the file format version had to be bumped. This change is
  701. /// implemented in a way that achieves backwards compatibility with
  702. /// version 3 (and in turn with version 2).
  703. ///
  704. /// 5 Introduced the new Timestamp column type that replaces DateTime.
  705. /// When opening an older database file, all DateTime columns will be
  706. /// automatically upgraded Timestamp columns.
  707. ///
  708. /// 6 Introduced a new structure for the StringIndex. Moved the commit
  709. /// logs into the Realm file. Changes to the transaction log format
  710. /// including reshuffling instructions. This is the format used in
  711. /// milestone 2.0.0.
  712. ///
  713. /// 7 Introduced "history schema version" as 10th entry in top array.
  714. ///
  715. /// 8 Subtables can now have search index.
  716. ///
  717. /// 9 Replication instruction values shuffled, instr_MoveRow added.
  718. ///
  719. /// 10 Cluster based table layout. Memory mapping changes which require
  720. /// special treatment of large files of preceding versions.
  721. ///
  722. /// 11 Same as 10, but version 11 files will have search index added on
  723. /// string primary key columns.
  724. ///
  725. /// 12 - 19 Room for new file formats in legacy code.
  726. ///
  727. /// 20 New data types: Decimal128 and ObjectId. Embedded tables.
  728. ///
  729. /// IMPORTANT: When introducing a new file format version, be sure to review
  730. /// the file validity checks in Group::open() and DB::do_open, the file
  731. /// format selection logic in
  732. /// Group::get_target_file_format_version_for_session(), and the file format
  733. /// upgrade logic in Group::upgrade_file_format(), AND the lists of accepted
  734. /// file formats and the version deletion list residing in "backup_restore.cpp"
  735. int get_file_format_version() const noexcept;
  736. void set_file_format_version(int) noexcept;
  737. int get_committed_file_format_version() const noexcept;
  738. /// The specified history type must be a value of Replication::HistoryType.
  739. static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept;
  740. void send_cascade_notification(const CascadeNotification& notification) const;
  741. void send_schema_change_notification() const;
  742. static void get_version_and_history_info(const Array& top, _impl::History::version_type& version,
  743. int& history_type, int& history_schema_version) noexcept;
  744. static ref_type get_history_ref(const Array& top) noexcept;
  745. void set_history_schema_version(int version);
  746. template <class Accessor>
  747. void set_history_parent(Accessor& history_root) noexcept;
  748. void prepare_top_for_history(int history_type, int history_schema_version, uint64_t file_ident);
  749. template <class Accessor>
  750. void prepare_history_parent(Accessor& history_root, int history_type, int history_schema_version,
  751. uint64_t file_ident);
  752. static void validate_top_array(const Array& arr, const SlabAlloc& alloc);
  753. size_t find_table_index(StringData name) const noexcept;
  754. TableKey ndx2key(size_t ndx) const;
  755. size_t key2ndx(TableKey key) const;
  756. size_t key2ndx_checked(TableKey key) const;
  757. void set_size() const noexcept;
  758. std::map<TableRef, ColKey> get_primary_key_columns_from_pk_table(TableRef pk_table);
  759. void check_table_name_uniqueness(StringData name)
  760. {
  761. if (m_table_names.find_first(name) != not_found)
  762. throw TableNameInUse();
  763. }
  764. friend class Table;
  765. friend class GroupWriter;
  766. friend class DB;
  767. friend class _impl::GroupFriend;
  768. friend class _impl::TransactLogConvenientEncoder;
  769. friend class _impl::TransactLogParser;
  770. friend class TrivialReplication;
  771. friend class metrics::QueryInfo;
  772. friend class metrics::Metrics;
  773. friend class Transaction;
  774. friend class TableKeyIterator;
  775. friend class CascadeState;
  776. };
  777. class TableKeyIterator {
  778. public:
  779. bool operator!=(const TableKeyIterator& other)
  780. {
  781. return m_pos != other.m_pos;
  782. }
  783. TableKeyIterator& operator++();
  784. TableKey operator*();
  785. private:
  786. friend class TableKeys;
  787. const Group* m_group;
  788. size_t m_pos;
  789. size_t m_index_in_group = 0;
  790. TableKey m_table_key;
  791. TableKeyIterator(const Group* g, size_t p)
  792. : m_group(g)
  793. , m_pos(p)
  794. {
  795. }
  796. void load_key();
  797. };
  798. class TableKeys {
  799. public:
  800. TableKeys(const Group* g)
  801. : m_iter(g, 0)
  802. {
  803. }
  804. size_t size() const
  805. {
  806. return m_iter.m_group->size();
  807. }
  808. bool empty() const
  809. {
  810. return size() == 0;
  811. }
  812. TableKey operator[](size_t p) const;
  813. TableKeyIterator begin() const
  814. {
  815. return TableKeyIterator(m_iter.m_group, 0);
  816. }
  817. TableKeyIterator end() const
  818. {
  819. return TableKeyIterator(m_iter.m_group, size());
  820. }
  821. private:
  822. mutable TableKeyIterator m_iter;
  823. };
  824. // Implementation
  825. inline TableKeys Group::get_table_keys() const
  826. {
  827. return TableKeys(this);
  828. }
  829. inline bool Group::is_attached() const noexcept
  830. {
  831. return m_attached;
  832. }
  833. inline bool Group::is_empty() const noexcept
  834. {
  835. if (!is_attached())
  836. return false;
  837. return size() == 0;
  838. }
  839. inline size_t Group::key2ndx(TableKey key) const
  840. {
  841. size_t idx = key.value & 0xFFFF;
  842. return idx;
  843. }
  844. inline StringData Group::get_table_name(TableKey key) const
  845. {
  846. size_t table_ndx = key2ndx_checked(key);
  847. return m_table_names.get(table_ndx);
  848. }
  849. inline bool Group::has_table(StringData name) const noexcept
  850. {
  851. size_t ndx = find_table_index(name);
  852. return ndx != not_found;
  853. }
  854. inline size_t Group::find_table_index(StringData name) const noexcept
  855. {
  856. if (m_table_names.is_attached())
  857. return m_table_names.find_first(name);
  858. return not_found;
  859. }
  860. inline TableKey Group::find_table(StringData name) const noexcept
  861. {
  862. if (!is_attached())
  863. return TableKey();
  864. size_t ndx = find_table_index(name);
  865. return (ndx != npos) ? ndx2key(ndx) : TableKey{};
  866. }
  867. inline TableRef Group::get_table(TableKey key)
  868. {
  869. if (!is_attached())
  870. throw LogicError(LogicError::detached_accessor);
  871. auto ndx = key2ndx_checked(key);
  872. Table* table = do_get_table(ndx); // Throws
  873. return TableRef(table, table ? table->m_alloc.get_instance_version() : 0);
  874. }
  875. inline ConstTableRef Group::get_table(TableKey key) const
  876. {
  877. if (!is_attached())
  878. throw LogicError(LogicError::detached_accessor);
  879. auto ndx = key2ndx_checked(key);
  880. const Table* table = do_get_table(ndx); // Throws
  881. return ConstTableRef(table, table ? table->m_alloc.get_instance_version() : 0);
  882. }
  883. inline TableRef Group::get_table(StringData name)
  884. {
  885. if (!is_attached())
  886. throw LogicError(LogicError::detached_accessor);
  887. Table* table = do_get_table(name); // Throws
  888. return TableRef(table, table ? table->m_alloc.get_instance_version() : 0);
  889. }
  890. inline ConstTableRef Group::get_table(StringData name) const
  891. {
  892. if (!is_attached())
  893. throw LogicError(LogicError::detached_accessor);
  894. const Table* table = do_get_table(name); // Throws
  895. return ConstTableRef(table, table ? table->m_alloc.get_instance_version() : 0);
  896. }
  897. inline TableRef Group::add_table(StringData name)
  898. {
  899. if (!is_attached())
  900. throw LogicError(LogicError::detached_accessor);
  901. check_table_name_uniqueness(name);
  902. Table* table = do_add_table(name, false); // Throws
  903. return TableRef(table, table->m_alloc.get_instance_version());
  904. }
  905. inline TableRef Group::add_embedded_table(StringData name)
  906. {
  907. if (!is_attached())
  908. throw LogicError(LogicError::detached_accessor);
  909. check_table_name_uniqueness(name);
  910. Table* table = do_add_table(name, true); // Throws
  911. return TableRef(table, table->m_alloc.get_instance_version());
  912. }
  913. inline TableRef Group::get_or_add_table(StringData name, bool* was_added)
  914. {
  915. if (!is_attached())
  916. throw LogicError(LogicError::detached_accessor);
  917. auto table = do_get_table(name);
  918. if (was_added)
  919. *was_added = !table;
  920. if (!table) {
  921. table = do_add_table(name, false);
  922. }
  923. return TableRef(table, table->m_alloc.get_instance_version());
  924. }
  925. inline void Group::init_array_parents() noexcept
  926. {
  927. m_table_names.set_parent(&m_top, 0);
  928. m_tables.set_parent(&m_top, 1);
  929. }
  930. inline void Group::update_child_ref(size_t child_ndx, ref_type new_ref)
  931. {
  932. m_tables.set(child_ndx, new_ref);
  933. }
  934. inline ref_type Group::get_child_ref(size_t child_ndx) const noexcept
  935. {
  936. return m_tables.get_as_ref(child_ndx);
  937. }
  938. inline bool Group::has_cascade_notification_handler() const noexcept
  939. {
  940. return !!m_notify_handler;
  941. }
  942. inline void
  943. Group::set_cascade_notification_handler(std::function<void(const CascadeNotification&)> new_handler) noexcept
  944. {
  945. m_notify_handler = std::move(new_handler);
  946. }
  947. inline void Group::send_cascade_notification(const CascadeNotification& notification) const
  948. {
  949. REALM_ASSERT_DEBUG(m_notify_handler);
  950. m_notify_handler(notification);
  951. }
  952. inline bool Group::has_schema_change_notification_handler() const noexcept
  953. {
  954. return !!m_schema_change_handler;
  955. }
  956. inline void Group::set_schema_change_notification_handler(std::function<void()> new_handler) noexcept
  957. {
  958. m_schema_change_handler = std::move(new_handler);
  959. }
  960. inline void Group::send_schema_change_notification() const
  961. {
  962. if (m_schema_change_handler)
  963. m_schema_change_handler();
  964. }
  965. inline ref_type Group::get_history_ref(const Array& top) noexcept
  966. {
  967. bool has_history = (top.is_attached() && top.size() > s_hist_type_ndx);
  968. if (has_history) {
  969. // This function is only used is shared mode (from DB)
  970. REALM_ASSERT(top.size() > s_hist_version_ndx);
  971. return top.get_as_ref(s_hist_ref_ndx);
  972. }
  973. return 0;
  974. }
  975. inline void Group::set_sync_file_id(uint64_t id)
  976. {
  977. while (m_top.size() < s_sync_file_id_ndx + 1)
  978. m_top.add(0);
  979. m_top.set(s_sync_file_id_ndx, RefOrTagged::make_tagged(id));
  980. }
  981. inline void Group::set_history_schema_version(int version)
  982. {
  983. while (m_top.size() < s_hist_version_ndx + 1)
  984. m_top.add(0);
  985. m_top.set(s_hist_version_ndx, RefOrTagged::make_tagged(unsigned(version))); // Throws
  986. }
  987. template <class Accessor>
  988. inline void Group::set_history_parent(Accessor& history_root) noexcept
  989. {
  990. history_root.set_parent(&m_top, 8);
  991. }
  992. template <class Accessor>
  993. void Group::prepare_history_parent(Accessor& history_root, int history_type, int history_schema_version,
  994. uint64_t file_ident)
  995. {
  996. prepare_top_for_history(history_type, history_schema_version, file_ident);
  997. set_history_parent(history_root);
  998. }
  999. class Group::TableWriter {
  1000. public:
  1001. struct HistoryInfo {
  1002. ref_type ref = 0;
  1003. int type = 0;
  1004. int version = 0;
  1005. uint64_t sync_file_id = 0;
  1006. };
  1007. virtual ref_type write_names(_impl::OutputStream&) = 0;
  1008. virtual ref_type write_tables(_impl::OutputStream&) = 0;
  1009. virtual HistoryInfo write_history(_impl::OutputStream&) = 0;
  1010. virtual ~TableWriter() noexcept {}
  1011. };
  1012. inline const Table* Group::do_get_table(size_t ndx) const
  1013. {
  1014. return const_cast<Group*>(this)->do_get_table(ndx); // Throws
  1015. }
  1016. inline const Table* Group::do_get_table(StringData name) const
  1017. {
  1018. return const_cast<Group*>(this)->do_get_table(name); // Throws
  1019. }
  1020. inline void Group::reset_free_space_tracking()
  1021. {
  1022. // if used whith a shared allocator, free space should never be reset through
  1023. // Group, but rather through the proper owner of the allocator, which is the DB object.
  1024. REALM_ASSERT(m_local_alloc);
  1025. m_alloc.reset_free_space_tracking(); // Throws
  1026. }
  1027. inline std::shared_ptr<metrics::Metrics> Group::get_metrics() const noexcept
  1028. {
  1029. return m_metrics;
  1030. }
  1031. inline void Group::set_metrics(std::shared_ptr<metrics::Metrics> shared) noexcept
  1032. {
  1033. m_metrics = shared;
  1034. }
  1035. // The purpose of this class is to give internal access to some, but
  1036. // not all of the non-public parts of the Group class.
  1037. class _impl::GroupFriend {
  1038. public:
  1039. static Allocator& get_alloc(const Group& group) noexcept
  1040. {
  1041. return group.m_alloc;
  1042. }
  1043. static ref_type get_top_ref(const Group& group) noexcept
  1044. {
  1045. return group.m_top.get_ref();
  1046. }
  1047. static ref_type get_history_ref(Allocator& alloc, ref_type top_ref) noexcept
  1048. {
  1049. Array top(alloc);
  1050. if (top_ref != 0)
  1051. top.init_from_ref(top_ref);
  1052. return Group::get_history_ref(top);
  1053. }
  1054. static ref_type get_history_ref(const Group& group) noexcept
  1055. {
  1056. return Group::get_history_ref(group.m_top);
  1057. }
  1058. static int get_file_format_version(const Group& group) noexcept
  1059. {
  1060. return group.get_file_format_version();
  1061. }
  1062. static void get_version_and_history_info(const Allocator& alloc, ref_type top_ref,
  1063. _impl::History::version_type& version, int& history_type,
  1064. int& history_schema_version) noexcept
  1065. {
  1066. Array top{const_cast<Allocator&>(alloc)};
  1067. if (top_ref != 0)
  1068. top.init_from_ref(top_ref);
  1069. Group::get_version_and_history_info(top, version, history_type, history_schema_version);
  1070. }
  1071. static void set_history_schema_version(Group& group, int version)
  1072. {
  1073. group.set_history_schema_version(version); // Throws
  1074. }
  1075. template <class Accessor>
  1076. static void set_history_parent(Group& group, Accessor& history_root) noexcept
  1077. {
  1078. group.set_history_parent(history_root);
  1079. }
  1080. template <class Accessor>
  1081. static void prepare_history_parent(Group& group, Accessor& history_root, int history_type,
  1082. int history_schema_version, uint64_t file_ident = 0)
  1083. {
  1084. group.prepare_history_parent(history_root, history_type, history_schema_version, file_ident); // Throws
  1085. }
  1086. // This is used by upgrade functions in Sync
  1087. static Table* get_table_by_ndx(Group& group, size_t ndx)
  1088. {
  1089. return group.do_get_table(ndx);
  1090. }
  1091. static int get_target_file_format_version_for_session(int current_file_format_version, int history_type) noexcept
  1092. {
  1093. return Group::get_target_file_format_version_for_session(current_file_format_version, history_type);
  1094. }
  1095. static void fake_target_file_format(const std::optional<int> format) noexcept;
  1096. };
  1097. class CascadeState {
  1098. public:
  1099. enum class Mode {
  1100. /// If we remove the last link to an object, delete that object, even if
  1101. /// the link we removed was not a strong link
  1102. All,
  1103. /// If we remove the last link to an object, delete that object only if
  1104. /// the link we removed was a strong link
  1105. Strong,
  1106. /// Never delete objects due to removing links
  1107. None
  1108. };
  1109. struct Link {
  1110. TableKey origin_table; ///< A group-level table.
  1111. ColKey origin_col_key; ///< Link column being nullified.
  1112. ObjKey origin_key; ///< Row in column being nullified.
  1113. /// The target row index which is being removed. Mostly relevant for
  1114. /// LinkList (to know which entries are being removed), but also
  1115. /// valid for Link.
  1116. ObjLink old_target_link;
  1117. };
  1118. CascadeState(Mode mode = Mode::Strong, Group* g = nullptr) noexcept
  1119. : m_mode(mode)
  1120. , m_group(g)
  1121. {
  1122. }
  1123. /// Indicate which links to take action on. Either all, strong or none.
  1124. Mode m_mode;
  1125. std::vector<std::pair<TableKey, ObjKey>> m_to_be_deleted;
  1126. std::vector<Link> m_to_be_nullified;
  1127. Group* m_group = nullptr;
  1128. bool notification_handler() const noexcept
  1129. {
  1130. return m_group && m_group->has_cascade_notification_handler();
  1131. }
  1132. void send_notifications(Group::CascadeNotification& notifications) const
  1133. {
  1134. REALM_ASSERT_DEBUG(notification_handler());
  1135. m_group->send_cascade_notification(notifications);
  1136. }
  1137. bool enqueue_for_cascade(const Obj& target_obj, bool link_is_strong, bool last_removed)
  1138. {
  1139. // Check if the object should be cascade deleted
  1140. if (m_mode == Mode::None || !last_removed) {
  1141. return false;
  1142. }
  1143. if (m_mode == Mode::All || link_is_strong) {
  1144. bool has_backlinks = target_obj.has_backlinks(m_mode == Mode::Strong);
  1145. if (!has_backlinks) {
  1146. // Object has no more backlinks - add to list for deletion
  1147. m_to_be_deleted.emplace_back(target_obj.get_table()->get_key(), target_obj.get_key());
  1148. return true;
  1149. }
  1150. }
  1151. return false;
  1152. }
  1153. void enqueue_for_nullification(Table& src_table, ColKey src_col_key, ObjKey origin_key, ObjLink target_link)
  1154. {
  1155. // Nullify immediately if we don't need to send cascade notifications
  1156. if (!notification_handler()) {
  1157. Obj obj = src_table.get_object(origin_key);
  1158. obj.nullify_link(src_col_key, target_link);
  1159. return;
  1160. }
  1161. // Otherwise enqueue it
  1162. m_to_be_nullified.push_back({src_table.get_key(), src_col_key, origin_key, target_link});
  1163. }
  1164. void send_notifications()
  1165. {
  1166. if (!notification_handler()) {
  1167. return;
  1168. }
  1169. Group::CascadeNotification notification;
  1170. for (auto& o : m_to_be_deleted)
  1171. notification.rows.emplace_back(o.first, o.second);
  1172. for (auto& l : m_to_be_nullified)
  1173. notification.links.emplace_back(l.origin_table, l.origin_col_key, l.origin_key,
  1174. l.old_target_link.get_obj_key());
  1175. send_notifications(notification);
  1176. }
  1177. };
  1178. } // namespace realm
  1179. #endif // REALM_GROUP_HPP