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

realm / realm-core / 2036

14 Feb 2024 05:06PM UTC coverage: 91.881% (+0.03%) from 91.851%
2036

push

Evergreen

web-flow
Mitigate races in accessing `m_initated` and `m_finalized` in various REALM_ASSERTs (#7338)

93026 of 171514 branches covered (54.24%)

46 of 48 new or added lines in 1 file covered. (95.83%)

66 existing lines in 10 files now uncovered.

235483 of 256292 relevant lines covered (91.88%)

6194496.88 hits per line

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

92.84
/src/realm/sync/instruction_replication.cpp
1
#include <realm/sync/instruction_replication.hpp>
2
#include <realm/transaction.hpp>
3
#include <realm/sync/transform.hpp> // TransformError
4
#include <realm/list.hpp>
5

6
namespace realm {
7
namespace sync {
8

9
void SyncReplication::reset()
10
{
278,280✔
11
    m_encoder.reset();
278,280✔
12

138,470✔
13
    m_last_table = nullptr;
278,280✔
14
    m_last_object = ObjKey();
278,280✔
15
    m_last_field = ColKey();
278,280✔
16
    m_last_class_name = InternString::npos;
278,280✔
17
    m_last_primary_key = Instruction::PrimaryKey();
278,280✔
18
    m_last_field_name = InternString::npos;
278,280✔
19
}
278,280✔
20

21
void SyncReplication::do_initiate_transact(Group& group, version_type current_version, bool history_updated)
22
{
236,510✔
23
    Replication::do_initiate_transact(group, current_version, history_updated);
236,510✔
24
    m_transaction = dynamic_cast<Transaction*>(&group); // FIXME: Is this safe?
236,510✔
25
    m_write_validator = make_write_validator(*m_transaction);
236,510✔
26
    reset();
236,510✔
27
}
236,510✔
28

29
Instruction::Payload SyncReplication::as_payload(Mixed value)
30
{
543,466✔
31
    if (value.is_null()) {
543,466✔
32
        return Instruction::Payload{};
×
33
    }
×
34

264,360✔
35
    switch (value.get_type()) {
543,466✔
36
        case type_Int: {
292,840✔
37
            return Instruction::Payload{value.get<int64_t>()};
292,840✔
38
        }
×
39
        case type_Bool: {
2,118✔
40
            return Instruction::Payload{value.get<bool>()};
2,118✔
41
        }
×
42
        case type_Float: {
2,240✔
43
            return Instruction::Payload{value.get<float>()};
2,240✔
44
        }
×
45
        case type_Double: {
3,112✔
46
            return Instruction::Payload{value.get<double>()};
3,112✔
47
        }
×
48
        case type_String: {
206,076✔
49
            auto str = value.get<StringData>();
206,076✔
50
            auto range = m_encoder.add_string_range(str);
206,076✔
51
            return Instruction::Payload{range};
206,076✔
52
        }
×
53
        case type_Binary: {
7,984✔
54
            auto binary = value.get<BinaryData>();
7,984✔
55
            auto range = m_encoder.add_string_range(StringData{binary.data(), binary.size()});
7,984✔
56
            const bool is_binary = true;
7,984✔
57
            return Instruction::Payload{range, is_binary};
7,984✔
58
        }
×
59
        case type_Timestamp: {
10,016✔
60
            return Instruction::Payload{value.get<Timestamp>()};
10,016✔
61
        }
×
62
        case type_Decimal: {
2,144✔
63
            return Instruction::Payload{value.get<Decimal128>()};
2,144✔
64
        }
×
65
        case type_ObjectId: {
13,616✔
66
            return Instruction::Payload{value.get<ObjectId>()};
13,616✔
67
        }
×
68
        case type_UUID: {
3,328✔
69
            return Instruction::Payload{value.get<UUID>()};
3,328✔
70
        }
×
71
        case type_TypedLink:
✔
72
            [[fallthrough]];
×
73
        case type_Link: {
✔
74
            REALM_TERMINATE("as_payload() needs table/collection for links");
75
            break;
×
76
        }
×
77
        case type_Mixed:
✔
78
            [[fallthrough]];
×
79
        case type_LinkList: {
✔
80
            REALM_TERMINATE("Invalid payload type");
81
            break;
×
82
        }
×
83
    }
×
84
    return Instruction::Payload{};
×
85
}
×
86

87
Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value)
88
{
229,822✔
89
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
229,822✔
90
}
229,822✔
91

92
Instruction::Payload SyncReplication::as_payload(const Table& table, ColKey col_key, Mixed value)
93
{
574,808✔
94
    if (value.is_null()) {
574,808✔
95
        // FIXME: `Mixed::get_type()` asserts on null.
518✔
96
        return Instruction::Payload{};
1,058✔
97
    }
1,058✔
98

279,258✔
99
    if (value.is_type(type_Link)) {
573,750✔
100
        ConstTableRef target_table = table.get_link_target(col_key);
17,002✔
101
        if (target_table->is_embedded()) {
17,002✔
102
            // FIXME: Include target table name to support Mixed of Embedded Objects.
4,770✔
103
            return Instruction::Payload::ObjectValue{};
9,598✔
104
        }
9,598✔
105

3,556✔
106
        Instruction::Payload::Link link;
7,404✔
107
        link.target_table = emit_class_name(*target_table);
7,404✔
108
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
7,404✔
109
        return Instruction::Payload{link};
7,404✔
110
    }
7,404✔
111
    else if (value.is_type(type_TypedLink)) {
556,748✔
112
        auto obj_link = value.get<ObjLink>();
13,272✔
113
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
13,272✔
114
        REALM_ASSERT(target_table);
13,272✔
115

6,564✔
116
        if (target_table->is_embedded()) {
13,272✔
117
            ConstTableRef static_target_table = table.get_link_target(col_key);
2,388✔
118

1,182✔
119
            if (static_target_table != target_table)
2,388✔
120
                REALM_TERMINATE("Dynamically typed embedded objects not supported yet.");
121
            return Instruction::Payload::ObjectValue{};
2,388✔
122
        }
2,388✔
123

5,382✔
124
        Instruction::Payload::Link link;
10,884✔
125
        link.target_table = emit_class_name(*target_table);
10,884✔
126
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
10,884✔
127
        return Instruction::Payload{link};
10,884✔
128
    }
10,884✔
129
    else {
543,476✔
130
        return as_payload(value);
543,476✔
131
    }
543,476✔
132
}
573,750✔
133

134
InternString SyncReplication::emit_class_name(StringData table_name)
135
{
371,850✔
136
    return m_encoder.intern_string(Group::table_name_to_class_name(table_name));
371,850✔
137
}
371,850✔
138

139
InternString SyncReplication::emit_class_name(const Table& table)
140
{
331,994✔
141
    return emit_class_name(table.get_name());
331,994✔
142
}
331,994✔
143

144
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
145
{
151,770✔
146
    using Type = Instruction::Payload::Type;
151,770✔
147
    switch (type) {
151,770✔
148
        case type_Int:
43,862✔
149
            return Type::Int;
43,862✔
150
        case type_Bool:
4,240✔
151
            return Type::Bool;
4,240✔
152
        case type_String:
36,808✔
153
            return Type::String;
36,808✔
154
        case type_Binary:
4,424✔
155
            return Type::Binary;
4,424✔
156
        case type_Timestamp:
5,278✔
157
            return Type::Timestamp;
5,278✔
158
        case type_Float:
4,436✔
159
            return Type::Float;
4,436✔
160
        case type_Double:
4,448✔
161
            return Type::Double;
4,448✔
162
        case type_Decimal:
4,240✔
163
            return Type::Decimal;
4,240✔
164
        case type_Link:
6,316✔
165
            return Type::Link;
6,316✔
166
        case type_LinkList:
3,520✔
167
            return Type::Link;
3,520✔
168
        case type_TypedLink:
4✔
169
            return Type::Link;
4✔
170
        case type_ObjectId:
23,462✔
171
            return Type::ObjectId;
23,462✔
172
        case type_UUID:
4,384✔
173
            return Type::UUID;
4,384✔
174
        case type_Mixed:
6,348✔
175
            return Type::Null;
6,348✔
176
    }
×
177
    unsupported_instruction();
×
178
    return Type::Int; // Make compiler happy
×
179
}
×
180

181
void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type)
182
{
21,058✔
183
    Replication::add_class(tk, name, table_type);
21,058✔
184

10,338✔
185
    bool is_class = m_transaction->table_is_public(tk);
21,058✔
186

10,338✔
187
    if (is_class && !m_short_circuit) {
21,058✔
188
        Instruction::AddTable instr;
1,780✔
189
        instr.table = emit_class_name(name);
1,780✔
190
        if (table_type == Table::Type::Embedded) {
1,780✔
191
            instr.type = Instruction::AddTable::EmbeddedTable{};
1,724✔
192
        }
1,724✔
193
        else {
56✔
194
            auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"?
56✔
195
            const bool is_nullable = false;
56✔
196
            bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
56✔
197
            instr.type = Instruction::AddTable::TopLevelTable{
56✔
198
                field,
56✔
199
                Instruction::Payload::Type::GlobalKey,
56✔
200
                is_nullable,
56✔
201
                is_asymmetric,
56✔
202
            };
56✔
203
        }
56✔
204
        emit(instr);
1,780✔
205
    }
1,780✔
206
}
21,058✔
207

208
void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field,
209
                                                 bool nullable, Table::Type table_type)
210
{
51,798✔
211
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
51,798✔
212

25,648✔
213
    bool is_class = m_transaction->table_is_public(tk);
51,798✔
214

25,648✔
215
    if (is_class && !m_short_circuit) {
51,798✔
216
        Instruction::AddTable instr;
38,004✔
217
        instr.table = emit_class_name(name);
38,004✔
218
        auto field = m_encoder.intern_string(pk_field);
38,004✔
219
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
38,004✔
220
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
38,004✔
221
        if (!is_valid_key_type(spec.pk_type)) {
38,004✔
222
            unsupported_instruction();
×
223
        }
×
224
        instr.type = std::move(spec);
38,004✔
225
        emit(instr);
38,004✔
226
    }
38,004✔
227
}
51,798✔
228

229
void SyncReplication::create_object(const Table* table, GlobalKey oid)
230
{
16,090✔
231
    if (table->is_embedded()) {
16,090✔
232
        unsupported_instruction(); // FIXME: TODO
×
233
    }
×
234

7,896✔
235
    Replication::create_object(table, oid);
16,090✔
236
    if (select_table(*table)) {
16,090✔
237
        if (table->get_primary_key_column()) {
72✔
238
            // Trying to create object without a primary key in a table that
239
            // has a primary key column.
240
            unsupported_instruction();
×
241
        }
×
242
        Instruction::CreateObject instr;
72✔
243
        instr.table = m_last_class_name;
72✔
244
        instr.object = oid;
72✔
245
        emit(instr);
72✔
246
    }
72✔
247
}
16,090✔
248

249
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
250
{
476,498✔
251
    if (value.is_null()) {
476,498✔
252
        return mpark::monostate{};
300✔
253
    }
300✔
254
    else if (value.get_type() == type_Int) {
476,198✔
255
        return value.get<int64_t>();
360,188✔
256
    }
360,188✔
257
    else if (value.get_type() == type_String) {
116,010✔
258
        return m_encoder.intern_string(value.get<StringData>());
954✔
259
    }
954✔
260
    else if (value.get_type() == type_ObjectId) {
115,056✔
261
        return value.get<ObjectId>();
115,000✔
262
    }
115,000✔
263
    else if (value.get_type() == type_UUID) {
56✔
264
        return value.get<UUID>();
52✔
265
    }
52✔
266
    else {
4✔
267
        // Unsupported primary key type.
2✔
268
        unsupported_instruction();
4✔
269
    }
4✔
270
}
476,498✔
271

272
void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value)
273
{
141,978✔
274
    if (table->is_embedded()) {
141,978✔
275
        // Trying to create an object with a primary key in an embedded table.
276
        unsupported_instruction();
×
277
    }
×
278

63,446✔
279
    Replication::create_object_with_primary_key(table, oid, value);
141,978✔
280
    if (select_table(*table)) {
141,978✔
281
        if (m_write_validator) {
88,614✔
282
            m_write_validator(*table);
1,606✔
283
        }
1,606✔
284

37,362✔
285
        auto col = table->get_primary_key_column();
88,614✔
286
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
88,614✔
287
            Instruction::CreateObject instr;
88,608✔
288
            instr.table = m_last_class_name;
88,608✔
289
            instr.object = as_primary_key(value);
88,608✔
290
            emit(instr);
88,608✔
291
        }
88,608✔
292
        else {
6✔
293
            // Trying to create object with primary key in table without a
2✔
294
            // primary key column, or with wrong primary key type.
2✔
295
            unsupported_instruction();
6✔
296
        }
6✔
297
    }
88,614✔
298
}
141,978✔
299

300

301
void SyncReplication::erase_class(TableKey table_key, size_t num_tables)
302
{
248✔
303
    Replication::erase_class(table_key, num_tables);
248✔
304

124✔
305
    StringData table_name = m_transaction->get_table_name(table_key);
248✔
306

124✔
307
    bool is_class = m_transaction->table_is_public(table_key);
248✔
308

124✔
309
    if (is_class && !m_short_circuit) {
248✔
310
        Instruction::EraseTable instr;
68✔
311
        instr.table = emit_class_name(table_name);
68✔
312
        emit(instr);
68✔
313
    }
68✔
314

124✔
315
    m_last_table = nullptr;
248✔
316
}
248✔
317

318
void SyncReplication::rename_class(TableKey, StringData)
319
{
×
320
    unsupported_instruction();
×
321
}
×
322

323
void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name,
324
                                    Table* target_table)
325
{
160,746✔
326
    Replication::insert_column(table, col_key, type, name, target_table);
160,746✔
327
    using CollectionType = Instruction::AddColumn::CollectionType;
160,746✔
328

78,272✔
329
    if (select_table(*table)) {
160,746✔
330
        Instruction::AddColumn instr;
90,382✔
331
        instr.table = m_last_class_name;
90,382✔
332
        instr.field = m_encoder.intern_string(name);
90,382✔
333
        instr.nullable = col_key.is_nullable();
90,382✔
334
        instr.type = get_payload_type(type);
90,382✔
335

43,752✔
336
        if (col_key.is_list()) {
90,382✔
337
            instr.collection_type = CollectionType::List;
14,880✔
338
        }
14,880✔
339
        else if (col_key.is_dictionary()) {
75,502✔
340
            instr.collection_type = CollectionType::Dictionary;
12,100✔
341
            auto key_type = table->get_dictionary_key_type(col_key);
12,100✔
342
            REALM_ASSERT(key_type == type_String);
12,100✔
343
            instr.key_type = get_payload_type(key_type);
12,100✔
344
        }
12,100✔
345
        else if (col_key.is_set()) {
63,402✔
346
            instr.collection_type = CollectionType::Set;
11,284✔
347
            auto value_type = table->get_column_type(col_key);
11,284✔
348
            REALM_ASSERT(value_type != type_LinkList);
11,284✔
349
            instr.type = get_payload_type(value_type);
11,284✔
350
            instr.key_type = Instruction::Payload::Type::Null;
11,284✔
351
        }
11,284✔
352
        else {
52,118✔
353
            REALM_ASSERT(!col_key.is_collection());
52,118✔
354
            instr.collection_type = CollectionType::Single;
52,118✔
355
            instr.key_type = Instruction::Payload::Type::Null;
52,118✔
356
        }
52,118✔
357

43,752✔
358
        // Mixed columns are always nullable.
43,752✔
359
        REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable ||
90,382!
360
                     instr.collection_type == CollectionType::Dictionary);
90,382✔
361

43,752✔
362
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
90,382✔
363
            instr.link_target_table = emit_class_name(*target_table);
9,100✔
364
        }
9,100✔
365
        else {
81,282✔
366
            instr.link_target_table = m_encoder.intern_string("");
81,282✔
367
        }
81,282✔
368
        emit(instr);
90,382✔
369
    }
90,382✔
370
}
160,746✔
371

372
void SyncReplication::erase_column(const Table* table, ColKey col_ndx)
373
{
12✔
374
    Replication::erase_column(table, col_ndx);
12✔
375

6✔
376
    if (select_table(*table)) {
12✔
377
        // Not allowed to remove PK/OID columns!
6✔
378
        REALM_ASSERT(col_ndx != table->get_primary_key_column());
12✔
379
        Instruction::EraseColumn instr;
12✔
380
        instr.table = m_last_class_name;
12✔
381
        instr.field = m_encoder.intern_string(table->get_column_name(col_ndx));
12✔
382
        emit(instr);
12✔
383
    }
12✔
384
}
12✔
385

386
void SyncReplication::rename_column(const Table*, ColKey, StringData)
387
{
×
388
    unsupported_instruction();
×
389
}
×
390

391
void SyncReplication::list_set(const CollectionBase& list, size_t ndx, Mixed value)
392
{
5,394✔
393
    bool prior_is_unresolved = list.get_any(ndx).is_unresolved_link();
5,394✔
394

2,700✔
395
    // If link is unresolved, it should not be communicated.
2,700✔
396
    if (value.is_unresolved_link()) {
5,394✔
397
        // ... but reported internally as a deletion if prior value was not unresolved
126✔
398
        if (!prior_is_unresolved)
252✔
399
            Replication::list_erase(list, ndx);
252✔
400
    }
252✔
401
    else {
5,142✔
402
        if (prior_is_unresolved) {
5,142✔
403
            Replication::list_insert(list, ndx, value, 0 /* prior size not used */);
16✔
404
        }
16✔
405
        else {
5,126✔
406
            Replication::list_set(list, ndx, value);
5,126✔
407
        }
5,126✔
408
    }
5,142✔
409

2,700✔
410
    if (select_collection(list)) {
5,394✔
411
        // If this is an embedded object then we need to emit and erase/insert instruction so that the old
1,806✔
412
        // object gets cleared, otherwise you'll only see the Update ObjectValue instruction, which is idempotent,
1,806✔
413
        // and that will lead to corrupted prior size for array operations inside the embedded object during
1,806✔
414
        // changeset application.
1,806✔
415
        auto needs_insert_erase_sequence = [&] {
3,610✔
416
            if (value.is_type(type_Link)) {
3,610✔
417
                return list.get_target_table()->is_embedded();
208✔
418
            }
208✔
419
            else if (value.is_type(type_TypedLink)) {
3,402✔
420
                return m_transaction->get_table(value.get_link().get_table_key())->is_embedded();
88✔
421
            }
88✔
422
            return false;
3,314✔
423
        };
3,314✔
424
        if (needs_insert_erase_sequence()) {
3,610✔
425
            REALM_ASSERT(!list.is_null(ndx));
8✔
426
            Instruction::ArrayErase erase_instr;
8✔
427
            populate_path_instr(erase_instr, list, static_cast<uint32_t>(ndx));
8✔
428
            erase_instr.prior_size = uint32_t(list.size());
8✔
429
            emit(erase_instr);
8✔
430

4✔
431
            Instruction::ArrayInsert insert_instr;
8✔
432
            populate_path_instr(insert_instr, list, static_cast<uint32_t>(ndx));
8✔
433
            insert_instr.prior_size = erase_instr.prior_size - 1;
8✔
434
            insert_instr.value = as_payload(list, value);
8✔
435
            emit(insert_instr);
8✔
436
        }
8✔
437
        else {
3,602✔
438
            Instruction::Update instr;
3,602✔
439
            populate_path_instr(instr, list, uint32_t(ndx));
3,602✔
440
            REALM_ASSERT(instr.is_array_update());
3,602✔
441
            instr.value = as_payload(list, value);
3,602✔
442
            instr.prior_size = uint32_t(list.size());
3,602✔
443
            emit(instr);
3,602✔
444
        }
3,602✔
445
    }
3,610✔
446
}
5,394✔
447

448
void SyncReplication::list_insert(const CollectionBase& list, size_t ndx, Mixed value, size_t prior_size)
449
{
343,328✔
450
    // If link is unresolved, it should not be communicated.
171,670✔
451
    if (!value.is_unresolved_link()) {
343,328✔
452
        Replication::list_insert(list, ndx, value, prior_size);
343,292✔
453
    }
343,292✔
454

171,670✔
455
    if (select_collection(list)) {
343,328✔
456
        Instruction::ArrayInsert instr;
177,684✔
457
        populate_path_instr(instr, list, uint32_t(ndx));
177,684✔
458
        instr.value = as_payload(list, value);
177,684✔
459
        instr.prior_size = uint32_t(prior_size);
177,684✔
460
        emit(instr);
177,684✔
461
    }
177,684✔
462
}
343,328✔
463

464
void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value)
465
{
3,922✔
466
    Replication::add_int(table, col, ndx, value);
3,922✔
467

1,960✔
468
    if (select_table(*table)) {
3,922✔
469
        REALM_ASSERT(col != table->get_primary_key_column());
1,358✔
470

678✔
471
        Instruction::AddInteger instr;
1,358✔
472
        populate_path_instr(instr, *table, ndx, col);
1,358✔
473
        instr.value = value;
1,358✔
474
        emit(instr);
1,358✔
475
    }
1,358✔
476
}
3,922✔
477

478
void SyncReplication::set(const Table* table, ColKey col, ObjKey key, Mixed value, _impl::Instruction variant)
479
{
899,434✔
480
    Replication::set(table, col, key, value, variant);
899,434✔
481

436,058✔
482
    if (key.is_unresolved()) {
899,434✔
483
        return;
452✔
484
    }
452✔
485

435,832✔
486
    if (col == table->get_primary_key_column()) {
898,982✔
487
        return;
1,482✔
488
    }
1,482✔
489

435,110✔
490
    // If link is unresolved, it should not be communicated.
435,110✔
491
    if (value.is_unresolved_link()) {
897,500✔
492
        return;
36✔
493
    }
36✔
494

435,092✔
495
    if (select_table(*table)) {
897,464✔
496
        // Omit of Update(NULL, default=true) for embedded object / dictionary
165,114✔
497
        // columns if the value is already NULL. This is a workaround for the
165,114✔
498
        // fact that erase always wins for nested structures, but we don't want
165,114✔
499
        // default values to win over later embedded object creation.
165,114✔
500
        if (variant == _impl::instr_SetDefault && value.is_null()) {
345,006✔
501
            if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) {
8✔
502
                return;
8✔
503
            }
8✔
UNCOV
504
            if (col.is_dictionary() && table->get_object(key).is_null(col)) {
×
505
                // Dictionary columns cannot currently be NULL, but this is
506
                // likely to change.
507
                return;
×
508
            }
×
509
        }
344,998✔
510

165,110✔
511
        Instruction::Update instr;
344,998✔
512
        populate_path_instr(instr, *table, key, col);
344,998✔
513
        instr.value = as_payload(*table, col, value);
344,998✔
514
        instr.is_default = (variant == _impl::instr_SetDefault);
344,998✔
515
        emit(instr);
344,998✔
516
    }
344,998✔
517
}
897,464✔
518

519

520
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
521
{
16,634✔
522
    Replication::remove_object(table, row_ndx);
16,634✔
523
    if (table->is_embedded())
16,634✔
524
        return;
6,742✔
525
    if (table->is_asymmetric())
9,892✔
526
        return;
856✔
527
    REALM_ASSERT(!row_ndx.is_unresolved());
9,036✔
528

4,438✔
529
    if (select_table(*table)) {
9,036✔
530
        Instruction::EraseObject instr;
2,934✔
531
        instr.table = m_last_class_name;
2,934✔
532
        instr.object = primary_key_for_object(*table, row_ndx);
2,934✔
533
        emit(instr);
2,934✔
534
    }
2,934✔
535
}
9,036✔
536

537

538
void SyncReplication::list_move(const CollectionBase& view, size_t from_ndx, size_t to_ndx)
539
{
412✔
540
    Replication::list_move(view, from_ndx, to_ndx);
412✔
541
    if (select_collection(view)) {
412✔
542
        Instruction::ArrayMove instr;
384✔
543
        populate_path_instr(instr, view, uint32_t(from_ndx));
384✔
544
        instr.ndx_2 = uint32_t(to_ndx);
384✔
545
        instr.prior_size = uint32_t(view.size());
384✔
546
        emit(instr);
384✔
547
    }
384✔
548
}
412✔
549

550
void SyncReplication::list_erase(const CollectionBase& list, size_t ndx)
551
{
3,992✔
552
    Mixed prior_value = list.get_any(ndx);
3,992✔
553
    // If link is unresolved, it should not be communicated.
2,048✔
554
    if (!prior_value.is_unresolved_link()) {
3,992✔
555
        Replication::list_erase(list, ndx);
3,980✔
556
    }
3,980✔
557

2,048✔
558
    size_t prior_size = list.size();
3,992✔
559
    if (select_collection(list)) {
3,992✔
560
        Instruction::ArrayErase instr;
2,396✔
561
        populate_path_instr(instr, list, uint32_t(ndx));
2,396✔
562
        instr.prior_size = uint32_t(prior_size);
2,396✔
563
        emit(instr);
2,396✔
564
    }
2,396✔
565
}
3,992✔
566

567
void SyncReplication::list_clear(const CollectionBase& view)
568
{
2,014✔
569
    Replication::list_clear(view);
2,014✔
570
    if (select_collection(view)) {
2,014✔
571
        Instruction::Clear instr;
234✔
572
        populate_path_instr(instr, view);
234✔
573
        emit(instr);
234✔
574
    }
234✔
575
}
2,014✔
576

577
void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
578
{
23,052✔
579
    Replication::set_insert(set, set_ndx, value);
23,052✔
580

11,450✔
581
    if (select_collection(set)) {
23,052✔
582
        Instruction::SetInsert instr;
20,720✔
583
        populate_path_instr(instr, set);
20,720✔
584
        instr.value = as_payload(set, value);
20,720✔
585
        emit(instr);
20,720✔
586
    }
20,720✔
587
}
23,052✔
588

589
void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
590
{
4,256✔
591
    Replication::set_erase(set, set_ndx, value);
4,256✔
592

2,128✔
593
    if (select_collection(set)) {
4,256✔
594
        Instruction::SetErase instr;
2,636✔
595
        populate_path_instr(instr, set);
2,636✔
596
        instr.value = as_payload(set, value);
2,636✔
597
        emit(instr);
2,636✔
598
    }
2,636✔
599
}
4,256✔
600

601
void SyncReplication::set_clear(const CollectionBase& set)
602
{
244✔
603
    Replication::set_clear(set);
244✔
604

122✔
605
    if (select_collection(set)) {
244✔
606
        Instruction::Clear instr;
212✔
607
        populate_path_instr(instr, set);
212✔
608
        emit(instr);
212✔
609
    }
212✔
610
}
244✔
611

612
void SyncReplication::dictionary_update(const CollectionBase& dict, const Mixed& key, const Mixed& value)
613
{
28,384✔
614
    // If link is unresolved, it should not be communicated.
14,104✔
615
    if (value.is_unresolved_link()) {
28,384✔
616
        return;
108✔
617
    }
108✔
618

14,050✔
619
    if (select_collection(dict)) {
28,276✔
620
        Instruction::Update instr;
25,172✔
621
        REALM_ASSERT(key.get_type() == type_String);
25,172✔
622
        populate_path_instr(instr, dict);
25,172✔
623
        StringData key_value = key.get_string();
25,172✔
624
        instr.path.push_back(m_encoder.intern_string(key_value));
25,172✔
625
        instr.value = as_payload(dict, value);
25,172✔
626
        instr.is_default = false;
25,172✔
627
        emit(instr);
25,172✔
628
    }
25,172✔
629
}
28,276✔
630

631
void SyncReplication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
632
{
24,460✔
633
    Replication::dictionary_insert(dict, ndx, key, value);
24,460✔
634
    dictionary_update(dict, key, value);
24,460✔
635
}
24,460✔
636

637
void SyncReplication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
638
{
3,924✔
639
    Replication::dictionary_set(dict, ndx, key, value);
3,924✔
640
    dictionary_update(dict, key, value);
3,924✔
641
}
3,924✔
642

643
void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
644
{
2,420✔
645
    Replication::dictionary_erase(dict, ndx, key);
2,420✔
646

1,210✔
647
    if (select_collection(dict)) {
2,420✔
648
        Instruction::Update instr;
1,880✔
649
        REALM_ASSERT(key.get_type() == type_String);
1,880✔
650
        populate_path_instr(instr, dict);
1,880✔
651
        StringData key_value = key.get_string();
1,880✔
652
        instr.path.push_back(m_encoder.intern_string(key_value));
1,880✔
653
        instr.value = Instruction::Payload::Erased{};
1,880✔
654
        instr.is_default = false;
1,880✔
655
        emit(instr);
1,880✔
656
    }
1,880✔
657
}
2,420✔
658

659
void SyncReplication::nullify_link(const Table* table, ColKey col_ndx, ObjKey ndx)
660
{
18✔
661
    Replication::nullify_link(table, col_ndx, ndx);
18✔
662

8✔
663
    if (select_table(*table)) {
18✔
664
        Instruction::Update instr;
14✔
665
        populate_path_instr(instr, *table, ndx, col_ndx);
14✔
666
        REALM_ASSERT(!instr.is_array_update());
14✔
667
        instr.value = Instruction::Payload{realm::util::none};
14✔
668
        instr.is_default = false;
14✔
669
        emit(instr);
14✔
670
    }
14✔
671
}
18✔
672

673
void SyncReplication::link_list_nullify(const Lst<ObjKey>& view, size_t ndx)
674
{
356✔
675
    size_t prior_size = view.size();
356✔
676
    Replication::link_list_nullify(view, ndx);
356✔
677
    if (select_collection(view)) {
356✔
678
        Instruction::ArrayErase instr;
192✔
679
        populate_path_instr(instr, view, uint32_t(ndx));
192✔
680
        instr.prior_size = uint32_t(prior_size);
192✔
681
        emit(instr);
192✔
682
    }
192✔
683
}
356✔
684

685
void SyncReplication::unsupported_instruction() const
686
{
×
687
    throw realm::sync::TransformError{"Unsupported instruction"};
×
688
}
×
689

690
bool SyncReplication::select_table(const Table& table)
691
{
2,611,960✔
692
    if (is_short_circuited()) {
2,611,960✔
693
        return false;
677,798✔
694
    }
677,798✔
695

941,558✔
696
    if (&table == m_last_table) {
1,934,162✔
697
        return true;
1,428,236✔
698
    }
1,428,236✔
699

250,880✔
700
    if (!m_transaction->table_is_public(table.get_key())) {
505,926✔
701
        return false;
201,684✔
702
    }
201,684✔
703

150,710✔
704
    m_last_class_name = emit_class_name(table);
304,242✔
705
    m_last_table = &table;
304,242✔
706
    m_last_field = ColKey{};
304,242✔
707
    m_last_object = ObjKey{};
304,242✔
708
    m_last_primary_key.reset();
304,242✔
709
    return true;
304,242✔
710
}
304,242✔
711

712
bool SyncReplication::select_collection(const CollectionBase& view)
713
{
413,744✔
714
    if (view.get_owner_key().is_unresolved()) {
413,744✔
715
        return false;
×
716
    }
×
717

206,768✔
718
    return select_table(*view.get_table());
413,744✔
719
}
413,744✔
720

721
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
722
{
387,904✔
723
    bool should_emit = select_table(table);
387,904✔
724
    REALM_ASSERT(should_emit);
387,904✔
725

192,772✔
726
    if (table.get_primary_key_column()) {
387,904✔
727
        return as_primary_key(table.get_primary_key(key));
387,874✔
728
    }
387,874✔
729

12✔
730
    GlobalKey global_key = table.get_object_id(key);
30✔
731
    return global_key;
30✔
732
}
30✔
733

734
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
735
                                          ColKey field)
736
{
645,180✔
737
    REALM_ASSERT(key);
645,180✔
738
    REALM_ASSERT(field);
645,180✔
739

314,934✔
740
    if (table.is_embedded()) {
645,180✔
741
        // For embedded objects, Obj::traverse_path() yields the top object
31,810✔
742
        // first, then objects in the path in order.
31,810✔
743
        auto obj = table.get_object(key);
63,702✔
744
        auto path_sizer = [&](size_t size) {
63,702✔
745
            REALM_ASSERT(size != 0);
63,702✔
746
            // Reserve 2 elements per path component, because link list entries
31,810✔
747
            // have both a field and an index.
31,810✔
748
            instr.path.m_path.reserve(size * 2);
63,702✔
749
        };
63,702✔
750

31,810✔
751
        auto visitor = [&](const Obj& path_obj, ColKey next_field, Mixed index) {
93,338✔
752
            auto element_table = path_obj.get_table();
93,338✔
753
            if (element_table->is_embedded()) {
93,338✔
754
                StringData field_name = element_table->get_column_name(next_field);
29,636✔
755
                InternString interned_field_name = m_encoder.intern_string(field_name);
29,636✔
756
                instr.path.push_back(interned_field_name);
29,636✔
757
            }
29,636✔
758
            else {
63,702✔
759
                // This is the top object, populate it the normal way.
31,810✔
760
                populate_path_instr(instr, *element_table, path_obj.get_key(), next_field);
63,702✔
761
            }
63,702✔
762

46,628✔
763
            if (next_field.is_list()) {
93,338✔
764
                instr.path.push_back(uint32_t(index.get_int()));
32,252✔
765
            }
32,252✔
766
            else if (next_field.is_dictionary()) {
61,086✔
767
                InternString interned_field_name = m_encoder.intern_string(index.get_string());
25,440✔
768
                instr.path.push_back(interned_field_name);
25,440✔
769
            }
25,440✔
770
        };
93,338✔
771

31,810✔
772
        obj.traverse_path(visitor, path_sizer);
63,702✔
773

31,810✔
774
        // The field in the embedded object is the last path component.
31,810✔
775
        StringData field_in_embedded = table.get_column_name(field);
63,702✔
776
        InternString interned_field_in_embedded = m_encoder.intern_string(field_in_embedded);
63,702✔
777
        instr.path.push_back(interned_field_in_embedded);
63,702✔
778
        return;
63,702✔
779
    }
63,702✔
780

283,124✔
781
    bool should_emit = select_table(table);
581,478✔
782
    REALM_ASSERT(should_emit);
581,478✔
783

283,124✔
784
    instr.table = m_last_class_name;
581,478✔
785

283,124✔
786
    if (m_last_object == key) {
581,478✔
787
        instr.object = *m_last_primary_key;
214,800✔
788
    }
214,800✔
789
    else {
366,678✔
790
        instr.object = primary_key_for_object(table, key);
366,678✔
791
        m_last_object = key;
366,678✔
792
        m_last_primary_key = instr.object;
366,678✔
793
    }
366,678✔
794

283,124✔
795
    if (m_last_field == field) {
581,478✔
796
        instr.field = m_last_field_name;
369,644✔
797
    }
369,644✔
798
    else {
211,834✔
799
        instr.field = m_encoder.intern_string(table.get_column_name(field));
211,834✔
800
        m_last_field = field;
211,834✔
801
        m_last_field_name = instr.field;
211,834✔
802
    }
211,834✔
803
}
581,478✔
804

805
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list)
806
{
235,128✔
807
    ConstTableRef source_table = list.get_table();
235,128✔
808
    ObjKey source_obj = list.get_owner_key();
235,128✔
809
    ColKey source_field = list.get_col_key();
235,128✔
810
    populate_path_instr(instr, *source_table, source_obj, source_field);
235,128✔
811
}
235,128✔
812

813
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
814
                                          uint32_t ndx)
815
{
184,274✔
816
    populate_path_instr(instr, list);
184,274✔
817
    instr.path.m_path.push_back(ndx);
184,274✔
818
}
184,274✔
819

820
} // namespace sync
821
} // namespace realm
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

© 2026 Coveralls, Inc