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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

75.13
/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,358,416✔
27
    X(EraseTable)                                                                                                    \
52,358,416✔
28
    X(AddColumn)                                                                                                     \
50,820,996✔
29
    X(EraseColumn)                                                                                                   \
49,717,598✔
30
    X(CreateObject)                                                                                                  \
46,379,858✔
31
    X(EraseObject)                                                                                                   \
46,379,846✔
32
    X(Update)                                                                                                        \
26,091,096✔
33
    X(AddInteger)                                                                                                    \
12,984,466✔
34
    X(ArrayInsert)                                                                                                   \
7,272,596✔
35
    X(ArrayMove)                                                                                                     \
6,524,454✔
36
    X(ArrayErase)                                                                                                    \
1,103,088✔
37
    X(Clear)                                                                                                         \
1,102,612✔
38
    X(SetInsert)                                                                                                     \
16,972✔
39
    X(SetErase)
52,358,416✔
40

41
struct StringBufferRange {
42
    uint32_t offset, size;
43

44
    friend bool operator==(const StringBufferRange& lhs, const StringBufferRange& rhs) noexcept
45
    {
451,360✔
46
        return lhs.offset == rhs.offset && lhs.size == rhs.size;
451,360✔
47
    }
451,360✔
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
    {
36,142,052✔
55
    }
36,142,052✔
56

57
    uint32_t value;
58

59
    constexpr bool operator==(const InternString& other) const noexcept
60
    {
9,595,964✔
61
        return value == other.value;
9,595,964✔
62
    }
9,595,964✔
63
    constexpr bool operator!=(const InternString& other) const noexcept
64
    {
3,238,592✔
65
        return value != other.value;
3,238,592✔
66
    }
3,238,592✔
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
    size_t size() const noexcept
88
    {
4,268,538✔
89
        return m_path.size();
4,268,538✔
90
    }
4,268,538✔
91

92
    void reserve(size_t sz)
93
    {
2,271,160✔
94
        m_path.reserve(sz);
2,271,160✔
95
    }
2,271,160✔
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,622,450✔
101
        return !m_path.empty() && mpark::holds_alternative<uint32_t>(m_path.back());
4,622,450✔
102
    }
4,622,450✔
103

104
    uint32_t& index() noexcept
105
    {
148,504✔
106
        REALM_ASSERT(is_array_index());
148,504✔
107
        return mpark::get<uint32_t>(m_path.back());
148,504✔
108
    }
148,504✔
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,200✔
136
        REALM_ASSERT(idx < m_path.size());
5,200✔
137
        return m_path[idx];
5,200✔
138
    }
5,200✔
139

140
    void push_back(Element element)
141
    {
2,081,620✔
142
        m_path.push_back(element);
2,081,620✔
143
    }
2,081,620✔
144

145
    void clear()
NEW
146
    {
×
NEW
147
        m_path.clear();
×
NEW
148
    }
×
149

150
    friend bool operator==(const Path& lhs, const Path& rhs) noexcept
151
    {
1,377,956✔
152
        return lhs.m_path == rhs.m_path;
1,377,956✔
153
    }
1,377,956✔
154

155
    using const_iterator = typename std::vector<Element>::const_iterator;
156
    const_iterator begin() const noexcept
157
    {
1,775,300✔
158
        return m_path.begin();
1,775,300✔
159
    }
1,775,300✔
160
    const_iterator end() const noexcept
161
    {
1,770,074✔
162
        return m_path.end();
1,770,074✔
163
    }
1,770,074✔
164

165
private:
166
    // FIXME: Use a "small_vector" type for this -- most paths are very short.
167
    // Alternatively, we could use some kind of interning with copy-on-write,
168
    // but that seems complicated.
169
    std::vector<Element> m_path;
170
};
171

172
struct Payload {
173
    /// Create a new object in-place (embedded object).
174
    struct ObjectValue {};
175
    /// Create an empty list in-place (does not clear an existing list).
176
    struct List {};
177
    /// Create an empty dictionary in-place (does not clear an existing dictionary).
178
    struct Dictionary {};
179
    /// Create an empty set in-place (does not clear an existing dictionary).
180
    struct Set {};
181
    /// Sentinel value for an erased dictionary element.
182
    struct Erased {};
183

184
    /// Payload data types, corresponding loosely to the `DataType` enum in
185
    /// Core, but with some special values:
186
    ///
187
    /// - Null (0) indicates a NULL value of any type.
188
    /// - GlobalKey (-1) indicates an internally generated object ID.
189
    /// - ObjectValue (-2) indicates the creation of an embedded object.
190
    /// - Dictionary (-3) indicates the creation of a dictionary.
191
    /// - Erased (-4) indicates that a dictionary element should be erased.
192
    /// - List (-5) indicates the creation of a list
193
    ///
194
    /// Furthermore, link values for both Link and LinkList columns are
195
    /// represented by a single Link type.
196
    ///
197
    /// Note: For Mixed columns (including typed links), no separate value is required, because the
198
    /// instruction set encodes the type of each value in the instruction.
199
    enum class Type : int8_t {
200
        // Special value indicating that a set should be created at the position.
201
        Set = -6,
202

203
        // Special value indicating that a list should be created at the position.
204
        List = -5,
205

206
        // Special value indicating that a dictionary element should be erased.
207
        Erased = -4,
208

209
        // Special value indicating that a dictionary should be created at the position.
210
        Dictionary = -3,
211

212
        // Special value indicating that an embedded object should be created at
213
        // the position.
214
        ObjectValue = -2,
215
        GlobalKey = -1,
216
        Null = 0,
217
        Int = 1,
218
        Bool = 2,
219
        String = 3,
220
        Binary = 4,
221
        Timestamp = 5,
222
        Float = 6,
223
        Double = 7,
224
        Decimal = 8,
225
        Link = 9,
226
        ObjectId = 10,
227
        UUID = 11,
228
    };
229

230
    struct Link {
231
        InternString target_table;
232
        PrimaryKey target;
233

234
        friend bool operator==(const Link& lhs, const Link& rhs) noexcept
235
        {
884✔
236
            return lhs.target_table == rhs.target_table && lhs.target == rhs.target;
884✔
237
        }
884✔
238
    };
239

240
    union Data {
241
        GlobalKey key;
242
        int64_t integer;
243
        bool boolean;
244
        StringBufferRange str;
245
        StringBufferRange binary;
246
        Timestamp timestamp;
247
        float fnum;
248
        double dnum;
249
        Decimal128 decimal;
250
        ObjectId object_id;
251
        UUID uuid;
252
        Link link;
253
        ObjLink typed_link;
254

255
        Data() {}
7,095,244✔
256
    };
257

258
    Data data;
259
    Type type;
260

261
    Payload()
262
        : Payload(realm::util::none)
263
    {
6,539,170✔
264
    }
6,539,170✔
265
    explicit Payload(bool value) noexcept
266
        : type(Type::Bool)
267
    {
2,118✔
268
        data.boolean = value;
2,118✔
269
    }
2,118✔
270
    explicit Payload(int64_t value) noexcept
271
        : type(Type::Int)
272
    {
296,988✔
273
        data.integer = value;
296,988✔
274
    }
296,988✔
275
    explicit Payload(float value) noexcept
276
        : type(Type::Float)
277
    {
2,240✔
278
        data.fnum = value;
2,240✔
279
    }
2,240✔
280
    explicit Payload(double value) noexcept
281
        : type(Type::Double)
282
    {
3,112✔
283
        data.dnum = value;
3,112✔
284
    }
3,112✔
285
    explicit Payload(Link value) noexcept
286
        : type(Type::Link)
287
    {
13,652✔
288
        data.link = value;
13,652✔
289
    }
13,652✔
290
    explicit Payload(StringBufferRange value, bool is_binary = false) noexcept
291
        : type(is_binary ? Type::Binary : Type::String)
292
    {
197,100✔
293
        if (is_binary) {
197,100✔
294
            data.binary = value;
7,984✔
295
        }
7,984✔
296
        else {
189,116✔
297
            data.str = value;
189,116✔
298
        }
189,116✔
299
    }
197,100✔
300
    explicit Payload(realm::util::None) noexcept
301
        : type(Type::Null)
302
    {
6,539,026✔
303
    }
6,539,026✔
304

305
    // Note: Intentionally implicit.
306
    Payload(const ObjectValue&) noexcept
307
        : type(Type::ObjectValue)
308
    {
11,722✔
309
    }
11,722✔
310

311
    // Note: Intentionally implicit.
312
    Payload(const Erased&) noexcept
313
        : type(Type::Erased)
314
    {
1,384✔
315
    }
1,384✔
316
    Payload(const Dictionary&) noexcept
317
        : type(Type::Dictionary)
NEW
318
    {
×
NEW
319
    }
×
320
    Payload(const List&) noexcept
321
        : type(Type::List)
NEW
322
    {
×
NEW
323
    }
×
324
    Payload(const Set&) noexcept
325
        : type(Type::Set)
NEW
326
    {
×
NEW
327
    }
×
328

329
    explicit Payload(Timestamp value) noexcept
330
        : type(value.is_null() ? Type::Null : Type::Timestamp)
331
    {
9,884✔
332
        if (value.is_null()) {
9,884✔
333
            type = Type::Null;
×
334
        }
×
335
        else {
9,884✔
336
            type = Type::Timestamp;
9,884✔
337
            data.timestamp = value;
9,884✔
338
        }
9,884✔
339
    }
9,884✔
340

341
    explicit Payload(ObjectId value) noexcept
342
        : type(Type::ObjectId)
343
    {
13,352✔
344
        data.object_id = value;
13,352✔
345
    }
13,352✔
346

347
    explicit Payload(Decimal128 value) noexcept
348
    {
2,144✔
349
        if (value.is_null()) {
2,144✔
350
            type = Type::Null;
×
351
        }
×
352
        else {
2,144✔
353
            type = Type::Decimal;
2,144✔
354
            data.decimal = value;
2,144✔
355
        }
2,144✔
356
    }
2,144✔
357

358
    explicit Payload(UUID value) noexcept
359
        : type(Type::UUID)
360
    {
3,328✔
361
        data.uuid = value;
3,328✔
362
    }
3,328✔
363

364
    Payload(const Payload&) noexcept = default;
365
    Payload& operator=(const Payload&) noexcept = default;
366

367
    bool is_null() const noexcept
368
    {
464✔
369
        return type == Type::Null;
464✔
370
    }
464✔
371

372
    friend bool operator==(const Payload& lhs, const Payload& rhs) noexcept
373
    {
906,834✔
374
        if (lhs.type == rhs.type) {
906,834✔
375
            switch (lhs.type) {
906,834✔
376
                case Type::Null:
16,920✔
377
                case Type::Erased:
16,920✔
378
                case Type::List:
16,920✔
379
                case Type::Set:
16,920✔
380
                case Type::Dictionary:
16,920✔
381
                case Type::ObjectValue:
17,092✔
382
                    return true;
17,092✔
383
                case Type::GlobalKey:
9,086✔
384
                    return lhs.data.key == rhs.data.key;
×
385
                case Type::Int:
437,290✔
386
                    return lhs.data.integer == rhs.data.integer;
437,290✔
387
                case Type::Bool:
9,102✔
388
                    return lhs.data.boolean == rhs.data.boolean;
32✔
389
                case Type::String:
451,204✔
390
                    return lhs.data.str == rhs.data.str;
451,204✔
391
                case Type::Binary:
9,086✔
392
                    return lhs.data.binary == rhs.data.binary;
×
393
                case Type::Timestamp:
9,086✔
394
                    return lhs.data.timestamp == rhs.data.timestamp;
×
395
                case Type::Float:
9,238✔
396
                    return lhs.data.fnum == rhs.data.fnum;
304✔
397
                case Type::Double:
9,102✔
398
                    return lhs.data.dnum == rhs.data.dnum;
32✔
399
                case Type::Decimal:
9,086✔
400
                    return lhs.data.decimal == rhs.data.decimal;
×
401
                case Type::Link:
9,528✔
402
                    return lhs.data.link == rhs.data.link;
884✔
403
                case Type::ObjectId:
9,086✔
404
                    return lhs.data.object_id == rhs.data.object_id;
×
405
                case Type::UUID:
9,086✔
406
                    return lhs.data.uuid == rhs.data.uuid;
×
UNCOV
407
            }
×
UNCOV
408
        }
×
UNCOV
409
        return false;
×
UNCOV
410
    }
×
411

412
    friend bool operator!=(const Payload& lhs, const Payload& rhs) noexcept
413
    {
×
414
        return !(lhs == rhs);
×
415
    }
×
416
};
417

418
/// All instructions are TableInstructions.
419
struct TableInstruction {
420
    InternString table;
421

422
protected:
423
    bool operator==(const TableInstruction& rhs) const noexcept
424
    {
4,987,332✔
425
        return table == rhs.table;
4,987,332✔
426
    }
4,987,332✔
427
};
428

429
/// All instructions except schema instructions are ObjectInstructions.
430
struct ObjectInstruction : TableInstruction {
431
    PrimaryKey object;
432

433
protected:
434
    bool operator==(const ObjectInstruction& rhs) const noexcept
435
    {
4,168,120✔
436
        return TableInstruction::operator==(rhs) && object == rhs.object;
4,168,126✔
437
    }
4,168,120✔
438
};
439

440
/// All instructions except schema instructions and CreateObject/EraseObject are PathInstructions.
441
struct PathInstruction : ObjectInstruction {
442
    InternString field;
443
    Path path;
444

445
    uint32_t& index() noexcept
446
    {
148,504✔
447
        return path.index();
148,504✔
448
    }
148,504✔
449

450
    uint32_t index() const noexcept
451
    {
×
452
        return path.index();
×
453
    }
×
454

455
protected:
456
    bool operator==(const PathInstruction& rhs) const noexcept
457
    {
1,377,972✔
458
        return ObjectInstruction::operator==(rhs) && field == rhs.field && path == rhs.path;
1,377,972✔
459
    }
1,377,972✔
460
};
461

462
struct AddTable : TableInstruction {
463
    // Note: Tables "without" a primary key have a secret primary key of type
464
    // ObjKey. The field name of such primary keys is assumed to be "_id".
465
    struct TopLevelTable {
466
        InternString pk_field;
467
        Payload::Type pk_type;
468
        bool pk_nullable;
469
        bool is_asymmetric;
470

471
        bool operator==(const TopLevelTable& rhs) const noexcept
472
        {
196,300✔
473
            return pk_field == rhs.pk_field && pk_type == rhs.pk_type && pk_nullable == rhs.pk_nullable &&
196,300✔
474
                   is_asymmetric == rhs.is_asymmetric;
196,300✔
475
        }
196,300✔
476
    };
477

478
    struct EmbeddedTable {
479
        bool operator==(const EmbeddedTable&) const noexcept
480
        {
48✔
481
            return true;
48✔
482
        }
48✔
483
    };
484

485
    mpark::variant<TopLevelTable, EmbeddedTable> type;
486

487
    bool operator==(const AddTable& rhs) const noexcept
488
    {
196,348✔
489
        return TableInstruction::operator==(rhs) && type == rhs.type;
196,348✔
490
    }
196,348✔
491
};
492

493
struct EraseTable : TableInstruction {
494
    using TableInstruction::TableInstruction;
495

496
    bool operator==(const EraseTable& rhs) const noexcept
497
    {
131,268✔
498
        return TableInstruction::operator==(rhs);
131,268✔
499
    }
131,268✔
500
};
501

502
struct AddColumn : TableInstruction {
503
    using TableInstruction::TableInstruction;
504

505
    // This is backwards compatible with previous boolean type where 0
506
    // indicated simple type and 1 indicated list.
507
    enum class CollectionType : uint8_t { Single, List, Dictionary, Set };
508

509
    InternString field;
510

511
    // `Type::Null` for Mixed columns. Mixed columns are always nullable.
512
    Payload::Type type;
513
    // `Type::Null` for other than dictionary columns
514
    Payload::Type key_type;
515

516
    bool nullable;
517

518
    // For Mixed columns, this is `none`. Mixed columns are always nullable.
519
    //
520
    // For dictionaries, this must always be `Type::String`.
521
    CollectionType collection_type;
522

523
    InternString link_target_table;
524

525
    bool operator==(const AddColumn& rhs) const noexcept
526
    {
491,596✔
527
        return TableInstruction::operator==(rhs) && field == rhs.field && type == rhs.type &&
491,596✔
528
               key_type == rhs.key_type && nullable == rhs.nullable && collection_type == rhs.collection_type &&
491,596✔
529
               link_target_table == rhs.link_target_table;
491,596✔
530
    }
491,596✔
531
};
532

533
struct EraseColumn : TableInstruction {
534
    using TableInstruction::TableInstruction;
535
    InternString field;
536

537
    bool operator==(const EraseColumn& rhs) const noexcept
538
    {
8✔
539
        return TableInstruction::operator==(rhs) && field == rhs.field;
8✔
540
    }
8✔
541
};
542

543
struct CreateObject : ObjectInstruction {
544
    using ObjectInstruction::ObjectInstruction;
545

546
    bool operator==(const CreateObject& rhs) const noexcept
547
    {
1,693,436✔
548
        return ObjectInstruction::operator==(rhs);
1,693,436✔
549
    }
1,693,436✔
550
};
551

552
struct EraseObject : ObjectInstruction {
553
    using ObjectInstruction::ObjectInstruction;
554

555
    bool operator==(const EraseObject& rhs) const noexcept
556
    {
1,096,776✔
557
        return ObjectInstruction::operator==(rhs);
1,096,776✔
558
    }
1,096,776✔
559
};
560

561
struct Update : PathInstruction {
562
    using PathInstruction::PathInstruction;
563

564
    // Note: For "ArrayUpdate", the path ends with an integer.
565
    Payload value;
566
    union {
567
        bool is_default;     // For fields
568
        uint32_t prior_size; // For "ArrayUpdate"
569
    };
570

571
    Update()
572
        : prior_size(0)
573
    {
2,132,376✔
574
    }
2,132,376✔
575

576
    bool is_array_update() const noexcept
577
    {
3,024,322✔
578
        return path.is_array_index();
3,024,322✔
579
    }
3,024,322✔
580

581
    bool operator==(const Update& rhs) const noexcept
582
    {
382,666✔
583
        return PathInstruction::operator==(rhs) && value == rhs.value &&
382,666✔
584
               (is_array_update() ? prior_size == rhs.prior_size : is_default == rhs.is_default);
380,950✔
585
    }
382,666✔
586
};
587

588
struct AddInteger : PathInstruction {
589
    using PathInstruction::PathInstruction;
590
    int64_t value;
591

592
    bool operator==(const AddInteger& rhs) const noexcept
593
    {
334,976✔
594
        return PathInstruction::operator==(rhs) && value == rhs.value;
334,976✔
595
    }
334,976✔
596
};
597

598
struct ArrayInsert : PathInstruction {
599
    // Note: The insertion index is the last path component.
600
    using PathInstruction::PathInstruction;
601
    Payload value;
602
    uint32_t prior_size;
603

604
    bool operator==(const ArrayInsert& rhs) const noexcept
605
    {
545,142✔
606
        return PathInstruction::operator==(rhs) && value == rhs.value && prior_size == rhs.prior_size;
545,142✔
607
    }
545,142✔
608
};
609

610
struct ArrayMove : PathInstruction {
611
    // Note: The move-from index is the last path component.
612
    using PathInstruction::PathInstruction;
613
    uint32_t ndx_2;
614
    uint32_t prior_size;
615

616
    bool operator==(const ArrayMove& rhs) const noexcept
617
    {
68✔
618
        return PathInstruction::operator==(rhs) && ndx_2 == rhs.ndx_2 && prior_size == rhs.prior_size;
68✔
619
    }
68✔
620
};
621

622
struct ArrayErase : PathInstruction {
623
    // Note: The erased index is the last path component.
624
    using PathInstruction::PathInstruction;
625
    uint32_t prior_size;
626

627
    bool operator==(const ArrayErase& rhs) const noexcept
628
    {
113,170✔
629
        return PathInstruction::operator==(rhs) && prior_size == rhs.prior_size;
113,170✔
630
    }
113,170✔
631
};
632

633
struct Clear : PathInstruction {
634
    using PathInstruction::PathInstruction;
635

636
    bool operator==(const Clear& rhs) const noexcept
637
    {
1,444✔
638
        return PathInstruction::operator==(rhs);
1,444✔
639
    }
1,444✔
640
};
641

642
struct SetInsert : PathInstruction {
643
    using PathInstruction::PathInstruction;
644
    Payload value;
645

646
    bool operator==(const SetInsert& rhs) const noexcept
647
    {
456✔
648
        return PathInstruction::operator==(rhs) && value == rhs.value;
456✔
649
    }
456✔
650
};
651

652
struct SetErase : PathInstruction {
653
    using PathInstruction::PathInstruction;
654
    Payload value;
655

656
    bool operator==(const SetErase& rhs) const noexcept
657
    {
68✔
658
        return PathInstruction::operator==(rhs) && value == rhs.value;
68✔
659
    }
68✔
660
};
661

662

663
} // namespace instr
664

665
struct Instruction {
666
#define REALM_DECLARE_INSTRUCTION_STRUCT(X) using X = instr::X;
667
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DECLARE_INSTRUCTION_STRUCT)
668
#undef REALM_DECLARE_INSTRUCTION_STRUCT
669

670
    using TableInstruction = instr::TableInstruction;
671
    using ObjectInstruction = instr::ObjectInstruction;
672
    using PathInstruction = instr::PathInstruction;
673
    using PrimaryKey = instr::PrimaryKey;
674
    using Payload = instr::Payload;
675
    using Path = instr::Path;
676
    using Vector = std::vector<Instruction>;
677

678
    // CAUTION: Any change to the enum values for the instruction types is a protocol-breaking
679
    // change!
680
    enum class Type : uint8_t {
681
        AddTable = 0,
682
        EraseTable = 1,
683
        CreateObject = 2,
684
        EraseObject = 3,
685
        Update = 4, // Note: Also covers ArrayUpdate
686
        AddInteger = 5,
687
        AddColumn = 6,
688
        EraseColumn = 7,
689
        ArrayInsert = 8,
690
        ArrayMove = 9,
691
        ArrayErase = 10,
692
        Clear = 11,
693
        SetInsert = 12,
694
        SetErase = 13,
695
    };
696

697
    template <Type t>
698
    struct GetType;
699
    template <class T>
700
    struct GetInstructionType;
701

702
    template <class T>
703
    Instruction(T instr);
704

705
    mpark::variant<Vector
706
#define REALM_INSTRUCTION_VARIANT_ALTERNATIVE(X) , X
707
                       REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_INSTRUCTION_VARIANT_ALTERNATIVE)
708
#undef REALM_INSTRUCTION_VARIANT_ALTERNATIVE
709
                   >
710
        m_instr;
711

712
    Type type() const noexcept;
713

714
    template <class F>
715
    decltype(auto) visit(F&& lambda);
716
    template <class F>
717
    decltype(auto) visit(F&& lambda) const;
718

719
    template <class T>
720
    T* get_if() noexcept;
721

722
    template <class T>
723
    const T* get_if() const noexcept
724
    {
98,049,992✔
725
        return const_cast<Instruction&>(*this).get_if<T>();
98,049,992✔
726
    }
98,049,992✔
727

728
    template <class T>
729
    T& get_as()
730
    {
6✔
731
        auto ptr = get_if<T>();
6✔
732
        REALM_ASSERT(ptr);
6✔
733
        return *ptr;
6✔
734
    }
6✔
735

736
    template <class T>
737
    const T& get_as() const
738
    {
739
        auto ptr = get_if<T>();
740
        REALM_ASSERT(ptr);
741
        return *ptr;
742
    }
743

744
    bool operator==(const Instruction& other) const noexcept;
745
    bool operator!=(const Instruction& other) const noexcept
746
    {
×
747
        return !(*this == other);
×
748
    }
×
749

750
    bool is_vector() const noexcept
751
    {
×
752
        return mpark::holds_alternative<Vector>(m_instr);
×
753
    }
×
754

755
    size_t path_length() const noexcept;
756

757
    Vector& convert_to_vector();
758
    void insert(size_t pos, Instruction instr);
759
    void erase(size_t pos);
760
    size_t size() const noexcept;
761
    bool is_empty() const noexcept;
762
    Instruction& at(size_t) noexcept;
763
    const Instruction& at(size_t) const noexcept;
764

765
private:
766
    template <class>
767
    struct Visitor;
768
};
769

770
inline const char* get_type_name(Instruction::Type type)
771
{
×
772
    switch (type) {
×
773
#define REALM_INSTRUCTION_TYPE_TO_STRING(X)                                                                          \
×
774
    case Instruction::Type::X:                                                                                       \
×
775
        return #X;
×
776
        REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_INSTRUCTION_TYPE_TO_STRING)
×
777
#undef REALM_INSTRUCTION_TYPE_TO_STRING
×
778
    }
×
779
    return "(invalid)";
×
780
}
×
781

782
inline std::ostream& operator<<(std::ostream& os, Instruction::Type type)
783
{
×
784
    return os << get_type_name(type);
×
785
}
×
786

787
inline const char* get_type_name(Instruction::Payload::Type type)
788
{
56✔
789
    using Type = Instruction::Payload::Type;
56✔
790
    switch (type) {
56✔
791
        case Type::Erased:
✔
792
            return "Erased";
×
NEW
793
        case Type::Set:
✔
NEW
794
            return "Set";
×
NEW
795
        case Type::List:
✔
NEW
796
            return "List";
×
797
        case Type::Dictionary:
✔
798
            return "Dictionary";
×
799
        case Type::ObjectValue:
✔
800
            return "ObjectValue";
×
801
        case Type::GlobalKey:
✔
802
            return "GlobalKey";
×
803
        case Type::Null:
✔
804
            return "Null";
×
805
        case Type::Int:
16✔
806
            return "Int";
16✔
807
        case Type::Bool:
✔
808
            return "Bool";
×
809
        case Type::String:
24✔
810
            return "String";
24✔
811
        case Type::Binary:
✔
812
            return "Binary";
×
813
        case Type::Timestamp:
✔
814
            return "Timestamp";
×
815
        case Type::Float:
✔
816
            return "Float";
×
817
        case Type::Double:
✔
818
            return "Double";
×
819
        case Type::Decimal:
✔
820
            return "Decimal";
×
821
        case Type::Link:
16✔
822
            return "Link";
16✔
823
        case Type::ObjectId:
✔
824
            return "ObjectId";
×
825
        case Type::UUID:
✔
826
            return "UUID";
×
827
    }
×
828
    return "(unknown)";
×
829
}
×
830

831
inline const char* get_collection_type(Instruction::AddColumn::CollectionType type)
832
{
4✔
833
    using Type = Instruction::AddColumn::CollectionType;
4✔
834
    switch (type) {
4✔
835
        case Type::Single:
✔
836
            return "Single";
×
837
        case Type::List:
4✔
838
            return "List";
4✔
839
        case Type::Dictionary:
✔
840
            return "Dictionary";
×
841
        case Type::Set:
✔
842
            return "Set";
×
843
    }
×
844
    return "(unknown)";
×
845
}
×
846

847
inline const char* get_type_name(util::Optional<Instruction::Payload::Type> type)
848
{
×
849
    if (type) {
×
850
        return get_type_name(*type);
×
851
    }
×
852
    else {
×
853
        return "Mixed";
×
854
    }
×
855
}
×
856

857
inline std::ostream& operator<<(std::ostream& os, Instruction::Payload::Type type)
858
{
×
859
    return os << get_type_name(type);
×
860
}
×
861

862
inline bool is_valid_key_type(Instruction::Payload::Type type) noexcept
863
{
314,312✔
864
    using Type = Instruction::Payload::Type;
314,312✔
865
    switch (type) {
314,312✔
866
        case Type::Int:
241,938✔
867
            [[fallthrough]];
241,938✔
868
        case Type::String:
290,756✔
869
            [[fallthrough]];
290,756✔
870
        case Type::ObjectId:
313,996✔
871
            [[fallthrough]];
313,996✔
872
        case Type::UUID:
314,288✔
873
            [[fallthrough]];
314,288✔
874
        case Type::GlobalKey:
314,312✔
875
            return true;
314,312✔
876
        case Type::Null: // Mixed is not a valid primary key
153,768✔
877
            [[fallthrough]];
×
878
        default:
✔
879
            return false;
×
880
    }
314,312✔
881
}
314,312✔
882

883
inline DataType get_data_type(Instruction::Payload::Type type) noexcept
884
{
33,788✔
885
    using Type = Instruction::Payload::Type;
33,788✔
886
    switch (type) {
33,788✔
887
        case Type::Int:
19,612✔
888
            return type_Int;
19,612✔
889
        case Type::Bool:
68✔
890
            return type_Bool;
68✔
891
        case Type::String:
11,976✔
892
            return type_String;
11,976✔
893
        case Type::Binary:
80✔
894
            return type_Binary;
80✔
895
        case Type::Timestamp:
226✔
896
            return type_Timestamp;
226✔
897
        case Type::Float:
100✔
898
            return type_Float;
100✔
899
        case Type::Double:
84✔
900
            return type_Double;
84✔
901
        case Type::Decimal:
52✔
902
            return type_Decimal;
52✔
903
        case Type::Link:
16✔
904
            return type_Link;
16✔
905
        case Type::ObjectId:
1,298✔
906
            return type_ObjectId;
1,298✔
907
        case Type::UUID:
148✔
908
            return type_UUID;
148✔
909
        case Type::Null: // Mixed is encoded as null
128✔
910
            return type_Mixed;
128✔
911
        case Type::Erased:
✔
912
            [[fallthrough]];
×
913
        case Type::Dictionary:
✔
914
            [[fallthrough]];
×
NEW
915
        case Type::List:
✔
NEW
916
            [[fallthrough]];
×
NEW
917
        case Type::Set:
✔
NEW
918
            [[fallthrough]];
×
919
        case Type::ObjectValue:
✔
920
            [[fallthrough]];
×
921
        case Type::GlobalKey:
✔
922
            REALM_TERMINATE(util::format("Invalid data type: %1", int8_t(type)).c_str());
×
923
    }
33,788✔
924
    return type_Int; // Make compiler happy
16,180✔
925
}
33,788✔
926

927
// 0x3f is the largest value that fits in a single byte in the variable-length
928
// encoded integer instruction format.
929
static constexpr uint8_t InstrTypeInternString = 0x3f;
930

931
// This instruction code is only ever used internally by the Changeset class
932
// to allow insertion/removal while keeping iterators stable. Should never
933
// make it onto the wire.
934
static constexpr uint8_t InstrTypeMultiInstruction = 0xff;
935

936
struct InstructionHandler {
937
    /// Notify the handler that an InternString meta-instruction was found.
938
    virtual void set_intern_string(uint32_t index, StringBufferRange) = 0;
939

940
    /// Notify the handler of the string value. The handler guarantees that the
941
    /// returned string range is valid at least until the next invocation of
942
    /// add_string_range().
943
    ///
944
    /// Instances of `StringBufferRange` passed to operator() after invoking
945
    /// this function are assumed to refer to ranges in this buffer.
946
    virtual StringBufferRange add_string_range(StringData) = 0;
947

948
    /// Handle an instruction.
949
    virtual void operator()(const Instruction&) = 0;
950
};
951

952

953
/// Implementation:
954

955
#define REALM_DEFINE_INSTRUCTION_GET_TYPE(X)                                                                         \
956
    template <>                                                                                                      \
957
    struct Instruction::GetType<Instruction::Type::X> {                                                              \
958
        using Type = Instruction::X;                                                                                 \
959
    };                                                                                                               \
960
    template <>                                                                                                      \
961
    struct Instruction::GetInstructionType<Instruction::X> {                                                         \
962
        static const Instruction::Type value = Instruction::Type::X;                                                 \
963
    };
964
REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_DEFINE_INSTRUCTION_GET_TYPE)
965
#undef REALM_DEFINE_INSTRUCTION_GET_TYPE
966

967
template <class T>
968
Instruction::Instruction(T instr)
969
    : m_instr(std::move(instr))
970
{
10,030,410✔
971
    static_assert(!std::is_same_v<T, Vector>);
10,030,410✔
972
}
10,030,410✔
973

974
template <class F>
975
struct Instruction::Visitor {
976
    F lambda; // reference type
977
    Visitor(F lambda)
978
        : lambda(lambda)
979
    {
980
    }
981

982
    template <class T>
983
    decltype(auto) operator()(T& instr)
984
    {
985
        return lambda(instr);
986
    }
987

988
    template <class T>
989
    decltype(auto) operator()(const T& instr)
990
    {
991
        return lambda(instr);
992
    }
993

994
    auto operator()(const Instruction::Vector&) -> decltype(lambda(std::declval<const Instruction::Update&>()))
995
    {
996
        REALM_TERMINATE("visiting instruction vector");
997
    }
998
    auto operator()(Instruction::Vector&) -> decltype(lambda(std::declval<Instruction::Update&>()))
999
    {
1000
        REALM_TERMINATE("visiting instruction vector");
1001
    }
1002
};
1003

1004
template <class F>
1005
inline decltype(auto) Instruction::visit(F&& lambda)
1006
{
49,529,686✔
1007
    // Cannot use std::visit, because it does not pass lvalue references to the visitor.
24,657,886✔
1008
    if (mpark::holds_alternative<Vector>(m_instr)) {
49,529,686!
1009
        REALM_TERMINATE("visiting instruction vector");
×
1010
    }
×
1011
#define REALM_VISIT_VARIANT(X)                                                                                       \
49,529,686✔
1012
    else if (mpark::holds_alternative<Instruction::X>(m_instr))                                                      \
386,228,040✔
1013
    {                                                                                                                \
168,699,488✔
1014
        return lambda(mpark::get<Instruction::X>(m_instr));                                                          \
49,531,258✔
1015
    }
49,531,258✔
1016
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT)
386,228,040!
1017
#undef REALM_VISIT_VARIANT
287,169,814✔
1018
    else
287,169,814✔
1019
    {
2,291,309,539✔
1020
        REALM_TERMINATE("Unhandled instruction variant entry");
2,147,483,871✔
1021
    }
2,147,483,871✔
1022
}
49,529,686✔
1023

1024
template <class F>
1025
inline decltype(auto) Instruction::visit(F&& lambda) const
1026
{
2,828,730✔
1027
    // Cannot use std::visit, because it does not pass lvalue references to the visitor.
1,383,870✔
1028
    if (mpark::holds_alternative<Vector>(m_instr)) {
2,828,730!
1029
        REALM_TERMINATE("visiting instruction vector");
×
1030
    }
×
1031
#define REALM_VISIT_VARIANT(X)                                                                                       \
2,828,730✔
1032
    else if (mpark::holds_alternative<Instruction::X>(m_instr))                                                      \
19,240,790✔
1033
    {                                                                                                                \
8,132,252✔
1034
        return lambda(mpark::get<Instruction::X>(m_instr));                                                          \
2,828,560✔
1035
    }
2,828,560✔
1036
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT)
19,240,790!
1037
#undef REALM_VISIT_VARIANT
13,584,568✔
1038
    else
13,584,568✔
1039
    {
6,687,560✔
1040
        REALM_TERMINATE("Unhandled instruction variant entry");
170✔
1041
    }
170✔
1042
}
2,828,730✔
1043

1044
inline Instruction::Type Instruction::type() const noexcept
1045
{
1,222,346✔
1046
    return visit([](auto&& instr) {
1,222,348✔
1047
        using T = std::remove_cv_t<std::remove_reference_t<decltype(instr)>>;
1,222,348✔
1048
        return GetInstructionType<T>::value;
1,222,348✔
1049
    });
1,222,348✔
1050
}
1,222,346✔
1051

1052
inline bool Instruction::operator==(const Instruction& other) const noexcept
1053
{
4,987,218✔
1054
    return m_instr == other.m_instr;
4,987,218✔
1055
}
4,987,218✔
1056

1057
template <class T>
1058
REALM_NOINLINE T* Instruction::get_if() noexcept
1059
{
108,801,864✔
1060
    // FIXME: Is there a way to express this without giant switch statements? Note: Putting the
54,395,594✔
1061
    // base class into a union does not seem to be allowed by the standard.
54,395,594✔
1062
    if constexpr (std::is_same_v<TableInstruction, T>) {
108,801,864✔
1063
        // This should compile to nothing but a comparison of the type.
54,395,594✔
1064
        return visit([](auto& instr) -> TableInstruction* {
54,395,594✔
1065
            return &instr;
×
1066
        });
×
1067
    }
×
1068
    else if constexpr (std::is_same_v<ObjectInstruction, T>) {
108,801,864✔
1069
        // This should compile to nothing but a comparison of the type.
38,811,120✔
1070
        return visit(util::overload{
77,608,832✔
1071
            [](AddTable&) -> ObjectInstruction* {
39,082,074✔
1072
                return nullptr;
528,390✔
1073
            },
528,390✔
1074
            [](EraseTable&) -> ObjectInstruction* {
38,998,204✔
1075
                return nullptr;
358,442✔
1076
            },
358,442✔
1077
            [](AddColumn&) -> ObjectInstruction* {
39,454,052✔
1078
                return nullptr;
1,248,054✔
1079
            },
1,248,054✔
1080
            [](EraseColumn&) -> ObjectInstruction* {
38,811,120✔
1081
                return nullptr;
×
1082
            },
×
1083
            [](auto& instr) -> ObjectInstruction* {
53,320,028✔
1084
                return &instr;
29,059,480✔
1085
            },
29,059,480✔
1086
        });
31,193,112✔
1087
    }
31,193,112✔
1088
    else if constexpr (std::is_same_v<PathInstruction, T>) {
77,608,832✔
1089
        // This should compile to nothing but a comparison of the type.
33,290,772✔
1090
        return visit(util::overload{
66,526,136✔
1091
            [](AddTable&) -> PathInstruction* {
33,443,368✔
1092
                return nullptr;
297,292✔
1093
            },
297,292✔
1094
            [](EraseTable&) -> PathInstruction* {
33,406,296✔
1095
                return nullptr;
221,552✔
1096
            },
221,552✔
1097
            [](AddColumn&) -> PathInstruction* {
33,654,012✔
1098
                return nullptr;
703,486✔
1099
            },
703,486✔
1100
            [](EraseColumn&) -> PathInstruction* {
33,290,772✔
1101
                return nullptr;
×
1102
            },
×
1103
            [](CreateObject&) -> PathInstruction* {
35,408,536✔
1104
                return nullptr;
4,175,772✔
1105
            },
4,175,772✔
1106
            [](EraseObject&) -> PathInstruction* {
34,672,812✔
1107
                return nullptr;
2,738,502✔
1108
            },
2,738,502✔
1109
            [](auto& instr) -> PathInstruction* {
34,722,286✔
1110
                return &instr;
2,946,528✔
1111
            },
2,946,528✔
1112
        });
11,082,692✔
1113
    }
11,082,692✔
1114
    else {
66,526,136✔
1115
        return mpark::get_if<T>(&m_instr);
66,526,136✔
1116
    }
66,526,136✔
1117
}
108,801,864✔
1118

1119
inline size_t Instruction::size() const noexcept
1120
{
135,910,670✔
1121
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
135,910,670✔
1122
        return vec->size();
2,409,824✔
1123
    }
2,409,824✔
1124
    return 1;
133,500,846✔
1125
}
133,500,846✔
1126

1127
inline bool Instruction::is_empty() const noexcept
1128
{
25,008✔
1129
    return size() == 0;
25,008✔
1130
}
25,008✔
1131

1132
inline Instruction& Instruction::at(size_t idx) noexcept
1133
{
101,074,306✔
1134
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
101,074,306✔
1135
        REALM_ASSERT(idx < vec->size());
×
1136
        return (*vec)[idx];
×
1137
    }
×
1138
    REALM_ASSERT(idx == 0);
101,074,306✔
1139
    return *this;
101,074,306✔
1140
}
101,074,306✔
1141

1142
inline const Instruction& Instruction::at(size_t idx) const noexcept
1143
{
1,605,244✔
1144
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
1,605,244✔
1145
        REALM_ASSERT(idx < vec->size());
×
1146
        return (*vec)[idx];
×
1147
    }
×
1148
    REALM_ASSERT(idx == 0);
1,605,244✔
1149
    return *this;
1,605,244✔
1150
}
1,605,244✔
1151

1152
inline size_t Instruction::path_length() const noexcept
1153
{
11,082,670✔
1154
    // Find the path length of the instruction. This affects how OT decides
5,520,366✔
1155
    // which instructions are potentially nesting.
5,520,366✔
1156
    //
5,520,366✔
1157
    // AddTable/EraseTable:   Length 1
5,520,366✔
1158
    // AddColumn/EraseColumn: Length 2 (table, field)
5,520,366✔
1159
    // Object instructions:   Length 2 (table, object)
5,520,366✔
1160
    // Path instructions:     Length 3 + m_path.size (table, object, field, path...)
5,520,366✔
1161
    if (auto path_instr = get_if<Instruction::PathInstruction>()) {
11,082,670✔
1162
        return 3 + path_instr->path.size();
2,946,432✔
1163
    }
2,946,432✔
1164
    if (get_if<Instruction::ObjectInstruction>()) {
8,136,238✔
1165
        return 2;
6,914,264✔
1166
    }
6,914,264✔
1167
    switch (type()) {
1,221,974✔
1168
        case Instruction::Type::AddColumn:
703,488✔
1169
            [[fallthrough]];
703,488✔
1170
        case Instruction::Type::EraseColumn: {
703,488✔
1171
            return 2;
703,488✔
1172
        }
703,488✔
1173
        case Instruction::Type::AddTable:
492,846✔
1174
            [[fallthrough]];
297,296✔
1175
        case Instruction::Type::EraseTable: {
518,848✔
1176
            return 1;
518,848✔
1177
        }
297,296✔
1178
        default:
144,698✔
1179
            REALM_TERMINATE("Unhandled instruction type in Instruction::path_len()");
×
1180
    }
1,221,974✔
1181
}
1,221,974✔
1182

1183
inline Instruction::Vector& Instruction::convert_to_vector()
1184
{
134,588✔
1185
    if (auto v = mpark::get_if<Vector>(&m_instr)) {
134,588✔
1186
        return *v;
×
1187
    }
×
1188
    else {
134,588✔
1189
        Vector vec;
134,588✔
1190
        vec.emplace_back(std::move(*this));
134,588✔
1191
        m_instr = std::move(vec);
134,588✔
1192
        return mpark::get<Vector>(m_instr);
134,588✔
1193
    }
134,588✔
1194
}
134,588✔
1195

1196
inline void Instruction::insert(size_t idx, Instruction instr)
1197
{
×
1198
    auto& vec = convert_to_vector();
×
1199
    REALM_ASSERT(idx <= vec.size());
×
1200
    vec.emplace(vec.begin() + idx, std::move(instr));
×
1201
}
×
1202

1203
inline void Instruction::erase(size_t idx)
1204
{
134,588✔
1205
    auto& vec = convert_to_vector();
134,588✔
1206
    REALM_ASSERT(idx < vec.size());
134,588✔
1207
    vec.erase(vec.begin() + idx);
134,588✔
1208
}
134,588✔
1209

1210
} // namespace sync
1211
} // namespace realm
1212

1213
#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