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

realm / realm-core / 2306

10 May 2024 05:50PM UTC coverage: 90.837% (-0.2%) from 91.065%
2306

push

Evergreen

danieltabacaru
Add back ability to format Objective-C code

102110 of 181070 branches covered (56.39%)

214623 of 236272 relevant lines covered (90.84%)

5666944.3 hits per line

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

39.71
/src/realm/sync/changeset.cpp
1
#include <realm/sync/changeset.hpp>
2

3
#if REALM_DEBUG
4
#include <iostream>
5
#include <iomanip>
6
#include <sstream>
7
#endif // REALM_DEBUG
8

9
using namespace realm;
10
using namespace realm::sync;
11
using namespace realm::util;
12

13
InternString Changeset::intern_string(StringData str)
14
{
212✔
15
    if (InternString interned = find_string(str))
212✔
16
        return interned;
×
17

18
    REALM_ASSERT(m_string_buffer.size() < std::numeric_limits<uint32_t>::max());
212✔
19
    REALM_ASSERT(m_strings.size() < std::numeric_limits<uint32_t>::max());
212✔
20
    REALM_ASSERT(str.size() < std::numeric_limits<uint32_t>::max());
212✔
21

22
    // FIXME: Very slow.
23
    uint32_t size = uint32_t(str.size());
212✔
24
    uint32_t offset = uint32_t(m_string_buffer.size());
212✔
25
    m_string_buffer.append(str.data(), size);
212✔
26
    uint32_t index = uint32_t(m_strings.size());
212✔
27
    m_strings.push_back(StringBufferRange{offset, size});
212✔
28
    return InternString{index};
212✔
29
}
212✔
30

31

32
InternString Changeset::find_string(StringData string) const noexcept
33
{
212✔
34
    // FIXME: Linear search can be very expensive as changesets can be very big
35
    std::size_t n = m_strings.size();
212✔
36
    for (std::size_t i = 0; i < n; ++i) {
428✔
37
        const auto& range = m_strings[i];
216✔
38
        StringData string_2{m_string_buffer.data() + range.offset, range.size};
216✔
39
        if (string_2 == string)
216✔
40
            return InternString{std::uint_least32_t(i)};
×
41
    }
216✔
42
    return InternString{};
212✔
43
}
212✔
44

45
PrimaryKey Changeset::get_key(const Instruction::PrimaryKey& key) const noexcept
46
{
1,541,816✔
47
    // we do not use the expected `mpark::visit(overload...` because in MSVC 2019 this
48
    // code produces a segfault for something that works on other compilers.
49
    // See https://github.com/realm/realm-core/issues/4624
50
    if (const auto int64_ptr = mpark::get_if<int64_t>(&key)) {
1,541,816✔
51
        return *int64_ptr;
1,392,082✔
52
    }
1,392,082✔
53
    else if (const auto intern_string_ptr = mpark::get_if<InternString>(&key)) {
149,734✔
54
        return this->get_string(*intern_string_ptr);
112,302✔
55
    }
112,302✔
56
    else if (const auto monostate_ptr = mpark::get_if<mpark::monostate>(&key)) {
37,432✔
57
        return *monostate_ptr;
×
58
    }
×
59
    else if (const auto global_key_ptr = mpark::get_if<GlobalKey>(&key)) {
37,432✔
60
        return *global_key_ptr;
×
61
    }
×
62
    else if (const auto oid_ptr = mpark::get_if<ObjectId>(&key)) {
37,432✔
63
        return *oid_ptr;
36,990✔
64
    }
36,990✔
65
    else if (const auto uuid_ptr = mpark::get_if<UUID>(&key)) {
442✔
66
        return *uuid_ptr;
100✔
67
    }
100✔
68
    else {
342✔
69
        REALM_UNREACHABLE(); // unhandled primary key type
70
    }
342✔
71
}
1,541,816✔
72

73
bool Changeset::operator==(const Changeset& that) const noexcept
74
{
60✔
75
    if (m_instructions == that.m_instructions) {
60✔
76
        return m_strings == that.m_strings;
60✔
77
    }
60✔
78
    return false;
×
79
}
60✔
80

81
std::ostream& Changeset::print_value(std::ostream& os, const Instruction::Payload& value) const noexcept
82
{
12✔
83
    using Type = Instruction::Payload::Type;
12✔
84

85
    os << get_type_name(value.type) << "(";
12✔
86
    auto& data = value.data;
12✔
87
    switch (value.type) {
12✔
88
        case Type::ObjectValue:
✔
89
            break;
×
90
        case Type::GlobalKey:
✔
91
            os << data.key;
×
92
            break;
×
93
        case Type::Erased:
✔
94
            break;
×
95
        case Type::Set:
✔
96
            break;
×
97
        case Type::List:
✔
98
            break;
×
99
        case Type::Dictionary:
✔
100
            break;
×
101
        case Type::Null:
✔
102
            break;
×
103
        case Type::Int:
✔
104
            os << data.integer;
×
105
            break;
×
106
        case Type::Bool:
✔
107
            os << data.boolean;
×
108
            break;
×
109
        case Type::String:
✔
110
            os << "\"" << get_string(data.str) << "\"";
×
111
            break;
×
112
        case Type::Binary:
✔
113
            os << "...";
×
114
            break;
×
115
        case Type::Timestamp:
✔
116
            os << data.timestamp;
×
117
            break;
×
118
        case Type::Float:
✔
119
            os << data.fnum;
×
120
            break;
×
121
        case Type::Double:
✔
122
            os << data.dnum;
×
123
            break;
×
124
        case Type::Decimal:
✔
125
            os << data.decimal;
×
126
            break;
×
127
        case Type::UUID:
✔
128
            os << data.uuid;
×
129
            break;
×
130
        case Type::Link: {
12✔
131
            os << "target_table = " << get_string(data.link.target_table) << ", "
12✔
132
               << "target = " << format_pk(get_key(data.link.target));
12✔
133
            break;
12✔
134
        };
×
135
        case Type::ObjectId:
✔
136
            os << data.object_id;
×
137
            break;
×
138
    }
12✔
139
    return os << ")";
12✔
140
}
12✔
141

142
std::ostream& Changeset::print_path(std::ostream& os, const Instruction::Path& path) const noexcept
143
{
×
144
    bool first = true;
×
145
    for (auto& element : path) {
×
146
        if (!first) {
×
147
            os << '.';
×
148
        }
×
149
        first = false;
×
150
        auto print = overload{
×
151
            [&](uint32_t index) {
×
152
                os << index;
×
153
            },
×
154
            [&](InternString str) {
×
155
                os << get_string(str);
×
156
            },
×
157
        };
×
158
        mpark::visit(print, element);
×
159
    }
×
160
    return os;
×
161
}
×
162

163
std::ostream& Changeset::print_path(std::ostream& os, InternString table, const Instruction::PrimaryKey& pk,
164
                                    util::Optional<InternString> field, const Instruction::Path* path) const
165
{
28✔
166
    os << get_string(table) << "[" << format_pk(get_key(pk)) << "]";
28✔
167
    if (field) {
28✔
168
        os << "." << get_string(*field);
12✔
169
    }
12✔
170
    if (path) {
28✔
171
        for (auto& element : *path) {
12✔
172
            if (auto subfield = mpark::get_if<InternString>(&element)) {
12✔
173
                os << "." << get_string(*subfield);
×
174
            }
×
175
            else if (auto index = mpark::get_if<uint32_t>(&element)) {
12✔
176
                os << "[" << *index << "]";
12✔
177
            }
12✔
178
            else {
×
179
                REALM_TERMINATE("Invalid path");
180
            }
×
181
        }
12✔
182
    }
12✔
183
    return os;
28✔
184
}
28✔
185

186
std::ostream& realm::sync::operator<<(std::ostream& os, const Changeset& changeset)
187
{
8✔
188
#if REALM_DEBUG // LCOV_EXCL_START
189
    changeset.print(os);
8✔
190
    return os;
8✔
191
#else
192
    return os << "[changeset with " << changeset.size() << " instructions]";
193
#endif
194
}
8✔
195

196

197
#if REALM_DEBUG // LCOV_EXCL_START
198
void Changeset::print(std::ostream& os) const
199
{
8✔
200
    Changeset::Printer printer{os};
8✔
201
    Changeset::Reflector reflector{printer, *this};
8✔
202
    os << std::left << std::setw(16) << "InternStrings";
8✔
203
    for (size_t i = 0; i < m_strings.size(); ++i) {
48✔
204
        os << i << "=\"" << get_string(m_strings.at(i)) << '"';
40✔
205
        if (i + 1 != m_strings.size())
40✔
206
            os << ", ";
32✔
207
    }
40✔
208
    os << "\n";
8✔
209

210
    reflector.visit_all();
8✔
211
}
8✔
212

213
void Changeset::print() const
214
{
×
215
    print(std::cerr);
×
216
}
×
217

218

219
void Changeset::verify() const
220
{
×
221
    for (size_t i = 0; i < m_strings.size(); ++i) {
×
222
        auto& range = m_strings.at(i);
×
223
        REALM_ASSERT(range.offset <= m_string_buffer.size());
×
224
        REALM_ASSERT(range.offset + range.size <= m_string_buffer.size());
×
225
    }
×
226

227
    auto verify_string_range = [&](StringBufferRange range) {
×
228
        REALM_ASSERT(range.offset <= m_string_buffer.size());
×
229
        REALM_ASSERT(range.offset + range.size <= m_string_buffer.size());
×
230
    };
×
231

232
    auto verify_intern_string = [&](InternString str) {
×
233
        auto range = get_intern_string(str);
×
234
        verify_string_range(range);
×
235
    };
×
236

237
    auto verify_key = [&](const Instruction::PrimaryKey& key) {
×
238
        mpark::visit(util::overload{[&](InternString str) {
×
239
                                        verify_intern_string(str);
×
240
                                    },
×
241
                                    [](auto&&) {}},
×
242
                     key);
×
243
    };
×
244

245
    auto verify_payload = [&](const Instruction::Payload& payload) {
×
246
        using Type = Instruction::Payload::Type;
×
247
        switch (payload.type) {
×
248
            case Type::String: {
×
249
                return verify_string_range(payload.data.str);
×
250
            }
×
251
            case Type::Binary: {
×
252
                return verify_string_range(payload.data.binary);
×
253
            }
×
254
            case Type::Link: {
×
255
                verify_intern_string(payload.data.link.target_table);
×
256
                return verify_key(payload.data.link.target);
×
257
            }
×
258
            default:
×
259
                return;
×
260
        }
×
261
    };
×
262

263
    auto verify_path = [&](const Instruction::Path& path) {
×
264
        for (auto& element : path) {
×
265
            mpark::visit(util::overload{[&](InternString str) {
×
266
                                            verify_intern_string(str);
×
267
                                        },
×
268
                                        [](auto&&) {}},
×
269
                         element);
×
270
        }
×
271
    };
×
272

273
    for (auto instr : *this) {
×
274
        if (!instr)
×
275
            continue;
×
276

277
        if (auto table_instr = instr->get_if<Instruction::TableInstruction>()) {
×
278
            verify_intern_string(table_instr->table);
×
279
            if (auto object_instr = instr->get_if<Instruction::ObjectInstruction>()) {
×
280
                verify_key(object_instr->object);
×
281

282
                if (auto path_instr = instr->get_if<Instruction::PathInstruction>()) {
×
283
                    verify_path(path_instr->path);
×
284
                }
×
285

286
                if (auto set_instr = instr->get_if<Instruction::Update>()) {
×
287
                    verify_payload(set_instr->value);
×
288
                }
×
289
                else if (auto insert_instr = instr->get_if<Instruction::ArrayInsert>()) {
×
290
                    verify_payload(insert_instr->value);
×
291
                }
×
292
            }
×
293
            else if (auto add_table_instr = instr->get_if<Instruction::AddTable>()) {
×
294
                mpark::visit(util::overload{
×
295
                                 [&](const Instruction::AddTable::TopLevelTable& spec) {
×
296
                                     REALM_ASSERT(is_valid_key_type(spec.pk_type));
×
297
                                     verify_intern_string(spec.pk_field);
×
298
                                 },
×
299
                                 [](const Instruction::AddTable::EmbeddedTable&) {},
×
300
                             },
×
301
                             add_table_instr->type);
×
302
            }
×
303
            else if (auto add_column_instr = instr->get_if<Instruction::AddColumn>()) {
×
304
                verify_intern_string(add_column_instr->field);
×
305
                if (add_column_instr->type == Instruction::Payload::Type::Link) {
×
306
                    verify_intern_string(add_column_instr->link_target_table);
×
307
                }
×
308
            }
×
309
            else if (auto erase_column_instr = instr->get_if<Instruction::EraseColumn>()) {
×
310
                verify_intern_string(erase_column_instr->field);
×
311
            }
×
312
        }
×
313
        else {
×
314
            REALM_TERMINATE("Corrupt instruction type");
315
        }
×
316
    }
×
317
}
×
318

319
void Changeset::Reflector::operator()(const Instruction::AddTable& p) const
320
{
8✔
321
    m_tracer.name("AddTable");
8✔
322
    table_instr(p);
8✔
323
    auto trace = util::overload{
8✔
324
        [&](const Instruction::AddTable::TopLevelTable& spec) {
8✔
325
            m_tracer.field("pk_field", spec.pk_field);
8✔
326
            m_tracer.field("pk_type", spec.pk_type);
8✔
327
            m_tracer.field("pk_nullable", spec.pk_nullable);
8✔
328
            m_tracer.field("is_asymmetric", spec.is_asymmetric);
8✔
329
        },
8✔
330
        [&](const Instruction::AddTable::EmbeddedTable&) {
8✔
331
            m_tracer.field("embedded", true);
×
332
        },
×
333
    };
8✔
334
    mpark::visit(trace, p.type);
8✔
335
}
8✔
336

337
void Changeset::Reflector::operator()(const Instruction::EraseTable& p) const
338
{
×
339
    m_tracer.name("EraseTable");
×
340
    table_instr(p);
×
341
}
×
342

343
void Changeset::Reflector::operator()(const Instruction::Update& p) const
344
{
×
345
    m_tracer.name("Update");
×
346
    path_instr(p);
×
347
    m_tracer.field("value", p.value);
×
348
    if (p.is_array_update()) {
×
349
        m_tracer.field("prior_size", p.prior_size);
×
350
    }
×
351
    else {
×
352
        m_tracer.field("default", p.is_default);
×
353
    }
×
354
}
×
355

356
void Changeset::Reflector::operator()(const Instruction::AddInteger& p) const
357
{
×
358
    m_tracer.name("AddInteger");
×
359
    path_instr(p);
×
360
    m_tracer.field("value", Instruction::Payload{p.value});
×
361
}
×
362

363
void Changeset::Reflector::operator()(const Instruction::CreateObject& p) const
364
{
16✔
365
    m_tracer.name("CreateObject");
16✔
366
    object_instr(p);
16✔
367
}
16✔
368

369
void Changeset::Reflector::operator()(const Instruction::EraseObject& p) const
370
{
×
371
    m_tracer.name("EraseObject");
×
372
    object_instr(p);
×
373
}
×
374

375
void Changeset::Reflector::operator()(const Instruction::ArrayInsert& p) const
376
{
12✔
377
    m_tracer.name("ArrayInsert");
12✔
378
    path_instr(p);
12✔
379
    m_tracer.field("value", p.value);
12✔
380
    m_tracer.field("prior_size", p.prior_size);
12✔
381
}
12✔
382

383
void Changeset::Reflector::operator()(const Instruction::ArrayMove& p) const
384
{
×
385
    m_tracer.name("ArrayMove");
×
386
    path_instr(p);
×
387
    m_tracer.field("ndx_2", p.ndx_2);
×
388
    m_tracer.field("prior_size", p.prior_size);
×
389
}
×
390

391
void Changeset::Reflector::operator()(const Instruction::ArrayErase& p) const
392
{
×
393
    m_tracer.name("ArrayErase");
×
394
    path_instr(p);
×
395
    m_tracer.field("prior_size", p.prior_size);
×
396
}
×
397

398
void Changeset::Reflector::operator()(const Instruction::Clear& p) const
399
{
×
400
    m_tracer.name("Clear");
×
401
    path_instr(p);
×
402
    m_tracer.field("collection_type", p.collection_type);
×
403
}
×
404

405
void Changeset::Reflector::operator()(const Instruction::SetInsert& p) const
406
{
×
407
    m_tracer.name("SetInsert");
×
408
    path_instr(p);
×
409
    m_tracer.field("value", p.value);
×
410
}
×
411

412
void Changeset::Reflector::operator()(const Instruction::SetErase& p) const
413
{
×
414
    m_tracer.name("SetErase");
×
415
    path_instr(p);
×
416
    m_tracer.field("value", p.value);
×
417
}
×
418

419
void Changeset::Reflector::operator()(const Instruction::AddColumn& p) const
420
{
4✔
421
    m_tracer.name("AddColumn");
4✔
422
    m_tracer.field("table", p.table);
4✔
423
    m_tracer.field("field", p.field);
4✔
424
    if (p.type != Instruction::Payload::Type::Null) {
4✔
425
        m_tracer.field("type", p.type);
4✔
426
    }
4✔
427
    else {
×
428
        m_tracer.field("type", Instruction::Payload::Type::Null);
×
429
    }
×
430
    m_tracer.field("nullable", p.nullable);
4✔
431
    m_tracer.field("collection_type", p.collection_type);
4✔
432
    if (p.type == Instruction::Payload::Type::Link) {
4✔
433
        m_tracer.field("target_table", p.link_target_table);
4✔
434
    }
4✔
435
    if (p.collection_type == Instruction::CollectionType::Dictionary) {
4✔
436
        m_tracer.field("key_type", p.key_type);
×
437
    }
×
438
}
4✔
439

440
void Changeset::Reflector::operator()(const Instruction::EraseColumn& p) const
441
{
×
442
    m_tracer.name("EraseColumn");
×
443
    m_tracer.field("table", p.table);
×
444
    m_tracer.field("field", p.field);
×
445
}
×
446

447
void Changeset::Reflector::table_instr(const Instruction::TableInstruction& p) const
448
{
8✔
449
    m_tracer.field("path", p.table);
8✔
450
}
8✔
451

452
void Changeset::Reflector::object_instr(const Instruction::ObjectInstruction& p) const
453
{
16✔
454
    m_tracer.path("path", p.table, p.object, util::none, nullptr);
16✔
455
}
16✔
456

457
void Changeset::Reflector::path_instr(const Instruction::PathInstruction& p) const
458
{
12✔
459
    m_tracer.path("path", p.table, p.object, p.field, &p.path);
12✔
460
}
12✔
461

462
void Changeset::Reflector::visit_all() const
463
{
8✔
464
    m_tracer.set_changeset(&m_changeset);
8✔
465
    for (auto instr : m_changeset) {
40✔
466
        if (!instr)
40✔
467
            continue;
×
468
        m_tracer.before_each();
40✔
469
        instr->visit(*this);
40✔
470
        m_tracer.after_each();
40✔
471
    }
40✔
472
    m_tracer.set_changeset(nullptr);
8✔
473
}
8✔
474

475
void Changeset::Printer::name(StringData n)
476
{
40✔
477
    pad_or_ellipsis(n, 16);
40✔
478
}
40✔
479

480
void Changeset::Printer::print_field(StringData name, std::string value)
481
{
116✔
482
    if (!m_first) {
116✔
483
        m_out << ", ";
76✔
484
    }
76✔
485
    m_first = false;
116✔
486
    m_out << name << "=" << value;
116✔
487
}
116✔
488

489
void Changeset::Printer::path(StringData name, InternString table, const Instruction::PrimaryKey& pk,
490
                              util::Optional<InternString> field, const Instruction::Path* path)
491
{
28✔
492
    std::stringstream ss;
28✔
493
    m_changeset->print_path(ss, table, pk, field, path);
28✔
494
    print_field(name, ss.str());
28✔
495
}
28✔
496

497
void Changeset::Printer::field(StringData n, InternString value)
498
{
28✔
499
    std::stringstream ss;
28✔
500
    ss << "\"" << m_changeset->get_string(value) << "\"";
28✔
501
    print_field(n, ss.str());
28✔
502
}
28✔
503

504
void Changeset::Printer::field(StringData n, Instruction::Payload::Type type)
505
{
12✔
506
    print_field(n, get_type_name(type));
12✔
507
}
12✔
508

509
void Changeset::Printer::field(StringData n, Instruction::CollectionType type)
510
{
4✔
511
    print_field(n, get_collection_type(type));
4✔
512
}
4✔
513

514
std::string Changeset::Printer::primary_key_to_string(const Instruction::PrimaryKey& key)
515
{
×
516
    auto convert = overload{
×
517
        [&](const mpark::monostate&) {
×
518
            return std::string("NULL");
×
519
        },
×
520
        [&](int64_t value) {
×
521
            std::stringstream ss;
×
522
            ss << value;
×
523
            return ss.str();
×
524
        },
×
525
        [&](InternString str) {
×
526
            std::stringstream ss;
×
527
            ss << "\"" << m_changeset->get_string(str) << "\"";
×
528
            return ss.str();
×
529
        },
×
530
        [&](GlobalKey key) {
×
531
            std::stringstream ss;
×
532
            ss << key;
×
533
            return ss.str();
×
534
        },
×
535
        [&](ObjectId id) {
×
536
            std::stringstream ss;
×
537
            ss << id;
×
538
            return ss.str();
×
539
        },
×
540
        [&](UUID uuid) {
×
541
            return uuid.to_string();
×
542
        },
×
543
    };
×
544
    return mpark::visit(convert, key);
×
545
}
×
546

547
void Changeset::Printer::field(StringData n, const Instruction::PrimaryKey& key)
548
{
×
549
    std::stringstream ss;
×
550
    ss << format_pk(m_changeset->get_key(key));
×
551
    print_field(n, ss.str());
×
552
}
×
553

554
void Changeset::Printer::field(StringData n, const Instruction::Payload& value)
555
{
12✔
556
    std::stringstream ss;
12✔
557
    m_changeset->print_value(ss, value);
12✔
558
    print_field(n, ss.str());
12✔
559
}
12✔
560

561
void Changeset::Printer::field(StringData n, const Instruction::Path& path)
562
{
×
563
    std::stringstream ss;
×
564
    ss << "[";
×
565
    bool first = true;
×
566
    for (auto& element : path) {
×
567
        if (!first) {
×
568
            ss << ".";
×
569
        }
×
570
        first = false;
×
571

572
        auto print = util::overload{
×
573
            [&](InternString field) {
×
574
                ss << m_changeset->get_string(field);
×
575
            },
×
576
            [&](uint32_t index) {
×
577
                ss << index;
×
578
            },
×
579
        };
×
580
        mpark::visit(print, element);
×
581
    }
×
582
    ss << "]";
×
583
    print_field(n, ss.str());
×
584
}
×
585

586
void Changeset::Printer::field(StringData n, uint32_t value)
587
{
32✔
588
    std::stringstream ss;
32✔
589
    ss << value;
32✔
590
    print_field(n, ss.str());
32✔
591
}
32✔
592

593
void Changeset::Printer::after_each()
594
{
40✔
595
    m_out << "\n";
40✔
596
    m_first = true;
40✔
597
}
40✔
598

599
void Changeset::Printer::pad_or_ellipsis(StringData s, int width) const
600
{
40✔
601
    // FIXME: Does not work with UTF-8.
602
    std::string str = s; // FIXME: StringData doesn't work with iomanip because it calls ios_base::write() directly
40✔
603
    if (str.size() > size_t(width)) {
40✔
604
        m_out << str.substr(0, width - 1) << "~";
×
605
    }
×
606
    else {
40✔
607
        m_out << std::left << std::setw(width) << str;
40✔
608
    }
40✔
609
}
40✔
610

611
#endif // REALM_DEBUG LCOV_EXCL_STOP
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