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

realm / realm-core / 2293

02 May 2024 08:09PM UTC coverage: 90.759% (+0.01%) from 90.747%
2293

push

Evergreen

web-flow
Fix a deadlock when accessing current user from inside an App listener (#7671)

App::switch_user() emitted changes without first releasing the lock on
m_user_mutex, leading to a deadlock if anyone inside the listener tried to
acquire the mutex. The rest of the places where we emitted changes were
correct.

The newly added wrapper catches this error when building with clang.

101946 of 180246 branches covered (56.56%)

14 of 17 new or added lines in 2 files covered. (82.35%)

67 existing lines in 15 files now uncovered.

212564 of 234207 relevant lines covered (90.76%)

5790527.56 hits per line

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

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

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

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

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

16
namespace {
17

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

38
struct Discriminant {
39
    timestamp_type timestamp;
40
    file_ident_type client_file_ident;
41
    Discriminant(timestamp_type t, file_ident_type p)
42
        : timestamp(t)
519,626✔
43
        , client_file_ident(p)
519,626✔
44
    {
1,070,628✔
45
    }
1,070,628✔
46

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

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

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

66
struct TransformerImpl;
67

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

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

77
    Side(TransformerImpl& transformer)
78
        : m_transformer(transformer)
46,808✔
79
        , m_discriminant(0, 0)
46,808✔
80
    {
101,740✔
81
    }
101,740✔
82

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

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

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

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

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

110
    const Discriminant& timestamp() const
111
    {
20,964✔
112
        return m_discriminant;
20,964✔
113
    }
20,964✔
114

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

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

132
struct MajorSide : Side {
133
    MajorSide(TransformerImpl& transformer)
134
        : Side(transformer)
23,406✔
135
    {
50,872✔
136
    }
50,872✔
137

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

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

152
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
681,168✔
153

154
        Side::init_with_instruction(get());
681,168✔
155
    }
681,168✔
156

157
    void skip_tombstones() noexcept final
158
    {
2,645,162✔
159
        while (m_position != m_changeset->end() && !*m_position) {
2,652,654✔
160
            ++m_position;
7,492✔
161
        }
7,492✔
162
    }
2,645,162✔
163

164
    void next_instruction() noexcept final
165
    {
660,106✔
166
        REALM_ASSERT(m_position != m_changeset->end());
660,106✔
167
        do {
660,800✔
168
            ++m_position;
660,800✔
169
        } while (m_position != m_changeset->end() && !*m_position);
660,800✔
170
    }
660,106✔
171

172
    Instruction& get() noexcept final
173
    {
3,451,472✔
174
        return **m_position;
3,451,472✔
175
    }
3,451,472✔
176

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

182
    Changeset::iterator m_position;
183
};
184

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

188
    MinorSide(TransformerImpl& transformer)
189
        : Side(transformer)
23,404✔
190
    {
50,870✔
191
    }
50,870✔
192

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

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

204
    Position begin() noexcept
205
    {
681,238✔
206
        return Position{m_conflict_ranges};
681,238✔
207
    }
681,238✔
208

209
    Position end() noexcept
210
    {
5,231,366✔
211
        return Position{m_conflict_ranges, Position::end_tag{}};
5,231,366✔
212
    }
5,231,366✔
213

214
    void update_changeset_pointer() noexcept
215
    {
1,338,470✔
216
        if (REALM_LIKELY(m_position != end())) {
1,338,470✔
217
            m_changeset = m_position.m_outer->first;
474,426✔
218
        }
474,426✔
219
        else {
864,044✔
220
            m_changeset = nullptr;
864,044✔
221
        }
864,044✔
222
    }
1,338,470✔
223

224
    void skip_tombstones() noexcept final
225
    {
1,495,792✔
226
        if (m_position != end() && *m_position)
1,495,792✔
227
            return;
725,560✔
228
        skip_tombstones_slow();
770,232✔
229
    }
770,232✔
230

231
    REALM_NOINLINE void skip_tombstones_slow() noexcept
232
    {
770,218✔
233
        while (m_position != end() && !*m_position) {
881,242✔
234
            ++m_position;
111,024✔
235
        }
111,024✔
236
        update_changeset_pointer();
770,218✔
237
    }
770,218✔
238

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

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

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

263
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
287,784✔
264

265
        Side::init_with_instruction(get());
287,784✔
266
    }
287,784✔
267

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

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

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

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

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

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

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

309
        const Changeset* m_changeset = nullptr;
310

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

556
    TransformerImpl(bool trace)
557
        : m_major_side{*this}
23,404✔
558
        , m_minor_side{*this}
23,404✔
559
        , m_trace{trace}
23,404✔
560
    {
50,870✔
561
    }
50,870✔
562

563
    void transform()
564
    {
641,690✔
565
        m_major_side.m_position = m_major_side.m_changeset->begin();
641,690✔
566
        m_major_side.skip_tombstones();
641,690✔
567

568
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
1,322,854✔
569
            m_major_side.init_with_instruction(m_major_side.m_position);
681,164✔
570

571
            set_conflict_ranges();
681,164✔
572
            m_minor_end = m_minor_side.end();
681,164✔
573
            m_minor_side.m_position = m_minor_side.begin();
681,164✔
574
            transform_major();
681,164✔
575

576
            if (!m_major_side.was_discarded)
681,164✔
577
                // Discarding the instruction moves to the next one.
578
                m_major_side.next_instruction();
660,104✔
579
            m_major_side.skip_tombstones();
681,164✔
580
        }
681,164✔
581
    }
641,690✔
582

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

763
    void transform_major()
764
    {
681,234✔
765
        m_minor_side.skip_tombstones();
681,234✔
766

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

772
        while (m_minor_side.m_position != m_minor_end) {
948,024✔
773
            m_minor_side.init_with_instruction(m_minor_side.m_position);
287,784✔
774

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

791
            if (m_major_side.was_discarded)
287,784✔
792
                break;
20,994✔
793
            if (!m_minor_side.was_discarded)
266,790✔
794
                // Discarding an instruction moves to the next one.
795
                m_minor_side.next_instruction();
260,092✔
796
            m_minor_side.skip_tombstones();
266,790✔
797
        }
266,790✔
798
    }
681,234✔
799

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

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

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

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

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

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

857
struct MergeUtils {
858
    MergeUtils(Side& left_side, Side& right_side)
859
        : m_left_side(left_side)
37,506✔
860
        , m_right_side(right_side)
37,506✔
861
    {
80,238✔
862
    }
80,238✔
863

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

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

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

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

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

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

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

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

959
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
960
    {
10,418✔
961
        if (left.size() == right.size()) {
10,418✔
962
            for (size_t i = 0; i < left.size(); ++i) {
10,734✔
963
                if (!same_path_element(left[i], right[i])) {
804✔
964
                    return false;
264✔
965
                }
264✔
966
            }
804✔
967
            return true;
9,930✔
968
        }
10,194✔
969
        return false;
224✔
970
    }
10,418✔
971

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1161
    MergeBase(Side& left_side, Side& right_side)
1162
        : MergeUtils(left_side, right_side)
28,258✔
1163
    {
61,264✔
1164
    }
61,264✔
1165
    MergeBase(MergeBase&&) = delete;
1166
};
1167

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

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

1210

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

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

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

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

1280

1281
/// AddTable rules
1282

1283
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1284

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

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

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

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

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

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

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

1354
/// EraseTable rules
1355

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

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

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

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

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

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

1400

1401
/// CreateObject rules
1402

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

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

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

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

1430

1431
/// Erase rules
1432

1433
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1434
{
9,704✔
1435
    if (is_prefix_of(outer, inner)) {
9,704!
1436
        // Erase always wins.
1437
        inner_side.discard();
960✔
1438
    }
960✔
1439
}
9,704✔
1440

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

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

1468

1469
/// Update rules
1470

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

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

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

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

1500
    using Type = Instruction::Payload::Type;
10,390✔
1501

1502
    if (same_path(left, right)) {
10,390✔
1503
        bool left_is_default = false;
8,946✔
1504
        bool right_is_default = false;
8,946✔
1505
        if (!(left.is_array_update() == right.is_array_update())) {
8,946✔
1506
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
×
1507
        }
×
1508

1509
        if (!left.is_array_update()) {
8,946✔
1510
            if (right.is_array_update()) {
8,902✔
1511
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
×
1512
            }
×
1513
            left_is_default = left.is_default;
8,902✔
1514
            right_is_default = right.is_default;
8,902✔
1515
        }
8,902✔
1516
        else if (!(left.prior_size == right.prior_size)) {
44✔
1517
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1518
        }
×
1519

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1732

1733
/// AddInteger rules
1734

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

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

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

1759

1760
/// AddColumn rules
1761

1762
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1763

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

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

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

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

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

1811
        // Name, type, nullability and link targets match -- discard both
1812
        // sides and proceed.
1813
        left_side.discard();
9,684✔
1814
        right_side.discard();
9,684✔
1815
    }
9,684✔
1816
}
15,012✔
1817

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

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

1832

1833
/// EraseColumn rules
1834

1835
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1836

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

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

1852
/// ArrayInsert rules
1853

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

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

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

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

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

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

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

1942
        left.prior_size++;
144✔
1943
        right.prior_size--;
144✔
1944
        if (right.index() <= left.index()) {
144✔
1945
            left.index() += 1; // --->
44✔
1946
        }
44✔
1947
        else {
100✔
1948
            right.index() -= 1; // <---
100✔
1949
        }
100✔
1950
    }
144✔
1951
}
292✔
1952

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

1958

1959
/// ArrayMove rules
1960

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

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

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

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

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

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

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

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

2077
        right.prior_size -= 1;
×
2078

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

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

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

2116

2117
/// ArrayErase rules
2118

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

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

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

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

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

2174

2175
/// Clear rules
2176

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

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

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

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

2216

2217
/// SetInsert rules
2218

2219
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2220

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

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

2264

2265
/// SetErase rules.
2266

2267
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2268

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

2287

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

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

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

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

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

2320
    if (their_side.get().get_if<Instruction::Update>()) {
287,778✔
2321
        REALM_ASSERT(their_side.m_path_len > 2);
18,440✔
2322
    }
18,440✔
2323
    if (our_side.get().get_if<Instruction::Update>()) {
287,778✔
2324
        REALM_ASSERT(our_side.m_path_len > 2);
20,708✔
2325
    }
20,708✔
2326
    if (their_side.get().get_if<Instruction::EraseObject>()) {
287,778✔
2327
        REALM_ASSERT(their_side.m_path_len == 2);
23,970✔
2328
    }
23,970✔
2329
    if (our_side.get().get_if<Instruction::EraseObject>()) {
287,778✔
2330
        REALM_ASSERT(our_side.m_path_len == 2);
28,248✔
2331
    }
28,248✔
2332

2333
    // Update selections on the major side (outer loop) according to events on
2334
    // the minor side (inner loop). The selection may only be impacted if the
2335
    // instruction level is lower (i.e. at a higher point in the hierarchy).
2336
    if (our_side.m_path_len < their_side.m_path_len) {
287,778✔
2337
        merge_nested(our_side, their_side);
21,038✔
2338
        if (their_side.was_discarded)
21,038✔
2339
            return;
1,578✔
2340
    }
21,038✔
2341
    else if (our_side.m_path_len > their_side.m_path_len) {
266,740✔
2342
        merge_nested(their_side, our_side);
26,576✔
2343
        if (our_side.was_discarded)
26,576✔
2344
            return;
1,486✔
2345
    }
26,576✔
2346

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

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

2370
    if (!our_side.was_discarded && !our_side.was_replaced) {
284,714✔
2371
        const auto& our_after = our_side.get();
265,746✔
2372
        if (!(our_after == our_before)) {
265,746✔
2373
            our_side.m_changeset->set_dirty(true);
1,108✔
2374
        }
1,108✔
2375
    }
265,746✔
2376
}
284,714✔
2377

2378
} // anonymous namespace
2379

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

2399
    _impl::ChangesetIndex their_index;
50,872✔
2400
    size_t their_num_instructions = 0;
50,872✔
2401
    size_t our_num_instructions = 0;
50,872✔
2402

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

2414
        their_index.scan_changeset(their_changesets[i]);
72,428✔
2415
    }
72,428✔
2416
    for (size_t i = 0; i < our_changesets.size(); ++i) {
692,524✔
2417
        Changeset& our_changeset = *our_changesets[i];
641,652✔
2418
        size_t num_instructions = our_changeset.size();
641,652✔
2419
        our_num_instructions += num_instructions;
641,652✔
2420
        logger.trace(util::LogCategory::changeset, "Scanning local changeset [%1/%2] (%3 instructions)", i + 1,
641,652✔
2421
                     our_changesets.size(), num_instructions);
641,652✔
2422

2423
        their_index.scan_changeset(our_changeset);
641,652✔
2424
    }
641,652✔
2425

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

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

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

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

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

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

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

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

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

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

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

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

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

2527
    std::vector<Changeset*> our_changesets;
67,028✔
2528

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

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

2541
        version_type begin_version = p->last_integrated_remote_version;
73,500✔
2542
        version_type end_version = current_local_version;
73,500✔
2543
        for (;;) {
715,158✔
2544
            HistoryEntry history_entry;
715,158✔
2545
            version_type version = history.find_history_entry(begin_version, end_version, history_entry);
715,158✔
2546
            if (version == 0)
715,158✔
2547
                break; // No more local changesets
73,502✔
2548

2549
            Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
641,656✔
2550
                                                                history_entry); // Throws
641,656✔
2551
            our_changesets.push_back(&our_changeset);
641,656✔
2552

2553
            begin_version = version;
641,656✔
2554
        }
641,656✔
2555

2556
        bool must_apply_all = false;
73,500✔
2557

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

2567
        auto continue_applying = true;
73,500✔
2568
        for (; p != same_base_range_end && continue_applying; ++p) {
174,728✔
2569
            // It is safe to stop applying the changesets if:
2570
            //      1. There are no reciprocal changesets
2571
            //      2. No reciprocal changeset was modified
2572
            continue_applying = changeset_applier(p) || must_apply_all;
101,228!
2573
        }
101,228✔
2574
        if (!continue_applying) {
73,500✔
2575
            break;
×
2576
        }
×
2577

2578
        our_changesets.clear(); // deliberately not releasing memory
73,500✔
2579
    }
73,500✔
2580

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

2585
    return p - parsed_changesets.begin();
67,028✔
2586
}
67,028✔
2587

2588

2589
Changeset& Transformer::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
2590
                                                 version_type version, const HistoryEntry& history_entry)
2591
{
641,664✔
2592
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
641,664✔
2593
    if (changeset.empty()) {
641,664✔
2594
        bool is_compressed = false;
551,106✔
2595
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
551,106✔
2596
        ChunkedBinaryInputStream in{data};
551,106✔
2597
        if (is_compressed) {
551,106✔
2598
            size_t total_size;
68,350✔
2599
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
68,350✔
2600
            REALM_ASSERT(decompressed);
68,350✔
2601
            sync::parse_changeset(*decompressed, changeset); // Throws
68,350✔
2602
        }
68,350✔
2603
        else {
482,756✔
2604
            sync::parse_changeset(in, changeset); // Throws
482,756✔
2605
        }
482,756✔
2606

2607
        changeset.version = version;
551,106✔
2608
        changeset.last_integrated_remote_version = history_entry.remote_version;
551,106✔
2609
        changeset.origin_timestamp = history_entry.origin_timestamp;
551,106✔
2610
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
551,106✔
2611
        if (origin_file_ident == 0)
551,106✔
2612
            origin_file_ident = local_file_ident;
306,136✔
2613
        changeset.origin_file_ident = origin_file_ident;
551,106✔
2614
    }
551,106✔
2615
    return changeset;
641,664✔
2616
}
641,664✔
2617

2618

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

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

2641
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
101,340✔
2642
    parse_changeset(remote_in, parsed_changeset); // Throws
101,340✔
2643

2644
    parsed_changeset.version = remote_changeset.remote_version;
101,340✔
2645
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
101,340✔
2646
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
101,340✔
2647
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
101,340✔
2648
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
101,340✔
2649
}
101,340✔
2650

2651
} // 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