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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM CUT coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 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,463,578✔
47
    return mpark::visit(overload{
1,463,578✔
48
                            [this](InternString str) -> PrimaryKey {
1,463,578✔
49
                                return get_string(str);
96,104✔
50
                            },
96,104✔
51
                            [](auto otherwise) -> PrimaryKey {
1,463,578✔
52
                                return otherwise;
1,366,338✔
53
                            },
1,366,338✔
54
                        },
1,463,578✔
55
                        key);
1,463,578✔
56
}
1,463,578✔
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
{
8✔
185
    Changeset::Printer printer{os};
8✔
186
    Changeset::Reflector reflector{printer, *this};
8✔
187
    os << std::left << std::setw(16) << "InternStrings";
8✔
188
    for (size_t i = 0; i < m_strings.size(); ++i) {
48✔
189
        os << i << "=\"" << get_string(m_strings.at(i)) << '"';
40✔
190
        if (i + 1 != m_strings.size())
40✔
191
            os << ", ";
32✔
192
    }
40✔
193
    os << "\n";
8✔
194

195
    reflector.visit_all();
8✔
196
}
8✔
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
{
8✔
306
    m_tracer.name("AddTable");
8✔
307
    table_instr(p);
8✔
308
    auto trace = util::overload{
8✔
309
        [&](const Instruction::AddTable::TopLevelTable& spec) {
8✔
310
            m_tracer.field("pk_field", spec.pk_field);
8✔
311
            m_tracer.field("pk_type", spec.pk_type);
8✔
312
            m_tracer.field("pk_nullable", spec.pk_nullable);
8✔
313
            m_tracer.field("is_asymmetric", spec.is_asymmetric);
8✔
314
        },
8✔
315
        [&](const Instruction::AddTable::EmbeddedTable&) {
8✔
316
            m_tracer.field("embedded", true);
×
317
        },
×
318
    };
8✔
319
    mpark::visit(trace, p.type);
8✔
320
}
8✔
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
{
4✔
406
    m_tracer.name("AddColumn");
4✔
407
    m_tracer.field("table", p.table);
4✔
408
    m_tracer.field("field", p.field);
4✔
409
    if (p.type != Instruction::Payload::Type::Null) {
4✔
410
        m_tracer.field("type", p.type);
4✔
411
    }
4✔
412
    else {
×
413
        m_tracer.field("type", Instruction::Payload::Type::Null);
×
414
    }
×
415
    m_tracer.field("nullable", p.nullable);
4✔
416
    m_tracer.field("collection_type", p.collection_type);
4✔
417
    if (p.type == Instruction::Payload::Type::Link) {
4✔
418
        m_tracer.field("target_table", p.link_target_table);
4✔
419
    }
4✔
420
    if (p.collection_type == Instruction::CollectionType::Dictionary) {
4✔
421
        m_tracer.field("key_type", p.key_type);
×
422
    }
×
423
}
4✔
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
{
8✔
434
    m_tracer.field("path", p.table);
8✔
435
}
8✔
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
{
8✔
449
    m_tracer.set_changeset(&m_changeset);
8✔
450
    for (auto instr : m_changeset) {
40✔
451
        if (!instr)
40✔
452
            continue;
×
453
        m_tracer.before_each();
40✔
454
        instr->visit(*this);
40✔
455
        m_tracer.after_each();
40✔
456
    }
40✔
457
    m_tracer.set_changeset(nullptr);
8✔
458
}
8✔
459

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

465
void Changeset::Printer::print_field(StringData name, std::string value)
466
{
116✔
467
    if (!m_first) {
116✔
468
        m_out << ", ";
76✔
469
    }
76✔
470
    m_first = false;
116✔
471
    m_out << name << "=" << value;
116✔
472
}
116✔
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
{
28✔
484
    std::stringstream ss;
28✔
485
    ss << "\"" << m_changeset->get_string(value) << "\"";
28✔
486
    print_field(n, ss.str());
28✔
487
}
28✔
488

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

494
void Changeset::Printer::field(StringData n, Instruction::CollectionType type)
495
{
4✔
496
    print_field(n, get_collection_type(type));
4✔
497
}
4✔
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
{
32✔
573
    std::stringstream ss;
32✔
574
    ss << value;
32✔
575
    print_field(n, ss.str());
32✔
576
}
32✔
577

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

584
void Changeset::Printer::pad_or_ellipsis(StringData s, int width) const
585
{
40✔
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
40✔
588
    if (str.size() > size_t(width)) {
40✔
589
        m_out << str.substr(0, width - 1) << "~";
×
590
    }
×
591
    else {
40✔
592
        m_out << std::left << std::setw(width) << str;
40✔
593
    }
40✔
594
}
40✔
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