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

realm / realm-core / github_pull_request_301264

30 Jul 2024 07:11PM UTC coverage: 91.111% (+0.009%) from 91.102%
github_pull_request_301264

Pull #7936

Evergreen

web-flow
Add support for multi-process subscription state change notifications (#7862)

As with the other multi-process notifications, the core idea here is to
eliminate the in-memory state and produce notifications based entirely on the
current state of the Realm file.

SubscriptionStore::update_state() has been replaced with separate functions for
the specific legal state transitions, which also take a write transaction as a
parameter. These functions are called by PendingBootstrapStore inside the same
write transaction as the bootstrap updates which changed the subscription
state. This is both a minor performance optimization (due to fewer writes) and
eliminates a brief window between the two writes where the Realm file was in an
inconsistent state.

There's a minor functional change here: previously old subscription sets were
superseded when the new one reached the Completed state, and now they are
superseded on AwaitingMark. This aligns it with when the new subscription set
becomes the one which is returned by get_active().
Pull Request #7936: Fix connection callback crashes when reloading with React Native

102800 of 181570 branches covered (56.62%)

216840 of 237996 relevant lines covered (91.11%)

5918493.47 hits per line

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

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

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

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

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

17
namespace {
18

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

39
struct Discriminant {
40
    timestamp_type timestamp;
41
    file_ident_type client_file_ident;
42
    Discriminant(timestamp_type t, file_ident_type p)
43
        : timestamp(t)
524,982✔
44
        , client_file_ident(p)
524,982✔
45
    {
1,077,060✔
46
    }
1,077,060✔
47

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

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

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

67
struct TransformerImpl;
68

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

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

78
    Side(TransformerImpl& transformer)
79
        : m_transformer(transformer)
48,086✔
80
        , m_discriminant(0, 0)
48,086✔
81
    {
101,754✔
82
    }
101,754✔
83

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

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

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

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

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

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

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

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

133
struct MajorSide : Side {
134
    MajorSide(TransformerImpl& transformer)
135
        : Side(transformer)
24,038✔
136
    {
50,872✔
137
    }
50,872✔
138

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

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

153
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
678,206✔
154

155
        Side::init_with_instruction(get());
678,206✔
156
    }
678,206✔
157

158
    void skip_tombstones() noexcept final
159
    {
2,642,802✔
160
        while (m_position != m_changeset->end() && !*m_position) {
2,655,126✔
161
            ++m_position;
12,324✔
162
        }
12,324✔
163
    }
2,642,802✔
164

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

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

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

183
    Changeset::iterator m_position;
184
};
185

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

189
    MinorSide(TransformerImpl& transformer)
190
        : Side(transformer)
24,044✔
191
    {
50,878✔
192
    }
50,878✔
193

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

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

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

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

215
    void update_changeset_pointer() noexcept
216
    {
1,361,420✔
217
        if (REALM_LIKELY(m_position != end())) {
1,361,420✔
218
            m_changeset = m_position.m_outer->first;
486,382✔
219
        }
486,382✔
220
        else {
875,038✔
221
            m_changeset = nullptr;
875,038✔
222
        }
875,038✔
223
    }
1,361,420✔
224

225
    void skip_tombstones() noexcept final
226
    {
1,519,102✔
227
        if (m_position != end() && *m_position)
1,519,102✔
228
            return;
744,496✔
229
        skip_tombstones_slow();
774,606✔
230
    }
774,606✔
231

232
    REALM_NOINLINE void skip_tombstones_slow() noexcept
233
    {
774,530✔
234
        while (m_position != end() && !*m_position) {
894,242✔
235
            ++m_position;
119,712✔
236
        }
119,712✔
237
        update_changeset_pointer();
774,530✔
238
    }
774,530✔
239

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

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

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

264
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
297,218✔
265

266
        Side::init_with_instruction(get());
297,218✔
267
    }
297,218✔
268

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

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

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

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

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

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

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

310
        const Changeset* m_changeset = nullptr;
311

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

557
    TransformerImpl(bool trace)
558
        : m_major_side{*this}
24,040✔
559
        , m_minor_side{*this}
24,040✔
560
        , m_trace{trace}
24,040✔
561
    {
50,874✔
562
    }
50,874✔
563

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

569
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
1,321,942✔
570
            m_major_side.init_with_instruction(m_major_side.m_position);
678,194✔
571

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

577
            if (!m_major_side.was_discarded)
678,194✔
578
                // Discarding the instruction moves to the next one.
579
                m_major_side.next_instruction();
656,004✔
580
            m_major_side.skip_tombstones();
678,194✔
581
        }
678,194✔
582
    }
643,748✔
583

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

764
    void transform_major()
765
    {
678,254✔
766
        m_minor_side.skip_tombstones();
678,254✔
767

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

773
        while (m_minor_side.m_position != m_minor_end) {
953,322✔
774
            m_minor_side.init_with_instruction(m_minor_side.m_position);
297,194✔
775

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

792
            if (m_major_side.was_discarded)
297,194✔
793
                break;
22,126✔
794
            if (!m_minor_side.was_discarded)
275,068✔
795
                // Discarding an instruction moves to the next one.
796
                m_minor_side.next_instruction();
268,704✔
797
            m_minor_side.skip_tombstones();
275,068✔
798
        }
275,068✔
799
    }
678,254✔
800

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

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

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

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

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

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

858
struct MergeUtils {
859
    MergeUtils(Side& left_side, Side& right_side)
860
        : m_left_side(left_side)
47,194✔
861
        , m_right_side(right_side)
47,194✔
862
    {
84,892✔
863
    }
84,892✔
864

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

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

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

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

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

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

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

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

960
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
961
    {
10,342✔
962
        if (left.size() == right.size()) {
10,342✔
963
            for (size_t i = 0; i < left.size(); ++i) {
10,666✔
964
                if (!same_path_element(left[i], right[i])) {
852✔
965
                    return false;
288✔
966
                }
288✔
967
            }
852✔
968
            return true;
9,814✔
969
        }
10,102✔
970
        return false;
240✔
971
    }
10,342✔
972

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

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

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

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

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

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

1022
            for (size_t i = 0; i < left.size() - 1; ++i) {
2,364✔
1023
                if (!same_path_element(left[i], right[i])) {
244✔
1024
                    return false;
36✔
1025
                }
36✔
1026
            }
244✔
1027
            return true;
2,120✔
1028
        }
2,156✔
1029
        return false;
356✔
1030
    }
2,512✔
1031

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

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

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

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

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

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

1067
    std::optional<Instruction::Path::Element> is_prefix_of(const Instruction::PathInstruction& left,
1068
                                                           const Instruction::PathInstruction& right) const noexcept
1069
    {
3,252✔
1070
        if (left.path.size() < right.path.size() && same_field(left, right)) {
3,252✔
1071
            for (size_t i = 0; i < left.path.size(); ++i) {
1,908✔
1072
                if (!same_path_element(left.path[i], right.path[i])) {
760✔
1073
                    return {};
64✔
1074
                }
64✔
1075
            }
760✔
1076
            return right.path[left.path.size()];
1,148✔
1077
        }
1,212✔
1078
        return {};
2,040✔
1079
    }
3,252✔
1080

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

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

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

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

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

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

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

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

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

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

1211

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

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

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

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

1281

1282
/// AddTable rules
1283

1284
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1285

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

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

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

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

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

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

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

1355
/// EraseTable rules
1356

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

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

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

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

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

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

1401

1402
/// CreateObject rules
1403

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

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

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

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

1431

1432
/// Erase rules
1433

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

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

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

1469

1470
/// Update rules
1471

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

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

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

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

1501
    using Type = Instruction::Payload::Type;
10,186✔
1502

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1733

1734
/// AddInteger rules
1735

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

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

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

1760

1761
/// AddColumn rules
1762

1763
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1764

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

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

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

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

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

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

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

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

1833

1834
/// EraseColumn rules
1835

1836
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1837

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

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

1853
/// ArrayInsert rules
1854

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

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

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

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

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

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

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

1943
        left.prior_size++;
428✔
1944
        right.prior_size--;
428✔
1945
        if (right.index() <= left.index()) {
428✔
1946
            left.index() += 1; // --->
172✔
1947
        }
172✔
1948
        else {
256✔
1949
            right.index() -= 1; // <---
256✔
1950
        }
256✔
1951
    }
428✔
1952
}
1,092✔
1953

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

1959

1960
/// ArrayMove rules
1961

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

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

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

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

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

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

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

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

2078
        right.prior_size -= 1;
×
2079

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

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

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

2117

2118
/// ArrayErase rules
2119

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

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

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

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

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

2175

2176
/// Clear rules
2177

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

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

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

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

2217

2218
/// SetInsert rules
2219

2220
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2221

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

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

2265

2266
/// SetErase rules.
2267

2268
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2269

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

2288

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

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

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

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

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

2321
    if (their_side.get().get_if<Instruction::Update>()) {
297,178✔
2322
        REALM_ASSERT(their_side.m_path_len > 2);
19,562✔
2323
    }
19,562✔
2324
    if (our_side.get().get_if<Instruction::Update>()) {
297,178✔
2325
        REALM_ASSERT(our_side.m_path_len > 2);
20,810✔
2326
    }
20,810✔
2327
    if (their_side.get().get_if<Instruction::EraseObject>()) {
297,178✔
2328
        REALM_ASSERT(their_side.m_path_len == 2);
22,194✔
2329
    }
22,194✔
2330
    if (our_side.get().get_if<Instruction::EraseObject>()) {
297,178✔
2331
        REALM_ASSERT(our_side.m_path_len == 2);
25,396✔
2332
    }
25,396✔
2333

2334
    // Update selections on the major side (outer loop) according to events on
2335
    // the minor side (inner loop). The selection may only be impacted if the
2336
    // instruction level is lower (i.e. at a higher point in the hierarchy).
2337
    if (our_side.m_path_len < their_side.m_path_len) {
297,178✔
2338
        merge_nested(our_side, their_side);
24,748✔
2339
        if (their_side.was_discarded)
24,748✔
2340
            return;
1,868✔
2341
    }
24,748✔
2342
    else if (our_side.m_path_len > their_side.m_path_len) {
272,430✔
2343
        merge_nested(their_side, our_side);
33,326✔
2344
        if (our_side.was_discarded)
33,326✔
2345
            return;
1,668✔
2346
    }
33,326✔
2347

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

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

2371
    if (!our_side.was_discarded && !our_side.was_replaced) {
293,642✔
2372
        const auto& our_after = our_side.get();
274,246✔
2373
        if (!(our_after == our_before)) {
274,246✔
2374
            our_side.m_changeset->set_dirty(true);
1,934✔
2375
        }
1,934✔
2376
    }
274,246✔
2377
}
293,642✔
2378

2379
} // anonymous namespace
2380

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

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

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

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

2424
        their_index.scan_changeset(our_changeset);
643,752✔
2425
    }
643,752✔
2426

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

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

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

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

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

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

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

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

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

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

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

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

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

2528
    std::vector<Changeset*> our_changesets;
67,328✔
2529

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

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

2542
        version_type begin_version = p->last_integrated_remote_version;
73,864✔
2543
        version_type end_version = current_local_version;
73,864✔
2544
        for (;;) {
717,610✔
2545
            HistoryEntry history_entry;
717,610✔
2546
            version_type version = history.find_history_entry(begin_version, end_version, history_entry);
717,610✔
2547
            if (version == 0)
717,610✔
2548
                break; // No more local changesets
73,870✔
2549

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

2554
            begin_version = version;
643,740✔
2555
        }
643,740✔
2556

2557
        bool must_apply_all = false;
73,864✔
2558

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

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

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

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

2586
    return p - parsed_changesets.begin();
67,328✔
2587
}
67,328✔
2588

2589

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

2608
        changeset.version = version;
541,060✔
2609
        changeset.last_integrated_remote_version = history_entry.remote_version;
541,060✔
2610
        changeset.origin_timestamp = history_entry.origin_timestamp;
541,060✔
2611
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
541,060✔
2612
        if (origin_file_ident == 0)
541,060✔
2613
            origin_file_ident = local_file_ident;
302,962✔
2614
        changeset.origin_file_ident = origin_file_ident;
541,060✔
2615
    }
541,060✔
2616
    return changeset;
643,766✔
2617
}
643,766✔
2618

2619

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

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

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

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

2652
} // namespace realm::sync
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc