group.hpp 51 KB

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