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

realm / realm-core / 1833

14 Nov 2023 03:52AM UTC coverage: 91.644% (-0.05%) from 91.69%
1833

push

Evergreen

web-flow
Merge feature/network-faultiness branch into master (#7120)

* Add baas-network-tests nightly task for testing sync client operation with non-ideal network conditions (#6852)

* Added support for starting baas proxy
* Fixed some issues when running the scripts
* minor updates to install_baas.sh
* Updates to scripts to run on evergreen spawn host
* Added total time output to object store tests
* Increased initial ssh connect attempts; renamed proxy to 'baas_proxy'
* Minor updates to help output
* Added baas network test to run bass with the proxy
* Added support for separate baas admin api url value
* Minor port check adjustments
* Removed 'compile' task from network_tests
* Disable development mode by default
* Added baas remote host and network tests instructions doc
* Minor updates to the instructions
* Minor updates to documentation

* Add non-ideal transfer and network fault tests with non-ideal network conditions (#7063)

* Added non-ideal transfer and network fault tests
* Added baas-network-tests to be allowed for PR runs
* Updated changelog
* Updated curl command to be silent
* Updated changelog
* Reverted some changes and added descriptions to config.yml

* Fixed test extra delay

* Added some evergreen script updates from another branch

* Disabled baas network faults tests

* Update flx migration test to enable dev mode

* Fix baas branch for network tests

* add more time to 'too large sync message error handling' test

* Added network faults test and pulled nonideal out of nightly tests until fixes are added

92116 of 168852 branches covered (0.0%)

179 of 192 new or added lines in 14 files covered. (93.23%)

140 existing lines in 19 files now uncovered.

231053 of 252119 relevant lines covered (91.64%)

6637899.19 hits per line

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

92.57
/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
{
743,864✔
11
    m_encoder.reset();
743,864✔
12

373,046✔
13
    m_last_table = nullptr;
743,864✔
14
    m_last_object = ObjKey();
743,864✔
15
    m_last_field = ColKey();
743,864✔
16
    m_last_class_name = InternString::npos;
743,864✔
17
    m_last_primary_key = Instruction::PrimaryKey();
743,864✔
18
    m_last_field_name = InternString::npos;
743,864✔
19
}
743,864✔
20

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

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

257,886✔
35
    switch (value.get_type()) {
530,310✔
36
        case type_Int: {
296,822✔
37
            return Instruction::Payload{value.get<int64_t>()};
296,822✔
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: {
189,324✔
49
            auto str = value.get<StringData>();
189,324✔
50
            auto range = m_encoder.add_string_range(str);
189,324✔
51
            return Instruction::Payload{range};
189,324✔
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: {
9,884✔
60
            return Instruction::Payload{value.get<Timestamp>()};
9,884✔
61
        }
×
62
        case type_Decimal: {
2,144✔
63
            return Instruction::Payload{value.get<Decimal128>()};
2,144✔
64
        }
×
65
        case type_ObjectId: {
13,352✔
66
            return Instruction::Payload{value.get<ObjectId>()};
13,352✔
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
{
216,746✔
89
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
216,746✔
90
}
216,746✔
91

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

270,332✔
99
    if (value.is_type(type_Link)) {
555,692✔
100
        ConstTableRef target_table = table.get_link_target(col_key);
14,958✔
101
        if (target_table->is_embedded()) {
14,958✔
102
            // FIXME: Include target table name to support Mixed of Embedded Objects.
4,672✔
103
            return Instruction::Payload::ObjectValue{};
9,402✔
104
        }
9,402✔
105

2,632✔
106
        Instruction::Payload::Link link;
5,556✔
107
        link.target_table = emit_class_name(*target_table);
5,556✔
108
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
5,556✔
109
        return Instruction::Payload{link};
5,556✔
110
    }
5,556✔
111
    else if (value.is_type(type_TypedLink)) {
540,734✔
112
        auto obj_link = value.get<ObjLink>();
10,424✔
113
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
10,424✔
114
        REALM_ASSERT(target_table);
10,424✔
115

5,140✔
116
        if (target_table->is_embedded()) {
10,424✔
117
            ConstTableRef static_target_table = table.get_link_target(col_key);
2,320✔
118

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

3,992✔
124
        Instruction::Payload::Link link;
8,104✔
125
        link.target_table = emit_class_name(*target_table);
8,104✔
126
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
8,104✔
127
        return Instruction::Payload{link};
8,104✔
128
    }
8,104✔
129
    else {
530,310✔
130
        return as_payload(value);
530,310✔
131
    }
530,310✔
132
}
555,692✔
133

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

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

144
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
145
{
151,040✔
146
    using Type = Instruction::Payload::Type;
151,040✔
147
    switch (type) {
151,040✔
148
        case type_Int:
46,674✔
149
            return Type::Int;
46,674✔
150
        case type_Bool:
4,240✔
151
            return Type::Bool;
4,240✔
152
        case type_String:
36,184✔
153
            return Type::String;
36,184✔
154
        case type_Binary:
4,424✔
155
            return Type::Binary;
4,424✔
156
        case type_Timestamp:
5,264✔
157
            return Type::Timestamp;
5,264✔
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:
5,804✔
165
            return Type::Link;
5,804✔
166
        case type_LinkList:
3,228✔
167
            return Type::Link;
3,228✔
168
        case type_TypedLink:
4✔
169
            return Type::Link;
4✔
170
        case type_ObjectId:
22,000✔
171
            return Type::ObjectId;
22,000✔
172
        case type_UUID:
4,384✔
173
            return Type::UUID;
4,384✔
174
        case type_Mixed:
5,708✔
175
            return Type::Null;
5,708✔
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
{
19,296✔
183
    Replication::add_class(tk, name, table_type);
19,296✔
184

9,456✔
185
    bool is_class = m_transaction->table_is_public(tk);
19,296✔
186

9,456✔
187
    if (is_class && !m_short_circuit) {
19,296✔
188
        Instruction::AddTable instr;
1,748✔
189
        instr.table = emit_class_name(name);
1,748✔
190
        if (table_type == Table::Type::Embedded) {
1,748✔
191
            instr.type = Instruction::AddTable::EmbeddedTable{};
1,692✔
192
        }
1,692✔
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,748✔
205
    }
1,748✔
206
}
19,296✔
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
{
61,144✔
211
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
61,144✔
212

30,454✔
213
    bool is_class = m_transaction->table_is_public(tk);
61,144✔
214

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

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

7,302✔
235
    Replication::create_object(table, oid);
14,904✔
236
    if (select_table(*table)) {
14,904✔
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
}
14,904✔
248

249
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
250
{
518,940✔
251
    if (value.is_null()) {
518,940✔
252
        return mpark::monostate{};
300✔
253
    }
300✔
254
    else if (value.get_type() == type_Int) {
518,640✔
255
        return value.get<int64_t>();
398,908✔
256
    }
398,908✔
257
    else if (value.get_type() == type_String) {
119,732✔
258
        return m_encoder.intern_string(value.get<StringData>());
12,878✔
259
    }
12,878✔
260
    else if (value.get_type() == type_ObjectId) {
106,854✔
261
        return value.get<ObjectId>();
106,802✔
262
    }
106,802✔
263
    else if (value.get_type() == type_UUID) {
52✔
264
        return value.get<UUID>();
52✔
265
    }
52✔
UNCOV
266
    else {
×
267
        // Unsupported primary key type.
UNCOV
268
        unsupported_instruction();
×
UNCOV
269
    }
×
270
}
518,940✔
271

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

111,034✔
279
    Replication::create_object_with_primary_key(table, oid, value);
236,480✔
280
    if (select_table(*table)) {
236,480✔
281
        if (m_write_validator) {
112,154✔
282
            m_write_validator(*table);
1,638✔
283
        }
1,638✔
284

49,106✔
285
        auto col = table->get_primary_key_column();
112,154✔
286
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
112,154✔
287
            Instruction::CreateObject instr;
112,148✔
288
            instr.table = m_last_class_name;
112,148✔
289
            instr.object = as_primary_key(value);
112,148✔
290
            emit(instr);
112,148✔
291
        }
112,148✔
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
    }
112,154✔
298
}
236,480✔
299

300

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

2,694✔
305
    StringData table_name = m_transaction->get_table_name(table_key);
5,258✔
306

2,694✔
307
    bool is_class = m_transaction->table_is_public(table_key);
5,258✔
308

2,694✔
309
    if (is_class && !m_short_circuit) {
5,258✔
310
        Instruction::EraseTable instr;
1,214✔
311
        instr.table = emit_class_name(table_name);
1,214✔
312
        emit(instr);
1,214✔
313
    }
1,214✔
314

2,694✔
315
    m_last_table = nullptr;
5,258✔
316
}
5,258✔
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
{
166,642✔
326
    Replication::insert_column(table, col_key, type, name, target_table);
166,642✔
327
    using CollectionType = Instruction::AddColumn::CollectionType;
166,642✔
328

81,184✔
329
    if (select_table(*table)) {
166,642✔
330
        Instruction::AddColumn instr;
89,132✔
331
        instr.table = m_last_class_name;
89,132✔
332
        instr.field = m_encoder.intern_string(name);
89,132✔
333
        instr.nullable = col_key.is_nullable();
89,132✔
334
        instr.type = get_payload_type(type);
89,132✔
335

43,084✔
336
        if (col_key.is_list()) {
89,132✔
337
            instr.collection_type = CollectionType::List;
16,080✔
338
        }
16,080✔
339
        else if (col_key.is_dictionary()) {
73,052✔
340
            instr.collection_type = CollectionType::Dictionary;
11,812✔
341
            auto key_type = table->get_dictionary_key_type(col_key);
11,812✔
342
            REALM_ASSERT(key_type == type_String);
11,812✔
343
            instr.key_type = get_payload_type(key_type);
11,812✔
344
        }
11,812✔
345
        else if (col_key.is_set()) {
61,240✔
346
            instr.collection_type = CollectionType::Set;
10,980✔
347
            auto value_type = table->get_column_type(col_key);
10,980✔
348
            REALM_ASSERT(value_type != type_LinkList);
10,980✔
349
            instr.type = get_payload_type(value_type);
10,980✔
350
            instr.key_type = Instruction::Payload::Type::Null;
10,980✔
351
        }
10,980✔
352
        else {
50,260✔
353
            REALM_ASSERT(!col_key.is_collection());
50,260✔
354
            instr.collection_type = CollectionType::Single;
50,260✔
355
            instr.key_type = Instruction::Payload::Type::Null;
50,260✔
356
        }
50,260✔
357

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

43,084✔
362
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
89,132✔
363
            instr.link_target_table = emit_class_name(*target_table);
8,440✔
364
        }
8,440✔
365
        else {
80,692✔
366
            instr.link_target_table = m_encoder.intern_string("");
80,692✔
367
        }
80,692✔
368
        emit(instr);
89,132✔
369
    }
89,132✔
370
}
166,642✔
371

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

4✔
376
    if (select_table(*table)) {
8✔
377
        // Not allowed to remove PK/OID columns!
4✔
378
        REALM_ASSERT(col_ndx != table->get_primary_key_column());
8✔
379
        Instruction::EraseColumn instr;
8✔
380
        instr.table = m_last_class_name;
8✔
381
        instr.field = m_encoder.intern_string(table->get_column_name(col_ndx));
8✔
382
        emit(instr);
8✔
383
    }
8✔
384
}
8✔
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
{
12,472✔
393
    Mixed prior_value = list.get_any(ndx);
12,472✔
394
    bool prior_is_unresolved =
12,472✔
395
        prior_value.is_type(type_Link, type_TypedLink) && prior_value.get<ObjKey>().is_unresolved();
12,472✔
396

6,248✔
397
    // If link is unresolved, it should not be communicated.
6,248✔
398
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
12,472✔
399
        // ... but reported internally as a deletion if prior value was not unresolved
78✔
400
        if (!prior_is_unresolved)
156✔
401
            Replication::list_erase(list, ndx);
156✔
402
    }
156✔
403
    else {
12,316✔
404
        if (prior_is_unresolved) {
12,316✔
405
            Replication::list_insert(list, ndx, value, 0 /* prior size not used */);
16✔
406
        }
16✔
407
        else {
12,300✔
408
            Replication::list_set(list, ndx, value);
12,300✔
409
        }
12,300✔
410
    }
12,316✔
411

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

4✔
433
            Instruction::ArrayInsert insert_instr;
8✔
434
            populate_path_instr(insert_instr, list, static_cast<uint32_t>(ndx));
8✔
435
            insert_instr.prior_size = erase_instr.prior_size - 1;
8✔
436
            insert_instr.value = as_payload(list, value);
8✔
437
            emit(insert_instr);
8✔
438
        }
8✔
439
        else {
6,052✔
440
            Instruction::Update instr;
6,052✔
441
            populate_path_instr(instr, list, uint32_t(ndx));
6,052✔
442
            REALM_ASSERT(instr.is_array_update());
6,052✔
443
            instr.value = as_payload(list, value);
6,052✔
444
            instr.prior_size = uint32_t(list.size());
6,052✔
445
            emit(instr);
6,052✔
446
        }
6,052✔
447
    }
6,060✔
448
}
12,472✔
449

450
void SyncReplication::list_insert(const CollectionBase& list, size_t ndx, Mixed value, size_t prior_size)
451
{
332,072✔
452
    // If link is unresolved, it should not be communicated.
167,214✔
453
    if (!(value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved())) {
332,072✔
454
        Replication::list_insert(list, ndx, value, prior_size);
332,036✔
455
    }
332,036✔
456

167,214✔
457
    if (select_collection(list)) {
332,072✔
458
        Instruction::ArrayInsert instr;
165,562✔
459
        populate_path_instr(instr, list, uint32_t(ndx));
165,562✔
460
        instr.value = as_payload(list, value);
165,562✔
461
        instr.prior_size = uint32_t(prior_size);
165,562✔
462
        emit(instr);
165,562✔
463
    }
165,562✔
464
}
332,072✔
465

466
void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value)
467
{
5,386✔
468
    Replication::add_int(table, col, ndx, value);
5,386✔
469

2,776✔
470
    if (select_table(*table)) {
5,386✔
471
        REALM_ASSERT(col != table->get_primary_key_column());
1,916✔
472

988✔
473
        Instruction::AddInteger instr;
1,916✔
474
        populate_path_instr(instr, *table, ndx, col);
1,916✔
475
        instr.value = value;
1,916✔
476
        emit(instr);
1,916✔
477
    }
1,916✔
478
}
5,386✔
479

480
void SyncReplication::set(const Table* table, ColKey col, ObjKey key, Mixed value, _impl::Instruction variant)
481
{
894,250✔
482
    Replication::set(table, col, key, value, variant);
894,250✔
483

432,260✔
484
    if (key.is_unresolved()) {
894,250✔
485
        return;
164✔
486
    }
164✔
487

432,178✔
488
    if (col == table->get_primary_key_column()) {
894,086✔
489
        return;
3,462✔
490
    }
3,462✔
491

430,518✔
492
    // If link is unresolved, it should not be communicated.
430,518✔
493
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
890,624✔
494
        return;
36✔
495
    }
36✔
496

430,500✔
497
    if (select_table(*table)) {
890,588✔
498
        // Omit of Update(NULL, default=true) for embedded object / dictionary
162,562✔
499
        // columns if the value is already NULL. This is a workaround for the
162,562✔
500
        // fact that erase always wins for nested structures, but we don't want
162,562✔
501
        // default values to win over later embedded object creation.
162,562✔
502
        if (variant == _impl::instr_SetDefault && value.is_null()) {
340,192✔
503
            if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) {
68✔
504
                return;
8✔
505
            }
8✔
506
            if (col.is_dictionary() && table->get_object(key).is_null(col)) {
60!
507
                // Dictionary columns cannot currently be NULL, but this is
508
                // likely to change.
509
                return;
×
510
            }
×
511
        }
340,184✔
512

162,558✔
513
        Instruction::Update instr;
340,184✔
514
        populate_path_instr(instr, *table, key, col);
340,184✔
515
        instr.value = as_payload(*table, col, value);
340,184✔
516
        instr.is_default = (variant == _impl::instr_SetDefault);
340,184✔
517
        emit(instr);
340,184✔
518
    }
340,184✔
519
}
890,588✔
520

521

522
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
523
{
73,258✔
524
    Replication::remove_object(table, row_ndx);
73,258✔
525
    if (table->is_embedded())
73,258✔
526
        return;
6,294✔
527
    if (table->is_asymmetric())
66,964✔
528
        return;
856✔
529
    REALM_ASSERT(!row_ndx.is_unresolved());
66,108✔
530

32,792✔
531
    if (select_table(*table)) {
66,108✔
532
        Instruction::EraseObject instr;
21,192✔
533
        instr.table = m_last_class_name;
21,192✔
534
        instr.object = primary_key_for_object(*table, row_ndx);
21,192✔
535
        emit(instr);
21,192✔
536
    }
21,192✔
537
}
66,108✔
538

539

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

552
void SyncReplication::list_erase(const CollectionBase& list, size_t ndx)
553
{
10,230✔
554
    Mixed prior_value = list.get_any(ndx);
10,230✔
555
    // If link is unresolved, it should not be communicated.
5,510✔
556
    if (!(prior_value.is_type(type_Link, type_TypedLink) && prior_value.get<ObjKey>().is_unresolved())) {
10,230✔
557
        Replication::list_erase(list, ndx);
10,218✔
558
    }
10,218✔
559

5,510✔
560
    size_t prior_size = list.size();
10,230✔
561
    if (select_collection(list)) {
10,230✔
562
        Instruction::ArrayErase instr;
4,440✔
563
        populate_path_instr(instr, list, uint32_t(ndx));
4,440✔
564
        instr.prior_size = uint32_t(prior_size);
4,440✔
565
        emit(instr);
4,440✔
566
    }
4,440✔
567
}
10,230✔
568

569
void SyncReplication::list_clear(const CollectionBase& view)
570
{
1,770✔
571
    Replication::list_clear(view);
1,770✔
572
    if (select_collection(view)) {
1,770✔
573
        Instruction::Clear instr;
240✔
574
        populate_path_instr(instr, view);
240✔
575
        emit(instr);
240✔
576
    }
240✔
577
}
1,770✔
578

579
void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
580
{
21,446✔
581
    Replication::set_insert(set, set_ndx, value);
21,446✔
582

10,646✔
583
    if (select_collection(set)) {
21,446✔
584
        Instruction::SetInsert instr;
19,216✔
585
        populate_path_instr(instr, set);
19,216✔
586
        instr.value = as_payload(set, value);
19,216✔
587
        emit(instr);
19,216✔
588
    }
19,216✔
589
}
21,446✔
590

591
void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
592
{
3,612✔
593
    Replication::set_erase(set, set_ndx, value);
3,612✔
594

1,808✔
595
    if (select_collection(set)) {
3,612✔
596
        Instruction::SetErase instr;
2,252✔
597
        populate_path_instr(instr, set);
2,252✔
598
        instr.value = as_payload(set, value);
2,252✔
599
        emit(instr);
2,252✔
600
    }
2,252✔
601
}
3,612✔
602

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

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

614
void SyncReplication::dictionary_update(const CollectionBase& dict, const Mixed& key, const Mixed& value)
615
{
26,604✔
616
    // If link is unresolved, it should not be communicated.
13,214✔
617
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
26,604✔
618
        return;
12✔
619
    }
12✔
620

13,208✔
621
    if (select_collection(dict)) {
26,592✔
622
        Instruction::Update instr;
23,656✔
623
        REALM_ASSERT(key.get_type() == type_String);
23,656✔
624
        populate_path_instr(instr, dict);
23,656✔
625
        StringData key_value = key.get_string();
23,656✔
626
        instr.path.push_back(m_encoder.intern_string(key_value));
23,656✔
627
        instr.value = as_payload(dict, value);
23,656✔
628
        instr.is_default = false;
23,656✔
629
        emit(instr);
23,656✔
630
    }
23,656✔
631
}
26,592✔
632

633
void SyncReplication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
634
{
23,108✔
635
    Replication::dictionary_insert(dict, ndx, key, value);
23,108✔
636
    dictionary_update(dict, key, value);
23,108✔
637
}
23,108✔
638

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

645
void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
646
{
2,292✔
647
    Replication::dictionary_erase(dict, ndx, key);
2,292✔
648

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

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

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

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

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

692
bool SyncReplication::select_table(const Table& table)
693
{
2,763,796✔
694
    if (is_short_circuited()) {
2,763,796✔
695
        return false;
825,470✔
696
    }
825,470✔
697

943,852✔
698
    if (&table == m_last_table) {
1,938,326✔
699
        return true;
1,402,320✔
700
    }
1,402,320✔
701

265,864✔
702
    if (!m_transaction->table_is_public(table.get_key())) {
536,006✔
703
        return false;
177,240✔
704
    }
177,240✔
705

177,918✔
706
    m_last_class_name = emit_class_name(table);
358,766✔
707
    m_last_table = &table;
358,766✔
708
    m_last_field = ColKey{};
358,766✔
709
    m_last_object = ObjKey{};
358,766✔
710
    m_last_primary_key.reset();
358,766✔
711
    return true;
358,766✔
712
}
358,766✔
713

714
bool SyncReplication::select_collection(const CollectionBase& view)
715
{
411,154✔
716
    if (view.get_owner_key().is_unresolved()) {
411,154✔
717
        return false;
×
718
    }
×
719

207,008✔
720
    return select_table(*view.get_table());
411,154✔
721
}
411,154✔
722

723
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
724
{
406,812✔
725
    bool should_emit = select_table(table);
406,812✔
726
    REALM_ASSERT(should_emit);
406,812✔
727

202,250✔
728
    if (table.get_primary_key_column()) {
406,812✔
729
        return as_primary_key(table.get_primary_key(key));
406,796✔
730
    }
406,796✔
731

6✔
732
    GlobalKey global_key = table.get_object_id(key);
16✔
733
    return global_key;
16✔
734
}
16✔
735

736
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
737
                                          ColKey field)
738
{
628,230✔
739
    REALM_ASSERT(key);
628,230✔
740
    REALM_ASSERT(field);
628,230✔
741

306,658✔
742
    if (table.is_embedded()) {
628,230✔
743
        // For embedded objects, Obj::traverse_path() yields the top object
31,084✔
744
        // first, then objects in the path in order.
31,084✔
745
        auto obj = table.get_object(key);
62,250✔
746
        auto path_sizer = [&](size_t size) {
62,250✔
747
            REALM_ASSERT(size != 0);
62,250✔
748
            // Reserve 2 elements per path component, because link list entries
31,084✔
749
            // have both a field and an index.
31,084✔
750
            instr.path.m_path.reserve(size * 2);
62,250✔
751
        };
62,250✔
752

31,084✔
753
        auto visitor = [&](const Obj& path_obj, ColKey next_field, Mixed index) {
91,226✔
754
            auto element_table = path_obj.get_table();
91,226✔
755
            if (element_table->is_embedded()) {
91,226✔
756
                StringData field_name = element_table->get_column_name(next_field);
28,976✔
757
                InternString interned_field_name = m_encoder.intern_string(field_name);
28,976✔
758
                instr.path.push_back(interned_field_name);
28,976✔
759
            }
28,976✔
760
            else {
62,250✔
761
                // This is the top object, populate it the normal way.
31,084✔
762
                populate_path_instr(instr, *element_table, path_obj.get_key(), next_field);
62,250✔
763
            }
62,250✔
764

45,572✔
765
            if (next_field.is_list()) {
91,226✔
766
                instr.path.push_back(uint32_t(index.get_int()));
31,724✔
767
            }
31,724✔
768
            else if (next_field.is_dictionary()) {
59,502✔
769
                InternString interned_field_name = m_encoder.intern_string(index.get_string());
24,692✔
770
                instr.path.push_back(interned_field_name);
24,692✔
771
            }
24,692✔
772
        };
91,226✔
773

31,084✔
774
        obj.traverse_path(visitor, path_sizer);
62,250✔
775

31,084✔
776
        // The field in the embedded object is the last path component.
31,084✔
777
        StringData field_in_embedded = table.get_column_name(field);
62,250✔
778
        InternString interned_field_in_embedded = m_encoder.intern_string(field_in_embedded);
62,250✔
779
        instr.path.push_back(interned_field_in_embedded);
62,250✔
780
        return;
62,250✔
781
    }
62,250✔
782

275,574✔
783
    bool should_emit = select_table(table);
565,980✔
784
    REALM_ASSERT(should_emit);
565,980✔
785

275,574✔
786
    instr.table = m_last_class_name;
565,980✔
787

275,574✔
788
    if (m_last_object == key) {
565,980✔
789
        instr.object = *m_last_primary_key;
194,018✔
790
    }
194,018✔
791
    else {
371,962✔
792
        instr.object = primary_key_for_object(table, key);
371,962✔
793
        m_last_object = key;
371,962✔
794
        m_last_primary_key = instr.object;
371,962✔
795
    }
371,962✔
796

275,574✔
797
    if (m_last_field == field) {
565,980✔
798
        instr.field = m_last_field_name;
346,122✔
799
    }
346,122✔
800
    else {
219,858✔
801
        instr.field = m_encoder.intern_string(table.get_column_name(field));
219,858✔
802
        m_last_field = field;
219,858✔
803
        m_last_field_name = instr.field;
219,858✔
804
    }
219,858✔
805
}
565,980✔
806

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

815
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
816
                                          uint32_t ndx)
817
{
176,446✔
818
    populate_path_instr(instr, list);
176,446✔
819
    instr.path.m_path.push_back(ndx);
176,446✔
820
}
176,446✔
821

822
} // namespace sync
823
} // 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