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

realm / realm-core / thomas.goyne_420

20 Jun 2024 08:37PM UTC coverage: 90.964% (-0.002%) from 90.966%
thomas.goyne_420

Pull #7796

Evergreen

tgoyne
Derive upload completion entirely from the state of the history

Rather than tracking a bunch of derived state in-memory, check for upload
completion by checking if there are any unuploaded changesets. This is both
multiprocess-compatible and is more precise than the old checks, which had some
false-negatives.
Pull Request #7796: RCORE-2160 Make upload completion reporting multiprocess-compatible

102120 of 180324 branches covered (56.63%)

112 of 117 new or added lines in 8 files covered. (95.73%)

46 existing lines in 16 files now uncovered.

214645 of 235966 relevant lines covered (90.96%)

5898967.27 hits per line

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

60.73
/src/realm/sync/transform.cpp
1
#include <realm/sync/transform.hpp>
2

3
#include <realm/sync/changeset_encoder.hpp>
4
#include <realm/sync/noinst/changeset_index.hpp>
5
#include <realm/sync/noinst/protocol_codec.hpp>
6

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

13
using namespace realm;
14
using namespace realm::sync;
15
using namespace realm::util;
16

17
namespace {
18

19
#if REALM_DEBUG
20
#if defined(_MSC_VER)
21
#define TERM_RED ""
22
#define TERM_YELLOW ""
23
#define TERM_CYAN ""
24
#define TERM_MAGENTA ""
25
#define TERM_GREEN ""
26
#define TERM_BOLD ""
27
#define TERM_RESET ""
28
#else
29
#define TERM_RED "\x1b[31;22m"
×
30
#define TERM_YELLOW "\x1b[33;22m"
×
31
#define TERM_CYAN "\x1b[36;22m"
×
32
#define TERM_MAGENTA "\x1b[35;22m"
×
33
#define TERM_GREEN "\x1b[32;22m"
34
#define TERM_BOLD "\x1b[1m"
35
#define TERM_RESET "\x1b[39;49;22m"
×
36
#endif
37
#endif
38

39
struct Discriminant {
40
    timestamp_type timestamp;
41
    file_ident_type client_file_ident;
42
    Discriminant(timestamp_type t, file_ident_type p)
43
        : timestamp(t)
463,366✔
44
        , client_file_ident(p)
463,366✔
45
    {
1,019,252✔
46
    }
1,019,252✔
47

48
    Discriminant(const Discriminant&) = default;
49
    Discriminant& operator=(const Discriminant&) = default;
50

51
    bool operator<(const Discriminant& other) const
52
    {
10,948✔
53
        return timestamp == other.timestamp ? (client_file_ident < other.client_file_ident)
10,948✔
54
                                            : timestamp < other.timestamp;
10,948✔
55
    }
10,948✔
56

57
    bool operator==(const Discriminant& other) const
58
    {
×
59
        return timestamp == other.timestamp && client_file_ident == other.client_file_ident;
×
60
    }
×
61
    bool operator!=(const Discriminant& other) const
62
    {
×
63
        return !((*this) == other);
×
64
    }
×
65
};
66

67
struct TransformerImpl;
68

69
struct Side {
70
    TransformerImpl& m_transformer;
71
    Changeset* m_changeset = nullptr;
72
    Discriminant m_discriminant;
73

74
    bool was_discarded = false;
75
    bool was_replaced = false;
76
    size_t m_path_len = 0;
77

78
    Side(TransformerImpl& transformer)
79
        : m_transformer(transformer)
44,102✔
80
        , m_discriminant(0, 0)
44,102✔
81
    {
98,454✔
82
    }
98,454✔
83

84
    virtual void skip_tombstones() noexcept = 0;
85
    virtual void next_instruction() noexcept = 0;
86
    virtual Instruction& get() noexcept = 0;
87

88
    void substitute(const Instruction& instr)
89
    {
×
90
        was_replaced = true;
×
91
        get() = instr;
×
92
    }
×
93

94
    StringData get_string(StringBufferRange range) const
95
    {
32✔
96
        // Relying on the transaction parser to only provide valid StringBufferRanges.
97
        return m_changeset->get_string(range);
32✔
98
    }
32✔
99

100
    StringData get_string(InternString intern_string) const
101
    {
26,558✔
102
        // Rely on the parser having checked the consistency of the interned strings
103
        return m_changeset->get_string(intern_string);
26,558✔
104
    }
26,558✔
105

106
    InternString intern_string(StringData data) const
107
    {
×
108
        return m_changeset->intern_string(data);
×
109
    }
×
110

111
    const Discriminant& timestamp() const
112
    {
21,892✔
113
        return m_discriminant;
21,892✔
114
    }
21,892✔
115

116
    InternString adopt_string(const Side& other_side, InternString other_string)
117
    {
×
118
        // FIXME: This needs to change if we choose to compare strings through a
119
        // mapping of InternStrings.
120
        StringData string = other_side.get_string(other_string);
×
121
        return intern_string(string);
×
122
    }
×
123

124
protected:
125
    void init_with_instruction(const Instruction& instr) noexcept
126
    {
920,774✔
127
        was_discarded = false;
920,774✔
128
        was_replaced = false;
920,774✔
129
        m_path_len = instr.path_length();
920,774✔
130
    }
920,774✔
131
};
132

133
struct MajorSide : Side {
134
    MajorSide(TransformerImpl& transformer)
135
        : Side(transformer)
22,056✔
136
    {
49,232✔
137
    }
49,232✔
138

139
    void set_next_changeset(Changeset* changeset) noexcept;
140
    void discard();
141
    void prepend(Instruction operation);
142
    template <class InputIterator>
143
    void prepend(InputIterator begin, InputIterator end);
144

145
    void init_with_instruction(Changeset::iterator position) noexcept
146
    {
632,072✔
147
        REALM_ASSERT(position >= m_changeset->begin());
632,072✔
148
        REALM_ASSERT(position != m_changeset->end());
632,072✔
149
        m_position = position;
632,072✔
150
        skip_tombstones();
632,072✔
151
        REALM_ASSERT(position != m_changeset->end());
632,072✔
152

153
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
632,072✔
154

155
        Side::init_with_instruction(get());
632,072✔
156
    }
632,072✔
157

158
    void skip_tombstones() noexcept final
159
    {
2,456,896✔
160
        while (m_position != m_changeset->end() && !*m_position) {
2,462,744✔
161
            ++m_position;
5,848✔
162
        }
5,848✔
163
    }
2,456,896✔
164

165
    void next_instruction() noexcept final
166
    {
610,322✔
167
        REALM_ASSERT(m_position != m_changeset->end());
610,322✔
168
        do {
610,898✔
169
            ++m_position;
610,898✔
170
        } while (m_position != m_changeset->end() && !*m_position);
610,898✔
171
    }
610,322✔
172

173
    Instruction& get() noexcept final
174
    {
3,305,846✔
175
        return **m_position;
3,305,846✔
176
    }
3,305,846✔
177

178
    size_t get_object_ids_in_current_instruction(_impl::ChangesetIndex::GlobalID* ids, size_t max_ids)
179
    {
578,358✔
180
        return _impl::get_object_ids_in_instruction(*m_changeset, get(), ids, max_ids);
578,358✔
181
    }
578,358✔
182

183
    Changeset::iterator m_position;
184
};
185

186
struct MinorSide : Side {
187
    using Position = _impl::ChangesetIndex::RangeIterator;
188

189
    MinorSide(TransformerImpl& transformer)
190
        : Side(transformer)
22,050✔
191
    {
49,226✔
192
    }
49,226✔
193

194
    void discard();
195
    void prepend(Instruction operation);
196
    template <class InputIterator>
197
    void prepend(InputIterator begin, InputIterator end);
198

199
    void substitute(const Instruction& instr)
200
    {
×
201
        was_replaced = true;
×
202
        get() = instr;
×
203
    }
×
204

205
    Position begin() noexcept
206
    {
632,158✔
207
        return Position{m_conflict_ranges};
632,158✔
208
    }
632,158✔
209

210
    Position end() noexcept
211
    {
5,043,306✔
212
        return Position{m_conflict_ranges, Position::end_tag{}};
5,043,306✔
213
    }
5,043,306✔
214

215
    void update_changeset_pointer() noexcept
216
    {
1,290,634✔
217
        if (REALM_LIKELY(m_position != end())) {
1,290,634✔
218
            m_changeset = m_position.m_outer->first;
477,402✔
219
        }
477,402✔
220
        else {
813,232✔
221
            m_changeset = nullptr;
813,232✔
222
        }
813,232✔
223
    }
1,290,634✔
224

225
    void skip_tombstones() noexcept final
226
    {
1,448,278✔
227
        if (m_position != end() && *m_position)
1,448,278✔
228
            return;
727,688✔
229
        skip_tombstones_slow();
720,590✔
230
    }
720,590✔
231

232
    REALM_NOINLINE void skip_tombstones_slow() noexcept
233
    {
720,560✔
234
        while (m_position != end() && !*m_position) {
835,852✔
235
            ++m_position;
115,292✔
236
        }
115,292✔
237
        update_changeset_pointer();
720,560✔
238
    }
720,560✔
239

240
    void next_instruction() noexcept final
241
    {
260,308✔
242
        REALM_ASSERT(m_position != end());
260,308✔
243
        ++m_position;
260,308✔
244
        update_changeset_pointer();
260,308✔
245
        skip_tombstones();
260,308✔
246
    }
260,308✔
247

248
    Instruction& get() noexcept final
249
    {
1,753,834✔
250
        Instruction* instr = *m_position;
1,753,834✔
251
        REALM_ASSERT(instr != nullptr);
1,753,834✔
252
        return *instr;
1,753,834✔
253
    }
1,753,834✔
254

255
    void init_with_instruction(Position position)
256
    {
288,842✔
257
        // REALM_ASSERT(position >= Position(m_conflict_ranges));
258
        REALM_ASSERT(position != end());
288,842✔
259
        m_position = position;
288,842✔
260
        update_changeset_pointer();
288,842✔
261
        skip_tombstones();
288,842✔
262
        REALM_ASSERT(position != end());
288,842✔
263

264
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
288,842✔
265

266
        Side::init_with_instruction(get());
288,842✔
267
    }
288,842✔
268

269
    _impl::ChangesetIndex::RangeIterator m_position;
270
    _impl::ChangesetIndex* m_changeset_index = nullptr;
271
    _impl::ChangesetIndex::Ranges* m_conflict_ranges = nullptr;
272
};
273

274
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug utilities
275

276
struct MergeTracer {
277
public:
278
    Side& m_minor;
279
    Side& m_major;
280
    const Changeset& m_minor_log;
281
    const Changeset& m_major_log;
282
    Instruction minor_before;
283
    Instruction major_before;
284

285
    // field => pair(original_value, change)
286
    struct Diff {
287
        std::map<std::string, std::pair<int64_t, int64_t>> numbers;
288
        std::map<std::string, std::pair<std::string, std::string>> strings;
289

290
        bool empty() const noexcept
291
        {
×
292
            return numbers.empty() && strings.empty();
×
293
        }
×
294
    };
295

296
    explicit MergeTracer(Side& minor, Side& major)
297
        : m_minor(minor)
298
        , m_major(major)
299
        , m_minor_log(*minor.m_changeset)
300
        , m_major_log(*major.m_changeset)
301
        , minor_before(minor.get())
302
        , major_before(major.get())
303
    {
×
304
    }
×
305

306
    struct FieldTracer : sync::Changeset::Reflector::Tracer {
307
        std::string m_name;
308
        std::map<std::string, std::string, std::less<>> m_fields;
309

310
        const Changeset* m_changeset = nullptr;
311

312
        void set_changeset(const Changeset* changeset) override
313
        {
×
314
            m_changeset = changeset;
×
315
        }
×
316

317
        StringData get_string(InternString str)
318
        {
×
319
            return m_changeset->get_string(str);
×
320
        }
×
321

322
        void name(StringData n) override
323
        {
×
324
            m_name = n;
×
325
        }
×
326

327
        void path(StringData n, InternString table, const Instruction::PrimaryKey& pk,
328
                  util::Optional<InternString> field, const Instruction::Path* path) override
329
        {
×
330
            std::stringstream ss;
×
331
            m_changeset->print_path(ss, table, pk, field, path);
×
332
            m_fields.emplace(n, ss.str());
×
333
        }
×
334

335
        void field(StringData n, InternString str) override
336
        {
×
337
            m_fields.emplace(n, get_string(str));
×
338
        }
×
339

340
        void field(StringData n, Instruction::Payload::Type type) override
341
        {
×
342
            m_fields.emplace(n, get_type_name(type));
×
343
        }
×
344

345
        void field(StringData n, Instruction::CollectionType type) override
346
        {
×
347
            m_fields.emplace(n, get_collection_type(type));
×
348
        }
×
349

350
        void field(StringData n, const Instruction::PrimaryKey& key) override
351
        {
×
352
            auto real_key = m_changeset->get_key(key);
×
353
            std::stringstream ss;
×
354
            ss << format_pk(real_key);
×
355
            m_fields.emplace(n, ss.str());
×
356
        }
×
357

358
        void field(StringData n, const Instruction::Payload& value) override
359
        {
×
360
            std::stringstream ss;
×
361
            m_changeset->print_value(ss, value);
×
362
            m_fields.emplace(n, ss.str());
×
363
        }
×
364

365
        void field(StringData n, const Instruction::Path& value) override
366
        {
×
367
            std::stringstream ss;
×
368
            m_changeset->print_path(ss, value);
×
369
            m_fields.emplace(n, ss.str());
×
370
        }
×
371

372
        void field(StringData n, uint32_t value) override
373
        {
×
374
            std::stringstream ss;
×
375
            ss << value;
×
376
            m_fields.emplace(n, ss.str());
×
377
        }
×
378
    };
379

380
    struct PrintDiffTracer : sync::Changeset::Reflector::Tracer {
381
        std::ostream& m_os;
382
        const FieldTracer& m_before;
383
        bool m_first = true;
384
        const Changeset* m_changeset = nullptr;
385

386
        PrintDiffTracer(std::ostream& os, const FieldTracer& before)
387
            : m_os(os)
388
            , m_before(before)
389
        {
×
390
        }
×
391

392
        void set_changeset(const Changeset* changeset) override
393
        {
×
394
            m_changeset = changeset;
×
395
        }
×
396

397
        StringData get_string(InternString str) const noexcept
398
        {
×
399
            return m_changeset->get_string(str);
×
400
        }
×
401

402
        void name(StringData n) override
403
        {
×
404
            m_os << std::left << std::setw(16) << std::string(n);
×
405
        }
×
406

407
        void path(StringData n, InternString table, const Instruction::PrimaryKey& pk,
408
                  util::Optional<InternString> field, const Instruction::Path* path) override
409
        {
×
410
            std::stringstream ss;
×
411
            m_changeset->print_path(ss, table, pk, field, path);
×
412
            diff_field(n, ss.str());
×
413
        }
×
414

415
        void field(StringData n, InternString str) override
416
        {
×
417
            diff_field(n, get_string(str));
×
418
        }
×
419

420
        void field(StringData n, Instruction::Payload::Type type) override
421
        {
×
422
            diff_field(n, get_type_name(type));
×
423
        }
×
424

425
        void field(StringData n, Instruction::CollectionType type) override
426
        {
×
427
            diff_field(n, get_collection_type(type));
×
428
        }
×
429

430
        void field(StringData n, const Instruction::PrimaryKey& value) override
431
        {
×
432
            std::stringstream ss;
×
433
            ss << format_pk(m_changeset->get_key(value));
×
434
            diff_field(n, ss.str());
×
435
        }
×
436

437
        void field(StringData n, const Instruction::Payload& value) override
438
        {
×
439
            std::stringstream ss;
×
440
            m_changeset->print_value(ss, value);
×
441
            diff_field(n, ss.str());
×
442
        }
×
443

444
        void field(StringData n, const Instruction::Path& value) override
445
        {
×
446
            std::stringstream ss;
×
447
            m_changeset->print_path(ss, value);
×
448
            diff_field(n, ss.str());
×
449
        }
×
450

451
        void field(StringData n, uint32_t value) override
452
        {
×
453
            std::stringstream ss;
×
454
            ss << value;
×
455
            diff_field(n, ss.str());
×
456
        }
×
457

458
        void diff_field(StringData name, std::string value)
459
        {
×
460
            std::stringstream ss;
×
461
            ss << name << "=";
×
462
            auto it = m_before.m_fields.find(name);
×
463
            if (it == m_before.m_fields.end() || it->second == value) {
×
464
                ss << value;
×
465
            }
×
466
            else {
×
467
                ss << it->second << "->" << value;
×
468
            }
×
469
            if (!m_first) {
×
470
                m_os << ", ";
×
471
            }
×
472
            m_os << ss.str();
×
473
            m_first = false;
×
474
        }
×
475
    };
476

477
    static void print_instr(std::ostream& os, const Instruction& instr, const Changeset& changeset)
478
    {
×
479
        Changeset::Printer printer{os};
×
480
        Changeset::Reflector reflector{printer, changeset};
×
481
        instr.visit(reflector);
×
482
    }
×
483

484
    bool print_diff(std::ostream& os, bool print_unmodified, const Instruction& before, const Changeset& before_log,
485
                    Side& side) const
486
    {
×
487
        if (side.was_discarded) {
×
488
            print_instr(os, before, before_log);
×
489
            os << " (DISCARDED)";
×
490
        }
×
491
        else if (side.was_replaced) {
×
492
            print_instr(os, before, before_log);
×
493
            os << " (REPLACED)";
×
494
        }
×
495
        else {
×
496
            Instruction after = side.get();
×
497
            if (print_unmodified || (before != after)) {
×
498
                FieldTracer before_tracer;
×
499
                before_tracer.set_changeset(&before_log);
×
500
                PrintDiffTracer after_tracer{os, before_tracer};
×
501
                Changeset::Reflector before_reflector{before_tracer, *side.m_changeset};
×
502
                Changeset::Reflector after_reflector{after_tracer, *side.m_changeset};
×
503
                before.visit(before_reflector);
×
504
                after.visit(after_reflector); // This prints the diff'ed instruction
×
505
            }
×
506
            else {
×
507
                os << "(=)";
×
508
            }
×
509
        }
×
510
        return true;
×
511
    }
×
512

513
    void print_diff(std::ostream& os, bool print_unmodified) const
514
    {
×
515
        bool must_print_minor = m_minor.was_discarded || m_minor.was_replaced;
×
516
        if (!must_print_minor) {
×
517
            Instruction minor_after = m_minor.get();
×
518
            must_print_minor = (minor_before != minor_after);
×
519
        }
×
520
        bool must_print_major = m_major.was_discarded || m_major.was_replaced;
×
521
        if (!must_print_major) {
×
522
            Instruction major_after = m_major.get();
×
523
            must_print_major = (major_before != major_after);
×
524
        }
×
525
        bool must_print = (print_unmodified || must_print_minor || must_print_major);
×
526
        if (must_print) {
×
527
            std::stringstream ss_minor;
×
528
            std::stringstream ss_major;
×
529

530
            print_diff(ss_minor, true, minor_before, m_minor_log, m_minor);
×
531
            print_diff(ss_major, print_unmodified, major_before, m_major_log, m_major);
×
532

533
            os << std::left << std::setw(80) << ss_minor.str();
×
534
            os << ss_major.str() << "\n";
×
535
        }
×
536
    }
×
537

538
    void pad_or_ellipsis(std::ostream& os, const std::string& str, int width) const
539
    {
×
540
        // FIXME: Does not work with UTF-8.
×
541
        if (str.size() > size_t(width)) {
×
542
            os << str.substr(0, width - 1) << "~";
×
543
        }
×
544
        else {
×
545
            os << std::left << std::setw(width) << str;
×
546
        }
×
547
    }
×
548
};
549
#endif // LCOV_EXCL_STOP REALM_DEBUG
550

551
struct TransformerImpl {
552
    MajorSide m_major_side;
553
    MinorSide m_minor_side;
554
    MinorSide::Position m_minor_end;
555
    bool m_trace;
556

557
    TransformerImpl(bool trace)
558
        : m_major_side{*this}
22,056✔
559
        , m_minor_side{*this}
22,056✔
560
        , m_trace{trace}
22,056✔
561
    {
49,232✔
562
    }
49,232✔
563

564
    void transform()
565
    {
596,766✔
566
        m_major_side.m_position = m_major_side.m_changeset->begin();
596,766✔
567
        m_major_side.skip_tombstones();
596,766✔
568

569
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
1,228,830✔
570
            m_major_side.init_with_instruction(m_major_side.m_position);
632,064✔
571

572
            set_conflict_ranges();
632,064✔
573
            m_minor_end = m_minor_side.end();
632,064✔
574
            m_minor_side.m_position = m_minor_side.begin();
632,064✔
575
            transform_major();
632,064✔
576

577
            if (!m_major_side.was_discarded)
632,064✔
578
                // Discarding the instruction moves to the next one.
579
                m_major_side.next_instruction();
610,338✔
580
            m_major_side.skip_tombstones();
632,064✔
581
        }
632,064✔
582
    }
596,766✔
583

584
    _impl::ChangesetIndex::Ranges* get_conflict_ranges_for_instruction(const Instruction& instr)
585
    {
631,998✔
586
        _impl::ChangesetIndex& index = *m_minor_side.m_changeset_index;
631,998✔
587

588
        if (_impl::is_schema_change(instr)) {
631,998✔
589
            ///
590
            /// CONFLICT GROUP: Everything touching that class
591
            ///
592
            auto ranges = index.get_everything();
53,736✔
593
            if (!ranges->empty()) {
53,736✔
594
#if REALM_DEBUG // LCOV_EXCL_START
595
                if (m_trace) {
40,996✔
596
                    std::cerr << TERM_RED << "Conflict group: Everything (due to schema change)\n" << TERM_RESET;
×
597
                }
×
598
#endif // REALM_DEBUG LCOV_EXCL_STOP
40,996✔
599
            }
40,996✔
600
            return ranges;
53,736✔
601
        }
53,736✔
602
        else {
578,262✔
603
            ///
604
            /// CONFLICT GROUP: Everything touching the involved objects,
605
            /// including schema changes.
606
            ///
607
            _impl::ChangesetIndex::GlobalID major_ids[2];
578,262✔
608
            size_t num_major_ids = m_major_side.get_object_ids_in_current_instruction(major_ids, 2);
578,262✔
609
            REALM_ASSERT(num_major_ids <= 2);
578,262✔
610
            REALM_ASSERT(num_major_ids >= 1);
578,262✔
611
#if REALM_DEBUG // LCOV_EXCL_START
612
            if (m_trace) {
578,262✔
613
                std::cerr << TERM_RED << "Conflict group: ";
×
614
                if (num_major_ids == 0) {
×
615
                    std::cerr << "(nothing - no object references)";
×
616
                }
×
617
                for (size_t i = 0; i < num_major_ids; ++i) {
×
618
                    std::cerr << major_ids[i].table_name << "[" << format_pk(major_ids[i].object_id) << "]";
×
619
                    if (i + 1 != num_major_ids)
×
620
                        std::cerr << ", ";
×
621
                }
×
622
                std::cerr << "\n" << TERM_RESET;
×
623
            }
×
624
#endif // REALM_DEBUG LCOV_EXCL_STOP
578,262✔
625
            auto ranges = index.get_modifications_for_object(major_ids[0]);
578,262✔
626
            if (num_major_ids == 2) {
578,262✔
627
                // Check that the index has correctly joined the ranges for the
628
                // two object IDs.
629
                REALM_ASSERT(ranges == index.get_modifications_for_object(major_ids[1]));
148✔
630
            }
148✔
631
            return ranges;
578,262✔
632
        }
578,262✔
633
    }
631,998✔
634

635
    void set_conflict_ranges()
636
    {
631,964✔
637
        const auto& major_instr = m_major_side.get();
631,964✔
638
        m_minor_side.m_conflict_ranges = get_conflict_ranges_for_instruction(major_instr);
631,964✔
639
        /* m_minor_side.m_changeset_index->verify(); */
640
    }
631,964✔
641

642
    void set_next_major_changeset(Changeset* changeset) noexcept
643
    {
596,812✔
644
        m_major_side.m_changeset = changeset;
596,812✔
645
        m_major_side.m_position = changeset->begin();
596,812✔
646
        m_major_side.skip_tombstones();
596,812✔
647
    }
596,812✔
648

649
    void discard_major()
650
    {
21,700✔
651
        m_major_side.m_position = m_major_side.m_changeset->erase_stable(m_major_side.m_position);
21,700✔
652
        m_major_side.was_discarded = true; // This terminates the loop in transform_major();
21,700✔
653
        m_major_side.m_changeset->set_dirty(true);
21,700✔
654
    }
21,700✔
655

656
    void discard_minor()
657
    {
21,006✔
658
        m_minor_side.was_discarded = true;
21,006✔
659
        m_minor_side.m_position = m_minor_side.m_changeset_index->erase_instruction(m_minor_side.m_position);
21,006✔
660
        m_minor_side.m_changeset->set_dirty(true);
21,006✔
661
        m_minor_side.update_changeset_pointer();
21,006✔
662
    }
21,006✔
663

664
    template <class InputIterator>
665
    void prepend_major(InputIterator instr_begin, InputIterator instr_end)
666
    {
×
667
        REALM_ASSERT(*m_major_side.m_position); // cannot prepend a tombstone
×
668
        auto insert_position = m_major_side.m_position;
×
669
        m_major_side.m_position = m_major_side.m_changeset->insert_stable(insert_position, instr_begin, instr_end);
×
670
        m_major_side.m_changeset->set_dirty(true);
×
671
        size_t num_prepended = instr_end - instr_begin;
×
672
        transform_prepended_major(num_prepended);
×
673
    }
×
674

675
    void prepend_major(Instruction instr)
676
    {
×
677
        const Instruction* begin = &instr;
×
678
        const Instruction* end = begin + 1;
×
679
        prepend_major(begin, end);
×
680
    }
×
681

682
    template <class InputIterator>
683
    void prepend_minor(InputIterator instr_begin, InputIterator instr_end)
684
    {
×
685
        REALM_ASSERT(*m_minor_side.m_position); // cannot prepend a tombstone
×
686
        auto insert_position = m_minor_side.m_position.m_pos;
×
687
        m_minor_side.m_position.m_pos =
×
688
            m_minor_side.m_changeset->insert_stable(insert_position, instr_begin, instr_end);
×
689
        m_minor_side.m_changeset->set_dirty(true);
×
690
        size_t num_prepended = instr_end - instr_begin;
×
691
        // Go back to the instruction that initiated this prepend
692
        for (size_t i = 0; i < num_prepended; ++i) {
×
693
            ++m_minor_side.m_position;
×
694
        }
×
695
        REALM_ASSERT(m_minor_end == m_minor_side.end());
×
696
    }
×
697

698
    void prepend_minor(Instruction instr)
699
    {
×
700
        const Instruction* begin = &instr;
×
701
        const Instruction* end = begin + 1;
×
702
        prepend_minor(begin, end);
×
703
    }
×
704

705
    void transform_prepended_major(size_t num_prepended)
706
    {
×
707
        auto orig_major_was_discarded = m_major_side.was_discarded;
×
708
        auto orig_major_path_len = m_major_side.m_path_len;
×
709

710
        // Reset 'was_discarded', as it should refer to the prepended
711
        // instructions in the below, not the instruction that instigated the
712
        // prepend.
713
        m_major_side.was_discarded = false;
×
714
        REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
715

716
#if defined(REALM_DEBUG) // LCOV_EXCL_START
×
717
        if (m_trace) {
×
718
            std::cerr << std::setw(80) << " ";
×
719
            MergeTracer::print_instr(std::cerr, m_major_side.get(), *m_major_side.m_changeset);
×
720
            std::cerr << " (PREPENDED " << num_prepended << ")\n";
×
721
        }
×
722
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
723

724
        for (size_t i = 0; i < num_prepended; ++i) {
×
725
            auto orig_minor_index = m_minor_side.m_position;
×
726
            auto orig_minor_was_discarded = m_minor_side.was_discarded;
×
727
            auto orig_minor_was_replaced = m_minor_side.was_replaced;
×
728
            auto orig_minor_path_len = m_minor_side.m_path_len;
×
729

730
            // Skip the instruction that initiated this prepend.
731
            if (!m_minor_side.was_discarded) {
×
732
                // Discarding an instruction moves to the next.
733
                m_minor_side.next_instruction();
×
734
            }
×
735

736
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
737
            m_major_side.init_with_instruction(m_major_side.m_position);
×
738
            REALM_ASSERT(!m_major_side.was_discarded);
×
739
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
740
            transform_major();
×
741
            if (!m_major_side.was_discarded) {
×
742
                // Discarding an instruction moves to the next.
743
                m_major_side.next_instruction();
×
744
            }
×
745
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
×
746

747
            m_minor_side.m_position = orig_minor_index;
×
748
            m_minor_side.was_discarded = orig_minor_was_discarded;
×
749
            m_minor_side.was_replaced = orig_minor_was_replaced;
×
750
            m_minor_side.m_path_len = orig_minor_path_len;
×
751
            m_minor_side.update_changeset_pointer();
×
752
        }
×
753

754
#if defined(REALM_DEBUG) // LCOV_EXCL_START
×
755
        if (m_trace) {
×
756
            std::cerr << TERM_CYAN << "(end transform of prepended major)\n" << TERM_RESET;
×
757
        }
×
758
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
759

760
        m_major_side.was_discarded = orig_major_was_discarded;
×
761
        m_major_side.m_path_len = orig_major_path_len;
×
762
    }
×
763

764
    void transform_major()
765
    {
632,154✔
766
        m_minor_side.skip_tombstones();
632,154✔
767

768
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
632,154✔
769
        const bool print_noop_merges = false;
632,154✔
770
        bool new_major = true; // print an instruction every time we go to the next major regardless
632,154✔
771
#endif                         // LCOV_EXCL_STOP REALM_DEBUG
632,154✔
772

773
        while (m_minor_side.m_position != m_minor_end) {
899,286✔
774
            m_minor_side.init_with_instruction(m_minor_side.m_position);
288,834✔
775

776
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
288,834✔
777
            if (m_trace) {
288,834✔
778
                MergeTracer tracer{m_minor_side, m_major_side};
×
779
                merge_instructions(m_major_side, m_minor_side);
×
780
                if (new_major)
×
781
                    std::cerr << TERM_CYAN << "\n(new major round)\n" << TERM_RESET;
×
782
                tracer.print_diff(std::cerr, new_major || print_noop_merges);
×
783
                new_major = false;
×
784
            }
×
785
            else {
288,834✔
786
#endif // LCOV_EXCL_STOP REALM_DEBUG
288,834✔
787
                merge_instructions(m_major_side, m_minor_side);
288,834✔
788
#if defined(REALM_DEBUG) // LCOV_EXCL_START
288,834✔
789
            }
288,834✔
790
#endif // LCOV_EXCL_STOP REALM_DEBUG
288,834✔
791

792
            if (m_major_side.was_discarded)
288,834✔
793
                break;
21,702✔
794
            if (!m_minor_side.was_discarded)
267,132✔
795
                // Discarding an instruction moves to the next one.
796
                m_minor_side.next_instruction();
260,308✔
797
            m_minor_side.skip_tombstones();
267,132✔
798
        }
267,132✔
799
    }
632,154✔
800

801
    void merge_instructions(MajorSide& left, MinorSide& right);
802
};
803

804
void MajorSide::set_next_changeset(Changeset* changeset) noexcept
805
{
596,812✔
806
    m_transformer.set_next_major_changeset(changeset);
596,812✔
807
}
596,812✔
808
void MajorSide::discard()
809
{
21,700✔
810
    m_transformer.discard_major();
21,700✔
811
}
21,700✔
812
void MajorSide::prepend(Instruction operation)
813
{
×
814
    m_transformer.prepend_major(std::move(operation));
×
815
}
×
816
template <class InputIterator>
817
void MajorSide::prepend(InputIterator begin, InputIterator end)
818
{
819
    m_transformer.prepend_major(std::move(begin), std::move(end));
820
}
821
void MinorSide::discard()
822
{
21,006✔
823
    m_transformer.discard_minor();
21,006✔
824
}
21,006✔
825
void MinorSide::prepend(Instruction operation)
826
{
×
827
    m_transformer.prepend_minor(std::move(operation));
×
828
}
×
829
template <class InputIterator>
830
void MinorSide::prepend(InputIterator begin, InputIterator end)
831
{
832
    m_transformer.prepend_minor(std::move(begin), std::move(end));
833
}
834

835
REALM_NORETURN void throw_bad_merge(std::string msg)
836
{
46✔
837
    throw sync::TransformError{std::move(msg)};
46✔
838
}
46✔
839

840
template <class... Params>
841
REALM_NORETURN void bad_merge(const char* msg, Params&&... params)
842
{
46✔
843
    throw_bad_merge(util::format(msg, std::forward<Params>(params)...));
46✔
844
}
46✔
845

846
REALM_NORETURN void bad_merge(Side& side, Instruction::PathInstruction instr, const std::string& msg)
847
{
×
848
    std::stringstream ss;
×
849
    side.m_changeset->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
850
    bad_merge("%1 (instruction target: %2). Please contact support.", msg, ss.str());
×
851
}
×
852

853
template <class LeftInstruction, class RightInstruction, class Enable = void>
854
struct Merge;
855
template <class Outer>
856
struct MergeNested;
857

858
struct MergeUtils {
859
    MergeUtils(Side& left_side, Side& right_side)
860
        : m_left_side(left_side)
41,512✔
861
        , m_right_side(right_side)
41,512✔
862
    {
80,718✔
863
    }
80,718✔
864

865
    // CAUTION: All of these utility functions rely implicitly on the "left" and
866
    // "right" arguments corresponding to the left and right sides. If the
867
    // arguments are switched, mayhem ensues.
868

869
    bool same_string(InternString left, InternString right) const noexcept
870
    {
110,494✔
871
        // FIXME: Optimize string comparison by building a map of InternString values up front.
872
        return m_left_side.m_changeset->get_string(left) == m_right_side.m_changeset->get_string(right);
110,494✔
873
    }
110,494✔
874

875
    bool same_key(const Instruction::PrimaryKey& left, const Instruction::PrimaryKey& right) const noexcept
876
    {
24,360✔
877
        // FIXME: Once we can compare string by InternString map lookups,
878
        // compare the string components of the keys using that.
879
        PrimaryKey left_key = m_left_side.m_changeset->get_key(left);
24,360✔
880
        PrimaryKey right_key = m_right_side.m_changeset->get_key(right);
24,360✔
881
        return left_key == right_key;
24,360✔
882
    }
24,360✔
883

884
    bool same_payload(const Instruction::Payload& left, const Instruction::Payload& right)
885
    {
216✔
886
        using Type = Instruction::Payload::Type;
216✔
887

888
        if (left.type != right.type)
216✔
889
            return false;
160✔
890

891
        switch (left.type) {
56✔
892
            case Type::Null:
✔
893
                return true;
×
894
            case Type::Erased:
✔
895
                return true;
×
896
            case Type::Set:
✔
897
                return true;
×
898
            case Type::List:
✔
899
                return true;
×
900
            case Type::Dictionary:
✔
901
                return true;
×
902
            case Type::ObjectValue:
✔
903
                return true;
×
904
            case Type::GlobalKey:
✔
905
                return left.data.key == right.data.key;
×
906
            case Type::Int:
24✔
907
                return left.data.integer == right.data.integer;
24✔
908
            case Type::Bool:
✔
909
                return left.data.boolean == right.data.boolean;
×
910
            case Type::String:
16✔
911
                return m_left_side.get_string(left.data.str) == m_right_side.get_string(right.data.str);
16✔
912
            case Type::Binary:
✔
913
                return m_left_side.get_string(left.data.binary) == m_right_side.get_string(right.data.binary);
×
914
            case Type::Timestamp:
✔
915
                return left.data.timestamp == right.data.timestamp;
×
916
            case Type::Float:
16✔
917
                return left.data.fnum == right.data.fnum;
16✔
918
            case Type::Double:
✔
919
                return left.data.dnum == right.data.dnum;
×
920
            case Type::Decimal:
✔
921
                return left.data.decimal == right.data.decimal;
×
922
            case Type::Link: {
✔
923
                if (!same_key(left.data.link.target, right.data.link.target)) {
×
924
                    return false;
×
925
                }
×
926
                auto left_target = m_left_side.get_string(left.data.link.target_table);
×
927
                auto right_target = m_right_side.get_string(right.data.link.target_table);
×
928
                return left_target == right_target;
×
929
            }
×
930
            case Type::ObjectId:
✔
931
                return left.data.object_id == right.data.object_id;
×
932
            case Type::UUID:
✔
933
                return left.data.uuid == right.data.uuid;
×
934
        }
56✔
935

936
        bad_merge("Invalid payload type in instruction");
×
937
    }
×
938

939
    bool same_path_element(const Instruction::Path::Element& left,
940
                           const Instruction::Path::Element& right) const noexcept
941
    {
1,700✔
942
        auto pred = util::overload{
1,700✔
943
            [](uint32_t lhs, uint32_t rhs) {
1,700✔
944
                return lhs == rhs;
564✔
945
            },
564✔
946
            [this](InternString lhs, InternString rhs) {
1,700✔
947
                return same_string(lhs, rhs);
1,136✔
948
            },
1,136✔
949
            // FIXME: Paths contain incompatible element types. Should we raise an error here?
950
            [](InternString, uint32_t) {
1,700✔
951
                return false;
×
952
            },
×
953
            [](uint32_t, InternString) {
1,700✔
954
                return false;
×
955
            },
×
956
        };
1,700✔
957
        return mpark::visit(pred, left, right);
1,700✔
958
    }
1,700✔
959

960
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
961
    {
11,032✔
962
        if (left.size() == right.size()) {
11,032✔
963
            for (size_t i = 0; i < left.size(); ++i) {
11,342✔
964
                if (!same_path_element(left[i], right[i])) {
792✔
965
                    return false;
256✔
966
                }
256✔
967
            }
792✔
968
            return true;
10,550✔
969
        }
10,806✔
970
        return false;
226✔
971
    }
11,032✔
972

973
    bool same_table(const Instruction::TableInstruction& left,
974
                    const Instruction::TableInstruction& right) const noexcept
975
    {
80,318✔
976
        return same_string(left.table, right.table);
80,318✔
977
    }
80,318✔
978

979
    bool same_object(const Instruction::ObjectInstruction& left,
980
                     const Instruction::ObjectInstruction& right) const noexcept
981
    {
53,062✔
982
        return same_table(left, right) && same_key(left.object, right.object);
53,062✔
983
    }
53,062✔
984

985
    template <class Left, class Right>
986
    bool same_column(const Left& left, const Right& right) const noexcept
987
    {
15,742✔
988
        if constexpr (std::is_convertible_v<const Right&, const Instruction::PathInstruction&>) {
15,742✔
989
            const Instruction::PathInstruction& rhs = right;
7,900✔
990
            return same_table(left, right) && same_string(left.field, rhs.field);
7,900!
991
        }
992
        else if constexpr (std::is_convertible_v<const Right&, const Instruction::ObjectInstruction&>) {
7,900✔
993
            // CreateObject/EraseObject do not have a column built in.
994
            return false;
7,900✔
995
        }
7,900✔
996
        else {
15,742✔
997
            return same_table(left, right) && same_string(left.field, right.field);
15,742!
998
        }
15,742✔
999
    }
15,742✔
1000

1001
    bool same_field(const Instruction::PathInstruction& left,
1002
                    const Instruction::PathInstruction& right) const noexcept
1003
    {
20,160✔
1004
        return same_object(left, right) && same_string(left.field, right.field);
20,160✔
1005
    }
20,160✔
1006

1007
    bool same_path(const Instruction::PathInstruction& left, const Instruction::PathInstruction& right) const noexcept
1008
    {
12,174✔
1009
        return same_field(left, right) && same_path(left.path, right.path);
12,174✔
1010
    }
12,174✔
1011

1012
    bool same_container(const Instruction::Path& left, const Instruction::Path& right) const noexcept
1013
    {
1,636✔
1014
        // The instructions refer to the same container if the paths have the
1015
        // same length, and elements [0..n-1] are equal (so the last element is
1016
        // disregarded). If the path length is 1, this counts as referring to
1017
        // the same container.
1018
        if (left.size() == right.size()) {
1,636✔
1019
            if (left.size() == 0)
1,280✔
1020
                return true;
×
1021

1022
            for (size_t i = 0; i < left.size() - 1; ++i) {
1,488✔
1023
                if (!same_path_element(left[i], right[i])) {
212✔
1024
                    return false;
4✔
1025
                }
4✔
1026
            }
212✔
1027
            return true;
1,276✔
1028
        }
1,280✔
1029
        return false;
356✔
1030
    }
1,636✔
1031

1032
    bool same_container(const Instruction::PathInstruction& left,
1033
                        const Instruction::PathInstruction& right) const noexcept
1034
    {
5,322✔
1035
        return same_field(left, right) && same_container(left.path, right.path);
5,322✔
1036
    }
5,322✔
1037

1038
    // NOTE: `is_prefix_of()` should only return true if the left is a strictly
1039
    // shorter path than the right, and the entire left path is the initial
1040
    // sequence of the right.
1041

1042
    bool is_prefix_of(const Instruction::ObjectInstruction&, const Instruction::TableInstruction&) const noexcept
1043
    {
×
1044
        // Right side is a schema instruction.
1045
        return false;
×
1046
    }
×
1047

1048
    bool is_prefix_of(const Instruction::ObjectInstruction& left,
1049
                      const Instruction::PathInstruction& right) const noexcept
1050
    {
8,388✔
1051
        return same_object(left, right);
8,388✔
1052
    }
8,388✔
1053

1054
    // Returns the next path element if the first path is a parent of the second path.
1055
    // Example:
1056
    //  * is_prefix_of(field1.123.field2, field1.123.field2.456) = 456
1057
    //  * is_prefix_of(field1.123.field2, field1.123.field3.456) = {}
1058

1059
    std::optional<Instruction::Path::Element> is_prefix_of(const Instruction::PathInstruction&,
1060
                                                           const Instruction::TableInstruction&) const noexcept
1061
    {
×
1062
        // Path instructions can never be full prefixes of table-level instructions. Note that this also covers
1063
        // ObjectInstructions.
1064
        return {};
×
1065
    }
×
1066

1067
    std::optional<Instruction::Path::Element> is_prefix_of(const Instruction::PathInstruction& left,
1068
                                                           const Instruction::PathInstruction& right) const noexcept
1069
    {
2,628✔
1070
        if (left.path.size() < right.path.size() && same_field(left, right)) {
2,628✔
1071
            for (size_t i = 0; i < left.path.size(); ++i) {
1,768✔
1072
                if (!same_path_element(left.path[i], right.path[i])) {
696✔
1073
                    return {};
32✔
1074
                }
32✔
1075
            }
696✔
1076
            return right.path[left.path.size()];
1,072✔
1077
        }
1,104✔
1078
        return {};
1,524✔
1079
    }
2,628✔
1080

1081
    // True if the left side is an instruction that touches a container within
1082
    // right's path. Equivalent to is_prefix_of, except the last element (the
1083
    // index) is not considered.
1084
    bool is_container_prefix_of(const Instruction::PathInstruction& left,
1085
                                const Instruction::PathInstruction& right) const
1086
    {
36✔
1087
        if (left.path.size() != 0 && left.path.size() < right.path.size() && same_field(left, right)) {
36✔
1088
            for (size_t i = 0; i < left.path.size() - 1; ++i) {
36✔
1089
                if (!same_path_element(left.path[i], right.path[i])) {
×
1090
                    return false;
×
1091
                }
×
1092
            }
×
1093
            return true;
36✔
1094
        }
36✔
1095
        return false;
×
1096
    }
36✔
1097

1098
    bool is_container_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const
1099
    {
×
1100
        return false;
×
1101
    }
×
1102

1103
    // When the left side has a shorter path, get the path component on the
1104
    // right that corresponds to the last component on the left.
1105
    //
1106
    // Note that this will only be used in the context of array indices, because
1107
    // those are the only path components that are modified during OT.
1108
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction& left,
1109
                                          Instruction::PathInstruction& right) const
1110
    {
36✔
1111
        REALM_ASSERT(left.path.size() != 0);
36✔
1112
        REALM_ASSERT(left.path.size() < right.path.size());
36✔
1113
        REALM_ASSERT(mpark::holds_alternative<uint32_t>(left.path.back()));
36✔
1114
        size_t index = left.path.size() - 1;
36✔
1115
        if (!mpark::holds_alternative<uint32_t>(right.path[index])) {
36✔
1116
            bad_merge("Inconsistent paths");
×
1117
        }
×
1118
        return mpark::get<uint32_t>(right.path[index]);
36✔
1119
    }
36✔
1120

1121
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction&,
1122
                                          const Instruction::TableInstruction&) const
1123
    {
×
1124
        // A path instruction can never have a shorter path than something that
1125
        // isn't a PathInstruction.
1126
        REALM_UNREACHABLE();
1127
    }
×
1128

1129
    void merge_get_vs_move(uint32_t& get_ndx, const uint32_t& move_from_ndx,
1130
                           const uint32_t& move_to_ndx) const noexcept
1131
    {
×
1132
        if (get_ndx == move_from_ndx) {
×
1133
            // CONFLICT: Update of a moved element.
1134
            //
1135
            // RESOLUTION: On the left side, use the MOVE operation to transform the
1136
            // UPDATE operation received from the right side.
1137
            get_ndx = move_to_ndx; // --->
×
1138
        }
×
1139
        else {
×
1140
            // Right update vs left remove
1141
            if (get_ndx > move_from_ndx) {
×
1142
                get_ndx -= 1; // --->
×
1143
            }
×
1144
            // Right update vs left insert
1145
            if (get_ndx >= move_to_ndx) {
×
1146
                get_ndx += 1; // --->
×
1147
            }
×
1148
        }
×
1149
    }
×
1150

1151
protected:
1152
    Side& m_left_side;
1153
    Side& m_right_side;
1154
};
1155

1156
template <class LeftInstruction, class RightInstruction>
1157
struct MergeBase : MergeUtils {
1158
    static const Instruction::Type A = Instruction::GetInstructionType<LeftInstruction>::value;
1159
    static const Instruction::Type B = Instruction::GetInstructionType<RightInstruction>::value;
1160
    static_assert(A >= B, "A < B. Please reverse the order of instruction types. :-)");
1161

1162
    MergeBase(Side& left_side, Side& right_side)
1163
        : MergeUtils(left_side, right_side)
32,272✔
1164
    {
62,798✔
1165
    }
62,798✔
1166
    MergeBase(MergeBase&&) = delete;
1167
};
1168

1169
#define DEFINE_MERGE(A, B)                                                                                           \
1170
    template <>                                                                                                      \
1171
    struct Merge<A, B> {                                                                                             \
1172
        template <class LeftSide, class RightSide>                                                                   \
1173
        struct DoMerge : MergeBase<A, B> {                                                                           \
1174
            A& left;                                                                                                 \
1175
            B& right;                                                                                                \
1176
            LeftSide& left_side;                                                                                     \
1177
            RightSide& right_side;                                                                                   \
1178
            DoMerge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                                   \
1179
                : MergeBase<A, B>(left_side, right_side)                                                             \
32,272✔
1180
                , left(left)                                                                                         \
32,272✔
1181
                , right(right)                                                                                       \
32,272✔
1182
                , left_side(left_side)                                                                               \
32,272✔
1183
                , right_side(right_side)                                                                             \
32,272✔
1184
            {                                                                                                        \
62,798✔
1185
            }                                                                                                        \
62,798✔
1186
            void do_merge();                                                                                         \
1187
        };                                                                                                           \
1188
        template <class LeftSide, class RightSide>                                                                   \
1189
        static inline void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                      \
1190
        {                                                                                                            \
62,798✔
1191
            DoMerge<LeftSide, RightSide> do_merge{left, right, left_side, right_side};                               \
62,798✔
1192
            do_merge.do_merge();                                                                                     \
62,798✔
1193
        }                                                                                                            \
62,798✔
1194
    };                                                                                                               \
1195
    template <class LeftSide, class RightSide>                                                                       \
1196
    void Merge<A, B>::DoMerge<LeftSide, RightSide>::do_merge()
1197

1198
#define DEFINE_MERGE_NOOP(A, B)                                                                                      \
1199
    template <>                                                                                                      \
1200
    struct Merge<A, B> {                                                                                             \
1201
        static const Instruction::Type left_type = Instruction::GetInstructionType<A>::value;                        \
1202
        static const Instruction::Type right_type = Instruction::GetInstructionType<B>::value;                       \
1203
        static_assert(left_type >= right_type,                                                                       \
1204
                      "left_type < right_type. Please reverse the order of instruction types. :-)");                 \
1205
        template <class LeftSide, class RightSide>                                                                   \
1206
        static inline void merge(A&, B&, LeftSide&, RightSide&)                                                      \
1207
        { /* Do nothing */                                                                                           \
223,016✔
1208
        }                                                                                                            \
223,016✔
1209
    }
1210

1211

1212
#define DEFINE_NESTED_MERGE(A)                                                                                       \
1213
    template <>                                                                                                      \
1214
    struct MergeNested<A> {                                                                                          \
1215
        template <class B, class OuterSide, class InnerSide>                                                         \
1216
        struct DoMerge : MergeUtils {                                                                                \
1217
            A& outer;                                                                                                \
1218
            B& inner;                                                                                                \
1219
            OuterSide& outer_side;                                                                                   \
1220
            InnerSide& inner_side;                                                                                   \
1221
            DoMerge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                                \
1222
                : MergeUtils(outer_side, inner_side)                                                                 \
9,240✔
1223
                , outer(outer)                                                                                       \
9,240✔
1224
                , inner(inner)                                                                                       \
9,240✔
1225
                , outer_side(outer_side)                                                                             \
9,240✔
1226
                , inner_side(inner_side)                                                                             \
9,240✔
1227
            {                                                                                                        \
17,920✔
1228
            }                                                                                                        \
17,920✔
1229
            void do_merge();                                                                                         \
1230
        };                                                                                                           \
1231
        template <class B, class OuterSide, class InnerSide>                                                         \
1232
        static inline void merge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                   \
1233
        {                                                                                                            \
17,920✔
1234
            DoMerge<B, OuterSide, InnerSide> do_merge{outer, inner, outer_side, inner_side};                         \
17,920✔
1235
            do_merge.do_merge();                                                                                     \
17,920✔
1236
        }                                                                                                            \
17,920✔
1237
    };                                                                                                               \
1238
    template <class B, class OuterSide, class InnerSide>                                                             \
1239
    void MergeNested<A>::DoMerge<B, OuterSide, InnerSide>::do_merge()
1240

1241
#define DEFINE_NESTED_MERGE_NOOP(A)                                                                                  \
1242
    template <>                                                                                                      \
1243
    struct MergeNested<A> {                                                                                          \
1244
        template <class B, class OuterSide, class InnerSide>                                                         \
1245
        static inline void merge(const A&, const B&, const OuterSide&, const InnerSide&)                             \
1246
        { /* Do nothing */                                                                                           \
28,672✔
1247
        }                                                                                                            \
28,672✔
1248
    }
1249

1250
// Implementation that reverses order.
1251
template <class A, class B>
1252
struct Merge<A, B,
1253
             typename std::enable_if<(Instruction::GetInstructionType<A>::value <
1254
                                      Instruction::GetInstructionType<B>::value)>::type> {
1255
    template <class LeftSide, class RightSide>
1256
    static void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)
1257
    {
39,648✔
1258
        Merge<B, A>::merge(right, left, right_side, left_side);
39,648✔
1259
    }
39,648✔
1260
};
1261

1262
///
1263
///  GET READY!
1264
///
1265
///  Realm supports 14 instructions at the time of this writing. Each
1266
///  instruction type needs one rule for each other instruction type. We only
1267
///  define one rule to handle each combination (A vs B and B vs A are handle by
1268
///  a single rule).
1269
///
1270
///  This gives (14 * (14 + 1)) / 2 = 105 merge rules below.
1271
///
1272
///  Merge rules are ordered such that the second instruction type is always of
1273
///  a lower enum value than the first.
1274
///
1275
///  Nested merge rules apply when one instruction has a strictly longer path
1276
///  than another. All instructions that have a path of the same length will
1277
///  meet each other through regular merge rules, regardless of whether they
1278
///  share a prefix.
1279
///
1280

1281

1282
/// AddTable rules
1283

1284
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1285

1286
DEFINE_MERGE(Instruction::AddTable, Instruction::AddTable)
1287
{
4,458✔
1288
    if (same_table(left, right)) {
4,458✔
1289
        StringData left_name = left_side.get_string(left.table);
4,126✔
1290
        if (auto left_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&left.type)) {
4,126✔
1291
            if (auto right_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&right.type)) {
3,890✔
1292
                StringData left_pk_name = left_side.get_string(left_spec->pk_field);
3,890✔
1293
                StringData right_pk_name = right_side.get_string(right_spec->pk_field);
3,890✔
1294
                if (left_pk_name != right_pk_name) {
3,890✔
1295
                    bad_merge(
6✔
1296
                        "Schema mismatch: '%1' has primary key '%2' on one side, but primary key '%3' on the other.",
6✔
1297
                        left_name, left_pk_name, right_pk_name);
6✔
1298
                }
6✔
1299

1300
                if (left_spec->pk_type != right_spec->pk_type) {
3,890✔
1301
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is of type %3 on one side and type "
8✔
1302
                              "%4 on the other.",
8✔
1303
                              left_name, left_pk_name, get_type_name(left_spec->pk_type),
8✔
1304
                              get_type_name(right_spec->pk_type));
8✔
1305
                }
8✔
1306

1307
                if (left_spec->pk_nullable != right_spec->pk_nullable) {
3,890✔
1308
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is nullable on one side, but not "
6✔
1309
                              "the other.",
6✔
1310
                              left_name, left_pk_name);
6✔
1311
                }
6✔
1312

1313
                if (left_spec->is_asymmetric != right_spec->is_asymmetric) {
3,890✔
1314
                    bad_merge("Schema mismatch: '%1' is asymmetric on one side, but not on the other.", left_name);
4✔
1315
                }
4✔
1316
            }
3,890✔
1317
            else {
×
1318
                bad_merge("Schema mismatch: '%1' has a primary key on one side, but not on the other.", left_name);
×
1319
            }
×
1320
        }
3,890✔
1321
        else if (mpark::get_if<Instruction::AddTable::EmbeddedTable>(&left.type)) {
236✔
1322
            if (!mpark::get_if<Instruction::AddTable::EmbeddedTable>(&right.type)) {
236✔
1323
                bad_merge("Schema mismatch: '%1' is an embedded table on one side, but not the other.", left_name);
×
1324
            }
×
1325
        }
236✔
1326

1327
        // Names are the same, PK presence is the same, and if there is a primary
1328
        // key, its name, type, and nullability are the same. Discard both sides.
1329
        left_side.discard();
4,126✔
1330
        right_side.discard();
4,126✔
1331
        return;
4,126✔
1332
    }
4,126✔
1333
}
4,458✔
1334

1335
DEFINE_MERGE(Instruction::EraseTable, Instruction::AddTable)
1336
{
192✔
1337
    if (same_table(left, right)) {
192✔
1338
        right_side.discard();
×
1339
    }
×
1340
}
192✔
1341

1342
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::AddTable);
1343
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::AddTable);
1344
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::AddTable);
1345
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddTable);
1346
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddTable);
1347
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddTable);
1348
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddTable);
1349
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddTable);
1350
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddTable);
1351
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddTable);
1352
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddTable);
1353
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddTable);
1354

1355
/// EraseTable rules
1356

1357
DEFINE_NESTED_MERGE(Instruction::EraseTable)
1358
{
6,812✔
1359
    if (same_table(outer, inner)) {
6,812!
1360
        inner_side.discard();
1,596✔
1361
    }
1,596✔
1362
}
6,812✔
1363

1364
DEFINE_MERGE(Instruction::EraseTable, Instruction::EraseTable)
1365
{
52✔
1366
    if (same_table(left, right)) {
52✔
1367
        left_side.discard();
8✔
1368
        right_side.discard();
8✔
1369
    }
8✔
1370
}
52✔
1371

1372
// Handled by nesting rule.
1373
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::EraseTable);
1374
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::EraseTable);
1375
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseTable);
1376
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseTable);
1377

1378
DEFINE_MERGE(Instruction::AddColumn, Instruction::EraseTable)
1379
{
344✔
1380
    // AddColumn on an erased table handled by nesting.
1381

1382
    if (left.type == Instruction::Payload::Type::Link && same_string(left.link_target_table, right.table)) {
344!
1383
        // Erase of a table where the left side adds a link column targeting it.
1384
        Instruction::EraseColumn erase_column;
×
1385
        erase_column.table = right_side.adopt_string(left_side, left.table);
×
1386
        erase_column.field = right_side.adopt_string(left_side, left.field);
×
1387
        right_side.prepend(erase_column);
×
1388
        left_side.discard();
×
1389
    }
×
1390
}
344✔
1391

1392
// Handled by nested rule.
1393
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseTable);
1394
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseTable);
1395
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseTable);
1396
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseTable);
1397
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseTable);
1398
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseTable);
1399
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseTable);
1400

1401

1402
/// CreateObject rules
1403

1404
// CreateObject cannot interfere with instructions that have a longer path.
1405
DEFINE_NESTED_MERGE_NOOP(Instruction::CreateObject);
1406

1407
// CreateObject is idempotent.
1408
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::CreateObject);
1409

1410
DEFINE_MERGE(Instruction::EraseObject, Instruction::CreateObject)
1411
{
18,434✔
1412
    if (same_object(left, right)) {
18,434✔
1413
        // CONFLICT: Create and Erase of the same object.
1414
        //
1415
        // RESOLUTION: Erase always wins.
1416
        right_side.discard();
840✔
1417
    }
840✔
1418
}
18,434✔
1419

1420
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::CreateObject);
1421
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::CreateObject);
1422
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::CreateObject);
1423
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::CreateObject);
1424
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::CreateObject);
1425
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::CreateObject);
1426
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::CreateObject);
1427
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::CreateObject);
1428
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::CreateObject);
1429
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::CreateObject);
1430

1431

1432
/// Erase rules
1433

1434
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1435
{
8,388✔
1436
    if (is_prefix_of(outer, inner)) {
8,388!
1437
        // Erase always wins.
1438
        inner_side.discard();
808✔
1439
    }
808✔
1440
}
8,388✔
1441

1442
DEFINE_MERGE(Instruction::EraseObject, Instruction::EraseObject)
1443
{
6,080✔
1444
    if (same_object(left, right)) {
6,080✔
1445
        // We keep the most recent erase. This prevents the situation where a
1446
        // high number of EraseObject instructions in the past trumps a
1447
        // Erase-Create pair in the future.
1448
        if (right_side.timestamp() < left_side.timestamp()) {
616✔
1449
            right_side.discard();
308✔
1450
        }
308✔
1451
        else {
308✔
1452
            left_side.discard();
308✔
1453
        }
308✔
1454
    }
616✔
1455
}
6,080✔
1456

1457
// Handled by nested merge.
1458
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseObject);
1459
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseObject);
1460
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::EraseObject);
1461
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseObject);
1462
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseObject);
1463
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseObject);
1464
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseObject);
1465
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseObject);
1466
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseObject);
1467
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseObject);
1468

1469

1470
/// Update rules
1471

1472
DEFINE_NESTED_MERGE(Instruction::Update)
1473
{
2,404✔
1474
    using Type = Instruction::Payload::Type;
2,404✔
1475

1476
    if (outer.value.type == Type::ObjectValue) {
2,404!
1477
        // Creating an embedded object is an idempotent operation, and should
1478
        // not eliminate updates to the subtree.
1479
        return;
64✔
1480
    }
64✔
1481

1482
    // Setting a value higher up in the hierarchy overwrites any modification to
1483
    // the inner value, regardless of when this happened.
1484
    if (auto next_element = is_prefix_of(outer, inner)) {
2,340!
1485
        //  If this is a collection in mixed, we will allow the inner instruction
1486
        //  to pass so long as it references the proper type (list or dictionary).
1487
        if (outer.value.type == Type::List && mpark::holds_alternative<uint32_t>(*next_element)) {
864!
1488
            return;
328✔
1489
        }
328✔
1490
        else if (outer.value.type == Type::Dictionary && mpark::holds_alternative<InternString>(*next_element)) {
536!
1491
            return;
128✔
1492
        }
128✔
1493
        inner_side.discard();
408✔
1494
    }
408✔
1495
}
2,340✔
1496

1497
DEFINE_MERGE(Instruction::Update, Instruction::Update)
1498
{
11,060✔
1499
    // The two instructions are at the same level of nesting.
1500

1501
    using Type = Instruction::Payload::Type;
11,060✔
1502

1503
    if (same_path(left, right)) {
11,060✔
1504
        bool left_is_default = false;
9,566✔
1505
        bool right_is_default = false;
9,566✔
1506
        if (!(left.is_array_update() == right.is_array_update())) {
9,566✔
1507
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
×
1508
        }
×
1509

1510
        if (!left.is_array_update()) {
9,566✔
1511
            if (right.is_array_update()) {
9,528✔
1512
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
×
1513
            }
×
1514
            left_is_default = left.is_default;
9,528✔
1515
            right_is_default = right.is_default;
9,528✔
1516
        }
9,528✔
1517
        else if (!(left.prior_size == right.prior_size)) {
38✔
1518
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1519
        }
×
1520

1521
        if (left.value.type != right.value.type) {
9,566✔
1522
            // Embedded object creation should always lose to an Update(value),
1523
            // because these structures are nested, and we need to discard any
1524
            // update inside the structure.
1525
            if (left.value.type == Type::ObjectValue) {
292✔
1526
                left_side.discard();
24✔
1527
                return;
24✔
1528
            }
24✔
1529
            else if (right.value.type == Type::ObjectValue) {
268✔
1530
                right_side.discard();
24✔
1531
                return;
24✔
1532
            }
24✔
1533
        }
292✔
1534

1535
        // Updates to List or Dictionary are idempotent. If both sides are setting to the same value,
1536
        // let them both pass through. It is important that the instruction application rules reflect this.
1537
        // If it is not two lists or dictionaries, then the normal last-writer-wins rules will take effect below.
1538
        if (left.value.type == right.value.type &&
9,518✔
1539
            (left.value.type == Type::List || left.value.type == Type::Dictionary)) {
9,518✔
1540
            return;
184✔
1541
        }
184✔
1542

1543
        // CONFLICT: Two updates of the same element.
1544
        //
1545
        // RESOLUTION: Suppress the effect of the UPDATE operation with the lower
1546
        // timestamp. Note that the timestamps can never be equal. This is
1547
        // achieved on both sides by discarding the received UPDATE operation if
1548
        // it has a lower timestamp than the previously applied UPDATE operation.
1549
        if (left_is_default == right_is_default) {
9,334✔
1550
            if (left_side.timestamp() < right_side.timestamp()) {
9,254✔
1551
                left_side.discard(); // --->
4,902✔
1552
            }
4,902✔
1553
            else {
4,352✔
1554
                right_side.discard(); // <---
4,352✔
1555
            }
4,352✔
1556
        }
9,254✔
1557
        else {
80✔
1558
            if (left_is_default) {
80✔
1559
                left_side.discard();
38✔
1560
            }
38✔
1561
            else {
42✔
1562
                right_side.discard();
42✔
1563
            }
42✔
1564
        }
80✔
1565
    }
9,334✔
1566
}
11,060✔
1567

1568
DEFINE_MERGE(Instruction::AddInteger, Instruction::Update)
1569
{
422✔
1570
    // The two instructions are at the same level of nesting.
1571

1572
    if (same_path(left, right)) {
422✔
1573
        // CONFLICT: Add vs Set of the same element.
1574
        //
1575
        // RESOLUTION: If the Add was later than the Set, add its value to
1576
        // the payload of the Set instruction. Otherwise, discard it.
1577

1578
        bool right_is_default = !right.is_array_update() && right.is_default;
352✔
1579

1580
        // Five Cases Here:
1581
        // 1. AddInteger is after Update and Update is of a non-integer type
1582
        //     - Discard the AddInteger; AddInteger to a mixed field is a no-op
1583
        // 2: AddInteger is after the Update and the Update instruction contains an integer payload:
1584
        //     - We increment the Update instruction payload
1585
        // 3: AddInteger is after Update and Update is null:
1586
        //     - No conflict
1587
        // 4: Update is after AddInteger and Update.default is false
1588
        //     - Discard the AddInteger
1589
        // 5: Update is after AddInteger and Update.default is true
1590
        //     - Treat the Update as if it were before the AddInteger instruction
1591

1592
        // Note: AddInteger survives SetDefault, regardless of timestamp.
1593
        if (right_side.timestamp() < left_side.timestamp() || right_is_default) {
352✔
1594
            if (right.value.is_null()) {
304✔
1595
                // The AddInteger happened "after" the Update(null). This becomes a
1596
                // no-op, but if the server later integrates a Update(int) that
1597
                // came-before the AddInteger, it will be taken into account again.
1598
                return;
56✔
1599
            }
56✔
1600

1601
            // The AddInteger happened after an Update with a non int type
1602
            // This must be operating on a mixed field. Discard the AddInteger
1603
            if (right.value.type != Instruction::Payload::Type::Int) {
248✔
1604
                left_side.discard();
32✔
1605
                return;
32✔
1606
            }
32✔
1607

1608
            // Wrapping add
1609
            uint64_t ua = uint64_t(right.value.data.integer);
216✔
1610
            uint64_t ub = uint64_t(left.value);
216✔
1611
            right.value.data.integer = int64_t(ua + ub);
216✔
1612
        }
216✔
1613
        else {
48✔
1614
            left_side.discard();
48✔
1615
        }
48✔
1616
    }
352✔
1617
}
422✔
1618

1619
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::Update);
1620

1621
DEFINE_MERGE(Instruction::EraseColumn, Instruction::Update)
1622
{
×
1623
    if (same_column(left, right)) {
×
1624
        right_side.discard();
×
1625
    }
×
1626
}
×
1627

1628
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::Update)
1629
{
2,350✔
1630
    if (same_container(left, right)) {
2,350✔
1631
        REALM_ASSERT(right.is_array_update());
264✔
1632
        if (!(left.prior_size == right.prior_size)) {
264✔
1633
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1634
        }
×
1635
        if (!(left.index() <= left.prior_size)) {
264✔
1636
            bad_merge(left_side, left, "Merge error: left.index() <= left.prior_size");
×
1637
        }
×
1638
        if (!(right.index() < right.prior_size)) {
264✔
1639
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1640
        }
×
1641
        right.prior_size += 1;
264✔
1642
        if (right.index() >= left.index()) {
264✔
1643
            right.index() += 1; // --->
100✔
1644
        }
100✔
1645
    }
264✔
1646
}
2,350✔
1647

1648
DEFINE_MERGE(Instruction::ArrayMove, Instruction::Update)
1649
{
24✔
1650
    if (same_container(left, right)) {
24✔
1651
        REALM_ASSERT(right.is_array_update());
×
1652

1653
        if (!(left.index() < left.prior_size)) {
×
1654
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1655
        }
×
1656
        if (!(right.index() < right.prior_size)) {
×
1657
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1658
        }
×
1659

1660
        // FIXME: This marks both sides as dirty, even when they are unmodified.
1661
        merge_get_vs_move(right.index(), left.index(), left.ndx_2);
×
1662
    }
×
1663
}
24✔
1664

1665
DEFINE_MERGE(Instruction::ArrayErase, Instruction::Update)
1666
{
384✔
1667
    if (same_container(left, right)) {
384✔
1668
        REALM_ASSERT(right.is_array_update());
52✔
1669
        if (!(left.prior_size == right.prior_size)) {
52✔
1670
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1671
        }
×
1672
        if (!(left.index() < left.prior_size)) {
52✔
1673
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1674
        }
×
1675
        if (!(right.index() < right.prior_size)) {
52✔
1676
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1677
        }
×
1678

1679
        // CONFLICT: Update of a removed element.
1680
        //
1681
        // RESOLUTION: Discard the UPDATE operation received on the right side.
1682
        right.prior_size -= 1;
52✔
1683

1684
        if (left.index() == right.index()) {
52✔
1685
            // CONFLICT: Update of a removed element.
1686
            //
1687
            // RESOLUTION: Discard the UPDATE operation received on the right side.
UNCOV
1688
            right_side.discard();
×
UNCOV
1689
        }
×
1690
        else if (right.index() > left.index()) {
52✔
1691
            right.index() -= 1;
16✔
1692
        }
16✔
1693
    }
52✔
1694
}
384✔
1695

1696
DEFINE_MERGE(Instruction::Clear, Instruction::Update)
1697
{
336✔
1698
    using Type = Instruction::Payload::Type;
336✔
1699
    using CollectionType = Instruction::CollectionType;
336✔
1700

1701
    // The two instructions are at the same level of nesting.
1702
    if (same_path(left, right)) {
336✔
1703
        REALM_ASSERT(right.value.type != Type::Set);
288✔
1704
        // If both sides are setting/operating on the same type, let them both pass through.
1705
        // It is important that the instruction application rules reflect this.
1706
        // If it is not two lists or dictionaries, then the normal last-writer-wins rules will take effect below.
1707
        if (left.collection_type == CollectionType::List && right.value.type == Type::List) {
288✔
1708
            return;
120✔
1709
        }
120✔
1710
        if (left.collection_type == CollectionType::Dictionary && right.value.type == Type::Dictionary) {
168✔
1711
            return;
16✔
1712
        }
16✔
1713

1714
        // CONFLICT: Clear and Update of the same element.
1715
        //
1716
        // RESOLUTION: Discard the instruction with the lower timestamp. This has the
1717
        // effect of preserving insertions that came after the clear (if it has the
1718
        // higher timestamp), or preserve additional updates (and potential insertions)
1719
        // that came after the update.
1720
        if (left_side.timestamp() < right_side.timestamp()) {
152✔
1721
            left_side.discard();
104✔
1722
        }
104✔
1723
        else {
48✔
1724
            right_side.discard();
48✔
1725
        }
48✔
1726
    }
152✔
1727
}
336✔
1728

1729
// Handled by nested rule
1730
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::Update);
1731
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::Update);
1732

1733

1734
/// AddInteger rules
1735

1736
DEFINE_NESTED_MERGE(Instruction::AddInteger)
1737
{
64✔
1738
    if (is_prefix_of(outer, inner)) {
64!
1739
        inner_side.discard();
×
1740
    }
×
1741
}
64✔
1742

1743
DEFINE_MERGE(Instruction::Clear, Instruction::AddInteger)
1744
{
16✔
1745
    // The two instructions are at the same level of nesting.
1746
    if (same_path(left, right)) {
16✔
1747
        right_side.discard();
16✔
1748
    }
16✔
1749
}
16✔
1750

1751
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddInteger);
1752
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddInteger);
1753
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddInteger);
1754
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddInteger);
1755
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddInteger);
1756
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddInteger);
1757
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddInteger);
1758
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddInteger);
1759

1760

1761
/// AddColumn rules
1762

1763
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1764

1765
DEFINE_MERGE(Instruction::AddColumn, Instruction::AddColumn)
1766
{
15,742✔
1767
    if (same_column(left, right)) {
15,742✔
1768
        StringData left_name = left_side.get_string(left.field);
10,134✔
1769
        if (left.type != right.type) {
10,134✔
1770
            bad_merge(
6✔
1771
                "Schema mismatch: Property '%1' in class '%2' is of type %3 on one side and type %4 on the other.",
6✔
1772
                left_name, left_side.get_string(left.table), get_type_name(left.type), get_type_name(right.type));
6✔
1773
        }
6✔
1774

1775
        if (left.nullable != right.nullable) {
10,134✔
1776
            bad_merge("Schema mismatch: Property '%1' in class '%2' is nullable on one side and not on the other.",
8✔
1777
                      left_name, left_side.get_string(left.table));
8✔
1778
        }
8✔
1779

1780
        if (left.collection_type != right.collection_type) {
10,134✔
1781
            auto collection_type_name = [](Instruction::CollectionType type) -> const char* {
×
1782
                switch (type) {
×
1783
                    case Instruction::CollectionType::Single:
×
1784
                        return "single value";
×
1785
                    case Instruction::CollectionType::List:
×
1786
                        return "list";
×
1787
                    case Instruction::CollectionType::Dictionary:
×
1788
                        return "dictionary";
×
1789
                    case Instruction::CollectionType::Set:
×
1790
                        return "set";
×
1791
                }
×
1792
                REALM_TERMINATE("");
1793
            };
×
1794

1795
            std::stringstream ss;
×
1796
            const char* left_type = collection_type_name(left.collection_type);
×
1797
            const char* right_type = collection_type_name(right.collection_type);
×
1798
            bad_merge("Schema mismatch: Property '%1' in class '%2' is a %3 on one side, and a %4 on the other.",
×
1799
                      left_name, left_side.get_string(left.table), left_type, right_type);
×
1800
        }
×
1801

1802
        if (left.type == Instruction::Payload::Type::Link) {
10,134✔
1803
            StringData left_target = left_side.get_string(left.link_target_table);
2,248✔
1804
            StringData right_target = right_side.get_string(right.link_target_table);
2,248✔
1805
            if (left_target != right_target) {
2,248✔
1806
                bad_merge("Schema mismatch: Link property '%1' in class '%2' points to class '%3' on one side and to "
8✔
1807
                          "'%4' on the other.",
8✔
1808
                          left_name, left_side.get_string(left.table), left_target, right_target);
8✔
1809
            }
8✔
1810
        }
2,248✔
1811

1812
        // Name, type, nullability and link targets match -- discard both
1813
        // sides and proceed.
1814
        left_side.discard();
10,134✔
1815
        right_side.discard();
10,134✔
1816
    }
10,134✔
1817
}
15,742✔
1818

1819
DEFINE_MERGE(Instruction::EraseColumn, Instruction::AddColumn)
1820
{
×
1821
    if (same_column(left, right)) {
×
1822
        right_side.discard();
×
1823
    }
×
1824
}
×
1825

1826
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddColumn);
1827
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddColumn);
1828
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddColumn);
1829
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddColumn);
1830
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddColumn);
1831
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddColumn);
1832

1833

1834
/// EraseColumn rules
1835

1836
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1837

1838
DEFINE_MERGE(Instruction::EraseColumn, Instruction::EraseColumn)
1839
{
×
1840
    if (same_column(left, right)) {
×
1841
        left_side.discard();
×
1842
        right_side.discard();
×
1843
    }
×
1844
}
×
1845

1846
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseColumn);
1847
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseColumn);
1848
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseColumn);
1849
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseColumn);
1850
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseColumn);
1851
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseColumn);
1852

1853
/// ArrayInsert rules
1854

1855
DEFINE_NESTED_MERGE(Instruction::ArrayInsert)
1856
{
28✔
1857
    if (is_container_prefix_of(outer, inner)) {
28!
1858
        auto& index = corresponding_index_in_path(outer, inner);
28✔
1859
        if (index >= outer.index()) {
28!
1860
            index += 1;
20✔
1861
        }
20✔
1862
    }
28✔
1863
}
28✔
1864

1865
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::ArrayInsert)
1866
{
1,844✔
1867
    if (same_container(left, right)) {
1,844✔
1868
        if (!(left.prior_size == right.prior_size)) {
724✔
1869
            bad_merge(right_side, right, "Exception: left.prior_size == right.prior_size");
×
1870
        }
×
1871
        left.prior_size++;
724✔
1872
        right.prior_size++;
724✔
1873

1874
        if (left.index() > right.index()) {
724✔
1875
            left.index() += 1; // --->
152✔
1876
        }
152✔
1877
        else if (left.index() < right.index()) {
572✔
1878
            right.index() += 1; // <---
148✔
1879
        }
148✔
1880
        else { // left index == right index
424✔
1881
            // CONFLICT: Two element insertions at the same position.
1882
            //
1883
            // Resolution: Place the inserted elements in order of increasing
1884
            // timestamp. Note that the timestamps can never be equal.
1885
            if (left_side.timestamp() < right_side.timestamp()) {
424✔
1886
                right.index() += 1;
216✔
1887
            }
216✔
1888
            else {
208✔
1889
                left.index() += 1;
208✔
1890
            }
208✔
1891
        }
424✔
1892
    }
724✔
1893
}
1,844✔
1894

1895
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayInsert)
1896
{
×
1897
    if (same_container(left, right)) {
×
1898
        left.prior_size += 1;
×
1899

1900
        // Left insertion vs right removal
1901
        if (right.index() > left.index()) {
×
1902
            right.index() -= 1; // --->
×
1903
        }
×
1904
        else {
×
1905
            left.index() += 1; // <---
×
1906
        }
×
1907

1908
        // Left insertion vs left insertion
1909
        if (right.index() < left.ndx_2) {
×
1910
            left.ndx_2 += 1; // <---
×
1911
        }
×
1912
        else if (right.index() > left.ndx_2) {
×
1913
            right.index() += 1; // --->
×
1914
        }
×
1915
        else { // right.index() == left.ndx_2
×
1916
            // CONFLICT: Insertion and movement to same position.
1917
            //
1918
            // RESOLUTION: Place the two elements in order of increasing
1919
            // timestamp. Note that the timestamps can never be equal.
1920
            if (left_side.timestamp() < right_side.timestamp()) {
×
1921
                left.ndx_2 += 1; // <---
×
1922
            }
×
1923
            else {
×
1924
                right.index() += 1; // --->
×
1925
            }
×
1926
        }
×
1927
    }
×
1928
}
×
1929

1930
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayInsert)
1931
{
622✔
1932
    if (same_container(left, right)) {
622✔
1933
        if (!(left.prior_size == right.prior_size)) {
196✔
1934
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1935
        }
×
1936
        if (!(left.index() < left.prior_size)) {
196✔
1937
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1938
        }
×
1939
        if (!(right.index() <= right.prior_size)) {
196✔
1940
            bad_merge(left_side, left, "Merge error: right.index() <= right.prior_size");
×
1941
        }
×
1942

1943
        left.prior_size++;
196✔
1944
        right.prior_size--;
196✔
1945
        if (right.index() <= left.index()) {
196✔
1946
            left.index() += 1; // --->
88✔
1947
        }
88✔
1948
        else {
108✔
1949
            right.index() -= 1; // <---
108✔
1950
        }
108✔
1951
    }
196✔
1952
}
622✔
1953

1954
// Handled by nested rules
1955
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayInsert);
1956
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayInsert);
1957
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayInsert);
1958

1959

1960
/// ArrayMove rules
1961

1962
DEFINE_NESTED_MERGE(Instruction::ArrayMove)
1963
{
×
1964
    if (is_container_prefix_of(outer, inner)) {
×
1965
        auto& index = corresponding_index_in_path(outer, inner);
×
1966
        merge_get_vs_move(outer.index(), index, outer.ndx_2);
×
1967
    }
×
1968
}
×
1969

1970
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayMove)
1971
{
28✔
1972
    if (same_container(left, right)) {
28✔
1973
        if (!(left.prior_size == right.prior_size)) {
28✔
1974
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1975
        }
×
1976
        if (!(left.index() < left.prior_size)) {
28✔
1977
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1978
        }
×
1979
        if (!(right.index() < right.prior_size)) {
28✔
1980
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1981
        }
×
1982
        if (!(left.ndx_2 < left.prior_size)) {
28✔
1983
            bad_merge(left_side, left, "Merge error: left.ndx_2 < left.prior_size");
×
1984
        }
×
1985
        if (!(right.ndx_2 < right.prior_size)) {
28✔
1986
            bad_merge(right_side, right, "Merge error: right.ndx_2 < right.prior_size");
×
1987
        }
×
1988

1989
        if (left.index() < right.index()) {
28✔
1990
            right.index() -= 1; // <---
4✔
1991
        }
4✔
1992
        else if (left.index() > right.index()) {
24✔
1993
            left.index() -= 1; // --->
4✔
1994
        }
4✔
1995
        else {
20✔
1996
            // CONFLICT: Two movements of same element.
1997
            //
1998
            // RESOLUTION: Respect the MOVE operation associated with the higher
1999
            // timestamp. If the previously applied MOVE operation has the higher
2000
            // timestamp, discard the received MOVE operation, otherwise use the
2001
            // previously applied MOVE operation to transform the received MOVE
2002
            // operation. Note that the timestamps are never equal.
2003
            if (left_side.timestamp() < right_side.timestamp()) {
20✔
2004
                right.index() = left.ndx_2; // <---
12✔
2005
                left_side.discard();        // --->
12✔
2006
                if (right.index() == right.ndx_2) {
12✔
2007
                    right_side.discard(); // <---
×
2008
                }
×
2009
            }
12✔
2010
            else {
8✔
2011
                left.index() = right.ndx_2; // --->
8✔
2012
                if (left.index() == left.ndx_2) {
8✔
2013
                    left_side.discard(); // --->
×
2014
                }
×
2015
                right_side.discard(); // <---
8✔
2016
            }
8✔
2017
            return;
20✔
2018
        }
20✔
2019

2020
        // Left insertion vs right removal
2021
        if (left.ndx_2 > right.index()) {
8✔
2022
            left.ndx_2 -= 1; // --->
4✔
2023
        }
4✔
2024
        else {
4✔
2025
            right.index() += 1; // <---
4✔
2026
        }
4✔
2027

2028
        // Left removal vs right insertion
2029
        if (left.index() < right.ndx_2) {
8✔
2030
            right.ndx_2 -= 1; // <---
4✔
2031
        }
4✔
2032
        else {
4✔
2033
            left.index() += 1; // --->
4✔
2034
        }
4✔
2035

2036
        // Left insertion vs right insertion
2037
        if (left.ndx_2 < right.ndx_2) {
8✔
2038
            right.ndx_2 += 1; // <---
4✔
2039
        }
4✔
2040
        else if (left.ndx_2 > right.ndx_2) {
4✔
2041
            left.ndx_2 += 1; // --->
4✔
2042
        }
4✔
2043
        else { // left.ndx_2 == right.ndx_2
×
2044
            // CONFLICT: Two elements moved to the same position
2045
            //
2046
            // RESOLUTION: Place the moved elements in order of increasing
2047
            // timestamp. Note that the timestamps can never be equal.
2048
            if (left_side.timestamp() < right_side.timestamp()) {
×
2049
                right.ndx_2 += 1; // <---
×
2050
            }
×
2051
            else {
×
2052
                left.ndx_2 += 1; // --->
×
2053
            }
×
2054
        }
×
2055

2056
        if (left.index() == left.ndx_2) {
8✔
2057
            left_side.discard(); // --->
×
2058
        }
×
2059
        if (right.index() == right.ndx_2) {
8✔
2060
            right_side.discard(); // <---
×
2061
        }
×
2062
    }
8✔
2063
}
28✔
2064

2065
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayMove)
2066
{
×
2067
    if (same_container(left, right)) {
×
2068
        if (!(left.prior_size == right.prior_size)) {
×
2069
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
2070
        }
×
2071
        if (!(left.index() < left.prior_size)) {
×
2072
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
2073
        }
×
2074
        if (!(right.index() < right.prior_size)) {
×
2075
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
2076
        }
×
2077

2078
        right.prior_size -= 1;
×
2079

2080
        if (left.index() == right.index()) {
×
2081
            // CONFLICT: Removal of a moved element.
2082
            //
2083
            // RESOLUTION: Discard the received MOVE operation on the left side, and
2084
            // use the previously applied MOVE operation to transform the received
2085
            // REMOVE operation on the right side.
2086
            left.index() = right.ndx_2; // --->
×
2087
            right_side.discard();       // <---
×
2088
        }
×
2089
        else {
×
2090
            // Left removal vs right removal
2091
            if (left.index() > right.index()) {
×
2092
                left.index() -= 1; // --->
×
2093
            }
×
2094
            else {                  // left.index() < right.index()
×
2095
                right.index() -= 1; // <---
×
2096
            }
×
2097
            // Left removal vs right insertion
2098
            if (left.index() >= right.ndx_2) {
×
2099
                left.index() += 1; // --->
×
2100
            }
×
2101
            else {
×
2102
                right.ndx_2 -= 1; // <---
×
2103
            }
×
2104

2105
            if (right.index() == right.ndx_2) {
×
2106
                right_side.discard(); // <---
×
2107
            }
×
2108
        }
×
2109
    }
×
2110
}
×
2111

2112
// Handled by nested rule.
2113
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayMove);
2114
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayMove);
2115
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayMove);
2116

2117

2118
/// ArrayErase rules
2119

2120
DEFINE_NESTED_MERGE(Instruction::ArrayErase)
2121
{
8✔
2122
    if (is_prefix_of(outer, inner)) {
8!
2123
        // Erase of subtree - inner instruction touches the subtree.
2124
        inner_side.discard();
×
2125
    }
×
2126
    else if (is_container_prefix_of(outer, inner)) {
8!
2127
        // Erase of a sibling element in the container - adjust the path.
2128
        auto& index = corresponding_index_in_path(outer, inner);
8✔
2129
        if (outer.index() < index) {
8!
2130
            index -= 1;
8✔
2131
        }
8✔
2132
        else {
×
2133
            REALM_ASSERT(index != outer.index());
×
2134
        }
×
2135
    }
8✔
2136
}
8✔
2137

2138
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayErase)
2139
{
70✔
2140
    if (same_container(left, right)) {
70✔
2141
        if (!(left.prior_size == right.prior_size)) {
12✔
2142
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
2143
        }
×
2144
        if (!(left.index() < left.prior_size)) {
12✔
2145
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
2146
        }
×
2147
        if (!(right.index() < right.prior_size)) {
12✔
2148
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
2149
        }
×
2150

2151
        left.prior_size -= 1;
12✔
2152
        right.prior_size -= 1;
12✔
2153

2154
        if (left.index() > right.index()) {
12✔
2155
            left.index() -= 1; // --->
4✔
2156
        }
4✔
2157
        else if (left.index() < right.index()) {
8✔
2158
            right.index() -= 1; // <---
8✔
2159
        }
8✔
UNCOV
2160
        else { // left.index() == right.index()
×
2161
            // CONFLICT: Two removals of the same element.
2162
            //
2163
            // RESOLUTION: On each side, discard the received REMOVE operation.
UNCOV
2164
            left_side.discard();  // --->
×
UNCOV
2165
            right_side.discard(); // <---
×
UNCOV
2166
        }
×
2167
    }
12✔
2168
}
70✔
2169

2170
// Handled by nested rules.
2171
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayErase);
2172
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayErase);
2173
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayErase);
2174

2175

2176
/// Clear rules
2177

2178
DEFINE_NESTED_MERGE(Instruction::Clear)
2179
{
216✔
2180
    // Note: Clear instructions do not have an index in their path.
2181
    if (is_prefix_of(outer, inner)) {
216!
2182
        inner_side.discard();
208✔
2183
    }
208✔
2184
}
216✔
2185

2186
DEFINE_MERGE(Instruction::Clear, Instruction::Clear)
2187
{
96✔
2188
    if (same_path(left, right)) {
96✔
2189
        // CONFLICT: Two clears of the same container.
2190
        //
2191
        // RESOLUTION: Discard the clear with the lower timestamp. This has the
2192
        // effect of preserving insertions that came after the clear from the
2193
        // side that has the higher timestamp.
2194
        if (left_side.timestamp() < right_side.timestamp()) {
96✔
2195
            left_side.discard();
52✔
2196
        }
52✔
2197
        else {
44✔
2198
            right_side.discard();
44✔
2199
        }
44✔
2200
    }
96✔
2201
}
96✔
2202

2203
DEFINE_MERGE(Instruction::SetInsert, Instruction::Clear)
2204
{
8✔
2205
    if (same_path(left, right)) {
8✔
2206
        left_side.discard();
4✔
2207
    }
4✔
2208
}
8✔
2209

2210
DEFINE_MERGE(Instruction::SetErase, Instruction::Clear)
2211
{
8✔
2212
    if (same_path(left, right)) {
8✔
2213
        left_side.discard();
4✔
2214
    }
4✔
2215
}
8✔
2216

2217

2218
/// SetInsert rules
2219

2220
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2221

2222
DEFINE_MERGE(Instruction::SetInsert, Instruction::SetInsert)
2223
{
156✔
2224
    if (same_path(left, right)) {
156✔
2225
        // CONFLICT: Two inserts into the same set.
2226
        //
2227
        // RESOLUTION: If the values are the same, discard the insertion with the lower timestamp. Otherwise,
2228
        // do nothing.
2229
        //
2230
        // NOTE: Set insertion is idempotent. Keeping the instruction with the higher timestamp is necessary
2231
        // because we want to maintain associativity in the case where intermittent erases (as ordered by
2232
        // timestamp) arrive at a later point in time.
2233
        if (same_payload(left.value, right.value)) {
152✔
2234
            if (left_side.timestamp() < right_side.timestamp()) {
24✔
2235
                left_side.discard();
12✔
2236
            }
12✔
2237
            else {
12✔
2238
                right_side.discard();
12✔
2239
            }
12✔
2240
        }
24✔
2241
    }
152✔
2242
}
156✔
2243

2244
DEFINE_MERGE(Instruction::SetErase, Instruction::SetInsert)
2245
{
64✔
2246
    if (same_path(left, right)) {
64✔
2247
        // CONFLICT: Insertion and erase in the same set.
2248
        //
2249
        // RESOLUTION: If the inserted/erased values are the same, discard the instruction with the lower
2250
        // timestamp. Otherwise, do nothing.
2251
        //
2252
        // Note: Set insertion and erase are both idempotent. Keeping the instruction with the higher
2253
        // timestamp is necessary because we want to maintain associativity.
2254
        if (same_payload(left.value, right.value)) {
64✔
2255
            if (left_side.timestamp() < right_side.timestamp()) {
×
2256
                left_side.discard();
×
2257
            }
×
2258
            else {
×
2259
                right_side.discard();
×
2260
            }
×
2261
        }
×
2262
    }
64✔
2263
}
64✔
2264

2265

2266
/// SetErase rules.
2267

2268
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2269

2270
DEFINE_MERGE(Instruction::SetErase, Instruction::SetErase)
2271
{
8✔
2272
    if (same_path(left, right)) {
8✔
2273
        // CONFLICT: Two erases in the same set.
2274
        //
2275
        // RESOLUTION: If the values are the same, discard the instruction with the lower timestamp.
2276
        // Otherwise, do nothing.
2277
        if (left.value == right.value) {
8✔
2278
            if (left_side.timestamp() < right_side.timestamp()) {
8✔
2279
                left_side.discard();
4✔
2280
            }
4✔
2281
            else {
4✔
2282
                right_side.discard();
4✔
2283
            }
4✔
2284
        }
8✔
2285
    }
8✔
2286
}
8✔
2287

2288

2289
///
2290
/// END OF MERGE RULES!
2291
///
2292

2293
template <class Left, class Right>
2294
void merge_instructions_2(Left& left, Right& right, MajorSide& left_side, MinorSide& right_side)
2295
{
285,814✔
2296
    Merge<Left, Right>::merge(left, right, left_side, right_side);
285,814✔
2297
}
285,814✔
2298

2299
template <class Outer, class Inner, class OuterSide, class InnerSide>
2300
void merge_nested_2(Outer& outer, Inner& inner, OuterSide& outer_side, InnerSide& inner_side)
2301
{
46,592✔
2302
    MergeNested<Outer>::merge(outer, inner, outer_side, inner_side);
46,592✔
2303
}
46,592✔
2304

2305
template <class OuterSide, class InnerSide>
2306
void merge_nested(OuterSide& outer_side, InnerSide& inner_side)
2307
{
46,592✔
2308
    outer_side.get().visit([&](auto& outer) {
46,594✔
2309
        inner_side.get().visit([&](auto& inner) {
46,594✔
2310
            merge_nested_2(outer, inner, outer_side, inner_side);
46,592✔
2311
        });
46,592✔
2312
    });
46,594✔
2313
}
46,592✔
2314

2315
void TransformerImpl::merge_instructions(MajorSide& their_side, MinorSide& our_side)
2316
{
288,820✔
2317
    // FIXME: Find a way to avoid heap-copies of the path.
2318
    Instruction their_before = their_side.get();
288,820✔
2319
    Instruction our_before = our_side.get();
288,820✔
2320

2321
    if (their_side.get().get_if<Instruction::Update>()) {
288,820✔
2322
        REALM_ASSERT(their_side.m_path_len > 2);
19,460✔
2323
    }
19,460✔
2324
    if (our_side.get().get_if<Instruction::Update>()) {
288,820✔
2325
        REALM_ASSERT(our_side.m_path_len > 2);
21,058✔
2326
    }
21,058✔
2327
    if (their_side.get().get_if<Instruction::EraseObject>()) {
288,820✔
2328
        REALM_ASSERT(their_side.m_path_len == 2);
22,468✔
2329
    }
22,468✔
2330
    if (our_side.get().get_if<Instruction::EraseObject>()) {
288,820✔
2331
        REALM_ASSERT(our_side.m_path_len == 2);
25,474✔
2332
    }
25,474✔
2333

2334
    // Update selections on the major side (outer loop) according to events on
2335
    // the minor side (inner loop). The selection may only be impacted if the
2336
    // instruction level is lower (i.e. at a higher point in the hierarchy).
2337
    if (our_side.m_path_len < their_side.m_path_len) {
288,820✔
2338
        merge_nested(our_side, their_side);
18,902✔
2339
        if (their_side.was_discarded)
18,902✔
2340
            return;
1,556✔
2341
    }
18,902✔
2342
    else if (our_side.m_path_len > their_side.m_path_len) {
269,918✔
2343
        merge_nested(their_side, our_side);
27,690✔
2344
        if (our_side.was_discarded)
27,690✔
2345
            return;
1,464✔
2346
    }
27,690✔
2347

2348
    if (!their_side.was_discarded && !our_side.was_discarded) {
285,812✔
2349
        // Even if the instructions were nested, we must still perform a regular
2350
        // merge, because link-related instructions contain information from higher
2351
        // levels (both rows, columns, and tables).
2352
        //
2353
        // FIXME: This condition goes away when dangling links are implemented.
2354
        their_side.get().visit([&](auto& their_instruction) {
285,812✔
2355
            our_side.get().visit([&](auto& our_instruction) {
285,814✔
2356
                merge_instructions_2(their_instruction, our_instruction, their_side, our_side);
285,814✔
2357
            });
285,814✔
2358
        });
285,808✔
2359
    }
285,810✔
2360

2361
    // Note: `left` and/or `right` may be dangling at this point due to
2362
    // discard/prepend. However, if they were not discarded, their iterators are
2363
    // required to point to an instruction of the same type.
2364
    if (!their_side.was_discarded && !their_side.was_replaced) {
285,800✔
2365
        const auto& their_after = their_side.get();
265,628✔
2366
        if (!(their_after == their_before)) {
265,628✔
2367
            their_side.m_changeset->set_dirty(true);
1,222✔
2368
        }
1,222✔
2369
    }
265,628✔
2370

2371
    if (!our_side.was_discarded && !our_side.was_replaced) {
285,800✔
2372
        const auto& our_after = our_side.get();
266,232✔
2373
        if (!(our_after == our_before)) {
266,232✔
2374
            our_side.m_changeset->set_dirty(true);
1,222✔
2375
        }
1,222✔
2376
    }
266,232✔
2377
}
285,800✔
2378

2379
} // anonymous namespace
2380

2381
namespace realm::sync {
2382
void Transformer::merge_changesets(file_ident_type local_file_ident, util::Span<Changeset> their_changesets,
2383
                                   util::Span<Changeset*> our_changesets, util::Logger& logger)
2384
{
49,230✔
2385
    REALM_ASSERT(our_changesets.size() != 0);
49,230✔
2386
    REALM_ASSERT(their_changesets.size() != 0);
49,230✔
2387
    bool trace = false;
49,230✔
2388
#if REALM_DEBUG && !REALM_UWP
49,230✔
2389
    // FIXME: Not thread-safe (use config parameter instead and confine environment reading to test/test_all.cpp)
2390
    const char* trace_p = ::getenv("UNITTEST_TRACE_TRANSFORM");
49,230✔
2391
    trace = (trace_p && StringData{trace_p} != "no");
49,230!
2392
    static std::mutex trace_mutex;
49,230✔
2393
    util::Optional<std::unique_lock<std::mutex>> l;
49,230✔
2394
    if (trace) {
49,230✔
2395
        l = std::unique_lock<std::mutex>{trace_mutex};
×
2396
    }
×
2397
#endif
49,230✔
2398
    TransformerImpl transformer{trace};
49,230✔
2399

2400
    _impl::ChangesetIndex their_index;
49,230✔
2401
    size_t their_num_instructions = 0;
49,230✔
2402
    size_t our_num_instructions = 0;
49,230✔
2403

2404
    // Loop through all instructions on both sides and build conflict groups.
2405
    // This causes the index to merge ranges that are connected by instructions
2406
    // on the left side, but which aren't connected on the right side.
2407
    // FIXME: The conflict groups can be persisted as part of the changeset to
2408
    // skip this step in the future.
2409
    for (size_t i = 0; i < their_changesets.size(); ++i) {
120,858✔
2410
        size_t num_instructions = their_changesets[i].size();
71,628✔
2411
        their_num_instructions += num_instructions;
71,628✔
2412
        logger.trace("Scanning incoming changeset [%1/%2] (%3 instructions)", i + 1, their_changesets.size(),
71,628✔
2413
                     num_instructions);
71,628✔
2414

2415
        their_index.scan_changeset(their_changesets[i]);
71,628✔
2416
    }
71,628✔
2417
    for (size_t i = 0; i < our_changesets.size(); ++i) {
645,926✔
2418
        Changeset& our_changeset = *our_changesets[i];
596,696✔
2419
        size_t num_instructions = our_changeset.size();
596,696✔
2420
        our_num_instructions += num_instructions;
596,696✔
2421
        logger.trace(util::LogCategory::changeset, "Scanning local changeset [%1/%2] (%3 instructions)", i + 1,
596,696✔
2422
                     our_changesets.size(), num_instructions);
596,696✔
2423

2424
        their_index.scan_changeset(our_changeset);
596,696✔
2425
    }
596,696✔
2426

2427
    // Build the index.
2428
    for (size_t i = 0; i < their_changesets.size(); ++i) {
120,840✔
2429
        logger.trace(util::LogCategory::changeset, "Indexing incoming changeset [%1/%2] (%3 instructions)", i + 1,
71,610✔
2430
                     their_changesets.size(), their_changesets[i].size());
71,610✔
2431
        their_index.add_changeset(their_changesets[i]);
71,610✔
2432
    }
71,610✔
2433

2434
    logger.debug(util::LogCategory::changeset,
49,230✔
2435
                 "Finished changeset indexing (incoming: %1 changeset(s) / %2 instructions, local: %3 "
49,230✔
2436
                 "changeset(s) / %4 instructions, conflict group(s): %5)",
49,230✔
2437
                 their_changesets.size(), their_num_instructions, our_changesets.size(), our_num_instructions,
49,230✔
2438
                 their_index.get_num_conflict_groups());
49,230✔
2439

2440
#if REALM_DEBUG // LCOV_EXCL_START
2441
    if (trace) {
49,230✔
2442
        std::cerr << TERM_YELLOW << "\n=> PEER " << std::hex << local_file_ident
×
2443
                  << " merging "
×
2444
                     "changeset(s)/from peer(s):\n";
×
2445
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2446
            std::cerr << "Changeset version " << std::dec << their_changesets[i].version << " from peer "
×
2447
                      << their_changesets[i].origin_file_ident << " at timestamp "
×
2448
                      << their_changesets[i].origin_timestamp << "\n";
×
2449
        }
×
2450
        std::cerr << "Transforming through local changeset(s):\n";
×
2451
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2452
            std::cerr << "Changeset version " << our_changesets[i]->version << " from peer "
×
2453
                      << our_changesets[i]->origin_file_ident << " at timestamp "
×
2454
                      << our_changesets[i]->origin_timestamp << "\n";
×
2455
        }
×
2456

2457
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2458
            std::cerr << TERM_RED << "\nLOCAL (RECIPROCAL) CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2459
            our_changesets[i]->print(std::cerr);
×
2460
        }
×
2461

2462
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2463
            std::cerr << TERM_RED << "\nINCOMING CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2464
            their_changesets[i].print(std::cerr);
×
2465
        }
×
2466

2467
        std::cerr << TERM_MAGENTA << "\nINCOMING CHANGESET INDEX:\n" << TERM_RESET;
×
2468
        their_index.print(std::cerr);
×
2469
        std::cerr << '\n';
×
2470
        their_index.verify();
×
2471

2472
        std::cerr << TERM_YELLOW << std::setw(80) << std::left << "MERGE TRACE (incoming):"
×
2473
                  << "MERGE TRACE (local):\n"
×
2474
                  << TERM_RESET;
×
2475
    }
×
2476
#else
2477
    static_cast<void>(local_file_ident);
2478
#endif // REALM_DEBUG LCOV_EXCL_STOP
2479

2480
    for (size_t i = 0; i < our_changesets.size(); ++i) {
645,994✔
2481
        logger.trace(
596,764✔
2482
            util::LogCategory::changeset,
596,764✔
2483
            "Transforming local changeset [%1/%2] through %3 incoming changeset(s) with %4 conflict group(s)", i + 1,
596,764✔
2484
            our_changesets.size(), their_changesets.size(), their_index.get_num_conflict_groups());
596,764✔
2485
        Changeset* our_changeset = our_changesets[i];
596,764✔
2486

2487
        transformer.m_major_side.set_next_changeset(our_changeset);
596,764✔
2488
        // MinorSide uses the index to find the Changeset.
2489
        transformer.m_minor_side.m_changeset_index = &their_index;
596,764✔
2490
        transformer.transform(); // Throws
596,764✔
2491
    }
596,764✔
2492

2493
    logger.debug(util::LogCategory::changeset,
49,230✔
2494
                 "Finished transforming %1 local changesets through %2 incoming changesets (%3 vs %4 "
49,230✔
2495
                 "instructions, in %5 conflict groups)",
49,230✔
2496
                 our_changesets.size(), their_changesets.size(), our_num_instructions, their_num_instructions,
49,230✔
2497
                 their_index.get_num_conflict_groups());
49,230✔
2498

2499
#if REALM_DEBUG // LCOV_EXCL_START
2500
    // Check that the index is still valid after transformation.
2501
    their_index.verify();
49,230✔
2502
#endif // REALM_DEBUG LCOV_EXCL_STOP
49,230✔
2503

2504
#if REALM_DEBUG // LCOV_EXCL_START
2505
    if (trace) {
49,230✔
2506
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2507
            std::cerr << TERM_CYAN << "\nRECIPROCAL CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2508
            our_changesets[i]->print(std::cerr);
×
2509
            std::cerr << '\n';
×
2510
        }
×
2511
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2512
            std::cerr << TERM_CYAN << "INCOMING CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2513
            their_changesets[i].print(std::cerr);
×
2514
            std::cerr << '\n';
×
2515
        }
×
2516
    }
×
2517
#endif // LCOV_EXCL_STOP REALM_DEBUG
49,230✔
2518
}
49,230✔
2519

2520
size_t Transformer::transform_remote_changesets(TransformHistory& history, file_ident_type local_file_ident,
2521
                                                version_type current_local_version,
2522
                                                util::Span<Changeset> parsed_changesets,
2523
                                                util::FunctionRef<bool(const Changeset*)> changeset_applier,
2524
                                                util::Logger& logger)
2525
{
65,532✔
2526
    REALM_ASSERT(local_file_ident != 0);
65,532✔
2527

2528
    std::vector<Changeset*> our_changesets;
65,532✔
2529

2530
    // p points to the beginning of a range of changesets that share the same
2531
    // "base", i.e. are based on the same local version.
2532
    auto p = parsed_changesets.begin();
65,532✔
2533
    auto parsed_changesets_end = parsed_changesets.end();
65,532✔
2534

2535
    while (p != parsed_changesets_end) {
137,158✔
2536
        // Find the range of incoming changesets that share the same
2537
        // last_integrated_local_version, which means we can merge them in one go.
2538
        auto same_base_range_end = std::find_if(p + 1, parsed_changesets_end, [&](auto& changeset) {
71,626✔
2539
            return p->last_integrated_remote_version != changeset.last_integrated_remote_version;
34,578✔
2540
        });
34,578✔
2541

2542
        version_type begin_version = p->last_integrated_remote_version;
71,626✔
2543
        version_type end_version = current_local_version;
71,626✔
2544
        for (;;) {
668,368✔
2545
            HistoryEntry history_entry;
668,368✔
2546
            version_type version = history.find_history_entry(begin_version, end_version, history_entry);
668,368✔
2547
            if (version == 0)
668,368✔
2548
                break; // No more local changesets
71,624✔
2549

2550
            Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
596,744✔
2551
                                                                history_entry); // Throws
596,744✔
2552
            our_changesets.push_back(&our_changeset);
596,744✔
2553

2554
            begin_version = version;
596,744✔
2555
        }
596,744✔
2556

2557
        bool must_apply_all = false;
71,626✔
2558

2559
        if (!our_changesets.empty()) {
71,626✔
2560
            merge_changesets(local_file_ident, {&*p, same_base_range_end}, our_changesets, logger); // Throws
49,230✔
2561
            // We need to apply all transformed changesets if at least one reciprocal changeset was modified
2562
            // during OT.
2563
            must_apply_all = std::any_of(our_changesets.begin(), our_changesets.end(), [](const Changeset* c) {
492,722✔
2564
                return c->is_dirty();
492,722✔
2565
            });
492,722✔
2566
        }
49,230✔
2567

2568
        auto continue_applying = true;
71,626✔
2569
        for (; p != same_base_range_end && continue_applying; ++p) {
171,688✔
2570
            // It is safe to stop applying the changesets if:
2571
            //      1. There are no reciprocal changesets
2572
            //      2. No reciprocal changeset was modified
2573
            continue_applying = changeset_applier(p) || must_apply_all;
100,062!
2574
        }
100,062✔
2575
        if (!continue_applying) {
71,626✔
2576
            break;
×
2577
        }
×
2578

2579
        our_changesets.clear(); // deliberately not releasing memory
71,626✔
2580
    }
71,626✔
2581

2582
    // NOTE: Any exception thrown during flushing *MUST* lead to rollback of
2583
    // the current transaction.
2584
    flush_reciprocal_transform_cache(history); // Throws
65,532✔
2585

2586
    return p - parsed_changesets.begin();
65,532✔
2587
}
65,532✔
2588

2589

2590
Changeset& Transformer::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
2591
                                                 version_type version, const HistoryEntry& history_entry)
2592
{
596,784✔
2593
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
596,784✔
2594
    if (changeset.empty()) {
596,784✔
2595
        bool is_compressed = false;
525,442✔
2596
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
525,442✔
2597
        ChunkedBinaryInputStream in{data};
525,442✔
2598
        if (is_compressed) {
525,442✔
2599
            size_t total_size;
60,834✔
2600
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
60,834✔
2601
            REALM_ASSERT(decompressed);
60,834✔
2602
            sync::parse_changeset(*decompressed, changeset); // Throws
60,834✔
2603
        }
60,834✔
2604
        else {
464,608✔
2605
            sync::parse_changeset(in, changeset); // Throws
464,608✔
2606
        }
464,608✔
2607

2608
        changeset.version = version;
525,442✔
2609
        changeset.last_integrated_remote_version = history_entry.remote_version;
525,442✔
2610
        changeset.origin_timestamp = history_entry.origin_timestamp;
525,442✔
2611
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
525,442✔
2612
        if (origin_file_ident == 0)
525,442✔
2613
            origin_file_ident = local_file_ident;
291,724✔
2614
        changeset.origin_file_ident = origin_file_ident;
525,442✔
2615
    }
525,442✔
2616
    return changeset;
596,784✔
2617
}
596,784✔
2618

2619

2620
void Transformer::flush_reciprocal_transform_cache(TransformHistory& history)
2621
{
65,486✔
2622
    auto changesets = std::move(m_reciprocal_transform_cache);
65,486✔
2623
    m_reciprocal_transform_cache.clear();
65,486✔
2624
    ChangesetEncoder::Buffer output_buffer;
65,486✔
2625
    for (const auto& [version, changeset] : changesets) {
521,236✔
2626
        if (changeset.is_dirty()) {
521,236✔
2627
            encode_changeset(changeset, output_buffer); // Throws
9,838✔
2628
            BinaryData data{output_buffer.data(), output_buffer.size()};
9,838✔
2629
            history.set_reciprocal_transform(version, data); // Throws
9,838✔
2630
            output_buffer.clear();
9,838✔
2631
        }
9,838✔
2632
    }
521,236✔
2633
}
65,486✔
2634

2635
void parse_remote_changeset(const RemoteChangeset& remote_changeset, Changeset& parsed_changeset)
2636
{
100,140✔
2637
    // origin_file_ident = 0 is currently used to indicate an entry of local
2638
    // origin.
2639
    REALM_ASSERT(remote_changeset.origin_file_ident != 0);
100,140✔
2640
    REALM_ASSERT(remote_changeset.remote_version != 0);
100,140✔
2641

2642
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
100,140✔
2643
    parse_changeset(remote_in, parsed_changeset); // Throws
100,140✔
2644

2645
    parsed_changeset.version = remote_changeset.remote_version;
100,140✔
2646
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
100,140✔
2647
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
100,140✔
2648
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
100,140✔
2649
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
100,140✔
2650
}
100,140✔
2651

2652
} // namespace realm::sync
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