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

realm / realm-core / daniel.tabacaru_887

07 Aug 2024 08:36PM UTC coverage: 91.108% (+0.02%) from 91.087%
daniel.tabacaru_887

Pull #7963

Evergreen

danieltabacaru
Unregister SyncSession callbacks when SyncManager is destroyed
Pull Request #7963: RJS-2784 Fix callback crashes when reloading with React Native

102796 of 181584 branches covered (56.61%)

35 of 37 new or added lines in 2 files covered. (94.59%)

294 existing lines in 9 files now uncovered.

217072 of 238259 relevant lines covered (91.11%)

5903576.39 hits per line

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

39.15
/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✔
UNCOV
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✔
UNCOV
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,507,094✔
47
    return mpark::visit(overload{
1,507,094✔
48
                            [this](InternString str) -> PrimaryKey {
1,507,094✔
49
                                return get_string(str);
86,178✔
50
                            },
86,178✔
51
                            [](auto otherwise) -> PrimaryKey {
1,507,094✔
52
                                return otherwise;
1,419,560✔
53
                            },
1,419,560✔
54
                        },
1,507,094✔
55
                        key);
1,507,094✔
56
}
1,507,094✔
57

58
bool Changeset::operator==(const Changeset& that) const noexcept
59
{
60✔
60
    if (m_instructions == that.m_instructions) {
60✔
61
        return m_strings == that.m_strings;
60✔
62
    }
60✔
63
    return false;
×
64
}
60✔
65

66
std::ostream& Changeset::print_value(std::ostream& os, const Instruction::Payload& value) const noexcept
67
{
12✔
68
    using Type = Instruction::Payload::Type;
12✔
69

70
    os << get_type_name(value.type) << "(";
12✔
71
    auto& data = value.data;
12✔
72
    switch (value.type) {
12✔
73
        case Type::ObjectValue:
✔
74
            break;
×
75
        case Type::GlobalKey:
✔
76
            os << data.key;
×
77
            break;
×
78
        case Type::Erased:
✔
79
            break;
×
80
        case Type::Set:
✔
81
            break;
×
82
        case Type::List:
✔
83
            break;
×
84
        case Type::Dictionary:
✔
85
            break;
×
86
        case Type::Null:
✔
87
            break;
×
88
        case Type::Int:
✔
89
            os << data.integer;
×
90
            break;
×
91
        case Type::Bool:
✔
92
            os << data.boolean;
×
93
            break;
×
94
        case Type::String:
✔
95
            os << "\"" << get_string(data.str) << "\"";
×
96
            break;
×
97
        case Type::Binary:
✔
98
            os << "...";
×
99
            break;
×
100
        case Type::Timestamp:
✔
101
            os << data.timestamp;
×
102
            break;
×
103
        case Type::Float:
✔
104
            os << data.fnum;
×
105
            break;
×
106
        case Type::Double:
✔
107
            os << data.dnum;
×
108
            break;
×
109
        case Type::Decimal:
✔
110
            os << data.decimal;
×
111
            break;
×
112
        case Type::UUID:
✔
113
            os << data.uuid;
×
114
            break;
×
115
        case Type::Link: {
12✔
116
            os << "target_table = " << get_string(data.link.target_table) << ", "
12✔
117
               << "target = " << format_pk(get_key(data.link.target));
12✔
118
            break;
12✔
119
        };
×
120
        case Type::ObjectId:
✔
121
            os << data.object_id;
×
122
            break;
×
123
    }
12✔
124
    return os << ")";
12✔
125
}
12✔
126

127
std::ostream& Changeset::print_path(std::ostream& os, const Instruction::Path& path) const noexcept
128
{
×
129
    bool first = true;
×
130
    for (auto& element : path) {
×
131
        if (!first) {
×
132
            os << '.';
×
133
        }
×
134
        first = false;
×
135
        auto print = overload{
×
136
            [&](uint32_t index) {
×
137
                os << index;
×
138
            },
×
139
            [&](InternString str) {
×
140
                os << get_string(str);
×
141
            },
×
142
        };
×
143
        mpark::visit(print, element);
×
144
    }
×
145
    return os;
×
146
}
×
147

148
std::ostream& Changeset::print_path(std::ostream& os, InternString table, const Instruction::PrimaryKey& pk,
149
                                    util::Optional<InternString> field, const Instruction::Path* path) const
150
{
28✔
151
    os << get_string(table) << "[" << format_pk(get_key(pk)) << "]";
28✔
152
    if (field) {
28✔
153
        os << "." << get_string(*field);
12✔
154
    }
12✔
155
    if (path) {
28✔
156
        for (auto& element : *path) {
12✔
157
            if (auto subfield = mpark::get_if<InternString>(&element)) {
12✔
158
                os << "." << get_string(*subfield);
×
159
            }
×
160
            else if (auto index = mpark::get_if<uint32_t>(&element)) {
12✔
161
                os << "[" << *index << "]";
12✔
162
            }
12✔
163
            else {
×
164
                REALM_TERMINATE("Invalid path");
165
            }
×
166
        }
12✔
167
    }
12✔
168
    return os;
28✔
169
}
28✔
170

171
std::ostream& realm::sync::operator<<(std::ostream& os, const Changeset& changeset)
172
{
8✔
173
#if REALM_DEBUG // LCOV_EXCL_START
174
    changeset.print(os);
8✔
175
    return os;
8✔
176
#else
177
    return os << "[changeset with " << changeset.size() << " instructions]";
178
#endif
179
}
8✔
180

181

182
#if REALM_DEBUG // LCOV_EXCL_START
183
void Changeset::print(std::ostream& os) const
184
{
12✔
185
    Changeset::Printer printer{os};
12✔
186
    Changeset::Reflector reflector{printer, *this};
12✔
187
    os << std::left << std::setw(16) << "InternStrings";
12✔
188
    for (size_t i = 0; i < m_strings.size(); ++i) {
96✔
189
        os << i << "=\"" << get_string(m_strings.at(i)) << '"';
84✔
190
        if (i + 1 != m_strings.size())
84✔
191
            os << ", ";
72✔
192
    }
84✔
193
    os << "\n";
12✔
194

195
    reflector.visit_all();
12✔
196
}
12✔
197

198
void Changeset::print() const
199
{
×
200
    print(std::cerr);
×
201
}
×
202

203

204
void Changeset::verify() const
205
{
×
206
    for (size_t i = 0; i < m_strings.size(); ++i) {
×
207
        auto& range = m_strings.at(i);
×
208
        REALM_ASSERT(range.offset <= m_string_buffer.size());
×
209
        REALM_ASSERT(range.offset + range.size <= m_string_buffer.size());
×
210
    }
×
211

212
    auto verify_string_range = [&](StringBufferRange range) {
×
213
        REALM_ASSERT(range.offset <= m_string_buffer.size());
×
214
        REALM_ASSERT(range.offset + range.size <= m_string_buffer.size());
×
215
    };
×
216

217
    auto verify_intern_string = [&](InternString str) {
×
218
        auto range = get_intern_string(str);
×
219
        verify_string_range(range);
×
220
    };
×
221

222
    auto verify_key = [&](const Instruction::PrimaryKey& key) {
×
223
        mpark::visit(util::overload{[&](InternString str) {
×
224
                                        verify_intern_string(str);
×
225
                                    },
×
226
                                    [](auto&&) {}},
×
227
                     key);
×
228
    };
×
229

230
    auto verify_payload = [&](const Instruction::Payload& payload) {
×
231
        using Type = Instruction::Payload::Type;
×
232
        switch (payload.type) {
×
233
            case Type::String: {
×
234
                return verify_string_range(payload.data.str);
×
235
            }
×
236
            case Type::Binary: {
×
237
                return verify_string_range(payload.data.binary);
×
238
            }
×
239
            case Type::Link: {
×
240
                verify_intern_string(payload.data.link.target_table);
×
241
                return verify_key(payload.data.link.target);
×
242
            }
×
243
            default:
×
244
                return;
×
245
        }
×
246
    };
×
247

248
    auto verify_path = [&](const Instruction::Path& path) {
×
249
        for (auto& element : path) {
×
250
            mpark::visit(util::overload{[&](InternString str) {
×
251
                                            verify_intern_string(str);
×
252
                                        },
×
253
                                        [](auto&&) {}},
×
254
                         element);
×
255
        }
×
256
    };
×
257

258
    for (auto instr : *this) {
×
259
        if (!instr)
×
260
            continue;
×
261

262
        if (auto table_instr = instr->get_if<Instruction::TableInstruction>()) {
×
263
            verify_intern_string(table_instr->table);
×
264
            if (auto object_instr = instr->get_if<Instruction::ObjectInstruction>()) {
×
265
                verify_key(object_instr->object);
×
266

267
                if (auto path_instr = instr->get_if<Instruction::PathInstruction>()) {
×
268
                    verify_path(path_instr->path);
×
269
                }
×
270

271
                if (auto set_instr = instr->get_if<Instruction::Update>()) {
×
272
                    verify_payload(set_instr->value);
×
273
                }
×
274
                else if (auto insert_instr = instr->get_if<Instruction::ArrayInsert>()) {
×
275
                    verify_payload(insert_instr->value);
×
276
                }
×
277
            }
×
278
            else if (auto add_table_instr = instr->get_if<Instruction::AddTable>()) {
×
279
                mpark::visit(util::overload{
×
280
                                 [&](const Instruction::AddTable::TopLevelTable& spec) {
×
281
                                     REALM_ASSERT(is_valid_key_type(spec.pk_type));
×
282
                                     verify_intern_string(spec.pk_field);
×
283
                                 },
×
284
                                 [](const Instruction::AddTable::EmbeddedTable&) {},
×
285
                             },
×
286
                             add_table_instr->type);
×
287
            }
×
288
            else if (auto add_column_instr = instr->get_if<Instruction::AddColumn>()) {
×
289
                verify_intern_string(add_column_instr->field);
×
290
                if (add_column_instr->type == Instruction::Payload::Type::Link) {
×
291
                    verify_intern_string(add_column_instr->link_target_table);
×
292
                }
×
293
            }
×
294
            else if (auto erase_column_instr = instr->get_if<Instruction::EraseColumn>()) {
×
295
                verify_intern_string(erase_column_instr->field);
×
296
            }
×
297
        }
×
298
        else {
×
299
            REALM_TERMINATE("Corrupt instruction type");
300
        }
×
301
    }
×
302
}
×
303

304
void Changeset::Reflector::operator()(const Instruction::AddTable& p) const
305
{
20✔
306
    m_tracer.name("AddTable");
20✔
307
    table_instr(p);
20✔
308
    auto trace = util::overload{
20✔
309
        [&](const Instruction::AddTable::TopLevelTable& spec) {
20✔
310
            m_tracer.field("pk_field", spec.pk_field);
20✔
311
            m_tracer.field("pk_type", spec.pk_type);
20✔
312
            m_tracer.field("pk_nullable", spec.pk_nullable);
20✔
313
            m_tracer.field("is_asymmetric", spec.is_asymmetric);
20✔
314
        },
20✔
315
        [&](const Instruction::AddTable::EmbeddedTable&) {
20✔
316
            m_tracer.field("embedded", true);
×
317
        },
×
318
    };
20✔
319
    mpark::visit(trace, p.type);
20✔
320
}
20✔
321

322
void Changeset::Reflector::operator()(const Instruction::EraseTable& p) const
323
{
×
324
    m_tracer.name("EraseTable");
×
325
    table_instr(p);
×
326
}
×
327

328
void Changeset::Reflector::operator()(const Instruction::Update& p) const
329
{
×
330
    m_tracer.name("Update");
×
331
    path_instr(p);
×
332
    m_tracer.field("value", p.value);
×
333
    if (p.is_array_update()) {
×
334
        m_tracer.field("prior_size", p.prior_size);
×
335
    }
×
336
    else {
×
337
        m_tracer.field("default", p.is_default);
×
338
    }
×
339
}
×
340

341
void Changeset::Reflector::operator()(const Instruction::AddInteger& p) const
342
{
×
343
    m_tracer.name("AddInteger");
×
344
    path_instr(p);
×
345
    m_tracer.field("value", Instruction::Payload{p.value});
×
346
}
×
347

348
void Changeset::Reflector::operator()(const Instruction::CreateObject& p) const
349
{
16✔
350
    m_tracer.name("CreateObject");
16✔
351
    object_instr(p);
16✔
352
}
16✔
353

354
void Changeset::Reflector::operator()(const Instruction::EraseObject& p) const
355
{
×
356
    m_tracer.name("EraseObject");
×
357
    object_instr(p);
×
358
}
×
359

360
void Changeset::Reflector::operator()(const Instruction::ArrayInsert& p) const
361
{
12✔
362
    m_tracer.name("ArrayInsert");
12✔
363
    path_instr(p);
12✔
364
    m_tracer.field("value", p.value);
12✔
365
    m_tracer.field("prior_size", p.prior_size);
12✔
366
}
12✔
367

368
void Changeset::Reflector::operator()(const Instruction::ArrayMove& p) const
369
{
×
370
    m_tracer.name("ArrayMove");
×
371
    path_instr(p);
×
372
    m_tracer.field("ndx_2", p.ndx_2);
×
373
    m_tracer.field("prior_size", p.prior_size);
×
374
}
×
375

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

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

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

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

404
void Changeset::Reflector::operator()(const Instruction::AddColumn& p) const
405
{
48✔
406
    m_tracer.name("AddColumn");
48✔
407
    m_tracer.field("table", p.table);
48✔
408
    m_tracer.field("field", p.field);
48✔
409
    if (p.type != Instruction::Payload::Type::Null) {
48✔
410
        m_tracer.field("type", p.type);
48✔
411
    }
48✔
412
    else {
×
413
        m_tracer.field("type", Instruction::Payload::Type::Null);
×
414
    }
×
415
    m_tracer.field("nullable", p.nullable);
48✔
416
    m_tracer.field("collection_type", p.collection_type);
48✔
417
    if (p.type == Instruction::Payload::Type::Link) {
48✔
418
        m_tracer.field("target_table", p.link_target_table);
8✔
419
    }
8✔
420
    if (p.collection_type == Instruction::CollectionType::Dictionary) {
48✔
421
        m_tracer.field("key_type", p.key_type);
×
422
    }
×
423
}
48✔
424

425
void Changeset::Reflector::operator()(const Instruction::EraseColumn& p) const
426
{
×
427
    m_tracer.name("EraseColumn");
×
428
    m_tracer.field("table", p.table);
×
429
    m_tracer.field("field", p.field);
×
430
}
×
431

432
void Changeset::Reflector::table_instr(const Instruction::TableInstruction& p) const
433
{
20✔
434
    m_tracer.field("path", p.table);
20✔
435
}
20✔
436

437
void Changeset::Reflector::object_instr(const Instruction::ObjectInstruction& p) const
438
{
16✔
439
    m_tracer.path("path", p.table, p.object, util::none, nullptr);
16✔
440
}
16✔
441

442
void Changeset::Reflector::path_instr(const Instruction::PathInstruction& p) const
443
{
12✔
444
    m_tracer.path("path", p.table, p.object, p.field, &p.path);
12✔
445
}
12✔
446

447
void Changeset::Reflector::visit_all() const
448
{
12✔
449
    m_tracer.set_changeset(&m_changeset);
12✔
450
    for (auto instr : m_changeset) {
96✔
451
        if (!instr)
96✔
452
            continue;
×
453
        m_tracer.before_each();
96✔
454
        instr->visit(*this);
96✔
455
        m_tracer.after_each();
96✔
456
    }
96✔
457
    m_tracer.set_changeset(nullptr);
12✔
458
}
12✔
459

460
void Changeset::Printer::name(StringData n)
461
{
96✔
462
    pad_or_ellipsis(n, 16);
96✔
463
}
96✔
464

465
void Changeset::Printer::print_field(StringData name, std::string value)
466
{
400✔
467
    if (!m_first) {
400✔
468
        m_out << ", ";
304✔
469
    }
304✔
470
    m_first = false;
400✔
471
    m_out << name << "=" << value;
400✔
472
}
400✔
473

474
void Changeset::Printer::path(StringData name, InternString table, const Instruction::PrimaryKey& pk,
475
                              util::Optional<InternString> field, const Instruction::Path* path)
476
{
28✔
477
    std::stringstream ss;
28✔
478
    m_changeset->print_path(ss, table, pk, field, path);
28✔
479
    print_field(name, ss.str());
28✔
480
}
28✔
481

482
void Changeset::Printer::field(StringData n, InternString value)
483
{
144✔
484
    std::stringstream ss;
144✔
485
    ss << "\"" << m_changeset->get_string(value) << "\"";
144✔
486
    print_field(n, ss.str());
144✔
487
}
144✔
488

489
void Changeset::Printer::field(StringData n, Instruction::Payload::Type type)
490
{
68✔
491
    print_field(n, get_type_name(type));
68✔
492
}
68✔
493

494
void Changeset::Printer::field(StringData n, Instruction::CollectionType type)
495
{
48✔
496
    print_field(n, get_collection_type(type));
48✔
497
}
48✔
498

499
std::string Changeset::Printer::primary_key_to_string(const Instruction::PrimaryKey& key)
500
{
×
501
    auto convert = overload{
×
502
        [&](const mpark::monostate&) {
×
503
            return std::string("NULL");
×
504
        },
×
505
        [&](int64_t value) {
×
506
            std::stringstream ss;
×
507
            ss << value;
×
508
            return ss.str();
×
509
        },
×
510
        [&](InternString str) {
×
511
            std::stringstream ss;
×
512
            ss << "\"" << m_changeset->get_string(str) << "\"";
×
513
            return ss.str();
×
514
        },
×
515
        [&](GlobalKey key) {
×
516
            std::stringstream ss;
×
517
            ss << key;
×
518
            return ss.str();
×
519
        },
×
520
        [&](ObjectId id) {
×
521
            std::stringstream ss;
×
522
            ss << id;
×
523
            return ss.str();
×
524
        },
×
525
        [&](UUID uuid) {
×
526
            return uuid.to_string();
×
527
        },
×
528
    };
×
529
    return mpark::visit(convert, key);
×
530
}
×
531

532
void Changeset::Printer::field(StringData n, const Instruction::PrimaryKey& key)
533
{
×
534
    std::stringstream ss;
×
535
    ss << format_pk(m_changeset->get_key(key));
×
536
    print_field(n, ss.str());
×
537
}
×
538

539
void Changeset::Printer::field(StringData n, const Instruction::Payload& value)
540
{
12✔
541
    std::stringstream ss;
12✔
542
    m_changeset->print_value(ss, value);
12✔
543
    print_field(n, ss.str());
12✔
544
}
12✔
545

546
void Changeset::Printer::field(StringData n, const Instruction::Path& path)
547
{
×
548
    std::stringstream ss;
×
549
    ss << "[";
×
550
    bool first = true;
×
551
    for (auto& element : path) {
×
552
        if (!first) {
×
553
            ss << ".";
×
554
        }
×
555
        first = false;
×
556

557
        auto print = util::overload{
×
558
            [&](InternString field) {
×
559
                ss << m_changeset->get_string(field);
×
560
            },
×
561
            [&](uint32_t index) {
×
562
                ss << index;
×
563
            },
×
564
        };
×
565
        mpark::visit(print, element);
×
566
    }
×
567
    ss << "]";
×
568
    print_field(n, ss.str());
×
569
}
×
570

571
void Changeset::Printer::field(StringData n, uint32_t value)
572
{
100✔
573
    std::stringstream ss;
100✔
574
    ss << value;
100✔
575
    print_field(n, ss.str());
100✔
576
}
100✔
577

578
void Changeset::Printer::after_each()
579
{
96✔
580
    m_out << "\n";
96✔
581
    m_first = true;
96✔
582
}
96✔
583

584
void Changeset::Printer::pad_or_ellipsis(StringData s, int width) const
585
{
96✔
586
    // FIXME: Does not work with UTF-8.
587
    std::string str = s; // FIXME: StringData doesn't work with iomanip because it calls ios_base::write() directly
96✔
588
    if (str.size() > size_t(width)) {
96✔
589
        m_out << str.substr(0, width - 1) << "~";
×
590
    }
×
591
    else {
96✔
592
        m_out << std::left << std::setw(width) << str;
96✔
593
    }
96✔
594
}
96✔
595

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