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

realm / realm-core / jorgen.edelbo_334

01 Jul 2024 07:22AM UTC coverage: 90.829% (-0.04%) from 90.865%
jorgen.edelbo_334

Pull #7803

Evergreen

jedelbo
Merge branch 'next-major' into feature/string-compression
Pull Request #7803: Feature/string compression

102912 of 180568 branches covered (56.99%)

1141 of 1267 new or added lines in 33 files covered. (90.06%)

172 existing lines in 24 files now uncovered.

218291 of 240332 relevant lines covered (90.83%)

7818396.4 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,700,220✔
27
    X(EraseTable)                                                                                                    \
5,700,220✔
28
    X(AddColumn)                                                                                                     \
5,593,804✔
29
    X(EraseColumn)                                                                                                   \
5,550,790✔
30
    X(CreateObject)                                                                                                  \
5,280,466✔
31
    X(EraseObject)                                                                                                   \
5,280,450✔
32
    X(Update)                                                                                                        \
4,010,946✔
33
    X(AddInteger)                                                                                                    \
3,489,124✔
34
    X(ArrayInsert)                                                                                                   \
947,504✔
35
    X(ArrayMove)                                                                                                     \
454,952✔
36
    X(ArrayErase)                                                                                                    \
45,600✔
37
    X(Clear)                                                                                                         \
45,088✔
38
    X(SetInsert)                                                                                                     \
13,680✔
39
    X(SetErase)
5,700,220✔
40

41
struct StringBufferRange {
42
    uint32_t offset, size;
43

44
    friend bool operator==(const StringBufferRange& lhs, const StringBufferRange& rhs) noexcept
45
    {
20,270✔
46
        return lhs.offset == rhs.offset && lhs.size == rhs.size;
20,270✔
47
    }
20,270✔
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,064,524✔
54
    {
10,352,362✔
55
    }
10,352,362✔
56

57
    uint32_t value;
58

59
    constexpr bool operator==(const InternString& other) const noexcept
60
    {
2,125,820✔
61
        return value == other.value;
2,125,820✔
62
    }
2,125,820✔
63
    constexpr bool operator!=(const InternString& other) const noexcept
64
    {
2,920,130✔
65
        return value != other.value;
2,920,130✔
66
    }
2,920,130✔
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,643,464✔
89
        return m_path.size();
1,643,464✔
90
    }
1,643,464✔
91

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

104
    uint32_t& index() noexcept
105
    {
6,180✔
106
        REALM_ASSERT(is_array_index());
6,180✔
107
        return mpark::get<uint32_t>(m_path.back());
6,180✔
108
    }
6,180✔
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,750✔
118
        REALM_ASSERT(!m_path.empty());
1,750✔
119
        return m_path.back();
1,750✔
120
    }
1,750✔
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,406✔
136
        REALM_ASSERT(idx < m_path.size());
7,406✔
137
        return m_path[idx];
7,406✔
138
    }
7,406✔
139

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

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

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

155
    using const_iterator = typename std::vector<Element>::const_iterator;
156
    const_iterator begin() const noexcept
157
    {
1,730,584✔
158
        return m_path.begin();
1,730,584✔
159
    }
1,730,584✔
160
    const_iterator end() const noexcept
161
    {
1,725,244✔
162
        return m_path.end();
1,725,244✔
163
    }
1,725,244✔
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,781,484✔
256
    };
257

258
    Data data;
259
    Type type;
260

261
    Payload()
262
        : Payload(realm::util::none)
1,558,222✔
263
    {
3,197,048✔
264
    }
3,197,048✔
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,772✔
272
    {
298,160✔
273
        data.integer = value;
298,160✔
274
    }
298,160✔
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,122✔
292
    {
215,470✔
293
        if (is_binary) {
215,470✔
294
            data.binary = value;
7,984✔
295
        }
7,984✔
296
        else {
207,486✔
297
            data.str = value;
207,486✔
298
        }
207,486✔
299
    }
215,470✔
300
    explicit Payload(realm::util::None) noexcept
301
        : type(Type::Null)
1,558,114✔
302
    {
3,196,954✔
303
    }
3,196,954✔
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
    {
304✔
369
        return type == Type::Null;
304✔
370
    }
304✔
371

372
    friend bool operator==(const Payload& lhs, const Payload& rhs) noexcept
373
    {
52,768✔
374
        if (lhs.type == rhs.type) {
52,768✔
375
            switch (lhs.type) {
52,768✔
376
                case Type::Null:
696✔
377
                case Type::Erased:
760✔
378
                case Type::List:
1,568✔
379
                case Type::Set:
1,568✔
380
                case Type::Dictionary:
2,028✔
381
                case Type::ObjectValue:
2,372✔
382
                    return true;
2,372✔
383
                case Type::GlobalKey:
✔
384
                    return lhs.data.key == rhs.data.key;
×
385
                case Type::Int:
29,014✔
386
                    return lhs.data.integer == rhs.data.integer;
29,014✔
387
                case Type::Bool:
32✔
388
                    return lhs.data.boolean == rhs.data.boolean;
32✔
389
                case Type::String:
20,114✔
390
                    return lhs.data.str == rhs.data.str;
20,114✔
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
            }
52,768✔
408
        }
52,768✔
UNCOV
409
        return false;
×
410
    }
52,768✔
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
    {
535,926✔
429
        return table == rhs.table;
535,926✔
430
    }
535,926✔
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
    {
491,682✔
440
        return TableInstruction::operator==(rhs) && object == rhs.object;
491,682✔
441
    }
491,682✔
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
    {
6,180✔
451
        return path.index();
6,180✔
452
    }
6,180✔
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
    {
366,964✔
462
        return ObjectInstruction::operator==(rhs) && field == rhs.field && path == rhs.path;
366,964✔
463
    }
366,964✔
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,230✔
477
            return pk_field == rhs.pk_field && pk_type == rhs.pk_type && pk_nullable == rhs.pk_nullable &&
8,230✔
478
                   is_asymmetric == rhs.is_asymmetric;
8,230✔
479
        }
8,230✔
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,278✔
493
        return TableInstruction::operator==(rhs) && type == rhs.type;
8,278✔
494
    }
8,278✔
495
};
496

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

500
    bool operator==(const EraseTable& rhs) const noexcept
501
    {
5,552✔
502
        return TableInstruction::operator==(rhs);
5,552✔
503
    }
5,552✔
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
    {
30,408✔
527
        return TableInstruction::operator==(rhs) && field == rhs.field && type == rhs.type &&
30,408✔
528
               key_type == rhs.key_type && nullable == rhs.nullable && collection_type == rhs.collection_type &&
30,408✔
529
               link_target_table == rhs.link_target_table;
30,408✔
530
    }
30,408✔
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
    {
78,452✔
548
        return ObjectInstruction::operator==(rhs);
78,452✔
549
    }
78,452✔
550
};
551

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

555
    bool operator==(const EraseObject& rhs) const noexcept
556
    {
46,264✔
557
        return ObjectInstruction::operator==(rhs);
46,264✔
558
    }
46,264✔
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)
710,156✔
573
    {
1,467,806✔
574
    }
1,467,806✔
575

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

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

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

592
    bool operator==(const AddInteger& rhs) const noexcept
593
    {
309,514✔
594
        return PathInstruction::operator==(rhs) && value == rhs.value;
309,514✔
595
    }
309,514✔
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
    {
23,516✔
606
        return PathInstruction::operator==(rhs) && value == rhs.value && prior_size == rhs.prior_size;
23,516✔
607
    }
23,516✔
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
    {
2,732✔
629
        return PathInstruction::operator==(rhs) && prior_size == rhs.prior_size;
2,732✔
630
    }
2,732✔
631
};
632

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

637
    bool operator==(const Clear& rhs) const noexcept
638
    {
880✔
639
        return PathInstruction::operator==(rhs);
880✔
640
    }
880✔
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
    {
9,341,004✔
727
        return const_cast<Instruction&>(*this).get_if<T>();
9,341,004✔
728
    }
9,341,004✔
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
{
52✔
791
    using Type = Instruction::Payload::Type;
52✔
792
    switch (type) {
52✔
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:
14✔
808
            return "Int";
14✔
809
        case Type::Bool:
✔
810
            return "Bool";
×
811
        case Type::String:
22✔
812
            return "String";
22✔
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
    }
52✔
830
    return "(unknown)";
×
831
}
52✔
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
{
68,840✔
866
    using Type = Instruction::Payload::Type;
68,840✔
867
    switch (type) {
68,840✔
868
        case Type::Int:
37,688✔
869
            [[fallthrough]];
37,688✔
870
        case Type::String:
41,046✔
871
            [[fallthrough]];
41,046✔
872
        case Type::ObjectId:
68,524✔
873
            [[fallthrough]];
68,524✔
874
        case Type::UUID:
68,816✔
875
            [[fallthrough]];
68,816✔
876
        case Type::GlobalKey:
68,840✔
877
            return true;
68,840✔
878
        case Type::Null: // Mixed is not a valid primary key
✔
879
            [[fallthrough]];
×
880
        default:
✔
881
            return false;
×
882
    }
68,840✔
883
}
68,840✔
884

885
inline DataType get_data_type(Instruction::Payload::Type type) noexcept
886
{
12,254✔
887
    using Type = Instruction::Payload::Type;
12,254✔
888
    switch (type) {
12,254✔
889
        case Type::Int:
5,228✔
890
            return type_Int;
5,228✔
891
        case Type::Bool:
68✔
892
            return type_Bool;
68✔
893
        case Type::String:
3,458✔
894
            return type_String;
3,458✔
895
        case Type::Binary:
80✔
896
            return type_Binary;
80✔
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,602✔
908
            return type_ObjectId;
1,602✔
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,254✔
926
    return type_Int; // Make compiler happy
×
927
}
12,254✔
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))
857,970✔
972
{
1,750,088✔
973
    static_assert(!std::is_same_v<T, Vector>);
1,750,088✔
974
}
1,750,088✔
975

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

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

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

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

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

1021
template <class T>
1022
REALM_NOINLINE T* Instruction::get_if() noexcept
1023
{
10,893,998✔
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,893,998✔
1027
        // This should compile to nothing but a comparison of the type.
1028
        return visit([](auto& instr) -> TableInstruction* {
5,726,220✔
1029
            return &instr;
×
1030
        });
×
1031
    }
1032
    else if constexpr (std::is_same_v<ObjectInstruction, T>) {
7,017,468✔
1033
        // This should compile to nothing but a comparison of the type.
1034
        return visit(util::overload{
5,551,766✔
1035
            [](AddTable&) -> ObjectInstruction* {
5,551,766✔
1036
                return nullptr;
34,390✔
1037
            },
34,390✔
1038
            [](EraseTable&) -> ObjectInstruction* {
5,551,766✔
1039
                return nullptr;
13,522✔
1040
            },
13,522✔
1041
            [](AddColumn&) -> ObjectInstruction* {
5,551,766✔
1042
                return nullptr;
89,866✔
1043
            },
89,866✔
1044
            [](EraseColumn&) -> ObjectInstruction* {
5,551,766✔
1045
                return nullptr;
×
1046
            },
×
1047
            [](auto& instr) -> ObjectInstruction* {
5,551,766✔
1048
                return &instr;
2,618,028✔
1049
            },
2,618,028✔
1050
        });
2,756,950✔
1051
    }
1,465,702✔
1052
    else if constexpr (std::is_same_v<PathInstruction, T>) {
4,719,486✔
1053
        // This should compile to nothing but a comparison of the type.
1054
        return visit(util::overload{
4,222,704✔
1055
            [](AddTable&) -> PathInstruction* {
4,222,704✔
1056
                return nullptr;
20,202✔
1057
            },
20,202✔
1058
            [](EraseTable&) -> PathInstruction* {
4,222,704✔
1059
                return nullptr;
8,474✔
1060
            },
8,474✔
1061
            [](AddColumn&) -> PathInstruction* {
4,222,704✔
1062
                return nullptr;
55,172✔
1063
            },
55,172✔
1064
            [](EraseColumn&) -> PathInstruction* {
4,222,704✔
1065
                return nullptr;
×
1066
            },
×
1067
            [](CreateObject&) -> PathInstruction* {
4,222,704✔
1068
                return nullptr;
229,008✔
1069
            },
229,008✔
1070
            [](EraseObject&) -> PathInstruction* {
4,222,704✔
1071
                return nullptr;
109,784✔
1072
            },
109,784✔
1073
            [](auto& instr) -> PathInstruction* {
4,222,704✔
1074
                return &instr;
533,180✔
1075
            },
533,180✔
1076
        });
955,746✔
1077
    }
496,778✔
1078
    else {
7,181,298✔
1079
        return mpark::get_if<T>(&m_instr);
7,181,298✔
1080
    }
7,181,298✔
1081
}
10,893,998✔
1082

1083
inline size_t Instruction::size() const noexcept
1084
{
16,722,598✔
1085
    if (auto vec = mpark::get_if<Vector>(&m_instr)) {
16,722,598✔
1086
        return vec->size();
464,080✔
1087
    }
464,080✔
1088
    return 1;
16,258,518✔
1089
}
16,722,598✔
1090

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

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

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

1116
inline size_t Instruction::path_length() const noexcept
1117
{
955,744✔
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>()) {
955,744✔
1126
        return 3 + path_instr->path.size();
532,798✔
1127
    }
532,798✔
1128
    if (get_if<Instruction::ObjectInstruction>()) {
422,946✔
1129
        return 2;
338,788✔
1130
    }
338,788✔
1131
    switch (type()) {
84,158✔
1132
        case Instruction::Type::AddColumn:
55,172✔
1133
            [[fallthrough]];
55,172✔
1134
        case Instruction::Type::EraseColumn: {
55,172✔
1135
            return 2;
55,172✔
1136
        }
55,172✔
1137
        case Instruction::Type::AddTable:
20,202✔
1138
            [[fallthrough]];
20,202✔
1139
        case Instruction::Type::EraseTable: {
28,676✔
1140
            return 1;
28,676✔
1141
        }
20,202✔
1142
        default:
✔
1143
            REALM_TERMINATE("Unhandled instruction type in Instruction::path_len()");
1144
    }
84,158✔
1145
}
84,158✔
1146

1147
inline Instruction::Vector& Instruction::convert_to_vector()
1148
{
42,362✔
1149
    if (auto v = mpark::get_if<Vector>(&m_instr)) {
42,362✔
1150
        return *v;
×
1151
    }
×
1152
    else {
42,362✔
1153
        Vector vec;
42,362✔
1154
        vec.emplace_back(std::move(*this));
42,362✔
1155
        m_instr = std::move(vec);
42,362✔
1156
        return mpark::get<Vector>(m_instr);
42,362✔
1157
    }
42,362✔
1158
}
42,362✔
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
{
42,362✔
1169
    auto& vec = convert_to_vector();
42,362✔
1170
    REALM_ASSERT(idx < vec.size());
42,362✔
1171
    vec.erase(vec.begin() + idx);
42,362✔
1172
}
42,362✔
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

© 2025 Coveralls, Inc