replication.hpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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_REPLICATION_HPP
  19. #define REALM_REPLICATION_HPP
  20. #include <algorithm>
  21. #include <limits>
  22. #include <memory>
  23. #include <exception>
  24. #include <string>
  25. #include <realm/util/assert.hpp>
  26. #include <realm/util/safe_int_ops.hpp>
  27. #include <realm/util/buffer.hpp>
  28. #include <realm/impl/cont_transact_hist.hpp>
  29. #include <realm/impl/transact_log.hpp>
  30. namespace realm {
  31. namespace util {
  32. class Logger;
  33. }
  34. // FIXME: Be careful about the possibility of one modification function being called by another where both do
  35. // transaction logging.
  36. /// Replication is enabled by passing an instance of an implementation of this
  37. /// class to the DB constructor.
  38. class Replication {
  39. public:
  40. virtual ~Replication() = default;
  41. // Formerly Replication:
  42. virtual void add_class(TableKey table_key, StringData table_name, Table::Type table_type);
  43. virtual void add_class_with_primary_key(TableKey, StringData table_name, DataType pk_type, StringData pk_field,
  44. bool nullable, Table::Type table_type);
  45. virtual void erase_class(TableKey table_key, size_t num_tables);
  46. virtual void rename_class(TableKey table_key, StringData new_name);
  47. virtual void insert_column(const Table*, ColKey col_key, DataType type, StringData name, Table* target_table);
  48. virtual void erase_column(const Table*, ColKey col_key);
  49. virtual void rename_column(const Table*, ColKey col_key, StringData name);
  50. virtual void add_int(const Table*, ColKey col_key, ObjKey key, int_fast64_t value);
  51. virtual void set(const Table*, ColKey col_key, ObjKey key, Mixed value,
  52. _impl::Instruction variant = _impl::instr_Set);
  53. virtual void list_set(const CollectionBase& list, size_t list_ndx, Mixed value);
  54. virtual void list_insert(const CollectionBase& list, size_t list_ndx, Mixed value, size_t prior_size);
  55. virtual void list_move(const CollectionBase&, size_t from_link_ndx, size_t to_link_ndx);
  56. virtual void list_erase(const CollectionBase&, size_t link_ndx);
  57. virtual void list_clear(const CollectionBase&);
  58. virtual void set_insert(const CollectionBase& set, size_t list_ndx, Mixed value);
  59. virtual void set_erase(const CollectionBase& set, size_t list_ndx, Mixed value);
  60. virtual void set_clear(const CollectionBase& set);
  61. virtual void dictionary_insert(const CollectionBase& dict, size_t dict_ndx, Mixed key, Mixed value);
  62. virtual void dictionary_set(const CollectionBase& dict, size_t dict_ndx, Mixed key, Mixed value);
  63. virtual void dictionary_erase(const CollectionBase& dict, size_t dict_ndx, Mixed key);
  64. virtual void create_object(const Table*, GlobalKey);
  65. virtual void create_object_with_primary_key(const Table*, ObjKey, Mixed);
  66. virtual void remove_object(const Table*, ObjKey);
  67. virtual void typed_link_change(const Table*, ColKey, TableKey);
  68. //@{
  69. /// Implicit nullifications due to removal of target row. This is redundant
  70. /// information from the point of view of replication, as the removal of the
  71. /// target row will reproduce the implicit nullifications in the target
  72. /// Realm anyway. The purpose of this instruction is to allow observers
  73. /// (reactor pattern) to be explicitly notified about the implicit
  74. /// nullifications.
  75. virtual void nullify_link(const Table*, ColKey col_key, ObjKey key);
  76. virtual void link_list_nullify(const Lst<ObjKey>&, size_t link_ndx);
  77. // Be sure to keep this type aligned with what is actually used in DB.
  78. using version_type = _impl::History::version_type;
  79. using InputStream = util::InputStream;
  80. class TransactLogApplier;
  81. class Interrupted; // Exception
  82. class SimpleIndexTranslator;
  83. std::string get_database_path() const;
  84. /// Called during construction of the associated DB object.
  85. ///
  86. /// \param db The associated DB object.
  87. virtual void initialize(DB& db);
  88. /// \defgroup replication_transactions
  89. //@{
  90. /// From the point of view of the Replication class, a transaction is
  91. /// initiated when, and only when the associated Transaction object calls
  92. /// initiate_transact() and the call is successful. The associated
  93. /// Transaction object must terminate every initiated transaction either by
  94. /// calling finalize_commit() or by calling abort_transact(). It may only
  95. /// call finalize_commit(), however, after calling prepare_commit(), and
  96. /// only when prepare_commit() succeeds. If prepare_commit() fails (i.e.,
  97. /// throws) abort_transact() must still be called.
  98. ///
  99. /// The associated Transaction object is supposed to terminate a transaction
  100. /// as soon as possible, and is required to terminate it before attempting
  101. /// to initiate a new one.
  102. ///
  103. /// initiate_transact() is called by the associated Transaction object as
  104. /// part of the initiation of a transaction, and at a time where the caller
  105. /// has acquired exclusive write access to the local Realm. The Replication
  106. /// implementation is allowed to perform "precursor transactions" on the
  107. /// local Realm at this time. During the initiated transaction, the
  108. /// associated DB object must inform the Replication object of all
  109. /// modifying operations by calling set_value() and friends.
  110. ///
  111. /// FIXME: There is currently no way for implementations to perform
  112. /// precursor transactions, since a regular transaction would cause a dead
  113. /// lock when it tries to acquire a write lock. Consider giving access to
  114. /// special non-locking precursor transactions via an extra argument to this
  115. /// function.
  116. ///
  117. /// prepare_commit() serves as the first phase of a two-phase commit. This
  118. /// function is called by the associated Transaction object immediately
  119. /// before the commit operation on the local Realm. The associated
  120. /// Transaction object will then, as the second phase, either call
  121. /// finalize_commit() or abort_transact() depending on whether the commit
  122. /// operation succeeded or not. The Replication implementation is allowed to
  123. /// modify the Realm via the associated Transaction object at this time
  124. /// (important to in-Realm histories).
  125. ///
  126. /// initiate_transact() and prepare_commit() are allowed to block the
  127. /// calling thread if, for example, they need to communicate over the
  128. /// network. If a calling thread is blocked in one of these functions, it
  129. /// must be possible to interrupt the blocking operation by having another
  130. /// thread call interrupt(). The contract is as follows: When interrupt() is
  131. /// called, then any execution of initiate_transact() or prepare_commit(),
  132. /// initiated before the interruption, must complete without blocking, or
  133. /// the execution must be aborted by throwing an Interrupted exception. If
  134. /// initiate_transact() or prepare_commit() throws Interrupted, it counts as
  135. /// a failed operation.
  136. ///
  137. /// finalize_commit() is called by the associated Transaction object
  138. /// immediately after a successful commit operation on the local Realm. This
  139. /// happens at a time where modification of the Realm is no longer possible
  140. /// via the associated Transaction object. In the case of in-Realm
  141. /// histories, the changes are automatically finalized as part of the commit
  142. /// operation performed by the caller prior to the invocation of
  143. /// finalize_commit(), so in that case, finalize_commit() might not need to
  144. /// do anything.
  145. ///
  146. /// abort_transact() is called by the associated Transaction object to
  147. /// terminate a transaction without committing. That is, any transaction
  148. /// that is not terminated by finalize_commit() is terminated by
  149. /// abort_transact(). This could be due to an explicit rollback, or due to a
  150. /// failed commit attempt.
  151. ///
  152. /// Note that finalize_commit() and abort_transact() are not allowed to
  153. /// throw.
  154. ///
  155. /// \param current_version The version of the snapshot that the current
  156. /// transaction is based on.
  157. ///
  158. /// \param history_updated Pass true only when the history has already been
  159. /// updated to reflect the currently bound snapshot, such as when
  160. /// _impl::History::update_early_from_top_ref() was called during the
  161. /// transition from a read transaction to the current write transaction.
  162. ///
  163. /// \throw Interrupted Thrown by initiate_transact() and prepare_commit() if
  164. /// a blocking operation was interrupted.
  165. void initiate_transact(Group& group, version_type current_version, bool history_updated);
  166. /// \param current_version The version of the snapshot that the current
  167. /// transaction is based on.
  168. /// \return prepare_commit() returns the version of the new snapshot
  169. /// produced by the transaction.
  170. version_type prepare_commit(version_type current_version);
  171. void finalize_commit() noexcept;
  172. //@}
  173. /// Get the list of uncommitted changes accumulated so far in the current
  174. /// write transaction.
  175. ///
  176. /// The callee retains ownership of the referenced memory. The ownership is
  177. /// not handed over to the caller.
  178. ///
  179. /// This function may be called only during a write transaction (prior to
  180. /// initiation of commit operation). In that case, the caller may assume that the
  181. /// returned memory reference stays valid for the remainder of the transaction (up
  182. /// until initiation of the commit operation).
  183. BinaryData get_uncommitted_changes() const noexcept;
  184. /// CAUTION: These values are stored in Realm files, so value reassignment
  185. /// is not allowed.
  186. enum HistoryType {
  187. /// No history available. No support for either continuous transactions
  188. /// or inter-client synchronization.
  189. hist_None = 0,
  190. /// Out-of-Realm history supporting continuous transactions.
  191. ///
  192. /// NOTE: This history type is no longer in use. The value needs to stay
  193. /// reserved in case someone tries to open an old Realm file.
  194. hist_OutOfRealm = 1,
  195. /// In-Realm history supporting continuous transactions
  196. /// (make_in_realm_history()).
  197. hist_InRealm = 2,
  198. /// In-Realm history supporting continuous transactions and client-side
  199. /// synchronization protocol (realm::sync::ClientHistory).
  200. hist_SyncClient = 3,
  201. /// In-Realm history supporting continuous transactions and server-side
  202. /// synchronization protocol (realm::_impl::ServerHistory).
  203. hist_SyncServer = 4
  204. };
  205. static const char* history_type_name(int);
  206. /// Returns the type of history maintained by this Replication
  207. /// implementation, or \ref hist_None if no history is maintained by it.
  208. ///
  209. /// This type is used to ensure that all session participants agree on
  210. /// history type, and that the Realm file contains a compatible type of
  211. /// history, at the beginning of a new session.
  212. ///
  213. /// As a special case, if there is no top array (Group::m_top) at the
  214. /// beginning of a new session, then the history type is still undecided and
  215. /// all history types (as returned by get_history_type()) are threfore
  216. /// allowed for the session initiator. Note that this case only arises if
  217. /// there was no preceding session, or if no transaction was sucessfully
  218. /// committed during any of the preceding sessions. As soon as a transaction
  219. /// is successfully committed, the Realm contains at least a top array, and
  220. /// from that point on, the history type is generally fixed, although still
  221. /// subject to certain allowed changes (as mentioned below).
  222. ///
  223. /// For the sake of backwards compatibility with older Realm files that does
  224. /// not store any history type, the following rule shall apply:
  225. ///
  226. /// - If the top array of a Realm file (Group::m_top) does not contain a
  227. /// history type, because it is too short, it shall be understood as
  228. /// implicitly storing the type \ref hist_None.
  229. ///
  230. /// Note: In what follows, the meaning of *preceding session* is: The last
  231. /// preceding session that modified the Realm by sucessfully committing a
  232. /// new snapshot.
  233. ///
  234. /// It shall be allowed to switch to a \ref hist_InRealm history if the
  235. /// stored history type is \ref hist_None. This can be done simply by adding
  236. /// a new history to the Realm file. This is possible because histories of
  237. /// this type a transient in nature, and need not survive from one session
  238. /// to the next.
  239. ///
  240. /// On the other hand, as soon as a history of type \ref hist_InRealm is
  241. /// added to a Realm file, that history type is binding for all subsequent
  242. /// sessions. In theory, this constraint is not necessary, and a later
  243. /// switch to \ref hist_None would be possible because of the transient
  244. /// nature of it, however, because the \ref hist_InRealm history remains in
  245. /// the Realm file, there are practical complications, and for that reason,
  246. /// such switching shall not be supported.
  247. ///
  248. /// The \ref hist_SyncClient history type can only be used if the stored
  249. /// history type is also \ref hist_SyncClient, or when there is no top array
  250. /// yet. Likewise, the \ref hist_SyncServer history type can only be used if
  251. /// the stored history type is also \ref hist_SyncServer, or when there is
  252. /// no top array yet. Additionally, when the stored history type is \ref
  253. /// hist_SyncClient or \ref hist_SyncServer, then all subsequent sessions
  254. /// must have the same type. These restrictions apply because such a history
  255. /// needs to be maintained persistently across sessions.
  256. ///
  257. /// In general, if there is no stored history type (no top array) at the
  258. /// beginning of a new session, or if the stored type disagrees with what is
  259. /// returned by get_history_type() (which is possible due to particular
  260. /// allowed changes of history type), the actual history type (as returned
  261. /// by get_history_type()) used during that session, must be stored in the
  262. /// Realm during the first successfully committed transaction in that
  263. /// session. But note that there is still no need to expand the top array to
  264. /// store the history type \ref hist_None, due to the rule mentioned above.
  265. ///
  266. /// This function must return \ref hist_None when, and only when
  267. /// get_history() returns null.
  268. virtual HistoryType get_history_type() const noexcept
  269. {
  270. return HistoryType::hist_None;
  271. }
  272. /// Returns the schema version of the history maintained by this Replication
  273. /// implementation, or 0 if no history is maintained by it. All session
  274. /// participants must agree on history schema version.
  275. ///
  276. /// Must return 0 if get_history_type() returns \ref hist_None.
  277. virtual int get_history_schema_version() const noexcept
  278. {
  279. return 0;
  280. }
  281. /// Implementation may assume that this function is only ever called with a
  282. /// stored schema version that is less than what was returned by
  283. /// get_history_schema_version().
  284. virtual bool is_upgradable_history_schema(int /* stored_schema_version */) const noexcept
  285. {
  286. return false;
  287. }
  288. /// The implementation may assume that this function is only ever called if
  289. /// is_upgradable_history_schema() was called with the same stored schema
  290. /// version, and returned true. This implies that the specified stored
  291. /// schema version is always strictly less than what was returned by
  292. /// get_history_schema_version().
  293. virtual void upgrade_history_schema(int /* stored_schema_version */) {}
  294. /// Returns an object that gives access to the history of changesets
  295. /// used by writers. All writers can share the same object as all write
  296. /// transactions are serialized.
  297. ///
  298. /// This function must return null when, and only when get_history_type()
  299. /// returns \ref hist_None.
  300. virtual _impl::History* _get_history_write()
  301. {
  302. return nullptr;
  303. }
  304. /// Returns an object that gives access to the history of changesets in a
  305. /// way that allows for continuous transactions to work. All readers must
  306. /// get their own exclusive object as readers are not blocking each other.
  307. /// (Group::advance_transact() in particular).
  308. ///
  309. /// This function must return null when, and only when get_history_type()
  310. /// returns \ref hist_None.
  311. virtual std::unique_ptr<_impl::History> _create_history_read()
  312. {
  313. return nullptr;
  314. }
  315. void set_logger(util::Logger* logger)
  316. {
  317. m_logger = logger;
  318. }
  319. util::Logger* get_logger() const noexcept
  320. {
  321. return m_logger;
  322. }
  323. protected:
  324. Replication() = default;
  325. //@{
  326. /// do_initiate_transact() is called by initiate_transact(), and likewise
  327. /// for do_prepare_commit()
  328. ///
  329. /// With respect to exception safety, the Replication implementation has two
  330. /// options: It can prepare to accept the accumulated changeset in
  331. /// do_prepapre_commit() by allocating all required resources, and delay the
  332. /// actual acceptance to finalize_commit(), which requires that the final
  333. /// acceptance can be done without any risk of failure. Alternatively, the
  334. /// Replication implementation can fully accept the changeset in
  335. /// do_prepapre_commit() (allowing for failure), and then discard that
  336. /// changeset during the next invocation of do_initiate_transact() if
  337. /// `current_version` indicates that the previous transaction failed.
  338. virtual void do_initiate_transact(Group& group, version_type current_version, bool history_updated);
  339. //@}
  340. // Formerly part of TrivialReplication:
  341. virtual version_type prepare_changeset(const char*, size_t, version_type orig_version)
  342. {
  343. return orig_version + 1;
  344. }
  345. virtual void finalize_changeset() noexcept {}
  346. private:
  347. struct CollectionId {
  348. TableKey table_key;
  349. ObjKey object_key;
  350. ColKey col_id;
  351. CollectionId() = default;
  352. CollectionId(const CollectionBase& list)
  353. : table_key(list.get_table()->get_key())
  354. , object_key(list.get_owner_key())
  355. , col_id(list.get_col_key())
  356. {
  357. }
  358. CollectionId(TableKey t, ObjKey k, ColKey c)
  359. : table_key(t)
  360. , object_key(k)
  361. , col_id(c)
  362. {
  363. }
  364. bool operator!=(const CollectionId& other)
  365. {
  366. return object_key != other.object_key || table_key != other.table_key || col_id != other.col_id;
  367. }
  368. };
  369. _impl::TransactLogBufferStream m_stream;
  370. _impl::TransactLogEncoder m_encoder{m_stream};
  371. util::Logger* m_logger = nullptr;
  372. mutable const Table* m_selected_table = nullptr;
  373. mutable CollectionId m_selected_list;
  374. void unselect_all() noexcept;
  375. void select_table(const Table*); // unselects link list
  376. void select_collection(const CollectionBase&);
  377. void do_select_table(const Table*);
  378. void do_select_collection(const CollectionBase&);
  379. void do_set(const Table*, ColKey col_key, ObjKey key, _impl::Instruction variant = _impl::instr_Set);
  380. size_t transact_log_size();
  381. };
  382. class Replication::Interrupted : public std::exception {
  383. public:
  384. const char* what() const noexcept override
  385. {
  386. return "Interrupted";
  387. }
  388. };
  389. // Implementation:
  390. inline void Replication::initiate_transact(Group& group, version_type current_version, bool history_updated)
  391. {
  392. if (auto hist = _get_history_write()) {
  393. hist->set_group(&group, history_updated);
  394. }
  395. do_initiate_transact(group, current_version, history_updated);
  396. unselect_all();
  397. }
  398. inline void Replication::finalize_commit() noexcept
  399. {
  400. finalize_changeset();
  401. }
  402. inline BinaryData Replication::get_uncommitted_changes() const noexcept
  403. {
  404. const char* data = m_stream.get_data();
  405. size_t size = m_encoder.write_position() - data;
  406. return BinaryData(data, size);
  407. }
  408. inline size_t Replication::transact_log_size()
  409. {
  410. return m_encoder.write_position() - m_stream.get_data();
  411. }
  412. inline void Replication::unselect_all() noexcept
  413. {
  414. m_selected_table = nullptr;
  415. m_selected_list = CollectionId();
  416. }
  417. inline void Replication::select_table(const Table* table)
  418. {
  419. if (table != m_selected_table)
  420. do_select_table(table); // Throws
  421. m_selected_list = CollectionId();
  422. }
  423. inline void Replication::select_collection(const CollectionBase& list)
  424. {
  425. if (CollectionId(list) != m_selected_list) {
  426. do_select_collection(list); // Throws
  427. }
  428. }
  429. inline void Replication::erase_class(TableKey table_key, size_t)
  430. {
  431. unselect_all();
  432. m_encoder.erase_class(table_key); // Throws
  433. }
  434. inline void Replication::rename_class(TableKey table_key, StringData)
  435. {
  436. unselect_all();
  437. m_encoder.rename_class(table_key); // Throws
  438. }
  439. inline void Replication::insert_column(const Table* t, ColKey col_key, DataType, StringData, Table*)
  440. {
  441. select_table(t); // Throws
  442. m_encoder.insert_column(col_key); // Throws
  443. }
  444. inline void Replication::erase_column(const Table* t, ColKey col_key)
  445. {
  446. select_table(t); // Throws
  447. m_encoder.erase_column(col_key); // Throws
  448. }
  449. inline void Replication::rename_column(const Table* t, ColKey col_key, StringData)
  450. {
  451. select_table(t); // Throws
  452. m_encoder.rename_column(col_key); // Throws
  453. }
  454. inline void Replication::do_set(const Table* t, ColKey col_key, ObjKey key, _impl::Instruction variant)
  455. {
  456. if (variant != _impl::Instruction::instr_SetDefault) {
  457. select_table(t); // Throws
  458. m_encoder.modify_object(col_key, key); // Throws
  459. }
  460. }
  461. inline void Replication::set(const Table* t, ColKey col_key, ObjKey key, Mixed, _impl::Instruction variant)
  462. {
  463. do_set(t, col_key, key, variant); // Throws
  464. }
  465. inline void Replication::add_int(const Table* t, ColKey col_key, ObjKey key, int_fast64_t)
  466. {
  467. do_set(t, col_key, key); // Throws
  468. }
  469. inline void Replication::nullify_link(const Table* t, ColKey col_key, ObjKey key)
  470. {
  471. select_table(t); // Throws
  472. m_encoder.modify_object(col_key, key); // Throws
  473. }
  474. inline void Replication::list_set(const CollectionBase& list, size_t list_ndx, Mixed)
  475. {
  476. select_collection(list); // Throws
  477. m_encoder.collection_set(list.translate_index(list_ndx)); // Throws
  478. }
  479. inline void Replication::list_insert(const CollectionBase& list, size_t list_ndx, Mixed, size_t)
  480. {
  481. select_collection(list); // Throws
  482. m_encoder.collection_insert(list.translate_index(list_ndx)); // Throws
  483. }
  484. inline void Replication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed)
  485. {
  486. select_collection(set); // Throws
  487. m_encoder.collection_insert(set_ndx); // Throws
  488. }
  489. inline void Replication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed)
  490. {
  491. select_collection(set); // Throws
  492. m_encoder.collection_erase(set_ndx); // Throws
  493. }
  494. inline void Replication::set_clear(const CollectionBase& set)
  495. {
  496. select_collection(set); // Throws
  497. m_encoder.collection_clear(set.size()); // Throws
  498. }
  499. inline void Replication::remove_object(const Table* t, ObjKey key)
  500. {
  501. select_table(t); // Throws
  502. m_encoder.remove_object(key); // Throws
  503. }
  504. inline void Replication::list_move(const CollectionBase& list, size_t from_link_ndx, size_t to_link_ndx)
  505. {
  506. select_collection(list); // Throws
  507. m_encoder.collection_move(list.translate_index(from_link_ndx), list.translate_index(to_link_ndx)); // Throws
  508. }
  509. inline void Replication::list_erase(const CollectionBase& list, size_t link_ndx)
  510. {
  511. select_collection(list); // Throws
  512. m_encoder.collection_erase(list.translate_index(link_ndx)); // Throws
  513. }
  514. inline void Replication::typed_link_change(const Table* source_table, ColKey col, TableKey dest_table)
  515. {
  516. select_table(source_table);
  517. m_encoder.typed_link_change(col, dest_table);
  518. }
  519. } // namespace realm
  520. #endif // REALM_REPLICATION_HPP