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

realm / realm-core / 1786

28 Oct 2023 12:35PM UTC coverage: 91.562% (-0.02%) from 91.582%
1786

push

Evergreen

web-flow
Improve configurations for sanitized builds (#6911)

* Refactor sanitizer flags for different build types:

** Enable address sanitizer for msvc
** Allow to build with sanitizer for diffent optimized build (also Debug)
** Make RelASAN, RelTSAN, RelUSAN, RelUSAN just shortcuts for half-optimized builds

* Fix usage of moved object for fuzz tester
* Check asan/tsan on macos x64/arm64
* Check asan with msvc 2019
* Remove Jenkins sanitized builders replaced by evergreen configs
* Work-around stack-use-after-scope with msvc2019 and mpark
* Fix crash on check with staled ColKeys
* fix a buffer overrun in a test
* fix a race in async_open_realm test util
* Add some logger related test fixes
* Work around catch2 limmitation with not thread safe asserts and TSAN races
* Run multiprocesses tests under sanitizers
* add assert for an error reported by undefined sanitizer
* Workaround uv scheduler main thread only constraint for callbacks called from non main thread and requesting a realm

---------

Co-authored-by: James Stone <james.stone@mongodb.com>

94310 of 173648 branches covered (0.0%)

54 of 63 new or added lines in 15 files covered. (85.71%)

2212 existing lines in 52 files now uncovered.

230602 of 251853 relevant lines covered (91.56%)

6943670.77 hits per line

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

61.68
/src/realm/sync/transform.cpp
1
#include <algorithm>
2
#include <functional>
3
#include <utility>
4
#include <vector>
5
#include <map>
6
#include <sstream>
7
#include <fstream>
8

9
#if REALM_DEBUG
10
#include <iostream> // std::cerr used for debug tracing
11
#include <mutex>    // std::unique_lock used for debug tracing
12
#endif              // REALM_DEBUG
13

14
#include <realm/util/buffer.hpp>
15
#include <realm/string_data.hpp>
16
#include <realm/data_type.hpp>
17
#include <realm/mixed.hpp>
18
#include <realm/column_fwd.hpp>
19
#include <realm/db.hpp>
20
#include <realm/impl/transact_log.hpp>
21
#include <realm/replication.hpp>
22
#include <realm/sync/instructions.hpp>
23
#include <realm/sync/protocol.hpp>
24
#include <realm/sync/transform.hpp>
25
#include <realm/sync/changeset_parser.hpp>
26
#include <realm/sync/changeset_encoder.hpp>
27
#include <realm/sync/noinst/changeset_index.hpp>
28
#include <realm/sync/noinst/protocol_codec.hpp>
29
#include <realm/util/logger.hpp>
30

31
namespace realm {
32

33
namespace {
34

35
#if REALM_DEBUG
36
#if defined(_MSC_VER)
37
#define TERM_RED ""
38
#define TERM_YELLOW ""
39
#define TERM_CYAN ""
40
#define TERM_MAGENTA ""
41
#define TERM_GREEN ""
42
#define TERM_BOLD ""
43
#define TERM_RESET ""
44
#else
UNCOV
45
#define TERM_RED "\x1b[31;22m"
×
UNCOV
46
#define TERM_YELLOW "\x1b[33;22m"
×
UNCOV
47
#define TERM_CYAN "\x1b[36;22m"
×
UNCOV
48
#define TERM_MAGENTA "\x1b[35;22m"
×
49
#define TERM_GREEN "\x1b[32;22m"
50
#define TERM_BOLD "\x1b[1m"
UNCOV
51
#define TERM_RESET "\x1b[39;49;22m"
×
52
#endif
53
#endif
54

55
} // unnamed namespace
56

57
using namespace realm;
58
using namespace realm::sync;
59
using namespace realm::util;
60

61
namespace _impl {
62

63
struct TransformerImpl::Discriminant {
64
    timestamp_type timestamp;
65
    file_ident_type client_file_ident;
66
    Discriminant(timestamp_type t, file_ident_type p)
67
        : timestamp(t)
68
        , client_file_ident(p)
69
    {
11,876,718✔
70
    }
11,876,718✔
71

72
    Discriminant(const Discriminant&) = default;
73
    Discriminant& operator=(const Discriminant&) = default;
74

75
    bool operator<(const Discriminant& other) const
76
    {
32,504✔
77
        return timestamp == other.timestamp ? (client_file_ident < other.client_file_ident)
16,550✔
78
                                            : timestamp < other.timestamp;
32,002✔
79
    }
32,504✔
80

81
    bool operator==(const Discriminant& other) const
UNCOV
82
    {
×
UNCOV
83
        return timestamp == other.timestamp && client_file_ident == other.client_file_ident;
×
UNCOV
84
    }
×
85
    bool operator!=(const Discriminant& other) const
UNCOV
86
    {
×
UNCOV
87
        return !((*this) == other);
×
88
    }
×
89
};
90

91
struct TransformerImpl::Side {
92
    Transformer& m_transformer;
93
    Changeset* m_changeset = nullptr;
94
    Discriminant m_discriminant;
95

96
    bool was_discarded = false;
97
    bool was_replaced = false;
98
    size_t m_path_len = 0;
99

100
    Side(Transformer& transformer)
101
        : m_transformer(transformer)
102
        , m_discriminant(0, 0)
103
    {
834,444✔
104
    }
834,444✔
105

106
    virtual void skip_tombstones() noexcept = 0;
107
    virtual void next_instruction() noexcept = 0;
108
    virtual Instruction& get() noexcept = 0;
109

110
    void substitute(const Instruction& instr)
UNCOV
111
    {
×
UNCOV
112
        was_replaced = true;
×
UNCOV
113
        get() = instr;
×
UNCOV
114
    }
×
115

116
    StringData get_string(StringBufferRange range) const
117
    {
32✔
118
        // Relying on the transaction parser to only provide valid StringBufferRanges.
16✔
119
        return m_changeset->get_string(range);
32✔
120
    }
32✔
121

122
    StringData get_string(InternString intern_string) const
123
    {
36,100✔
124
        // Rely on the parser having checked the consistency of the interned strings
17,708✔
125
        return m_changeset->get_string(intern_string);
36,100✔
126
    }
36,100✔
127

128
    InternString intern_string(StringData data) const
129
    {
×
130
        return m_changeset->intern_string(data);
×
131
    }
×
132

133
    const Discriminant& timestamp() const
134
    {
65,004✔
135
        return m_discriminant;
65,004✔
136
    }
65,004✔
137

138
    InternString adopt_string(const Side& other_side, InternString other_string)
139
    {
×
140
        // FIXME: This needs to change if we choose to compare strings through a
141
        // mapping of InternStrings.
142
        StringData string = other_side.get_string(other_string);
×
143
        return intern_string(string);
×
144
    }
×
145

146
    Instruction::PrimaryKey adopt_key(const Side& other_side, const Instruction::PrimaryKey& other_key)
147
    {
×
148
        if (auto str = mpark::get_if<InternString>(&other_key)) {
×
149
            return adopt_string(other_side, *str);
×
150
        }
×
151
        else {
×
152
            // Non-string keys do not need to be adopted.
×
153
            return other_key;
×
UNCOV
154
        }
×
UNCOV
155
    }
×
156

157
    void adopt_path(Instruction::PathInstruction& instr, const Side& other_side,
158
                    const Instruction::PathInstruction& other)
UNCOV
159
    {
×
UNCOV
160
        instr.table = adopt_string(other_side, other.table);
×
UNCOV
161
        instr.object = adopt_key(other_side, other.object);
×
UNCOV
162
        instr.field = adopt_string(other_side, other.field);
×
UNCOV
163
        instr.path.m_path.clear();
×
UNCOV
164
        instr.path.m_path.reserve(other.path.size());
×
UNCOV
165
        for (auto& element : other.path.m_path) {
×
UNCOV
166
            auto push = util::overload{
×
UNCOV
167
                [&](uint32_t index) {
×
UNCOV
168
                    instr.path.m_path.push_back(index);
×
UNCOV
169
                },
×
UNCOV
170
                [&](InternString str) {
×
UNCOV
171
                    instr.path.m_path.push_back(adopt_string(other_side, str));
×
UNCOV
172
                },
×
UNCOV
173
            };
×
UNCOV
174
            mpark::visit(push, element);
×
UNCOV
175
        }
×
UNCOV
176
    }
×
177

178
protected:
179
    void init_with_instruction(const Instruction& instr) noexcept
180
    {
11,042,088✔
181
        was_discarded = false;
11,042,088✔
182
        was_replaced = false;
11,042,088✔
183
        m_path_len = instr.path_length();
11,042,088✔
184
    }
11,042,088✔
185
};
186

187
struct TransformerImpl::MajorSide : TransformerImpl::Side {
188
    MajorSide(Transformer& transformer)
189
        : Side(transformer)
190
    {
417,224✔
191
    }
417,224✔
192

193
    void set_next_changeset(Changeset* changeset) noexcept;
194
    void discard();
195
    void prepend(Instruction operation);
196
    template <class InputIterator>
197
    void prepend(InputIterator begin, InputIterator end);
198

199
    void init_with_instruction(Changeset::iterator position) noexcept
200
    {
8,406,890✔
201
        REALM_ASSERT(position >= m_changeset->begin());
8,406,890✔
202
        REALM_ASSERT(position != m_changeset->end());
8,406,890✔
203
        m_position = position;
8,406,890✔
204
        skip_tombstones();
8,406,890✔
205
        REALM_ASSERT(position != m_changeset->end());
8,406,890✔
206

4,234,602✔
207
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
8,406,890✔
208

4,234,602✔
209
        Side::init_with_instruction(get());
8,406,890✔
210
    }
8,406,890✔
211

212
    void skip_tombstones() noexcept final
213
    {
36,409,120✔
214
        while (m_position != m_changeset->end() && !*m_position) {
36,410,448✔
215
            ++m_position;
1,328✔
216
        }
1,328✔
217
    }
36,409,120✔
218

219
    void next_instruction() noexcept final
220
    {
8,337,932✔
221
        REALM_ASSERT(m_position != m_changeset->end());
8,337,932✔
222
        do {
8,338,062✔
223
            ++m_position;
8,338,062✔
224
        } while (m_position != m_changeset->end() && !*m_position);
8,338,062✔
225
    }
8,337,932✔
226

227
    Instruction& get() noexcept final
228
    {
38,444,448✔
229
        return **m_position;
38,444,448✔
230
    }
38,444,448✔
231

232
    size_t get_object_ids_in_current_instruction(_impl::ChangesetIndex::GlobalID* ids, size_t max_ids)
233
    {
7,484,108✔
234
        return _impl::get_object_ids_in_instruction(*m_changeset, get(), ids, max_ids);
7,484,108✔
235
    }
7,484,108✔
236

237
    Changeset::iterator m_position;
238
};
239

240
struct TransformerImpl::MinorSide : TransformerImpl::Side {
241
    using Position = _impl::ChangesetIndex::RangeIterator;
242

243
    MinorSide(Transformer& transformer)
244
        : Side(transformer)
245
    {
417,222✔
246
    }
417,222✔
247

248
    void discard();
249
    void prepend(Instruction operation);
250
    template <class InputIterator>
251
    void prepend(InputIterator begin, InputIterator end);
252

253
    void substitute(const Instruction& instr)
UNCOV
254
    {
×
UNCOV
255
        was_replaced = true;
×
UNCOV
256
        get() = instr;
×
UNCOV
257
    }
×
258

259
    Position begin() noexcept
260
    {
8,406,986✔
261
        return Position{m_conflict_ranges};
8,406,986✔
262
    }
8,406,986✔
263

264
    Position end() noexcept
265
    {
59,620,340✔
266
        return Position{m_conflict_ranges, Position::end_tag{}};
59,620,340✔
267
    }
59,620,340✔
268

269
    void update_changeset_pointer() noexcept
270
    {
15,923,760✔
271
        if (REALM_LIKELY(m_position != end())) {
15,923,760✔
272
            m_changeset = m_position.m_outer->first;
2,822,768✔
273
        }
2,822,768✔
274
        else {
13,100,992✔
275
            m_changeset = nullptr;
13,100,992✔
276
        }
13,100,992✔
277
    }
15,923,760✔
278

279
    void skip_tombstones() noexcept final
280
    {
16,124,368✔
281
        if (m_position != end() && *m_position)
16,124,368✔
282
            return;
5,421,012✔
283
        skip_tombstones_slow();
10,703,356✔
284
    }
10,703,356✔
285

286
    REALM_NOINLINE void skip_tombstones_slow() noexcept
287
    {
10,703,934✔
288
        while (m_position != end() && !*m_position) {
11,384,112✔
289
            ++m_position;
680,178✔
290
        }
680,178✔
291
        update_changeset_pointer();
10,703,934✔
292
    }
10,703,934✔
293

294
    void next_instruction() noexcept final
295
    {
2,516,240✔
296
        REALM_ASSERT(m_position != end());
2,516,240✔
297
        ++m_position;
2,516,240✔
298
        update_changeset_pointer();
2,516,240✔
299
        skip_tombstones();
2,516,240✔
300
    }
2,516,240✔
301

302
    Instruction& get() noexcept final
303
    {
16,784,272✔
304
        Instruction* instr = *m_position;
16,784,272✔
305
        REALM_ASSERT(instr != nullptr);
16,784,272✔
306
        return *instr;
16,784,272✔
307
    }
16,784,272✔
308

309
    void init_with_instruction(Position position)
310
    {
2,635,534✔
311
        // REALM_ASSERT(position >= Position(m_conflict_ranges));
1,284,792✔
312
        REALM_ASSERT(position != end());
2,635,534✔
313
        m_position = position;
2,635,534✔
314
        update_changeset_pointer();
2,635,534✔
315
        skip_tombstones();
2,635,534✔
316
        REALM_ASSERT(position != end());
2,635,534✔
317

1,284,792✔
318
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
2,635,534✔
319

1,284,792✔
320
        Side::init_with_instruction(get());
2,635,534✔
321
    }
2,635,534✔
322

323
    _impl::ChangesetIndex::RangeIterator m_position;
324
    _impl::ChangesetIndex* m_changeset_index = nullptr;
325
    _impl::ChangesetIndex::Ranges* m_conflict_ranges = nullptr;
326
};
327

328
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug utilities
329

330
struct TransformerImpl::MergeTracer {
331
public:
332
    Side& m_minor;
333
    Side& m_major;
334
    const Changeset& m_minor_log;
335
    const Changeset& m_major_log;
336
    Instruction minor_before;
337
    Instruction major_before;
338

339
    // field => pair(original_value, change)
340
    struct Diff {
341
        std::map<std::string, std::pair<int64_t, int64_t>> numbers;
342
        std::map<std::string, std::pair<std::string, std::string>> strings;
343

344
        bool empty() const noexcept
345
        {
×
346
            return numbers.empty() && strings.empty();
×
UNCOV
347
        }
×
348
    };
349

350
    explicit MergeTracer(Side& minor, Side& major)
351
        : m_minor(minor)
352
        , m_major(major)
353
        , m_minor_log(*minor.m_changeset)
354
        , m_major_log(*major.m_changeset)
355
        , minor_before(minor.get())
356
        , major_before(major.get())
UNCOV
357
    {
×
UNCOV
358
    }
×
359

360
    struct FieldTracer : sync::Changeset::Reflector::Tracer {
361
        std::string m_name;
362
        std::map<std::string, std::string, std::less<>> m_fields;
363

364
        const Changeset* m_changeset = nullptr;
365

366
        void set_changeset(const Changeset* changeset) override
367
        {
×
368
            m_changeset = changeset;
×
369
        }
×
370

371
        StringData get_string(InternString str)
372
        {
×
373
            return m_changeset->get_string(str);
×
374
        }
×
375

376
        void name(StringData n) override
377
        {
×
378
            m_name = n;
×
379
        }
×
380

381
        void path(StringData n, InternString table, const Instruction::PrimaryKey& pk,
382
                  util::Optional<InternString> field, const Instruction::Path* path) override
383
        {
×
384
            std::stringstream ss;
×
385
            m_changeset->print_path(ss, table, pk, field, path);
×
386
            m_fields.emplace(n, ss.str());
×
387
        }
×
388

389
        void field(StringData n, InternString str) override
390
        {
×
391
            m_fields.emplace(n, get_string(str));
×
392
        }
×
393

394
        void field(StringData n, Instruction::Payload::Type type) override
UNCOV
395
        {
×
UNCOV
396
            m_fields.emplace(n, get_type_name(type));
×
397
        }
×
398

399
        void field(StringData n, Instruction::AddColumn::CollectionType type) override
400
        {
×
401
            m_fields.emplace(n, get_collection_type(type));
×
UNCOV
402
        }
×
403

404
        void field(StringData n, const Instruction::PrimaryKey& key) override
405
        {
×
406
            auto real_key = m_changeset->get_key(key);
×
407
            std::stringstream ss;
×
408
            ss << format_pk(real_key);
×
UNCOV
409
            m_fields.emplace(n, ss.str());
×
UNCOV
410
        }
×
411

412
        void field(StringData n, const Instruction::Payload& value) override
UNCOV
413
        {
×
UNCOV
414
            std::stringstream ss;
×
UNCOV
415
            m_changeset->print_value(ss, value);
×
UNCOV
416
            m_fields.emplace(n, ss.str());
×
UNCOV
417
        }
×
418

419
        void field(StringData n, const Instruction::Path& value) override
420
        {
×
421
            std::stringstream ss;
×
UNCOV
422
            m_changeset->print_path(ss, value);
×
UNCOV
423
            m_fields.emplace(n, ss.str());
×
424
        }
×
425

426
        void field(StringData n, uint32_t value) override
UNCOV
427
        {
×
UNCOV
428
            std::stringstream ss;
×
429
            ss << value;
×
430
            m_fields.emplace(n, ss.str());
×
431
        }
×
432
    };
433

434
    struct PrintDiffTracer : sync::Changeset::Reflector::Tracer {
435
        std::ostream& m_os;
436
        const FieldTracer& m_before;
437
        bool m_first = true;
438
        const Changeset* m_changeset = nullptr;
439

440
        PrintDiffTracer(std::ostream& os, const FieldTracer& before)
441
            : m_os(os)
442
            , m_before(before)
443
        {
×
444
        }
×
445

446
        void set_changeset(const Changeset* changeset) override
447
        {
×
448
            m_changeset = changeset;
×
449
        }
×
450

451
        StringData get_string(InternString str) const noexcept
452
        {
×
453
            return m_changeset->get_string(str);
×
454
        }
×
455

456
        void name(StringData n) override
457
        {
×
458
            m_os << std::left << std::setw(16) << std::string(n);
×
459
        }
×
460

461
        void path(StringData n, InternString table, const Instruction::PrimaryKey& pk,
462
                  util::Optional<InternString> field, const Instruction::Path* path) override
463
        {
×
464
            std::stringstream ss;
×
465
            m_changeset->print_path(ss, table, pk, field, path);
×
466
            diff_field(n, ss.str());
×
UNCOV
467
        }
×
468

469
        void field(StringData n, InternString str) override
470
        {
×
471
            diff_field(n, get_string(str));
×
472
        }
×
473

474
        void field(StringData n, Instruction::Payload::Type type) override
UNCOV
475
        {
×
476
            diff_field(n, get_type_name(type));
×
477
        }
×
478

479
        void field(StringData n, Instruction::AddColumn::CollectionType type) override
480
        {
×
UNCOV
481
            diff_field(n, get_collection_type(type));
×
UNCOV
482
        }
×
483

484
        void field(StringData n, const Instruction::PrimaryKey& value) override
485
        {
×
486
            std::stringstream ss;
×
487
            ss << format_pk(m_changeset->get_key(value));
×
UNCOV
488
            diff_field(n, ss.str());
×
UNCOV
489
        }
×
490

491
        void field(StringData n, const Instruction::Payload& value) override
492
        {
×
493
            std::stringstream ss;
×
494
            m_changeset->print_value(ss, value);
×
495
            diff_field(n, ss.str());
×
496
        }
×
497

498
        void field(StringData n, const Instruction::Path& value) override
499
        {
×
500
            std::stringstream ss;
×
501
            m_changeset->print_path(ss, value);
×
502
            diff_field(n, ss.str());
×
503
        }
×
504

505
        void field(StringData n, uint32_t value) override
UNCOV
506
        {
×
UNCOV
507
            std::stringstream ss;
×
UNCOV
508
            ss << value;
×
509
            diff_field(n, ss.str());
×
510
        }
×
511

512
        void diff_field(StringData name, std::string value)
513
        {
×
UNCOV
514
            std::stringstream ss;
×
UNCOV
515
            ss << name << "=";
×
UNCOV
516
            auto it = m_before.m_fields.find(name);
×
517
            if (it == m_before.m_fields.end() || it->second == value) {
×
518
                ss << value;
×
519
            }
×
520
            else {
×
521
                ss << it->second << "->" << value;
×
522
            }
×
523
            if (!m_first) {
×
524
                m_os << ", ";
×
525
            }
×
526
            m_os << ss.str();
×
527
            m_first = false;
×
528
        }
×
529
    };
530

531
    static void print_instr(std::ostream& os, const Instruction& instr, const Changeset& changeset)
532
    {
×
533
        Changeset::Printer printer{os};
×
534
        Changeset::Reflector reflector{printer, changeset};
×
535
        instr.visit(reflector);
×
536
    }
×
537

538
    bool print_diff(std::ostream& os, bool print_unmodified, const Instruction& before, const Changeset& before_log,
539
                    Side& side) const
540
    {
×
541
        if (side.was_discarded) {
×
542
            print_instr(os, before, before_log);
×
UNCOV
543
            os << " (DISCARDED)";
×
UNCOV
544
        }
×
545
        else if (side.was_replaced) {
×
546
            print_instr(os, before, before_log);
×
547
            os << " (REPLACED)";
×
548
        }
×
549
        else {
×
550
            Instruction after = side.get();
×
551
            if (print_unmodified || (before != after)) {
×
552
                FieldTracer before_tracer;
×
553
                before_tracer.set_changeset(&before_log);
×
554
                PrintDiffTracer after_tracer{os, before_tracer};
×
555
                Changeset::Reflector before_reflector{before_tracer, *side.m_changeset};
×
556
                Changeset::Reflector after_reflector{after_tracer, *side.m_changeset};
×
557
                before.visit(before_reflector);
×
558
                after.visit(after_reflector); // This prints the diff'ed instruction
×
559
            }
×
UNCOV
560
            else {
×
561
                os << "(=)";
×
562
            }
×
UNCOV
563
        }
×
564
        return true;
×
565
    }
×
566

567
    void print_diff(std::ostream& os, bool print_unmodified) const
UNCOV
568
    {
×
UNCOV
569
        bool must_print_minor = m_minor.was_discarded || m_minor.was_replaced;
×
570
        if (!must_print_minor) {
×
571
            Instruction minor_after = m_minor.get();
×
572
            must_print_minor = (minor_before != minor_after);
×
573
        }
×
574
        bool must_print_major = m_major.was_discarded || m_major.was_replaced;
×
575
        if (!must_print_major) {
×
576
            Instruction major_after = m_major.get();
×
577
            must_print_major = (major_before != major_after);
×
578
        }
×
UNCOV
579
        bool must_print = (print_unmodified || must_print_minor || must_print_major);
×
UNCOV
580
        if (must_print) {
×
UNCOV
581
            std::stringstream ss_minor;
×
UNCOV
582
            std::stringstream ss_major;
×
583

UNCOV
584
            print_diff(ss_minor, true, minor_before, m_minor_log, m_minor);
×
UNCOV
585
            print_diff(ss_major, print_unmodified, major_before, m_major_log, m_major);
×
586

UNCOV
587
            os << std::left << std::setw(80) << ss_minor.str();
×
UNCOV
588
            os << ss_major.str() << "\n";
×
UNCOV
589
        }
×
UNCOV
590
    }
×
591

592
    void pad_or_ellipsis(std::ostream& os, const std::string& str, int width) const
UNCOV
593
    {
×
UNCOV
594
        // FIXME: Does not work with UTF-8.
×
UNCOV
595
        if (str.size() > size_t(width)) {
×
UNCOV
596
            os << str.substr(0, width - 1) << "~";
×
UNCOV
597
        }
×
UNCOV
598
        else {
×
UNCOV
599
            os << std::left << std::setw(width) << str;
×
UNCOV
600
        }
×
UNCOV
601
    }
×
602
};
603
#endif // LCOV_EXCL_STOP REALM_DEBUG
604

605

606
struct TransformerImpl::Transformer {
607
    MajorSide m_major_side;
608
    MinorSide m_minor_side;
609
    MinorSide::Position m_minor_end;
610
    bool m_trace;
611

612
    Transformer(bool trace)
613
        : m_major_side{*this}
614
        , m_minor_side{*this}
615
        , m_trace{trace}
616
    {
417,224✔
617
    }
417,224✔
618

619
    void transform()
620
    {
9,798,064✔
621
        m_major_side.m_position = m_major_side.m_changeset->begin();
9,798,064✔
622
        m_major_side.skip_tombstones();
9,798,064✔
623

4,917,230✔
624
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
18,204,962✔
625
            m_major_side.init_with_instruction(m_major_side.m_position);
8,406,898✔
626

4,234,604✔
627
            set_conflict_ranges();
8,406,898✔
628
            m_minor_end = m_minor_side.end();
8,406,898✔
629
            m_minor_side.m_position = m_minor_side.begin();
8,406,898✔
630
            transform_major();
8,406,898✔
631

4,234,604✔
632
            if (!m_major_side.was_discarded)
8,406,898✔
633
                // Discarding the instruction moves to the next one.
4,200,596✔
634
                m_major_side.next_instruction();
8,337,932✔
635
            m_major_side.skip_tombstones();
8,406,898✔
636
        }
8,406,898✔
637
    }
9,798,064✔
638

639
    _impl::ChangesetIndex::Ranges* get_conflict_ranges_for_instruction(const Instruction& instr)
640
    {
8,406,788✔
641
        _impl::ChangesetIndex& index = *m_minor_side.m_changeset_index;
8,406,788✔
642

4,234,594✔
643
        if (_impl::is_schema_change(instr)) {
8,406,788✔
644
            ///
458,942✔
645
            /// CONFLICT GROUP: Everything touching that class
458,942✔
646
            ///
458,942✔
647
            auto ranges = index.get_everything();
922,800✔
648
            if (!ranges->empty()) {
922,800✔
649
#if REALM_DEBUG // LCOV_EXCL_START
338,934✔
650
                if (m_trace) {
679,812✔
651
                    std::cerr << TERM_RED << "Conflict group: Everything (due to schema change)\n" << TERM_RESET;
×
652
                }
×
653
#endif // REALM_DEBUG LCOV_EXCL_STOP
679,812✔
654
            }
679,812✔
655
            return ranges;
922,800✔
656
        }
922,800✔
657
        else {
7,483,988✔
658
            ///
3,775,652✔
659
            /// CONFLICT GROUP: Everything touching the involved objects,
3,775,652✔
660
            /// including schema changes.
3,775,652✔
661
            ///
3,775,652✔
662
            _impl::ChangesetIndex::GlobalID major_ids[2];
7,483,988✔
663
            size_t num_major_ids = m_major_side.get_object_ids_in_current_instruction(major_ids, 2);
7,483,988✔
664
            REALM_ASSERT(num_major_ids <= 2);
7,483,988✔
665
            REALM_ASSERT(num_major_ids >= 1);
7,483,988✔
666
#if REALM_DEBUG // LCOV_EXCL_START
3,775,652✔
667
            if (m_trace) {
7,483,988✔
UNCOV
668
                std::cerr << TERM_RED << "Conflict group: ";
×
UNCOV
669
                if (num_major_ids == 0) {
×
UNCOV
670
                    std::cerr << "(nothing - no object references)";
×
UNCOV
671
                }
×
UNCOV
672
                for (size_t i = 0; i < num_major_ids; ++i) {
×
UNCOV
673
                    std::cerr << major_ids[i].table_name << "[" << format_pk(major_ids[i].object_id) << "]";
×
UNCOV
674
                    if (i + 1 != num_major_ids)
×
UNCOV
675
                        std::cerr << ", ";
×
UNCOV
676
                }
×
UNCOV
677
                std::cerr << "\n" << TERM_RESET;
×
UNCOV
678
            }
×
679
#endif // REALM_DEBUG LCOV_EXCL_STOP
7,483,988✔
680
            auto ranges = index.get_modifications_for_object(major_ids[0]);
7,483,988✔
681
            if (num_major_ids == 2) {
7,483,988✔
682
                // Check that the index has correctly joined the ranges for the
74✔
683
                // two object IDs.
74✔
684
                REALM_ASSERT(ranges == index.get_modifications_for_object(major_ids[1]));
148✔
685
            }
148✔
686
            return ranges;
7,483,988✔
687
        }
7,483,988✔
688
    }
8,406,788✔
689

690
    void set_conflict_ranges()
691
    {
8,406,836✔
692
        const auto& major_instr = m_major_side.get();
8,406,836✔
693
        m_minor_side.m_conflict_ranges = get_conflict_ranges_for_instruction(major_instr);
8,406,836✔
694
        /* m_minor_side.m_changeset_index->verify(); */
4,234,590✔
695
    }
8,406,836✔
696

697
    void set_next_major_changeset(Changeset* changeset) noexcept
698
    {
9,798,108✔
699
        m_major_side.m_changeset = changeset;
9,798,108✔
700
        m_major_side.m_position = changeset->begin();
9,798,108✔
701
        m_major_side.skip_tombstones();
9,798,108✔
702
    }
9,798,108✔
703

704
    void discard_major()
705
    {
68,986✔
706
        m_major_side.m_position = m_major_side.m_changeset->erase_stable(m_major_side.m_position);
68,986✔
707
        m_major_side.was_discarded = true; // This terminates the loop in transform_major();
68,986✔
708
        m_major_side.m_changeset->set_dirty(true);
68,986✔
709
    }
68,986✔
710

711
    void discard_minor()
712
    {
68,452✔
713
        m_minor_side.was_discarded = true;
68,452✔
714
        m_minor_side.m_position = m_minor_side.m_changeset_index->erase_instruction(m_minor_side.m_position);
68,452✔
715
        m_minor_side.m_changeset->set_dirty(true);
68,452✔
716
        m_minor_side.update_changeset_pointer();
68,452✔
717
    }
68,452✔
718

719
    template <class InputIterator>
720
    void prepend_major(InputIterator instr_begin, InputIterator instr_end)
721
    {
×
UNCOV
722
        REALM_ASSERT(*m_major_side.m_position); // cannot prepend a tombstone
×
723
        auto insert_position = m_major_side.m_position;
×
724
        m_major_side.m_position = m_major_side.m_changeset->insert_stable(insert_position, instr_begin, instr_end);
×
725
        m_major_side.m_changeset->set_dirty(true);
×
726
        size_t num_prepended = instr_end - instr_begin;
×
727
        transform_prepended_major(num_prepended);
×
UNCOV
728
    }
×
729

730
    void prepend_major(Instruction instr)
731
    {
×
732
        const Instruction* begin = &instr;
×
733
        const Instruction* end = begin + 1;
×
734
        prepend_major(begin, end);
×
UNCOV
735
    }
×
736

737
    template <class InputIterator>
738
    void prepend_minor(InputIterator instr_begin, InputIterator instr_end)
739
    {
×
UNCOV
740
        REALM_ASSERT(*m_minor_side.m_position); // cannot prepend a tombstone
×
UNCOV
741
        auto insert_position = m_minor_side.m_position.m_pos;
×
UNCOV
742
        m_minor_side.m_position.m_pos =
×
UNCOV
743
            m_minor_side.m_changeset->insert_stable(insert_position, instr_begin, instr_end);
×
744
        m_minor_side.m_changeset->set_dirty(true);
×
745
        size_t num_prepended = instr_end - instr_begin;
×
746
        // Go back to the instruction that initiated this prepend
747
        for (size_t i = 0; i < num_prepended; ++i) {
×
748
            ++m_minor_side.m_position;
×
749
        }
×
750
        REALM_ASSERT(m_minor_end == m_minor_side.end());
×
751
    }
×
752

753
    void prepend_minor(Instruction instr)
UNCOV
754
    {
×
755
        const Instruction* begin = &instr;
×
756
        const Instruction* end = begin + 1;
×
757
        prepend_minor(begin, end);
×
758
    }
×
759

760
    void transform_prepended_major(size_t num_prepended)
UNCOV
761
    {
×
762
        auto orig_major_was_discarded = m_major_side.was_discarded;
×
UNCOV
763
        auto orig_major_path_len = m_major_side.m_path_len;
×
764

765
        // Reset 'was_discarded', as it should refer to the prepended
766
        // instructions in the below, not the instruction that instigated the
767
        // prepend.
768
        m_major_side.was_discarded = false;
×
769
        REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
770

771
#if defined(REALM_DEBUG) // LCOV_EXCL_START
×
772
        if (m_trace) {
×
UNCOV
773
            std::cerr << std::setw(80) << " ";
×
774
            MergeTracer::print_instr(std::cerr, m_major_side.get(), *m_major_side.m_changeset);
×
775
            std::cerr << " (PREPENDED " << num_prepended << ")\n";
×
776
        }
×
UNCOV
777
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
778

779
        for (size_t i = 0; i < num_prepended; ++i) {
×
780
            auto orig_minor_index = m_minor_side.m_position;
×
781
            auto orig_minor_was_discarded = m_minor_side.was_discarded;
×
782
            auto orig_minor_was_replaced = m_minor_side.was_replaced;
×
783
            auto orig_minor_path_len = m_minor_side.m_path_len;
×
784

785
            // Skip the instruction that initiated this prepend.
786
            if (!m_minor_side.was_discarded) {
×
787
                // Discarding an instruction moves to the next.
788
                m_minor_side.next_instruction();
×
789
            }
×
790

791
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
792
            m_major_side.init_with_instruction(m_major_side.m_position);
×
793
            REALM_ASSERT(!m_major_side.was_discarded);
×
UNCOV
794
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
UNCOV
795
            transform_major();
×
UNCOV
796
            if (!m_major_side.was_discarded) {
×
797
                // Discarding an instruction moves to the next.
UNCOV
798
                m_major_side.next_instruction();
×
UNCOV
799
            }
×
UNCOV
800
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
801

UNCOV
802
            m_minor_side.m_position = orig_minor_index;
×
UNCOV
803
            m_minor_side.was_discarded = orig_minor_was_discarded;
×
UNCOV
804
            m_minor_side.was_replaced = orig_minor_was_replaced;
×
UNCOV
805
            m_minor_side.m_path_len = orig_minor_path_len;
×
UNCOV
806
            m_minor_side.update_changeset_pointer();
×
UNCOV
807
        }
×
808

809
#if defined(REALM_DEBUG) // LCOV_EXCL_START
×
810
        if (m_trace) {
×
811
            std::cerr << TERM_CYAN << "(end transform of prepended major)\n" << TERM_RESET;
×
812
        }
×
813
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
814

815
        m_major_side.was_discarded = orig_major_was_discarded;
×
UNCOV
816
        m_major_side.m_path_len = orig_major_path_len;
×
UNCOV
817
    }
×
818

819
    void transform_major()
820
    {
8,406,964✔
821
        m_minor_side.skip_tombstones();
8,406,964✔
822

4,234,678✔
823
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
8,406,964✔
824
        const bool print_noop_merges = false;
8,406,964✔
825
        bool new_major = true; // print an instruction every time we go to the next major regardless
8,406,964✔
826
#endif                         // LCOV_EXCL_STOP REALM_DEBUG
8,406,964✔
827

4,234,678✔
828
        while (m_minor_side.m_position != m_minor_end) {
10,973,492✔
829
            m_minor_side.init_with_instruction(m_minor_side.m_position);
2,635,516✔
830

1,284,774✔
831
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
2,635,516✔
832
            if (m_trace) {
2,635,516✔
UNCOV
833
                MergeTracer tracer{m_minor_side, m_major_side};
×
UNCOV
834
                merge_instructions(m_major_side, m_minor_side);
×
UNCOV
835
                if (new_major)
×
UNCOV
836
                    std::cerr << TERM_CYAN << "\n(new major round)\n" << TERM_RESET;
×
UNCOV
837
                tracer.print_diff(std::cerr, new_major || print_noop_merges);
×
UNCOV
838
                new_major = false;
×
UNCOV
839
            }
×
840
            else {
2,635,516✔
841
#endif // LCOV_EXCL_STOP REALM_DEBUG
2,635,516✔
842
                merge_instructions(m_major_side, m_minor_side);
2,635,516✔
843
#if defined(REALM_DEBUG) // LCOV_EXCL_START
2,635,516✔
844
            }
2,635,516✔
845
#endif // LCOV_EXCL_STOP REALM_DEBUG
2,635,516✔
846

1,284,774✔
847
            if (m_major_side.was_discarded)
2,635,516✔
848
                break;
68,988✔
849
            if (!m_minor_side.was_discarded)
2,566,528✔
850
                // Discarding an instruction moves to the next one.
1,225,838✔
851
                m_minor_side.next_instruction();
2,516,244✔
852
            m_minor_side.skip_tombstones();
2,566,528✔
853
        }
2,566,528✔
854
    }
8,406,964✔
855

856
    void merge_instructions(MajorSide& left, MinorSide& right);
857
    template <class OuterSide, class InnerSide>
858
    void merge_nested(OuterSide& outer, InnerSide& inner);
859
};
860

861
void TransformerImpl::MajorSide::set_next_changeset(Changeset* changeset) noexcept
862
{
9,798,120✔
863
    m_transformer.set_next_major_changeset(changeset);
9,798,120✔
864
}
9,798,120✔
865
void TransformerImpl::MajorSide::discard()
866
{
68,986✔
867
    m_transformer.discard_major();
68,986✔
868
}
68,986✔
869
void TransformerImpl::MajorSide::prepend(Instruction operation)
UNCOV
870
{
×
UNCOV
871
    m_transformer.prepend_major(std::move(operation));
×
UNCOV
872
}
×
873
template <class InputIterator>
874
void TransformerImpl::MajorSide::prepend(InputIterator begin, InputIterator end)
875
{
876
    m_transformer.prepend_major(std::move(begin), std::move(end));
877
}
878
void TransformerImpl::MinorSide::discard()
879
{
68,452✔
880
    m_transformer.discard_minor();
68,452✔
881
}
68,452✔
882
void TransformerImpl::MinorSide::prepend(Instruction operation)
UNCOV
883
{
×
UNCOV
884
    m_transformer.prepend_minor(std::move(operation));
×
UNCOV
885
}
×
886
template <class InputIterator>
887
void TransformerImpl::MinorSide::prepend(InputIterator begin, InputIterator end)
888
{
889
    m_transformer.prepend_minor(std::move(begin), std::move(end));
890
}
891
} // namespace _impl
892

893
namespace {
894

895
REALM_NORETURN void throw_bad_merge(std::string msg)
896
{
44✔
897
    throw sync::TransformError{std::move(msg)};
44✔
898
}
44✔
899

900
template <class... Params>
901
REALM_NORETURN void bad_merge(const char* msg, Params&&... params)
902
{
44✔
903
    throw_bad_merge(util::format(msg, std::forward<Params>(params)...));
44✔
904
}
44✔
905

906
REALM_NORETURN void bad_merge(_impl::TransformerImpl::Side& side, Instruction::PathInstruction instr,
907
                              const std::string& msg)
UNCOV
908
{
×
UNCOV
909
    std::stringstream ss;
×
UNCOV
910
    side.m_changeset->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
UNCOV
911
    bad_merge("%1 (instruction target: %2). Please contact support.", msg, ss.str());
×
UNCOV
912
}
×
913

914
template <class LeftInstruction, class RightInstruction, class Enable = void>
915
struct Merge;
916
template <class Outer>
917
struct MergeNested;
918

919
struct MergeUtils {
920
    using TransformerImpl = _impl::TransformerImpl;
921
    MergeUtils(TransformerImpl::Side& left_side, TransformerImpl::Side& right_side)
922
        : m_left_side(left_side)
923
        , m_right_side(right_side)
924
    {
1,245,852✔
925
    }
1,245,852✔
926

927
    // CAUTION: All of these utility functions rely implicitly on the "left" and
928
    // "right" arguments corresponding to the left and right sides. If the
929
    // arguments are switched, mayhem ensues.
930

931
    bool same_string(InternString left, InternString right) const noexcept
932
    {
1,313,456✔
933
        // FIXME: Optimize string comparison by building a map of InternString values up front.
633,184✔
934
        return m_left_side.m_changeset->get_string(left) == m_right_side.m_changeset->get_string(right);
1,313,456✔
935
    }
1,313,456✔
936

937
    bool same_key(const Instruction::PrimaryKey& left, const Instruction::PrimaryKey& right) const noexcept
938
    {
315,210✔
939
        // FIXME: Once we can compare string by InternString map lookups,
148,366✔
940
        // compare the string components of the keys using that.
148,366✔
941
        PrimaryKey left_key = m_left_side.m_changeset->get_key(left);
315,210✔
942
        PrimaryKey right_key = m_right_side.m_changeset->get_key(right);
315,210✔
943
        return left_key == right_key;
315,210✔
944
    }
315,210✔
945

946
    bool same_payload(const Instruction::Payload& left, const Instruction::Payload& right)
947
    {
216✔
948
        using Type = Instruction::Payload::Type;
216✔
949

108✔
950
        if (left.type != right.type)
216✔
951
            return false;
160✔
952

28✔
953
        switch (left.type) {
56✔
954
            case Type::Null:
✔
955
                return true;
×
956
            case Type::Erased:
✔
957
                return true;
×
958
            case Type::Dictionary:
✔
959
                return true;
×
960
            case Type::ObjectValue:
✔
961
                return true;
×
UNCOV
962
            case Type::GlobalKey:
✔
963
                return left.data.key == right.data.key;
×
964
            case Type::Int:
24✔
965
                return left.data.integer == right.data.integer;
24✔
UNCOV
966
            case Type::Bool:
✔
UNCOV
967
                return left.data.boolean == right.data.boolean;
×
968
            case Type::String:
16✔
969
                return m_left_side.get_string(left.data.str) == m_right_side.get_string(right.data.str);
16✔
UNCOV
970
            case Type::Binary:
✔
UNCOV
971
                return m_left_side.get_string(left.data.binary) == m_right_side.get_string(right.data.binary);
×
UNCOV
972
            case Type::Timestamp:
✔
UNCOV
973
                return left.data.timestamp == right.data.timestamp;
×
974
            case Type::Float:
16✔
975
                return left.data.fnum == right.data.fnum;
16✔
UNCOV
976
            case Type::Double:
✔
UNCOV
977
                return left.data.dnum == right.data.dnum;
×
UNCOV
978
            case Type::Decimal:
✔
979
                return left.data.decimal == right.data.decimal;
×
980
            case Type::Link: {
✔
UNCOV
981
                if (!same_key(left.data.link.target, right.data.link.target)) {
×
UNCOV
982
                    return false;
×
UNCOV
983
                }
×
UNCOV
984
                auto left_target = m_left_side.get_string(left.data.link.target_table);
×
UNCOV
985
                auto right_target = m_right_side.get_string(right.data.link.target_table);
×
UNCOV
986
                return left_target == right_target;
×
UNCOV
987
            }
×
UNCOV
988
            case Type::ObjectId:
✔
UNCOV
989
                return left.data.object_id == right.data.object_id;
×
UNCOV
990
            case Type::UUID:
✔
UNCOV
991
                return left.data.uuid == right.data.uuid;
×
UNCOV
992
        }
×
993

UNCOV
994
        bad_merge("Invalid payload type in instruction");
×
UNCOV
995
    }
×
996

997
    bool same_path_element(const Instruction::Path::Element& left,
998
                           const Instruction::Path::Element& right) const noexcept
999
    {
1,440✔
1000
        auto pred = util::overload{
1,440✔
1001
            [](uint32_t lhs, uint32_t rhs) {
1,356✔
1002
                return lhs == rhs;
1,272✔
1003
            },
1,272✔
1004
            [this](InternString lhs, InternString rhs) {
546✔
1005
                return same_string(lhs, rhs);
168✔
1006
            },
168✔
1007
            // FIXME: Paths contain incompatible element types. Should we raise an error here?
462✔
1008
            [](InternString, uint32_t) {
462✔
NEW
1009
                return false;
×
NEW
1010
            },
×
1011
            [](uint32_t, InternString) {
462✔
UNCOV
1012
                return false;
×
UNCOV
1013
            },
×
1014
        };
1,440✔
1015
        return mpark::visit(pred, left, right);
1,440✔
1016
    }
1,440✔
1017

1018
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
1019
    {
11,196✔
1020
        if (left.size() == right.size()) {
11,196✔
1021
            for (size_t i = 0; i < left.size(); ++i) {
11,480✔
1022
                if (!same_path_element(left[i], right[i])) {
1,432✔
1023
                    return false;
1,080✔
1024
                }
1,080✔
1025
            }
1,432✔
1026
            return true;
10,378✔
1027
        }
68✔
1028
        return false;
68✔
1029
    }
68✔
1030

1031
    bool same_table(const Instruction::TableInstruction& left,
1032
                    const Instruction::TableInstruction& right) const noexcept
1033
    {
1,236,516✔
1034
        return same_string(left.table, right.table);
1,236,516✔
1035
    }
1,236,516✔
1036

1037
    bool same_object(const Instruction::ObjectInstruction& left,
1038
                     const Instruction::ObjectInstruction& right) const noexcept
1039
    {
1,007,042✔
1040
        return same_table(left, right) && same_key(left.object, right.object);
1,007,042✔
1041
    }
1,007,042✔
1042

1043
    template <class Left, class Right>
1044
    bool same_column(const Left& left, const Right& right) const noexcept
1045
    {
35,766✔
1046
        if constexpr (std::is_convertible_v<const Right&, const Instruction::PathInstruction&>) {
35,766✔
1047
            const Instruction::PathInstruction& rhs = right;
35,766✔
1048
            return same_table(left, right) && same_string(left.field, rhs.field);
18,036!
UNCOV
1049
        }
×
1050
        else if constexpr (std::is_convertible_v<const Right&, const Instruction::ObjectInstruction&>) {
35,766✔
1051
            // CreateObject/EraseObject do not have a column built in.
18,036✔
1052
            return false;
35,766✔
1053
        }
35,766✔
1054
        else {
35,766✔
1055
            return same_table(left, right) && same_string(left.field, right.field);
35,766!
1056
        }
35,766✔
1057
    }
35,766✔
1058

1059
    bool same_field(const Instruction::PathInstruction& left,
1060
                    const Instruction::PathInstruction& right) const noexcept
1061
    {
216,254✔
1062
        return same_object(left, right) && same_string(left.field, right.field);
216,254✔
1063
    }
216,254✔
1064

1065
    bool same_path(const Instruction::PathInstruction& left, const Instruction::PathInstruction& right) const noexcept
1066
    {
29,298✔
1067
        return same_field(left, right) && same_path(left.path, right.path);
29,298✔
1068
    }
29,298✔
1069

1070
    bool same_container(const Instruction::Path& left, const Instruction::Path& right) const noexcept
1071
    {
33,660✔
1072
        // The instructions refer to the same container if the paths have the
13,868✔
1073
        // same length, and elements [0..n-1] are equal (so the last element is
13,868✔
1074
        // disregarded). If the path length is 1, this counts as referring to
13,868✔
1075
        // the same container.
13,868✔
1076
        if (left.size() == right.size()) {
33,660✔
1077
            if (left.size() == 0)
33,636✔
UNCOV
1078
                return true;
×
1079

13,856✔
1080
            for (size_t i = 0; i < left.size() - 1; ++i) {
33,636✔
1081
                if (!same_path_element(left[i], right[i])) {
×
1082
                    return false;
×
1083
                }
×
UNCOV
1084
            }
×
1085
            return true;
33,636✔
1086
        }
24✔
1087
        return false;
24✔
1088
    }
24✔
1089

1090
    bool same_container(const Instruction::PathInstruction& left,
1091
                        const Instruction::PathInstruction& right) const noexcept
1092
    {
147,014✔
1093
        return same_field(left, right) && same_container(left.path, right.path);
147,014✔
1094
    }
147,014✔
1095

1096
    // NOTE: `is_prefix_of()` should only return true if the left is a strictly
1097
    // shorter path than the right, and the entire left path is the initial
1098
    // sequence of the right.
1099

1100
    bool is_prefix_of(const Instruction::AddTable& left, const Instruction::TableInstruction& right) const noexcept
UNCOV
1101
    {
×
UNCOV
1102
        return same_table(left, right);
×
1103
    }
×
1104

1105
    bool is_prefix_of(const Instruction::EraseTable& left, const Instruction::TableInstruction& right) const noexcept
1106
    {
173,276✔
1107
        return same_table(left, right);
173,276✔
1108
    }
173,276✔
1109

1110
    bool is_prefix_of(const Instruction::AddColumn&, const Instruction::TableInstruction&) const noexcept
UNCOV
1111
    {
×
UNCOV
1112
        // Right side is a schema instruction.
×
UNCOV
1113
        return false;
×
UNCOV
1114
    }
×
1115

1116
    bool is_prefix_of(const Instruction::EraseColumn&, const Instruction::TableInstruction&) const noexcept
UNCOV
1117
    {
×
1118
        // Right side is a schema instruction.
×
1119
        return false;
×
UNCOV
1120
    }
×
1121

1122
    bool is_prefix_of(const Instruction::AddColumn& left, const Instruction::ObjectInstruction& right) const noexcept
UNCOV
1123
    {
×
UNCOV
1124
        return same_column(left, right);
×
UNCOV
1125
    }
×
1126

1127
    bool is_prefix_of(const Instruction::EraseColumn& left,
1128
                      const Instruction::ObjectInstruction& right) const noexcept
UNCOV
1129
    {
×
UNCOV
1130
        return same_column(left, right);
×
UNCOV
1131
    }
×
1132

1133
    bool is_prefix_of(const Instruction::ObjectInstruction&, const Instruction::TableInstruction&) const noexcept
UNCOV
1134
    {
×
1135
        // Right side is a schema instruction.
UNCOV
1136
        return false;
×
UNCOV
1137
    }
×
1138

1139
    bool is_prefix_of(const Instruction::ObjectInstruction& left,
1140
                      const Instruction::PathInstruction& right) const noexcept
1141
    {
250,898✔
1142
        return same_object(left, right);
250,898✔
1143
    }
250,898✔
1144

1145
    bool is_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const noexcept
1146
    {
×
1147
        // Path instructions can never be full prefixes of table-level instructions. Note that this also covers
1148
        // ObjectInstructions.
1149
        return false;
×
1150
    }
×
1151

1152
    bool is_prefix_of(const Instruction::PathInstruction& left,
1153
                      const Instruction::PathInstruction& right) const noexcept
1154
    {
39,916✔
1155
        if (left.path.size() < right.path.size() && same_field(left, right)) {
39,916✔
1156
            for (size_t i = 0; i < left.path.size(); ++i) {
488✔
1157
                if (!same_path_element(left.path[i], right.path[i])) {
8✔
1158
                    return false;
8✔
1159
                }
8✔
1160
            }
8✔
1161
            return true;
484✔
1162
        }
39,428✔
1163
        return false;
39,428✔
1164
    }
39,428✔
1165

1166
    // True if the left side is an instruction that touches a container within
1167
    // right's path. Equivalent to is_prefix_of, except the last element (the
1168
    // index) is not considered.
1169
    bool is_container_prefix_of(const Instruction::PathInstruction& left,
1170
                                const Instruction::PathInstruction& right) const
1171
    {
24✔
1172
        if (left.path.size() != 0 && left.path.size() < right.path.size() && same_field(left, right)) {
24✔
1173
            for (size_t i = 0; i < left.path.size() - 1; ++i) {
24✔
1174
                if (!same_path_element(left.path[i], right.path[i])) {
×
1175
                    return false;
×
UNCOV
1176
                }
×
UNCOV
1177
            }
×
1178
            return true;
24✔
1179
        }
×
1180
        return false;
×
UNCOV
1181
    }
×
1182

1183
    bool is_container_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const
1184
    {
×
1185
        return false;
×
UNCOV
1186
    }
×
1187

1188
    bool value_targets_table(const Instruction::Payload& value,
1189
                             const Instruction::TableInstruction& right) const noexcept
UNCOV
1190
    {
×
UNCOV
1191
        if (value.type == Instruction::Payload::Type::Link) {
×
UNCOV
1192
            StringData target_table = m_left_side.get_string(value.data.link.target_table);
×
UNCOV
1193
            StringData right_table = m_right_side.get_string(right.table);
×
UNCOV
1194
            return target_table == right_table;
×
UNCOV
1195
        }
×
UNCOV
1196
        return false;
×
UNCOV
1197
    }
×
1198

1199
    bool value_targets_object(const Instruction::Payload& value,
1200
                              const Instruction::ObjectInstruction& right) const noexcept
1201
    {
×
UNCOV
1202
        if (value_targets_table(value, right)) {
×
UNCOV
1203
            return same_key(value.data.link.target, right.object);
×
UNCOV
1204
        }
×
UNCOV
1205
        return false;
×
UNCOV
1206
    }
×
1207

1208
    bool value_targets_object(const Instruction::Update& left, const Instruction::ObjectInstruction& right) const
UNCOV
1209
    {
×
1210
        return value_targets_object(left.value, right);
×
1211
    }
×
1212

1213
    bool value_targets_object(const Instruction::ArrayInsert& left, const Instruction::ObjectInstruction& right) const
UNCOV
1214
    {
×
1215
        return value_targets_object(left.value, right);
×
1216
    }
×
1217

1218
    // When the left side has a shorter path, get the path component on the
1219
    // right that corresponds to the last component on the left.
1220
    //
1221
    // Note that this will only be used in the context of array indices, because
1222
    // those are the only path components that are modified during OT.
1223
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction& left,
1224
                                          Instruction::PathInstruction& right) const
1225
    {
24✔
1226
        REALM_ASSERT(left.path.size() != 0);
24✔
1227
        REALM_ASSERT(left.path.size() < right.path.size());
24✔
1228
        REALM_ASSERT(mpark::holds_alternative<uint32_t>(left.path.back()));
24✔
1229
        size_t index = left.path.size() - 1;
24✔
1230
        if (!mpark::holds_alternative<uint32_t>(right.path[index])) {
24✔
1231
            bad_merge("Inconsistent paths");
×
1232
        }
×
1233
        return mpark::get<uint32_t>(right.path[index]);
24✔
1234
    }
24✔
1235

1236
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction&,
1237
                                          const Instruction::TableInstruction&) const
UNCOV
1238
    {
×
1239
        // A path instruction can never have a shorter path than something that
1240
        // isn't a PathInstruction.
UNCOV
1241
        REALM_UNREACHABLE();
×
UNCOV
1242
    }
×
1243

1244
    void merge_get_vs_move(uint32_t& get_ndx, const uint32_t& move_from_ndx,
1245
                           const uint32_t& move_to_ndx) const noexcept
UNCOV
1246
    {
×
UNCOV
1247
        if (get_ndx == move_from_ndx) {
×
1248
            // CONFLICT: Update of a moved element.
1249
            //
1250
            // RESOLUTION: On the left side, use the MOVE operation to transform the
1251
            // UPDATE operation received from the right side.
UNCOV
1252
            get_ndx = move_to_ndx; // --->
×
UNCOV
1253
        }
×
UNCOV
1254
        else {
×
1255
            // Right update vs left remove
UNCOV
1256
            if (get_ndx > move_from_ndx) {
×
UNCOV
1257
                get_ndx -= 1; // --->
×
UNCOV
1258
            }
×
1259
            // Right update vs left insert
UNCOV
1260
            if (get_ndx >= move_to_ndx) {
×
UNCOV
1261
                get_ndx += 1; // --->
×
UNCOV
1262
            }
×
UNCOV
1263
        }
×
UNCOV
1264
    }
×
1265

1266
protected:
1267
    TransformerImpl::Side& m_left_side;
1268
    TransformerImpl::Side& m_right_side;
1269
};
1270

1271
template <class LeftInstruction, class RightInstruction>
1272
struct MergeBase : MergeUtils {
1273
    static const Instruction::Type A = Instruction::GetInstructionType<LeftInstruction>::value;
1274
    static const Instruction::Type B = Instruction::GetInstructionType<RightInstruction>::value;
1275
    static_assert(A >= B, "A < B. Please reverse the order of instruction types. :-)");
1276

1277
    MergeBase(TransformerImpl::Side& left_side, TransformerImpl::Side& right_side)
1278
        : MergeUtils(left_side, right_side)
1279
    {
781,682✔
1280
    }
781,682✔
1281
    MergeBase(MergeBase&&) = delete;
1282
};
1283

1284
#define DEFINE_MERGE(A, B)                                                                                           \
1285
    template <>                                                                                                      \
1286
    struct Merge<A, B> {                                                                                             \
1287
        template <class LeftSide, class RightSide>                                                                   \
1288
        struct DoMerge : MergeBase<A, B> {                                                                           \
1289
            A& left;                                                                                                 \
1290
            B& right;                                                                                                \
1291
            LeftSide& left_side;                                                                                     \
1292
            RightSide& right_side;                                                                                   \
1293
            DoMerge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                                   \
1294
                : MergeBase<A, B>(left_side, right_side)                                                             \
1295
                , left(left)                                                                                         \
1296
                , right(right)                                                                                       \
1297
                , left_side(left_side)                                                                               \
1298
                , right_side(right_side)                                                                             \
1299
            {                                                                                                        \
781,680✔
1300
            }                                                                                                        \
781,680✔
1301
            void do_merge();                                                                                         \
1302
        };                                                                                                           \
1303
        template <class LeftSide, class RightSide>                                                                   \
1304
        static inline void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                      \
1305
        {                                                                                                            \
781,682✔
1306
            DoMerge<LeftSide, RightSide> do_merge{left, right, left_side, right_side};                               \
781,682✔
1307
            do_merge.do_merge();                                                                                     \
781,682✔
1308
        }                                                                                                            \
781,682✔
1309
    };                                                                                                               \
1310
    template <class LeftSide, class RightSide>                                                                       \
1311
    void Merge<A, B>::DoMerge<LeftSide, RightSide>::do_merge()
1312

1313
#define DEFINE_MERGE_NOOP(A, B)                                                                                      \
1314
    template <>                                                                                                      \
1315
    struct Merge<A, B> {                                                                                             \
1316
        static const Instruction::Type left_type = Instruction::GetInstructionType<A>::value;                        \
1317
        static const Instruction::Type right_type = Instruction::GetInstructionType<B>::value;                       \
1318
        static_assert(left_type >= right_type,                                                                       \
1319
                      "left_type < right_type. Please reverse the order of instruction types. :-)");                 \
1320
        template <class LeftSide, class RightSide>                                                                   \
1321
        static inline void merge(A&, B&, LeftSide&, RightSide&)                                                      \
1322
        { /* Do nothing */                                                                                           \
1,792,332✔
1323
        }                                                                                                            \
1,792,332✔
1324
    }
1325

1326

1327
#define DEFINE_NESTED_MERGE(A)                                                                                       \
1328
    template <>                                                                                                      \
1329
    struct MergeNested<A> {                                                                                          \
1330
        template <class B, class OuterSide, class InnerSide>                                                         \
1331
        struct DoMerge : MergeUtils {                                                                                \
1332
            A& outer;                                                                                                \
1333
            B& inner;                                                                                                \
1334
            OuterSide& outer_side;                                                                                   \
1335
            InnerSide& inner_side;                                                                                   \
1336
            DoMerge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                                \
1337
                : MergeUtils(outer_side, inner_side)                                                                 \
1338
                , outer(outer)                                                                                       \
1339
                , inner(inner)                                                                                       \
1340
                , outer_side(outer_side)                                                                             \
1341
                , inner_side(inner_side)                                                                             \
1342
            {                                                                                                        \
464,170✔
1343
            }                                                                                                        \
464,170✔
1344
            void do_merge();                                                                                         \
1345
        };                                                                                                           \
1346
        template <class B, class OuterSide, class InnerSide>                                                         \
1347
        static inline void merge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                   \
1348
        {                                                                                                            \
464,170✔
1349
            DoMerge<B, OuterSide, InnerSide> do_merge{outer, inner, outer_side, inner_side};                         \
464,170✔
1350
            do_merge.do_merge();                                                                                     \
464,170✔
1351
        }                                                                                                            \
464,170✔
1352
    };                                                                                                               \
1353
    template <class B, class OuterSide, class InnerSide>                                                             \
1354
    void MergeNested<A>::DoMerge<B, OuterSide, InnerSide>::do_merge()
1355

1356
#define DEFINE_NESTED_MERGE_NOOP(A)                                                                                  \
1357
    template <>                                                                                                      \
1358
    struct MergeNested<A> {                                                                                          \
1359
        template <class B, class OuterSide, class InnerSide>                                                         \
1360
        static inline void merge(const A&, const B&, const OuterSide&, const InnerSide&)                             \
1361
        { /* Do nothing */                                                                                           \
669,422✔
1362
        }                                                                                                            \
669,422✔
1363
    }
1364

1365
// Implementation that reverses order.
1366
template <class A, class B>
1367
struct Merge<A, B,
1368
             typename std::enable_if<(Instruction::GetInstructionType<A>::value <
1369
                                      Instruction::GetInstructionType<B>::value)>::type> {
1370
    template <class LeftSide, class RightSide>
1371
    static void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)
1372
    {
920,212✔
1373
        Merge<B, A>::merge(right, left, right_side, left_side);
920,212✔
1374
    }
920,212✔
1375
};
1376

1377
///
1378
///  GET READY!
1379
///
1380
///  Realm supports 14 instructions at the time of this writing. Each
1381
///  instruction type needs one rule for each other instruction type. We only
1382
///  define one rule to handle each combination (A vs B and B vs A are handle by
1383
///  a single rule).
1384
///
1385
///  This gives (14 * (14 + 1)) / 2 = 105 merge rules below.
1386
///
1387
///  Merge rules are ordered such that the second instruction type is always of
1388
///  a lower enum value than the first.
1389
///
1390
///  Nested merge rules apply when one instruction has a strictly longer path
1391
///  than another. All instructions that have a path of the same length will
1392
///  meet each other through regular merge rules, regardless of whether they
1393
///  share a prefix.
1394
///
1395

1396

1397
/// AddTable rules
1398

1399
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1400

1401
DEFINE_MERGE(Instruction::AddTable, Instruction::AddTable)
1402
{
14,288✔
1403
    if (same_table(left, right)) {
14,288✔
1404
        StringData left_name = left_side.get_string(left.table);
7,440✔
1405
        if (auto left_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&left.type)) {
7,440✔
1406
            if (auto right_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&right.type)) {
7,312✔
1407
                StringData left_pk_name = left_side.get_string(left_spec->pk_field);
7,312✔
1408
                StringData right_pk_name = right_side.get_string(right_spec->pk_field);
7,312✔
1409
                if (left_pk_name != right_pk_name) {
7,312✔
1410
                    bad_merge(
6✔
1411
                        "Schema mismatch: '%1' has primary key '%2' on one side, but primary key '%3' on the other.",
6✔
1412
                        left_name, left_pk_name, right_pk_name);
6✔
1413
                }
6✔
1414

3,540✔
1415
                if (left_spec->pk_type != right_spec->pk_type) {
7,312✔
1416
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is of type %3 on one side and type "
6✔
1417
                              "%4 on the other.",
6✔
1418
                              left_name, left_pk_name, get_type_name(left_spec->pk_type),
6✔
1419
                              get_type_name(right_spec->pk_type));
6✔
1420
                }
6✔
1421

3,540✔
1422
                if (left_spec->pk_nullable != right_spec->pk_nullable) {
7,312✔
1423
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is nullable on one side, but not "
6✔
1424
                              "the other.",
6✔
1425
                              left_name, left_pk_name);
6✔
1426
                }
6✔
1427

3,540✔
1428
                if (left_spec->is_asymmetric != right_spec->is_asymmetric) {
7,312✔
1429
                    bad_merge("Schema mismatch: '%1' is asymmetric on one side, but not on the other.", left_name);
4✔
1430
                }
4✔
1431
            }
7,312✔
UNCOV
1432
            else {
×
UNCOV
1433
                bad_merge("Schema mismatch: '%1' has a primary key on one side, but not on the other.", left_name);
×
UNCOV
1434
            }
×
1435
        }
7,312✔
1436
        else if (mpark::get_if<Instruction::AddTable::EmbeddedTable>(&left.type)) {
128✔
1437
            if (!mpark::get_if<Instruction::AddTable::EmbeddedTable>(&right.type)) {
128✔
UNCOV
1438
                bad_merge("Schema mismatch: '%1' is an embedded table on one side, but not the other.", left_name);
×
UNCOV
1439
            }
×
1440
        }
128✔
1441

3,604✔
1442
        // Names are the same, PK presence is the same, and if there is a primary
3,604✔
1443
        // key, its name, type, and nullability are the same. Discard both sides.
3,604✔
1444
        left_side.discard();
7,440✔
1445
        right_side.discard();
7,440✔
1446
        return;
7,440✔
1447
    }
7,440✔
1448
}
14,288✔
1449

1450
DEFINE_MERGE(Instruction::EraseTable, Instruction::AddTable)
1451
{
4,512✔
1452
    if (same_table(left, right)) {
4,512✔
UNCOV
1453
        right_side.discard();
×
UNCOV
1454
    }
×
1455
}
4,512✔
1456

1457
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::AddTable);
1458
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::AddTable);
1459
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::AddTable);
1460
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddTable);
1461
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddTable);
1462
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddTable);
1463
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddTable);
1464
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddTable);
1465
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddTable);
1466
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddTable);
1467
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddTable);
1468
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddTable);
1469

1470
/// EraseTable rules
1471

1472
DEFINE_NESTED_MERGE(Instruction::EraseTable)
1473
{
173,276✔
1474
    if (is_prefix_of(outer, inner)) {
173,276!
1475
        inner_side.discard();
39,488✔
1476
    }
39,488✔
1477
}
173,276✔
1478

1479
DEFINE_MERGE(Instruction::EraseTable, Instruction::EraseTable)
1480
{
1,636✔
1481
    if (same_table(left, right)) {
1,636✔
1482
        left_side.discard();
388✔
1483
        right_side.discard();
388✔
1484
    }
388✔
1485
}
1,636✔
1486

1487
// Handled by nesting rule.
1488
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::EraseTable);
1489
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::EraseTable);
1490
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseTable);
1491
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseTable);
1492

1493
DEFINE_MERGE(Instruction::AddColumn, Instruction::EraseTable)
1494
{
9,276✔
1495
    // AddColumn on an erased table handled by nesting.
4,536✔
1496

4,536✔
1497
    if (left.type == Instruction::Payload::Type::Link && same_string(left.link_target_table, right.table)) {
9,276!
1498
        // Erase of a table where the left side adds a link column targeting it.
UNCOV
1499
        Instruction::EraseColumn erase_column;
×
UNCOV
1500
        erase_column.table = right_side.adopt_string(left_side, left.table);
×
UNCOV
1501
        erase_column.field = right_side.adopt_string(left_side, left.field);
×
UNCOV
1502
        right_side.prepend(erase_column);
×
UNCOV
1503
        left_side.discard();
×
UNCOV
1504
    }
×
1505
}
9,276✔
1506

1507
// Handled by nested rule.
1508
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseTable);
1509
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseTable);
1510
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseTable);
1511
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseTable);
1512
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseTable);
1513
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseTable);
1514
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseTable);
1515

1516

1517
/// CreateObject rules
1518

1519
// CreateObject cannot interfere with instructions that have a longer path.
1520
DEFINE_NESTED_MERGE_NOOP(Instruction::CreateObject);
1521

1522
// CreateObject is idempotent.
1523
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::CreateObject);
1524

1525
DEFINE_MERGE(Instruction::EraseObject, Instruction::CreateObject)
1526
{
401,322✔
1527
    if (same_object(left, right)) {
401,322✔
1528
        // CONFLICT: Create and Erase of the same object.
8,146✔
1529
        //
8,146✔
1530
        // RESOLUTION: Erase always wins.
8,146✔
1531
        right_side.discard();
15,424✔
1532
    }
15,424✔
1533
}
401,322✔
1534

1535
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::CreateObject);
1536
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::CreateObject);
1537
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::CreateObject);
1538
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::CreateObject);
1539
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::CreateObject);
1540
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::CreateObject);
1541
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::CreateObject);
1542
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::CreateObject);
1543
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::CreateObject);
1544
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::CreateObject);
1545

1546

1547
/// Erase rules
1548

1549
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1550
{
250,898✔
1551
    if (is_prefix_of(outer, inner)) {
250,898!
1552
        // Erase always wins.
10,394✔
1553
        inner_side.discard();
21,576✔
1554
    }
21,576✔
1555
}
250,898✔
1556

1557
DEFINE_MERGE(Instruction::EraseObject, Instruction::EraseObject)
1558
{
138,566✔
1559
    if (same_object(left, right)) {
138,566✔
1560
        // We keep the most recent erase. This prevents the situation where a
7,344✔
1561
        // high number of EraseObject instructions in the past trumps a
7,344✔
1562
        // Erase-Create pair in the future.
7,344✔
1563
        if (right_side.timestamp() < left_side.timestamp()) {
14,096✔
1564
            right_side.discard();
7,048✔
1565
        }
7,048✔
1566
        else {
7,048✔
1567
            left_side.discard();
7,048✔
1568
        }
7,048✔
1569
    }
14,096✔
1570
}
138,566✔
1571

1572
// Handled by nested merge.
1573
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseObject);
1574
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseObject);
1575
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::EraseObject);
1576
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseObject);
1577
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseObject);
1578
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseObject);
1579
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseObject);
1580
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseObject);
1581
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseObject);
1582
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseObject);
1583

1584

1585
/// Set rules
1586

1587
DEFINE_NESTED_MERGE(Instruction::Update)
1588
{
39,184✔
1589
    using Type = Instruction::Payload::Type;
39,184✔
1590

17,002✔
1591
    if (outer.value.type == Type::ObjectValue || outer.value.type == Type::Dictionary) {
39,184!
1592
        // Creating an embedded object or a dictionary is an idempotent
32✔
1593
        // operation, and should not eliminate updates to the subtree.
32✔
1594
        return;
64✔
1595
    }
64✔
1596

16,970✔
1597
    // Setting a value higher up in the hierarchy overwrites any modification to
16,970✔
1598
    // the inner value, regardless of when this happened.
16,970✔
1599
    if (is_prefix_of(outer, inner)) {
39,120!
1600
        inner_side.discard();
80✔
1601
    }
80✔
1602
}
39,120✔
1603

1604
DEFINE_MERGE(Instruction::Update, Instruction::Update)
1605
{
26,636✔
1606
    // The two instructions are at the same level of nesting.
11,770✔
1607

11,770✔
1608
    using Type = Instruction::Payload::Type;
26,636✔
1609

11,770✔
1610
    if (same_path(left, right)) {
26,636✔
1611
        bool left_is_default = false;
9,324✔
1612
        bool right_is_default = false;
9,324✔
1613
        if (!(left.is_array_update() == right.is_array_update())) {
9,324✔
UNCOV
1614
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
×
UNCOV
1615
        }
×
1616

4,430✔
1617
        if (!left.is_array_update()) {
9,324✔
1618
            if (right.is_array_update()) {
9,066✔
UNCOV
1619
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
×
UNCOV
1620
            }
×
1621
            left_is_default = left.is_default;
9,066✔
1622
            right_is_default = right.is_default;
9,066✔
1623
        }
9,066✔
1624
        else if (!(left.prior_size == right.prior_size)) {
258✔
UNCOV
1625
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
UNCOV
1626
        }
×
1627

4,430✔
1628
        if (left.value.type != right.value.type) {
9,324✔
1629
            // Embedded object / dictionary creation should always lose to an
92✔
1630
            // Update(value), because these structures are nested, and we need to
92✔
1631
            // discard any update inside the structure.
92✔
1632
            if (left.value.type == Type::Dictionary || left.value.type == Type::ObjectValue) {
200✔
1633
                left_side.discard();
24✔
1634
                return;
24✔
1635
            }
24✔
1636
            else if (right.value.type == Type::Dictionary || right.value.type == Type::ObjectValue) {
176✔
1637
                right_side.discard();
24✔
1638
                return;
24✔
1639
            }
24✔
1640
        }
9,276✔
1641

4,406✔
1642
        // CONFLICT: Two updates of the same element.
4,406✔
1643
        //
4,406✔
1644
        // RESOLUTION: Suppress the effect of the UPDATE operation with the lower
4,406✔
1645
        // timestamp. Note that the timestamps can never be equal. This is
4,406✔
1646
        // achieved on both sides by discarding the received UPDATE operation if
4,406✔
1647
        // it has a lower timestamp than the previously applied UPDATE operation.
4,406✔
1648
        if (left_is_default == right_is_default) {
9,276✔
1649
            if (left_side.timestamp() < right_side.timestamp()) {
9,086✔
1650
                left_side.discard(); // --->
4,762✔
1651
            }
4,762✔
1652
            else {
4,324✔
1653
                right_side.discard(); // <---
4,324✔
1654
            }
4,324✔
1655
        }
9,086✔
1656
        else {
190✔
1657
            if (left_is_default) {
190✔
1658
                left_side.discard();
96✔
1659
            }
96✔
1660
            else {
94✔
1661
                right_side.discard();
94✔
1662
            }
94✔
1663
        }
190✔
1664
    }
9,276✔
1665
}
26,636✔
1666

1667
DEFINE_MERGE(Instruction::AddInteger, Instruction::Update)
1668
{
2,410✔
1669
    // The two instructions are at the same level of nesting.
1,120✔
1670

1,120✔
1671
    if (same_path(left, right)) {
2,410✔
1672
        // CONFLICT: Add vs Set of the same element.
252✔
1673
        //
252✔
1674
        // RESOLUTION: If the Add was later than the Set, add its value to
252✔
1675
        // the payload of the Set instruction. Otherwise, discard it.
252✔
1676

252✔
1677
        if (!(right.value.type == Instruction::Payload::Type::Int || right.value.is_null())) {
480✔
1678
            bad_merge(right_side, right,
×
1679
                      "Merge error: right.value.type == Instruction::Payload::Type::Int || right.value.is_null()");
×
1680
        }
×
1681

252✔
1682
        bool right_is_default = !right.is_array_update() && right.is_default;
480✔
1683

252✔
1684
        // Note: AddInteger survives SetDefault, regardless of timestamp.
252✔
1685
        if (right_side.timestamp() < left_side.timestamp() || right_is_default) {
480✔
1686
            if (right.value.is_null()) {
408✔
1687
                // The AddInteger happened "after" the Set(null). This becomes a
28✔
1688
                // no-op, but if the server later integrates a Set(int) that
28✔
1689
                // came-before the AddInteger, it will be taken into account again.
28✔
1690
                return;
56✔
1691
            }
56✔
1692

196✔
1693
            // Wrapping add
196✔
1694
            uint64_t ua = uint64_t(right.value.data.integer);
352✔
1695
            uint64_t ub = uint64_t(left.value);
352✔
1696
            right.value.data.integer = int64_t(ua + ub);
352✔
1697
        }
352✔
1698
        else {
72✔
1699
            left_side.discard();
72✔
1700
        }
72✔
1701
    }
480✔
1702
}
2,410✔
1703

1704
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::Update);
1705

1706
DEFINE_MERGE(Instruction::EraseColumn, Instruction::Update)
1707
{
×
1708
    if (same_column(left, right)) {
×
1709
        right_side.discard();
×
1710
    }
×
1711
}
×
1712

1713
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::Update)
1714
{
53,018✔
1715
    if (same_container(left, right)) {
53,018✔
1716
        REALM_ASSERT(right.is_array_update());
7,952✔
1717
        if (!(left.prior_size == right.prior_size)) {
7,952✔
UNCOV
1718
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
UNCOV
1719
        }
×
1720
        if (!(left.index() <= left.prior_size)) {
7,952✔
UNCOV
1721
            bad_merge(left_side, left, "Merge error: left.index() <= left.prior_size");
×
UNCOV
1722
        }
×
1723
        if (!(right.index() < right.prior_size)) {
7,952✔
1724
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1725
        }
×
1726
        right.prior_size += 1;
7,952✔
1727
        if (right.index() >= left.index()) {
7,952✔
1728
            right.index() += 1; // --->
3,264✔
1729
        }
3,264✔
1730
    }
7,952✔
1731
}
53,018✔
1732

1733
DEFINE_MERGE(Instruction::ArrayMove, Instruction::Update)
1734
{
24✔
1735
    if (same_container(left, right)) {
24✔
UNCOV
1736
        REALM_ASSERT(right.is_array_update());
×
1737

UNCOV
1738
        if (!(left.index() < left.prior_size)) {
×
UNCOV
1739
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
1740
        }
×
UNCOV
1741
        if (!(right.index() < right.prior_size)) {
×
UNCOV
1742
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
UNCOV
1743
        }
×
1744

1745
        // FIXME: This marks both sides as dirty, even when they are unmodified.
UNCOV
1746
        merge_get_vs_move(right.index(), left.index(), left.ndx_2);
×
UNCOV
1747
    }
×
1748
}
24✔
1749

1750
DEFINE_MERGE(Instruction::ArrayErase, Instruction::Update)
1751
{
11,162✔
1752
    if (same_container(left, right)) {
11,162✔
1753
        REALM_ASSERT(right.is_array_update());
2,312✔
1754
        if (!(left.prior_size == right.prior_size)) {
2,312✔
UNCOV
1755
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
UNCOV
1756
        }
×
1757
        if (!(left.index() < left.prior_size)) {
2,312✔
UNCOV
1758
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
1759
        }
×
1760
        if (!(right.index() < right.prior_size)) {
2,312✔
UNCOV
1761
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
UNCOV
1762
        }
×
1763

876✔
1764
        // CONFLICT: Update of a removed element.
876✔
1765
        //
876✔
1766
        // RESOLUTION: Discard the UPDATE operation received on the right side.
876✔
1767
        right.prior_size -= 1;
2,312✔
1768

876✔
1769
        if (left.index() == right.index()) {
2,312✔
1770
            // CONFLICT: Update of a removed element.
248✔
1771
            //
248✔
1772
            // RESOLUTION: Discard the UPDATE operation received on the right side.
248✔
1773
            right_side.discard();
540✔
1774
        }
540✔
1775
        else if (right.index() > left.index()) {
1,772✔
1776
            right.index() -= 1;
884✔
1777
        }
884✔
1778
    }
2,312✔
1779
}
11,162✔
1780

1781
// Handled by nested rule
1782
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::Update);
1783
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::Update);
1784
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::Update);
1785

1786

1787
/// AddInteger rules
1788

1789
DEFINE_NESTED_MERGE_NOOP(Instruction::AddInteger);
1790
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddInteger);
1791
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddInteger);
1792
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddInteger);
1793
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddInteger);
1794
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddInteger);
1795
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddInteger);
1796
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddInteger);
1797
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddInteger);
1798
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddInteger);
1799

1800

1801
/// AddColumn rules
1802

1803
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1804

1805
DEFINE_MERGE(Instruction::AddColumn, Instruction::AddColumn)
1806
{
35,766✔
1807
    if (same_column(left, right)) {
35,766✔
1808
        StringData left_name = left_side.get_string(left.field);
10,150✔
1809
        if (left.type != right.type) {
10,150✔
1810
            bad_merge(
8✔
1811
                "Schema mismatch: Property '%1' in class '%2' is of type %3 on one side and type %4 on the other.",
8✔
1812
                left_name, left_side.get_string(left.table), get_type_name(left.type), get_type_name(right.type));
8✔
1813
        }
8✔
1814

5,082✔
1815
        if (left.nullable != right.nullable) {
10,150✔
1816
            bad_merge("Schema mismatch: Property '%1' in class '%2' is nullable on one side and not on the other.",
6✔
1817
                      left_name, left_side.get_string(left.table));
6✔
1818
        }
6✔
1819

5,082✔
1820
        if (left.collection_type != right.collection_type) {
10,150✔
UNCOV
1821
            auto collection_type_name = [](Instruction::AddColumn::CollectionType type) -> const char* {
×
UNCOV
1822
                switch (type) {
×
UNCOV
1823
                    case Instruction::AddColumn::CollectionType::Single:
×
UNCOV
1824
                        return "single value";
×
UNCOV
1825
                    case Instruction::AddColumn::CollectionType::List:
×
UNCOV
1826
                        return "list";
×
UNCOV
1827
                    case Instruction::AddColumn::CollectionType::Dictionary:
×
UNCOV
1828
                        return "dictionary";
×
1829
                    case Instruction::AddColumn::CollectionType::Set:
×
1830
                        return "set";
×
1831
                }
×
1832
                REALM_TERMINATE("");
×
1833
            };
×
1834

UNCOV
1835
            std::stringstream ss;
×
UNCOV
1836
            const char* left_type = collection_type_name(left.collection_type);
×
UNCOV
1837
            const char* right_type = collection_type_name(right.collection_type);
×
UNCOV
1838
            bad_merge("Schema mismatch: Property '%1' in class '%2' is a %3 on one side, and a %4 on the other.",
×
UNCOV
1839
                      left_name, left_side.get_string(left.table), left_type, right_type);
×
UNCOV
1840
        }
×
1841

5,082✔
1842
        if (left.type == Instruction::Payload::Type::Link) {
10,150✔
1843
            StringData left_target = left_side.get_string(left.link_target_table);
1,932✔
1844
            StringData right_target = right_side.get_string(right.link_target_table);
1,932✔
1845
            if (left_target != right_target) {
1,932✔
1846
                bad_merge("Schema mismatch: Link property '%1' in class '%2' points to class '%3' on one side and to "
8✔
1847
                          "'%4' on the other.",
8✔
1848
                          left_name, left_side.get_string(left.table), left_target, right_target);
8✔
1849
            }
8✔
1850
        }
1,932✔
1851

5,082✔
1852
        // Name, type, nullability and link targets match -- discard both
5,082✔
1853
        // sides and proceed.
5,082✔
1854
        left_side.discard();
10,150✔
1855
        right_side.discard();
10,150✔
1856
    }
10,150✔
1857
}
35,766✔
1858

1859
DEFINE_MERGE(Instruction::EraseColumn, Instruction::AddColumn)
UNCOV
1860
{
×
UNCOV
1861
    if (same_column(left, right)) {
×
UNCOV
1862
        right_side.discard();
×
UNCOV
1863
    }
×
UNCOV
1864
}
×
1865

1866
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddColumn);
1867
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddColumn);
1868
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddColumn);
1869
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddColumn);
1870
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddColumn);
1871
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddColumn);
1872

1873

1874
/// EraseColumn rules
1875

1876
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1877

1878
DEFINE_MERGE(Instruction::EraseColumn, Instruction::EraseColumn)
1879
{
×
UNCOV
1880
    if (same_column(left, right)) {
×
UNCOV
1881
        left_side.discard();
×
UNCOV
1882
        right_side.discard();
×
UNCOV
1883
    }
×
UNCOV
1884
}
×
1885

1886
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseColumn);
1887
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseColumn);
1888
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseColumn);
1889
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseColumn);
1890
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseColumn);
1891
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseColumn);
1892

1893
/// ArrayInsert rules
1894

1895
DEFINE_NESTED_MERGE(Instruction::ArrayInsert)
1896
{
16✔
1897
    if (is_container_prefix_of(outer, inner)) {
16!
1898
        auto& index = corresponding_index_in_path(outer, inner);
16✔
1899
        if (index >= outer.index()) {
16!
1900
            index += 1;
8✔
1901
        }
8✔
1902
    }
16✔
1903
}
16✔
1904

1905
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::ArrayInsert)
1906
{
56,436✔
1907
    if (same_container(left, right)) {
56,436✔
1908
        if (!(left.prior_size == right.prior_size)) {
15,072✔
UNCOV
1909
            bad_merge(right_side, right, "Exception: left.prior_size == right.prior_size");
×
1910
        }
×
1911
        left.prior_size++;
15,072✔
1912
        right.prior_size++;
15,072✔
1913

6,578✔
1914
        if (left.index() > right.index()) {
15,072✔
1915
            left.index() += 1; // --->
3,146✔
1916
        }
3,146✔
1917
        else if (left.index() < right.index()) {
11,926✔
1918
            right.index() += 1; // <---
3,150✔
1919
        }
3,150✔
1920
        else { // left index == right index
8,776✔
1921
            // CONFLICT: Two element insertions at the same position.
4,116✔
1922
            //
4,116✔
1923
            // Resolution: Place the inserted elements in order of increasing
4,116✔
1924
            // timestamp. Note that the timestamps can never be equal.
4,116✔
1925
            if (left_side.timestamp() < right_side.timestamp()) {
8,776✔
1926
                right.index() += 1;
4,384✔
1927
            }
4,384✔
1928
            else {
4,392✔
1929
                left.index() += 1;
4,392✔
1930
            }
4,392✔
1931
        }
8,776✔
1932
    }
15,072✔
1933
}
56,436✔
1934

1935
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayInsert)
1936
{
×
1937
    if (same_container(left, right)) {
×
UNCOV
1938
        left.prior_size += 1;
×
1939

1940
        // Left insertion vs right removal
UNCOV
1941
        if (right.index() > left.index()) {
×
UNCOV
1942
            right.index() -= 1; // --->
×
1943
        }
×
1944
        else {
×
UNCOV
1945
            left.index() += 1; // <---
×
1946
        }
×
1947

1948
        // Left insertion vs left insertion
1949
        if (right.index() < left.ndx_2) {
×
1950
            left.ndx_2 += 1; // <---
×
UNCOV
1951
        }
×
UNCOV
1952
        else if (right.index() > left.ndx_2) {
×
UNCOV
1953
            right.index() += 1; // --->
×
UNCOV
1954
        }
×
UNCOV
1955
        else { // right.index() == left.ndx_2
×
1956
            // CONFLICT: Insertion and movement to same position.
1957
            //
1958
            // RESOLUTION: Place the two elements in order of increasing
1959
            // timestamp. Note that the timestamps can never be equal.
UNCOV
1960
            if (left_side.timestamp() < right_side.timestamp()) {
×
UNCOV
1961
                left.ndx_2 += 1; // <---
×
UNCOV
1962
            }
×
UNCOV
1963
            else {
×
UNCOV
1964
                right.index() += 1; // --->
×
UNCOV
1965
            }
×
UNCOV
1966
        }
×
UNCOV
1967
    }
×
UNCOV
1968
}
×
1969

1970
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayInsert)
1971
{
23,510✔
1972
    if (same_container(left, right)) {
23,510✔
1973
        if (!(left.prior_size == right.prior_size)) {
7,228✔
1974
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1975
        }
×
1976
        if (!(left.index() < left.prior_size)) {
7,228✔
1977
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
1978
        }
×
1979
        if (!(right.index() <= right.prior_size)) {
7,228✔
UNCOV
1980
            bad_merge(left_side, left, "Merge error: right.index() <= right.prior_size");
×
UNCOV
1981
        }
×
1982

3,290✔
1983
        left.prior_size++;
7,228✔
1984
        right.prior_size--;
7,228✔
1985
        if (right.index() <= left.index()) {
7,228✔
1986
            left.index() += 1; // --->
2,880✔
1987
        }
2,880✔
1988
        else {
4,348✔
1989
            right.index() -= 1; // <---
4,348✔
1990
        }
4,348✔
1991
    }
7,228✔
1992
}
23,510✔
1993

1994
// Handled by nested rules
1995
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayInsert);
1996
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayInsert);
1997
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayInsert);
1998

1999

2000
/// ArrayMove rules
2001

2002
DEFINE_NESTED_MERGE(Instruction::ArrayMove)
UNCOV
2003
{
×
UNCOV
2004
    if (is_container_prefix_of(outer, inner)) {
×
UNCOV
2005
        auto& index = corresponding_index_in_path(outer, inner);
×
UNCOV
2006
        merge_get_vs_move(outer.index(), index, outer.ndx_2);
×
UNCOV
2007
    }
×
UNCOV
2008
}
×
2009

2010
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayMove)
2011
{
28✔
2012
    if (same_container(left, right)) {
28✔
2013
        if (!(left.prior_size == right.prior_size)) {
28✔
UNCOV
2014
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
UNCOV
2015
        }
×
2016
        if (!(left.index() < left.prior_size)) {
28✔
2017
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
2018
        }
×
2019
        if (!(right.index() < right.prior_size)) {
28✔
UNCOV
2020
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
UNCOV
2021
        }
×
2022
        if (!(left.ndx_2 < left.prior_size)) {
28✔
2023
            bad_merge(left_side, left, "Merge error: left.ndx_2 < left.prior_size");
×
UNCOV
2024
        }
×
2025
        if (!(right.ndx_2 < right.prior_size)) {
28✔
UNCOV
2026
            bad_merge(right_side, right, "Merge error: right.ndx_2 < right.prior_size");
×
UNCOV
2027
        }
×
2028

14✔
2029
        if (left.index() < right.index()) {
28✔
2030
            right.index() -= 1; // <---
4✔
2031
        }
4✔
2032
        else if (left.index() > right.index()) {
24✔
2033
            left.index() -= 1; // --->
4✔
2034
        }
4✔
2035
        else {
20✔
2036
            // CONFLICT: Two movements of same element.
10✔
2037
            //
10✔
2038
            // RESOLUTION: Respect the MOVE operation associated with the higher
10✔
2039
            // timestamp. If the previously applied MOVE operation has the higher
10✔
2040
            // timestamp, discard the received MOVE operation, otherwise use the
10✔
2041
            // previously applied MOVE operation to transform the received MOVE
10✔
2042
            // operation. Note that the timestamps are never equal.
10✔
2043
            if (left_side.timestamp() < right_side.timestamp()) {
20✔
2044
                right.index() = left.ndx_2; // <---
12✔
2045
                left_side.discard();        // --->
12✔
2046
                if (right.index() == right.ndx_2) {
12✔
UNCOV
2047
                    right_side.discard(); // <---
×
UNCOV
2048
                }
×
2049
            }
12✔
2050
            else {
8✔
2051
                left.index() = right.ndx_2; // --->
8✔
2052
                if (left.index() == left.ndx_2) {
8✔
UNCOV
2053
                    left_side.discard(); // --->
×
UNCOV
2054
                }
×
2055
                right_side.discard(); // <---
8✔
2056
            }
8✔
2057
            return;
20✔
2058
        }
20✔
2059

4✔
2060
        // Left insertion vs right removal
4✔
2061
        if (left.ndx_2 > right.index()) {
8✔
2062
            left.ndx_2 -= 1; // --->
4✔
2063
        }
4✔
2064
        else {
4✔
2065
            right.index() += 1; // <---
4✔
2066
        }
4✔
2067

4✔
2068
        // Left removal vs right insertion
4✔
2069
        if (left.index() < right.ndx_2) {
8✔
2070
            right.ndx_2 -= 1; // <---
4✔
2071
        }
4✔
2072
        else {
4✔
2073
            left.index() += 1; // --->
4✔
2074
        }
4✔
2075

4✔
2076
        // Left insertion vs right insertion
4✔
2077
        if (left.ndx_2 < right.ndx_2) {
8✔
2078
            right.ndx_2 += 1; // <---
4✔
2079
        }
4✔
2080
        else if (left.ndx_2 > right.ndx_2) {
4✔
2081
            left.ndx_2 += 1; // --->
4✔
2082
        }
4✔
2083
        else { // left.ndx_2 == right.ndx_2
×
2084
            // CONFLICT: Two elements moved to the same position
2085
            //
2086
            // RESOLUTION: Place the moved elements in order of increasing
2087
            // timestamp. Note that the timestamps can never be equal.
UNCOV
2088
            if (left_side.timestamp() < right_side.timestamp()) {
×
2089
                right.ndx_2 += 1; // <---
×
UNCOV
2090
            }
×
UNCOV
2091
            else {
×
UNCOV
2092
                left.ndx_2 += 1; // --->
×
UNCOV
2093
            }
×
UNCOV
2094
        }
×
2095

4✔
2096
        if (left.index() == left.ndx_2) {
8✔
2097
            left_side.discard(); // --->
×
2098
        }
×
2099
        if (right.index() == right.ndx_2) {
8✔
2100
            right_side.discard(); // <---
×
2101
        }
×
2102
    }
8✔
2103
}
28✔
2104

2105
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayMove)
UNCOV
2106
{
×
2107
    if (same_container(left, right)) {
×
2108
        if (!(left.prior_size == right.prior_size)) {
×
2109
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
2110
        }
×
2111
        if (!(left.index() < left.prior_size)) {
×
2112
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
2113
        }
×
2114
        if (!(right.index() < right.prior_size)) {
×
2115
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
2116
        }
×
2117

2118
        right.prior_size -= 1;
×
2119

UNCOV
2120
        if (left.index() == right.index()) {
×
2121
            // CONFLICT: Removal of a moved element.
2122
            //
2123
            // RESOLUTION: Discard the received MOVE operation on the left side, and
2124
            // use the previously applied MOVE operation to transform the received
2125
            // REMOVE operation on the right side.
UNCOV
2126
            left.index() = right.ndx_2; // --->
×
UNCOV
2127
            right_side.discard();       // <---
×
UNCOV
2128
        }
×
UNCOV
2129
        else {
×
2130
            // Left removal vs right removal
UNCOV
2131
            if (left.index() > right.index()) {
×
UNCOV
2132
                left.index() -= 1; // --->
×
2133
            }
×
2134
            else {                  // left.index() < right.index()
×
UNCOV
2135
                right.index() -= 1; // <---
×
UNCOV
2136
            }
×
2137
            // Left removal vs right insertion
UNCOV
2138
            if (left.index() >= right.ndx_2) {
×
UNCOV
2139
                left.index() += 1; // --->
×
UNCOV
2140
            }
×
2141
            else {
×
2142
                right.ndx_2 -= 1; // <---
×
2143
            }
×
2144

UNCOV
2145
            if (right.index() == right.ndx_2) {
×
UNCOV
2146
                right_side.discard(); // <---
×
UNCOV
2147
            }
×
UNCOV
2148
        }
×
UNCOV
2149
    }
×
UNCOV
2150
}
×
2151

2152
// Handled by nested rule.
2153
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayMove);
2154
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayMove);
2155
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayMove);
2156

2157

2158
/// ArrayErase rules
2159

2160
DEFINE_NESTED_MERGE(Instruction::ArrayErase)
2161
{
8✔
2162
    if (is_prefix_of(outer, inner)) {
8!
2163
        // Erase of subtree - inner instruction touches the subtree.
UNCOV
2164
        inner_side.discard();
×
UNCOV
2165
    }
×
2166
    else if (is_container_prefix_of(outer, inner)) {
8!
2167
        // Erase of a sibling element in the container - adjust the path.
4✔
2168
        auto& index = corresponding_index_in_path(outer, inner);
8✔
2169
        if (outer.index() < index) {
8!
2170
            index -= 1;
8✔
2171
        }
8✔
UNCOV
2172
        else {
×
UNCOV
2173
            REALM_ASSERT(index != outer.index());
×
UNCOV
2174
        }
×
2175
    }
8✔
2176
}
8✔
2177

2178
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayErase)
2179
{
2,836✔
2180
    if (same_container(left, right)) {
2,836✔
2181
        if (!(left.prior_size == right.prior_size)) {
1,044✔
UNCOV
2182
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
UNCOV
2183
        }
×
2184
        if (!(left.index() < left.prior_size)) {
1,044✔
UNCOV
2185
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
UNCOV
2186
        }
×
2187
        if (!(right.index() < right.prior_size)) {
1,044✔
UNCOV
2188
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
UNCOV
2189
        }
×
2190

434✔
2191
        left.prior_size -= 1;
1,044✔
2192
        right.prior_size -= 1;
1,044✔
2193

434✔
2194
        if (left.index() > right.index()) {
1,044✔
2195
            left.index() -= 1; // --->
396✔
2196
        }
396✔
2197
        else if (left.index() < right.index()) {
648✔
2198
            right.index() -= 1; // <---
400✔
2199
        }
400✔
2200
        else { // left.index() == right.index()
248✔
2201
            // CONFLICT: Two removals of the same element.
100✔
2202
            //
100✔
2203
            // RESOLUTION: On each side, discard the received REMOVE operation.
100✔
2204
            left_side.discard();  // --->
248✔
2205
            right_side.discard(); // <---
248✔
2206
        }
248✔
2207
    }
1,044✔
2208
}
2,836✔
2209

2210
// Handled by nested rules.
2211
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayErase);
2212
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayErase);
2213
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayErase);
2214

2215

2216
/// Clear rules
2217

2218
DEFINE_NESTED_MERGE(Instruction::Clear)
2219
{
788✔
2220
    // Note: Clear instructions do not have an index in their path.
158✔
2221
    if (is_prefix_of(outer, inner)) {
788!
2222
        inner_side.discard();
400✔
2223
    }
400✔
2224
}
788✔
2225

2226
DEFINE_MERGE(Instruction::Clear, Instruction::Clear)
2227
{
20✔
2228
    if (same_path(left, right)) {
20✔
2229
        // CONFLICT: Two clears of the same container.
8✔
2230
        //
8✔
2231
        // RESOLUTION: Discard the clear with the lower timestamp. This has the
8✔
2232
        // effect of preserving insertions that came after the clear from the
8✔
2233
        // side that has the higher timestamp.
8✔
2234
        if (left_side.timestamp() < right_side.timestamp()) {
20✔
2235
            left_side.discard();
14✔
2236
        }
14✔
2237
        else {
6✔
2238
            right_side.discard();
6✔
2239
        }
6✔
2240
    }
20✔
2241
}
20✔
2242

2243
DEFINE_MERGE(Instruction::SetInsert, Instruction::Clear)
2244
{
8✔
2245
    if (same_path(left, right)) {
8!
2246
        left_side.discard();
4✔
2247
    }
4✔
2248
}
8✔
2249

2250
DEFINE_MERGE(Instruction::SetErase, Instruction::Clear)
2251
{
8✔
2252
    if (same_path(left, right)) {
8!
2253
        left_side.discard();
4✔
2254
    }
4✔
2255
}
8✔
2256

2257

2258
/// SetInsert rules
2259

2260
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2261

2262
DEFINE_MERGE(Instruction::SetInsert, Instruction::SetInsert)
2263
{
156✔
2264
    if (same_path(left, right)) {
156✔
2265
        // CONFLICT: Two inserts into the same set.
76✔
2266
        //
76✔
2267
        // RESOLUTION: If the values are the same, discard the insertion with the lower timestamp. Otherwise,
76✔
2268
        // do nothing.
76✔
2269
        //
76✔
2270
        // NOTE: Set insertion is idempotent. Keeping the instruction with the higher timestamp is necessary
76✔
2271
        // because we want to maintain associativity in the case where intermittent erases (as ordered by
76✔
2272
        // timestamp) arrive at a later point in time.
76✔
2273
        if (same_payload(left.value, right.value)) {
152✔
2274
            if (left_side.timestamp() < right_side.timestamp()) {
24✔
2275
                left_side.discard();
12✔
2276
            }
12✔
2277
            else {
12✔
2278
                right_side.discard();
12✔
2279
            }
12✔
2280
        }
24✔
2281
    }
152✔
2282
}
156✔
2283

2284
DEFINE_MERGE(Instruction::SetErase, Instruction::SetInsert)
2285
{
64✔
2286
    if (same_path(left, right)) {
64✔
2287
        // CONFLICT: Insertion and erase in the same set.
32✔
2288
        //
32✔
2289
        // RESOLUTION: If the inserted/erased values are the same, discard the instruction with the lower
32✔
2290
        // timestamp. Otherwise, do nothing.
32✔
2291
        //
32✔
2292
        // Note: Set insertion and erase are both idempotent. Keeping the instruction with the higher
32✔
2293
        // timestamp is necessary because we want to maintain associativity.
32✔
2294
        if (same_payload(left.value, right.value)) {
64✔
2295
            if (left_side.timestamp() < right_side.timestamp()) {
×
UNCOV
2296
                left_side.discard();
×
UNCOV
2297
            }
×
UNCOV
2298
            else {
×
UNCOV
2299
                right_side.discard();
×
UNCOV
2300
            }
×
UNCOV
2301
        }
×
2302
    }
64✔
2303
}
64✔
2304

2305

2306
/// SetErase rules.
2307

2308
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2309

2310
DEFINE_MERGE(Instruction::SetErase, Instruction::SetErase)
UNCOV
2311
{
×
UNCOV
2312
    if (same_path(left, right)) {
×
2313
        // CONFLICT: Two erases in the same set.
2314
        //
2315
        // RESOLUTION: If the values are the same, discard the instruction with the lower timestamp.
2316
        // Otherwise, do nothing.
UNCOV
2317
        if (left.value == right.value) {
×
UNCOV
2318
            if (left_side.timestamp() < right_side.timestamp()) {
×
UNCOV
2319
                left_side.discard();
×
UNCOV
2320
            }
×
UNCOV
2321
            else {
×
UNCOV
2322
                right_side.discard();
×
UNCOV
2323
            }
×
UNCOV
2324
        }
×
UNCOV
2325
    }
×
UNCOV
2326
}
×
2327

2328

2329
///
2330
/// END OF MERGE RULES!
2331
///
2332

2333
} // namespace
2334

2335
namespace _impl {
2336
template <class Left, class Right>
2337
void merge_instructions_2(Left& left, Right& right, TransformerImpl::MajorSide& left_side,
2338
                          TransformerImpl::MinorSide& right_side)
2339
{
2,574,014✔
2340
    Merge<Left, Right>::merge(left, right, left_side, right_side);
2,574,014✔
2341
}
2,574,014✔
2342

2343
template <class Outer, class Inner, class OuterSide, class InnerSide>
2344
void merge_nested_2(Outer& outer, Inner& inner, OuterSide& outer_side, InnerSide& inner_side)
2345
{
1,133,592✔
2346
    MergeNested<Outer>::merge(outer, inner, outer_side, inner_side);
1,133,592✔
2347
}
1,133,592✔
2348

2349
void TransformerImpl::Transformer::merge_instructions(MajorSide& their_side, MinorSide& our_side)
2350
{
2,635,498✔
2351
    // FIXME: Find a way to avoid heap-copies of the path.
1,284,752✔
2352
    Instruction their_before = their_side.get();
2,635,498✔
2353
    Instruction our_before = our_side.get();
2,635,498✔
2354

1,284,752✔
2355
    if (their_side.get().get_if<Instruction::Update>()) {
2,635,498✔
2356
        REALM_ASSERT(their_side.m_path_len > 2);
189,954✔
2357
    }
189,954✔
2358
    if (our_side.get().get_if<Instruction::Update>()) {
2,635,498✔
2359
        REALM_ASSERT(our_side.m_path_len > 2);
237,362✔
2360
    }
237,362✔
2361
    if (their_side.get().get_if<Instruction::EraseObject>()) {
2,635,498✔
2362
        REALM_ASSERT(their_side.m_path_len == 2);
533,740✔
2363
    }
533,740✔
2364
    if (our_side.get().get_if<Instruction::EraseObject>()) {
2,635,498✔
2365
        REALM_ASSERT(our_side.m_path_len == 2);
605,542✔
2366
    }
605,542✔
2367

1,284,752✔
2368
    // Update selections on the major side (outer loop) according to events on
1,284,752✔
2369
    // the minor side (inner loop). The selection may only be impacted if the
1,284,752✔
2370
    // instruction level is lower (i.e. at a higher point in the hierarchy).
1,284,752✔
2371
    if (our_side.m_path_len < their_side.m_path_len) {
2,635,498✔
2372
        merge_nested(our_side, their_side);
478,626✔
2373
        if (their_side.was_discarded)
478,626✔
2374
            return;
30,798✔
2375
    }
2,156,872✔
2376
    else if (our_side.m_path_len > their_side.m_path_len) {
2,156,872✔
2377
        merge_nested(their_side, our_side);
654,966✔
2378
        if (our_side.was_discarded)
654,966✔
2379
            return;
30,746✔
2380
    }
2,573,954✔
2381

1,254,978✔
2382
    if (!their_side.was_discarded && !our_side.was_discarded) {
2,573,966✔
2383
        // Even if the instructions were nested, we must still perform a regular
1,254,990✔
2384
        // merge, because link-related instructions contain information from higher
1,254,990✔
2385
        // levels (both rows, columns, and tables).
1,254,990✔
2386
        //
1,254,990✔
2387
        // FIXME: This condition goes away when dangling links are implemented.
1,254,990✔
2388
        their_side.get().visit([&](auto& their_instruction) {
2,574,002✔
2389
            our_side.get().visit([&](auto& our_instruction) {
2,574,008✔
2390
                merge_instructions_2(their_instruction, our_instruction, their_side, our_side);
2,574,008✔
2391
            });
2,574,008✔
2392
        });
2,574,002✔
2393
    }
2,573,954✔
2394

1,254,978✔
2395
    // Note: `left` and/or `right` may be dangling at this point due to
1,254,978✔
2396
    // discard/prepend. However, if they were not discarded, their iterators are
1,254,978✔
2397
    // required to point to an instruction of the same type.
1,254,978✔
2398
    if (!their_side.was_discarded && !their_side.was_replaced) {
2,573,954✔
2399
        const auto& their_after = their_side.get();
2,535,754✔
2400
        if (!(their_after == their_before)) {
2,535,754✔
2401
            their_side.m_changeset->set_dirty(true);
28,150✔
2402
        }
28,150✔
2403
    }
2,535,754✔
2404

1,254,978✔
2405
    if (!our_side.was_discarded && !our_side.was_replaced) {
2,573,954✔
2406
        const auto& our_after = our_side.get();
2,536,258✔
2407
        if (!(our_after == our_before)) {
2,536,258✔
2408
            our_side.m_changeset->set_dirty(true);
28,154✔
2409
        }
28,154✔
2410
    }
2,536,258✔
2411
}
2,573,954✔
2412

2413

2414
template <class OuterSide, class InnerSide>
2415
void TransformerImpl::Transformer::merge_nested(OuterSide& outer_side, InnerSide& inner_side)
2416
{
1,133,592✔
2417
    outer_side.get().visit([&](auto& outer) {
1,133,590✔
2418
        inner_side.get().visit([&](auto& inner) {
1,133,592✔
2419
            merge_nested_2(outer, inner, outer_side, inner_side);
1,133,592✔
2420
        });
1,133,592✔
2421
    });
1,133,590✔
2422
}
1,133,592✔
2423

2424

2425
void TransformerImpl::merge_changesets(file_ident_type local_file_ident, Changeset* their_changesets,
2426
                                       size_t their_size, Changeset** our_changesets, size_t our_size,
2427
                                       util::Logger& logger)
2428
{
417,220✔
2429
    REALM_ASSERT(their_size != 0);
417,220✔
2430
    REALM_ASSERT(our_size != 0);
417,220✔
2431
    bool trace = false;
417,220✔
2432
#if REALM_DEBUG && !REALM_UWP
417,220✔
2433
    // FIXME: Not thread-safe (use config parameter instead and confine environment reading to test/test_all.cpp)
210,678✔
2434
    const char* trace_p = ::getenv("UNITTEST_TRACE_TRANSFORM");
417,220✔
2435
    trace = (trace_p && StringData{trace_p} != "no");
417,220!
2436
    static std::mutex trace_mutex;
417,220✔
2437
    util::Optional<std::unique_lock<std::mutex>> l;
417,220✔
2438
    if (trace) {
417,220✔
UNCOV
2439
        l = std::unique_lock<std::mutex>{trace_mutex};
×
UNCOV
2440
    }
×
2441
#endif
417,220✔
2442
    Transformer transformer{trace};
417,220✔
2443

210,678✔
2444
    _impl::ChangesetIndex their_index;
417,220✔
2445
    size_t their_num_instructions = 0;
417,220✔
2446
    size_t our_num_instructions = 0;
417,220✔
2447

210,678✔
2448
    // Loop through all instructions on both sides and build conflict groups.
210,678✔
2449
    // This causes the index to merge ranges that are connected by instructions
210,678✔
2450
    // on the left side, but which aren't connected on the right side.
210,678✔
2451
    // FIXME: The conflict groups can be persisted as part of the changeset to
210,678✔
2452
    // skip this step in the future.
210,678✔
2453
    for (size_t i = 0; i < their_size; ++i) {
857,754✔
2454
        size_t num_instructions = their_changesets[i].size();
440,534✔
2455
        their_num_instructions += num_instructions;
440,534✔
2456
        logger.trace("Scanning incoming changeset [%1/%2] (%3 instructions)", i + 1, their_size, num_instructions);
440,534✔
2457

221,610✔
2458
        their_index.scan_changeset(their_changesets[i]);
440,534✔
2459
    }
440,534✔
2460
    for (size_t i = 0; i < our_size; ++i) {
10,215,258✔
2461
        Changeset& our_changeset = *our_changesets[i];
9,798,038✔
2462
        size_t num_instructions = our_changeset.size();
9,798,038✔
2463
        our_num_instructions += num_instructions;
9,798,038✔
2464
        logger.trace("Scanning local changeset [%1/%2] (%3 instructions)", i + 1, our_size, num_instructions);
9,798,038✔
2465

4,917,210✔
2466
        their_index.scan_changeset(our_changeset);
9,798,038✔
2467
    }
9,798,038✔
2468

210,678✔
2469
    // Build the index.
210,678✔
2470
    for (size_t i = 0; i < their_size; ++i) {
857,752✔
2471
        logger.trace("Indexing incoming changeset [%1/%2] (%3 instructions)", i + 1, their_size,
440,532✔
2472
                     their_changesets[i].size());
440,532✔
2473
        their_index.add_changeset(their_changesets[i]);
440,532✔
2474
    }
440,532✔
2475

210,678✔
2476
    logger.debug("Finished changeset indexing (incoming: %1 changeset(s) / %2 instructions, local: %3 "
417,220✔
2477
                 "changeset(s) / %4 instructions, conflict group(s): %5)",
417,220✔
2478
                 their_size, their_num_instructions, our_size, our_num_instructions,
417,220✔
2479
                 their_index.get_num_conflict_groups());
417,220✔
2480

210,678✔
2481
#if REALM_DEBUG // LCOV_EXCL_START
210,678✔
2482
    if (trace) {
417,220✔
2483
        std::cerr << TERM_YELLOW << "\n=> PEER " << std::hex << local_file_ident
×
UNCOV
2484
                  << " merging "
×
UNCOV
2485
                     "changeset(s)/from peer(s):\n";
×
UNCOV
2486
        for (size_t i = 0; i < their_size; ++i) {
×
UNCOV
2487
            std::cerr << "Changeset version " << std::dec << their_changesets[i].version << " from peer "
×
UNCOV
2488
                      << their_changesets[i].origin_file_ident << " at timestamp "
×
UNCOV
2489
                      << their_changesets[i].origin_timestamp << "\n";
×
UNCOV
2490
        }
×
UNCOV
2491
        std::cerr << "Transforming through local changeset(s):\n";
×
UNCOV
2492
        for (size_t i = 0; i < our_size; ++i) {
×
UNCOV
2493
            std::cerr << "Changeset version " << our_changesets[i]->version << " from peer "
×
UNCOV
2494
                      << our_changesets[i]->origin_file_ident << " at timestamp "
×
UNCOV
2495
                      << our_changesets[i]->origin_timestamp << "\n";
×
UNCOV
2496
        }
×
2497

UNCOV
2498
        for (size_t i = 0; i < our_size; ++i) {
×
UNCOV
2499
            std::cerr << TERM_RED << "\nLOCAL (RECIPROCAL) CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
UNCOV
2500
            our_changesets[i]->print(std::cerr);
×
UNCOV
2501
        }
×
2502

UNCOV
2503
        for (size_t i = 0; i < their_size; ++i) {
×
UNCOV
2504
            std::cerr << TERM_RED << "\nINCOMING CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
UNCOV
2505
            their_changesets[i].print(std::cerr);
×
UNCOV
2506
        }
×
2507

UNCOV
2508
        std::cerr << TERM_MAGENTA << "\nINCOMING CHANGESET INDEX:\n" << TERM_RESET;
×
UNCOV
2509
        their_index.print(std::cerr);
×
UNCOV
2510
        std::cerr << '\n';
×
UNCOV
2511
        their_index.verify();
×
2512

2513
        std::cerr << TERM_YELLOW << std::setw(80) << std::left << "MERGE TRACE (incoming):"
×
2514
                  << "MERGE TRACE (local):\n"
×
2515
                  << TERM_RESET;
×
2516
    }
×
2517
#else
2518
    static_cast<void>(local_file_ident);
2519
#endif // REALM_DEBUG LCOV_EXCL_STOP
2520

210,678✔
2521
    for (size_t i = 0; i < our_size; ++i) {
10,215,304✔
2522
        logger.trace(
9,798,084✔
2523
            "Transforming local changeset [%1/%2] through %3 incoming changeset(s) with %4 conflict group(s)", i + 1,
9,798,084✔
2524
            our_size, their_size, their_index.get_num_conflict_groups());
9,798,084✔
2525
        Changeset* our_changeset = our_changesets[i];
9,798,084✔
2526

4,917,248✔
2527
        transformer.m_major_side.set_next_changeset(our_changeset);
9,798,084✔
2528
        // MinorSide uses the index to find the Changeset.
4,917,248✔
2529
        transformer.m_minor_side.m_changeset_index = &their_index;
9,798,084✔
2530
        transformer.transform(); // Throws
9,798,084✔
2531
    }
9,798,084✔
2532

210,678✔
2533
    logger.debug("Finished transforming %1 local changesets through %2 incoming changesets (%3 vs %4 "
417,220✔
2534
                 "instructions, in %5 conflict groups)",
417,220✔
2535
                 our_size, their_size, our_num_instructions, their_num_instructions,
417,220✔
2536
                 their_index.get_num_conflict_groups());
417,220✔
2537

210,678✔
2538
#if REALM_DEBUG // LCOV_EXCL_START
210,678✔
2539
    // Check that the index is still valid after transformation.
210,678✔
2540
    their_index.verify();
417,220✔
2541
#endif // REALM_DEBUG LCOV_EXCL_STOP
417,220✔
2542

210,678✔
2543
#if REALM_DEBUG // LCOV_EXCL_START
210,678✔
2544
    if (trace) {
417,220✔
UNCOV
2545
        for (size_t i = 0; i < our_size; ++i) {
×
UNCOV
2546
            std::cerr << TERM_CYAN << "\nRECIPROCAL CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
UNCOV
2547
            our_changesets[i]->print(std::cerr);
×
UNCOV
2548
            std::cerr << '\n';
×
UNCOV
2549
        }
×
UNCOV
2550
        for (size_t i = 0; i < their_size; ++i) {
×
UNCOV
2551
            std::cerr << TERM_CYAN << "INCOMING CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
UNCOV
2552
            their_changesets[i].print(std::cerr);
×
UNCOV
2553
            std::cerr << '\n';
×
UNCOV
2554
        }
×
UNCOV
2555
    }
×
2556
#endif // LCOV_EXCL_STOP REALM_DEBUG
417,220✔
2557
}
417,220✔
2558

2559
size_t TransformerImpl::transform_remote_changesets(TransformHistory& history, file_ident_type local_file_ident,
2560
                                                    version_type current_local_version,
2561
                                                    util::Span<Changeset> parsed_changesets,
2562
                                                    util::UniqueFunction<bool(const Changeset*)> changeset_applier,
2563
                                                    util::Logger& logger)
2564
{
453,070✔
2565
    REALM_ASSERT(local_file_ident != 0);
453,070✔
2566

227,838✔
2567
    std::vector<Changeset*> our_changesets;
453,070✔
2568

227,838✔
2569
    // p points to the beginning of a range of changesets that share the same
227,838✔
2570
    // "base", i.e. are based on the same local version.
227,838✔
2571
    auto p = parsed_changesets.begin();
453,070✔
2572
    auto parsed_changesets_end = parsed_changesets.end();
453,070✔
2573

227,838✔
2574
    try {
453,070✔
2575
        while (p != parsed_changesets_end) {
911,252✔
2576
            // Find the range of incoming changesets that share the same
230,808✔
2577
            // last_integrated_local_version, which means we can merge them in one go.
230,808✔
2578
            auto same_base_range_end = std::find_if(p + 1, parsed_changesets_end, [&](auto& changeset) {
249,868✔
2579
                return p->last_integrated_remote_version != changeset.last_integrated_remote_version;
35,688✔
2580
            });
35,688✔
2581

230,808✔
2582
            version_type begin_version = p->last_integrated_remote_version;
458,182✔
2583
            version_type end_version = current_local_version;
458,182✔
2584
            for (;;) {
10,256,260✔
2585
                HistoryEntry history_entry;
10,256,260✔
2586
                version_type version = history.find_history_entry(begin_version, end_version, history_entry);
10,256,260✔
2587
                if (version == 0)
10,256,260✔
2588
                    break; // No more local changesets
458,184✔
2589

4,917,240✔
2590
                Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
9,798,076✔
2591
                                                                    history_entry); // Throws
9,798,076✔
2592
                our_changesets.push_back(&our_changeset);
9,798,076✔
2593

4,917,240✔
2594
                begin_version = version;
9,798,076✔
2595
            }
9,798,076✔
2596

230,808✔
2597
            bool must_apply_all = false;
458,182✔
2598

230,808✔
2599
            if (!our_changesets.empty()) {
458,182✔
2600
                merge_changesets(local_file_ident, &*p, same_base_range_end - p, our_changesets.data(),
417,220✔
2601
                                 our_changesets.size(), logger); // Throws
417,220✔
2602
                // We need to apply all transformed changesets if at least one reciprocal changeset was modified
210,678✔
2603
                // during OT.
210,678✔
2604
                must_apply_all = std::any_of(our_changesets.begin(), our_changesets.end(), [](const Changeset* c) {
9,010,614✔
2605
                    return c->is_dirty();
9,010,614✔
2606
                });
9,010,614✔
2607
            }
417,220✔
2608

230,808✔
2609
            auto continue_applying = true;
458,182✔
2610
            for (; p != same_base_range_end && continue_applying; ++p) {
946,902✔
2611
                // It is safe to stop applying the changesets if:
244,450✔
2612
                //      1. There are no reciprocal changesets
244,450✔
2613
                //      2. No reciprocal changeset was modified
244,450✔
2614
                continue_applying = changeset_applier(p) || must_apply_all;
488,720!
2615
            }
488,720✔
2616
            if (!continue_applying) {
458,182✔
UNCOV
2617
                break;
×
UNCOV
2618
            }
×
2619

230,808✔
2620
            our_changesets.clear(); // deliberately not releasing memory
458,182✔
2621
        }
458,182✔
2622
    }
453,070✔
2623
    catch (...) {
227,860✔
2624
        // If an exception was thrown while merging, the transform cache will
22✔
2625
        // be polluted. This is a problem since the same cache object is reused
22✔
2626
        // by multiple invocations to transform_remote_changesets(), so we must
22✔
2627
        // clear the cache before rethrowing.
22✔
2628
        //
22✔
2629
        // Note that some valid changesets can still cause exceptions to be
22✔
2630
        // thrown by the merge algorithm, namely incompatible schema changes.
22✔
2631
        m_reciprocal_transform_cache.clear();
44✔
2632
        throw;
44✔
2633
    }
44✔
2634

227,814✔
2635
    // NOTE: Any exception thrown during flushing *MUST* lead to rollback of
227,814✔
2636
    // the current transaction.
227,814✔
2637
    flush_reciprocal_transform_cache(history); // Throws
453,024✔
2638

227,814✔
2639
    return p - parsed_changesets.begin();
453,024✔
2640
}
453,024✔
2641

2642

2643
Changeset& TransformerImpl::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
2644
                                                     version_type version, const HistoryEntry& history_entry)
2645
{
9,798,090✔
2646
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
9,798,090✔
2647
    if (changeset.empty()) {
9,798,090✔
2648
        bool is_compressed = false;
9,773,322✔
2649
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
9,773,322✔
2650
        ChunkedBinaryInputStream in{data};
9,773,322✔
2651
        if (is_compressed) {
9,773,322✔
2652
            size_t total_size;
44,410✔
2653
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
44,410✔
2654
            REALM_ASSERT(decompressed);
44,410✔
2655
            sync::parse_changeset(*decompressed, changeset); // Throws
44,410✔
2656
        }
44,410✔
2657
        else {
9,728,912✔
2658
            sync::parse_changeset(in, changeset); // Throws
9,728,912✔
2659
        }
9,728,912✔
2660

4,903,982✔
2661
        changeset.version = version;
9,773,322✔
2662
        changeset.last_integrated_remote_version = history_entry.remote_version;
9,773,322✔
2663
        changeset.origin_timestamp = history_entry.origin_timestamp;
9,773,322✔
2664
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
9,773,322✔
2665
        if (origin_file_ident == 0)
9,773,322✔
2666
            origin_file_ident = local_file_ident;
5,647,736✔
2667
        changeset.origin_file_ident = origin_file_ident;
9,773,322✔
2668
    }
9,773,322✔
2669
    return changeset;
9,798,090✔
2670
}
9,798,090✔
2671

2672

2673
void TransformerImpl::flush_reciprocal_transform_cache(TransformHistory& history)
2674
{
453,022✔
2675
    auto changesets = std::move(m_reciprocal_transform_cache);
453,022✔
2676
    m_reciprocal_transform_cache.clear();
453,022✔
2677
    ChangesetEncoder::Buffer output_buffer;
453,022✔
2678
    for (const auto& [version, changeset] : changesets) {
9,772,246✔
2679
        if (changeset.is_dirty()) {
9,772,246✔
2680
            encode_changeset(changeset, output_buffer); // Throws
85,740✔
2681
            BinaryData data{output_buffer.data(), output_buffer.size()};
85,740✔
2682
            history.set_reciprocal_transform(version, data); // Throws
85,740✔
2683
            output_buffer.clear();
85,740✔
2684
        }
85,740✔
2685
    }
9,772,246✔
2686
}
453,022✔
2687

2688
} // namespace _impl
2689

2690
namespace sync {
2691
std::unique_ptr<Transformer> make_transformer()
2692
{
18,270✔
2693
    return std::make_unique<_impl::TransformerImpl>(); // Throws
18,270✔
2694
}
18,270✔
2695

2696

2697
void parse_remote_changeset(const Transformer::RemoteChangeset& remote_changeset, Changeset& parsed_changeset)
2698
{
488,788✔
2699
    // origin_file_ident = 0 is currently used to indicate an entry of local
244,444✔
2700
    // origin.
244,444✔
2701
    REALM_ASSERT(remote_changeset.origin_file_ident != 0);
488,788✔
2702
    REALM_ASSERT(remote_changeset.remote_version != 0);
488,788✔
2703

244,444✔
2704
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
488,788✔
2705
    parse_changeset(remote_in, parsed_changeset); // Throws
488,788✔
2706

244,444✔
2707
    parsed_changeset.version = remote_changeset.remote_version;
488,788✔
2708
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
488,788✔
2709
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
488,788✔
2710
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
488,788✔
2711
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
488,788✔
2712
}
488,788✔
2713

2714
} // namespace sync
2715
} // namespace realm
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