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

realm / realm-core / github_pull_request_281922

31 Oct 2023 09:13AM UTC coverage: 90.445% (-0.08%) from 90.528%
github_pull_request_281922

Pull #7039

Evergreen

jedelbo
Merge branch 'next-major' into je/global-key
Pull Request #7039: Remove ability to synchronize objects without primary key

95324 of 175822 branches covered (0.0%)

101 of 105 new or added lines in 13 files covered. (96.19%)

238 existing lines in 19 files now uncovered.

232657 of 257235 relevant lines covered (90.45%)

6351359.67 hits per line

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

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

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

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

31
namespace realm {
32

33
namespace {
34

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

55
} // unnamed namespace
56

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

61
namespace _impl {
62

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

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

75
    bool operator<(const Discriminant& other) const
76
    {
33,078✔
77
        return timestamp == other.timestamp ? (client_file_ident < other.client_file_ident)
17,138✔
78
                                            : timestamp < other.timestamp;
32,484✔
79
    }
33,078✔
80

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4,177,108✔
207
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
8,514,138✔
208

4,177,108✔
209
        Side::init_with_instruction(get());
8,514,138✔
210
    }
8,514,138✔
211

212
    void skip_tombstones() noexcept final
213
    {
36,779,228✔
214
        while (m_position != m_changeset->end() && !*m_position) {
36,780,572✔
215
            ++m_position;
1,344✔
216
        }
1,344✔
217
    }
36,779,228✔
218

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

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

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

237
    Changeset::iterator m_position;
238
};
239

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

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

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

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

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

264
    Position end() noexcept
265
    {
60,165,890✔
266
        return Position{m_conflict_ranges, Position::end_tag{}};
60,165,890✔
267
    }
60,165,890✔
268

269
    void update_changeset_pointer() noexcept
270
    {
16,068,722✔
271
        if (REALM_LIKELY(m_position != end())) {
16,068,722✔
272
            m_changeset = m_position.m_outer->first;
2,833,932✔
273
        }
2,833,932✔
274
        else {
13,234,790✔
275
            m_changeset = nullptr;
13,234,790✔
276
        }
13,234,790✔
277
    }
16,068,722✔
278

279
    void skip_tombstones() noexcept final
280
    {
16,268,788✔
281
        if (m_position != end() && *m_position)
16,268,788✔
282
            return;
5,443,358✔
283
        skip_tombstones_slow();
10,825,430✔
284
    }
10,825,430✔
285

286
    REALM_NOINLINE void skip_tombstones_slow() noexcept
287
    {
10,825,700✔
288
        while (m_position != end() && !*m_position) {
11,494,834✔
289
            ++m_position;
669,134✔
290
        }
669,134✔
291
        update_changeset_pointer();
10,825,700✔
292
    }
10,825,700✔
293

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

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

309
    void init_with_instruction(Position position)
310
    {
2,646,446✔
311
        // REALM_ASSERT(position >= Position(m_conflict_ranges));
1,302,342✔
312
        REALM_ASSERT(position != end());
2,646,446✔
313
        m_position = position;
2,646,446✔
314
        update_changeset_pointer();
2,646,446✔
315
        skip_tombstones();
2,646,446✔
316
        REALM_ASSERT(position != end());
2,646,446✔
317

1,302,342✔
318
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
2,646,446✔
319

1,302,342✔
320
        Side::init_with_instruction(get());
2,646,446✔
321
    }
2,646,446✔
322

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

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

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

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

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

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

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

364
        const Changeset* m_changeset = nullptr;
365

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

605

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

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

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

4,865,124✔
624
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
18,389,974✔
625
            m_major_side.init_with_instruction(m_major_side.m_position);
8,514,168✔
626

4,177,128✔
627
            set_conflict_ranges();
8,514,168✔
628
            m_minor_end = m_minor_side.end();
8,514,168✔
629
            m_minor_side.m_position = m_minor_side.begin();
8,514,168✔
630
            transform_major();
8,514,168✔
631

4,177,128✔
632
            if (!m_major_side.was_discarded)
8,514,168✔
633
                // Discarding the instruction moves to the next one.
4,143,140✔
634
                m_major_side.next_instruction();
8,446,590✔
635
            m_major_side.skip_tombstones();
8,514,168✔
636
        }
8,514,168✔
637
    }
9,875,806✔
638

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4,177,182✔
828
        while (m_minor_side.m_position != m_minor_end) {
11,093,066✔
829
            m_minor_side.init_with_instruction(m_minor_side.m_position);
2,646,444✔
830

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

1,302,340✔
847
            if (m_major_side.was_discarded)
2,646,444✔
848
                break;
67,622✔
849
            if (!m_minor_side.was_discarded)
2,578,822✔
850
                // Discarding an instruction moves to the next one.
1,243,422✔
851
                m_minor_side.next_instruction();
2,529,624✔
852
            m_minor_side.skip_tombstones();
2,578,822✔
853
        }
2,578,822✔
854
    }
8,514,244✔
855

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

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

893
namespace {
894

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

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

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

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

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

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

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

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

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

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

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

998
        bad_merge("Invalid payload type in instruction");
×
999
    }
×
1000

1001
    bool same_path_element(const Instruction::Path::Element& left,
1002
                           const Instruction::Path::Element& right) const noexcept
1003
    {
1,540✔
1004
        const auto& pred = util::overload{
1,540✔
1005
            [&](uint32_t lhs, uint32_t rhs) {
1,456✔
1006
                return lhs == rhs;
1,372✔
1007
            },
1,372✔
1008
            [&](InternString lhs, InternString rhs) {
1,090✔
1009
                return same_string(lhs, rhs);
168✔
1010
            },
168✔
1011
            [&](const auto&, const auto&) {
1,006✔
1012
                // FIXME: Paths contain incompatible element types. Should we raise an
1013
                // error here?
1014
                return false;
×
1015
            },
×
1016
        };
1,540✔
1017
        return mpark::visit(pred, left, right);
1,540✔
1018
    }
1,540✔
1019

1020
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
1021
    {
11,032✔
1022
        if (left.size() == right.size()) {
11,032✔
1023
            for (size_t i = 0; i < left.size(); ++i) {
11,324✔
1024
                if (!same_path_element(left[i], right[i])) {
1,532✔
1025
                    return false;
1,172✔
1026
                }
1,172✔
1027
            }
1,532✔
1028
            return true;
10,586✔
1029
        }
68✔
1030
        return false;
68✔
1031
    }
68✔
1032

1033
    bool same_table(const Instruction::TableInstruction& left,
1034
                    const Instruction::TableInstruction& right) const noexcept
1035
    {
1,254,998✔
1036
        return same_string(left.table, right.table);
1,254,998✔
1037
    }
1,254,998✔
1038

1039
    bool same_object(const Instruction::ObjectInstruction& left,
1040
                     const Instruction::ObjectInstruction& right) const noexcept
1041
    {
1,033,936✔
1042
        return same_table(left, right) && same_key(left.object, right.object);
1,033,936✔
1043
    }
1,033,936✔
1044

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

1061
    bool same_field(const Instruction::PathInstruction& left,
1062
                    const Instruction::PathInstruction& right) const noexcept
1063
    {
210,482✔
1064
        return same_object(left, right) && same_string(left.field, right.field);
210,482✔
1065
    }
210,482✔
1066

1067
    bool same_path(const Instruction::PathInstruction& left, const Instruction::PathInstruction& right) const noexcept
1068
    {
27,944✔
1069
        return same_field(left, right) && same_path(left.path, right.path);
27,944✔
1070
    }
27,944✔
1071

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

17,316✔
1082
            for (size_t i = 0; i < left.size() - 1; ++i) {
35,156✔
1083
                if (!same_path_element(left[i], right[i])) {
×
1084
                    return false;
×
1085
                }
×
1086
            }
×
1087
            return true;
35,156✔
1088
        }
24✔
1089
        return false;
24✔
1090
    }
24✔
1091

1092
    bool same_container(const Instruction::PathInstruction& left,
1093
                        const Instruction::PathInstruction& right) const noexcept
1094
    {
144,056✔
1095
        return same_field(left, right) && same_container(left.path, right.path);
144,056✔
1096
    }
144,056✔
1097

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

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

1107
    bool is_prefix_of(const Instruction::EraseTable& left, const Instruction::TableInstruction& right) const noexcept
1108
    {
164,644✔
1109
        return same_table(left, right);
164,644✔
1110
    }
164,644✔
1111

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

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

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

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

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

1141
    bool is_prefix_of(const Instruction::ObjectInstruction& left,
1142
                      const Instruction::PathInstruction& right) const noexcept
1143
    {
246,274✔
1144
        return same_object(left, right);
246,274✔
1145
    }
246,274✔
1146

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

1154
    bool is_prefix_of(const Instruction::PathInstruction& left,
1155
                      const Instruction::PathInstruction& right) const noexcept
1156
    {
38,456✔
1157
        if (left.path.size() < right.path.size() && same_field(left, right)) {
38,456✔
1158
            for (size_t i = 0; i < left.path.size(); ++i) {
372✔
1159
                if (!same_path_element(left.path[i], right.path[i])) {
8✔
1160
                    return false;
8✔
1161
                }
8✔
1162
            }
8✔
1163
            return true;
368✔
1164
        }
38,084✔
1165
        return false;
38,084✔
1166
    }
38,084✔
1167

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

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

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

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

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

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

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

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

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

1268
protected:
1269
    TransformerImpl::Side& m_left_side;
1270
    TransformerImpl::Side& m_right_side;
1271
};
1272

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

1279
    MergeBase(TransformerImpl::Side& left_side, TransformerImpl::Side& right_side)
1280
        : MergeUtils(left_side, right_side)
1281
    {
814,424✔
1282
    }
814,424✔
1283
    MergeBase(MergeBase&&) = delete;
1284
};
1285

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

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

1328

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

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

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

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

1398

1399
/// AddTable rules
1400

1401
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
1402

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

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

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

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

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

1452
DEFINE_MERGE(Instruction::EraseTable, Instruction::AddTable)
1453
{
4,164✔
1454
    if (same_table(left, right)) {
4,164✔
1455
        right_side.discard();
×
1456
    }
×
1457
}
4,164✔
1458

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

1472
/// EraseTable rules
1473

1474
DEFINE_NESTED_MERGE(Instruction::EraseTable)
1475
{
164,644✔
1476
    if (is_prefix_of(outer, inner)) {
164,644!
1477
        inner_side.discard();
36,364✔
1478
    }
36,364✔
1479
}
164,644✔
1480

1481
DEFINE_MERGE(Instruction::EraseTable, Instruction::EraseTable)
1482
{
1,340✔
1483
    if (same_table(left, right)) {
1,340✔
1484
        left_side.discard();
288✔
1485
        right_side.discard();
288✔
1486
    }
288✔
1487
}
1,340✔
1488

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

1495
DEFINE_MERGE(Instruction::AddColumn, Instruction::EraseTable)
1496
{
8,828✔
1497
    // AddColumn on an erased table handled by nesting.
4,524✔
1498

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

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

1518

1519
/// CreateObject rules
1520

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

1524
// CreateObject is idempotent.
1525
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::CreateObject);
1526

1527
DEFINE_MERGE(Instruction::EraseObject, Instruction::CreateObject)
1528
{
424,972✔
1529
    if (same_object(left, right)) {
424,972✔
1530
        // CONFLICT: Create and Erase of the same object.
8,274✔
1531
        //
8,274✔
1532
        // RESOLUTION: Erase always wins.
8,274✔
1533
        right_side.discard();
16,740✔
1534
    }
16,740✔
1535
}
424,972✔
1536

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

1548

1549
/// Erase rules
1550

1551
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1552
{
246,274✔
1553
    if (is_prefix_of(outer, inner)) {
246,274!
1554
        // Erase always wins.
10,558✔
1555
        inner_side.discard();
20,744✔
1556
    }
20,744✔
1557
}
246,274✔
1558

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

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

1586

1587
/// Set rules
1588

1589
DEFINE_NESTED_MERGE(Instruction::Update)
1590
{
37,880✔
1591
    using Type = Instruction::Payload::Type;
37,880✔
1592

19,298✔
1593
    if (outer.value.type == Type::ObjectValue || outer.value.type == Type::Dictionary) {
37,880!
1594
        // Creating an embedded object or a dictionary is an idempotent
32✔
1595
        // operation, and should not eliminate updates to the subtree.
32✔
1596
        return;
64✔
1597
    }
64✔
1598

19,266✔
1599
    // Setting a value higher up in the hierarchy overwrites any modification to
19,266✔
1600
    // the inner value, regardless of when this happened.
19,266✔
1601
    if (is_prefix_of(outer, inner)) {
37,816!
1602
        inner_side.discard();
80✔
1603
    }
80✔
1604
}
37,816✔
1605

1606
DEFINE_MERGE(Instruction::Update, Instruction::Update)
1607
{
25,300✔
1608
    // The two instructions are at the same level of nesting.
13,246✔
1609

13,246✔
1610
    using Type = Instruction::Payload::Type;
25,300✔
1611

13,246✔
1612
    if (same_path(left, right)) {
25,300✔
1613
        bool left_is_default = false;
9,022✔
1614
        bool right_is_default = false;
9,022✔
1615
        if (!(left.is_array_update() == right.is_array_update())) {
9,022✔
1616
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
×
1617
        }
×
1618

4,530✔
1619
        if (!left.is_array_update()) {
9,022✔
1620
            if (right.is_array_update()) {
8,762✔
1621
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
×
1622
            }
×
1623
            left_is_default = left.is_default;
8,762✔
1624
            right_is_default = right.is_default;
8,762✔
1625
        }
8,762✔
1626
        else if (!(left.prior_size == right.prior_size)) {
260✔
1627
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1628
        }
×
1629

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

4,506✔
1644
        // CONFLICT: Two updates of the same element.
4,506✔
1645
        //
4,506✔
1646
        // RESOLUTION: Suppress the effect of the UPDATE operation with the lower
4,506✔
1647
        // timestamp. Note that the timestamps can never be equal. This is
4,506✔
1648
        // achieved on both sides by discarding the received UPDATE operation if
4,506✔
1649
        // it has a lower timestamp than the previously applied UPDATE operation.
4,506✔
1650
        if (left_is_default == right_is_default) {
8,974✔
1651
            if (left_side.timestamp() < right_side.timestamp()) {
8,818✔
1652
                left_side.discard(); // --->
4,618✔
1653
            }
4,618✔
1654
            else {
4,200✔
1655
                right_side.discard(); // <---
4,200✔
1656
            }
4,200✔
1657
        }
8,818✔
1658
        else {
156✔
1659
            if (left_is_default) {
156✔
1660
                left_side.discard();
80✔
1661
            }
80✔
1662
            else {
76✔
1663
                right_side.discard();
76✔
1664
            }
76✔
1665
        }
156✔
1666
    }
8,974✔
1667
}
25,300✔
1668

1669
DEFINE_MERGE(Instruction::AddInteger, Instruction::Update)
1670
{
2,390✔
1671
    // The two instructions are at the same level of nesting.
900✔
1672

900✔
1673
    if (same_path(left, right)) {
2,390✔
1674
        // CONFLICT: Add vs Set of the same element.
256✔
1675
        //
256✔
1676
        // RESOLUTION: If the Add was later than the Set, add its value to
256✔
1677
        // the payload of the Set instruction. Otherwise, discard it.
256✔
1678

256✔
1679
        if (!(right.value.type == Instruction::Payload::Type::Int || right.value.is_null())) {
532✔
1680
            bad_merge(right_side, right,
×
1681
                      "Merge error: right.value.type == Instruction::Payload::Type::Int || right.value.is_null()");
×
1682
        }
×
1683

256✔
1684
        bool right_is_default = !right.is_array_update() && right.is_default;
532✔
1685

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

188✔
1695
            // Wrapping add
188✔
1696
            uint64_t ua = uint64_t(right.value.data.integer);
380✔
1697
            uint64_t ub = uint64_t(left.value);
380✔
1698
            right.value.data.integer = int64_t(ua + ub);
380✔
1699
        }
380✔
1700
        else {
96✔
1701
            left_side.discard();
96✔
1702
        }
96✔
1703
    }
532✔
1704
}
2,390✔
1705

1706
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::Update);
1707

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

1715
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::Update)
1716
{
52,826✔
1717
    if (same_container(left, right)) {
52,826✔
1718
        REALM_ASSERT(right.is_array_update());
8,292✔
1719
        if (!(left.prior_size == right.prior_size)) {
8,292✔
1720
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1721
        }
×
1722
        if (!(left.index() <= left.prior_size)) {
8,292✔
1723
            bad_merge(left_side, left, "Merge error: left.index() <= left.prior_size");
×
1724
        }
×
1725
        if (!(right.index() < right.prior_size)) {
8,292✔
1726
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1727
        }
×
1728
        right.prior_size += 1;
8,292✔
1729
        if (right.index() >= left.index()) {
8,292✔
1730
            right.index() += 1; // --->
3,548✔
1731
        }
3,548✔
1732
    }
8,292✔
1733
}
52,826✔
1734

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

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

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

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

1,288✔
1766
        // CONFLICT: Update of a removed element.
1,288✔
1767
        //
1,288✔
1768
        // RESOLUTION: Discard the UPDATE operation received on the right side.
1,288✔
1769
        right.prior_size -= 1;
2,552✔
1770

1,288✔
1771
        if (left.index() == right.index()) {
2,552✔
1772
            // CONFLICT: Update of a removed element.
260✔
1773
            //
260✔
1774
            // RESOLUTION: Discard the UPDATE operation received on the right side.
260✔
1775
            right_side.discard();
552✔
1776
        }
552✔
1777
        else if (right.index() > left.index()) {
2,000✔
1778
            right.index() -= 1;
1,076✔
1779
        }
1,076✔
1780
    }
2,552✔
1781
}
11,156✔
1782

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

1788

1789
/// AddInteger rules
1790

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

1802

1803
/// AddColumn rules
1804

1805
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1806

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

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

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

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

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

5,040✔
1854
        // Name, type, nullability and link targets match -- discard both
5,040✔
1855
        // sides and proceed.
5,040✔
1856
        left_side.discard();
10,108✔
1857
        right_side.discard();
10,108✔
1858
    }
10,108✔
1859
}
36,764✔
1860

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

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

1875

1876
/// EraseColumn rules
1877

1878
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
1879

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

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

1895
/// ArrayInsert rules
1896

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

1907
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::ArrayInsert)
1908
{
53,856✔
1909
    if (same_container(left, right)) {
53,856✔
1910
        if (!(left.prior_size == right.prior_size)) {
15,748✔
1911
            bad_merge(right_side, right, "Exception: left.prior_size == right.prior_size");
×
1912
        }
×
1913
        left.prior_size++;
15,748✔
1914
        right.prior_size++;
15,748✔
1915

7,642✔
1916
        if (left.index() > right.index()) {
15,748✔
1917
            left.index() += 1; // --->
3,480✔
1918
        }
3,480✔
1919
        else if (left.index() < right.index()) {
12,268✔
1920
            right.index() += 1; // <---
3,484✔
1921
        }
3,484✔
1922
        else { // left index == right index
8,784✔
1923
            // CONFLICT: Two element insertions at the same position.
4,312✔
1924
            //
4,312✔
1925
            // Resolution: Place the inserted elements in order of increasing
4,312✔
1926
            // timestamp. Note that the timestamps can never be equal.
4,312✔
1927
            if (left_side.timestamp() < right_side.timestamp()) {
8,784✔
1928
                right.index() += 1;
4,388✔
1929
            }
4,388✔
1930
            else {
4,396✔
1931
                left.index() += 1;
4,396✔
1932
            }
4,396✔
1933
        }
8,784✔
1934
    }
15,748✔
1935
}
53,856✔
1936

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

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

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

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

3,362✔
1985
        left.prior_size++;
7,536✔
1986
        right.prior_size--;
7,536✔
1987
        if (right.index() <= left.index()) {
7,536✔
1988
            left.index() += 1; // --->
3,084✔
1989
        }
3,084✔
1990
        else {
4,452✔
1991
            right.index() -= 1; // <---
4,452✔
1992
        }
4,452✔
1993
    }
7,536✔
1994
}
23,538✔
1995

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

2001

2002
/// ArrayMove rules
2003

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

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

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

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

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

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

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

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

2120
        right.prior_size -= 1;
×
2121

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

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

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

2159

2160
/// ArrayErase rules
2161

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

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

414✔
2193
        left.prior_size -= 1;
1,000✔
2194
        right.prior_size -= 1;
1,000✔
2195

414✔
2196
        if (left.index() > right.index()) {
1,000✔
2197
            left.index() -= 1; // --->
394✔
2198
        }
394✔
2199
        else if (left.index() < right.index()) {
606✔
2200
            right.index() -= 1; // <---
398✔
2201
        }
398✔
2202
        else { // left.index() == right.index()
208✔
2203
            // CONFLICT: Two removals of the same element.
72✔
2204
            //
72✔
2205
            // RESOLUTION: On each side, discard the received REMOVE operation.
72✔
2206
            left_side.discard();  // --->
208✔
2207
            right_side.discard(); // <---
208✔
2208
        }
208✔
2209
    }
1,000✔
2210
}
2,628✔
2211

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

2217

2218
/// Clear rules
2219

2220
DEFINE_NESTED_MERGE(Instruction::Clear)
2221
{
632✔
2222
    // Note: Clear instructions do not have an index in their path.
424✔
2223
    if (is_prefix_of(outer, inner)) {
632!
2224
        inner_side.discard();
284✔
2225
    }
284✔
2226
}
632✔
2227

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

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

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

2259

2260
/// SetInsert rules
2261

2262
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
2263

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

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

2307

2308
/// SetErase rules.
2309

2310
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2311

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

2330

2331
///
2332
/// END OF MERGE RULES!
2333
///
2334

2335
} // namespace
2336

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

2345
template <class Outer, class Inner, class OuterSide, class InnerSide>
2346
void merge_nested_2(Outer& outer, Inner& inner, OuterSide& outer_side, InnerSide& inner_side)
2347
{
1,082,860✔
2348
    MergeNested<Outer>::merge(outer, inner, outer_side, inner_side);
1,082,860✔
2349
}
1,082,860✔
2350

2351
void TransformerImpl::Transformer::merge_instructions(MajorSide& their_side, MinorSide& our_side)
2352
{
2,646,442✔
2353
    // FIXME: Find a way to avoid heap-copies of the path.
1,302,336✔
2354
    Instruction their_before = their_side.get();
2,646,442✔
2355
    Instruction our_before = our_side.get();
2,646,442✔
2356

1,302,336✔
2357
    if (their_side.get().get_if<Instruction::Update>()) {
2,646,442✔
2358
        REALM_ASSERT(their_side.m_path_len > 2);
181,140✔
2359
    }
181,140✔
2360
    if (our_side.get().get_if<Instruction::Update>()) {
2,646,442✔
2361
        REALM_ASSERT(our_side.m_path_len > 2);
225,492✔
2362
    }
225,492✔
2363
    if (their_side.get().get_if<Instruction::EraseObject>()) {
2,646,442✔
2364
        REALM_ASSERT(their_side.m_path_len == 2);
558,490✔
2365
    }
558,490✔
2366
    if (our_side.get().get_if<Instruction::EraseObject>()) {
2,646,442✔
2367
        REALM_ASSERT(our_side.m_path_len == 2);
629,974✔
2368
    }
629,974✔
2369

1,302,336✔
2370
    // Update selections on the major side (outer loop) according to events on
1,302,336✔
2371
    // the minor side (inner loop). The selection may only be impacted if the
1,302,336✔
2372
    // instruction level is lower (i.e. at a higher point in the hierarchy).
1,302,336✔
2373
    if (our_side.m_path_len < their_side.m_path_len) {
2,646,442✔
2374
        merge_nested(our_side, their_side);
448,708✔
2375
        if (their_side.was_discarded)
448,708✔
2376
            return;
28,762✔
2377
    }
2,197,734✔
2378
    else if (our_side.m_path_len > their_side.m_path_len) {
2,197,734✔
2379
        merge_nested(their_side, our_side);
634,150✔
2380
        if (our_side.was_discarded)
634,150✔
2381
            return;
28,710✔
2382
    }
2,588,970✔
2383

1,273,078✔
2384
    if (!their_side.was_discarded && !our_side.was_discarded) {
2,588,970✔
2385
        // Even if the instructions were nested, we must still perform a regular
1,273,076✔
2386
        // merge, because link-related instructions contain information from higher
1,273,076✔
2387
        // levels (both rows, columns, and tables).
1,273,076✔
2388
        //
1,273,076✔
2389
        // FIXME: This condition goes away when dangling links are implemented.
1,273,076✔
2390
        their_side.get().visit([&](auto& their_instruction) {
2,588,970✔
2391
            our_side.get().visit([&](auto& our_instruction) {
2,588,970✔
2392
                merge_instructions_2(their_instruction, our_instruction, their_side, our_side);
2,588,970✔
2393
            });
2,588,970✔
2394
        });
2,588,970✔
2395
    }
2,588,964✔
2396

1,273,078✔
2397
    // Note: `left` and/or `right` may be dangling at this point due to
1,273,078✔
2398
    // discard/prepend. However, if they were not discarded, their iterators are
1,273,078✔
2399
    // required to point to an instruction of the same type.
1,273,078✔
2400
    if (!their_side.was_discarded && !their_side.was_replaced) {
2,588,970✔
2401
        const auto& their_after = their_side.get();
2,550,074✔
2402
        if (!(their_after == their_before)) {
2,550,074✔
2403
            their_side.m_changeset->set_dirty(true);
29,428✔
2404
        }
29,428✔
2405
    }
2,550,074✔
2406

1,273,078✔
2407
    if (!our_side.was_discarded && !our_side.was_replaced) {
2,588,970✔
2408
        const auto& our_after = our_side.get();
2,550,540✔
2409
        if (!(our_after == our_before)) {
2,550,540✔
2410
            our_side.m_changeset->set_dirty(true);
29,432✔
2411
        }
29,432✔
2412
    }
2,550,540✔
2413
}
2,588,970✔
2414

2415

2416
template <class OuterSide, class InnerSide>
2417
void TransformerImpl::Transformer::merge_nested(OuterSide& outer_side, InnerSide& inner_side)
2418
{
1,082,858✔
2419
    outer_side.get().visit([&](auto& outer) {
1,082,860✔
2420
        inner_side.get().visit([&](auto& inner) {
1,082,858✔
2421
            merge_nested_2(outer, inner, outer_side, inner_side);
1,082,858✔
2422
        });
1,082,858✔
2423
    });
1,082,860✔
2424
}
1,082,858✔
2425

2426

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

211,030✔
2446
    _impl::ChangesetIndex their_index;
417,912✔
2447
    size_t their_num_instructions = 0;
417,912✔
2448
    size_t our_num_instructions = 0;
417,912✔
2449

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

221,948✔
2461
        their_index.scan_changeset(their_changesets[i]);
441,120✔
2462
    }
441,120✔
2463
    for (size_t i = 0; i < our_size; ++i) {
10,293,676✔
2464
        Changeset& our_changeset = *our_changesets[i];
9,875,764✔
2465
        size_t num_instructions = our_changeset.size();
9,875,764✔
2466
        our_num_instructions += num_instructions;
9,875,764✔
2467
        logger.trace(util::LogCategory::changeset, "Scanning local changeset [%1/%2] (%3 instructions)", i + 1,
9,875,764✔
2468
                     our_size, num_instructions);
9,875,764✔
2469

4,865,078✔
2470
        their_index.scan_changeset(our_changeset);
9,875,764✔
2471
    }
9,875,764✔
2472

211,030✔
2473
    // Build the index.
211,030✔
2474
    for (size_t i = 0; i < their_size; ++i) {
859,024✔
2475
        logger.trace(util::LogCategory::changeset, "Indexing incoming changeset [%1/%2] (%3 instructions)", i + 1,
441,112✔
2476
                     their_size, their_changesets[i].size());
441,112✔
2477
        their_index.add_changeset(their_changesets[i]);
441,112✔
2478
    }
441,112✔
2479

211,030✔
2480
    logger.debug(util::LogCategory::changeset,
417,912✔
2481
                 "Finished changeset indexing (incoming: %1 changeset(s) / %2 instructions, local: %3 "
417,912✔
2482
                 "changeset(s) / %4 instructions, conflict group(s): %5)",
417,912✔
2483
                 their_size, their_num_instructions, our_size, our_num_instructions,
417,912✔
2484
                 their_index.get_num_conflict_groups());
417,912✔
2485

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

2503
        for (size_t i = 0; i < our_size; ++i) {
×
2504
            std::cerr << TERM_RED << "\nLOCAL (RECIPROCAL) CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2505
            our_changesets[i]->print(std::cerr);
×
2506
        }
×
2507

2508
        for (size_t i = 0; i < their_size; ++i) {
×
2509
            std::cerr << TERM_RED << "\nINCOMING CHANGESET BEFORE MERGE:\n" << TERM_RESET;
×
2510
            their_changesets[i].print(std::cerr);
×
2511
        }
×
2512

2513
        std::cerr << TERM_MAGENTA << "\nINCOMING CHANGESET INDEX:\n" << TERM_RESET;
×
2514
        their_index.print(std::cerr);
×
2515
        std::cerr << '\n';
×
2516
        their_index.verify();
×
2517

2518
        std::cerr << TERM_YELLOW << std::setw(80) << std::left << "MERGE TRACE (incoming):"
×
2519
                  << "MERGE TRACE (local):\n"
×
2520
                  << TERM_RESET;
×
2521
    }
×
2522
#else
2523
    static_cast<void>(local_file_ident);
2524
#endif // REALM_DEBUG LCOV_EXCL_STOP
2525

211,030✔
2526
    for (size_t i = 0; i < our_size; ++i) {
10,293,742✔
2527
        logger.trace(
9,875,830✔
2528
            util::LogCategory::changeset,
9,875,830✔
2529
            "Transforming local changeset [%1/%2] through %3 incoming changeset(s) with %4 conflict group(s)", i + 1,
9,875,830✔
2530
            our_size, their_size, their_index.get_num_conflict_groups());
9,875,830✔
2531
        Changeset* our_changeset = our_changesets[i];
9,875,830✔
2532

4,865,128✔
2533
        transformer.m_major_side.set_next_changeset(our_changeset);
9,875,830✔
2534
        // MinorSide uses the index to find the Changeset.
4,865,128✔
2535
        transformer.m_minor_side.m_changeset_index = &their_index;
9,875,830✔
2536
        transformer.transform(); // Throws
9,875,830✔
2537
    }
9,875,830✔
2538

211,030✔
2539
    logger.debug(util::LogCategory::changeset,
417,912✔
2540
                 "Finished transforming %1 local changesets through %2 incoming changesets (%3 vs %4 "
417,912✔
2541
                 "instructions, in %5 conflict groups)",
417,912✔
2542
                 our_size, their_size, our_num_instructions, their_num_instructions,
417,912✔
2543
                 their_index.get_num_conflict_groups());
417,912✔
2544

211,030✔
2545
#if REALM_DEBUG // LCOV_EXCL_START
211,030✔
2546
    // Check that the index is still valid after transformation.
211,030✔
2547
    their_index.verify();
417,912✔
2548
#endif // REALM_DEBUG LCOV_EXCL_STOP
417,912✔
2549

211,030✔
2550
#if REALM_DEBUG // LCOV_EXCL_START
211,030✔
2551
    if (trace) {
417,912✔
2552
        for (size_t i = 0; i < our_size; ++i) {
×
2553
            std::cerr << TERM_CYAN << "\nRECIPROCAL CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2554
            our_changesets[i]->print(std::cerr);
×
2555
            std::cerr << '\n';
×
2556
        }
×
2557
        for (size_t i = 0; i < their_size; ++i) {
×
2558
            std::cerr << TERM_CYAN << "INCOMING CHANGESET AFTER MERGE:\n" << TERM_RESET;
×
2559
            their_changesets[i].print(std::cerr);
×
2560
            std::cerr << '\n';
×
2561
        }
×
2562
    }
×
2563
#endif // LCOV_EXCL_STOP REALM_DEBUG
417,912✔
2564
}
417,912✔
2565

2566
size_t TransformerImpl::transform_remote_changesets(TransformHistory& history, file_ident_type local_file_ident,
2567
                                                    version_type current_local_version,
2568
                                                    util::Span<Changeset> parsed_changesets,
2569
                                                    util::UniqueFunction<bool(const Changeset*)> changeset_applier,
2570
                                                    util::Logger& logger)
2571
{
453,346✔
2572
    REALM_ASSERT(local_file_ident != 0);
453,346✔
2573

227,954✔
2574
    std::vector<Changeset*> our_changesets;
453,346✔
2575

227,954✔
2576
    // p points to the beginning of a range of changesets that share the same
227,954✔
2577
    // "base", i.e. are based on the same local version.
227,954✔
2578
    auto p = parsed_changesets.begin();
453,346✔
2579
    auto parsed_changesets_end = parsed_changesets.end();
453,346✔
2580

227,954✔
2581
    try {
453,346✔
2582
        while (p != parsed_changesets_end) {
912,004✔
2583
            // Find the range of incoming changesets that share the same
231,012✔
2584
            // last_integrated_local_version, which means we can merge them in one go.
231,012✔
2585
            auto same_base_range_end = std::find_if(p + 1, parsed_changesets_end, [&](auto& changeset) {
250,220✔
2586
                return p->last_integrated_remote_version != changeset.last_integrated_remote_version;
35,990✔
2587
            });
35,990✔
2588

231,012✔
2589
            version_type begin_version = p->last_integrated_remote_version;
458,658✔
2590
            version_type end_version = current_local_version;
458,658✔
2591
            for (;;) {
10,334,452✔
2592
                HistoryEntry history_entry;
10,334,452✔
2593
                version_type version = history.find_history_entry(begin_version, end_version, history_entry);
10,334,452✔
2594
                if (version == 0)
10,334,452✔
2595
                    break; // No more local changesets
458,658✔
2596

4,865,126✔
2597
                Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
9,875,794✔
2598
                                                                    history_entry); // Throws
9,875,794✔
2599
                our_changesets.push_back(&our_changeset);
9,875,794✔
2600

4,865,126✔
2601
                begin_version = version;
9,875,794✔
2602
            }
9,875,794✔
2603

231,012✔
2604
            bool must_apply_all = false;
458,658✔
2605

231,012✔
2606
            if (!our_changesets.empty()) {
458,658✔
2607
                merge_changesets(local_file_ident, &*p, same_base_range_end - p, our_changesets.data(),
417,914✔
2608
                                 our_changesets.size(), logger); // Throws
417,914✔
2609
                // We need to apply all transformed changesets if at least one reciprocal changeset was modified
211,032✔
2610
                // during OT.
211,032✔
2611
                must_apply_all = std::any_of(our_changesets.begin(), our_changesets.end(), [](const Changeset* c) {
9,067,930✔
2612
                    return c->is_dirty();
9,067,930✔
2613
                });
9,067,930✔
2614
            }
417,914✔
2615

231,012✔
2616
            auto continue_applying = true;
458,658✔
2617
            for (; p != same_base_range_end && continue_applying; ++p) {
947,948✔
2618
                // It is safe to stop applying the changesets if:
244,716✔
2619
                //      1. There are no reciprocal changesets
244,716✔
2620
                //      2. No reciprocal changeset was modified
244,716✔
2621
                continue_applying = changeset_applier(p) || must_apply_all;
489,290!
2622
            }
489,290✔
2623
            if (!continue_applying) {
458,658✔
2624
                break;
×
2625
            }
×
2626

231,012✔
2627
            our_changesets.clear(); // deliberately not releasing memory
458,658✔
2628
        }
458,658✔
2629
    }
453,346✔
2630
    catch (...) {
227,980✔
2631
        // If an exception was thrown while merging, the transform cache will
20✔
2632
        // be polluted. This is a problem since the same cache object is reused
20✔
2633
        // by multiple invocations to transform_remote_changesets(), so we must
20✔
2634
        // clear the cache before rethrowing.
20✔
2635
        //
20✔
2636
        // Note that some valid changesets can still cause exceptions to be
20✔
2637
        // thrown by the merge algorithm, namely incompatible schema changes.
20✔
2638
        m_reciprocal_transform_cache.clear();
46✔
2639
        throw;
46✔
2640
    }
46✔
2641

227,940✔
2642
    // NOTE: Any exception thrown during flushing *MUST* lead to rollback of
227,940✔
2643
    // the current transaction.
227,940✔
2644
    flush_reciprocal_transform_cache(history); // Throws
453,306✔
2645

227,940✔
2646
    return p - parsed_changesets.begin();
453,306✔
2647
}
453,306✔
2648

2649

2650
Changeset& TransformerImpl::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
2651
                                                     version_type version, const HistoryEntry& history_entry)
2652
{
9,875,800✔
2653
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
9,875,800✔
2654
    if (changeset.empty()) {
9,875,800✔
2655
        bool is_compressed = false;
9,850,498✔
2656
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
9,850,498✔
2657
        ChunkedBinaryInputStream in{data};
9,850,498✔
2658
        if (is_compressed) {
9,850,498✔
2659
            size_t total_size;
44,374✔
2660
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
44,374✔
2661
            REALM_ASSERT(decompressed);
44,374✔
2662
            sync::parse_changeset(*decompressed, changeset); // Throws
44,374✔
2663
        }
44,374✔
2664
        else {
9,806,124✔
2665
            sync::parse_changeset(in, changeset); // Throws
9,806,124✔
2666
        }
9,806,124✔
2667

4,850,662✔
2668
        changeset.version = version;
9,850,498✔
2669
        changeset.last_integrated_remote_version = history_entry.remote_version;
9,850,498✔
2670
        changeset.origin_timestamp = history_entry.origin_timestamp;
9,850,498✔
2671
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
9,850,498✔
2672
        if (origin_file_ident == 0)
9,850,498✔
2673
            origin_file_ident = local_file_ident;
5,631,924✔
2674
        changeset.origin_file_ident = origin_file_ident;
9,850,498✔
2675
    }
9,850,498✔
2676
    return changeset;
9,875,800✔
2677
}
9,875,800✔
2678

2679

2680
void TransformerImpl::flush_reciprocal_transform_cache(TransformHistory& history)
2681
{
453,310✔
2682
    auto changesets = std::move(m_reciprocal_transform_cache);
453,310✔
2683
    m_reciprocal_transform_cache.clear();
453,310✔
2684
    ChangesetEncoder::Buffer output_buffer;
453,310✔
2685
    for (const auto& [version, changeset] : changesets) {
9,849,488✔
2686
        if (changeset.is_dirty()) {
9,849,488✔
2687
            encode_changeset(changeset, output_buffer); // Throws
85,668✔
2688
            BinaryData data{output_buffer.data(), output_buffer.size()};
85,668✔
2689
            history.set_reciprocal_transform(version, data); // Throws
85,668✔
2690
            output_buffer.clear();
85,668✔
2691
        }
85,668✔
2692
    }
9,849,488✔
2693
}
453,310✔
2694

2695
} // namespace _impl
2696

2697
namespace sync {
2698
std::unique_ptr<Transformer> make_transformer()
2699
{
18,258✔
2700
    return std::make_unique<_impl::TransformerImpl>(); // Throws
18,258✔
2701
}
18,258✔
2702

2703

2704
void parse_remote_changeset(const Transformer::RemoteChangeset& remote_changeset, Changeset& parsed_changeset)
2705
{
489,378✔
2706
    // origin_file_ident = 0 is currently used to indicate an entry of local
244,744✔
2707
    // origin.
244,744✔
2708
    REALM_ASSERT(remote_changeset.origin_file_ident != 0);
489,378✔
2709
    REALM_ASSERT(remote_changeset.remote_version != 0);
489,378✔
2710

244,744✔
2711
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
489,378✔
2712
    parse_changeset(remote_in, parsed_changeset); // Throws
489,378✔
2713

244,744✔
2714
    parsed_changeset.version = remote_changeset.remote_version;
489,378✔
2715
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
489,378✔
2716
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
489,378✔
2717
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
489,378✔
2718
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
489,378✔
2719
}
489,378✔
2720

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

© 2025 Coveralls, Inc