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

realm / realm-core / 1834

14 Nov 2023 07:48AM UTC coverage: 91.677% (+0.03%) from 91.644%
1834

push

Evergreen

jedelbo
Explicitly instantiate Set<ObjKey> in set.cpp

`Set<ObjKey>` is the single type not implicitly instantiated in
`Obj::get_setbase_ptr()`, so it needs to be explicitly instantiated.

92172 of 168858 branches covered (0.0%)

231140 of 252123 relevant lines covered (91.68%)

6725894.02 hits per line

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

63.08
/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)
43
        , client_file_ident(p)
44
    {
11,994,584✔
45
    }
11,994,584✔
46

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

50
    bool operator<(const Discriminant& other) const
51
    {
32,352✔
52
        return timestamp == other.timestamp ? (client_file_ident < other.client_file_ident)
16,530✔
53
                                            : timestamp < other.timestamp;
31,842✔
54
    }
32,352✔
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)
79
        , m_discriminant(0, 0)
80
    {
830,876✔
81
    }
830,876✔
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.
16✔
96
        return m_changeset->get_string(range);
32✔
97
    }
32✔
98

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

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

110
    const Discriminant& timestamp() const
111
    {
64,702✔
112
        return m_discriminant;
64,702✔
113
    }
64,702✔
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
    {
11,163,584✔
126
        was_discarded = false;
11,163,584✔
127
        was_replaced = false;
11,163,584✔
128
        m_path_len = instr.path_length();
11,163,584✔
129
    }
11,163,584✔
130
};
131

132
struct MajorSide : Side {
133
    MajorSide(TransformerImpl& transformer)
134
        : Side(transformer)
135
    {
415,436✔
136
    }
415,436✔
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
    {
8,528,852✔
146
        REALM_ASSERT(position >= m_changeset->begin());
8,528,852✔
147
        REALM_ASSERT(position != m_changeset->end());
8,528,852✔
148
        m_position = position;
8,528,852✔
149
        skip_tombstones();
8,528,852✔
150
        REALM_ASSERT(position != m_changeset->end());
8,528,852✔
151

4,252,648✔
152
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
8,528,852✔
153

4,252,648✔
154
        Side::init_with_instruction(get());
8,528,852✔
155
    }
8,528,852✔
156

157
    void skip_tombstones() noexcept final
158
    {
36,870,836✔
159
        while (m_position != m_changeset->end() && !*m_position) {
36,871,944✔
160
            ++m_position;
1,108✔
161
        }
1,108✔
162
    }
36,870,836✔
163

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

172
    Instruction& get() noexcept final
173
    {
38,757,694✔
174
        return **m_position;
38,757,694✔
175
    }
38,757,694✔
176

177
    size_t get_object_ids_in_current_instruction(_impl::ChangesetIndex::GlobalID* ids, size_t max_ids)
178
    {
7,604,672✔
179
        return _impl::get_object_ids_in_instruction(*m_changeset, get(), ids, max_ids);
7,604,672✔
180
    }
7,604,672✔
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)
190
    {
415,438✔
191
    }
415,438✔
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
    {
8,528,922✔
206
        return Position{m_conflict_ranges};
8,528,922✔
207
    }
8,528,922✔
208

209
    Position end() noexcept
210
    {
60,120,158✔
211
        return Position{m_conflict_ranges, Position::end_tag{}};
60,120,158✔
212
    }
60,120,158✔
213

214
    void update_changeset_pointer() noexcept
215
    {
16,048,602✔
216
        if (REALM_LIKELY(m_position != end())) {
16,048,602✔
217
            m_changeset = m_position.m_outer->first;
2,820,414✔
218
        }
2,820,414✔
219
        else {
13,228,188✔
220
            m_changeset = nullptr;
13,228,188✔
221
        }
13,228,188✔
222
    }
16,048,602✔
223

224
    void skip_tombstones() noexcept final
225
    {
16,248,900✔
226
        if (m_position != end() && *m_position)
16,248,900✔
227
            return;
5,420,032✔
228
        skip_tombstones_slow();
10,828,868✔
229
    }
10,828,868✔
230

231
    REALM_NOINLINE void skip_tombstones_slow() noexcept
232
    {
10,829,050✔
233
        while (m_position != end() && !*m_position) {
11,509,638✔
234
            ++m_position;
680,588✔
235
        }
680,588✔
236
        update_changeset_pointer();
10,829,050✔
237
    }
10,829,050✔
238

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

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

254
    void init_with_instruction(Position position)
255
    {
2,634,974✔
256
        // REALM_ASSERT(position >= Position(m_conflict_ranges));
1,317,606✔
257
        REALM_ASSERT(position != end());
2,634,974✔
258
        m_position = position;
2,634,974✔
259
        update_changeset_pointer();
2,634,974✔
260
        skip_tombstones();
2,634,974✔
261
        REALM_ASSERT(position != end());
2,634,974✔
262

1,317,606✔
263
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
2,634,974✔
264

1,317,606✔
265
        Side::init_with_instruction(get());
2,634,974✔
266
    }
2,634,974✔
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::AddColumn::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::AddColumn::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}
558
        , m_minor_side{*this}
559
        , m_trace{trace}
560
    {
415,436✔
561
    }
415,436✔
562

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

4,945,940✔
568
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
18,435,736✔
569
            m_major_side.init_with_instruction(m_major_side.m_position);
8,528,846✔
570

4,252,646✔
571
            set_conflict_ranges();
8,528,846✔
572
            m_minor_end = m_minor_side.end();
8,528,846✔
573
            m_minor_side.m_position = m_minor_side.begin();
8,528,846✔
574
            transform_major();
8,528,846✔
575

4,252,646✔
576
            if (!m_major_side.was_discarded)
8,528,846✔
577
                // Discarding the instruction moves to the next one.
4,218,312✔
578
                m_major_side.next_instruction();
8,461,476✔
579
            m_major_side.skip_tombstones();
8,528,846✔
580
        }
8,528,846✔
581
    }
9,906,890✔
582

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

4,252,652✔
587
        if (_impl::is_schema_change(instr)) {
8,528,806✔
588
            ///
471,282✔
589
            /// CONFLICT GROUP: Everything touching that class
471,282✔
590
            ///
471,282✔
591
            auto ranges = index.get_everything();
924,184✔
592
            if (!ranges->empty()) {
924,184✔
593
#if REALM_DEBUG // LCOV_EXCL_START
346,362✔
594
                if (m_trace) {
680,544✔
595
                    std::cerr << TERM_RED << "Conflict group: Everything (due to schema change)\n" << TERM_RESET;
×
596
                }
×
597
#endif // REALM_DEBUG LCOV_EXCL_STOP
680,544✔
598
            }
680,544✔
599
            return ranges;
924,184✔
600
        }
924,184✔
601
        else {
7,604,622✔
602
            ///
3,781,370✔
603
            /// CONFLICT GROUP: Everything touching the involved objects,
3,781,370✔
604
            /// including schema changes.
3,781,370✔
605
            ///
3,781,370✔
606
            _impl::ChangesetIndex::GlobalID major_ids[2];
7,604,622✔
607
            size_t num_major_ids = m_major_side.get_object_ids_in_current_instruction(major_ids, 2);
7,604,622✔
608
            REALM_ASSERT(num_major_ids <= 2);
7,604,622✔
609
            REALM_ASSERT(num_major_ids >= 1);
7,604,622✔
610
#if REALM_DEBUG // LCOV_EXCL_START
3,781,370✔
611
            if (m_trace) {
7,604,622✔
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
7,604,622✔
624
            auto ranges = index.get_modifications_for_object(major_ids[0]);
7,604,622✔
625
            if (num_major_ids == 2) {
7,604,622✔
626
                // Check that the index has correctly joined the ranges for the
74✔
627
                // two object IDs.
74✔
628
                REALM_ASSERT(ranges == index.get_modifications_for_object(major_ids[1]));
148✔
629
            }
148✔
630
            return ranges;
7,604,622✔
631
        }
7,604,622✔
632
    }
8,528,806✔
633

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

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

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

655
    void discard_minor()
656
    {
66,834✔
657
        m_minor_side.was_discarded = true;
66,834✔
658
        m_minor_side.m_position = m_minor_side.m_changeset_index->erase_instruction(m_minor_side.m_position);
66,834✔
659
        m_minor_side.m_changeset->set_dirty(true);
66,834✔
660
        m_minor_side.update_changeset_pointer();
66,834✔
661
    }
66,834✔
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
    {
8,528,904✔
765
        m_minor_side.skip_tombstones();
8,528,904✔
766

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

4,252,702✔
772
        while (m_minor_side.m_position != m_minor_end) {
11,096,464✔
773
            m_minor_side.init_with_instruction(m_minor_side.m_position);
2,634,950✔
774

1,317,582✔
775
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
2,634,950✔
776
            if (m_trace) {
2,634,950✔
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 {
2,634,950✔
785
#endif // LCOV_EXCL_STOP REALM_DEBUG
2,634,950✔
786
                merge_instructions(m_major_side, m_minor_side);
2,634,950✔
787
#if defined(REALM_DEBUG) // LCOV_EXCL_START
2,634,950✔
788
            }
2,634,950✔
789
#endif // LCOV_EXCL_STOP REALM_DEBUG
2,634,950✔
790

1,317,582✔
791
            if (m_major_side.was_discarded)
2,634,950✔
792
                break;
67,390✔
793
            if (!m_minor_side.was_discarded)
2,567,560✔
794
                // Discarding an instruction moves to the next one.
1,257,714✔
795
                m_minor_side.next_instruction();
2,517,908✔
796
            m_minor_side.skip_tombstones();
2,567,560✔
797
        }
2,567,560✔
798
    }
8,528,904✔
799

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

803
void MajorSide::set_next_changeset(Changeset* changeset) noexcept
804
{
9,906,964✔
805
    m_transformer.set_next_major_changeset(changeset);
9,906,964✔
806
}
9,906,964✔
807
void MajorSide::discard()
808
{
67,390✔
809
    m_transformer.discard_major();
67,390✔
810
}
67,390✔
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
{
66,832✔
822
    m_transformer.discard_minor();
66,832✔
823
}
66,832✔
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
{
52✔
836
    throw sync::TransformError{std::move(msg)};
52✔
837
}
52✔
838

839
template <class... Params>
840
REALM_NORETURN void bad_merge(const char* msg, Params&&... params)
841
{
52✔
842
    throw_bad_merge(util::format(msg, std::forward<Params>(params)...));
52✔
843
}
52✔
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)
860
        , m_right_side(right_side)
861
    {
1,235,872✔
862
    }
1,235,872✔
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
    {
1,299,158✔
870
        // FIXME: Optimize string comparison by building a map of InternString values up front.
642,268✔
871
        return m_left_side.m_changeset->get_string(left) == m_right_side.m_changeset->get_string(right);
1,299,158✔
872
    }
1,299,158✔
873

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

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

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

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

931
        bad_merge("Invalid payload type in instruction");
×
932
    }
×
933

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

955
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
956
    {
11,648✔
957
        if (left.size() == right.size()) {
11,648✔
958
            for (size_t i = 0; i < left.size(); ++i) {
11,912✔
959
                if (!same_path_element(left[i], right[i])) {
1,736✔
960
                    return false;
1,392✔
961
                }
1,392✔
962
            }
1,736✔
963
            return true;
10,810✔
964
        }
80✔
965
        return false;
80✔
966
    }
80✔
967

968
    bool same_table(const Instruction::TableInstruction& left,
969
                    const Instruction::TableInstruction& right) const noexcept
970
    {
1,226,720✔
971
        return same_string(left.table, right.table);
1,226,720✔
972
    }
1,226,720✔
973

974
    bool same_object(const Instruction::ObjectInstruction& left,
975
                     const Instruction::ObjectInstruction& right) const noexcept
976
    {
1,003,766✔
977
        return same_table(left, right) && same_key(left.object, right.object);
1,003,766✔
978
    }
1,003,766✔
979

980
    template <class Left, class Right>
981
    bool same_column(const Left& left, const Right& right) const noexcept
982
    {
35,544✔
983
        if constexpr (std::is_convertible_v<const Right&, const Instruction::PathInstruction&>) {
35,544✔
984
            const Instruction::PathInstruction& rhs = right;
35,544✔
985
            return same_table(left, right) && same_string(left.field, rhs.field);
17,894!
986
        }
×
987
        else if constexpr (std::is_convertible_v<const Right&, const Instruction::ObjectInstruction&>) {
35,544✔
988
            // CreateObject/EraseObject do not have a column built in.
17,894✔
989
            return false;
35,544✔
990
        }
35,544✔
991
        else {
35,544✔
992
            return same_table(left, right) && same_string(left.field, right.field);
35,544!
993
        }
35,544✔
994
    }
35,544✔
995

996
    bool same_field(const Instruction::PathInstruction& left,
997
                    const Instruction::PathInstruction& right) const noexcept
998
    {
200,004✔
999
        return same_object(left, right) && same_string(left.field, right.field);
200,004✔
1000
    }
200,004✔
1001

1002
    bool same_path(const Instruction::PathInstruction& left, const Instruction::PathInstruction& right) const noexcept
1003
    {
27,450✔
1004
        return same_field(left, right) && same_path(left.path, right.path);
27,450✔
1005
    }
27,450✔
1006

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

13,216✔
1017
            for (size_t i = 0; i < left.size() - 1; ++i) {
32,800✔
1018
                if (!same_path_element(left[i], right[i])) {
×
1019
                    return false;
×
1020
                }
×
1021
            }
×
1022
            return true;
32,800✔
1023
        }
24✔
1024
        return false;
24✔
1025
    }
24✔
1026

1027
    bool same_container(const Instruction::PathInstruction& left,
1028
                        const Instruction::PathInstruction& right) const noexcept
1029
    {
139,144✔
1030
        return same_field(left, right) && same_container(left.path, right.path);
139,144✔
1031
    }
139,144✔
1032

1033
    // NOTE: `is_prefix_of()` should only return true if the left is a strictly
1034
    // shorter path than the right, and the entire left path is the initial
1035
    // sequence of the right.
1036

1037
    bool is_prefix_of(const Instruction::AddTable& left, const Instruction::TableInstruction& right) const noexcept
1038
    {
×
1039
        return same_table(left, right);
×
1040
    }
×
1041

1042
    bool is_prefix_of(const Instruction::EraseTable& left, const Instruction::TableInstruction& right) const noexcept
1043
    {
167,536✔
1044
        return same_table(left, right);
167,536✔
1045
    }
167,536✔
1046

1047
    bool is_prefix_of(const Instruction::AddColumn&, const Instruction::TableInstruction&) const noexcept
1048
    {
×
1049
        // Right side is a schema instruction.
×
1050
        return false;
×
1051
    }
×
1052

1053
    bool is_prefix_of(const Instruction::EraseColumn&, const Instruction::TableInstruction&) const noexcept
1054
    {
×
1055
        // Right side is a schema instruction.
×
1056
        return false;
×
1057
    }
×
1058

1059
    bool is_prefix_of(const Instruction::AddColumn& left, const Instruction::ObjectInstruction& right) const noexcept
1060
    {
×
1061
        return same_column(left, right);
×
1062
    }
×
1063

1064
    bool is_prefix_of(const Instruction::EraseColumn& left,
1065
                      const Instruction::ObjectInstruction& right) const noexcept
1066
    {
×
1067
        return same_column(left, right);
×
1068
    }
×
1069

1070
    bool is_prefix_of(const Instruction::ObjectInstruction&, const Instruction::TableInstruction&) const noexcept
1071
    {
×
1072
        // Right side is a schema instruction.
1073
        return false;
×
1074
    }
×
1075

1076
    bool is_prefix_of(const Instruction::ObjectInstruction& left,
1077
                      const Instruction::PathInstruction& right) const noexcept
1078
    {
238,082✔
1079
        return same_object(left, right);
238,082✔
1080
    }
238,082✔
1081

1082
    bool is_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const noexcept
1083
    {
×
1084
        // Path instructions can never be full prefixes of table-level instructions. Note that this also covers
1085
        // ObjectInstructions.
1086
        return false;
×
1087
    }
×
1088

1089
    bool is_prefix_of(const Instruction::PathInstruction& left,
1090
                      const Instruction::PathInstruction& right) const noexcept
1091
    {
33,388✔
1092
        if (left.path.size() < right.path.size() && same_field(left, right)) {
33,388✔
1093
            for (size_t i = 0; i < left.path.size(); ++i) {
420✔
1094
                if (!same_path_element(left.path[i], right.path[i])) {
8✔
1095
                    return false;
8✔
1096
                }
8✔
1097
            }
8✔
1098
            return true;
416✔
1099
        }
32,968✔
1100
        return false;
32,968✔
1101
    }
32,968✔
1102

1103
    // True if the left side is an instruction that touches a container within
1104
    // right's path. Equivalent to is_prefix_of, except the last element (the
1105
    // index) is not considered.
1106
    bool is_container_prefix_of(const Instruction::PathInstruction& left,
1107
                                const Instruction::PathInstruction& right) const
1108
    {
24✔
1109
        if (left.path.size() != 0 && left.path.size() < right.path.size() && same_field(left, right)) {
24✔
1110
            for (size_t i = 0; i < left.path.size() - 1; ++i) {
24✔
1111
                if (!same_path_element(left.path[i], right.path[i])) {
×
1112
                    return false;
×
1113
                }
×
1114
            }
×
1115
            return true;
24✔
1116
        }
×
1117
        return false;
×
1118
    }
×
1119

1120
    bool is_container_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const
1121
    {
×
1122
        return false;
×
1123
    }
×
1124

1125
    // When the left side has a shorter path, get the path component on the
1126
    // right that corresponds to the last component on the left.
1127
    //
1128
    // Note that this will only be used in the context of array indices, because
1129
    // those are the only path components that are modified during OT.
1130
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction& left,
1131
                                          Instruction::PathInstruction& right) const
1132
    {
24✔
1133
        REALM_ASSERT(left.path.size() != 0);
24✔
1134
        REALM_ASSERT(left.path.size() < right.path.size());
24✔
1135
        REALM_ASSERT(mpark::holds_alternative<uint32_t>(left.path.back()));
24✔
1136
        size_t index = left.path.size() - 1;
24✔
1137
        if (!mpark::holds_alternative<uint32_t>(right.path[index])) {
24✔
1138
            bad_merge("Inconsistent paths");
×
1139
        }
×
1140
        return mpark::get<uint32_t>(right.path[index]);
24✔
1141
    }
24✔
1142

1143
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction&,
1144
                                          const Instruction::TableInstruction&) const
1145
    {
×
1146
        // A path instruction can never have a shorter path than something that
1147
        // isn't a PathInstruction.
1148
        REALM_UNREACHABLE();
1149
    }
×
1150

1151
    void merge_get_vs_move(uint32_t& get_ndx, const uint32_t& move_from_ndx,
1152
                           const uint32_t& move_to_ndx) const noexcept
1153
    {
×
1154
        if (get_ndx == move_from_ndx) {
×
1155
            // CONFLICT: Update of a moved element.
1156
            //
1157
            // RESOLUTION: On the left side, use the MOVE operation to transform the
1158
            // UPDATE operation received from the right side.
1159
            get_ndx = move_to_ndx; // --->
×
1160
        }
×
1161
        else {
×
1162
            // Right update vs left remove
1163
            if (get_ndx > move_from_ndx) {
×
1164
                get_ndx -= 1; // --->
×
1165
            }
×
1166
            // Right update vs left insert
1167
            if (get_ndx >= move_to_ndx) {
×
1168
                get_ndx += 1; // --->
×
1169
            }
×
1170
        }
×
1171
    }
×
1172

1173
protected:
1174
    Side& m_left_side;
1175
    Side& m_right_side;
1176
};
1177

1178
template <class LeftInstruction, class RightInstruction>
1179
struct MergeBase : MergeUtils {
1180
    static const Instruction::Type A = Instruction::GetInstructionType<LeftInstruction>::value;
1181
    static const Instruction::Type B = Instruction::GetInstructionType<RightInstruction>::value;
1182
    static_assert(A >= B, "A < B. Please reverse the order of instruction types. :-)");
1183

1184
    MergeBase(Side& left_side, Side& right_side)
1185
        : MergeUtils(left_side, right_side)
1186
    {
796,786✔
1187
    }
796,786✔
1188
    MergeBase(MergeBase&&) = delete;
1189
};
1190

1191
#define DEFINE_MERGE(A, B)                                                                                           \
1192
    template <>                                                                                                      \
1193
    struct Merge<A, B> {                                                                                             \
1194
        template <class LeftSide, class RightSide>                                                                   \
1195
        struct DoMerge : MergeBase<A, B> {                                                                           \
1196
            A& left;                                                                                                 \
1197
            B& right;                                                                                                \
1198
            LeftSide& left_side;                                                                                     \
1199
            RightSide& right_side;                                                                                   \
1200
            DoMerge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                                   \
1201
                : MergeBase<A, B>(left_side, right_side)                                                             \
1202
                , left(left)                                                                                         \
1203
                , right(right)                                                                                       \
1204
                , left_side(left_side)                                                                               \
1205
                , right_side(right_side)                                                                             \
1206
            {                                                                                                        \
796,784✔
1207
            }                                                                                                        \
796,784✔
1208
            void do_merge();                                                                                         \
1209
        };                                                                                                           \
1210
        template <class LeftSide, class RightSide>                                                                   \
1211
        static inline void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                      \
1212
        {                                                                                                            \
796,782✔
1213
            DoMerge<LeftSide, RightSide> do_merge{left, right, left_side, right_side};                               \
796,782✔
1214
            do_merge.do_merge();                                                                                     \
796,782✔
1215
        }                                                                                                            \
796,782✔
1216
    };                                                                                                               \
1217
    template <class LeftSide, class RightSide>                                                                       \
1218
    void Merge<A, B>::DoMerge<LeftSide, RightSide>::do_merge()
1219

1220
#define DEFINE_MERGE_NOOP(A, B)                                                                                      \
1221
    template <>                                                                                                      \
1222
    struct Merge<A, B> {                                                                                             \
1223
        static const Instruction::Type left_type = Instruction::GetInstructionType<A>::value;                        \
1224
        static const Instruction::Type right_type = Instruction::GetInstructionType<B>::value;                       \
1225
        static_assert(left_type >= right_type,                                                                       \
1226
                      "left_type < right_type. Please reverse the order of instruction types. :-)");                 \
1227
        template <class LeftSide, class RightSide>                                                                   \
1228
        static inline void merge(A&, B&, LeftSide&, RightSide&)                                                      \
1229
        { /* Do nothing */                                                                                           \
1,779,034✔
1230
        }                                                                                                            \
1,779,034✔
1231
    }
1232

1233

1234
#define DEFINE_NESTED_MERGE(A)                                                                                       \
1235
    template <>                                                                                                      \
1236
    struct MergeNested<A> {                                                                                          \
1237
        template <class B, class OuterSide, class InnerSide>                                                         \
1238
        struct DoMerge : MergeUtils {                                                                                \
1239
            A& outer;                                                                                                \
1240
            B& inner;                                                                                                \
1241
            OuterSide& outer_side;                                                                                   \
1242
            InnerSide& inner_side;                                                                                   \
1243
            DoMerge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                                \
1244
                : MergeUtils(outer_side, inner_side)                                                                 \
1245
                , outer(outer)                                                                                       \
1246
                , inner(inner)                                                                                       \
1247
                , outer_side(outer_side)                                                                             \
1248
                , inner_side(inner_side)                                                                             \
1249
            {                                                                                                        \
439,086✔
1250
            }                                                                                                        \
439,086✔
1251
            void do_merge();                                                                                         \
1252
        };                                                                                                           \
1253
        template <class B, class OuterSide, class InnerSide>                                                         \
1254
        static inline void merge(A& outer, B& inner, OuterSide& outer_side, InnerSide& inner_side)                   \
1255
        {                                                                                                            \
439,086✔
1256
            DoMerge<B, OuterSide, InnerSide> do_merge{outer, inner, outer_side, inner_side};                         \
439,086✔
1257
            do_merge.do_merge();                                                                                     \
439,086✔
1258
        }                                                                                                            \
439,086✔
1259
    };                                                                                                               \
1260
    template <class B, class OuterSide, class InnerSide>                                                             \
1261
    void MergeNested<A>::DoMerge<B, OuterSide, InnerSide>::do_merge()
1262

1263
#define DEFINE_NESTED_MERGE_NOOP(A)                                                                                  \
1264
    template <>                                                                                                      \
1265
    struct MergeNested<A> {                                                                                          \
1266
        template <class B, class OuterSide, class InnerSide>                                                         \
1267
        static inline void merge(const A&, const B&, const OuterSide&, const InnerSide&)                             \
1268
        { /* Do nothing */                                                                                           \
641,026✔
1269
        }                                                                                                            \
641,026✔
1270
    }
1271

1272
// Implementation that reverses order.
1273
template <class A, class B>
1274
struct Merge<A, B,
1275
             typename std::enable_if<(Instruction::GetInstructionType<A>::value <
1276
                                      Instruction::GetInstructionType<B>::value)>::type> {
1277
    template <class LeftSide, class RightSide>
1278
    static void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)
1279
    {
911,292✔
1280
        Merge<B, A>::merge(right, left, right_side, left_side);
911,292✔
1281
    }
911,292✔
1282
};
1283

1284
///
1285
///  GET READY!
1286
///
1287
///  Realm supports 14 instructions at the time of this writing. Each
1288
///  instruction type needs one rule for each other instruction type. We only
1289
///  define one rule to handle each combination (A vs B and B vs A are handle by
1290
///  a single rule).
1291
///
1292
///  This gives (14 * (14 + 1)) / 2 = 105 merge rules below.
1293
///
1294
///  Merge rules are ordered such that the second instruction type is always of
1295
///  a lower enum value than the first.
1296
///
1297
///  Nested merge rules apply when one instruction has a strictly longer path
1298
///  than another. All instructions that have a path of the same length will
1299
///  meet each other through regular merge rules, regardless of whether they
1300
///  share a prefix.
1301
///
1302

1303

1304
/// AddTable rules
1305

1306
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1307

1308
DEFINE_MERGE(Instruction::AddTable, Instruction::AddTable)
1309
{
14,192✔
1310
    if (same_table(left, right)) {
14,192✔
1311
        StringData left_name = left_side.get_string(left.table);
7,388✔
1312
        if (auto left_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&left.type)) {
7,388✔
1313
            if (auto right_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&right.type)) {
7,228✔
1314
                StringData left_pk_name = left_side.get_string(left_spec->pk_field);
7,228✔
1315
                StringData right_pk_name = right_side.get_string(right_spec->pk_field);
7,228✔
1316
                if (left_pk_name != right_pk_name) {
7,228✔
1317
                    bad_merge(
8✔
1318
                        "Schema mismatch: '%1' has primary key '%2' on one side, but primary key '%3' on the other.",
8✔
1319
                        left_name, left_pk_name, right_pk_name);
8✔
1320
                }
8✔
1321

3,610✔
1322
                if (left_spec->pk_type != right_spec->pk_type) {
7,228✔
1323
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is of type %3 on one side and type "
8✔
1324
                              "%4 on the other.",
8✔
1325
                              left_name, left_pk_name, get_type_name(left_spec->pk_type),
8✔
1326
                              get_type_name(right_spec->pk_type));
8✔
1327
                }
8✔
1328

3,610✔
1329
                if (left_spec->pk_nullable != right_spec->pk_nullable) {
7,228✔
1330
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is nullable on one side, but not "
8✔
1331
                              "the other.",
8✔
1332
                              left_name, left_pk_name);
8✔
1333
                }
8✔
1334

3,610✔
1335
                if (left_spec->is_asymmetric != right_spec->is_asymmetric) {
7,228✔
1336
                    bad_merge("Schema mismatch: '%1' is asymmetric on one side, but not on the other.", left_name);
4✔
1337
                }
4✔
1338
            }
7,228✔
1339
            else {
×
1340
                bad_merge("Schema mismatch: '%1' has a primary key on one side, but not on the other.", left_name);
×
1341
            }
×
1342
        }
7,228✔
1343
        else if (mpark::get_if<Instruction::AddTable::EmbeddedTable>(&left.type)) {
160✔
1344
            if (!mpark::get_if<Instruction::AddTable::EmbeddedTable>(&right.type)) {
160✔
1345
                bad_merge("Schema mismatch: '%1' is an embedded table on one side, but not the other.", left_name);
×
1346
            }
×
1347
        }
160✔
1348

3,690✔
1349
        // Names are the same, PK presence is the same, and if there is a primary
3,690✔
1350
        // key, its name, type, and nullability are the same. Discard both sides.
3,690✔
1351
        left_side.discard();
7,388✔
1352
        right_side.discard();
7,388✔
1353
        return;
7,388✔
1354
    }
7,388✔
1355
}
14,192✔
1356

1357
DEFINE_MERGE(Instruction::EraseTable, Instruction::AddTable)
1358
{
4,288✔
1359
    if (same_table(left, right)) {
4,288✔
1360
        right_side.discard();
×
1361
    }
×
1362
}
4,288✔
1363

1364
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::AddTable);
1365
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::AddTable);
1366
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::AddTable);
1367
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddTable);
1368
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddTable);
1369
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddTable);
1370
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddTable);
1371
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddTable);
1372
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddTable);
1373
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddTable);
1374
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddTable);
1375
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddTable);
1376

1377
/// EraseTable rules
1378

1379
DEFINE_NESTED_MERGE(Instruction::EraseTable)
1380
{
167,536✔
1381
    if (is_prefix_of(outer, inner)) {
167,536!
1382
        inner_side.discard();
38,212✔
1383
    }
38,212✔
1384
}
167,536✔
1385

1386
DEFINE_MERGE(Instruction::EraseTable, Instruction::EraseTable)
1387
{
1,392✔
1388
    if (same_table(left, right)) {
1,392✔
1389
        left_side.discard();
308✔
1390
        right_side.discard();
308✔
1391
    }
308✔
1392
}
1,392✔
1393

1394
// Handled by nesting rule.
1395
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::EraseTable);
1396
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::EraseTable);
1397
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseTable);
1398
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseTable);
1399

1400
DEFINE_MERGE(Instruction::AddColumn, Instruction::EraseTable)
1401
{
9,092✔
1402
    // AddColumn on an erased table handled by nesting.
4,696✔
1403

4,696✔
1404
    if (left.type == Instruction::Payload::Type::Link && same_string(left.link_target_table, right.table)) {
9,092!
1405
        // Erase of a table where the left side adds a link column targeting it.
1406
        Instruction::EraseColumn erase_column;
×
1407
        erase_column.table = right_side.adopt_string(left_side, left.table);
×
1408
        erase_column.field = right_side.adopt_string(left_side, left.field);
×
1409
        right_side.prepend(erase_column);
×
1410
        left_side.discard();
×
1411
    }
×
1412
}
9,092✔
1413

1414
// Handled by nested rule.
1415
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseTable);
1416
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseTable);
1417
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseTable);
1418
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseTable);
1419
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseTable);
1420
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseTable);
1421
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseTable);
1422

1423

1424
/// CreateObject rules
1425

1426
// CreateObject cannot interfere with instructions that have a longer path.
1427
DEFINE_NESTED_MERGE_NOOP(Instruction::CreateObject);
1428

1429
// CreateObject is idempotent.
1430
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::CreateObject);
1431

1432
DEFINE_MERGE(Instruction::EraseObject, Instruction::CreateObject)
1433
{
420,946✔
1434
    if (same_object(left, right)) {
420,946✔
1435
        // CONFLICT: Create and Erase of the same object.
8,494✔
1436
        //
8,494✔
1437
        // RESOLUTION: Erase always wins.
8,494✔
1438
        right_side.discard();
16,476✔
1439
    }
16,476✔
1440
}
420,946✔
1441

1442
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::CreateObject);
1443
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::CreateObject);
1444
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::CreateObject);
1445
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::CreateObject);
1446
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::CreateObject);
1447
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::CreateObject);
1448
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::CreateObject);
1449
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::CreateObject);
1450
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::CreateObject);
1451
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::CreateObject);
1452

1453

1454
/// Erase rules
1455

1456
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1457
{
238,082✔
1458
    if (is_prefix_of(outer, inner)) {
238,082!
1459
        // Erase always wins.
10,238✔
1460
        inner_side.discard();
20,540✔
1461
    }
20,540✔
1462
}
238,082✔
1463

1464
DEFINE_MERGE(Instruction::EraseObject, Instruction::EraseObject)
1465
{
144,738✔
1466
    if (same_object(left, right)) {
144,738✔
1467
        // We keep the most recent erase. This prevents the situation where a
7,300✔
1468
        // high number of EraseObject instructions in the past trumps a
7,300✔
1469
        // Erase-Create pair in the future.
7,300✔
1470
        if (right_side.timestamp() < left_side.timestamp()) {
14,052✔
1471
            right_side.discard();
7,026✔
1472
        }
7,026✔
1473
        else {
7,026✔
1474
            left_side.discard();
7,026✔
1475
        }
7,026✔
1476
    }
14,052✔
1477
}
144,738✔
1478

1479
// Handled by nested merge.
1480
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseObject);
1481
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseObject);
1482
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::EraseObject);
1483
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseObject);
1484
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseObject);
1485
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseObject);
1486
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseObject);
1487
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseObject);
1488
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseObject);
1489
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseObject);
1490

1491

1492
/// Set rules
1493

1494
DEFINE_NESTED_MERGE(Instruction::Update)
1495
{
32,690✔
1496
    using Type = Instruction::Payload::Type;
32,690✔
1497

15,714✔
1498
    if (outer.value.type == Type::ObjectValue || outer.value.type == Type::Dictionary) {
32,690!
1499
        // Creating an embedded object or a dictionary is an idempotent
32✔
1500
        // operation, and should not eliminate updates to the subtree.
32✔
1501
        return;
64✔
1502
    }
64✔
1503

15,682✔
1504
    // Setting a value higher up in the hierarchy overwrites any modification to
15,682✔
1505
    // the inner value, regardless of when this happened.
15,682✔
1506
    if (is_prefix_of(outer, inner)) {
32,626!
1507
        inner_side.discard();
80✔
1508
    }
80✔
1509
}
32,626✔
1510

1511
DEFINE_MERGE(Instruction::Update, Instruction::Update)
1512
{
24,604✔
1513
    // The two instructions are at the same level of nesting.
12,650✔
1514

12,650✔
1515
    using Type = Instruction::Payload::Type;
24,604✔
1516

12,650✔
1517
    if (same_path(left, right)) {
24,604✔
1518
        bool left_is_default = false;
9,400✔
1519
        bool right_is_default = false;
9,400✔
1520
        if (!(left.is_array_update() == right.is_array_update())) {
9,400✔
1521
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
×
1522
        }
×
1523

5,162✔
1524
        if (!left.is_array_update()) {
9,400✔
1525
            if (right.is_array_update()) {
9,164✔
1526
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
×
1527
            }
×
1528
            left_is_default = left.is_default;
9,164✔
1529
            right_is_default = right.is_default;
9,164✔
1530
        }
9,164✔
1531
        else if (!(left.prior_size == right.prior_size)) {
236✔
1532
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1533
        }
×
1534

5,162✔
1535
        if (left.value.type != right.value.type) {
9,400✔
1536
            // Embedded object / dictionary creation should always lose to an
140✔
1537
            // Update(value), because these structures are nested, and we need to
140✔
1538
            // discard any update inside the structure.
140✔
1539
            if (left.value.type == Type::Dictionary || left.value.type == Type::ObjectValue) {
228✔
1540
                left_side.discard();
24✔
1541
                return;
24✔
1542
            }
24✔
1543
            else if (right.value.type == Type::Dictionary || right.value.type == Type::ObjectValue) {
204✔
1544
                right_side.discard();
24✔
1545
                return;
24✔
1546
            }
24✔
1547
        }
9,352✔
1548

5,138✔
1549
        // CONFLICT: Two updates of the same element.
5,138✔
1550
        //
5,138✔
1551
        // RESOLUTION: Suppress the effect of the UPDATE operation with the lower
5,138✔
1552
        // timestamp. Note that the timestamps can never be equal. This is
5,138✔
1553
        // achieved on both sides by discarding the received UPDATE operation if
5,138✔
1554
        // it has a lower timestamp than the previously applied UPDATE operation.
5,138✔
1555
        if (left_is_default == right_is_default) {
9,352✔
1556
            if (left_side.timestamp() < right_side.timestamp()) {
9,194✔
1557
                left_side.discard(); // --->
4,800✔
1558
            }
4,800✔
1559
            else {
4,394✔
1560
                right_side.discard(); // <---
4,394✔
1561
            }
4,394✔
1562
        }
9,194✔
1563
        else {
158✔
1564
            if (left_is_default) {
158✔
1565
                left_side.discard();
86✔
1566
            }
86✔
1567
            else {
72✔
1568
                right_side.discard();
72✔
1569
            }
72✔
1570
        }
158✔
1571
    }
9,352✔
1572
}
24,604✔
1573

1574
DEFINE_MERGE(Instruction::AddInteger, Instruction::Update)
1575
{
2,594✔
1576
    // The two instructions are at the same level of nesting.
1,476✔
1577

1,476✔
1578
    if (same_path(left, right)) {
2,594✔
1579
        // CONFLICT: Add vs Set of the same element.
288✔
1580
        //
288✔
1581
        // RESOLUTION: If the Add was later than the Set, add its value to
288✔
1582
        // the payload of the Set instruction. Otherwise, discard it.
288✔
1583

288✔
1584
        if (!(right.value.type == Instruction::Payload::Type::Int || right.value.is_null())) {
536✔
1585
            bad_merge(right_side, right,
×
1586
                      "Merge error: right.value.type == Instruction::Payload::Type::Int || right.value.is_null()");
×
1587
        }
×
1588

288✔
1589
        bool right_is_default = !right.is_array_update() && right.is_default;
536✔
1590

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

184✔
1600
            // Wrapping add
184✔
1601
            uint64_t ua = uint64_t(right.value.data.integer);
340✔
1602
            uint64_t ub = uint64_t(left.value);
340✔
1603
            right.value.data.integer = int64_t(ua + ub);
340✔
1604
        }
340✔
1605
        else {
140✔
1606
            left_side.discard();
140✔
1607
        }
140✔
1608
    }
536✔
1609
}
2,594✔
1610

1611
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::Update);
1612

1613
DEFINE_MERGE(Instruction::EraseColumn, Instruction::Update)
1614
{
×
1615
    if (same_column(left, right)) {
×
1616
        right_side.discard();
×
1617
    }
×
1618
}
×
1619

1620
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::Update)
1621
{
49,292✔
1622
    if (same_container(left, right)) {
49,292✔
1623
        REALM_ASSERT(right.is_array_update());
8,404✔
1624
        if (!(left.prior_size == right.prior_size)) {
8,404✔
1625
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1626
        }
×
1627
        if (!(left.index() <= left.prior_size)) {
8,404✔
1628
            bad_merge(left_side, left, "Merge error: left.index() <= left.prior_size");
×
1629
        }
×
1630
        if (!(right.index() < right.prior_size)) {
8,404✔
1631
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1632
        }
×
1633
        right.prior_size += 1;
8,404✔
1634
        if (right.index() >= left.index()) {
8,404✔
1635
            right.index() += 1; // --->
3,452✔
1636
        }
3,452✔
1637
    }
8,404✔
1638
}
49,292✔
1639

1640
DEFINE_MERGE(Instruction::ArrayMove, Instruction::Update)
1641
{
24✔
1642
    if (same_container(left, right)) {
24✔
1643
        REALM_ASSERT(right.is_array_update());
×
1644

1645
        if (!(left.index() < left.prior_size)) {
×
1646
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1647
        }
×
1648
        if (!(right.index() < right.prior_size)) {
×
1649
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1650
        }
×
1651

1652
        // FIXME: This marks both sides as dirty, even when they are unmodified.
1653
        merge_get_vs_move(right.index(), left.index(), left.ndx_2);
×
1654
    }
×
1655
}
24✔
1656

1657
DEFINE_MERGE(Instruction::ArrayErase, Instruction::Update)
1658
{
10,710✔
1659
    if (same_container(left, right)) {
10,710✔
1660
        REALM_ASSERT(right.is_array_update());
2,276✔
1661
        if (!(left.prior_size == right.prior_size)) {
2,276✔
1662
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1663
        }
×
1664
        if (!(left.index() < left.prior_size)) {
2,276✔
1665
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1666
        }
×
1667
        if (!(right.index() < right.prior_size)) {
2,276✔
1668
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1669
        }
×
1670

1,016✔
1671
        // CONFLICT: Update of a removed element.
1,016✔
1672
        //
1,016✔
1673
        // RESOLUTION: Discard the UPDATE operation received on the right side.
1,016✔
1674
        right.prior_size -= 1;
2,276✔
1675

1,016✔
1676
        if (left.index() == right.index()) {
2,276✔
1677
            // CONFLICT: Update of a removed element.
212✔
1678
            //
212✔
1679
            // RESOLUTION: Discard the UPDATE operation received on the right side.
212✔
1680
            right_side.discard();
520✔
1681
        }
520✔
1682
        else if (right.index() > left.index()) {
1,756✔
1683
            right.index() -= 1;
992✔
1684
        }
992✔
1685
    }
2,276✔
1686
}
10,710✔
1687

1688
// Handled by nested rule
1689
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::Update);
1690
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::Update);
1691
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::Update);
1692

1693

1694
/// AddInteger rules
1695

1696
DEFINE_NESTED_MERGE_NOOP(Instruction::AddInteger);
1697
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddInteger);
1698
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddInteger);
1699
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddInteger);
1700
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddInteger);
1701
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddInteger);
1702
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddInteger);
1703
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddInteger);
1704
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddInteger);
1705
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddInteger);
1706

1707

1708
/// AddColumn rules
1709

1710
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1711

1712
DEFINE_MERGE(Instruction::AddColumn, Instruction::AddColumn)
1713
{
35,544✔
1714
    if (same_column(left, right)) {
35,544✔
1715
        StringData left_name = left_side.get_string(left.field);
9,332✔
1716
        if (left.type != right.type) {
9,332✔
1717
            bad_merge(
8✔
1718
                "Schema mismatch: Property '%1' in class '%2' is of type %3 on one side and type %4 on the other.",
8✔
1719
                left_name, left_side.get_string(left.table), get_type_name(left.type), get_type_name(right.type));
8✔
1720
        }
8✔
1721

4,662✔
1722
        if (left.nullable != right.nullable) {
9,332✔
1723
            bad_merge("Schema mismatch: Property '%1' in class '%2' is nullable on one side and not on the other.",
8✔
1724
                      left_name, left_side.get_string(left.table));
8✔
1725
        }
8✔
1726

4,662✔
1727
        if (left.collection_type != right.collection_type) {
9,332✔
1728
            auto collection_type_name = [](Instruction::AddColumn::CollectionType type) -> const char* {
×
1729
                switch (type) {
×
1730
                    case Instruction::AddColumn::CollectionType::Single:
×
1731
                        return "single value";
×
1732
                    case Instruction::AddColumn::CollectionType::List:
×
1733
                        return "list";
×
1734
                    case Instruction::AddColumn::CollectionType::Dictionary:
×
1735
                        return "dictionary";
×
1736
                    case Instruction::AddColumn::CollectionType::Set:
×
1737
                        return "set";
×
1738
                }
×
1739
                REALM_TERMINATE("");
1740
            };
×
1741

1742
            std::stringstream ss;
×
1743
            const char* left_type = collection_type_name(left.collection_type);
×
1744
            const char* right_type = collection_type_name(right.collection_type);
×
1745
            bad_merge("Schema mismatch: Property '%1' in class '%2' is a %3 on one side, and a %4 on the other.",
×
1746
                      left_name, left_side.get_string(left.table), left_type, right_type);
×
1747
        }
×
1748

4,662✔
1749
        if (left.type == Instruction::Payload::Type::Link) {
9,332✔
1750
            StringData left_target = left_side.get_string(left.link_target_table);
1,996✔
1751
            StringData right_target = right_side.get_string(right.link_target_table);
1,996✔
1752
            if (left_target != right_target) {
1,996✔
1753
                bad_merge("Schema mismatch: Link property '%1' in class '%2' points to class '%3' on one side and to "
8✔
1754
                          "'%4' on the other.",
8✔
1755
                          left_name, left_side.get_string(left.table), left_target, right_target);
8✔
1756
            }
8✔
1757
        }
1,996✔
1758

4,662✔
1759
        // Name, type, nullability and link targets match -- discard both
4,662✔
1760
        // sides and proceed.
4,662✔
1761
        left_side.discard();
9,332✔
1762
        right_side.discard();
9,332✔
1763
    }
9,332✔
1764
}
35,544✔
1765

1766
DEFINE_MERGE(Instruction::EraseColumn, Instruction::AddColumn)
1767
{
×
1768
    if (same_column(left, right)) {
×
1769
        right_side.discard();
×
1770
    }
×
1771
}
×
1772

1773
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddColumn);
1774
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddColumn);
1775
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddColumn);
1776
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddColumn);
1777
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddColumn);
1778
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddColumn);
1779

1780

1781
/// EraseColumn rules
1782

1783
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1784

1785
DEFINE_MERGE(Instruction::EraseColumn, Instruction::EraseColumn)
1786
{
×
1787
    if (same_column(left, right)) {
×
1788
        left_side.discard();
×
1789
        right_side.discard();
×
1790
    }
×
1791
}
×
1792

1793
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseColumn);
1794
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseColumn);
1795
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseColumn);
1796
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseColumn);
1797
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseColumn);
1798
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseColumn);
1799

1800
/// ArrayInsert rules
1801

1802
DEFINE_NESTED_MERGE(Instruction::ArrayInsert)
1803
{
16✔
1804
    if (is_container_prefix_of(outer, inner)) {
16!
1805
        auto& index = corresponding_index_in_path(outer, inner);
16✔
1806
        if (index >= outer.index()) {
16!
1807
            index += 1;
8✔
1808
        }
8✔
1809
    }
16✔
1810
}
16✔
1811

1812
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::ArrayInsert)
1813
{
53,674✔
1814
    if (same_container(left, right)) {
53,674✔
1815
        if (!(left.prior_size == right.prior_size)) {
14,404✔
1816
            bad_merge(right_side, right, "Exception: left.prior_size == right.prior_size");
×
1817
        }
×
1818
        left.prior_size++;
14,404✔
1819
        right.prior_size++;
14,404✔
1820

5,614✔
1821
        if (left.index() > right.index()) {
14,404✔
1822
            left.index() += 1; // --->
2,944✔
1823
        }
2,944✔
1824
        else if (left.index() < right.index()) {
11,460✔
1825
            right.index() += 1; // <---
2,948✔
1826
        }
2,948✔
1827
        else { // left index == right index
8,512✔
1828
            // CONFLICT: Two element insertions at the same position.
3,348✔
1829
            //
3,348✔
1830
            // Resolution: Place the inserted elements in order of increasing
3,348✔
1831
            // timestamp. Note that the timestamps can never be equal.
3,348✔
1832
            if (left_side.timestamp() < right_side.timestamp()) {
8,512✔
1833
                right.index() += 1;
4,252✔
1834
            }
4,252✔
1835
            else {
4,260✔
1836
                left.index() += 1;
4,260✔
1837
            }
4,260✔
1838
        }
8,512✔
1839
    }
14,404✔
1840
}
53,674✔
1841

1842
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayInsert)
1843
{
×
1844
    if (same_container(left, right)) {
×
1845
        left.prior_size += 1;
×
1846

1847
        // Left insertion vs right removal
1848
        if (right.index() > left.index()) {
×
1849
            right.index() -= 1; // --->
×
1850
        }
×
1851
        else {
×
1852
            left.index() += 1; // <---
×
1853
        }
×
1854

1855
        // Left insertion vs left insertion
1856
        if (right.index() < left.ndx_2) {
×
1857
            left.ndx_2 += 1; // <---
×
1858
        }
×
1859
        else if (right.index() > left.ndx_2) {
×
1860
            right.index() += 1; // --->
×
1861
        }
×
1862
        else { // right.index() == left.ndx_2
×
1863
            // CONFLICT: Insertion and movement to same position.
1864
            //
1865
            // RESOLUTION: Place the two elements in order of increasing
1866
            // timestamp. Note that the timestamps can never be equal.
1867
            if (left_side.timestamp() < right_side.timestamp()) {
×
1868
                left.ndx_2 += 1; // <---
×
1869
            }
×
1870
            else {
×
1871
                right.index() += 1; // --->
×
1872
            }
×
1873
        }
×
1874
    }
×
1875
}
×
1876

1877
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayInsert)
1878
{
22,766✔
1879
    if (same_container(left, right)) {
22,766✔
1880
        if (!(left.prior_size == right.prior_size)) {
6,708✔
1881
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1882
        }
×
1883
        if (!(left.index() < left.prior_size)) {
6,708✔
1884
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1885
        }
×
1886
        if (!(right.index() <= right.prior_size)) {
6,708✔
1887
            bad_merge(left_side, left, "Merge error: right.index() <= right.prior_size");
×
1888
        }
×
1889

2,610✔
1890
        left.prior_size++;
6,708✔
1891
        right.prior_size--;
6,708✔
1892
        if (right.index() <= left.index()) {
6,708✔
1893
            left.index() += 1; // --->
2,564✔
1894
        }
2,564✔
1895
        else {
4,144✔
1896
            right.index() -= 1; // <---
4,144✔
1897
        }
4,144✔
1898
    }
6,708✔
1899
}
22,766✔
1900

1901
// Handled by nested rules
1902
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayInsert);
1903
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayInsert);
1904
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayInsert);
1905

1906

1907
/// ArrayMove rules
1908

1909
DEFINE_NESTED_MERGE(Instruction::ArrayMove)
1910
{
×
1911
    if (is_container_prefix_of(outer, inner)) {
×
1912
        auto& index = corresponding_index_in_path(outer, inner);
×
1913
        merge_get_vs_move(outer.index(), index, outer.ndx_2);
×
1914
    }
×
1915
}
×
1916

1917
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayMove)
1918
{
28✔
1919
    if (same_container(left, right)) {
28✔
1920
        if (!(left.prior_size == right.prior_size)) {
28✔
1921
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1922
        }
×
1923
        if (!(left.index() < left.prior_size)) {
28✔
1924
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1925
        }
×
1926
        if (!(right.index() < right.prior_size)) {
28✔
1927
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1928
        }
×
1929
        if (!(left.ndx_2 < left.prior_size)) {
28✔
1930
            bad_merge(left_side, left, "Merge error: left.ndx_2 < left.prior_size");
×
1931
        }
×
1932
        if (!(right.ndx_2 < right.prior_size)) {
28✔
1933
            bad_merge(right_side, right, "Merge error: right.ndx_2 < right.prior_size");
×
1934
        }
×
1935

14✔
1936
        if (left.index() < right.index()) {
28✔
1937
            right.index() -= 1; // <---
4✔
1938
        }
4✔
1939
        else if (left.index() > right.index()) {
24✔
1940
            left.index() -= 1; // --->
4✔
1941
        }
4✔
1942
        else {
20✔
1943
            // CONFLICT: Two movements of same element.
10✔
1944
            //
10✔
1945
            // RESOLUTION: Respect the MOVE operation associated with the higher
10✔
1946
            // timestamp. If the previously applied MOVE operation has the higher
10✔
1947
            // timestamp, discard the received MOVE operation, otherwise use the
10✔
1948
            // previously applied MOVE operation to transform the received MOVE
10✔
1949
            // operation. Note that the timestamps are never equal.
10✔
1950
            if (left_side.timestamp() < right_side.timestamp()) {
20✔
1951
                right.index() = left.ndx_2; // <---
12✔
1952
                left_side.discard();        // --->
12✔
1953
                if (right.index() == right.ndx_2) {
12✔
1954
                    right_side.discard(); // <---
×
1955
                }
×
1956
            }
12✔
1957
            else {
8✔
1958
                left.index() = right.ndx_2; // --->
8✔
1959
                if (left.index() == left.ndx_2) {
8✔
1960
                    left_side.discard(); // --->
×
1961
                }
×
1962
                right_side.discard(); // <---
8✔
1963
            }
8✔
1964
            return;
20✔
1965
        }
20✔
1966

4✔
1967
        // Left insertion vs right removal
4✔
1968
        if (left.ndx_2 > right.index()) {
8✔
1969
            left.ndx_2 -= 1; // --->
4✔
1970
        }
4✔
1971
        else {
4✔
1972
            right.index() += 1; // <---
4✔
1973
        }
4✔
1974

4✔
1975
        // Left removal vs right insertion
4✔
1976
        if (left.index() < right.ndx_2) {
8✔
1977
            right.ndx_2 -= 1; // <---
4✔
1978
        }
4✔
1979
        else {
4✔
1980
            left.index() += 1; // --->
4✔
1981
        }
4✔
1982

4✔
1983
        // Left insertion vs right insertion
4✔
1984
        if (left.ndx_2 < right.ndx_2) {
8✔
1985
            right.ndx_2 += 1; // <---
4✔
1986
        }
4✔
1987
        else if (left.ndx_2 > right.ndx_2) {
4✔
1988
            left.ndx_2 += 1; // --->
4✔
1989
        }
4✔
1990
        else { // left.ndx_2 == right.ndx_2
×
1991
            // CONFLICT: Two elements moved to the same position
1992
            //
1993
            // RESOLUTION: Place the moved elements in order of increasing
1994
            // timestamp. Note that the timestamps can never be equal.
1995
            if (left_side.timestamp() < right_side.timestamp()) {
×
1996
                right.ndx_2 += 1; // <---
×
1997
            }
×
1998
            else {
×
1999
                left.ndx_2 += 1; // --->
×
2000
            }
×
2001
        }
×
2002

4✔
2003
        if (left.index() == left.ndx_2) {
8✔
2004
            left_side.discard(); // --->
×
2005
        }
×
2006
        if (right.index() == right.ndx_2) {
8✔
2007
            right_side.discard(); // <---
×
2008
        }
×
2009
    }
8✔
2010
}
28✔
2011

2012
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayMove)
2013
{
×
2014
    if (same_container(left, right)) {
×
2015
        if (!(left.prior_size == right.prior_size)) {
×
2016
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
2017
        }
×
2018
        if (!(left.index() < left.prior_size)) {
×
2019
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
2020
        }
×
2021
        if (!(right.index() < right.prior_size)) {
×
2022
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
2023
        }
×
2024

2025
        right.prior_size -= 1;
×
2026

2027
        if (left.index() == right.index()) {
×
2028
            // CONFLICT: Removal of a moved element.
2029
            //
2030
            // RESOLUTION: Discard the received MOVE operation on the left side, and
2031
            // use the previously applied MOVE operation to transform the received
2032
            // REMOVE operation on the right side.
2033
            left.index() = right.ndx_2; // --->
×
2034
            right_side.discard();       // <---
×
2035
        }
×
2036
        else {
×
2037
            // Left removal vs right removal
2038
            if (left.index() > right.index()) {
×
2039
                left.index() -= 1; // --->
×
2040
            }
×
2041
            else {                  // left.index() < right.index()
×
2042
                right.index() -= 1; // <---
×
2043
            }
×
2044
            // Left removal vs right insertion
2045
            if (left.index() >= right.ndx_2) {
×
2046
                left.index() += 1; // --->
×
2047
            }
×
2048
            else {
×
2049
                right.ndx_2 -= 1; // <---
×
2050
            }
×
2051

2052
            if (right.index() == right.ndx_2) {
×
2053
                right_side.discard(); // <---
×
2054
            }
×
2055
        }
×
2056
    }
×
2057
}
×
2058

2059
// Handled by nested rule.
2060
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayMove);
2061
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayMove);
2062
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayMove);
2063

2064

2065
/// ArrayErase rules
2066

2067
DEFINE_NESTED_MERGE(Instruction::ArrayErase)
2068
{
8✔
2069
    if (is_prefix_of(outer, inner)) {
8!
2070
        // Erase of subtree - inner instruction touches the subtree.
2071
        inner_side.discard();
×
2072
    }
×
2073
    else if (is_container_prefix_of(outer, inner)) {
8!
2074
        // Erase of a sibling element in the container - adjust the path.
4✔
2075
        auto& index = corresponding_index_in_path(outer, inner);
8✔
2076
        if (outer.index() < index) {
8!
2077
            index -= 1;
8✔
2078
        }
8✔
2079
        else {
×
2080
            REALM_ASSERT(index != outer.index());
×
2081
        }
×
2082
    }
8✔
2083
}
8✔
2084

2085
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayErase)
2086
{
2,650✔
2087
    if (same_container(left, right)) {
2,650✔
2088
        if (!(left.prior_size == right.prior_size)) {
980✔
2089
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
2090
        }
×
2091
        if (!(left.index() < left.prior_size)) {
980✔
2092
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
2093
        }
×
2094
        if (!(right.index() < right.prior_size)) {
980✔
2095
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
2096
        }
×
2097

406✔
2098
        left.prior_size -= 1;
980✔
2099
        right.prior_size -= 1;
980✔
2100

406✔
2101
        if (left.index() > right.index()) {
980✔
2102
            left.index() -= 1; // --->
378✔
2103
        }
378✔
2104
        else if (left.index() < right.index()) {
602✔
2105
            right.index() -= 1; // <---
382✔
2106
        }
382✔
2107
        else { // left.index() == right.index()
220✔
2108
            // CONFLICT: Two removals of the same element.
124✔
2109
            //
124✔
2110
            // RESOLUTION: On each side, discard the received REMOVE operation.
124✔
2111
            left_side.discard();  // --->
220✔
2112
            right_side.discard(); // <---
220✔
2113
        }
220✔
2114
    }
980✔
2115
}
2,650✔
2116

2117
// Handled by nested rules.
2118
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayErase);
2119
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayErase);
2120
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayErase);
2121

2122

2123
/// Clear rules
2124

2125
DEFINE_NESTED_MERGE(Instruction::Clear)
2126
{
754✔
2127
    // Note: Clear instructions do not have an index in their path.
314✔
2128
    if (is_prefix_of(outer, inner)) {
754!
2129
        inner_side.discard();
332✔
2130
    }
332✔
2131
}
754✔
2132

2133
DEFINE_MERGE(Instruction::Clear, Instruction::Clear)
2134
{
16✔
2135
    if (same_path(left, right)) {
16✔
2136
        // CONFLICT: Two clears of the same container.
8✔
2137
        //
8✔
2138
        // RESOLUTION: Discard the clear with the lower timestamp. This has the
8✔
2139
        // effect of preserving insertions that came after the clear from the
8✔
2140
        // side that has the higher timestamp.
8✔
2141
        if (left_side.timestamp() < right_side.timestamp()) {
16✔
2142
            left_side.discard();
12✔
2143
        }
12✔
2144
        else {
4✔
2145
            right_side.discard();
4✔
2146
        }
4✔
2147
    }
16✔
2148
}
16✔
2149

2150
DEFINE_MERGE(Instruction::SetInsert, Instruction::Clear)
2151
{
8✔
2152
    if (same_path(left, right)) {
8!
2153
        left_side.discard();
4✔
2154
    }
4✔
2155
}
8✔
2156

2157
DEFINE_MERGE(Instruction::SetErase, Instruction::Clear)
2158
{
8✔
2159
    if (same_path(left, right)) {
8!
2160
        left_side.discard();
4✔
2161
    }
4✔
2162
}
8✔
2163

2164

2165
/// SetInsert rules
2166

2167
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2168

2169
DEFINE_MERGE(Instruction::SetInsert, Instruction::SetInsert)
2170
{
156✔
2171
    if (same_path(left, right)) {
156✔
2172
        // CONFLICT: Two inserts into the same set.
76✔
2173
        //
76✔
2174
        // RESOLUTION: If the values are the same, discard the insertion with the lower timestamp. Otherwise,
76✔
2175
        // do nothing.
76✔
2176
        //
76✔
2177
        // NOTE: Set insertion is idempotent. Keeping the instruction with the higher timestamp is necessary
76✔
2178
        // because we want to maintain associativity in the case where intermittent erases (as ordered by
76✔
2179
        // timestamp) arrive at a later point in time.
76✔
2180
        if (same_payload(left.value, right.value)) {
152✔
2181
            if (left_side.timestamp() < right_side.timestamp()) {
24✔
2182
                left_side.discard();
12✔
2183
            }
12✔
2184
            else {
12✔
2185
                right_side.discard();
12✔
2186
            }
12✔
2187
        }
24✔
2188
    }
152✔
2189
}
156✔
2190

2191
DEFINE_MERGE(Instruction::SetErase, Instruction::SetInsert)
2192
{
64✔
2193
    if (same_path(left, right)) {
64✔
2194
        // CONFLICT: Insertion and erase in the same set.
32✔
2195
        //
32✔
2196
        // RESOLUTION: If the inserted/erased values are the same, discard the instruction with the lower
32✔
2197
        // timestamp. Otherwise, do nothing.
32✔
2198
        //
32✔
2199
        // Note: Set insertion and erase are both idempotent. Keeping the instruction with the higher
32✔
2200
        // timestamp is necessary because we want to maintain associativity.
32✔
2201
        if (same_payload(left.value, right.value)) {
64✔
2202
            if (left_side.timestamp() < right_side.timestamp()) {
×
2203
                left_side.discard();
×
2204
            }
×
2205
            else {
×
2206
                right_side.discard();
×
2207
            }
×
2208
        }
×
2209
    }
64✔
2210
}
64✔
2211

2212

2213
/// SetErase rules.
2214

2215
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2216

2217
DEFINE_MERGE(Instruction::SetErase, Instruction::SetErase)
2218
{
×
2219
    if (same_path(left, right)) {
×
2220
        // CONFLICT: Two erases in the same set.
2221
        //
2222
        // RESOLUTION: If the values are the same, discard the instruction with the lower timestamp.
2223
        // Otherwise, do nothing.
2224
        if (left.value == right.value) {
×
2225
            if (left_side.timestamp() < right_side.timestamp()) {
×
2226
                left_side.discard();
×
2227
            }
×
2228
            else {
×
2229
                right_side.discard();
×
2230
            }
×
2231
        }
×
2232
    }
×
2233
}
×
2234

2235

2236
///
2237
/// END OF MERGE RULES!
2238
///
2239

2240
template <class Left, class Right>
2241
void merge_instructions_2(Left& left, Right& right, MajorSide& left_side, MinorSide& right_side)
2242
{
2,575,816✔
2243
    Merge<Left, Right>::merge(left, right, left_side, right_side);
2,575,816✔
2244
}
2,575,816✔
2245

2246
template <class Outer, class Inner, class OuterSide, class InnerSide>
2247
void merge_nested_2(Outer& outer, Inner& inner, OuterSide& outer_side, InnerSide& inner_side)
2248
{
1,080,112✔
2249
    MergeNested<Outer>::merge(outer, inner, outer_side, inner_side);
1,080,112✔
2250
}
1,080,112✔
2251

2252
template <class OuterSide, class InnerSide>
2253
void merge_nested(OuterSide& outer_side, InnerSide& inner_side)
2254
{
1,080,112✔
2255
    outer_side.get().visit([&](auto& outer) {
1,080,110✔
2256
        inner_side.get().visit([&](auto& inner) {
1,080,110✔
2257
            merge_nested_2(outer, inner, outer_side, inner_side);
1,080,110✔
2258
        });
1,080,110✔
2259
    });
1,080,110✔
2260
}
1,080,112✔
2261

2262
void TransformerImpl::merge_instructions(MajorSide& their_side, MinorSide& our_side)
2263
{
2,634,890✔
2264
    // FIXME: Find a way to avoid heap-copies of the path.
1,317,526✔
2265
    Instruction their_before = their_side.get();
2,634,890✔
2266
    Instruction our_before = our_side.get();
2,634,890✔
2267

1,317,526✔
2268
    if (their_side.get().get_if<Instruction::Update>()) {
2,634,890✔
2269
        REALM_ASSERT(their_side.m_path_len > 2);
171,706✔
2270
    }
171,706✔
2271
    if (our_side.get().get_if<Instruction::Update>()) {
2,634,890✔
2272
        REALM_ASSERT(our_side.m_path_len > 2);
223,646✔
2273
    }
223,646✔
2274
    if (their_side.get().get_if<Instruction::EraseObject>()) {
2,634,890✔
2275
        REALM_ASSERT(their_side.m_path_len == 2);
542,022✔
2276
    }
542,022✔
2277
    if (our_side.get().get_if<Instruction::EraseObject>()) {
2,634,890✔
2278
        REALM_ASSERT(our_side.m_path_len == 2);
620,660✔
2279
    }
620,660✔
2280

1,317,526✔
2281
    // Update selections on the major side (outer loop) according to events on
1,317,526✔
2282
    // the minor side (inner loop). The selection may only be impacted if the
1,317,526✔
2283
    // instruction level is lower (i.e. at a higher point in the hierarchy).
1,317,526✔
2284
    if (our_side.m_path_len < their_side.m_path_len) {
2,634,890✔
2285
        merge_nested(our_side, their_side);
451,270✔
2286
        if (their_side.was_discarded)
451,270✔
2287
            return;
29,628✔
2288
    }
2,183,620✔
2289
    else if (our_side.m_path_len > their_side.m_path_len) {
2,183,620✔
2290
        merge_nested(their_side, our_side);
628,842✔
2291
        if (our_side.was_discarded)
628,842✔
2292
            return;
29,536✔
2293
    }
2,575,726✔
2294

1,287,548✔
2295
    if (!their_side.was_discarded && !our_side.was_discarded) {
2,575,754✔
2296
        // Even if the instructions were nested, we must still perform a regular
1,287,574✔
2297
        // merge, because link-related instructions contain information from higher
1,287,574✔
2298
        // levels (both rows, columns, and tables).
1,287,574✔
2299
        //
1,287,574✔
2300
        // FIXME: This condition goes away when dangling links are implemented.
1,287,574✔
2301
        their_side.get().visit([&](auto& their_instruction) {
2,575,812✔
2302
            our_side.get().visit([&](auto& our_instruction) {
2,575,814✔
2303
                merge_instructions_2(their_instruction, our_instruction, their_side, our_side);
2,575,814✔
2304
            });
2,575,814✔
2305
        });
2,575,812✔
2306
    }
2,575,752✔
2307

1,287,548✔
2308
    // Note: `left` and/or `right` may be dangling at this point due to
1,287,548✔
2309
    // discard/prepend. However, if they were not discarded, their iterators are
1,287,548✔
2310
    // required to point to an instruction of the same type.
1,287,548✔
2311
    if (!their_side.was_discarded && !their_side.was_replaced) {
2,575,726✔
2312
        const auto& their_after = their_side.get();
2,538,000✔
2313
        if (!(their_after == their_before)) {
2,538,000✔
2314
            their_side.m_changeset->set_dirty(true);
27,138✔
2315
        }
27,138✔
2316
    }
2,538,000✔
2317

1,287,548✔
2318
    if (!our_side.was_discarded && !our_side.was_replaced) {
2,575,726✔
2319
        const auto& our_after = our_side.get();
2,538,472✔
2320
        if (!(our_after == our_before)) {
2,538,472✔
2321
            our_side.m_changeset->set_dirty(true);
27,142✔
2322
        }
27,142✔
2323
    }
2,538,472✔
2324
}
2,575,726✔
2325

2326
} // anonymous namespace
2327

2328
namespace realm::sync {
2329
void Transformer::merge_changesets(file_ident_type local_file_ident, util::Span<Changeset> their_changesets,
2330
                                   util::Span<Changeset*> our_changesets, util::Logger& logger)
2331
{
415,442✔
2332
    REALM_ASSERT(our_changesets.size() != 0);
415,442✔
2333
    REALM_ASSERT(their_changesets.size() != 0);
415,442✔
2334
    bool trace = false;
415,442✔
2335
#if REALM_DEBUG && !REALM_UWP
415,442✔
2336
    // FIXME: Not thread-safe (use config parameter instead and confine environment reading to test/test_all.cpp)
209,610✔
2337
    const char* trace_p = ::getenv("UNITTEST_TRACE_TRANSFORM");
415,442✔
2338
    trace = (trace_p && StringData{trace_p} != "no");
415,442!
2339
    static std::mutex trace_mutex;
415,442✔
2340
    util::Optional<std::unique_lock<std::mutex>> l;
415,442✔
2341
    if (trace) {
415,442✔
2342
        l = std::unique_lock<std::mutex>{trace_mutex};
×
2343
    }
×
2344
#endif
415,442✔
2345
    TransformerImpl transformer{trace};
415,442✔
2346

209,610✔
2347
    _impl::ChangesetIndex their_index;
415,442✔
2348
    size_t their_num_instructions = 0;
415,442✔
2349
    size_t our_num_instructions = 0;
415,442✔
2350

209,610✔
2351
    // Loop through all instructions on both sides and build conflict groups.
209,610✔
2352
    // This causes the index to merge ranges that are connected by instructions
209,610✔
2353
    // on the left side, but which aren't connected on the right side.
209,610✔
2354
    // FIXME: The conflict groups can be persisted as part of the changeset to
209,610✔
2355
    // skip this step in the future.
209,610✔
2356
    for (size_t i = 0; i < their_changesets.size(); ++i) {
855,558✔
2357
        size_t num_instructions = their_changesets[i].size();
440,116✔
2358
        their_num_instructions += num_instructions;
440,116✔
2359
        logger.trace("Scanning incoming changeset [%1/%2] (%3 instructions)", i + 1, their_changesets.size(),
440,116✔
2360
                     num_instructions);
440,116✔
2361

220,440✔
2362
        their_index.scan_changeset(their_changesets[i]);
440,116✔
2363
    }
440,116✔
2364
    for (size_t i = 0; i < our_changesets.size(); ++i) {
10,322,324✔
2365
        Changeset& our_changeset = *our_changesets[i];
9,906,882✔
2366
        size_t num_instructions = our_changeset.size();
9,906,882✔
2367
        our_num_instructions += num_instructions;
9,906,882✔
2368
        logger.trace("Scanning local changeset [%1/%2] (%3 instructions)", i + 1, our_changesets.size(),
9,906,882✔
2369
                     num_instructions);
9,906,882✔
2370

4,945,930✔
2371
        their_index.scan_changeset(our_changeset);
9,906,882✔
2372
    }
9,906,882✔
2373

209,610✔
2374
    // Build the index.
209,610✔
2375
    for (size_t i = 0; i < their_changesets.size(); ++i) {
855,580✔
2376
        logger.trace("Indexing incoming changeset [%1/%2] (%3 instructions)", i + 1, their_changesets.size(),
440,138✔
2377
                     their_changesets[i].size());
440,138✔
2378
        their_index.add_changeset(their_changesets[i]);
440,138✔
2379
    }
440,138✔
2380

209,610✔
2381
    logger.debug("Finished changeset indexing (incoming: %1 changeset(s) / %2 instructions, local: %3 "
415,442✔
2382
                 "changeset(s) / %4 instructions, conflict group(s): %5)",
415,442✔
2383
                 their_changesets.size(), their_num_instructions, our_changesets.size(), our_num_instructions,
415,442✔
2384
                 their_index.get_num_conflict_groups());
415,442✔
2385

209,610✔
2386
#if REALM_DEBUG // LCOV_EXCL_START
209,610✔
2387
    if (trace) {
415,442✔
2388
        std::cerr << TERM_YELLOW << "\n=> PEER " << std::hex << local_file_ident
×
2389
                  << " merging "
×
2390
                     "changeset(s)/from peer(s):\n";
×
2391
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2392
            std::cerr << "Changeset version " << std::dec << their_changesets[i].version << " from peer "
×
2393
                      << their_changesets[i].origin_file_ident << " at timestamp "
×
2394
                      << their_changesets[i].origin_timestamp << "\n";
×
2395
        }
×
2396
        std::cerr << "Transforming through local changeset(s):\n";
×
2397
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2398
            std::cerr << "Changeset version " << our_changesets[i]->version << " from peer "
×
2399
                      << our_changesets[i]->origin_file_ident << " at timestamp "
×
2400
                      << our_changesets[i]->origin_timestamp << "\n";
×
2401
        }
×
2402

2403
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2404
            std::cerr << TERM_RED << "\nLOCAL (RECIPROCAL) CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2405
            our_changesets[i]->print(std::cerr);
×
2406
        }
×
2407

2408
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2409
            std::cerr << TERM_RED << "\nINCOMING CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2410
            their_changesets[i].print(std::cerr);
×
2411
        }
×
2412

2413
        std::cerr << TERM_MAGENTA << "\nINCOMING CHANGESET INDEX:\n" << TERM_RESET;
×
2414
        their_index.print(std::cerr);
×
2415
        std::cerr << '\n';
×
2416
        their_index.verify();
×
2417

2418
        std::cerr << TERM_YELLOW << std::setw(80) << std::left << "MERGE TRACE (incoming):"
×
2419
                  << "MERGE TRACE (local):\n"
×
2420
                  << TERM_RESET;
×
2421
    }
×
2422
#else
2423
    static_cast<void>(local_file_ident);
2424
#endif // REALM_DEBUG LCOV_EXCL_STOP
2425

209,610✔
2426
    for (size_t i = 0; i < our_changesets.size(); ++i) {
10,322,362✔
2427
        logger.trace(
9,906,920✔
2428
            "Transforming local changeset [%1/%2] through %3 incoming changeset(s) with %4 conflict group(s)", i + 1,
9,906,920✔
2429
            our_changesets.size(), their_changesets.size(), their_index.get_num_conflict_groups());
9,906,920✔
2430
        Changeset* our_changeset = our_changesets[i];
9,906,920✔
2431

4,945,954✔
2432
        transformer.m_major_side.set_next_changeset(our_changeset);
9,906,920✔
2433
        // MinorSide uses the index to find the Changeset.
4,945,954✔
2434
        transformer.m_minor_side.m_changeset_index = &their_index;
9,906,920✔
2435
        transformer.transform(); // Throws
9,906,920✔
2436
    }
9,906,920✔
2437

209,610✔
2438
    logger.debug("Finished transforming %1 local changesets through %2 incoming changesets (%3 vs %4 "
415,442✔
2439
                 "instructions, in %5 conflict groups)",
415,442✔
2440
                 our_changesets.size(), their_changesets.size(), our_num_instructions, their_num_instructions,
415,442✔
2441
                 their_index.get_num_conflict_groups());
415,442✔
2442

209,610✔
2443
#if REALM_DEBUG // LCOV_EXCL_START
209,610✔
2444
    // Check that the index is still valid after transformation.
209,610✔
2445
    their_index.verify();
415,442✔
2446
#endif // REALM_DEBUG LCOV_EXCL_STOP
415,442✔
2447

209,610✔
2448
#if REALM_DEBUG // LCOV_EXCL_START
209,610✔
2449
    if (trace) {
415,442✔
2450
        for (size_t i = 0; i < our_changesets.size(); ++i) {
×
2451
            std::cerr << TERM_CYAN << "\nRECIPROCAL CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2452
            our_changesets[i]->print(std::cerr);
×
2453
            std::cerr << '\n';
×
2454
        }
×
2455
        for (size_t i = 0; i < their_changesets.size(); ++i) {
×
2456
            std::cerr << TERM_CYAN << "INCOMING CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2457
            their_changesets[i].print(std::cerr);
×
2458
            std::cerr << '\n';
×
2459
        }
×
2460
    }
×
2461
#endif // LCOV_EXCL_STOP REALM_DEBUG
415,442✔
2462
}
415,442✔
2463

2464
size_t Transformer::transform_remote_changesets(TransformHistory& history, file_ident_type local_file_ident,
2465
                                                version_type current_local_version,
2466
                                                util::Span<Changeset> parsed_changesets,
2467
                                                util::FunctionRef<bool(const Changeset*)> changeset_applier,
2468
                                                util::Logger& logger)
2469
{
452,176✔
2470
    REALM_ASSERT(local_file_ident != 0);
452,176✔
2471

227,498✔
2472
    std::vector<Changeset*> our_changesets;
452,176✔
2473

227,498✔
2474
    // p points to the beginning of a range of changesets that share the same
227,498✔
2475
    // "base", i.e. are based on the same local version.
227,498✔
2476
    auto p = parsed_changesets.begin();
452,176✔
2477
    auto parsed_changesets_end = parsed_changesets.end();
452,176✔
2478

227,498✔
2479
    while (p != parsed_changesets_end) {
909,234✔
2480
        // Find the range of incoming changesets that share the same
230,318✔
2481
        // last_integrated_local_version, which means we can merge them in one go.
230,318✔
2482
        auto same_base_range_end = std::find_if(p + 1, parsed_changesets_end, [&](auto& changeset) {
250,360✔
2483
            return p->last_integrated_remote_version != changeset.last_integrated_remote_version;
36,640✔
2484
        });
36,640✔
2485

230,318✔
2486
        version_type begin_version = p->last_integrated_remote_version;
457,058✔
2487
        version_type end_version = current_local_version;
457,058✔
2488
        for (;;) {
10,363,966✔
2489
            HistoryEntry history_entry;
10,363,966✔
2490
            version_type version = history.find_history_entry(begin_version, end_version, history_entry);
10,363,966✔
2491
            if (version == 0)
10,363,966✔
2492
                break; // No more local changesets
457,062✔
2493

4,945,950✔
2494
            Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
9,906,904✔
2495
                                                                history_entry); // Throws
9,906,904✔
2496
            our_changesets.push_back(&our_changeset);
9,906,904✔
2497

4,945,950✔
2498
            begin_version = version;
9,906,904✔
2499
        }
9,906,904✔
2500

230,318✔
2501
        bool must_apply_all = false;
457,058✔
2502

230,318✔
2503
        if (!our_changesets.empty()) {
457,058✔
2504
            merge_changesets(local_file_ident, {&*p, same_base_range_end}, our_changesets, logger); // Throws
415,442✔
2505
            // We need to apply all transformed changesets if at least one reciprocal changeset was modified
209,610✔
2506
            // during OT.
209,610✔
2507
            must_apply_all = std::any_of(our_changesets.begin(), our_changesets.end(), [](const Changeset* c) {
9,119,040✔
2508
                return c->is_dirty();
9,119,040✔
2509
            });
9,119,040✔
2510
        }
415,442✔
2511

230,318✔
2512
        auto continue_applying = true;
457,058✔
2513
        for (; p != same_base_range_end && continue_applying; ++p) {
945,826✔
2514
            // It is safe to stop applying the changesets if:
244,072✔
2515
            //      1. There are no reciprocal changesets
244,072✔
2516
            //      2. No reciprocal changeset was modified
244,072✔
2517
            continue_applying = changeset_applier(p) || must_apply_all;
488,768!
2518
        }
488,768✔
2519
        if (!continue_applying) {
457,058✔
2520
            break;
×
2521
        }
×
2522

230,318✔
2523
        our_changesets.clear(); // deliberately not releasing memory
457,058✔
2524
    }
457,058✔
2525

227,498✔
2526
    // NOTE: Any exception thrown during flushing *MUST* lead to rollback of
227,498✔
2527
    // the current transaction.
227,498✔
2528
    flush_reciprocal_transform_cache(history); // Throws
452,176✔
2529

227,498✔
2530
    return p - parsed_changesets.begin();
452,176✔
2531
}
452,176✔
2532

2533

2534
Changeset& Transformer::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
2535
                                                 version_type version, const HistoryEntry& history_entry)
2536
{
9,906,922✔
2537
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
9,906,922✔
2538
    if (changeset.empty()) {
9,906,922✔
2539
        bool is_compressed = false;
9,882,416✔
2540
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
9,882,416✔
2541
        ChunkedBinaryInputStream in{data};
9,882,416✔
2542
        if (is_compressed) {
9,882,416✔
2543
            size_t total_size;
43,914✔
2544
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
43,914✔
2545
            REALM_ASSERT(decompressed);
43,914✔
2546
            sync::parse_changeset(*decompressed, changeset); // Throws
43,914✔
2547
        }
43,914✔
2548
        else {
9,838,502✔
2549
            sync::parse_changeset(in, changeset); // Throws
9,838,502✔
2550
        }
9,838,502✔
2551

4,931,974✔
2552
        changeset.version = version;
9,882,416✔
2553
        changeset.last_integrated_remote_version = history_entry.remote_version;
9,882,416✔
2554
        changeset.origin_timestamp = history_entry.origin_timestamp;
9,882,416✔
2555
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
9,882,416✔
2556
        if (origin_file_ident == 0)
9,882,416✔
2557
            origin_file_ident = local_file_ident;
5,691,140✔
2558
        changeset.origin_file_ident = origin_file_ident;
9,882,416✔
2559
    }
9,882,416✔
2560
    return changeset;
9,906,922✔
2561
}
9,906,922✔
2562

2563

2564
void Transformer::flush_reciprocal_transform_cache(TransformHistory& history)
2565
{
452,132✔
2566
    auto changesets = std::move(m_reciprocal_transform_cache);
452,132✔
2567
    m_reciprocal_transform_cache.clear();
452,132✔
2568
    ChangesetEncoder::Buffer output_buffer;
452,132✔
2569
    for (const auto& [version, changeset] : changesets) {
9,881,446✔
2570
        if (changeset.is_dirty()) {
9,881,446✔
2571
            encode_changeset(changeset, output_buffer); // Throws
83,828✔
2572
            BinaryData data{output_buffer.data(), output_buffer.size()};
83,828✔
2573
            history.set_reciprocal_transform(version, data); // Throws
83,828✔
2574
            output_buffer.clear();
83,828✔
2575
        }
83,828✔
2576
    }
9,881,446✔
2577
}
452,132✔
2578

2579
void parse_remote_changeset(const RemoteChangeset& remote_changeset, Changeset& parsed_changeset)
2580
{
488,822✔
2581
    // origin_file_ident = 0 is currently used to indicate an entry of local
244,082✔
2582
    // origin.
244,082✔
2583
    REALM_ASSERT(remote_changeset.origin_file_ident != 0);
488,822✔
2584
    REALM_ASSERT(remote_changeset.remote_version != 0);
488,822✔
2585

244,082✔
2586
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
488,822✔
2587
    parse_changeset(remote_in, parsed_changeset); // Throws
488,822✔
2588

244,082✔
2589
    parsed_changeset.version = remote_changeset.remote_version;
488,822✔
2590
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
488,822✔
2591
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
488,822✔
2592
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
488,822✔
2593
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
488,822✔
2594
}
488,822✔
2595

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

© 2026 Coveralls, Inc