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

realm / realm-core / 1669

13 Sep 2023 06:44PM UTC coverage: 91.193% (-0.07%) from 91.258%
1669

push

Evergreen

GitHub
Update History Command tool to work with realms with file format version 23 (#6970)

95936 of 175880 branches covered (0.0%)

233596 of 256155 relevant lines covered (91.19%)

6735051.02 hits per line

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

75.52
/src/realm/sync/instructions.hpp
1

2
#ifndef REALM_IMPL_INSTRUCTIONS_HPP
3
#define REALM_IMPL_INSTRUCTIONS_HPP
4

5
#include <iosfwd> // string conversion, debug prints
6
#include <memory> // shared_ptr
7
#include <type_traits>
8
#include <unordered_map>
9
#include <vector>
10

11
#include <external/mpark/variant.hpp>
12
#include <realm/binary_data.hpp>
13
#include <realm/data_type.hpp>
14
#include <realm/string_data.hpp>
15
#include <realm/sync/object_id.hpp>
16
#include <realm/table_ref.hpp>
17
#include <realm/timestamp.hpp>
18
#include <realm/util/input_stream.hpp>
19
#include <realm/util/overload.hpp>
20

21
namespace realm {
22

23
namespace sync {
24

25
#define REALM_FOR_EACH_INSTRUCTION_TYPE(X)                                                                           \
26
    X(AddTable)                                                                                                      \
52,158,076✔
27
    X(EraseTable)                                                                                                    \
52,158,076✔
28
    X(AddColumn)                                                                                                     \
50,598,914✔
29
    X(EraseColumn)                                                                                                   \
49,481,826✔
30
    X(CreateObject)                                                                                                  \
46,261,904✔
31
    X(EraseObject)                                                                                                   \
46,261,892✔
32
    X(Update)                                                                                                        \
26,200,538✔
33
    X(AddInteger)                                                                                                    \
12,998,182✔
34
    X(ArrayInsert)                                                                                                   \
7,231,266✔
35
    X(ArrayMove)                                                                                                     \
6,467,912✔
36
    X(ArrayErase)                                                                                                    \
1,112,664✔
37
    X(Clear)                                                                                                         \
1,112,212✔
38
    X(SetInsert)                                                                                                     \
14,330✔
39
    X(SetErase)
52,158,076✔
40

41
struct StringBufferRange {
42
    uint32_t offset, size;
43

44
    friend bool operator==(const StringBufferRange& lhs, const StringBufferRange& rhs) noexcept
45
    {
447,164✔
46
        return lhs.offset == rhs.offset && lhs.size == rhs.size;
447,164✔
47
    }
447,164✔
48
};
49

50
struct InternString {
51
    static const InternString npos;
52
    explicit constexpr InternString(uint32_t v = uint32_t(-1)) noexcept
53
        : value(v)
54
    {
35,987,254✔
55
    }
35,987,254✔
56

57
    uint32_t value;
58

59
    constexpr bool operator==(const InternString& other) const noexcept
60
    {
9,494,322✔
61
        return value == other.value;
9,494,322✔
62
    }
9,494,322✔
63
    constexpr bool operator!=(const InternString& other) const noexcept
64
    {
3,244,878✔
65
        return value != other.value;
3,244,878✔
66
    }
3,244,878✔
67
    constexpr bool operator<(const InternString& other) const noexcept
68
    {
×
69
        return value < other.value;
×
70
    }
×
71

72
    explicit operator bool() const noexcept
73
    {
216✔
74
        return (value != npos.value);
216✔
75
    }
216✔
76
};
77

78
struct Instruction;
79

80
namespace instr {
81

82
using PrimaryKey = mpark::variant<mpark::monostate, int64_t, GlobalKey, InternString, ObjectId, UUID>;
83

84
struct Path {
85
    using Element = mpark::variant<InternString, uint32_t>;
86

87
    // FIXME: Use a "small_vector" type for this -- most paths are very short.
88
    // Alternatively, we could use some kind of interning with copy-on-write,
89
    // but that seems complicated.
90
    std::vector<Element> m_path;
91

92
    size_t size() const noexcept
93
    {
3,207,056✔
94
        return m_path.size();
3,207,056✔
95
    }
3,207,056✔
96

97
    // If this path is referring to an element of an array (the last path
98
    // element is an integer index), return true.
99
    bool is_array_index() const noexcept
100
    {
4,625,718✔
101
        return !m_path.empty() && mpark::holds_alternative<uint32_t>(m_path.back());
4,625,718✔
102
    }
4,625,718✔
103

104
    uint32_t& index() noexcept
105
    {
165,988✔
106
        REALM_ASSERT(is_array_index());
165,988✔
107
        return mpark::get<uint32_t>(m_path.back());
165,988✔
108
    }
165,988✔
109

110
    uint32_t index() const noexcept
111
    {
×
112
        REALM_ASSERT(is_array_index());
×
113
        return mpark::get<uint32_t>(m_path.back());
×
114
    }
×
115

116
    Element& back() noexcept
117
    {
1,448✔
118
        REALM_ASSERT(!m_path.empty());
1,448✔
119
        return m_path.back();
1,448✔
120
    }
1,448✔
121

122
    const Element& back() const noexcept
123
    {
24✔
124
        REALM_ASSERT(!m_path.empty());
24✔
125
        return m_path.back();
24✔
126
    }
24✔
127

128
    Element& operator[](size_t idx) noexcept
129
    {
2,616✔
130
        REALM_ASSERT(idx < m_path.size());
2,616✔
131
        return m_path[idx];
2,616✔
132
    }
2,616✔
133

134
    const Element& operator[](size_t idx) const noexcept
135
    {
5,768✔
136
        REALM_ASSERT(idx < m_path.size());
5,768✔
137
        return m_path[idx];
5,768✔
138
    }
5,768✔
139

140
    void push_back(Element element)
141
    {
173,198✔
142
        m_path.push_back(element);
173,198✔
143
    }
173,198✔
144

145
    friend bool operator==(const Path& lhs, const Path& rhs) noexcept
146
    {
1,393,888✔
147
        return lhs.m_path == rhs.m_path;
1,393,888✔
148
    }
1,393,888✔
149

150
    using const_iterator = typename std::vector<Element>::const_iterator;
151
    const_iterator begin() const noexcept
152
    {
681,814✔
153
        return m_path.begin();
681,814✔
154
    }
681,814✔
155
    const_iterator end() const noexcept
156
    {
676,678✔
157
        return m_path.end();
676,678✔
158
    }
676,678✔
159
};
160

161
struct Payload {
162
    /// Create a new object in-place (embedded object).
163
    struct ObjectValue {
164
    };
165
    /// Create an empty dictionary in-place (does not clear an existing dictionary).
166
    struct Dictionary {
167
    };
168
    /// Sentinel value for an erased dictionary element.
169
    struct Erased {
170
    };
171

172
    /// Payload data types, corresponding loosely to the `DataType` enum in
173
    /// Core, but with some special values:
174
    ///
175
    /// - Null (0) indicates a NULL value of any type.
176
    /// - GlobalKey (-1) indicates an internally generated object ID.
177
    /// - ObjectValue (-2) indicates the creation of an embedded object.
178
    /// - Dictionary (-3) indicates the creation of a dictionary.
179
    /// - Erased (-4) indicates that a dictionary element should be erased.
180
    /// - Undefined (-5) indicates the
181
    ///
182
    /// Furthermore, link values for both Link and LinkList columns are
183
    /// represented by a single Link type.
184
    ///
185
    /// Note: For Mixed columns (including typed links), no separate value is required, because the
186
    /// instruction set encodes the type of each value in the instruction.
187
    enum class Type : int8_t {
188
        // Special value indicating that a dictionary element should be erased.
189
        Erased = -4,
190

191
        // Special value indicating that a dictionary should be created at the position.
192
        Dictionary = -3,
193

194
        // Special value indicating that an embedded object should be created at
195
        // the position.
196
        ObjectValue = -2,
197
        GlobalKey = -1,
198
        Null = 0,
199
        Int = 1,
200
        Bool = 2,
201
        String = 3,
202
        Binary = 4,
203
        Timestamp = 5,
204
        Float = 6,
205
        Double = 7,
206
        Decimal = 8,
207
        Link = 9,
208
        ObjectId = 10,
209
        UUID = 11,
210
    };
211

212
    struct Link {
213
        InternString target_table;
214
        PrimaryKey target;
215

216
        friend bool operator==(const Link& lhs, const Link& rhs) noexcept
217
        {
884✔
218
            return lhs.target_table == rhs.target_table && lhs.target == rhs.target;
884✔
219
        }
884✔
220
    };
221

222
    union Data {
223
        GlobalKey key;
224
        int64_t integer;
225
        bool boolean;
226
        StringBufferRange str;
227
        StringBufferRange binary;
228
        Timestamp timestamp;
229
        float fnum;
230
        double dnum;
231
        Decimal128 decimal;
232
        ObjectId object_id;
233
        UUID uuid;
234
        Link link;
235
        ObjLink typed_link;
236

237
        Data() {}
7,054,892✔
238
    };
239

240
    Data data;
241
    Type type;
242

243
    Payload()
244
        : Payload(realm::util::none)
245
    {
6,498,246✔
246
    }
6,498,246✔
247
    explicit Payload(bool value) noexcept
248
        : type(Type::Bool)
249
    {
2,118✔
250
        data.boolean = value;
2,118✔
251
    }
2,118✔
252
    explicit Payload(int64_t value) noexcept
253
        : type(Type::Int)
254
    {
296,794✔
255
        data.integer = value;
296,794✔
256
    }
296,794✔
257
    explicit Payload(float value) noexcept
258
        : type(Type::Float)
259
    {
2,240✔
260
        data.fnum = value;
2,240✔
261
    }
2,240✔
262
    explicit Payload(double value) noexcept
263
        : type(Type::Double)
264
    {
3,112✔
265
        data.dnum = value;
3,112✔
266
    }
3,112✔
267
    explicit Payload(Link value) noexcept
268
        : type(Type::Link)
269
    {
13,640✔
270
        data.link = value;
13,640✔
271
    }
13,640✔
272
    explicit Payload(StringBufferRange value, bool is_binary = false) noexcept
273
        : type(is_binary ? Type::Binary : Type::String)
274
    {
197,016✔
275
        if (is_binary) {
197,016✔
276
            data.binary = value;
7,984✔
277
        }
7,984✔
278
        else {
189,032✔
279
            data.str = value;
189,032✔
280
        }
189,032✔
281
    }
197,016✔
282
    explicit Payload(realm::util::None) noexcept
283
        : type(Type::Null)
284
    {
6,498,058✔
285
    }
6,498,058✔
286

287
    // Note: Intentionally implicit.
288
    Payload(const ObjectValue&) noexcept
289
        : type(Type::ObjectValue)
290
    {
11,706✔
291
    }
11,706✔
292

293
    // Note: Intentionally implicit.
294
    Payload(const Erased&) noexcept
295
        : type(Type::Erased)
296
    {
1,848✔
297
    }
1,848✔
298

299
    explicit Payload(Timestamp value) noexcept
300
        : type(value.is_null() ? Type::Null : Type::Timestamp)
301
    {
9,884✔
302
        if (value.is_null()) {
9,884✔
303
            type = Type::Null;
×
304
        }
×
305
        else {
9,884✔
306
            type = Type::Timestamp;
9,884✔
307
            data.timestamp = value;
9,884✔
308
        }
9,884✔
309
    }
9,884✔
310

311
    explicit Payload(ObjectId value) noexcept
312
        : type(Type::ObjectId)
313
    {
13,352✔
314
        data.object_id = value;
13,352✔
315
    }
13,352✔
316

317
    explicit Payload(Decimal128 value) noexcept
318
    {
2,144✔
319
        if (value.is_null()) {
2,144✔
320
            type = Type::Null;
×
321
        }
×
322
        else {
2,144✔
323
            type = Type::Decimal;
2,144✔
324
            data.decimal = value;
2,144✔
325
        }
2,144✔
326
    }
2,144✔
327

328
    explicit Payload(UUID value) noexcept
329
        : type(Type::UUID)
330
    {
3,328✔
331
        data.uuid = value;
3,328✔
332
    }
3,328✔
333

334
    Payload(const Payload&) noexcept = default;
335
    Payload& operator=(const Payload&) noexcept = default;
336

337
    bool is_null() const noexcept
338
    {
440✔
339
        return type == Type::Null;
440✔
340
    }
440✔
341

342
    friend bool operator==(const Payload& lhs, const Payload& rhs) noexcept
343
    {
913,146✔
344
        if (lhs.type == rhs.type) {
913,146✔
345
            switch (lhs.type) {
913,146✔
346
                case Type::Erased:
✔
347
                    return true;
×
348
                case Type::Dictionary:
✔
349
                    return true;
×
350
                case Type::ObjectValue:
344✔
351
                    return true;
344✔
352
                case Type::GlobalKey:
✔
353
                    return lhs.data.key == rhs.data.key;
×
354
                case Type::Null:
14,080✔
355
                    return true;
14,080✔
356
                case Type::Int:
450,460✔
357
                    return lhs.data.integer == rhs.data.integer;
450,460✔
358
                case Type::Bool:
32✔
359
                    return lhs.data.boolean == rhs.data.boolean;
32✔
360
                case Type::String:
447,008✔
361
                    return lhs.data.str == rhs.data.str;
447,008✔
362
                case Type::Binary:
✔
363
                    return lhs.data.binary == rhs.data.binary;
×
364
                case Type::Timestamp:
✔
365
                    return lhs.data.timestamp == rhs.data.timestamp;
×
366
                case Type::Float:
304✔
367
                    return lhs.data.fnum == rhs.data.fnum;
304✔
368
                case Type::Double:
32✔
369
                    return lhs.data.dnum == rhs.data.dnum;
32✔
370
                case Type::Decimal:
✔
371
                    return lhs.data.decimal == rhs.data.decimal;
×
372
                case Type::Link:
884✔
373
                    return lhs.data.link == rhs.data.link;
884✔
374
                case Type::ObjectId:
✔
375
                    return lhs.data.object_id == rhs.data.object_id;
×
376
                case Type::UUID:
✔
377
                    return lhs.data.uuid == rhs.data.uuid;
×
378
            }
×
379
        }
×
380
        return false;
×
381
    }
×
382

383
    friend bool operator!=(const Payload& lhs, const Payload& rhs) noexcept
384
    {
×
385
        return !(lhs == rhs);
×
386
    }
×
387
};
388

389
/// All instructions are TableInstructions.
390
struct TableInstruction {
391
    InternString table;
392

393
protected:
394
    bool operator==(const TableInstruction& rhs) const noexcept
395
    {
4,924,598✔
396
        return table == rhs.table;
4,924,598✔
397
    }
4,924,598✔
398
};
399

400
/// All instructions except schema instructions are ObjectInstructions.
401
struct ObjectInstruction : TableInstruction {
402
    PrimaryKey object;
403

404
protected:
405
    bool operator==(const ObjectInstruction& rhs) const noexcept
406
    {
4,120,782✔
407
        return TableInstruction::operator==(rhs) && object == rhs.object;
4,120,782✔
408
    }
4,120,782✔
409
};
410

411
/// All instructions except schema instructions and CreateObject/EraseObject are PathInstructions.
412
struct PathInstruction : ObjectInstruction {
413
    InternString field;
414
    Path path;
415

416
    uint32_t& index() noexcept
417
    {
165,988✔
418
        return path.index();
165,988✔
419
    }
165,988✔
420

421
    uint32_t index() const noexcept
422
    {
×
423
        return path.index();
×
424
    }
×
425

426
protected:
427
    bool operator==(const PathInstruction& rhs) const noexcept
428
    {
1,393,894✔
429
        return ObjectInstruction::operator==(rhs) && field == rhs.field && path == rhs.path;
1,393,900✔
430
    }
1,393,894✔
431
};
432

433
struct AddTable : TableInstruction {
434
    // Note: Tables "without" a primary key have a secret primary key of type
435
    // ObjKey. The field name of such primary keys is assumed to be "_id".
436
    struct TopLevelTable {
437
        InternString pk_field;
438
        Payload::Type pk_type;
439
        bool pk_nullable;
440
        bool is_asymmetric;
441

442
        bool operator==(const TopLevelTable& rhs) const noexcept
443
        {
196,608✔
444
            return pk_field == rhs.pk_field && pk_type == rhs.pk_type && pk_nullable == rhs.pk_nullable &&
196,608✔
445
                   is_asymmetric == rhs.is_asymmetric;
196,608✔
446
        }
196,608✔
447
    };
448

449
    struct EmbeddedTable {
450
        bool operator==(const EmbeddedTable&) const noexcept
451
        {
48✔
452
            return true;
48✔
453
        }
48✔
454
    };
455

456
    mpark::variant<TopLevelTable, EmbeddedTable> type;
457

458
    bool operator==(const AddTable& rhs) const noexcept
459
    {
196,656✔
460
        return TableInstruction::operator==(rhs) && type == rhs.type;
196,656✔
461
    }
196,656✔
462
};
463

464
struct EraseTable : TableInstruction {
465
    using TableInstruction::TableInstruction;
466

467
    bool operator==(const EraseTable& rhs) const noexcept
468
    {
133,612✔
469
        return TableInstruction::operator==(rhs);
133,612✔
470
    }
133,612✔
471
};
472

473
struct AddColumn : TableInstruction {
474
    using TableInstruction::TableInstruction;
475

476
    // This is backwards compatible with previous boolean type where 0
477
    // indicated simple type and 1 indicated list.
478
    enum class CollectionType : uint8_t { Single, List, Dictionary, Set };
479

480
    InternString field;
481

482
    // `Type::Null` for Mixed columns. Mixed columns are always nullable.
483
    Payload::Type type;
484
    // `Type::Null` for other than dictionary columns
485
    Payload::Type key_type;
486

487
    bool nullable;
488

489
    // For Mixed columns, this is `none`. Mixed columns are always nullable.
490
    //
491
    // For dictionaries, this must always be `Type::String`.
492
    CollectionType collection_type;
493

494
    InternString link_target_table;
495

496
    bool operator==(const AddColumn& rhs) const noexcept
497
    {
473,542✔
498
        return TableInstruction::operator==(rhs) && field == rhs.field && type == rhs.type &&
473,542✔
499
               key_type == rhs.key_type && nullable == rhs.nullable && collection_type == rhs.collection_type &&
473,542✔
500
               link_target_table == rhs.link_target_table;
473,542✔
501
    }
473,542✔
502
};
503

504
struct EraseColumn : TableInstruction {
505
    using TableInstruction::TableInstruction;
506
    InternString field;
507

508
    bool operator==(const EraseColumn& rhs) const noexcept
509
    {
8✔
510
        return TableInstruction::operator==(rhs) && field == rhs.field;
8✔
511
    }
8✔
512
};
513

514
struct CreateObject : ObjectInstruction {
515
    using ObjectInstruction::ObjectInstruction;
516

517
    bool operator==(const CreateObject& rhs) const noexcept
518
    {
1,646,024✔
519
        return ObjectInstruction::operator==(rhs);
1,646,024✔
520
    }
1,646,024✔
521
};
522

523
struct EraseObject : ObjectInstruction {
524
    using ObjectInstruction::ObjectInstruction;
525

526
    bool operator==(const EraseObject& rhs) const noexcept
527
    {
1,080,868✔
528
        return ObjectInstruction::operator==(rhs);
1,080,868✔
529
    }
1,080,868✔
530
};
531

532
struct Update : PathInstruction {
533
    using PathInstruction::PathInstruction;
534

535
    // Note: For "ArrayUpdate", the path ends with an integer.
536
    Payload value;
537
    union {
538
        bool is_default;     // For fields
539
        uint32_t prior_size; // For "ArrayUpdate"
540
    };
541

542
    Update()
543
        : prior_size(0)
544
    {
2,134,752✔
545
    }
2,134,752✔
546

547
    bool is_array_update() const noexcept
548
    {
3,032,654✔
549
        return path.is_array_index();
3,032,654✔
550
    }
3,032,654✔
551

552
    bool operator==(const Update& rhs) const noexcept
553
    {
379,738✔
554
        return PathInstruction::operator==(rhs) && value == rhs.value &&
379,738✔
555
               (is_array_update() ? prior_size == rhs.prior_size : is_default == rhs.is_default);
377,528✔
556
    }
379,738✔
557
};
558

559
struct AddInteger : PathInstruction {
560
    using PathInstruction::PathInstruction;
561
    int64_t value;
562

563
    bool operator==(const AddInteger& rhs) const noexcept
564
    {
336,552✔
565
        return PathInstruction::operator==(rhs) && value == rhs.value;
336,552✔
566
    }
336,552✔
567
};
568

569
struct ArrayInsert : PathInstruction {
570
    // Note: The insertion index is the last path component.
571
    using PathInstruction::PathInstruction;
572
    Payload value;
573
    uint32_t prior_size;
574

575
    bool operator==(const ArrayInsert& rhs) const noexcept
576
    {
557,208✔
577
        return PathInstruction::operator==(rhs) && value == rhs.value && prior_size == rhs.prior_size;
557,208✔
578
    }
557,208✔
579
};
580

581
struct ArrayMove : PathInstruction {
582
    // Note: The move-from index is the last path component.
583
    using PathInstruction::PathInstruction;
584
    uint32_t ndx_2;
585
    uint32_t prior_size;
586

587
    bool operator==(const ArrayMove& rhs) const noexcept
588
    {
68✔
589
        return PathInstruction::operator==(rhs) && ndx_2 == rhs.ndx_2 && prior_size == rhs.prior_size;
68✔
590
    }
68✔
591
};
592

593
struct ArrayErase : PathInstruction {
594
    // Note: The erased index is the last path component.
595
    using PathInstruction::PathInstruction;
596
    uint32_t prior_size;
597

598
    bool operator==(const ArrayErase& rhs) const noexcept
599
    {
118,694✔
600
        return PathInstruction::operator==(rhs) && prior_size == rhs.prior_size;
118,694✔
601
    }
118,694✔
602
};
603

604
struct Clear : PathInstruction {
605
    using PathInstruction::PathInstruction;
606

607
    bool operator==(const Clear& rhs) const noexcept
608
    {
1,116✔
609
        return PathInstruction::operator==(rhs);
1,116✔
610
    }
1,116✔
611
};
612

613
struct SetInsert : PathInstruction {
614
    using PathInstruction::PathInstruction;
615
    Payload value;
616

617
    bool operator==(const SetInsert& rhs) const noexcept
618
    {
452✔
619
        return PathInstruction::operator==(rhs) && value == rhs.value;
452✔
620
    }
452✔
621
};
622

623
struct SetErase : PathInstruction {
624
    using PathInstruction::PathInstruction;
625
    Payload value;
626

627
    bool operator==(const SetErase& rhs) const noexcept
628
    {
68✔
629
        return PathInstruction::operator==(rhs) && value == rhs.value;
68✔
630
    }
68✔
631
};
632

633

634
} // namespace instr
635

636
struct Instruction {
637
#define REALM_DECLARE_INSTRUCTION_STRUCT(X) using X = instr::X;
638
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_STRUCT)
639
#undef REALM_DECLARE_INSTRUCTION_STRUCT
640

641
    using TableInstruction = instr::TableInstruction;
642
    using ObjectInstruction = instr::ObjectInstruction;
643
    using PathInstruction = instr::PathInstruction;
644
    using PrimaryKey = instr::PrimaryKey;
645
    using Payload = instr::Payload;
646
    using Path = instr::Path;
647
    using Vector = std::vector<Instruction>;
648

649
    // CAUTION: Any change to the enum values for the instruction types is a protocol-breaking
650
    // change!
651
    enum class Type : uint8_t {
652
        AddTable = 0,
653
        EraseTable = 1,
654
        CreateObject = 2,
655
        EraseObject = 3,
656
        Update = 4, // Note: Also covers ArrayUpdate
657
        AddInteger = 5,
658
        AddColumn = 6,
659
        EraseColumn = 7,
660
        ArrayInsert = 8,
661
        ArrayMove = 9,
662
        ArrayErase = 10,
663
        Clear = 11,
664
        SetInsert = 12,
665
        SetErase = 13,
666
    };
667

668
    template <Type t>
669
    struct GetType;
670
    template <class T>
671
    struct GetInstructionType;
672

673
    template <class T>
674
    Instruction(T instr);
675

676
    mpark::variant<Vector
677
#define REALM_INSTRUCTION_VARIANT_ALTERNATIVE(X) , X
678
                       REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_INSTRUCTION_VARIANT_ALTERNATIVE)
679
#undef REALM_INSTRUCTION_VARIANT_ALTERNATIVE
680
                   >
681
        m_instr;
682

683
    Type type() const noexcept;
684

685
    template <class F>
686
    decltype(auto) visit(F&& lambda);
687
    template <class F>
688
    decltype(auto) visit(F&& lambda) const;
689

690
    template <class T>
691
    T* get_if() noexcept;
692

693
    template <class T>
694
    const T* get_if() const noexcept
695
    {
97,825,278✔
696
        return const_cast<Instruction&>(*this).get_if<T>();
97,825,278✔
697
    }
97,825,278✔
698

699
    template <class T>
700
    T& get_as()
701
    {
6✔
702
        auto ptr = get_if<T>();
6✔
703
        REALM_ASSERT(ptr);
6✔
704
        return *ptr;
6✔
705
    }
6✔
706

707
    template <class T>
708
    const T& get_as() const
709
    {
710
        auto ptr = get_if<T>();
711
        REALM_ASSERT(ptr);
712
        return *ptr;
713
    }
714

715
    bool operator==(const Instruction& other) const noexcept;
716
    bool operator!=(const Instruction& other) const noexcept
717
    {
×
718
        return !(*this == other);
×
719
    }
×
720

721
    bool is_vector() const noexcept
722
    {
×
723
        return mpark::holds_alternative<Vector>(m_instr);
×
724
    }
×
725

726
    size_t path_length() const noexcept;
727

728
    Vector& convert_to_vector();
729
    void insert(size_t pos, Instruction instr);
730
    void erase(size_t pos);
731
    size_t size() const noexcept;
732
    bool is_empty() const noexcept;
733
    Instruction& at(size_t) noexcept;
734
    const Instruction& at(size_t) const noexcept;
735

736
private:
737
    template <class>
738
    struct Visitor;
739
};
740

741
inline const char* get_type_name(Instruction::Type type)
742
{
×
743
    switch (type) {
×
744
#define REALM_INSTRUCTION_TYPE_TO_STRING(X)                                                                          \
×
745
    case Instruction::Type::X:                                                                                       \
×
746
        return #X;
×
747
        REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_INSTRUCTION_TYPE_TO_STRING)
×
748
#undef REALM_INSTRUCTION_TYPE_TO_STRING
×
749
    }
×
750
    return "(invalid)";
×
751
}
×
752

753
inline std::ostream& operator<<(std::ostream& os, Instruction::Type type)
754
{
×
755
    return os << get_type_name(type);
×
756
}
×
757

758
inline const char* get_type_name(Instruction::Payload::Type type)
759
{
56✔
760
    using Type = Instruction::Payload::Type;
56✔
761
    switch (type) {
56✔
762
        case Type::Erased:
✔
763
            return "Erased";
×
764
        case Type::Dictionary:
✔
765
            return "Dictionary";
×
766
        case Type::ObjectValue:
✔
767
            return "ObjectValue";
×
768
        case Type::GlobalKey:
✔
769
            return "GlobalKey";
×
770
        case Type::Null:
✔
771
            return "Null";
×
772
        case Type::Int:
16✔
773
            return "Int";
16✔
774
        case Type::Bool:
✔
775
            return "Bool";
×
776
        case Type::String:
24✔
777
            return "String";
24✔
778
        case Type::Binary:
✔
779
            return "Binary";
×
780
        case Type::Timestamp:
✔
781
            return "Timestamp";
×
782
        case Type::Float:
✔
783
            return "Float";
×
784
        case Type::Double:
✔
785
            return "Double";
×
786
        case Type::Decimal:
✔
787
            return "Decimal";
×
788
        case Type::Link:
16✔
789
            return "Link";
16✔
790
        case Type::ObjectId:
✔
791
            return "ObjectId";
×
792
        case Type::UUID:
✔
793
            return "UUID";
×
794
    }
×
795
    return "(unknown)";
×
796
}
×
797

798
inline const char* get_collection_type(Instruction::AddColumn::CollectionType type)
799
{
4✔
800
    using Type = Instruction::AddColumn::CollectionType;
4✔
801
    switch (type) {
4✔
802
        case Type::Single:
✔
803
            return "Single";
×
804
        case Type::List:
4✔
805
            return "List";
4✔
806
        case Type::Dictionary:
✔
807
            return "Dictionary";
×
808
        case Type::Set:
✔
809
            return "Set";
×
810
    }
×
811
    return "(unknown)";
×
812
}
×
813

814
inline const char* get_type_name(util::Optional<Instruction::Payload::Type> type)
815
{
×
816
    if (type) {
×
817
        return get_type_name(*type);
×
818
    }
×
819
    else {
×
820
        return "Mixed";
×
821
    }
×
822
}
×
823

824
inline std::ostream& operator<<(std::ostream& os, Instruction::Payload::Type type)
825
{
×
826
    return os << get_type_name(type);
×
827
}
×
828

829
inline bool is_valid_key_type(Instruction::Payload::Type type) noexcept
830
{
320,506✔
831
    using Type = Instruction::Payload::Type;
320,506✔
832
    switch (type) {
320,506✔
833
        case Type::Int:
249,324✔
834
            [[fallthrough]];
249,324✔
835
        case Type::String:
297,186✔
836
            [[fallthrough]];
297,186✔
837
        case Type::ObjectId:
320,190✔
838
            [[fallthrough]];
320,190✔
839
        case Type::UUID:
320,482✔
840
            [[fallthrough]];
320,482✔
841
        case Type::GlobalKey:
320,506✔
842
            return true;
320,506✔
843
        case Type::Null: // Mixed is not a valid primary key
156,012✔
844
            [[fallthrough]];
×
845
        default:
✔
846
            return false;
×
847
    }
320,506✔
848
}
320,506✔
849

850
inline DataType get_data_type(Instruction::Payload::Type type) noexcept
851
{
33,732✔
852
    using Type = Instruction::Payload::Type;
33,732✔
853
    switch (type) {
33,732✔
854
        case Type::Int:
19,412✔
855
            return type_Int;
19,412✔
856
        case Type::Bool:
68✔
857
            return type_Bool;
68✔
858
        case Type::String:
12,124✔
859
            return type_String;
12,124✔
860
        case Type::Binary:
80✔
861
            return type_Binary;
80✔
862
        case Type::Timestamp:
226✔
863
            return type_Timestamp;
226✔
864
        case Type::Float:
100✔
865
            return type_Float;
100✔
866
        case Type::Double:
84✔
867
            return type_Double;
84✔
868
        case Type::Decimal:
52✔
869
            return type_Decimal;
52✔
870
        case Type::Link:
16✔
871
            return type_Link;
16✔
872
        case Type::ObjectId:
1,298✔
873
            return type_ObjectId;
1,298✔
874
        case Type::UUID:
148✔
875
            return type_UUID;
148✔
876
        case Type::Null: // Mixed is encoded as null
124✔
877
            return type_Mixed;
124✔
878
        case Type::Erased:
✔
879
            [[fallthrough]];
×
880
        case Type::Dictionary:
✔
881
            [[fallthrough]];
×
882
        case Type::ObjectValue:
✔
883
            [[fallthrough]];
×
884
        case Type::GlobalKey:
✔
885
            REALM_TERMINATE(util::format("Invalid data type: %1", int8_t(type)).c_str());
×
886
    }
33,732✔
887
    return type_Int; // Make compiler happy
16,288✔
888
}
33,732✔
889

890
// 0x3f is the largest value that fits in a single byte in the variable-length
891
// encoded integer instruction format.
892
static constexpr uint8_t InstrTypeInternString = 0x3f;
893

894
// This instruction code is only ever used internally by the Changeset class
895
// to allow insertion/removal while keeping iterators stable. Should never
896
// make it onto the wire.
897
static constexpr uint8_t InstrTypeMultiInstruction = 0xff;
898

899
struct InstructionHandler {
900
    /// Notify the handler that an InternString meta-instruction was found.
901
    virtual void set_intern_string(uint32_t index, StringBufferRange) = 0;
902

903
    /// Notify the handler of the string value. The handler guarantees that the
904
    /// returned string range is valid at least until the next invocation of
905
    /// add_string_range().
906
    ///
907
    /// Instances of `StringBufferRange` passed to operator() after invoking
908
    /// this function are assumed to refer to ranges in this buffer.
909
    virtual StringBufferRange add_string_range(StringData) = 0;
910

911
    /// Handle an instruction.
912
    virtual void operator()(const Instruction&) = 0;
913
};
914

915

916
/// Implementation:
917

918
#define REALM_DEFINE_INSTRUCTION_GET_TYPE(X)                                                                         \
919
    template <>                                                                                                      \
920
    struct Instruction::GetType<Instruction::Type::X> {                                                              \
921
        using Type = Instruction::X;                                                                                 \
922
    };                                                                                                               \
923
    template <>                                                                                                      \
924
    struct Instruction::GetInstructionType<Instruction::X> {                                                         \
925
        static const Instruction::Type value = Instruction::Type::X;                                                 \
926
    };
927
REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_GET_TYPE)
928
#undef REALM_DEFINE_INSTRUCTION_GET_TYPE
929

930
template <class T>
931
Instruction::Instruction(T instr)
932
    : m_instr(std::move(instr))
933
{
9,996,804✔
934
    static_assert(!std::is_same_v<T, Vector>);
9,996,804✔
935
}
9,996,804✔
936

937
template <class F>
938
struct Instruction::Visitor {
939
    F lambda; // reference type
940
    Visitor(F lambda)
941
        : lambda(lambda)
942
    {
943
    }
944

945
    template <class T>
946
    decltype(auto) operator()(T& instr)
947
    {
948
        return lambda(instr);
949
    }
950

951
    template <class T>
952
    decltype(auto) operator()(const T& instr)
953
    {
954
        return lambda(instr);
955
    }
956

957
    auto operator()(const Instruction::Vector&) -> decltype(lambda(std::declval<const Instruction::Update&>()))
958
    {
959
        REALM_TERMINATE("visiting instruction vector");
960
    }
961
    auto operator()(Instruction::Vector&) -> decltype(lambda(std::declval<Instruction::Update&>()))
962
    {
963
        REALM_TERMINATE("visiting instruction vector");
964
    }
965
};
966

967
template <class F>
968
inline decltype(auto) Instruction::visit(F&& lambda)
969
{
49,340,854✔
970
    // Cannot use std::visit, because it does not pass lvalue references to the visitor.
24,653,466✔
971
    if (mpark::holds_alternative<Vector>(m_instr)) {
49,340,854!
972
        REALM_TERMINATE("visiting instruction vector");
×
973
    }
×
974
#define REALM_VISIT_VARIANT(X)                                                                                       \
49,340,854✔
975
    else if (mpark::holds_alternative<Instruction::X>(m_instr))                                                      \
385,026,468✔
976
    {                                                                                                                \
168,204,008✔
977
        return lambda(mpark::get<Instruction::X>(m_instr));                                                          \
49,341,730✔
978
    }
49,341,730✔
979
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT)
385,026,468!
980
#undef REALM_VISIT_VARIANT
286,347,284✔
981
    else
286,347,284✔
982
    {
2,290,999,301✔
983
        REALM_TERMINATE("Unhandled instruction variant entry");
2,147,483,737✔
984
    }
2,147,483,737✔
985
}
49,340,854✔
986

987
template <class F>
988
inline decltype(auto) Instruction::visit(F&& lambda) const
989
{
2,817,222✔
990
    // Cannot use std::visit, because it does not pass lvalue references to the visitor.
1,391,784✔
991
    if (mpark::holds_alternative<Vector>(m_instr)) {
2,817,222!
992
        REALM_TERMINATE("visiting instruction vector");
×
993
    }
×
994
#define REALM_VISIT_VARIANT(X)                                                                                       \
2,817,222✔
995
    else if (mpark::holds_alternative<Instruction::X>(m_instr))                                                      \
19,189,400✔
996
    {                                                                                                                \
8,129,896✔
997
        return lambda(mpark::get<Instruction::X>(m_instr));                                                          \
2,817,204✔
998
    }
2,817,204✔
999
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT)
19,189,400!
1000
#undef REALM_VISIT_VARIANT
13,555,912✔
1001
    else
13,555,912✔
1002
    {
2,147,483,669✔
1003
        REALM_TERMINATE("Unhandled instruction variant entry");
2,147,483,669✔
1004
    }
2,147,483,669✔
1005
}
2,817,222✔
1006

1007
inline Instruction::Type Instruction::type() const noexcept
1008
{
1,205,094✔
1009
    return visit([](auto&& instr) {
1,205,096✔
1010
        using T = std::remove_cv_t<std::remove_reference_t<decltype(instr)>>;
1,205,096✔
1011
        return GetInstructionType<T>::value;
1,205,096✔
1012
    });
1,205,096✔
1013
}
1,205,094✔
1014

1015
inline bool Instruction::operator==(const Instruction& other) const noexcept
1016
{
4,924,566✔
1017
    return m_instr == other.m_instr;
4,924,566✔
1018
}
4,924,566✔
1019

1020
template <class T>
1021
REALM_NOINLINE T* Instruction::get_if() noexcept
1022
{
108,447,704✔
1023
    // FIXME: Is there a way to express this without giant switch statements? Note: Putting the
54,533,246✔
1024
    // base class into a union does not seem to be allowed by the standard.
54,533,246✔
1025
    if constexpr (std::is_same_v<TableInstruction, T>) {
108,447,704✔
1026
        // This should compile to nothing but a comparison of the type.
54,533,246✔
1027
        return visit([](auto& instr) -> TableInstruction* {
54,533,246✔
1028
            return &instr;
×
1029
        });
×
1030
    }
×
1031
    else if constexpr (std::is_same_v<ObjectInstruction, T>) {
108,447,704✔
1032
        // This should compile to nothing but a comparison of the type.
38,879,324✔
1033
        return visit(util::overload{
77,321,778✔
1034
            [](AddTable&) -> ObjectInstruction* {
39,157,890✔
1035
                return nullptr;
539,688✔
1036
            },
539,688✔
1037
            [](EraseTable&) -> ObjectInstruction* {
39,058,870✔
1038
                return nullptr;
362,944✔
1039
            },
362,944✔
1040
            [](AddColumn&) -> ObjectInstruction* {
39,485,316✔
1041
                return nullptr;
1,205,276✔
1042
            },
1,205,276✔
1043
            [](EraseColumn&) -> ObjectInstruction* {
38,879,324✔
1044
                return nullptr;
×
1045
            },
×
1046
            [](auto& instr) -> ObjectInstruction* {
53,287,938✔
1047
                return &instr;
29,018,806✔
1048
            },
29,018,806✔
1049
        });
31,126,002✔
1050
    }
31,126,002✔
1051
    else if constexpr (std::is_same_v<PathInstruction, T>) {
77,321,778✔
1052
        // This should compile to nothing but a comparison of the type.
33,361,666✔
1053
        return visit(util::overload{
66,278,070✔
1054
            [](AddTable&) -> PathInstruction* {
33,517,840✔
1055
                return nullptr;
302,484✔
1056
            },
302,484✔
1057
            [](EraseTable&) -> PathInstruction* {
33,472,770✔
1058
                return nullptr;
224,250✔
1059
            },
224,250✔
1060
            [](AddColumn&) -> PathInstruction* {
33,703,810✔
1061
                return nullptr;
678,348✔
1062
            },
678,348✔
1063
            [](EraseColumn&) -> PathInstruction* {
33,361,666✔
1064
                return nullptr;
×
1065
            },
×
1066
            [](CreateObject&) -> PathInstruction* {
35,444,408✔
1067
                return nullptr;
4,125,362✔
1068
            },
4,125,362✔
1069
            [](EraseObject&) -> PathInstruction* {
34,748,544✔
1070
                return nullptr;
2,753,846✔
1071
            },
2,753,846✔
1072
            [](auto& instr) -> PathInstruction* {
34,808,786✔
1073
                return &instr;
2,959,610✔
1074
            },
2,959,610✔
1075
        });
11,043,700✔
1076
    }
11,043,700✔
1077
    else {
66,278,070✔
1078
        return mpark::get_if<T>(&m_instr);
66,278,070✔
1079
    }
66,278,070✔
1080
}
108,447,704✔
1081

1082
inline size_t Instruction::size() const noexcept
1083
{
135,455,242✔
1084
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
135,455,242✔
1085
        return vec->size();
2,394,704✔
1086
    }
2,394,704✔
1087
    return 1;
133,060,538✔
1088
}
133,060,538✔
1089

1090
inline bool Instruction::is_empty() const noexcept
1091
{
23,830✔
1092
    return size() == 0;
23,830✔
1093
}
23,830✔
1094

1095
inline Instruction& Instruction::at(size_t idx) noexcept
1096
{
100,607,220✔
1097
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
100,607,220✔
1098
        REALM_ASSERT(idx < vec->size());
×
1099
        return (*vec)[idx];
×
1100
    }
×
1101
    REALM_ASSERT(idx == 0);
100,607,220✔
1102
    return *this;
100,607,220✔
1103
}
100,607,220✔
1104

1105
inline const Instruction& Instruction::at(size_t idx) const noexcept
1106
{
1,611,788✔
1107
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
1,611,788✔
1108
        REALM_ASSERT(idx < vec->size());
×
1109
        return (*vec)[idx];
×
1110
    }
×
1111
    REALM_ASSERT(idx == 0);
1,611,788✔
1112
    return *this;
1,611,788✔
1113
}
1,611,788✔
1114

1115
inline size_t Instruction::path_length() const noexcept
1116
{
11,043,696✔
1117
    // Find the path length of the instruction. This affects how OT decides
5,517,658✔
1118
    // which instructions are potentially nesting.
5,517,658✔
1119
    //
5,517,658✔
1120
    // AddTable/EraseTable:   Length 1
5,517,658✔
1121
    // AddColumn/EraseColumn: Length 2 (table, field)
5,517,658✔
1122
    // Object instructions:   Length 2 (table, object)
5,517,658✔
1123
    // Path instructions:     Length 3 + m_path.size (table, object, field, path...)
5,517,658✔
1124
    if (auto path_instr = get_if<Instruction::PathInstruction>()) {
11,043,696✔
1125
        return 3 + path_instr->path.size();
2,959,544✔
1126
    }
2,959,544✔
1127
    if (get_if<Instruction::ObjectInstruction>()) {
8,084,152✔
1128
        return 2;
6,879,214✔
1129
    }
6,879,214✔
1130
    switch (type()) {
1,204,938✔
1131
        case Instruction::Type::AddColumn:
678,348✔
1132
            [[fallthrough]];
678,348✔
1133
        case Instruction::Type::EraseColumn: {
678,348✔
1134
            return 2;
678,348✔
1135
        }
678,348✔
1136
        case Instruction::Type::AddTable:
492,378✔
1137
            [[fallthrough]];
302,484✔
1138
        case Instruction::Type::EraseTable: {
526,734✔
1139
            return 1;
526,734✔
1140
        }
302,484✔
1141
        default:
146,310✔
1142
            REALM_TERMINATE("Unhandled instruction type in Instruction::path_len()");
×
1143
    }
1,204,938✔
1144
}
1,204,938✔
1145

1146
inline Instruction::Vector& Instruction::convert_to_vector()
1147
{
133,958✔
1148
    if (auto v = mpark::get_if<Vector>(&m_instr)) {
133,958✔
1149
        return *v;
×
1150
    }
×
1151
    else {
133,958✔
1152
        Vector vec;
133,958✔
1153
        vec.emplace_back(std::move(*this));
133,958✔
1154
        m_instr = std::move(vec);
133,958✔
1155
        return mpark::get<Vector>(m_instr);
133,958✔
1156
    }
133,958✔
1157
}
133,958✔
1158

1159
inline void Instruction::insert(size_t idx, Instruction instr)
1160
{
×
1161
    auto& vec = convert_to_vector();
×
1162
    REALM_ASSERT(idx <= vec.size());
×
1163
    vec.emplace(vec.begin() + idx, std::move(instr));
×
1164
}
×
1165

1166
inline void Instruction::erase(size_t idx)
1167
{
133,956✔
1168
    auto& vec = convert_to_vector();
133,956✔
1169
    REALM_ASSERT(idx < vec.size());
133,956✔
1170
    vec.erase(vec.begin() + idx);
133,956✔
1171
}
133,956✔
1172

1173
} // namespace sync
1174
} // namespace realm
1175

1176
#endif // REALM_IMPL_INSTRUCTIONS_HPP
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc