• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / jorgen.edelbo_322

20 Jun 2024 09:43AM UTC coverage: 84.879% (-6.1%) from 90.966%
jorgen.edelbo_322

Pull #7826

Evergreen

jedelbo
remove set_direct methods from integer compressors
Pull Request #7826: Merge Next major

66056 of 81292 branches covered (81.26%)

3131 of 3738 new or added lines in 54 files covered. (83.76%)

566 existing lines in 29 files now uncovered.

84791 of 99896 relevant lines covered (84.88%)

15351931.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.64
/src/realm/sync/noinst/client_history_impl.hpp
1
///////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2022 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

19
#ifndef REALM_NOINST_CLIENT_HISTORY_IMPL_HPP
20
#define REALM_NOINST_CLIENT_HISTORY_IMPL_HPP
21

22
#include <realm/array_integer.hpp>
23
#include <realm/sync/client_base.hpp>
24
#include <realm/sync/history.hpp>
25
#include <realm/util/functional.hpp>
26
#include <realm/util/optional.hpp>
27

28
namespace realm::_impl::client_reset {
29
struct RecoveredChange;
30
}
31

32
namespace realm::sync {
33

34
class ClientReplication;
35
// As new schema versions come into existence, describe them here.
36
//
37
//  0  Initial version
38
//
39
//  1  Added support for stable IDs.
40
//
41
//  2  Now allowing continuous transactions history and synchronization history
42
//     to be separately trimmed.
43
//
44
//     Added a slot for `progress_upload_server_version` to the root array.
45
//
46
//     Reordered slots in root array.
47
//
48
//     Added a `schema_versions` table for the purpose of recording the creation
49
//     of, and the migrations of the history compartment from one schema version
50
//     to the next.
51
//
52
//     Slots pertaining to cooked history were moved into subarray
53
//     `cooked_history`.
54
//
55
//     Added slots `base_server_version` and `server_versions` to
56
//     `cooked_history` array. The former contains a server version, and the
57
//     latter contains a ref to a column of server versions.
58
//
59
//  3..9 Reserved for Core-5 based sync
60
//
61
//  10   Stable IDs supported by core.
62
//
63
//  11   Path-based instruction format for MongoDB Realm Sync (v10)
64
//
65
//       Cooked history was removed, except to verify that there is no cooked history.
66
//
67
//  12   History entries are compressed.
68

69
constexpr int get_client_history_schema_version() noexcept
70
{
57,120✔
71
    return 12;
57,120✔
72
}
57,120✔
73

74
class IntegrationException : public Exception {
75
public:
76
    IntegrationException(ErrorCodes::Error error, std::string message,
77
                         ProtocolError error_for_server = ProtocolError::other_session_error)
78
        : Exception(error, message)
18✔
79
        , error_for_server(error_for_server)
18✔
80
    {
36✔
81
    }
36✔
82

83
    explicit IntegrationException(Status status)
84
        : Exception(std::move(status))
4✔
85
        , error_for_server(ProtocolError::other_session_error)
4✔
86
    {
8✔
87
    }
8✔
88

89
    ProtocolError error_for_server;
90
};
91

92
class ClientHistory final : public _impl::History, public TransformHistory {
93
public:
94
    using version_type = sync::version_type;
95

96
    struct UploadChangeset {
97
        timestamp_type origin_timestamp;
98
        file_ident_type origin_file_ident;
99
        UploadCursor progress;
100
        ChunkedBinaryData changeset;
101
        std::unique_ptr<char[]> buffer;
102
    };
103

104
    /// set_history_adjustments() is used by client reset to adjust the
105
    /// content of the history compartment. The DB associated with
106
    /// this history object must be in a write transaction when this function
107
    /// is called.
108
    void set_history_adjustments(util::Logger& logger, version_type current_version,
109
                                 SaltedFileIdent client_file_ident, SaltedVersion server_version,
110
                                 const std::vector<_impl::client_reset::RecoveredChange>&);
111

112
    struct LocalChange {
113
        version_type version;
114
        ChunkedBinaryData changeset;
115
    };
116
    /// get_local_changes returns a list of changes which have not been uploaded yet
117
    /// 'current_version' is the version that the history should be updated to.
118
    ///
119
    /// The history must be in a transaction when this function is called.
120
    std::vector<LocalChange> get_local_changes(version_type current_version) const;
121

122
    /// Get the version of the latest snapshot of the associated Realm, as well
123
    /// as the client file identifier and the synchronization progress as they
124
    /// are stored in that snapshot.
125
    ///
126
    /// The returned current client version is the version produced by the last
127
    /// changeset in the history. The type of version returned here, is the one
128
    /// that identifies an entry in the sync history. Whether this is the same
129
    /// as the snapshot number of the Realm file depends on the history
130
    /// implementation.
131
    ///
132
    /// The returned client file identifier is the one that was last stored by
133
    /// set_client_file_ident(), or `SaltedFileIdent{0, 0}` if
134
    /// set_client_file_ident() has never been called.
135
    ///
136
    /// The returned SyncProgress is the one that was last stored by
137
    /// set_sync_progress(), or `SyncProgress{}` if set_sync_progress() has
138
    /// never been called.
139
    void get_status(version_type& current_client_version, SaltedFileIdent& client_file_ident,
140
                    SyncProgress& progress) const;
141

142
    /// Stores the server assigned client file identifier in the associated
143
    /// Realm file, such that it is available via get_status() during future
144
    /// synchronization sessions. It is an error to set this identifier more
145
    /// than once per Realm file.
146
    ///
147
    /// \param client_file_ident The server assigned client-side file
148
    /// identifier. A client-side file identifier is a non-zero positive integer
149
    /// strictly less than 2**64. The server guarantees that all client-side
150
    /// file identifiers generated on behalf of a particular server Realm are
151
    /// unique with respect to each other. The server is free to generate
152
    /// identical identifiers for two client files if they are associated with
153
    /// different server Realms.
154
    ///
155
    /// \param fix_up_object_ids The object ids that depend on client file ident
156
    /// will be fixed in both state and history if this parameter is true. If
157
    /// it is known that there are no objects to fix, it can be set to false to
158
    /// achieve higher performance.
159
    ///
160
    /// The client is required to obtain the file identifier before engaging in
161
    /// synchronization proper, and it must store the identifier and use it to
162
    /// reestablish the connection between the client file and the server file
163
    /// when engaging in future synchronization sessions.
164
    void set_client_file_ident(SaltedFileIdent client_file_ident, bool fix_up_object_ids);
165

166
    /// Stores the synchronization progress in the associated Realm file in a
167
    /// way that makes it available via get_status() during future
168
    /// synchronization sessions. Progress is reported by the server in the
169
    /// DOWNLOAD message.
170
    ///
171
    /// See struct SyncProgress for a description of \a progress.
172
    ///
173
    /// \param downloadable_bytes If specified, and if the implementation cares
174
    /// about byte-level progress, this function updates the persistent record
175
    /// of the estimate of the number of remaining bytes to be downloaded.
176
    void set_sync_progress(const SyncProgress& progress, const std::uint_fast64_t* downloadable_bytes, VersionInfo&);
177

178
    /// \brief Scan through the history for changesets to be uploaded.
179
    ///
180
    /// This function scans the history for changesets to be uploaded, i.e., for
181
    /// changesets that are not empty, and were not produced by integration of
182
    /// changesets recieved from the server. The scan begins at the position
183
    /// specified by the initial value of \a upload_progress.client_version, and
184
    /// ends no later than at the position specified by \a end_version.
185
    ///
186
    /// The implementation is allowed to end the scan before \a end_version,
187
    /// such as to limit the combined size of returned changesets. However, if
188
    /// the specified range contains any changesets that are supposed to be
189
    /// uploaded, this function must return at least one.
190
    ///
191
    /// Upon return, \a upload_progress will have been updated to point to the
192
    /// position from which the next scan should resume. This must be a position
193
    /// after the last returned changeset, and before any remaining changesets
194
    /// that are supposed to be uploaded, although never a position that
195
    /// succeeds \a end_version.
196
    ///
197
    /// The value passed as \a upload_progress by the caller, must either be one
198
    /// that was produced by an earlier invocation of
199
    /// find_uploadable_changesets(), one that was returned by get_status(), or
200
    /// one that was received by the client in a DOWNLOAD message from the
201
    /// server. When the value comes from a DOWNLOAD message, it is supposed to
202
    /// reflect a value of UploadChangeset::progress produced by an earlier
203
    /// invocation of find_uploadable_changesets().
204
    ///
205
    /// Found changesets are added to \a uploadable_changesets.
206
    ///
207
    /// \param locked_server_version will be set to the value that should be
208
    /// used as `<locked server version>` in a DOWNLOAD message.
209
    ///
210
    /// For changesets of local origin, UploadChangeset::origin_file_ident will
211
    /// be zero.
212
    void find_uploadable_changesets(UploadCursor& upload_progress, version_type end_version,
213
                                    std::vector<UploadChangeset>& uploadable_changesets,
214
                                    version_type& locked_server_version) const;
215

216
    /// \brief Integrate a sequence of changesets received from the server using
217
    /// a single Realm transaction.
218
    ///
219
    /// Each changeset will be transformed as if by a call to
220
    /// Transformer::transform_remote_changeset(), and then applied to the
221
    /// associated Realm.
222
    ///
223
    /// As a final step, each changeset will be added to the local history (list
224
    /// of applied changesets).
225
    ///
226
    /// This function checks whether the specified changesets specify valid
227
    /// remote origin file identifiers and whether the changesets contain valid
228
    /// sequences of instructions. The caller must already have ensured that the
229
    /// origin file identifiers are strictly positive and not equal to the file
230
    /// identifier assigned to this client by the server.
231
    ///
232
    /// If any of the changesets are invalid, this function returns false and
233
    /// sets `integration_error` to the appropriate value. If they are all
234
    /// deemed valid, this function updates \a version_info to reflect the new
235
    /// version produced by the transaction.
236
    ///
237
    /// \param progress The synchronization progress is what was received in the
238
    /// DOWNLOAD message along with the specified changesets. The progress will
239
    /// be persisted along with the changesets.
240
    ///
241
    /// \param downloadable_bytes If specified, and if the implementation cares
242
    /// about byte-level progress, this function updates the persistent record
243
    /// of the estimate of the number of remaining bytes to be downloaded.
244
    ///
245
    /// \param transact If specified, it is a transaction to be used to commit
246
    /// the server changesets after they were transformed.
247
    /// Note: In FLX, the transaction is left in reading state when bootstrap ends.
248
    /// In all other cases, the transaction is left in reading state when the function returns.
249
    void integrate_server_changesets(
250
        const SyncProgress& progress, const std::uint_fast64_t* downloadable_bytes,
251
        util::Span<const RemoteChangeset> changesets, VersionInfo& new_version, DownloadBatchState download_type,
252
        util::Logger&, const TransactionRef& transact,
253
        util::UniqueFunction<void(const TransactionRef&, util::Span<Changeset>)> run_in_write_tr = nullptr);
254

255
    static void get_upload_download_bytes(DB*, std::uint_fast64_t&, std::uint_fast64_t&, std::uint_fast64_t&,
256
                                          std::uint_fast64_t&, std::uint_fast64_t&);
257

258
    // Overriding member functions in realm::TransformHistory
259
    version_type find_history_entry(version_type, version_type, HistoryEntry&) const noexcept override;
260
    ChunkedBinaryData get_reciprocal_transform(version_type, bool&) const override;
261
    void set_reciprocal_transform(version_type, BinaryData) override;
262

263
public: // Stuff in this section is only used by CLI tools.
264
    /// set_local_origin_timestamp_override() allows you to override the origin timestamp of new changesets
265
    /// of local origin. This should only be used for testing and defaults to calling
266
    /// generate_changeset_timestamp().
267
    void set_local_origin_timestamp_source(util::UniqueFunction<timestamp_type()> source_fn);
268

269
private:
270
    friend class ClientReplication;
271
    static constexpr version_type s_initial_version = 1;
272

273
    ClientHistory(ClientReplication& owner)
274
        : m_replication(owner)
22,806✔
275
    {
46,012✔
276
    }
46,012✔
277

278
    ClientReplication& m_replication;
279
    DB* m_db = nullptr;
280

281
    /// The version on which the first changeset in the continuous transactions
282
    /// history is based, or if that history is empty, the version associated
283
    /// with currently bound snapshot. In general, `m_ct_history_base_version +
284
    /// m_ct_history.size()` is equal to the version that is associated with the
285
    /// currently bound snapshot, but after add_ct_history_entry() is called, it
286
    /// is equal to that plus one.
287
    mutable version_type m_ct_history_base_version = 0;
288

289
    /// Version on which the first changeset in the synchronization history is
290
    /// based, or if that history is empty, the version on which the next
291
    /// changeset, that is added, is based.  In general,
292
    /// `m_sync_history_base_version + m_sync_history_size` is equal to the
293
    /// version, that is associated with the currently bound snapshot, but after
294
    /// add_sync_history_entry() is called, it is equal to that plus one.
295
    mutable version_type m_sync_history_base_version = 0;
296

297
    using IntegerBpTree = BPlusTree<int64_t>;
298
    struct Arrays {
299
        // Create the client history arrays in the target group
300
        Arrays(DB&, Group& group);
301
        // Initialize accessors for the existing history arrays
302
        Arrays(Allocator& alloc, Group* group, ref_type ref);
303

304
        void init_from_ref(ref_type ref);
305
        void verify() const;
306

307
        // Root of history compartment
308
        Array root;
309

310
        /// Continuous transactions history
311
        BinaryColumn ct_history;
312

313
        /// A column of changesets, one row for each entry in the history.
314
        ///
315
        /// FIXME: Ideally, the B+tree accessor below should have been just
316
        /// Bptree<BinaryData>, but Bptree<BinaryData> seems to not allow that yet.
317
        BinaryColumn changesets;
318
        BinaryColumn reciprocal_transforms;
319

320
        IntegerBpTree remote_versions;
321
        IntegerBpTree origin_file_idents;
322
        IntegerBpTree origin_timestamps;
323

324
    private:
325
        Arrays(Allocator&) noexcept;
326
    };
327

328
    // clang-format off
329

330
    // Sizes of fixed-size arrays
331
    static constexpr int s_root_size            = 21;
332
    static constexpr int s_schema_versions_size =  4;
333

334
    // Slots in root array of history compartment
335
    static constexpr int s_ct_history_iip = 0;                          // column ref
336
    static constexpr int s_client_file_ident_iip = 1;                   // integer
337
    static constexpr int s_client_file_ident_salt_iip = 2;              // integer
338
    static constexpr int s_progress_latest_server_version_iip = 3;      // integer
339
    static constexpr int s_progress_latest_server_version_salt_iip = 4; // integer
340
    static constexpr int s_progress_download_server_version_iip = 5;    // integer
341
    static constexpr int s_progress_download_client_version_iip = 6;    // integer
342
    static constexpr int s_progress_upload_client_version_iip = 7;      // integer
343
    static constexpr int s_progress_upload_server_version_iip = 8;      // integer
344
    static constexpr int s_progress_downloaded_bytes_iip = 9;           // integer
345
    static constexpr int s_progress_downloadable_bytes_iip = 10;        // integer
346
    static constexpr int s_progress_uploaded_bytes_iip = 11;            // integer
347
    static constexpr int s_progress_uploadable_bytes_iip = 12;          // integer
348
    static constexpr int s_changesets_iip = 13;                         // column ref
349
    static constexpr int s_reciprocal_transforms_iip = 14;              // column ref
350
    static constexpr int s_remote_versions_iip = 15;                    // column ref
351
    static constexpr int s_origin_file_idents_iip = 16;                 // column ref
352
    static constexpr int s_origin_timestamps_iip = 17;                  // column ref
353
    static constexpr int s_object_id_history_state_iip = 18;            // ref
354
    static constexpr int s_cooked_history_iip = 19;                     // ref (removed)
355
    static constexpr int s_schema_versions_iip = 20;                    // table ref
356

357
    // Slots in root array of `schema_versions` table
358
    static constexpr int s_sv_schema_versions_iip = 0;   // integer
359
    static constexpr int s_sv_library_versions_iip = 1;  // ref
360
    static constexpr int s_sv_snapshot_versions_iip = 2; // integer (version_type)
361
    static constexpr int s_sv_timestamps_iip = 3;        // integer (seconds since epoch)
362

363
    // clang-format on
364

365
    // The construction of the array accessors need to be delayed, because the
366
    // allocator (Allocator) is not known at the time of construction of the
367
    // ServerHistory object.
368
    mutable util::Optional<Arrays> m_arrays;
369

370
    // When applying server changesets, we create a history entry with the data
371
    // from the server instead of using the one generated from applying the
372
    // instructions to the local data. integrate_server_changesets() sets this
373
    // to true to indicate to add_changeset() that it should skip creating a
374
    // history entry.
375
    //
376
    // This field is guarded by the DB's write lock and should only be accessed
377
    // while that is held.
378
    mutable bool m_applying_server_changeset = false;
379
    bool m_applying_client_reset = false;
380

381
    // Cache of s_progress_download_server_version_iip and
382
    // s_progress_download_client_version_iip slots of history compartment root
383
    // array.
384
    mutable DownloadCursor m_progress_download = {0, 0};
385

386
    version_type m_version_of_oldest_bound_snapshot = 0;
387

388
    util::UniqueFunction<timestamp_type()> m_local_origin_timestamp_source = generate_changeset_timestamp;
389

390
    void initialize(DB& db) noexcept
391
    {
46,010✔
392
        m_db = &db;
46,010✔
393
    }
46,010✔
394

395
    static version_type find_sync_history_entry(Arrays& arrays, version_type base_version, version_type begin_version,
396
                                                version_type end_version, HistoryEntry& entry,
397
                                                version_type& last_integrated_server_version) noexcept;
398

399
    // sum_of_history_entry_sizes calculates the sum of the changeset sizes of the local history
400
    // entries that produced a version that succeeds `begin_version` and precedes `end_version`.
401
    std::uint_fast64_t sum_of_history_entry_sizes(version_type begin_version,
402
                                                  version_type end_version) const noexcept;
403

404
    size_t transform_and_apply_server_changesets(util::Span<Changeset> changesets_to_integrate, TransactionRef,
405
                                                 util::Logger&, std::uint64_t& downloaded_bytes,
406
                                                 bool allow_lock_release);
407

408
    void prepare_for_write();
409
    Replication::version_type add_changeset(BinaryData changeset, BinaryData sync_changeset);
410
    void add_sync_history_entry(const HistoryEntry&);
411
    void update_sync_progress(const SyncProgress&, const std::uint_fast64_t* downloadable_bytes, TransactionRef);
412
    void trim_ct_history();
413
    void trim_sync_history();
414
    void do_trim_sync_history(std::size_t n);
415
    void clamp_sync_version_range(version_type& begin, version_type& end) const noexcept;
416
    void fix_up_client_file_ident_in_stored_changesets(Transaction&, file_ident_type);
417
    void record_current_schema_version();
418
    static void record_current_schema_version(Array& schema_versions, version_type snapshot_version);
419
    void compress_stored_changesets();
420

421
    size_t sync_history_size() const noexcept
422
    {
2,796,556✔
423
        return m_arrays ? m_arrays->changesets.size() : 0;
2,148,823,249✔
424
    }
2,796,556✔
425
    size_t ct_history_size() const noexcept
426
    {
724,576✔
427
        return m_arrays ? m_arrays->ct_history.size() : 0;
724,576✔
428
    }
724,576✔
429

430
    // Overriding member functions in realm::_impl::History
431
    void set_group(Group* group, bool updated = false) override;
432
    void update_from_ref_and_version(ref_type ref, version_type version) override;
433
    void update_from_parent(version_type current_version) override;
434
    void get_changesets(version_type, version_type, BinaryIterator*) const noexcept override;
435
    void set_oldest_bound_version(version_type) override;
436
    void verify() const override;
437
    bool no_pending_local_changes(version_type version) const override;
438
};
439

440
class ClientReplication final : public SyncReplication {
441
public:
442
    ClientReplication(bool apply_server_changes = true)
443
        : m_history(*this)
9,954✔
444
        , m_apply_server_changes(apply_server_changes)
9,954✔
445
    {
20,250✔
446
    }
20,250✔
447

448
    // A write validator factory takes a write transaction and returns a UniqueFunction containing a
449
    // SyncReplication::WriteValidator. The factory will get called at the start of a write transaction
450
    // and the WriteValidator it returns will be re-used for all mutations within the transaction.
451
    using WriteValidatorFactory = util::UniqueFunction<WriteValidator>(Transaction&);
452
    void set_write_validator_factory(util::UniqueFunction<WriteValidatorFactory> validator_factory)
453
    {
1,708✔
454
        m_write_validator_factory = std::move(validator_factory);
1,708✔
455
    }
1,708✔
456

457
    // Overriding member functions in realm::Replication
458
    void initialize(DB& sg) override;
459
    HistoryType get_history_type() const noexcept override;
460
    int get_history_schema_version() const noexcept override;
461
    bool is_upgradable_history_schema(int) const noexcept override;
462
    void upgrade_history_schema(int) override;
463

464
    _impl::History* _get_history_write() override
465
    {
351,216✔
466
        return &m_history;
351,216✔
467
    }
351,216✔
468
    std::unique_ptr<_impl::History> _create_history_read() override
469
    {
25,762✔
470
        auto hist = std::unique_ptr<ClientHistory>(new ClientHistory(*this));
25,762✔
471
        hist->initialize(*m_history.m_db);
25,762✔
472
        return hist;
25,762✔
473
    }
25,762✔
474

475
    // Overriding member functions in realm::Replication
476
    version_type prepare_changeset(const char*, size_t, version_type) override;
477

478
    ClientHistory& get_history() noexcept
479
    {
135,768✔
480
        return m_history;
135,768✔
481
    }
135,768✔
482

483
    const ClientHistory& get_history() const noexcept
UNCOV
484
    {
×
UNCOV
485
        return m_history;
×
UNCOV
486
    }
×
487

488
    bool apply_server_changes() const noexcept
489
    {
25,344✔
490
        return m_apply_server_changes;
25,344✔
491
    }
25,344✔
492

493
protected:
494
    util::UniqueFunction<WriteValidator> make_write_validator(Transaction& tr) override;
495

496
private:
497
    ClientHistory m_history;
498
    const bool m_apply_server_changes;
499
    util::UniqueFunction<WriteValidatorFactory> m_write_validator_factory;
500
};
501

502

503
// Implementation
504

505
// Clamp the beginning of the specified upload skippable version range to the
506
// beginning of the synchronization history.
507
//
508
// A version range whose beginning is related to
509
// `m_progress_download.last_intergated_client_version` is susceptible to fall
510
// wholly, or partly before the beginning of the synchronization history due to
511
// aggressive trimming.
512
//
513
// This is not a problem because
514
//
515
// - all such ranges are used in contexts where upload skippable history entries
516
//   have no effect,
517
//
518
// - the beginning of such a range is always greater than or equal to
519
//   `m_progress_download.last_integrated_client_version`, and
520
//
521
// - the trimming rules of the synchronization history ensure that whenever such
522
//   a range refers to a history entry that is no longer in the history, then
523
//   that entry is upload skippable.
524
//
525
// See trim_sync_history() for further details, and in particular, for a
526
// definition of *upload skippable*.
527
inline void ClientHistory::clamp_sync_version_range(version_type& begin, version_type& end) const noexcept
528
{
41,564✔
529
    REALM_ASSERT(begin <= end);
41,564✔
530
    REALM_ASSERT(m_progress_download.last_integrated_client_version <= begin);
41,564✔
531
    if (begin < m_sync_history_base_version) {
41,564✔
532
        begin = m_sync_history_base_version;
19,244✔
533
        if (end < m_sync_history_base_version)
19,244✔
534
            end = m_sync_history_base_version;
262✔
535
    }
19,244✔
536
}
41,564✔
537

538

539
/// \brief Create a "sync history" implementation of the realm::Replication
540
/// interface.
541
///
542
/// The intended role for such an object is as a plugin for new
543
/// realm::DB objects.
544
std::unique_ptr<ClientReplication> make_client_replication();
545

546
} // namespace realm::sync
547

548
#endif // REALM_NOINST_CLIENT_HISTORY_IMPL_HPP
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc