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

realm / realm-core / thomas.goyne_112

27 Oct 2023 10:49AM UTC coverage: 91.586% (+0.02%) from 91.571%
thomas.goyne_112

push

Evergreen

web-flow
Merge pull request #7085 from realm/release/13.23.2

Release/13.23.2

91754 of 168238 branches covered (0.0%)

230143 of 251285 relevant lines covered (91.59%)

7082763.83 hits per line

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

61.61
/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
11,930,006✔
45
#define TERM_RED "\x1b[31;22m"
11,930,006✔
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"
33,702✔
52
#endif
17,720✔
53
#endif
33,204✔
54

33,702✔
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
    {
70
    }
71

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

75
    bool operator<(const Discriminant& other) const
76
    {
77
        return timestamp == other.timestamp ? (client_file_ident < other.client_file_ident)
78
                                            : timestamp < other.timestamp;
79
    }
80

828,540✔
81
    bool operator==(const Discriminant& other) const
828,540✔
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;
32✔
95

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

100
    Side(Transformer& transformer)
36,150✔
101
        : m_transformer(transformer)
18,116✔
102
        , m_discriminant(0, 0)
36,150✔
103
    {
36,150✔
104
    }
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
    {
67,396✔
112
        was_replaced = true;
67,396✔
113
        get() = instr;
67,396✔
114
    }
115

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

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

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

×
133
    const Discriminant& timestamp() const
134
    {
135
        return m_discriminant;
136
    }
×
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,
11,101,362✔
158
                    const Instruction::PathInstruction& other)
11,101,362✔
159
    {
11,101,362✔
160
        instr.table = adopt_string(other_side, other.table);
11,101,362✔
161
        instr.object = adopt_key(other_side, other.object);
11,101,362✔
162
        instr.field = adopt_string(other_side, other.field);
163
        instr.path.m_path.clear();
164
        instr.path.m_path.reserve(other.path.size());
165
        for (auto& element : other.path.m_path) {
166
            auto push = util::overload{
167
                [&](uint32_t index) {
414,270✔
168
                    instr.path.m_path.push_back(index);
414,270✔
169
                },
170
                [&](InternString str) {
171
                    instr.path.m_path.push_back(adopt_string(other_side, str));
172
                },
173
            };
174
            mpark::visit(push, element);
175
        }
176
    }
177

8,586,164✔
178
protected:
8,586,164✔
179
    void init_with_instruction(const Instruction& instr) noexcept
8,586,164✔
180
    {
8,586,164✔
181
        was_discarded = false;
8,586,164✔
182
        was_replaced = false;
8,586,164✔
183
        m_path_len = instr.path_length();
4,305,896✔
184
    }
8,586,164✔
185
};
4,305,896✔
186

8,586,164✔
187
struct TransformerImpl::MajorSide : TransformerImpl::Side {
8,586,164✔
188
    MajorSide(Transformer& transformer)
189
        : Side(transformer)
190
    {
36,941,318✔
191
    }
36,942,418✔
192

1,100✔
193
    void set_next_changeset(Changeset* changeset) noexcept;
1,100✔
194
    void discard();
36,941,318✔
195
    void prepend(Instruction operation);
196
    template <class InputIterator>
197
    void prepend(InputIterator begin, InputIterator end);
8,519,766✔
198

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

38,331,090✔
207
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
38,331,090✔
208

209
        Side::init_with_instruction(get());
210
    }
7,684,082✔
211

7,684,082✔
212
    void skip_tombstones() noexcept final
7,684,082✔
213
    {
214
        while (m_position != m_changeset->end() && !*m_position) {
215
            ++m_position;
216
        }
217
    }
218

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

227
    Instruction& get() noexcept final
228
    {
229
        return **m_position;
230
    }
231

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

237
    Changeset::iterator m_position;
8,586,256✔
238
};
8,586,256✔
239

8,586,256✔
240
struct TransformerImpl::MinorSide : TransformerImpl::Side {
241
    using Position = _impl::ChangesetIndex::RangeIterator;
242

59,112,304✔
243
    MinorSide(Transformer& transformer)
59,112,304✔
244
        : Side(transformer)
59,112,304✔
245
    {
246
    }
247

15,752,456✔
248
    void discard();
15,752,456✔
249
    void prepend(Instruction operation);
2,703,656✔
250
    template <class InputIterator>
2,703,656✔
251
    void prepend(InputIterator begin, InputIterator end);
13,048,800✔
252

13,048,800✔
253
    void substitute(const Instruction& instr)
13,048,800✔
254
    {
15,752,456✔
255
        was_replaced = true;
256
        get() = instr;
257
    }
15,951,344✔
258

15,951,344✔
259
    Position begin() noexcept
5,181,220✔
260
    {
10,770,124✔
261
        return Position{m_conflict_ranges};
10,770,124✔
262
    }
263

264
    Position end() noexcept
10,770,230✔
265
    {
11,393,650✔
266
        return Position{m_conflict_ranges, Position::end_tag{}};
623,420✔
267
    }
623,420✔
268

10,770,230✔
269
    void update_changeset_pointer() noexcept
10,770,230✔
270
    {
271
        if (REALM_LIKELY(m_position != end())) {
272
            m_changeset = m_position.m_outer->first;
2,400,954✔
273
        }
2,400,954✔
274
        else {
2,400,954✔
275
            m_changeset = nullptr;
2,400,954✔
276
        }
2,400,954✔
277
    }
2,400,954✔
278

279
    void skip_tombstones() noexcept final
280
    {
15,992,114✔
281
        if (m_position != end() && *m_position)
15,992,114✔
282
            return;
15,992,114✔
283
        skip_tombstones_slow();
15,992,114✔
284
    }
15,992,114✔
285

286
    REALM_NOINLINE void skip_tombstones_slow() noexcept
287
    {
2,515,420✔
288
        while (m_position != end() && !*m_position) {
1,226,886✔
289
            ++m_position;
2,515,420✔
290
        }
2,515,420✔
291
        update_changeset_pointer();
2,515,420✔
292
    }
2,515,420✔
293

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

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

309
    void init_with_instruction(Position position)
310
    {
311
        // REALM_ASSERT(position >= Position(m_conflict_ranges));
312
        REALM_ASSERT(position != end());
313
        m_position = position;
314
        update_changeset_pointer();
315
        skip_tombstones();
316
        REALM_ASSERT(position != end());
317

318
        m_discriminant = Discriminant{m_changeset->origin_timestamp, m_changeset->origin_file_ident};
319

320
        Side::init_with_instruction(get());
321
    }
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
414,270✔
593
    {
414,270✔
594
        // FIXME: Does not work with UTF-8.
595
        if (str.size() > size_t(width)) {
596
            os << str.substr(0, width - 1) << "~";
9,885,040✔
597
        }
9,885,040✔
598
        else {
9,885,040✔
599
            os << std::left << std::setw(width) << str;
4,943,978✔
600
        }
18,471,214✔
601
    }
8,586,174✔
602
};
4,305,892✔
603
#endif // LCOV_EXCL_STOP REALM_DEBUG
8,586,174✔
604

8,586,174✔
605

8,586,174✔
606
struct TransformerImpl::Transformer {
8,586,174✔
607
    MajorSide m_major_side;
4,305,892✔
608
    MinorSide m_minor_side;
8,586,174✔
609
    MinorSide::Position m_minor_end;
4,272,940✔
610
    bool m_trace;
8,519,772✔
611

8,586,174✔
612
    Transformer(bool trace)
8,586,174✔
613
        : m_major_side{*this}
9,885,040✔
614
        , m_minor_side{*this}
615
        , m_trace{trace}
616
    {
8,586,072✔
617
    }
8,586,072✔
618

4,305,874✔
619
    void transform()
8,586,072✔
620
    {
445,432✔
621
        m_major_side.m_position = m_major_side.m_changeset->begin();
445,432✔
622
        m_major_side.skip_tombstones();
445,432✔
623

902,118✔
624
        while (m_major_side.m_position != m_major_side.m_changeset->end()) {
902,118✔
625
            m_major_side.init_with_instruction(m_major_side.m_position);
330,318✔
626

665,646✔
627
            set_conflict_ranges();
×
628
            m_minor_end = m_minor_side.end();
×
629
            m_minor_side.m_position = m_minor_side.begin();
665,646✔
630
            transform_major();
665,646✔
631

902,118✔
632
            if (!m_major_side.was_discarded)
902,118✔
633
                // Discarding the instruction moves to the next one.
7,683,954✔
634
                m_major_side.next_instruction();
3,860,442✔
635
            m_major_side.skip_tombstones();
3,860,442✔
636
        }
3,860,442✔
637
    }
3,860,442✔
638

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

3,860,442✔
643
        if (_impl::is_schema_change(instr)) {
7,683,954✔
644
            ///
×
645
            /// CONFLICT GROUP: Everything touching that class
×
646
            ///
×
647
            auto ranges = index.get_everything();
×
648
            if (!ranges->empty()) {
×
649
#if REALM_DEBUG // LCOV_EXCL_START
×
650
                if (m_trace) {
×
651
                    std::cerr << TERM_RED << "Conflict group: Everything (due to schema change)\n" << TERM_RESET;
×
652
                }
×
653
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
654
            }
×
655
            return ranges;
7,683,954✔
656
        }
7,683,954✔
657
        else {
7,683,954✔
658
            ///
74✔
659
            /// CONFLICT GROUP: Everything touching the involved objects,
74✔
660
            /// including schema changes.
148✔
661
            ///
148✔
662
            _impl::ChangesetIndex::GlobalID major_ids[2];
7,683,954✔
663
            size_t num_major_ids = m_major_side.get_object_ids_in_current_instruction(major_ids, 2);
7,683,954✔
664
            REALM_ASSERT(num_major_ids <= 2);
8,586,072✔
665
            REALM_ASSERT(num_major_ids >= 1);
666
#if REALM_DEBUG // LCOV_EXCL_START
667
            if (m_trace) {
8,586,106✔
668
                std::cerr << TERM_RED << "Conflict group: ";
8,586,106✔
669
                if (num_major_ids == 0) {
8,586,106✔
670
                    std::cerr << "(nothing - no object references)";
4,305,868✔
671
                }
8,586,106✔
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)
9,885,120✔
675
                        std::cerr << ", ";
9,885,120✔
676
                }
9,885,120✔
677
                std::cerr << "\n" << TERM_RESET;
9,885,120✔
678
            }
9,885,120✔
679
#endif // REALM_DEBUG LCOV_EXCL_STOP
680
            auto ranges = index.get_modifications_for_object(major_ids[0]);
681
            if (num_major_ids == 2) {
66,456✔
682
                // Check that the index has correctly joined the ranges for the
66,456✔
683
                // two object IDs.
66,456✔
684
                REALM_ASSERT(ranges == index.get_modifications_for_object(major_ids[1]));
66,456✔
685
            }
66,456✔
686
            return ranges;
687
        }
688
    }
65,994✔
689

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

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

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

×
711
    void discard_minor()
×
712
    {
713
        m_minor_side.was_discarded = true;
714
        m_minor_side.m_position = m_minor_side.m_changeset_index->erase_instruction(m_minor_side.m_position);
715
        m_minor_side.m_changeset->set_dirty(true);
×
716
        m_minor_side.update_changeset_pointer();
×
717
    }
×
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) {
8,586,230✔
797
                // Discarding an instruction moves to the next.
8,586,230✔
798
                m_major_side.next_instruction();
4,305,990✔
799
            }
8,586,230✔
800
            REALM_ASSERT(m_major_side.m_position != m_major_side.m_changeset->end());
8,586,230✔
801

8,586,230✔
802
            m_minor_side.m_position = orig_minor_index;
8,586,230✔
803
            m_minor_side.was_discarded = orig_minor_was_discarded;
4,305,990✔
804
            m_minor_side.was_replaced = orig_minor_was_replaced;
11,035,192✔
805
            m_minor_side.m_path_len = orig_minor_path_len;
2,515,418✔
806
            m_minor_side.update_changeset_pointer();
1,226,884✔
807
        }
2,515,418✔
808

2,515,418✔
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;
2,515,418✔
817
    }
2,515,418✔
818

2,515,418✔
819
    void transform_major()
2,515,418✔
820
    {
2,515,418✔
821
        m_minor_side.skip_tombstones();
2,515,418✔
822

1,226,884✔
823
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
2,515,418✔
824
        const bool print_noop_merges = false;
66,456✔
825
        bool new_major = true; // print an instruction every time we go to the next major regardless
2,448,962✔
826
#endif                         // LCOV_EXCL_STOP REALM_DEBUG
1,170,066✔
827

2,400,954✔
828
        while (m_minor_side.m_position != m_minor_end) {
2,448,962✔
829
            m_minor_side.init_with_instruction(m_minor_side.m_position);
2,448,962✔
830

8,586,230✔
831
#if defined(REALM_DEBUG) // LCOV_EXCL_START Debug tracing
832
            if (m_trace) {
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;
9,885,136✔
837
                tracer.print_diff(std::cerr, new_major || print_noop_merges);
9,885,136✔
838
                new_major = false;
9,885,136✔
839
            }
840
            else {
66,456✔
841
#endif // LCOV_EXCL_STOP REALM_DEBUG
66,456✔
842
                merge_instructions(m_major_side, m_minor_side);
66,456✔
843
#if defined(REALM_DEBUG) // LCOV_EXCL_START
844
            }
×
845
#endif // LCOV_EXCL_STOP REALM_DEBUG
×
846

×
847
            if (m_major_side.was_discarded)
848
                break;
849
            if (!m_minor_side.was_discarded)
850
                // Discarding an instruction moves to the next one.
851
                m_minor_side.next_instruction();
852
            m_minor_side.skip_tombstones();
853
        }
65,994✔
854
    }
65,994✔
855

65,994✔
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
{
863
    m_transformer.set_next_major_changeset(changeset);
864
}
865
void TransformerImpl::MajorSide::discard()
866
{
867
    m_transformer.discard_major();
48✔
868
}
48✔
869
void TransformerImpl::MajorSide::prepend(Instruction operation)
48✔
870
{
871
    m_transformer.prepend_major(std::move(operation));
872
}
873
template <class InputIterator>
48✔
874
void TransformerImpl::MajorSide::prepend(InputIterator begin, InputIterator end)
48✔
875
{
48✔
876
    m_transformer.prepend_major(std::move(begin), std::move(end));
877
}
878
void TransformerImpl::MinorSide::discard()
×
879
{
×
880
    m_transformer.discard_minor();
×
881
}
×
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 {
1,185,376✔
894

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

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

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

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

108✔
919
struct MergeUtils {
220✔
920
    using TransformerImpl = _impl::TransformerImpl;
160✔
921
    MergeUtils(TransformerImpl::Side& left_side, TransformerImpl::Side& right_side)
28✔
922
        : m_left_side(left_side)
60✔
923
        , m_right_side(right_side)
✔
924
    {
×
925
    }
✔
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
    {
×
933
        // FIXME: Optimize string comparison by building a map of InternString values up front.
28✔
934
        return m_left_side.m_changeset->get_string(left) == m_right_side.m_changeset->get_string(right);
28✔
935
    }
✔
936

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

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

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

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

10,422✔
994
        bad_merge("Invalid payload type in instruction");
70✔
995
    }
70✔
996

70✔
997
    bool same_path_element(const Instruction::Path::Element& left,
998
                           const Instruction::Path::Element& right) const noexcept
999
    {
1000
        const auto& pred = util::overload{
1,176,998✔
1001
            [&](uint32_t lhs, uint32_t rhs) {
1,176,998✔
1002
                return lhs == rhs;
1,176,998✔
1003
            },
1004
            [&](InternString lhs, InternString rhs) {
1005
                return same_string(lhs, rhs);
1006
            },
966,252✔
1007
            [&](const auto&, const auto&) {
966,252✔
1008
                // FIXME: Paths contain incompatible element types. Should we raise an
966,252✔
1009
                // error here?
1010
                return false;
1011
            },
1012
        };
35,494✔
1013
        return mpark::visit(pred, left, right);
35,494✔
1014
    }
35,494✔
1015

17,744!
1016
    bool same_path(const Instruction::Path& left, const Instruction::Path& right) const noexcept
×
1017
    {
35,494✔
1018
        if (left.size() == right.size()) {
17,744✔
1019
            for (size_t i = 0; i < left.size(); ++i) {
35,494✔
1020
                if (!same_path_element(left[i], right[i])) {
35,494✔
1021
                    return false;
35,494✔
1022
                }
35,494!
1023
            }
35,494✔
1024
            return true;
35,494✔
1025
        }
1026
        return false;
1027
    }
1028

204,726✔
1029
    bool same_table(const Instruction::TableInstruction& left,
204,726✔
1030
                    const Instruction::TableInstruction& right) const noexcept
204,726✔
1031
    {
1032
        return same_string(left.table, right.table);
1033
    }
27,462✔
1034

27,462✔
1035
    bool same_object(const Instruction::ObjectInstruction& left,
27,462✔
1036
                     const Instruction::ObjectInstruction& right) const noexcept
1037
    {
1038
        return same_table(left, right) && same_key(left.object, right.object);
35,572✔
1039
    }
18,300✔
1040

18,300✔
1041
    template <class Left, class Right>
18,300✔
1042
    bool same_column(const Left& left, const Right& right) const noexcept
18,300✔
1043
    {
35,572✔
1044
        if constexpr (std::is_convertible_v<const Right&, const Instruction::PathInstruction&>) {
35,548✔
1045
            const Instruction::PathInstruction& rhs = right;
×
1046
            return same_table(left, right) && same_string(left.field, rhs.field);
18,288✔
1047
        }
35,548✔
1048
        else if constexpr (std::is_convertible_v<const Right&, const Instruction::ObjectInstruction&>) {
×
1049
            // CreateObject/EraseObject do not have a column built in.
×
1050
            return false;
×
1051
        }
×
1052
        else {
35,548✔
1053
            return same_table(left, right) && same_string(left.field, right.field);
24✔
1054
        }
24✔
1055
    }
24✔
1056

1057
    bool same_field(const Instruction::PathInstruction& left,
1058
                    const Instruction::PathInstruction& right) const noexcept
1059
    {
139,400✔
1060
        return same_object(left, right) && same_string(left.field, right.field);
139,400✔
1061
    }
139,400✔
1062

1063
    bool same_path(const Instruction::PathInstruction& left, const Instruction::PathInstruction& right) const noexcept
1064
    {
1065
        return same_field(left, right) && same_path(left.path, right.path);
1066
    }
1067

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

1078
            for (size_t i = 0; i < left.size() - 1; ++i) {
×
1079
                if (!same_path_element(left[i], right[i])) {
×
1080
                    return false;
×
1081
                }
×
1082
            }
1083
            return true;
1084
        }
×
1085
        return false;
×
1086
    }
×
1087

×
1088
    bool same_container(const Instruction::PathInstruction& left,
1089
                        const Instruction::PathInstruction& right) const noexcept
1090
    {
×
1091
        return same_field(left, right) && same_container(left.path, right.path);
×
1092
    }
×
1093

1094
    // NOTE: `is_prefix_of()` should only return true if the left is a strictly
1095
    // shorter path than the right, and the entire left path is the initial
1096
    // sequence of the right.
×
1097

×
1098
    bool is_prefix_of(const Instruction::AddTable& left, const Instruction::TableInstruction& right) const noexcept
×
1099
    {
1100
        return same_table(left, right);
1101
    }
×
1102

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

1108
    bool is_prefix_of(const Instruction::AddColumn&, const Instruction::TableInstruction&) const noexcept
232,334✔
1109
    {
232,334✔
1110
        // Right side is a schema instruction.
232,334✔
1111
        return false;
1112
    }
1113

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

1120
    bool is_prefix_of(const Instruction::AddColumn& left, const Instruction::ObjectInstruction& right) const noexcept
1121
    {
37,844✔
1122
        return same_column(left, right);
37,844✔
1123
    }
424✔
1124

8✔
1125
    bool is_prefix_of(const Instruction::EraseColumn& left,
8✔
1126
                      const Instruction::ObjectInstruction& right) const noexcept
8✔
1127
    {
8✔
1128
        return same_column(left, right);
420✔
1129
    }
37,420✔
1130

37,420✔
1131
    bool is_prefix_of(const Instruction::ObjectInstruction&, const Instruction::TableInstruction&) const noexcept
37,420✔
1132
    {
1133
        // Right side is a schema instruction.
1134
        return false;
1135
    }
1136

1137
    bool is_prefix_of(const Instruction::ObjectInstruction& left,
1138
                      const Instruction::PathInstruction& right) const noexcept
24✔
1139
    {
24✔
1140
        return same_object(left, right);
24✔
1141
    }
×
1142

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

1150
    bool is_prefix_of(const Instruction::PathInstruction& left,
1151
                      const Instruction::PathInstruction& right) const noexcept
×
1152
    {
×
1153
        if (left.path.size() < right.path.size() && same_field(left, right)) {
×
1154
            for (size_t i = 0; i < left.path.size(); ++i) {
1155
                if (!same_path_element(left.path[i], right.path[i])) {
1156
                    return false;
1157
                }
×
1158
            }
×
1159
            return true;
×
1160
        }
×
1161
        return false;
×
1162
    }
×
1163

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

1181
    bool is_container_prefix_of(const Instruction::PathInstruction&, const Instruction::TableInstruction&) const
×
1182
    {
×
1183
        return false;
×
1184
    }
1185

1186
    bool value_targets_table(const Instruction::Payload& value,
1187
                             const Instruction::TableInstruction& right) const noexcept
1188
    {
1189
        if (value.type == Instruction::Payload::Type::Link) {
1190
            StringData target_table = m_left_side.get_string(value.data.link.target_table);
1191
            StringData right_table = m_right_side.get_string(right.table);
1192
            return target_table == right_table;
24✔
1193
        }
24✔
1194
        return false;
24✔
1195
    }
24✔
1196

24✔
1197
    bool value_targets_object(const Instruction::Payload& value,
24✔
1198
                              const Instruction::ObjectInstruction& right) const noexcept
×
1199
    {
×
1200
        if (value_targets_table(value, right)) {
24✔
1201
            return same_key(value.data.link.target, right.object);
24✔
1202
        }
1203
        return false;
1204
    }
1205

×
1206
    bool value_targets_object(const Instruction::Update& left, const Instruction::ObjectInstruction& right) const
1207
    {
1208
        return value_targets_object(left.value, right);
×
1209
    }
×
1210

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

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

1234
    uint32_t& corresponding_index_in_path(const Instruction::PathInstruction&,
1235
                                          const Instruction::TableInstruction&) const
1236
    {
1237
        // A path instruction can never have a shorter path than something that
1238
        // isn't a PathInstruction.
1239
        REALM_UNREACHABLE();
1240
    }
1241

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

1264
protected:
1265
    TransformerImpl::Side& m_left_side;
1266
    TransformerImpl::Side& m_right_side;
759,946✔
1267
};
759,946✔
1268

1269
template <class LeftInstruction, class RightInstruction>
1270
struct MergeBase : MergeUtils {
1271
    static const Instruction::Type A = Instruction::GetInstructionType<LeftInstruction>::value;
1272
    static const Instruction::Type B = Instruction::GetInstructionType<RightInstruction>::value;
759,942✔
1273
    static_assert(A >= B, "A < B. Please reverse the order of instruction types. :-)");
759,942✔
1274

759,942✔
1275
    MergeBase(TransformerImpl::Side& left_side, TransformerImpl::Side& right_side)
759,942✔
1276
        : MergeUtils(left_side, right_side)
1277
    {
1278
    }
1279
    MergeBase(MergeBase&&) = delete;
1280
};
1281

1282
#define DEFINE_MERGE(A, B)                                                                                           \
1283
    template <>                                                                                                      \
1284
    struct Merge<A, B> {                                                                                             \
1285
        template <class LeftSide, class RightSide>                                                                   \
1286
        struct DoMerge : MergeBase<A, B> {                                                                           \
1287
            A& left;                                                                                                 \
1288
            B& right;                                                                                                \
1289
            LeftSide& left_side;                                                                                     \
1,700,588✔
1290
            RightSide& right_side;                                                                                   \
1,700,588✔
1291
            DoMerge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                                   \
1292
                : MergeBase<A, B>(left_side, right_side)                                                             \
1293
                , left(left)                                                                                         \
1294
                , right(right)                                                                                       \
1295
                , left_side(left_side)                                                                               \
1296
                , right_side(right_side)                                                                             \
1297
            {                                                                                                        \
1298
            }                                                                                                        \
1299
            void do_merge();                                                                                         \
1300
        };                                                                                                           \
1301
        template <class LeftSide, class RightSide>                                                                   \
1302
        static inline void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)                      \
1303
        {                                                                                                            \
1304
            DoMerge<LeftSide, RightSide> do_merge{left, right, left_side, right_side};                               \
1305
            do_merge.do_merge();                                                                                     \
1306
        }                                                                                                            \
1307
    };                                                                                                               \
1308
    template <class LeftSide, class RightSide>                                                                       \
1309
    void Merge<A, B>::DoMerge<LeftSide, RightSide>::do_merge()
425,426✔
1310

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

1324

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

1354
#define DEFINE_NESTED_MERGE_NOOP(A)                                                                                  \
1355
    template <>                                                                                                      \
1356
    struct MergeNested<A> {                                                                                          \
1357
        template <class B, class OuterSide, class InnerSide>                                                         \
1358
        static inline void merge(const A&, const B&, const OuterSide&, const InnerSide&)                             \
1359
        { /* Do nothing */                                                                                           \
1360
        }                                                                                                            \
1361
    }
1362

1363
// Implementation that reverses order.
1364
template <class A, class B>
1365
struct Merge<A, B,
1366
             typename std::enable_if<(Instruction::GetInstructionType<A>::value <
1367
                                      Instruction::GetInstructionType<B>::value)>::type> {
1368
    template <class LeftSide, class RightSide>
1369
    static void merge(A& left, B& right, LeftSide& left_side, RightSide& right_side)
14,594✔
1370
    {
14,594✔
1371
        Merge<B, A>::merge(right, left, right_side, left_side);
7,504✔
1372
    }
7,504✔
1373
};
7,376✔
1374

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

6✔
1394

3,732✔
1395
/// AddTable rules
7,376✔
1396

4✔
1397
DEFINE_NESTED_MERGE_NOOP(Instruction::AddTable);
4✔
1398

7,376✔
1399
DEFINE_MERGE(Instruction::AddTable, Instruction::AddTable)
×
1400
{
×
1401
    if (same_table(left, right)) {
×
1402
        StringData left_name = left_side.get_string(left.table);
7,376✔
1403
        if (auto left_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&left.type)) {
128✔
1404
            if (auto right_spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&right.type)) {
128✔
1405
                StringData left_pk_name = left_side.get_string(left_spec->pk_field);
×
1406
                StringData right_pk_name = right_side.get_string(right_spec->pk_field);
×
1407
                if (left_pk_name != right_pk_name) {
128✔
1408
                    bad_merge(
3,796✔
1409
                        "Schema mismatch: '%1' has primary key '%2' on one side, but primary key '%3' on the other.",
3,796✔
1410
                        left_name, left_pk_name, right_pk_name);
3,796✔
1411
                }
7,504✔
1412

7,504✔
1413
                if (left_spec->pk_type != right_spec->pk_type) {
7,504✔
1414
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is of type %3 on one side and type "
7,504✔
1415
                              "%4 on the other.",
14,594✔
1416
                              left_name, left_pk_name, get_type_name(left_spec->pk_type),
1417
                              get_type_name(right_spec->pk_type));
1418
                }
4,036✔
1419

4,036✔
1420
                if (left_spec->pk_nullable != right_spec->pk_nullable) {
×
1421
                    bad_merge("Schema mismatch: '%1' has primary key '%2', which is nullable on one side, but not "
×
1422
                              "the other.",
4,036✔
1423
                              left_name, left_pk_name);
1424
                }
1425

1426
                if (left_spec->is_asymmetric != right_spec->is_asymmetric) {
1427
                    bad_merge("Schema mismatch: '%1' is asymmetric on one side, but not on the other.", left_name);
1428
                }
1429
            }
1430
            else {
1431
                bad_merge("Schema mismatch: '%1' has a primary key on one side, but not on the other.", left_name);
1432
            }
1433
        }
1434
        else if (mpark::get_if<Instruction::AddTable::EmbeddedTable>(&left.type)) {
1435
            if (!mpark::get_if<Instruction::AddTable::EmbeddedTable>(&right.type)) {
1436
                bad_merge("Schema mismatch: '%1' is an embedded table on one side, but not the other.", left_name);
1437
            }
1438
        }
1439

1440
        // Names are the same, PK presence is the same, and if there is a primary
155,168✔
1441
        // key, its name, type, and nullability are the same. Discard both sides.
155,168!
1442
        left_side.discard();
34,304✔
1443
        right_side.discard();
34,304✔
1444
        return;
155,168✔
1445
    }
1446
}
1447

1,456✔
1448
DEFINE_MERGE(Instruction::EraseTable, Instruction::AddTable)
1,456✔
1449
{
336✔
1450
    if (same_table(left, right)) {
336✔
1451
        right_side.discard();
336✔
1452
    }
1,456✔
1453
}
1454

1455
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::AddTable);
1456
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::AddTable);
1457
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::AddTable);
1458
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddTable);
1459
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddTable);
1460
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddTable);
1461
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddTable);
8,320✔
1462
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddTable);
3,780✔
1463
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddTable);
3,780✔
1464
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddTable);
8,320!
1465
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddTable);
1466
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddTable);
×
1467

×
1468
/// EraseTable rules
×
1469

×
1470
DEFINE_NESTED_MERGE(Instruction::EraseTable)
×
1471
{
×
1472
    if (is_prefix_of(outer, inner)) {
8,320✔
1473
        inner_side.discard();
1474
    }
1475
}
1476

1477
DEFINE_MERGE(Instruction::EraseTable, Instruction::EraseTable)
1478
{
1479
    if (same_table(left, right)) {
1480
        left_side.discard();
1481
        right_side.discard();
1482
    }
1483
}
1484

1485
// Handled by nesting rule.
1486
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::EraseTable);
1487
DEFINE_MERGE_NOOP(Instruction::EraseObject, Instruction::EraseTable);
1488
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseTable);
1489
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseTable);
1490

1491
DEFINE_MERGE(Instruction::AddColumn, Instruction::EraseTable)
1492
{
1493
    // AddColumn on an erased table handled by nesting.
388,528✔
1494

388,528✔
1495
    if (left.type == Instruction::Payload::Type::Link && same_string(left.link_target_table, right.table)) {
8,190✔
1496
        // Erase of a table where the left side adds a link column targeting it.
8,190✔
1497
        Instruction::EraseColumn erase_column;
8,190✔
1498
        erase_column.table = right_side.adopt_string(left_side, left.table);
16,900✔
1499
        erase_column.field = right_side.adopt_string(left_side, left.field);
16,900✔
1500
        right_side.prepend(erase_column);
388,528✔
1501
        left_side.discard();
1502
    }
1503
}
1504

1505
// Handled by nested rule.
1506
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseTable);
1507
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseTable);
1508
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseTable);
1509
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseTable);
1510
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseTable);
1511
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseTable);
1512
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseTable);
1513

1514

1515
/// CreateObject rules
1516

1517
// CreateObject cannot interfere with instructions that have a longer path.
232,334✔
1518
DEFINE_NESTED_MERGE_NOOP(Instruction::CreateObject);
232,334!
1519

9,498✔
1520
// CreateObject is idempotent.
20,160✔
1521
DEFINE_MERGE_NOOP(Instruction::CreateObject, Instruction::CreateObject);
20,160✔
1522

232,334✔
1523
DEFINE_MERGE(Instruction::EraseObject, Instruction::CreateObject)
1524
{
1525
    if (same_object(left, right)) {
140,664✔
1526
        // CONFLICT: Create and Erase of the same object.
140,664✔
1527
        //
7,404✔
1528
        // RESOLUTION: Erase always wins.
7,404✔
1529
        right_side.discard();
7,404✔
1530
    }
14,800✔
1531
}
7,400✔
1532

7,400✔
1533
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::CreateObject);
7,400✔
1534
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::CreateObject);
7,400✔
1535
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::CreateObject);
7,400✔
1536
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::CreateObject);
14,800✔
1537
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::CreateObject);
140,664✔
1538
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::CreateObject);
1539
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::CreateObject);
1540
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::CreateObject);
1541
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::CreateObject);
1542
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::CreateObject);
1543

1544

1545
/// Erase rules
1546

1547
DEFINE_NESTED_MERGE(Instruction::EraseObject)
1548
{
1549
    if (is_prefix_of(outer, inner)) {
1550
        // Erase always wins.
1551
        inner_side.discard();
1552
    }
1553
}
1554

1555
DEFINE_MERGE(Instruction::EraseObject, Instruction::EraseObject)
37,068✔
1556
{
37,068✔
1557
    if (same_object(left, right)) {
17,748✔
1558
        // We keep the most recent erase. This prevents the situation where a
37,068!
1559
        // high number of EraseObject instructions in the past trumps a
32✔
1560
        // Erase-Create pair in the future.
32✔
1561
        if (right_side.timestamp() < left_side.timestamp()) {
64✔
1562
            right_side.discard();
64✔
1563
        }
17,716✔
1564
        else {
17,716✔
1565
            left_side.discard();
17,716✔
1566
        }
37,004!
1567
    }
80✔
1568
}
80✔
1569

37,004✔
1570
// Handled by nested merge.
1571
DEFINE_MERGE_NOOP(Instruction::Update, Instruction::EraseObject);
1572
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::EraseObject);
25,006✔
1573
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::EraseObject);
12,264✔
1574
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::EraseObject);
12,264✔
1575
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseObject);
25,006✔
1576
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseObject);
12,264✔
1577
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseObject);
25,006✔
1578
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseObject);
9,060✔
1579
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseObject);
9,060✔
1580
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseObject);
9,060✔
1581

×
1582

×
1583
/// Set rules
4,882✔
1584

9,060✔
1585
DEFINE_NESTED_MERGE(Instruction::Update)
8,712✔
1586
{
×
1587
    using Type = Instruction::Payload::Type;
×
1588

8,712✔
1589
    if (outer.value.type == Type::ObjectValue || outer.value.type == Type::Dictionary) {
8,712✔
1590
        // Creating an embedded object or a dictionary is an idempotent
8,712✔
1591
        // operation, and should not eliminate updates to the subtree.
348✔
1592
        return;
×
1593
    }
×
1594

4,882✔
1595
    // Setting a value higher up in the hierarchy overwrites any modification to
9,060✔
1596
    // the inner value, regardless of when this happened.
72✔
1597
    if (is_prefix_of(outer, inner)) {
72✔
1598
        inner_side.discard();
72✔
1599
    }
144✔
1600
}
24✔
1601

24✔
1602
DEFINE_MERGE(Instruction::Update, Instruction::Update)
24✔
1603
{
120✔
1604
    // The two instructions are at the same level of nesting.
24✔
1605

24✔
1606
    using Type = Instruction::Payload::Type;
24✔
1607

9,012✔
1608
    if (same_path(left, right)) {
4,858✔
1609
        bool left_is_default = false;
4,858✔
1610
        bool right_is_default = false;
4,858✔
1611
        if (!(left.is_array_update() == right.is_array_update())) {
4,858✔
1612
            bad_merge(left_side, left, "Merge error: left.is_array_update() == right.is_array_update()");
4,858✔
1613
        }
4,858✔
1614

4,858✔
1615
        if (!left.is_array_update()) {
9,012✔
1616
            if (right.is_array_update()) {
8,846✔
1617
                bad_merge(right_side, right, "Merge error: !right.is_array_update()");
4,606✔
1618
            }
4,606✔
1619
            left_is_default = left.is_default;
4,240✔
1620
            right_is_default = right.is_default;
4,240✔
1621
        }
4,240✔
1622
        else if (!(left.prior_size == right.prior_size)) {
8,846✔
1623
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
166✔
1624
        }
166✔
1625

84✔
1626
        if (left.value.type != right.value.type) {
84✔
1627
            // Embedded object / dictionary creation should always lose to an
82✔
1628
            // Update(value), because these structures are nested, and we need to
82✔
1629
            // discard any update inside the structure.
82✔
1630
            if (left.value.type == Type::Dictionary || left.value.type == Type::ObjectValue) {
166✔
1631
                left_side.discard();
9,012✔
1632
                return;
25,006✔
1633
            }
1634
            else if (right.value.type == Type::Dictionary || right.value.type == Type::ObjectValue) {
1635
                right_side.discard();
2,198✔
1636
                return;
1,036✔
1637
            }
1,036✔
1638
        }
2,198✔
1639

192✔
1640
        // CONFLICT: Two updates of the same element.
192✔
1641
        //
192✔
1642
        // RESOLUTION: Suppress the effect of the UPDATE operation with the lower
192✔
1643
        // timestamp. Note that the timestamps can never be equal. This is
192✔
1644
        // achieved on both sides by discarding the received UPDATE operation if
436✔
1645
        // it has a lower timestamp than the previously applied UPDATE operation.
×
1646
        if (left_is_default == right_is_default) {
×
1647
            if (left_side.timestamp() < right_side.timestamp()) {
×
1648
                left_side.discard(); // --->
192✔
1649
            }
436✔
1650
            else {
192✔
1651
                right_side.discard(); // <---
192✔
1652
            }
436✔
1653
        }
344✔
1654
        else {
32✔
1655
            if (left_is_default) {
32✔
1656
                left_side.discard();
32✔
1657
            }
60✔
1658
            else {
60✔
1659
                right_side.discard();
128✔
1660
            }
128✔
1661
        }
284✔
1662
    }
284✔
1663
}
284✔
1664

284✔
1665
DEFINE_MERGE(Instruction::AddInteger, Instruction::Update)
92✔
1666
{
92✔
1667
    // The two instructions are at the same level of nesting.
92✔
1668

436✔
1669
    if (same_path(left, right)) {
2,198✔
1670
        // CONFLICT: Add vs Set of the same element.
1671
        //
1672
        // RESOLUTION: If the Add was later than the Set, add its value to
1673
        // the payload of the Set instruction. Otherwise, discard it.
1674

×
1675
        if (!(right.value.type == Instruction::Payload::Type::Int || right.value.is_null())) {
×
1676
            bad_merge(right_side, right,
×
1677
                      "Merge error: right.value.type == Instruction::Payload::Type::Int || right.value.is_null()");
×
1678
        }
×
1679

1680
        bool right_is_default = !right.is_array_update() && right.is_default;
1681

50,362✔
1682
        // Note: AddInteger survives SetDefault, regardless of timestamp.
50,362✔
1683
        if (right_side.timestamp() < left_side.timestamp() || right_is_default) {
8,564✔
1684
            if (right.value.is_null()) {
8,564✔
1685
                // The AddInteger happened "after" the Set(null). This becomes a
×
1686
                // no-op, but if the server later integrates a Set(int) that
×
1687
                // came-before the AddInteger, it will be taken into account again.
8,564✔
1688
                return;
×
1689
            }
×
1690

8,564✔
1691
            // Wrapping add
×
1692
            uint64_t ua = uint64_t(right.value.data.integer);
×
1693
            uint64_t ub = uint64_t(left.value);
8,564✔
1694
            right.value.data.integer = int64_t(ua + ub);
8,564✔
1695
        }
3,448✔
1696
        else {
3,448✔
1697
            left_side.discard();
8,564✔
1698
        }
50,362✔
1699
    }
1700
}
1701

24✔
1702
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::Update);
24✔
1703

×
1704
DEFINE_MERGE(Instruction::EraseColumn, Instruction::Update)
1705
{
×
1706
    if (same_column(left, right)) {
×
1707
        right_side.discard();
×
1708
    }
×
1709
}
×
1710

×
1711
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::Update)
1712
{
1713
    if (same_container(left, right)) {
×
1714
        REALM_ASSERT(right.is_array_update());
×
1715
        if (!(left.prior_size == right.prior_size)) {
24✔
1716
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
1717
        }
1718
        if (!(left.index() <= left.prior_size)) {
10,894✔
1719
            bad_merge(left_side, left, "Merge error: left.index() <= left.prior_size");
10,894✔
1720
        }
2,704✔
1721
        if (!(right.index() < right.prior_size)) {
2,704✔
1722
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
×
1723
        }
×
1724
        right.prior_size += 1;
2,704✔
1725
        if (right.index() >= left.index()) {
×
1726
            right.index() += 1; // --->
×
1727
        }
2,704✔
1728
    }
×
1729
}
×
1730

1,448✔
1731
DEFINE_MERGE(Instruction::ArrayMove, Instruction::Update)
1,448✔
1732
{
1,448✔
1733
    if (same_container(left, right)) {
1,448✔
1734
        REALM_ASSERT(right.is_array_update());
2,704✔
1735

1,448✔
1736
        if (!(left.index() < left.prior_size)) {
2,704✔
1737
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
328✔
1738
        }
328✔
1739
        if (!(right.index() < right.prior_size)) {
328✔
1740
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
596✔
1741
        }
596✔
1742

2,108✔
1743
        // FIXME: This marks both sides as dirty, even when they are unmodified.
1,128✔
1744
        merge_get_vs_move(right.index(), left.index(), left.ndx_2);
1,128✔
1745
    }
2,704✔
1746
}
10,894✔
1747

1748
DEFINE_MERGE(Instruction::ArrayErase, Instruction::Update)
1749
{
1750
    if (same_container(left, right)) {
1751
        REALM_ASSERT(right.is_array_update());
1752
        if (!(left.prior_size == right.prior_size)) {
1753
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
1754
        }
1755
        if (!(left.index() < left.prior_size)) {
1756
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
1757
        }
1758
        if (!(right.index() < right.prior_size)) {
1759
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
1760
        }
1761

1762
        // CONFLICT: Update of a removed element.
1763
        //
1764
        // RESOLUTION: Discard the UPDATE operation received on the right side.
1765
        right.prior_size -= 1;
1766

1767
        if (left.index() == right.index()) {
1768
            // CONFLICT: Update of a removed element.
1769
            //
1770
            // RESOLUTION: Discard the UPDATE operation received on the right side.
1771
            right_side.discard();
1772
        }
1773
        else if (right.index() > left.index()) {
35,494✔
1774
            right.index() -= 1;
35,494✔
1775
        }
10,014✔
1776
    }
10,014✔
1777
}
8✔
1778

8✔
1779
// Handled by nested rule
8✔
1780
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::Update);
8✔
1781
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::Update);
4,918✔
1782
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::Update);
10,014✔
1783

8✔
1784

8✔
1785
/// AddInteger rules
8✔
1786

4,918✔
1787
DEFINE_NESTED_MERGE_NOOP(Instruction::AddInteger);
10,014✔
1788
DEFINE_MERGE_NOOP(Instruction::AddInteger, Instruction::AddInteger);
×
1789
DEFINE_MERGE_NOOP(Instruction::AddColumn, Instruction::AddInteger);
×
1790
DEFINE_MERGE_NOOP(Instruction::EraseColumn, Instruction::AddInteger);
×
1791
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddInteger);
×
1792
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddInteger);
×
1793
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddInteger);
×
1794
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddInteger);
×
1795
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddInteger);
×
1796
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddInteger);
×
1797

×
1798

×
1799
/// AddColumn rules
×
1800

×
1801
DEFINE_NESTED_MERGE_NOOP(Instruction::AddColumn);
1802

×
1803
DEFINE_MERGE(Instruction::AddColumn, Instruction::AddColumn)
×
1804
{
×
1805
    if (same_column(left, right)) {
×
1806
        StringData left_name = left_side.get_string(left.field);
×
1807
        if (left.type != right.type) {
×
1808
            bad_merge(
4,918✔
1809
                "Schema mismatch: Property '%1' in class '%2' is of type %3 on one side and type %4 on the other.",
10,014✔
1810
                left_name, left_side.get_string(left.table), get_type_name(left.type), get_type_name(right.type));
1,930✔
1811
        }
1,930✔
1812

1,930✔
1813
        if (left.nullable != right.nullable) {
6✔
1814
            bad_merge("Schema mismatch: Property '%1' in class '%2' is nullable on one side and not on the other.",
6✔
1815
                      left_name, left_side.get_string(left.table));
6✔
1816
        }
6✔
1817

1,930✔
1818
        if (left.collection_type != right.collection_type) {
4,918✔
1819
            auto collection_type_name = [](Instruction::AddColumn::CollectionType type) -> const char* {
4,918✔
1820
                switch (type) {
4,918✔
1821
                    case Instruction::AddColumn::CollectionType::Single:
10,014✔
1822
                        return "single value";
10,014✔
1823
                    case Instruction::AddColumn::CollectionType::List:
10,014✔
1824
                        return "list";
35,494✔
1825
                    case Instruction::AddColumn::CollectionType::Dictionary:
1826
                        return "dictionary";
1827
                    case Instruction::AddColumn::CollectionType::Set:
×
1828
                        return "set";
×
1829
                }
×
1830
                REALM_TERMINATE("");
×
1831
            };
×
1832

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

1840
        if (left.type == Instruction::Payload::Type::Link) {
1841
            StringData left_target = left_side.get_string(left.link_target_table);
1842
            StringData right_target = right_side.get_string(right.link_target_table);
1843
            if (left_target != right_target) {
1844
                bad_merge("Schema mismatch: Link property '%1' in class '%2' points to class '%3' on one side and to "
1845
                          "'%4' on the other.",
1846
                          left_name, left_side.get_string(left.table), left_target, right_target);
×
1847
            }
×
1848
        }
×
1849

×
1850
        // Name, type, nullability and link targets match -- discard both
×
1851
        // sides and proceed.
×
1852
        left_side.discard();
1853
        right_side.discard();
1854
    }
1855
}
1856

1857
DEFINE_MERGE(Instruction::EraseColumn, Instruction::AddColumn)
1858
{
1859
    if (same_column(left, right)) {
1860
        right_side.discard();
1861
    }
1862
}
1863

16✔
1864
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::AddColumn);
16!
1865
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::AddColumn);
16✔
1866
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::AddColumn);
16!
1867
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::AddColumn);
8✔
1868
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::AddColumn);
8✔
1869
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::AddColumn);
16✔
1870

16✔
1871

1872
/// EraseColumn rules
1873

52,796✔
1874
DEFINE_NESTED_MERGE_NOOP(Instruction::EraseColumn);
52,796✔
1875

15,568✔
1876
DEFINE_MERGE(Instruction::EraseColumn, Instruction::EraseColumn)
×
1877
{
×
1878
    if (same_column(left, right)) {
15,568✔
1879
        left_side.discard();
15,568✔
1880
        right_side.discard();
7,830✔
1881
    }
15,568✔
1882
}
3,004✔
1883

3,004✔
1884
DEFINE_MERGE_NOOP(Instruction::ArrayInsert, Instruction::EraseColumn);
12,564✔
1885
DEFINE_MERGE_NOOP(Instruction::ArrayMove, Instruction::EraseColumn);
3,008✔
1886
DEFINE_MERGE_NOOP(Instruction::ArrayErase, Instruction::EraseColumn);
3,008✔
1887
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::EraseColumn);
9,556✔
1888
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::EraseColumn);
4,820✔
1889
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::EraseColumn);
4,820✔
1890

4,820✔
1891
/// ArrayInsert rules
4,820✔
1892

9,556✔
1893
DEFINE_NESTED_MERGE(Instruction::ArrayInsert)
4,774✔
1894
{
4,774✔
1895
    if (is_container_prefix_of(outer, inner)) {
4,782✔
1896
        auto& index = corresponding_index_in_path(outer, inner);
4,782✔
1897
        if (index >= outer.index()) {
4,782✔
1898
            index += 1;
9,556✔
1899
        }
15,568✔
1900
    }
52,796✔
1901
}
1902

1903
DEFINE_MERGE(Instruction::ArrayInsert, Instruction::ArrayInsert)
×
1904
{
×
1905
    if (same_container(left, right)) {
×
1906
        if (!(left.prior_size == right.prior_size)) {
1907
            bad_merge(right_side, right, "Exception: left.prior_size == right.prior_size");
1908
        }
×
1909
        left.prior_size++;
×
1910
        right.prior_size++;
×
1911

×
1912
        if (left.index() > right.index()) {
×
1913
            left.index() += 1; // --->
×
1914
        }
1915
        else if (left.index() < right.index()) {
1916
            right.index() += 1; // <---
×
1917
        }
×
1918
        else { // left index == right index
×
1919
            // CONFLICT: Two element insertions at the same position.
×
1920
            //
×
1921
            // Resolution: Place the inserted elements in order of increasing
×
1922
            // timestamp. Note that the timestamps can never be equal.
×
1923
            if (left_side.timestamp() < right_side.timestamp()) {
1924
                right.index() += 1;
1925
            }
1926
            else {
1927
                left.index() += 1;
×
1928
            }
×
1929
        }
×
1930
    }
×
1931
}
×
1932

×
1933
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayInsert)
×
1934
{
×
1935
    if (same_container(left, right)) {
×
1936
        left.prior_size += 1;
1937

1938
        // Left insertion vs right removal
22,650✔
1939
        if (right.index() > left.index()) {
22,650✔
1940
            right.index() -= 1; // --->
7,600✔
1941
        }
×
1942
        else {
×
1943
            left.index() += 1; // <---
7,600✔
1944
        }
×
1945

×
1946
        // Left insertion vs left insertion
7,600✔
1947
        if (right.index() < left.ndx_2) {
×
1948
            left.ndx_2 += 1; // <---
×
1949
        }
3,994✔
1950
        else if (right.index() > left.ndx_2) {
7,600✔
1951
            right.index() += 1; // --->
7,600✔
1952
        }
7,600✔
1953
        else { // right.index() == left.ndx_2
3,028✔
1954
            // CONFLICT: Insertion and movement to same position.
3,028✔
1955
            //
4,572✔
1956
            // RESOLUTION: Place the two elements in order of increasing
4,572✔
1957
            // timestamp. Note that the timestamps can never be equal.
4,572✔
1958
            if (left_side.timestamp() < right_side.timestamp()) {
7,600✔
1959
                left.ndx_2 += 1; // <---
22,650✔
1960
            }
1961
            else {
1962
                right.index() += 1; // --->
1963
            }
1964
        }
1965
    }
1966
}
1967

1968
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayInsert)
1969
{
1970
    if (same_container(left, right)) {
×
1971
        if (!(left.prior_size == right.prior_size)) {
×
1972
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
×
1973
        }
×
1974
        if (!(left.index() < left.prior_size)) {
×
1975
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
1976
        }
1977
        if (!(right.index() <= right.prior_size)) {
1978
            bad_merge(left_side, left, "Merge error: right.index() <= right.prior_size");
28✔
1979
        }
28✔
1980

28✔
1981
        left.prior_size++;
×
1982
        right.prior_size--;
×
1983
        if (right.index() <= left.index()) {
28✔
1984
            left.index() += 1; // --->
×
1985
        }
×
1986
        else {
28✔
1987
            right.index() -= 1; // <---
×
1988
        }
×
1989
    }
28✔
1990
}
×
1991

×
1992
// Handled by nested rules
28✔
1993
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayInsert);
×
1994
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayInsert);
×
1995
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayInsert);
14✔
1996

28✔
1997

4✔
1998
/// ArrayMove rules
4✔
1999

24✔
2000
DEFINE_NESTED_MERGE(Instruction::ArrayMove)
4✔
2001
{
4✔
2002
    if (is_container_prefix_of(outer, inner)) {
20✔
2003
        auto& index = corresponding_index_in_path(outer, inner);
10✔
2004
        merge_get_vs_move(outer.index(), index, outer.ndx_2);
10✔
2005
    }
10✔
2006
}
10✔
2007

10✔
2008
DEFINE_MERGE(Instruction::ArrayMove, Instruction::ArrayMove)
10✔
2009
{
10✔
2010
    if (same_container(left, right)) {
20✔
2011
        if (!(left.prior_size == right.prior_size)) {
12✔
2012
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
12✔
2013
        }
12✔
2014
        if (!(left.index() < left.prior_size)) {
×
2015
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
×
2016
        }
12✔
2017
        if (!(right.index() < right.prior_size)) {
8✔
2018
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
8✔
2019
        }
8✔
2020
        if (!(left.ndx_2 < left.prior_size)) {
×
2021
            bad_merge(left_side, left, "Merge error: left.ndx_2 < left.prior_size");
×
2022
        }
8✔
2023
        if (!(right.ndx_2 < right.prior_size)) {
8✔
2024
            bad_merge(right_side, right, "Merge error: right.ndx_2 < right.prior_size");
20✔
2025
        }
20✔
2026

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

×
2058
        // Left insertion vs right removal
×
2059
        if (left.ndx_2 > right.index()) {
×
2060
            left.ndx_2 -= 1; // --->
×
2061
        }
×
2062
        else {
4✔
2063
            right.index() += 1; // <---
8✔
2064
        }
×
2065

×
2066
        // Left removal vs right insertion
8✔
2067
        if (left.index() < right.ndx_2) {
×
2068
            right.ndx_2 -= 1; // <---
×
2069
        }
8✔
2070
        else {
28✔
2071
            left.index() += 1; // --->
2072
        }
2073

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

×
2094
        if (left.index() == left.ndx_2) {
×
2095
            left_side.discard(); // --->
×
2096
        }
×
2097
        if (right.index() == right.ndx_2) {
2098
            right_side.discard(); // <---
×
2099
        }
×
2100
    }
×
2101
}
×
2102

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

×
2116
        right.prior_size -= 1;
×
2117

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

8✔
2143
            if (right.index() == right.ndx_2) {
8✔
2144
                right_side.discard(); // <---
2145
            }
2146
        }
2,646✔
2147
    }
2,646✔
2148
}
1,084✔
2149

×
2150
// Handled by nested rule.
×
2151
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayMove);
1,084✔
2152
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayMove);
×
2153
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayMove);
×
2154

1,084✔
2155

×
2156
/// ArrayErase rules
×
2157

558✔
2158
DEFINE_NESTED_MERGE(Instruction::ArrayErase)
1,084✔
2159
{
1,084✔
2160
    if (is_prefix_of(outer, inner)) {
558✔
2161
        // Erase of subtree - inner instruction touches the subtree.
1,084✔
2162
        inner_side.discard();
432✔
2163
    }
432✔
2164
    else if (is_container_prefix_of(outer, inner)) {
652✔
2165
        // Erase of a sibling element in the container - adjust the path.
436✔
2166
        auto& index = corresponding_index_in_path(outer, inner);
436✔
2167
        if (outer.index() < index) {
216✔
2168
            index -= 1;
104✔
2169
        }
104✔
2170
        else {
104✔
2171
            REALM_ASSERT(index != outer.index());
216✔
2172
        }
216✔
2173
    }
216✔
2174
}
1,084✔
2175

2,646✔
2176
DEFINE_MERGE(Instruction::ArrayErase, Instruction::ArrayErase)
2177
{
2178
    if (same_container(left, right)) {
2179
        if (!(left.prior_size == right.prior_size)) {
2180
            bad_merge(left_side, left, "Merge error: left.prior_size == right.prior_size");
2181
        }
2182
        if (!(left.index() < left.prior_size)) {
2183
            bad_merge(left_side, left, "Merge error: left.index() < left.prior_size");
2184
        }
2185
        if (!(right.index() < right.prior_size)) {
2186
            bad_merge(right_side, right, "Merge error: right.index() < right.prior_size");
832✔
2187
        }
174✔
2188

832!
2189
        left.prior_size -= 1;
336✔
2190
        right.prior_size -= 1;
336✔
2191

832✔
2192
        if (left.index() > right.index()) {
2193
            left.index() -= 1; // --->
2194
        }
16✔
2195
        else if (left.index() < right.index()) {
16✔
2196
            right.index() -= 1; // <---
8✔
2197
        }
8✔
2198
        else { // left.index() == right.index()
8✔
2199
            // CONFLICT: Two removals of the same element.
8✔
2200
            //
8✔
2201
            // RESOLUTION: On each side, discard the received REMOVE operation.
16✔
2202
            left_side.discard();  // --->
12✔
2203
            right_side.discard(); // <---
12✔
2204
        }
4✔
2205
    }
4✔
2206
}
4✔
2207

16✔
2208
// Handled by nested rules.
16✔
2209
DEFINE_MERGE_NOOP(Instruction::Clear, Instruction::ArrayErase);
2210
DEFINE_MERGE_NOOP(Instruction::SetInsert, Instruction::ArrayErase);
2211
DEFINE_MERGE_NOOP(Instruction::SetErase, Instruction::ArrayErase);
8✔
2212

8!
2213

4✔
2214
/// Clear rules
4✔
2215

8✔
2216
DEFINE_NESTED_MERGE(Instruction::Clear)
2217
{
2218
    // Note: Clear instructions do not have an index in their path.
8✔
2219
    if (is_prefix_of(outer, inner)) {
8!
2220
        inner_side.discard();
4✔
2221
    }
4✔
2222
}
8✔
2223

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

152✔
2241
DEFINE_MERGE(Instruction::SetInsert, Instruction::Clear)
24✔
2242
{
12✔
2243
    if (same_path(left, right)) {
12✔
2244
        left_side.discard();
12✔
2245
    }
12✔
2246
}
12✔
2247

24✔
2248
DEFINE_MERGE(Instruction::SetErase, Instruction::Clear)
152✔
2249
{
156✔
2250
    if (same_path(left, right)) {
2251
        left_side.discard();
2252
    }
68✔
2253
}
68✔
2254

32✔
2255

32✔
2256
/// SetInsert rules
32✔
2257

32✔
2258
DEFINE_NESTED_MERGE_NOOP(Instruction::SetInsert);
32✔
2259

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

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

2,460,530✔
2303

2,460,530✔
2304
/// SetErase rules.
2,460,530✔
2305

2306
DEFINE_NESTED_MERGE_NOOP(Instruction::SetErase);
2307

2308
DEFINE_MERGE(Instruction::SetErase, Instruction::SetErase)
1,048,356✔
2309
{
1,048,356✔
2310
    if (same_path(left, right)) {
1,048,356✔
2311
        // CONFLICT: Two erases in the same set.
2312
        //
2313
        // RESOLUTION: If the values are the same, discard the instruction with the lower timestamp.
2314
        // Otherwise, do nothing.
1,048,354✔
2315
        if (left.value == right.value) {
1,048,354✔
2316
            if (left_side.timestamp() < right_side.timestamp()) {
1,048,356✔
2317
                left_side.discard();
1,048,356✔
2318
            }
1,048,356✔
2319
            else {
1,048,354✔
2320
                right_side.discard();
1,048,354✔
2321
            }
2322
        }
2323
    }
2,515,414✔
2324
}
1,226,882✔
2325

2,515,414✔
2326

2,515,414✔
2327
///
1,226,882✔
2328
/// END OF MERGE RULES!
2,515,414✔
2329
///
170,728✔
2330

170,728✔
2331
} // namespace
2,515,414✔
2332

217,632✔
2333
namespace _impl {
217,632✔
2334
template <class Left, class Right>
2,515,414✔
2335
void merge_instructions_2(Left& left, Right& right, TransformerImpl::MajorSide& left_side,
518,156✔
2336
                          TransformerImpl::MinorSide& right_side)
518,156✔
2337
{
2,515,414✔
2338
    Merge<Left, Right>::merge(left, right, left_side, right_side);
592,184✔
2339
}
592,184✔
2340

1,226,882✔
2341
template <class Outer, class Inner, class OuterSide, class InnerSide>
1,226,882✔
2342
void merge_nested_2(Outer& outer, Inner& inner, OuterSide& outer_side, InnerSide& inner_side)
1,226,882✔
2343
{
1,226,882✔
2344
    MergeNested<Outer>::merge(outer, inner, outer_side, inner_side);
2,515,414✔
2345
}
428,166✔
2346

428,166✔
2347
void TransformerImpl::Transformer::merge_instructions(MajorSide& their_side, MinorSide& our_side)
27,466✔
2348
{
2,087,248✔
2349
    // FIXME: Find a way to avoid heap-copies of the path.
2,087,248✔
2350
    Instruction their_before = their_side.get();
620,188✔
2351
    Instruction our_before = our_side.get();
620,188✔
2352

27,414✔
2353
    if (their_side.get().get_if<Instruction::Update>()) {
2,460,534✔
2354
        REALM_ASSERT(their_side.m_path_len > 2);
1,199,948✔
2355
    }
2,460,538✔
2356
    if (our_side.get().get_if<Instruction::Update>()) {
1,199,946✔
2357
        REALM_ASSERT(our_side.m_path_len > 2);
1,199,946✔
2358
    }
1,199,946✔
2359
    if (their_side.get().get_if<Instruction::EraseObject>()) {
1,199,946✔
2360
        REALM_ASSERT(their_side.m_path_len == 2);
1,199,946✔
2361
    }
2,460,536✔
2362
    if (our_side.get().get_if<Instruction::EraseObject>()) {
2,460,530✔
2363
        REALM_ASSERT(our_side.m_path_len == 2);
2,460,530✔
2364
    }
2,460,530✔
2365

2,460,528✔
2366
    // Update selections on the major side (outer loop) according to events on
2,460,534✔
2367
    // the minor side (inner loop). The selection may only be impacted if the
1,199,948✔
2368
    // instruction level is lower (i.e. at a higher point in the hierarchy).
1,199,948✔
2369
    if (our_side.m_path_len < their_side.m_path_len) {
1,199,948✔
2370
        merge_nested(our_side, their_side);
1,199,948✔
2371
        if (their_side.was_discarded)
2,460,534✔
2372
            return;
2,421,506✔
2373
    }
2,421,506✔
2374
    else if (our_side.m_path_len > their_side.m_path_len) {
29,530✔
2375
        merge_nested(their_side, our_side);
29,530✔
2376
        if (our_side.was_discarded)
2,421,506✔
2377
            return;
1,199,948✔
2378
    }
2,460,534✔
2379

2,421,918✔
2380
    if (!their_side.was_discarded && !our_side.was_discarded) {
2,421,918✔
2381
        // Even if the instructions were nested, we must still perform a regular
29,534✔
2382
        // merge, because link-related instructions contain information from higher
29,534✔
2383
        // levels (both rows, columns, and tables).
2,421,918✔
2384
        //
2,460,534✔
2385
        // FIXME: This condition goes away when dangling links are implemented.
2386
        their_side.get().visit([&](auto& their_instruction) {
2387
            our_side.get().visit([&](auto& our_instruction) {
2388
                merge_instructions_2(their_instruction, our_instruction, their_side, our_side);
2389
            });
2390
        });
2391
    }
414,268✔
2392

414,268✔
2393
    // Note: `left` and/or `right` may be dangling at this point due to
414,268✔
2394
    // discard/prepend. However, if they were not discarded, their iterators are
414,268✔
2395
    // required to point to an instruction of the same type.
414,268✔
2396
    if (!their_side.was_discarded && !their_side.was_replaced) {
210,018✔
2397
        const auto& their_after = their_side.get();
414,268✔
2398
        if (!(their_after == their_before)) {
414,268!
2399
            their_side.m_changeset->set_dirty(true);
414,268✔
2400
        }
414,268✔
2401
    }
414,268✔
2402

×
2403
    if (!our_side.was_discarded && !our_side.was_replaced) {
×
2404
        const auto& our_after = our_side.get();
414,268✔
2405
        if (!(our_after == our_before)) {
414,268✔
2406
            our_side.m_changeset->set_dirty(true);
210,018✔
2407
        }
414,268✔
2408
    }
414,268✔
2409
}
414,268✔
2410

210,018✔
2411

210,018✔
2412
template <class OuterSide, class InnerSide>
210,018✔
2413
void TransformerImpl::Transformer::merge_nested(OuterSide& outer_side, InnerSide& inner_side)
210,018✔
2414
{
210,018✔
2415
    outer_side.get().visit([&](auto& outer) {
210,018✔
2416
        inner_side.get().visit([&](auto& inner) {
852,988✔
2417
            merge_nested_2(outer, inner, outer_side, inner_side);
438,720✔
2418
        });
438,720✔
2419
    });
438,720✔
2420
}
438,720✔
2421

221,058✔
2422

438,720✔
2423
void TransformerImpl::merge_changesets(file_ident_type local_file_ident, Changeset* their_changesets,
438,720✔
2424
                                       size_t their_size, Changeset** our_changesets, size_t our_size,
10,299,348✔
2425
                                       util::Logger& logger)
9,885,080✔
2426
{
9,885,080✔
2427
    REALM_ASSERT(their_size != 0);
9,885,080✔
2428
    REALM_ASSERT(our_size != 0);
9,885,080✔
2429
    bool trace = false;
9,885,080✔
2430
#if REALM_DEBUG && !REALM_UWP
4,943,976✔
2431
    // FIXME: Not thread-safe (use config parameter instead and confine environment reading to test/test_all.cpp)
9,885,080✔
2432
    const char* trace_p = ::getenv("UNITTEST_TRACE_TRANSFORM");
9,885,080✔
2433
    trace = (trace_p && StringData{trace_p} != "no");
210,018✔
2434
    static std::mutex trace_mutex;
210,018✔
2435
    util::Optional<std::unique_lock<std::mutex>> l;
852,974✔
2436
    if (trace) {
438,706✔
2437
        l = std::unique_lock<std::mutex>{trace_mutex};
438,706✔
2438
    }
438,706✔
2439
#endif
438,706✔
2440
    Transformer transformer{trace};
210,018✔
2441

414,268✔
2442
    _impl::ChangesetIndex their_index;
414,268✔
2443
    size_t their_num_instructions = 0;
414,268✔
2444
    size_t our_num_instructions = 0;
414,268✔
2445

210,018✔
2446
    // Loop through all instructions on both sides and build conflict groups.
210,018✔
2447
    // This causes the index to merge ranges that are connected by instructions
414,268✔
2448
    // on the left side, but which aren't connected on the right side.
×
2449
    // FIXME: The conflict groups can be persisted as part of the changeset to
×
2450
    // skip this step in the future.
×
2451
    for (size_t i = 0; i < their_size; ++i) {
×
2452
        size_t num_instructions = their_changesets[i].size();
×
2453
        their_num_instructions += num_instructions;
×
2454
        logger.trace("Scanning incoming changeset [%1/%2] (%3 instructions)", i + 1, their_size, num_instructions);
×
2455

×
2456
        their_index.scan_changeset(their_changesets[i]);
×
2457
    }
×
2458
    for (size_t i = 0; i < our_size; ++i) {
×
2459
        Changeset& our_changeset = *our_changesets[i];
×
2460
        size_t num_instructions = our_changeset.size();
×
2461
        our_num_instructions += num_instructions;
×
2462
        logger.trace("Scanning local changeset [%1/%2] (%3 instructions)", i + 1, our_size, num_instructions);
2463

×
2464
        their_index.scan_changeset(our_changeset);
×
2465
    }
×
2466

×
2467
    // Build the index.
2468
    for (size_t i = 0; i < their_size; ++i) {
×
2469
        logger.trace("Indexing incoming changeset [%1/%2] (%3 instructions)", i + 1, their_size,
×
2470
                     their_changesets[i].size());
×
2471
        their_index.add_changeset(their_changesets[i]);
×
2472
    }
2473

×
2474
    logger.debug("Finished changeset indexing (incoming: %1 changeset(s) / %2 instructions, local: %3 "
×
2475
                 "changeset(s) / %4 instructions, conflict group(s): %5)",
×
2476
                 their_size, their_num_instructions, our_size, our_num_instructions,
×
2477
                 their_index.get_num_conflict_groups());
2478

×
2479
#if REALM_DEBUG // LCOV_EXCL_START
×
2480
    if (trace) {
×
2481
        std::cerr << TERM_YELLOW << "\n=> PEER " << std::hex << local_file_ident
×
2482
                  << " merging "
2483
                     "changeset(s)/from peer(s):\n";
2484
        for (size_t i = 0; i < their_size; ++i) {
2485
            std::cerr << "Changeset version " << std::dec << their_changesets[i].version << " from peer "
210,018✔
2486
                      << their_changesets[i].origin_file_ident << " at timestamp "
10,299,366✔
2487
                      << their_changesets[i].origin_timestamp << "\n";
9,885,098✔
2488
        }
9,885,098✔
2489
        std::cerr << "Transforming through local changeset(s):\n";
9,885,098✔
2490
        for (size_t i = 0; i < our_size; ++i) {
9,885,098✔
2491
            std::cerr << "Changeset version " << our_changesets[i]->version << " from peer "
4,943,980✔
2492
                      << our_changesets[i]->origin_file_ident << " at timestamp "
9,885,098✔
2493
                      << our_changesets[i]->origin_timestamp << "\n";
4,943,980✔
2494
        }
9,885,098✔
2495

9,885,098✔
2496
        for (size_t i = 0; i < our_size; ++i) {
9,885,098✔
2497
            std::cerr << TERM_RED << "\nLOCAL (RECIPROCAL) CHANGESET BEFORE MERGE:\n" << TERM_RESET;
210,018✔
2498
            our_changesets[i]->print(std::cerr);
414,268✔
2499
        }
414,268✔
2500

414,268✔
2501
        for (size_t i = 0; i < their_size; ++i) {
414,268✔
2502
            std::cerr << TERM_RED << "\nINCOMING CHANGESET BEFORE MERGE:\n" << TERM_RESET;
210,018✔
2503
            their_changesets[i].print(std::cerr);
210,018✔
2504
        }
210,018✔
2505

414,268✔
2506
        std::cerr << TERM_MAGENTA << "\nINCOMING CHANGESET INDEX:\n" << TERM_RESET;
414,268✔
2507
        their_index.print(std::cerr);
210,018✔
2508
        std::cerr << '\n';
210,018✔
2509
        their_index.verify();
414,268✔
2510

×
2511
        std::cerr << TERM_YELLOW << std::setw(80) << std::left << "MERGE TRACE (incoming):"
×
2512
                  << "MERGE TRACE (local):\n"
×
2513
                  << TERM_RESET;
×
2514
    }
×
2515
#else
×
2516
    static_cast<void>(local_file_ident);
×
2517
#endif // REALM_DEBUG LCOV_EXCL_STOP
×
2518

×
2519
    for (size_t i = 0; i < our_size; ++i) {
×
2520
        logger.trace(
×
2521
            "Transforming local changeset [%1/%2] through %3 incoming changeset(s) with %4 conflict group(s)", i + 1,
414,268✔
2522
            our_size, their_size, their_index.get_num_conflict_groups());
414,268✔
2523
        Changeset* our_changeset = our_changesets[i];
2524

2525
        transformer.m_major_side.set_next_changeset(our_changeset);
2526
        // MinorSide uses the index to find the Changeset.
2527
        transformer.m_minor_side.m_changeset_index = &their_index;
2528
        transformer.transform(); // Throws
2529
    }
451,632✔
2530

451,632✔
2531
    logger.debug("Finished transforming %1 local changesets through %2 incoming changesets (%3 vs %4 "
227,636✔
2532
                 "instructions, in %5 conflict groups)",
451,632✔
2533
                 our_size, their_size, our_num_instructions, their_num_instructions,
227,636✔
2534
                 their_index.get_num_conflict_groups());
227,636✔
2535

227,636✔
2536
#if REALM_DEBUG // LCOV_EXCL_START
451,632✔
2537
    // Check that the index is still valid after transformation.
451,632✔
2538
    their_index.verify();
227,636✔
2539
#endif // REALM_DEBUG LCOV_EXCL_STOP
907,962✔
2540

230,594✔
2541
#if REALM_DEBUG // LCOV_EXCL_START
230,594✔
2542
    if (trace) {
250,710✔
2543
        for (size_t i = 0; i < our_size; ++i) {
36,814✔
2544
            std::cerr << TERM_CYAN << "\nRECIPROCAL CHANGESET AFTER MERGE:\n" << TERM_RESET;
36,814✔
2545
            our_changesets[i]->print(std::cerr);
230,594✔
2546
            std::cerr << '\n';
456,330✔
2547
        }
456,330✔
2548
        for (size_t i = 0; i < their_size; ++i) {
10,341,428✔
2549
            std::cerr << TERM_CYAN << "INCOMING CHANGESET AFTER MERGE:\n" << TERM_RESET;
10,341,428✔
2550
            their_changesets[i].print(std::cerr);
10,341,428✔
2551
            std::cerr << '\n';
10,341,428✔
2552
        }
456,338✔
2553
    }
4,943,992✔
2554
#endif // LCOV_EXCL_STOP REALM_DEBUG
9,885,090✔
2555
}
9,885,090✔
2556

9,885,090✔
2557
size_t TransformerImpl::transform_remote_changesets(TransformHistory& history, file_ident_type local_file_ident,
4,943,992✔
2558
                                                    version_type current_local_version,
9,885,090✔
2559
                                                    util::Span<Changeset> parsed_changesets,
9,885,090✔
2560
                                                    util::UniqueFunction<bool(const Changeset*)> changeset_applier,
230,594✔
2561
                                                    util::Logger& logger)
456,330✔
2562
{
230,594✔
2563
    REALM_ASSERT(local_file_ident != 0);
456,330✔
2564

414,270✔
2565
    std::vector<Changeset*> our_changesets;
210,020✔
2566

210,020✔
2567
    // p points to the beginning of a range of changesets that share the same
9,067,686✔
2568
    // "base", i.e. are based on the same local version.
9,067,686✔
2569
    auto p = parsed_changesets.begin();
9,067,686✔
2570
    auto parsed_changesets_end = parsed_changesets.end();
414,270✔
2571

230,594✔
2572
    try {
456,330✔
2573
        while (p != parsed_changesets_end) {
944,736✔
2574
            // Find the range of incoming changesets that share the same
244,326✔
2575
            // last_integrated_local_version, which means we can merge them in one go.
244,326✔
2576
            auto same_base_range_end = std::find_if(p + 1, parsed_changesets_end, [&](auto& changeset) {
244,326✔
2577
                return p->last_integrated_remote_version != changeset.last_integrated_remote_version;
488,406!
2578
            });
488,406✔
2579

456,330✔
2580
            version_type begin_version = p->last_integrated_remote_version;
×
2581
            version_type end_version = current_local_version;
×
2582
            for (;;) {
230,594✔
2583
                HistoryEntry history_entry;
456,330✔
2584
                version_type version = history.find_history_entry(begin_version, end_version, history_entry);
456,330✔
2585
                if (version == 0)
227,636✔
2586
                    break; // No more local changesets
227,636✔
2587

227,636✔
2588
                Changeset& our_changeset = get_reciprocal_transform(history, local_file_ident, version,
451,632✔
2589
                                                                    history_entry); // Throws
227,636✔
2590
                our_changesets.push_back(&our_changeset);
451,632✔
2591

451,632✔
2592
                begin_version = version;
2593
            }
2594

2595
            bool must_apply_all = false;
2596

9,885,084✔
2597
            if (!our_changesets.empty()) {
9,885,084✔
2598
                merge_changesets(local_file_ident, &*p, same_base_range_end - p, our_changesets.data(),
9,885,084✔
2599
                                 our_changesets.size(), logger); // Throws
9,861,114✔
2600
                // We need to apply all transformed changesets if at least one reciprocal changeset was modified
9,861,114✔
2601
                // during OT.
9,861,114✔
2602
                must_apply_all = std::any_of(our_changesets.begin(), our_changesets.end(), [](const Changeset* c) {
9,861,114✔
2603
                    return c->is_dirty();
44,292✔
2604
                });
44,292✔
2605
            }
44,292✔
2606

44,292✔
2607
            auto continue_applying = true;
44,292✔
2608
            for (; p != same_base_range_end && continue_applying; ++p) {
9,816,822✔
2609
                // It is safe to stop applying the changesets if:
9,816,822✔
2610
                //      1. There are no reciprocal changesets
9,816,822✔
2611
                //      2. No reciprocal changeset was modified
4,930,328✔
2612
                continue_applying = changeset_applier(p) || must_apply_all;
9,861,114✔
2613
            }
9,861,114✔
2614
            if (!continue_applying) {
9,861,114✔
2615
                break;
9,861,114✔
2616
            }
9,861,114✔
2617

5,637,900✔
2618
            our_changesets.clear(); // deliberately not releasing memory
9,861,114✔
2619
        }
9,861,114✔
2620
    }
9,885,084✔
2621
    catch (...) {
9,885,084✔
2622
        // If an exception was thrown while merging, the transform cache will
2623
        // be polluted. This is a problem since the same cache object is reused
2624
        // by multiple invocations to transform_remote_changesets(), so we must
2625
        // clear the cache before rethrowing.
451,594✔
2626
        //
451,594✔
2627
        // Note that some valid changesets can still cause exceptions to be
451,594✔
2628
        // thrown by the merge algorithm, namely incompatible schema changes.
451,594✔
2629
        m_reciprocal_transform_cache.clear();
9,860,290✔
2630
        throw;
9,860,290✔
2631
    }
84,626✔
2632

84,626✔
2633
    // NOTE: Any exception thrown during flushing *MUST* lead to rollback of
84,626✔
2634
    // the current transaction.
84,626✔
2635
    flush_reciprocal_transform_cache(history); // Throws
84,626✔
2636

9,860,290✔
2637
    return p - parsed_changesets.begin();
451,594✔
2638
}
2639

2640

488,382✔
2641
Changeset& TransformerImpl::get_reciprocal_transform(TransformHistory& history, file_ident_type local_file_ident,
244,268✔
2642
                                                     version_type version, const HistoryEntry& history_entry)
244,268✔
2643
{
488,382✔
2644
    auto& changeset = m_reciprocal_transform_cache[version]; // Throws
488,382✔
2645
    if (changeset.empty()) {
244,268✔
2646
        bool is_compressed = false;
488,382✔
2647
        ChunkedBinaryData data = history.get_reciprocal_transform(version, is_compressed);
488,382✔
2648
        ChunkedBinaryInputStream in{data};
244,268✔
2649
        if (is_compressed) {
488,382✔
2650
            size_t total_size;
488,382✔
2651
            auto decompressed = util::compression::decompress_nonportable_input_stream(in, total_size);
488,382✔
2652
            REALM_ASSERT(decompressed);
488,382✔
2653
            sync::parse_changeset(*decompressed, changeset); // Throws
488,382✔
2654
        }
488,382✔
2655
        else {
2656
            sync::parse_changeset(in, changeset); // Throws
2657
        }
2658

2659
        changeset.version = version;
2660
        changeset.last_integrated_remote_version = history_entry.remote_version;
2661
        changeset.origin_timestamp = history_entry.origin_timestamp;
2662
        file_ident_type origin_file_ident = history_entry.origin_file_ident;
2663
        if (origin_file_ident == 0)
2664
            origin_file_ident = local_file_ident;
2665
        changeset.origin_file_ident = origin_file_ident;
2666
    }
2667
    return changeset;
2668
}
2669

2670

2671
void TransformerImpl::flush_reciprocal_transform_cache(TransformHistory& history)
2672
{
2673
    auto changesets = std::move(m_reciprocal_transform_cache);
2674
    m_reciprocal_transform_cache.clear();
2675
    ChangesetEncoder::Buffer output_buffer;
2676
    for (const auto& [version, changeset] : changesets) {
2677
        if (changeset.is_dirty()) {
2678
            encode_changeset(changeset, output_buffer); // Throws
2679
            BinaryData data{output_buffer.data(), output_buffer.size()};
2680
            history.set_reciprocal_transform(version, data); // Throws
2681
            output_buffer.clear();
2682
        }
2683
    }
2684
}
2685

2686
} // namespace _impl
2687

2688
namespace sync {
2689
std::unique_ptr<Transformer> make_transformer()
2690
{
2691
    return std::make_unique<_impl::TransformerImpl>(); // Throws
2692
}
2693

2694

2695
void parse_remote_changeset(const Transformer::RemoteChangeset& remote_changeset, Changeset& parsed_changeset)
2696
{
2697
    // origin_file_ident = 0 is currently used to indicate an entry of local
2698
    // origin.
2699
    REALM_ASSERT(remote_changeset.origin_file_ident != 0);
2700
    REALM_ASSERT(remote_changeset.remote_version != 0);
2701

2702
    ChunkedBinaryInputStream remote_in{remote_changeset.data};
2703
    parse_changeset(remote_in, parsed_changeset); // Throws
2704

2705
    parsed_changeset.version = remote_changeset.remote_version;
2706
    parsed_changeset.last_integrated_remote_version = remote_changeset.last_integrated_local_version;
2707
    parsed_changeset.origin_timestamp = remote_changeset.origin_timestamp;
2708
    parsed_changeset.origin_file_ident = remote_changeset.origin_file_ident;
2709
    parsed_changeset.original_changeset_size = remote_changeset.original_changeset_size;
2710
}
2711

2712
} // namespace sync
2713
} // 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