1 /*************************************************************************
6 * [2011] - [2015] Realm Inc
9 * NOTICE: All information contained herein is, and remains
10 * the property of Realm Incorporated and its suppliers,
11 * if any. The intellectual and technical concepts contained
12 * herein are proprietary to Realm Incorporated
13 * and its suppliers and may be covered by U.S. and Foreign Patents,
14 * patents in process, and are protected by trade secret or copyright law.
15 * Dissemination of this information or reproduction of this material
16 * is strictly forbidden unless prior written permission is obtained
17 * from Realm Incorporated.
19 **************************************************************************/
21 #ifndef REALM_SYNC_TRANSFORM_HPP
22 #define REALM_SYNC_TRANSFORM_HPP
26 #include <realm/util/buffer.hpp>
27 #include <realm/impl/cont_transact_hist.hpp>
28 #include <realm/group_shared.hpp>
29 #include <realm/chunked_binary.hpp>
36 /// Represents an entry in the history of changes in a sync-enabled Realm
37 /// file. Server and client use different history formats, but this class is
38 /// used both on the server and the client side. Each history entry corresponds
39 /// to a version of the Realm state. For server and client-side histories, these
40 /// versions are referred to as *server versions* and *client versions*
41 /// respectively. These versions may, or may not correspond to Realm snapshot
42 /// numbers (on the server-side they currently do not).
44 /// FIXME: Move this class into a separate header
45 /// (`<realm/sync/history_entry.hpp>`).
48 using timestamp_type = uint_fast64_t;
49 using file_ident_type = uint_fast64_t;
50 using version_type = _impl::History::version_type;
52 /// The time of origination of the changes referenced by this history entry,
53 /// meassured as the number of milliseconds since 2015-01-01T00:00:00Z, not
54 /// including leap seconds. For changes of local origin, this is the local
55 /// time at the point when the local transaction was committed. For changes
56 /// of remote origin, it is the remote time of origin at the client
57 /// identified by `origin_client_file_ident`.
58 timestamp_type origin_timestamp;
60 /// For changes of local origin, this is the identifier of the local
61 /// file. On the client side, the special value, zero, is used as a stand-in
62 /// for the actual file identifier. This is necessary because changes may
63 /// occur on the client-side before it obtains the the actual identifier
64 /// from the server. Depending on context, the special value, zero, will, or
65 /// will not have been replaced by the actual local file identifier.
67 /// For changes of remote origin, this is the identifier of the file in the
68 /// context of which this change originated. This may be a client, or a
69 /// server-side file. For example, when a change "travels" from client file
70 /// A with identifier 2, through the server, to client file B with
71 /// identifier 3, then `origin_client_file_ident` will be 2 on the server
72 /// and in client file A. On the other hand, if the change originates on the
73 /// server, and the server-side file identifier is 1, then
74 /// `origin_client_file_ident` will be 1 in both client files.
76 /// FIXME: Rename this member to `origin_file_ident`. It is no longer
77 /// necessarily a client-side file.
78 file_ident_type origin_client_file_ident;
80 /// For changes of local origin on the client side, this is the last server
81 /// version integrated locally prior to this history entry. In other words,
82 /// it is a copy of `remote_version` of the last preceding history entry
83 /// that carries changes of remote origin, or zero if there is no such
84 /// preceding history entry.
86 /// For changes of local origin on the server-side, this is always zero.
88 /// For changes of remote origin, this is the version produced within the
89 /// remote-side Realm file by the change that gave rise to this history
90 /// entry. The remote-side Realm file is not always the same Realm file from
91 /// which the change originated. On the client side, the remote side is
92 /// always the server side, and `remote_version` is always a server version
93 /// (since clients do not speak directly to each other). On the server side,
94 /// the remote side is always a client side, and `remote_version` is always
96 version_type remote_version;
98 /// Referenced memory is not owned by this class.
99 ChunkedBinaryData changeset;
103 /// The interface between the sync history and the operational transformer
105 class TransformHistory {
107 using timestamp_type = HistoryEntry::timestamp_type;
108 using file_ident_type = HistoryEntry::file_ident_type;
109 using version_type = HistoryEntry::version_type;
111 /// Get the first history entry where the produced version is greater than
112 /// `begin_version` and less than or equal to `end_version`, and whose
113 /// changeset is neither empty, nor produced by integration of a changeset
114 /// received from the specified remote peer.
116 /// If \a buffer is non-null, memory will be allocated and transferred to
117 /// \a buffer. The changeset will be copied into the newly allocated memory.
119 /// If \a buffer is null, the changeset is not copied out of the Realm,
120 /// and entry.changeset.data() does not point to the changeset.
121 /// The changeset in the Realm could be chunked, hence it is not possible
122 /// to point to it with BinaryData. entry.changeset.size() always gives the
123 /// size of the changeset.
125 /// \param begin_version, end_version The range of versions to consider. If
126 /// `begin_version` is equal to `end_version`, this is the empty range. If
127 /// `begin_version` is zero, it means that everything preceeding
128 /// `end_version` is to be considered, which is again the empty range if
129 /// `end_version` is also zero. Zero is is special value in that no
130 /// changeset produces that version. It is an error if `end_version`
131 /// preceeds `begin_version`, or if `end_version` is zero and
132 /// `begin_version` is not.
134 /// \param not_from_remote_client_file_ident Skip entries whose changeset is
135 /// produced by integration of changesets received from this remote
136 /// peer. Zero if the remote peer is the server, otherwise the peer
137 /// identifier of a client.
139 /// \param only_nonempty Skip entries with empty changesets.
141 /// \return The version produced by the changeset of the located history
142 /// entry, or zero if no history entry exists matching the specified
144 virtual version_type find_history_entry(version_type begin_version, version_type end_version,
145 file_ident_type not_from_remote_client_file_ident,
146 bool only_nonempty, HistoryEntry& entry) const noexcept = 0;
148 /// Get the specified reciprocal changeset. The targeted history entry is
149 /// the one whose untransformed changeset produced the specified version.
151 /// \param remote_client_file_ident Zero if the remote peer is the server,
152 /// otherwise the peer identifier of a client.
153 virtual ChunkedBinaryData get_reciprocal_transform(version_type version,
154 file_ident_type remote_client_file_ident) const = 0;
156 /// Replace the specified reciprocally transformed changeset. The targeted
157 /// history entry is the one whose untransformed changeset produced the
158 /// specified version.
160 /// \param remote_client_file_ident See get_reciprocal_transform().
162 /// \param encoded_changeset The new reciprocally transformed changeset.
163 virtual void set_reciprocal_transform(version_type version,
164 file_ident_type remote_client_file_ident,
165 BinaryData encoded_changeset) = 0;
167 virtual ~TransformHistory() noexcept {}
171 class TransformError; // Exception
175 using timestamp_type = HistoryEntry::timestamp_type;
176 using file_ident_type = HistoryEntry::file_ident_type;
177 using version_type = HistoryEntry::version_type;
179 class RemoteChangeset;
182 /// Produce an operationally transformed version of the specified changesets,
183 /// which are assumed to be of remote origin, and received from remote peer
184 /// P. Note that P is not necessarily the peer from which the changes
187 /// Operational transformation is carried out between the specified
188 /// changesets and all causally unrelated changesets in the local history. A
189 /// changeset in the local history is causally unrelated if, and only if it
190 /// occurs after the local changeset that produced
191 /// `remote_changeset.last_integrated_local_version` and is not a produced
192 /// by integration of a changeset received from P. This assumes that
193 /// `remote_changeset.last_integrated_local_version` is set to the local
194 /// version produced by the last local changeset, that was integrated by P
195 /// before P produced the specified changeset.
197 /// The operational transformation is reciprocal (two-way), so it also
198 /// transforms the causally unrelated local changesets. This process does
199 /// not modify the history itself (the changesets available through
200 /// TransformHistory::get_history_entry()), instead the reciprocally
201 /// transformed changesets are stored separately, and individually for each
202 /// remote peer, such that they can participate in transformation of the
203 /// next incoming changeset from P. Note that the peer identifier of P can
204 /// be derived from `origin_client_file_ident` and information about whether
205 /// the local peer is a server or a client.
207 /// In general, if A and B are two causally unrelated (alternative)
208 /// changesets based on the same version V, then the operational
209 /// transformation between A and B produces changesets A' and B' such that
210 /// both of the concatenated changesets A + B' and B + A' produce the same
211 /// final state when applied to V. Operational transformation is meaningful
212 /// only when carried out between alternative changesets based on the same
215 /// \return The size of the transformed version of the specified
216 /// changesets. Upon return, the transformed changesets are concatenated
217 /// and placed in \a output_buffer.
219 /// \throw TransformError Thrown if operational transformation fails due to
220 /// a problem with the specified changeset.
221 virtual void transform_remote_changesets(TransformHistory&,
222 version_type current_local_version,
223 Changeset* changesets,
224 std::size_t num_changesets,
225 Reporter* = nullptr) = 0;
227 virtual ~Transformer() noexcept {}
230 /// \param local_client_file_ident The server assigned local client file
231 /// identifier. This must be zero on the server-side, and only on the
233 std::unique_ptr<Transformer> make_transformer(Transformer::file_ident_type local_client_file_ident);
235 class Transformer::RemoteChangeset {
237 /// The version produced on the remote peer by this changeset.
239 /// On the server, the remote peer is the client from which the changeset
240 /// originated, and `remote_version` is the client version produced by the
241 /// changeset on that client.
243 /// On a client, the remote peer is the server, and `remote_version` is the
244 /// server version produced by this changeset on the server. Since the
245 /// server is never the originator of changes, this changeset must in turn
246 /// have been produced on the server by integration of a changeset uploaded
247 /// by some other client.
248 version_type remote_version = 0;
250 /// The last local version that has been integrated into `remote_version`.
252 /// A local version, L, has been integrated into a remote version, R, when,
253 /// and only when L is the latest local version such that all preceeding
254 /// changesets in the local history have been integrated by the remote peer
257 /// On the server, this is the last server version integrated into the
258 /// client version `remote_version`. On a client, it is the last client
259 /// version integrated into the server version `remote_version`.
260 version_type last_integrated_local_version = 0;
262 /// The changeset itself.
263 ChunkedBinaryData data;
265 /// Same meaning as `HistoryEntry::origin_timestamp`.
266 timestamp_type origin_timestamp = 0;
268 /// Same meaning as `HistoryEntry::origin_client_file_ident`.
269 file_ident_type origin_client_file_ident = 0;
271 /// If the changeset was compacted during download, the size of the original
273 size_t original_changeset_size = 0;
276 RemoteChangeset(version_type rv, version_type lv, ChunkedBinaryData d, timestamp_type ot,
280 class Transformer::Reporter {
282 virtual void report_merges(long num_merges) = 0;
286 void parse_remote_changeset(const Transformer::RemoteChangeset&, Changeset&);
293 class TransformError: public std::runtime_error {
295 TransformError(const std::string& message):
296 std::runtime_error(message)
301 inline Transformer::RemoteChangeset::RemoteChangeset(version_type rv, version_type lv,
302 ChunkedBinaryData d, timestamp_type ot,
305 last_integrated_local_version(lv),
307 origin_timestamp(ot),
308 origin_client_file_ident(fi)
315 #endif // REALM_SYNC_TRANSFORM_HPP