group.hpp 50 KB

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