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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM CUT coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

94.05
/src/realm/sync/changeset_encoder.cpp
1
#include <realm/sync/noinst/integer_codec.hpp>
2
#include <realm/sync/changeset_encoder.hpp>
3

4
using namespace realm;
5
using namespace realm::sync;
6

7
void ChangesetEncoder::operator()(const Instruction::AddTable& instr)
8
{
53,202✔
9
    auto spec = mpark::get_if<Instruction::AddTable::TopLevelTable>(&instr.type);
53,202✔
10
    const bool is_embedded = (spec == nullptr);
53,202✔
11
    Table::Type table_type;
53,202✔
12
    if (!is_embedded) {
53,202✔
13
        if (spec->is_asymmetric) {
51,338✔
14
            table_type = Table::Type::TopLevelAsymmetric;
96✔
15
        }
96✔
16
        else {
51,242✔
17
            table_type = Table::Type::TopLevel;
51,242✔
18
        }
51,242✔
19
    }
51,338✔
20
    else {
1,864✔
21
        table_type = Table::Type::Embedded;
1,864✔
22
    }
1,864✔
23
    auto table_type_int = static_cast<uint8_t>(table_type);
53,202✔
24
    append(Instruction::Type::AddTable, instr.table, table_type_int);
53,202✔
25
    if (!is_embedded) {
53,202✔
26
        append_value(spec->pk_field);
51,338✔
27
        append_value(spec->pk_type);
51,338✔
28
        append_value(spec->pk_nullable);
51,338✔
29
    }
51,338✔
30
}
53,202✔
31

32
void ChangesetEncoder::operator()(const Instruction::EraseTable& instr)
33
{
4,960✔
34
    append(Instruction::Type::EraseTable, instr.table);
4,960✔
35
}
4,960✔
36

37
void ChangesetEncoder::operator()(const Instruction::CreateObject& instr)
38
{
245,562✔
39
    append(Instruction::Type::CreateObject, instr.table, instr.object);
245,562✔
40
}
245,562✔
41

42
void ChangesetEncoder::operator()(const Instruction::EraseObject& instr)
43
{
76,666✔
44
    append(Instruction::Type::EraseObject, instr.table, instr.object);
76,666✔
45
}
76,666✔
46

47
void ChangesetEncoder::operator()(const Instruction::Update& instr)
48
{
842,566✔
49
    if (instr.is_array_update()) {
842,566✔
50
        append_path_instr(Instruction::Type::Update, instr, instr.value, instr.prior_size);
17,038✔
51
    }
17,038✔
52
    else {
825,528✔
53
        append_path_instr(Instruction::Type::Update, instr, instr.value, instr.is_default);
825,528✔
54
    }
825,528✔
55
}
842,566✔
56

57
// Appends sequence [value-type, dumb-value]
58
void ChangesetEncoder::append_value(const Instruction::Payload& payload)
59
{
1,078,016✔
60
    using Type = Instruction::Payload::Type;
1,078,016✔
61

524,774✔
62
    append_value(payload.type);
1,078,016✔
63
    const auto& data = payload.data;
1,078,016✔
64

524,774✔
65
    switch (payload.type) {
1,078,016✔
66
        case Type::GlobalKey: {
✔
67
            return append_value(data.key);
×
68
        }
×
69
        case Type::Int: {
750,302✔
70
            return append_value(data.integer);
750,302✔
71
        }
×
72
        case Type::Bool: {
2,150✔
73
            return append_value(data.boolean);
2,150✔
74
        }
×
75
        case Type::String: {
235,502✔
76
            return append_string(data.str);
235,502✔
77
        }
×
78
        case Type::Binary: {
19,584✔
79
            return append_string(data.binary);
19,584✔
80
        }
×
81
        case Type::Timestamp: {
12,774✔
82
            return append_value(data.timestamp);
12,774✔
83
        }
×
84
        case Type::Float: {
2,372✔
85
            return append_value(data.fnum);
2,372✔
86
        }
×
87
        case Type::Double: {
4,024✔
88
            return append_value(data.dnum);
4,024✔
89
        }
×
90
        case Type::Decimal: {
2,144✔
91
            return append_value(data.decimal);
2,144✔
92
        }
×
93
        case Type::ObjectId: {
13,384✔
94
            return append_value(data.object_id);
13,384✔
95
        }
×
96
        case Type::UUID: {
3,360✔
97
            return append_value(data.uuid);
3,360✔
98
        }
×
99
        case Type::Link: {
14,732✔
100
            return append_value(data.link);
14,732✔
101
        }
×
102
        case Type::Erased:
1,416✔
103
            [[fallthrough]];
1,416✔
104
        case Type::Set:
1,464✔
105
            [[fallthrough]];
1,464✔
106
        case Type::List:
2,124✔
107
            [[fallthrough]];
2,124✔
108
        case Type::Dictionary:
2,436✔
109
            [[fallthrough]];
2,436✔
110
        case Type::ObjectValue:
15,914✔
111
            [[fallthrough]];
15,914✔
112
        case Type::Null:
17,760✔
113
            // The payload type does not carry additional data.
8,814✔
114
            return;
17,760✔
UNCOV
115
    }
×
UNCOV
116
    REALM_TERMINATE("Invalid payload type.");
×
UNCOV
117
}
×
118

119
void ChangesetEncoder::append_value(Instruction::Payload::Type type)
120
{
2,686,694✔
121
    append_value(int64_t(type));
2,686,694✔
122
}
2,686,694✔
123

124
void ChangesetEncoder::append_value(Instruction::AddColumn::CollectionType type)
125
{
109,676✔
126
    append_value(uint8_t(type));
109,676✔
127
}
109,676✔
128

129
void ChangesetEncoder::append_value(const Instruction::Payload::Link& link)
130
{
14,732✔
131
    append_value(link.target_table);
14,732✔
132
    append_value(link.target);
14,732✔
133
}
14,732✔
134

135
void ChangesetEncoder::append_value(const Instruction::PrimaryKey& pk)
136
{
1,436,610✔
137
    using Type = Instruction::Payload::Type;
1,436,610✔
138
    auto append = util::overload{
1,436,610✔
139
        [&](mpark::monostate) {
696,926✔
140
            append_value(Type::Null);
340✔
141
        },
340✔
142
        [&](int64_t value) {
1,241,932✔
143
            append_value(Type::Int);
1,079,080✔
144
            append_value(value);
1,079,080✔
145
        },
1,079,080✔
146
        [&](InternString str) {
725,868✔
147
            // Note: Contextual difference. In payloads, Type::String denotes a
26,702✔
148
            // StringBufferRange, but here it denotes to an InternString.
26,702✔
149
            append_value(Type::String);
55,814✔
150
            append_value(str);
55,814✔
151
        },
55,814✔
152
        [&](GlobalKey key) {
696,810✔
153
            append_value(Type::GlobalKey);
108✔
154
            append_value(key);
108✔
155
        },
108✔
156
        [&](ObjectId id) {
861,854✔
157
            append_value(Type::ObjectId);
300,920✔
158
            append_value(id);
300,920✔
159
        },
300,920✔
160
        [&](UUID uuid) {
696,830✔
161
            append_value(Type::UUID);
148✔
162
            append_value(uuid);
148✔
163
        },
148✔
164
    };
1,436,610✔
165
    mpark::visit(std::move(append), pk);
1,436,610✔
166
}
1,436,610✔
167

168
void ChangesetEncoder::append_value(const Instruction::Path& path)
169
{
1,099,898✔
170
    append_value(uint32_t(path.size()));
1,099,898✔
171
    for (auto& element : path) {
751,540✔
172
        // Integer path elements are encoded as their integer values.
213,880✔
173
        // String path elements are encoded as [-1, intern_string_id].
213,880✔
174
        if (auto index = mpark::get_if<uint32_t>(&element)) {
430,036✔
175
            append_value(int64_t(*index));
282,434✔
176
        }
282,434✔
177
        else if (auto name = mpark::get_if<InternString>(&element)) {
147,602✔
178
            // Since indices cannot be negative, use -1 to indicate that the path element is a
73,660✔
179
            // string.
73,660✔
180
            append_value(int64_t(-1));
147,602✔
181
            append_value(*name);
147,602✔
182
        }
147,602✔
183
    }
430,036✔
184
}
1,099,898✔
185

186
void ChangesetEncoder::operator()(const Instruction::AddInteger& instr)
187
{
6,580✔
188
    append_path_instr(Instruction::Type::AddInteger, instr, instr.value);
6,580✔
189
}
6,580✔
190

191
void ChangesetEncoder::operator()(const Instruction::AddColumn& instr)
192
{
109,676✔
193
    bool is_dictionary = (instr.collection_type == Instruction::AddColumn::CollectionType::Dictionary);
109,676✔
194
    // Mixed columns are always nullable.
52,802✔
195
    REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable || is_dictionary);
109,676!
196
    append(Instruction::Type::AddColumn, instr.table, instr.field, instr.type, instr.nullable, instr.collection_type);
109,676✔
197

52,802✔
198
    if (instr.type == Instruction::Payload::Type::Link) {
109,676✔
199
        append_value(instr.link_target_table);
8,652✔
200
    }
8,652✔
201
    if (is_dictionary) {
109,676✔
202
        append_value(instr.key_type);
11,812✔
203
    }
11,812✔
204
}
109,676✔
205

206
void ChangesetEncoder::operator()(const Instruction::EraseColumn& instr)
207
{
12✔
208
    append(Instruction::Type::EraseColumn, instr.table, instr.field);
12✔
209
}
12✔
210

211
void ChangesetEncoder::operator()(const Instruction::ArrayInsert& instr)
212
{
213,900✔
213
    append_path_instr(Instruction::Type::ArrayInsert, instr, instr.value, instr.prior_size);
213,900✔
214
}
213,900✔
215

216
void ChangesetEncoder::operator()(const Instruction::ArrayMove& instr)
217
{
364✔
218
    append_path_instr(Instruction::Type::ArrayMove, instr, instr.ndx_2, instr.prior_size);
364✔
219
}
364✔
220

221
void ChangesetEncoder::operator()(const Instruction::ArrayErase& instr)
222
{
14,044✔
223
    append_path_instr(Instruction::Type::ArrayErase, instr, instr.prior_size);
14,044✔
224
}
14,044✔
225

226
void ChangesetEncoder::operator()(const Instruction::Clear& instr)
227
{
842✔
228
    uint32_t prior_size = 0; // Ignored
842✔
229
    append_path_instr(Instruction::Type::Clear, instr, prior_size);
842✔
230
}
842✔
231

232
void ChangesetEncoder::operator()(const Instruction::SetInsert& instr)
233
{
19,636✔
234
    append_path_instr(Instruction::Type::SetInsert, instr, instr.value);
19,636✔
235
}
19,636✔
236

237
void ChangesetEncoder::operator()(const Instruction::SetErase& instr)
238
{
2,308✔
239
    append_path_instr(Instruction::Type::SetErase, instr, instr.value);
2,308✔
240
}
2,308✔
241

242
InternString ChangesetEncoder::intern_string(StringData str)
243
{
1,012,764✔
244
    auto it = m_intern_strings_rev.find(static_cast<std::string_view>(str));
1,012,764✔
245
    if (it == m_intern_strings_rev.end()) {
1,012,764✔
246
        size_t index = m_intern_strings_rev.size();
500,436✔
247
        // FIXME: Assert might be able to be removed after refactoring of changeset_parser types?
239,808✔
248
        REALM_ASSERT_RELEASE_EX(index <= std::numeric_limits<uint32_t>::max(), index);
500,436✔
249
        bool inserted;
500,436✔
250
        std::tie(it, inserted) = m_intern_strings_rev.insert({std::string{str}, uint32_t(index)});
500,436✔
251
        REALM_ASSERT_RELEASE_EX(inserted, str);
500,436✔
252

239,808✔
253
        StringBufferRange range = add_string_range(str);
500,436✔
254
        set_intern_string(uint32_t(index), range);
500,436✔
255
    }
500,436✔
256

492,894✔
257
    return InternString{it->second};
1,012,764✔
258
}
1,012,764✔
259

260
void ChangesetEncoder::set_intern_string(uint32_t index, StringBufferRange range)
261
{
1,110,012✔
262
    // Emit InternString metainstruction:
536,382✔
263
    append_int(uint64_t(InstrTypeInternString));
1,110,012✔
264
    append_int(index);
1,110,012✔
265
    append_string(range);
1,110,012✔
266
}
1,110,012✔
267

268
StringBufferRange ChangesetEncoder::add_string_range(StringData data)
269
{
1,030,248✔
270
    m_string_range = static_cast<std::string_view>(data);
1,030,248✔
271
    REALM_ASSERT(data.size() <= std::numeric_limits<uint32_t>::max());
1,030,248✔
272
    return StringBufferRange{0, uint32_t(data.size())};
1,030,248✔
273
}
1,030,248✔
274

275
void ChangesetEncoder::append_bytes(const void* bytes, size_t size)
276
{
17,366,242✔
277
    // FIXME: It would be better to move ownership of `m_buffer` to the caller,
8,410,116✔
278
    // potentially reducing the number of allocations to zero (amortized).
8,410,116✔
279
    m_buffer.reserve(1024); // lower the amount of reallocations
17,366,242✔
280
    m_buffer.append(static_cast<const char*>(bytes), size);
17,366,242✔
281
}
17,366,242✔
282

283
void ChangesetEncoder::append_string(StringBufferRange str)
284
{
1,365,026✔
285
    REALM_ASSERT(str.offset + str.size <= m_string_range.size());
1,365,026✔
286
    append_value(uint64_t(str.size));
1,365,026✔
287
    append_bytes(m_string_range.data() + str.offset, str.size);
1,365,026✔
288
}
1,365,026✔
289

290
template <class... Args>
291
void ChangesetEncoder::append(Instruction::Type t, Args&&... args)
292
{
490,078✔
293
    append_value(uint8_t(t));
490,078✔
294
    int unpack[] = {0, (append_value(args), 0)...};
490,078✔
295
    static_cast<void>(unpack);
490,078✔
296
}
490,078✔
297

298
template <class... Args>
299
void ChangesetEncoder::append_path_instr(Instruction::Type t, const Instruction::PathInstruction& instr,
300
                                         Args&&... args)
301
{
1,099,912✔
302
    append_value(uint8_t(t));
1,099,912✔
303
    append_value(instr.table);
1,099,912✔
304
    append_value(instr.object);
1,099,912✔
305
    append_value(instr.field);
1,099,912✔
306
    append_value(instr.path);
1,099,912✔
307
    (append_value(std::forward<Args>(args)), ...);
1,099,912✔
308
}
1,099,912✔
309

310
template <class T>
311
void ChangesetEncoder::append_int(T integer)
312
{
15,711,032✔
313
    char buffer[_impl::encode_int_max_bytes<T>()];
15,711,032✔
314
    std::size_t n = _impl::encode_int(buffer, integer);
15,711,032✔
315
    append_bytes(buffer, n);
15,711,032✔
316
}
15,711,032✔
317

318
void ChangesetEncoder::append_value(DataType type)
UNCOV
319
{
×
UNCOV
320
    append_value(uint64_t(type));
×
UNCOV
321
}
×
322

323
void ChangesetEncoder::append_value(bool v)
324
{
990,404✔
325
    // Reduce template instantiations of append_int
479,508✔
326
    append_value(uint8_t(v));
990,404✔
327
}
990,404✔
328

329
void ChangesetEncoder::append_value(uint8_t integer)
330
{
2,742,120✔
331
    // Reduce template instantiations of append_int
1,329,060✔
332
    append_value(uint64_t(integer));
2,742,120✔
333
}
2,742,120✔
334

335
void ChangesetEncoder::append_value(uint32_t integer)
336
{
4,420,132✔
337
    // Reduce template instantiations of append_int
2,151,114✔
338
    append_value(uint64_t(integer));
4,420,132✔
339
}
4,420,132✔
340

341
void ChangesetEncoder::append_value(uint64_t integer)
342
{
8,516,692✔
343
    append_int(integer);
8,516,692✔
344
}
8,516,692✔
345

346
void ChangesetEncoder::append_value(int64_t integer)
347
{
4,976,526✔
348
    append_int(integer);
4,976,526✔
349
}
4,976,526✔
350

351
void ChangesetEncoder::append_value(float number)
352
{
2,372✔
353
    append_bytes(&number, sizeof(number));
2,372✔
354
}
2,372✔
355

356
void ChangesetEncoder::append_value(double number)
357
{
4,024✔
358
    append_bytes(&number, sizeof(number));
4,024✔
359
}
4,024✔
360

361
void ChangesetEncoder::append_value(InternString str)
362
{
3,076,838✔
363
    REALM_ASSERT(str != InternString::npos);
3,076,838✔
364
    append_value(str.value);
3,076,838✔
365
}
3,076,838✔
366

367
void ChangesetEncoder::append_value(GlobalKey oid)
368
{
108✔
369
    append_value(oid.hi());
108✔
370
    append_value(oid.lo());
108✔
371
}
108✔
372

373
void ChangesetEncoder::append_value(Timestamp timestamp)
374
{
12,774✔
375
    append_value(timestamp.get_seconds());
12,774✔
376
    append_value(int64_t(timestamp.get_nanoseconds()));
12,774✔
377
}
12,774✔
378

379
void ChangesetEncoder::append_value(ObjectId id)
380
{
314,314✔
381
    append_bytes(&id, sizeof(id));
314,314✔
382
}
314,314✔
383

384
void ChangesetEncoder::append_value(UUID id)
385
{
3,508✔
386
    const auto bytes = id.to_bytes();
3,508✔
387
    append_bytes(bytes.data(), bytes.size());
3,508✔
388
}
3,508✔
389

390
void ChangesetEncoder::append_value(Decimal128 id)
391
{
2,144✔
392
    Decimal128::Bid128 cx;
2,144✔
393
    int exp;
2,144✔
394
    bool sign;
2,144✔
395
    id.unpack(cx, exp, sign);
2,144✔
396
    constexpr int max_bytes = 17; // 113 bits / 7
2,144✔
397
    char buffer[max_bytes];
2,144✔
398
    _impl::Bid128 tmp;
2,144✔
399
    memcpy(&tmp, &cx, sizeof(Decimal128::Bid128));
2,144✔
400
    auto n = _impl::encode_int(buffer, tmp);
2,144✔
401
    REALM_ASSERT(n <= max_bytes);
2,144✔
402
    append_bytes(buffer, n);
2,144✔
403
    append_value(int64_t(exp));
2,144✔
404
    append_value(sign);
2,144✔
405
}
2,144✔
406

407
auto ChangesetEncoder::release() noexcept -> Buffer
408
{
8✔
409
    m_intern_strings_rev.clear();
8✔
410
    return std::move(m_buffer);
8✔
411
}
8✔
412

413
void ChangesetEncoder::reset() noexcept
414
{
747,512✔
415
    m_intern_strings_rev.clear();
747,512✔
416
    m_buffer.clear();
747,512✔
417
}
747,512✔
418

419
void ChangesetEncoder::encode_single(const Changeset& log)
420
{
573,042✔
421
    // Checking if the log is empty avoids serialized interned strings in a
286,004✔
422
    // changeset where all meaningful instructions have been discarded due to
286,004✔
423
    // merge or compaction.
286,004✔
424
    if (!log.empty()) {
573,042✔
425
        add_string_range(log.string_data());
332,392✔
426
        const auto& strings = log.interned_strings();
332,392✔
427
        for (size_t i = 0; i < strings.size(); ++i) {
942,026✔
428
            set_intern_string(uint32_t(i), strings[i]); // Throws
609,634✔
429
        }
609,634✔
430
        for (auto instr : log) {
757,804✔
431
            if (!instr)
757,804✔
432
                continue;
2,198✔
433
            (*this)(*instr); // Throws
755,606✔
434
        }
755,606✔
435
    }
332,392✔
436
}
573,042✔
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