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

realm / realm-core / 2496

16 Jul 2024 09:59PM UTC coverage: 90.985% (-0.02%) from 91.001%
2496

push

Evergreen

web-flow
RCORE-2190 Sync client can crash if a session is resumed before UNBIND message finishes sending (#7874)

* Pulled 'make_client_reset_handler()' changes from role change feature branch
* Added test to reproduce and fix for the 'recognize_sync_version()' assert failure
* Moved new test under REALM_ENABLE_AUTH_TESTS define
* Reverted request_download_completion_notification() and updates from review

102398 of 180616 branches covered (56.69%)

113 of 119 new or added lines in 4 files covered. (94.96%)

81 existing lines in 15 files now uncovered.

215488 of 236838 relevant lines covered (90.99%)

5829632.87 hits per line

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

76.03
/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)                                                                                                      \
5,483,096✔
27
    X(EraseTable)                                                                                                    \
5,483,096✔
28
    X(AddColumn)                                                                                                     \
5,373,538✔
29
    X(EraseColumn)                                                                                                   \
5,333,050✔
30
    X(CreateObject)                                                                                                  \
5,087,848✔
31
    X(EraseObject)                                                                                                   \
5,087,832✔
32
    X(Update)                                                                                                        \
3,887,858✔
33
    X(AddInteger)                                                                                                    \
3,421,188✔
34
    X(ArrayInsert)                                                                                                   \
1,000,060✔
35
    X(ArrayMove)                                                                                                     \
500,320✔
36
    X(ArrayErase)                                                                                                    \
62,954✔
37
    X(Clear)                                                                                                         \
62,442✔
38
    X(SetInsert)                                                                                                     \
12,308✔
39
    X(SetErase)
5,483,096✔
40

41
struct StringBufferRange {
42
    uint32_t offset, size;
43

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

50
struct InternString {
51
    static const InternString npos;
52
    explicit constexpr InternString(uint32_t v = uint32_t(-1)) noexcept
53
        : value(v)
5,013,080✔
54
    {
10,347,770✔
55
    }
10,347,770✔
56

57
    uint32_t value;
58

59
    constexpr bool operator==(const InternString& other) const noexcept
60
    {
2,123,460✔
61
        return value == other.value;
2,123,460✔
62
    }
2,123,460✔
63
    constexpr bool operator!=(const InternString& other) const noexcept
64
    {
2,922,148✔
65
        return value != other.value;
2,922,148✔
66
    }
2,922,148✔
67
    constexpr bool operator<(const InternString& other) const noexcept
68
    {
×
69
        return value < other.value;
×
70
    }
×
71

72
    explicit operator bool() const noexcept
73
    {
220✔
74
        return (value != npos.value);
220✔
75
    }
220✔
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
    {
1,629,440✔
89
        return m_path.size();
1,629,440✔
90
    }
1,629,440✔
91

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

104
    uint32_t& index() noexcept
105
    {
10,636✔
106
        REALM_ASSERT(is_array_index());
10,636✔
107
        return mpark::get<uint32_t>(m_path.back());
10,636✔
108
    }
10,636✔
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,744✔
118
        REALM_ASSERT(!m_path.empty());
1,744✔
119
        return m_path.back();
1,744✔
120
    }
1,744✔
121

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

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

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

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

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

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

155
    using const_iterator = typename std::vector<Element>::const_iterator;
156
    const_iterator begin() const noexcept
157
    {
1,732,238✔
158
        return m_path.begin();
1,732,238✔
159
    }
1,732,238✔
160
    const_iterator end() const noexcept
161
    {
1,726,900✔
162
        return m_path.end();
1,726,900✔
163
    }
1,726,900✔
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 set).
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() {}
3,779,596✔
256
    };
257

258
    Data data;
259
    Type type;
260

261
    Payload()
262
        : Payload(realm::util::none)
1,530,954✔
263
    {
3,195,140✔
264
    }
3,195,140✔
265
    explicit Payload(bool value) noexcept
266
        : type(Type::Bool)
1,054✔
267
    {
2,118✔
268
        data.boolean = value;
2,118✔
269
    }
2,118✔
270
    explicit Payload(int64_t value) noexcept
271
        : type(Type::Int)
148,710✔
272
    {
298,286✔
273
        data.integer = value;
298,286✔
274
    }
298,286✔
275
    explicit Payload(float value) noexcept
276
        : type(Type::Float)
1,116✔
277
    {
2,240✔
278
        data.fnum = value;
2,240✔
279
    }
2,240✔
280
    explicit Payload(double value) noexcept
281
        : type(Type::Double)
1,552✔
282
    {
3,112✔
283
        data.dnum = value;
3,112✔
284
    }
3,112✔
285
    explicit Payload(Link value) noexcept
286
        : type(Type::Link)
8,972✔
287
    {
18,356✔
288
        data.link = value;
18,356✔
289
    }
18,356✔
290
    explicit Payload(StringBufferRange value, bool is_binary = false) noexcept
291
        : type(is_binary ? Type::Binary : Type::String)
101,106✔
292
    {
215,458✔
293
        if (is_binary) {
215,458✔
294
            data.binary = value;
8,024✔
295
        }
8,024✔
296
        else {
207,434✔
297
            data.str = value;
207,434✔
298
        }
207,434✔
299
    }
215,458✔
300
    explicit Payload(realm::util::None) noexcept
301
        : type(Type::Null)
1,530,620✔
302
    {
3,194,892✔
303
    }
3,194,892✔
304

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

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

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

341
    explicit Payload(ObjectId value) noexcept
342
        : type(Type::ObjectId)
6,804✔
343
    {
13,616✔
344
        data.object_id = value;
13,616✔
345
    }
13,616✔
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)
1,660✔
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
    {
308✔
369
        return type == Type::Null;
308✔
370
    }
308✔
371

372
    friend bool operator==(const Payload& lhs, const Payload& rhs) noexcept
373
    {
61,948✔
374
        if (lhs.type == rhs.type) {
61,948✔
375
            switch (lhs.type) {
61,948✔
376
                case Type::Null:
706✔
377
                case Type::Erased:
770✔
378
                case Type::List:
1,578✔
379
                case Type::Set:
1,578✔
380
                case Type::Dictionary:
2,038✔
381
                case Type::ObjectValue:
2,382✔
382
                    return true;
2,382✔
383
                case Type::GlobalKey:
✔
384
                    return lhs.data.key == rhs.data.key;
×
385
                case Type::Int:
31,032✔
386
                    return lhs.data.integer == rhs.data.integer;
31,032✔
387
                case Type::Bool:
32✔
388
                    return lhs.data.boolean == rhs.data.boolean;
32✔
389
                case Type::String:
27,264✔
390
                    return lhs.data.str == rhs.data.str;
27,264✔
391
                case Type::Binary:
✔
392
                    return lhs.data.binary == rhs.data.binary;
×
393
                case Type::Timestamp:
✔
394
                    return lhs.data.timestamp == rhs.data.timestamp;
×
395
                case Type::Float:
304✔
396
                    return lhs.data.fnum == rhs.data.fnum;
304✔
397
                case Type::Double:
48✔
398
                    return lhs.data.dnum == rhs.data.dnum;
48✔
399
                case Type::Decimal:
✔
400
                    return lhs.data.decimal == rhs.data.decimal;
×
401
                case Type::Link:
884✔
402
                    return lhs.data.link == rhs.data.link;
884✔
403
                case Type::ObjectId:
✔
404
                    return lhs.data.object_id == rhs.data.object_id;
×
405
                case Type::UUID:
✔
406
                    return lhs.data.uuid == rhs.data.uuid;
×
407
            }
61,948✔
408
        }
61,948✔
UNCOV
409
        return false;
×
410
    }
61,948✔
411

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

418
// This is backwards compatible with previous boolean type where 0
419
// indicated simple type and 1 indicated list.
420
enum class CollectionType : uint8_t { Single, List, Dictionary, Set };
421

422
/// All instructions are TableInstructions.
423
struct TableInstruction {
424
    InternString table;
425

426
protected:
427
    bool operator==(const TableInstruction& rhs) const noexcept
428
    {
522,446✔
429
        return table == rhs.table;
522,446✔
430
    }
522,446✔
431
};
432

433
/// All instructions except schema instructions are ObjectInstructions.
434
struct ObjectInstruction : TableInstruction {
435
    PrimaryKey object;
436

437
protected:
438
    bool operator==(const ObjectInstruction& rhs) const noexcept
439
    {
483,250✔
440
        return TableInstruction::operator==(rhs) && object == rhs.object;
483,250✔
441
    }
483,250✔
442
};
443

444
/// All instructions except schema instructions and CreateObject/EraseObject are PathInstructions.
445
struct PathInstruction : ObjectInstruction {
446
    InternString field;
447
    Path path;
448

449
    uint32_t& index() noexcept
450
    {
10,636✔
451
        return path.index();
10,636✔
452
    }
10,636✔
453

454
    uint32_t index() const noexcept
455
    {
×
456
        return path.index();
×
457
    }
×
458

459
protected:
460
    bool operator==(const PathInstruction& rhs) const noexcept
461
    {
380,144✔
462
        return ObjectInstruction::operator==(rhs) && field == rhs.field && path == rhs.path;
380,146✔
463
    }
380,144✔
464
};
465

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

475
        bool operator==(const TopLevelTable& rhs) const noexcept
476
        {
8,376✔
477
            return pk_field == rhs.pk_field && pk_type == rhs.pk_type && pk_nullable == rhs.pk_nullable &&
8,376✔
478
                   is_asymmetric == rhs.is_asymmetric;
8,376✔
479
        }
8,376✔
480
    };
481

482
    struct EmbeddedTable {
483
        bool operator==(const EmbeddedTable&) const noexcept
484
        {
48✔
485
            return true;
48✔
486
        }
48✔
487
    };
488

489
    mpark::variant<TopLevelTable, EmbeddedTable> type;
490

491
    bool operator==(const AddTable& rhs) const noexcept
492
    {
8,424✔
493
        return TableInstruction::operator==(rhs) && type == rhs.type;
8,424✔
494
    }
8,424✔
495
};
496

497
struct EraseTable : TableInstruction {
498
    using TableInstruction::TableInstruction;
499

500
    bool operator==(const EraseTable& rhs) const noexcept
501
    {
4,948✔
502
        return TableInstruction::operator==(rhs);
4,948✔
503
    }
4,948✔
504
};
505

506
struct AddColumn : TableInstruction {
507
    using TableInstruction::TableInstruction;
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
    {
25,816✔
527
        return TableInstruction::operator==(rhs) && field == rhs.field && type == rhs.type &&
25,816✔
528
               key_type == rhs.key_type && nullable == rhs.nullable && collection_type == rhs.collection_type &&
25,816✔
529
               link_target_table == rhs.link_target_table;
25,816✔
530
    }
25,816✔
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
    {
67,160✔
548
        return ObjectInstruction::operator==(rhs);
67,160✔
549
    }
67,160✔
550
};
551

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

555
    bool operator==(const EraseObject& rhs) const noexcept
556
    {
35,948✔
557
        return ObjectInstruction::operator==(rhs);
35,948✔
558
    }
35,948✔
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)
702,690✔
573
    {
1,462,232✔
574
    }
1,462,232✔
575

576
    bool is_array_update() const noexcept
577
    {
1,989,032✔
578
        return path.is_array_index();
1,989,032✔
579
    }
1,989,032✔
580

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

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

592
    bool operator==(const AddInteger& rhs) const noexcept
593
    {
310,434✔
594
        return PathInstruction::operator==(rhs) && value == rhs.value;
310,434✔
595
    }
310,434✔
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
    {
29,246✔
606
        return PathInstruction::operator==(rhs) && value == rhs.value && prior_size == rhs.prior_size;
29,246✔
607
    }
29,246✔
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
    {
5,216✔
629
        return PathInstruction::operator==(rhs) && prior_size == rhs.prior_size;
5,216✔
630
    }
5,216✔
631
};
632

633
struct Clear : PathInstruction {
634
    using PathInstruction::PathInstruction;
635
    CollectionType collection_type;
636

637
    bool operator==(const Clear& rhs) const noexcept
638
    {
832✔
639
        return PathInstruction::operator==(rhs);
832✔
640
    }
832✔
641
};
642

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

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

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

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

663

664
} // namespace instr
665

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

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

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

699
    template <Type t>
700
    struct GetType;
701
    template <class T>
702
    struct GetInstructionType;
703

704
    template <class T>
705
    Instruction(T instr);
706

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

714
    Type type() const noexcept;
715

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

721
    template <class T>
722
    T* get_if() noexcept;
723

724
    template <class T>
725
    const T* get_if() const noexcept
726
    {
8,911,584✔
727
        return const_cast<Instruction&>(*this).get_if<T>();
8,911,584✔
728
    }
8,911,584✔
729

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

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

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

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

757
    size_t path_length() const noexcept;
758

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

767
private:
768
    template <class V, class F>
769
    static decltype(auto) visit(F&& lambda, V&& instr);
770
};
771

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

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

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

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

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

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

864
inline bool is_valid_key_type(Instruction::Payload::Type type) noexcept
865
{
69,710✔
866
    using Type = Instruction::Payload::Type;
69,710✔
867
    switch (type) {
69,710✔
868
        case Type::Int:
39,360✔
869
            [[fallthrough]];
39,360✔
870
        case Type::String:
41,812✔
871
            [[fallthrough]];
41,812✔
872
        case Type::ObjectId:
69,394✔
873
            [[fallthrough]];
69,394✔
874
        case Type::UUID:
69,686✔
875
            [[fallthrough]];
69,686✔
876
        case Type::GlobalKey:
69,710✔
877
            return true;
69,710✔
878
        case Type::Null: // Mixed is not a valid primary key
✔
879
            [[fallthrough]];
×
880
        default:
✔
881
            return false;
×
882
    }
69,710✔
883
}
69,710✔
884

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

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

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

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

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

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

954

955
/// Implementation:
956

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

969
template <class T>
970
Instruction::Instruction(T instr)
971
    : m_instr(std::move(instr))
846,544✔
972
{
1,740,454✔
973
    static_assert(!std::is_same_v<T, Vector>);
1,740,454✔
974
}
1,740,454✔
975

976
template <class V, class F>
977
inline decltype(auto) Instruction::visit(F&& lambda, V&& instr)
978
{
5,483,096✔
979
    // Cannot use std::visit, because it does not pass lvalue references to the visitor.
980
    if (mpark::holds_alternative<Vector>(instr)) {
5,483,096✔
981
        REALM_TERMINATE("visiting instruction vector");
982
    }
×
983
#define REALM_VISIT_VARIANT(X)                                                                                       \
5,483,096✔
984
    else if (auto ptr = mpark::get_if<Instruction::X>(&instr))                                                       \
46,278,686✔
985
    {                                                                                                                \
35,320,386✔
986
        return lambda(*ptr);                                                                                         \
5,481,902✔
987
    }
5,481,902✔
988
    REALM_FOR_EACH_INSTRUCTION_TYPE(REALM_VISIT_VARIANT)
46,278,686!
989
#undef REALM_VISIT_VARIANT
2,147,484,993✔
990
    else
2,147,484,993✔
991
    {
2,147,484,993✔
992
        REALM_TERMINATE("Unhandled instruction variant entry");
993
    }
2,147,484,993✔
994
}
5,483,096✔
995

996
template <class F>
997
inline decltype(auto) Instruction::visit(F&& lambda)
998
{
4,207,930✔
999
    return visit(std::forward<F>(lambda), m_instr);
4,207,930✔
1000
}
4,207,930✔
1001

1002
template <class F>
1003
inline decltype(auto) Instruction::visit(F&& lambda) const
1004
{
1,275,106✔
1005
    return visit(std::forward<F>(lambda), m_instr);
1,275,106✔
1006
}
1,275,106✔
1007

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

1016
inline bool Instruction::operator==(const Instruction& other) const noexcept
1017
{
522,436✔
1018
    return m_instr == other.m_instr;
522,436✔
1019
}
522,436✔
1020

1021
template <class T>
1022
REALM_NOINLINE T* Instruction::get_if() noexcept
1023
{
10,435,984✔
1024
    // FIXME: Is there a way to express this without giant switch statements? Note: Putting the
1025
    // base class into a union does not seem to be allowed by the standard.
1026
    if constexpr (std::is_same_v<TableInstruction, T>) {
10,435,984✔
1027
        // This should compile to nothing but a comparison of the type.
1028
        return visit([](auto& instr) -> TableInstruction* {
5,548,264✔
1029
            return &instr;
×
1030
        });
×
1031
    }
1032
    else if constexpr (std::is_same_v<ObjectInstruction, T>) {
6,763,614✔
1033
        // This should compile to nothing but a comparison of the type.
1034
        return visit(util::overload{
5,376,556✔
1035
            [](AddTable&) -> ObjectInstruction* {
5,376,556✔
1036
                return nullptr;
35,818✔
1037
            },
35,818✔
1038
            [](EraseTable&) -> ObjectInstruction* {
5,376,556✔
1039
                return nullptr;
12,976✔
1040
            },
12,976✔
1041
            [](AddColumn&) -> ObjectInstruction* {
5,376,556✔
1042
                return nullptr;
80,746✔
1043
            },
80,746✔
1044
            [](EraseColumn&) -> ObjectInstruction* {
5,376,556✔
1045
                return nullptr;
×
1046
            },
×
1047
            [](auto& instr) -> ObjectInstruction* {
5,376,556✔
1048
                return &instr;
2,471,726✔
1049
            },
2,471,726✔
1050
        });
2,602,412✔
1051
    }
1,387,062✔
1052
    else if constexpr (std::is_same_v<PathInstruction, T>) {
4,576,548✔
1053
        // This should compile to nothing but a comparison of the type.
1054
        return visit(util::overload{
4,088,440✔
1055
            [](AddTable&) -> PathInstruction* {
4,088,440✔
1056
                return nullptr;
20,808✔
1057
            },
20,808✔
1058
            [](EraseTable&) -> PathInstruction* {
4,088,440✔
1059
                return nullptr;
8,072✔
1060
            },
8,072✔
1061
            [](AddColumn&) -> PathInstruction* {
4,088,440✔
1062
                return nullptr;
49,686✔
1063
            },
49,686✔
1064
            [](EraseColumn&) -> PathInstruction* {
4,088,440✔
1065
                return nullptr;
×
1066
            },
×
1067
            [](CreateObject&) -> PathInstruction* {
4,088,440✔
1068
                return nullptr;
212,630✔
1069
            },
212,630✔
1070
            [](EraseObject&) -> PathInstruction* {
4,088,440✔
1071
                return nullptr;
96,350✔
1072
            },
96,350✔
1073
            [](auto& instr) -> PathInstruction* {
4,088,440✔
1074
                return &instr;
515,866✔
1075
            },
515,866✔
1076
        });
903,434✔
1077
    }
488,092✔
1078
    else {
6,930,126✔
1079
        return mpark::get_if<T>(&m_instr);
6,930,126✔
1080
    }
6,930,126✔
1081
}
10,435,984✔
1082

1083
inline size_t Instruction::size() const noexcept
1084
{
16,132,586✔
1085
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
16,132,586✔
1086
        return vec->size();
442,976✔
1087
    }
442,976✔
1088
    return 1;
15,689,610✔
1089
}
16,132,586✔
1090

1091
inline bool Instruction::is_empty() const noexcept
1092
{
27,940✔
1093
    return size() == 0;
27,940✔
1094
}
27,940✔
1095

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

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

1116
inline size_t Instruction::path_length() const noexcept
1117
{
903,454✔
1118
    // Find the path length of the instruction. This affects how OT decides
1119
    // which instructions are potentially nesting.
1120
    //
1121
    // AddTable/EraseTable:   Length 1
1122
    // AddColumn/EraseColumn: Length 2 (table, field)
1123
    // Object instructions:   Length 2 (table, object)
1124
    // Path instructions:     Length 3 + m_path.size (table, object, field, path...)
1125
    if (auto path_instr = get_if<Instruction::PathInstruction>()) {
903,454✔
1126
        return 3 + path_instr->path.size();
515,832✔
1127
    }
515,832✔
1128
    if (get_if<Instruction::ObjectInstruction>()) {
387,622✔
1129
        return 2;
308,974✔
1130
    }
308,974✔
1131
    switch (type()) {
78,648✔
1132
        case Instruction::Type::AddColumn:
49,686✔
1133
            [[fallthrough]];
49,686✔
1134
        case Instruction::Type::EraseColumn: {
49,686✔
1135
            return 2;
49,686✔
1136
        }
49,686✔
1137
        case Instruction::Type::AddTable:
20,808✔
1138
            [[fallthrough]];
20,808✔
1139
        case Instruction::Type::EraseTable: {
28,880✔
1140
            return 1;
28,880✔
1141
        }
20,808✔
1142
        default:
✔
1143
            REALM_TERMINATE("Unhandled instruction type in Instruction::path_len()");
1144
    }
78,648✔
1145
}
78,648✔
1146

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

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

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

1174
} // namespace sync
1175
} // namespace realm
1176

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