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

realm / realm-core / 2094

02 Mar 2024 12:43AM UTC coverage: 90.925% (+0.005%) from 90.92%
2094

push

Evergreen

web-flow
Use updated curl on evergreen windows hosts (#7409)

93946 of 173116 branches covered (54.27%)

238403 of 262196 relevant lines covered (90.93%)

5592101.41 hits per line

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

90.49
/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
{
276,460✔
11
    m_encoder.reset();
276,460✔
12

139,044✔
13
    m_last_table = nullptr;
276,460✔
14
    m_last_object = ObjKey();
276,460✔
15
    m_last_field_name = StringData();
276,460✔
16
    m_last_class_name = InternString::npos;
276,460✔
17
    m_last_primary_key = Instruction::PrimaryKey();
276,460✔
18
    m_last_interned_field_name = InternString::npos;
276,460✔
19
}
276,460✔
20

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

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

264,042✔
35
    auto type = value.get_type();
543,310✔
36
    switch (type) {
543,310✔
37
        case type_Int: {
292,444✔
38
            return Instruction::Payload{value.get<int64_t>()};
292,444✔
39
        }
×
40
        case type_Bool: {
2,118✔
41
            return Instruction::Payload{value.get<bool>()};
2,118✔
42
        }
×
43
        case type_Float: {
2,240✔
44
            return Instruction::Payload{value.get<float>()};
2,240✔
45
        }
×
46
        case type_Double: {
3,112✔
47
            return Instruction::Payload{value.get<double>()};
3,112✔
48
        }
×
49
        case type_String: {
206,262✔
50
            auto str = value.get<StringData>();
206,262✔
51
            auto range = m_encoder.add_string_range(str);
206,262✔
52
            return Instruction::Payload{range};
206,262✔
53
        }
×
54
        case type_Binary: {
7,984✔
55
            auto binary = value.get<BinaryData>();
7,984✔
56
            auto range = m_encoder.add_string_range(StringData{binary.data(), binary.size()});
7,984✔
57
            const bool is_binary = true;
7,984✔
58
            return Instruction::Payload{range, is_binary};
7,984✔
59
        }
×
60
        case type_Timestamp: {
10,016✔
61
            return Instruction::Payload{value.get<Timestamp>()};
10,016✔
62
        }
×
63
        case type_Decimal: {
2,144✔
64
            return Instruction::Payload{value.get<Decimal128>()};
2,144✔
65
        }
×
66
        case type_ObjectId: {
13,616✔
67
            return Instruction::Payload{value.get<ObjectId>()};
13,616✔
68
        }
×
69
        case type_UUID: {
3,328✔
70
            return Instruction::Payload{value.get<UUID>()};
3,328✔
71
        }
×
72
        case type_TypedLink:
✔
73
            [[fallthrough]];
×
74
        case type_Link: {
✔
75
            REALM_TERMINATE("as_payload() needs table/collection for links");
76
            break;
×
77
        }
×
78
        case type_Mixed: {
✔
79
            REALM_TERMINATE("Invalid payload type");
80
            break;
×
81
        }
×
82
    }
×
83
    if (type == type_Dictionary) {
×
84
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
×
85
            throw IllegalOperation("Cannot sync nested dictionary");
×
86
        return Instruction::Payload(Instruction::Payload::Dictionary());
×
87
    }
×
88
    else if (type == type_List) {
×
89
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
×
90
            throw IllegalOperation("Cannot sync nested list");
×
91
        return Instruction::Payload(Instruction::Payload::List());
×
92
    }
×
93
    else if (type == type_Set) {
×
94
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
×
95
            throw IllegalOperation("Cannot sync nested set");
×
96
        return Instruction::Payload(Instruction::Payload::Set());
×
97
    }
×
98
    return Instruction::Payload{};
×
99
}
×
100

101
Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value)
102
{
229,736✔
103
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
229,736✔
104
}
229,736✔
105

106
Instruction::Payload SyncReplication::as_payload(const Table& table, ColKey col_key, Mixed value)
107
{
574,796✔
108
    if (value.is_null()) {
574,796✔
109
        // FIXME: `Mixed::get_type()` asserts on null.
520✔
110
        return Instruction::Payload{};
1,062✔
111
    }
1,062✔
112

279,090✔
113
    if (value.is_type(type_Link)) {
573,734✔
114
        ConstTableRef target_table = table.get_link_target(col_key);
17,002✔
115
        if (target_table->is_embedded()) {
17,002✔
116
            // FIXME: Include target table name to support Mixed of Embedded Objects.
4,770✔
117
            return Instruction::Payload::ObjectValue{};
9,598✔
118
        }
9,598✔
119

3,556✔
120
        Instruction::Payload::Link link;
7,404✔
121
        link.target_table = emit_class_name(*target_table);
7,404✔
122
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
7,404✔
123
        return Instruction::Payload{link};
7,404✔
124
    }
7,404✔
125
    else if (value.is_type(type_TypedLink)) {
556,732✔
126
        auto obj_link = value.get<ObjLink>();
13,264✔
127
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
13,264✔
128
        REALM_ASSERT(target_table);
13,264✔
129

6,560✔
130
        if (target_table->is_embedded()) {
13,264✔
131
            ConstTableRef static_target_table = table.get_link_target(col_key);
2,388✔
132

1,182✔
133
            if (static_target_table != target_table)
2,388✔
134
                REALM_TERMINATE("Dynamically typed embedded objects not supported yet.");
135
            return Instruction::Payload::ObjectValue{};
2,388✔
136
        }
2,388✔
137

5,378✔
138
        Instruction::Payload::Link link;
10,876✔
139
        link.target_table = emit_class_name(*target_table);
10,876✔
140
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
10,876✔
141
        return Instruction::Payload{link};
10,876✔
142
    }
10,876✔
143
    else {
543,468✔
144
        return as_payload(value);
543,468✔
145
    }
543,468✔
146
}
573,734✔
147

148
InternString SyncReplication::emit_class_name(StringData table_name)
149
{
371,792✔
150
    return m_encoder.intern_string(Group::table_name_to_class_name(table_name));
371,792✔
151
}
371,792✔
152

153
InternString SyncReplication::emit_class_name(const Table& table)
154
{
331,988✔
155
    return emit_class_name(table.get_name());
331,988✔
156
}
331,988✔
157

158
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
159
{
141,128✔
160
    using Type = Instruction::Payload::Type;
141,128✔
161
    switch (type) {
141,128✔
162
        case type_Int:
42,864✔
163
            return Type::Int;
42,864✔
164
        case type_Bool:
3,416✔
165
            return Type::Bool;
3,416✔
166
        case type_String:
35,960✔
167
            return Type::String;
35,960✔
168
        case type_Binary:
3,568✔
169
            return Type::Binary;
3,568✔
170
        case type_Timestamp:
4,422✔
171
            return Type::Timestamp;
4,422✔
172
        case type_Float:
3,580✔
173
            return Type::Float;
3,580✔
174
        case type_Double:
3,592✔
175
            return Type::Double;
3,592✔
176
        case type_Decimal:
3,416✔
177
            return Type::Decimal;
3,416✔
178
        case type_Link:
9,100✔
179
            return Type::Link;
9,100✔
180
        case type_TypedLink:
✔
181
            return Type::Link;
×
182
        case type_ObjectId:
21,962✔
183
            return Type::ObjectId;
21,962✔
184
        case type_UUID:
3,560✔
185
            return Type::UUID;
3,560✔
186
        case type_Mixed:
5,688✔
187
            return Type::Null;
5,688✔
188
    }
×
189
    unsupported_instruction();
×
190
    return Type::Int; // Make compiler happy
×
191
}
×
192

193
void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type)
194
{
21,074✔
195
    Replication::add_class(tk, name, table_type);
21,074✔
196

10,346✔
197
    bool is_class = m_transaction->table_is_public(tk);
21,074✔
198

10,346✔
199
    if (is_class && !m_short_circuit) {
21,074✔
200
        Instruction::AddTable instr;
1,780✔
201
        instr.table = emit_class_name(name);
1,780✔
202
        if (table_type == Table::Type::Embedded) {
1,780✔
203
            instr.type = Instruction::AddTable::EmbeddedTable{};
1,724✔
204
        }
1,724✔
205
        else {
56✔
206
            auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"?
56✔
207
            const bool is_nullable = false;
56✔
208
            bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
56✔
209
            instr.type = Instruction::AddTable::TopLevelTable{
56✔
210
                field,
56✔
211
                Instruction::Payload::Type::GlobalKey,
56✔
212
                is_nullable,
56✔
213
                is_asymmetric,
56✔
214
            };
56✔
215
        }
56✔
216
        emit(instr);
1,780✔
217
    }
1,780✔
218
}
21,074✔
219

220
void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field,
221
                                                 bool nullable, Table::Type table_type)
222
{
51,750✔
223
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
51,750✔
224

25,586✔
225
    bool is_class = m_transaction->table_is_public(tk);
51,750✔
226

25,586✔
227
    if (is_class && !m_short_circuit) {
51,750✔
228
        Instruction::AddTable instr;
37,974✔
229
        instr.table = emit_class_name(name);
37,974✔
230
        auto field = m_encoder.intern_string(pk_field);
37,974✔
231
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
37,974✔
232
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
37,974✔
233
        if (!is_valid_key_type(spec.pk_type)) {
37,974✔
234
            unsupported_instruction();
×
235
        }
×
236
        instr.type = std::move(spec);
37,974✔
237
        emit(instr);
37,974✔
238
    }
37,974✔
239
}
51,750✔
240

241
void SyncReplication::create_object(const Table* table, GlobalKey oid)
242
{
16,094✔
243
    if (table->is_embedded()) {
16,094✔
244
        unsupported_instruction(); // FIXME: TODO
×
245
    }
×
246

7,898✔
247
    Replication::create_object(table, oid);
16,094✔
248
    if (select_table(*table)) {
16,094✔
249
        if (table->get_primary_key_column()) {
72✔
250
            // Trying to create object without a primary key in a table that
251
            // has a primary key column.
252
            unsupported_instruction();
×
253
        }
×
254
        Instruction::CreateObject instr;
72✔
255
        instr.table = m_last_class_name;
72✔
256
        instr.object = oid;
72✔
257
        emit(instr);
72✔
258
    }
72✔
259
}
16,094✔
260

261
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
262
{
476,058✔
263
    if (value.is_null()) {
476,058✔
264
        return mpark::monostate{};
300✔
265
    }
300✔
266
    else if (value.get_type() == type_Int) {
475,758✔
267
        return value.get<int64_t>();
359,670✔
268
    }
359,670✔
269
    else if (value.get_type() == type_String) {
116,088✔
270
        return m_encoder.intern_string(value.get<StringData>());
984✔
271
    }
984✔
272
    else if (value.get_type() == type_ObjectId) {
115,104✔
273
        return value.get<ObjectId>();
115,000✔
274
    }
115,000✔
275
    else if (value.get_type() == type_UUID) {
104✔
276
        return value.get<UUID>();
52✔
277
    }
52✔
278
    else {
52✔
279
        // Unsupported primary key type.
48✔
280
        unsupported_instruction();
52✔
281
    }
52✔
282
}
476,058✔
283

284
void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value)
285
{
141,770✔
286
    if (table->is_embedded()) {
141,770✔
287
        // Trying to create an object with a primary key in an embedded table.
288
        unsupported_instruction();
×
289
    }
×
290

63,702✔
291
    Replication::create_object_with_primary_key(table, oid, value);
141,770✔
292
    if (select_table(*table)) {
141,770✔
293
        if (m_write_validator) {
88,562✔
294
            m_write_validator(*table);
1,606✔
295
        }
1,606✔
296

37,394✔
297
        auto col = table->get_primary_key_column();
88,562✔
298
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
88,562✔
299
            Instruction::CreateObject instr;
88,560✔
300
            instr.table = m_last_class_name;
88,560✔
301
            instr.object = as_primary_key(value);
88,560✔
302
            emit(instr);
88,560✔
303
        }
88,560✔
304
        else {
2✔
305
            // Trying to create object with primary key in table without a
306
            // primary key column, or with wrong primary key type.
307
            unsupported_instruction();
2✔
308
        }
2✔
309
    }
88,562✔
310
}
141,770✔
311

312

313
void SyncReplication::erase_class(TableKey table_key, StringData table_name, size_t num_tables)
314
{
208✔
315
    Replication::erase_class(table_key, table_name, num_tables);
208✔
316

84✔
317
    bool is_class = m_transaction->table_is_public(table_key);
208✔
318

84✔
319
    if (is_class && !m_short_circuit) {
208✔
320
        Instruction::EraseTable instr;
50✔
321
        instr.table = emit_class_name(table_name);
50✔
322
        emit(instr);
50✔
323
    }
50✔
324

84✔
325
    m_last_table = nullptr;
208✔
326
}
208✔
327

328
void SyncReplication::rename_class(TableKey, StringData)
329
{
×
330
    unsupported_instruction();
×
331
}
×
332

333
void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name,
334
                                    Table* target_table)
335
{
161,568✔
336
    Replication::insert_column(table, col_key, type, name, target_table);
161,568✔
337
    using CollectionType = Instruction::AddColumn::CollectionType;
161,568✔
338

78,640✔
339
    if (select_table(*table)) {
161,568✔
340
        Instruction::AddColumn instr;
91,056✔
341
        instr.table = m_last_class_name;
91,056✔
342
        instr.field = m_encoder.intern_string(name);
91,056✔
343
        instr.nullable = col_key.is_nullable();
91,056✔
344
        instr.type = get_payload_type(type);
91,056✔
345
        instr.key_type = Instruction::Payload::Type::Null;
91,056✔
346

44,070✔
347
        if (col_key.is_list()) {
91,056✔
348
            instr.collection_type = CollectionType::List;
14,872✔
349
        }
14,872✔
350
        else if (col_key.is_dictionary()) {
76,184✔
351
            instr.collection_type = CollectionType::Dictionary;
12,100✔
352
            auto key_type = table->get_dictionary_key_type(col_key);
12,100✔
353
            REALM_ASSERT(key_type == type_String);
12,100✔
354
            instr.key_type = get_payload_type(key_type);
12,100✔
355
        }
12,100✔
356
        else if (col_key.is_set()) {
64,084✔
357
            instr.collection_type = CollectionType::Set;
11,284✔
358
        }
11,284✔
359
        else {
52,800✔
360
            REALM_ASSERT(!col_key.is_collection());
52,800✔
361
            instr.collection_type = CollectionType::Single;
52,800✔
362
        }
52,800✔
363

44,070✔
364
        // Mixed columns are always nullable.
44,070✔
365
        REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable ||
91,056!
366
                     instr.collection_type == CollectionType::Dictionary);
91,056✔
367

44,070✔
368
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
91,056✔
369
            instr.link_target_table = emit_class_name(*target_table);
9,100✔
370
        }
9,100✔
371
        else {
81,956✔
372
            instr.link_target_table = m_encoder.intern_string("");
81,956✔
373
        }
81,956✔
374
        emit(instr);
91,056✔
375
    }
91,056✔
376
}
161,568✔
377

378
void SyncReplication::erase_column(const Table* table, ColKey col_ndx)
379
{
12✔
380
    Replication::erase_column(table, col_ndx);
12✔
381

6✔
382
    if (select_table(*table)) {
12✔
383
        // Not allowed to remove PK/OID columns!
6✔
384
        REALM_ASSERT(col_ndx != table->get_primary_key_column());
12✔
385
        Instruction::EraseColumn instr;
12✔
386
        instr.table = m_last_class_name;
12✔
387
        instr.field = m_encoder.intern_string(table->get_column_name(col_ndx));
12✔
388
        emit(instr);
12✔
389
    }
12✔
390
}
12✔
391

392
void SyncReplication::rename_column(const Table*, ColKey, StringData)
393
{
×
394
    unsupported_instruction();
×
395
}
×
396

397
void SyncReplication::list_set(const CollectionBase& list, size_t ndx, Mixed value)
398
{
5,318✔
399
    bool prior_is_unresolved = list.get_any(ndx).is_unresolved_link();
5,318✔
400

2,666✔
401
    // If link is unresolved, it should not be communicated.
2,666✔
402
    if (value.is_unresolved_link()) {
5,318✔
403
        // ... but reported internally as a deletion if prior value was not unresolved
126✔
404
        if (!prior_is_unresolved)
252✔
405
            Replication::list_erase(list, ndx);
252✔
406
    }
252✔
407
    else {
5,066✔
408
        if (prior_is_unresolved) {
5,066✔
409
            Replication::list_insert(list, ndx, value, 0 /* prior size not used */);
16✔
410
        }
16✔
411
        else {
5,050✔
412
            Replication::list_set(list, ndx, value);
5,050✔
413
        }
5,050✔
414
    }
5,066✔
415

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

4✔
437
            Instruction::ArrayInsert insert_instr;
8✔
438
            populate_path_instr(insert_instr, list, static_cast<uint32_t>(ndx));
8✔
439
            insert_instr.prior_size = erase_instr.prior_size - 1;
8✔
440
            insert_instr.value = as_payload(list, value);
8✔
441
            emit(insert_instr);
8✔
442
        }
8✔
443
        else {
3,576✔
444
            Instruction::Update instr;
3,576✔
445
            populate_path_instr(instr, list, uint32_t(ndx));
3,576✔
446
            REALM_ASSERT(instr.is_array_update());
3,576✔
447
            instr.value = as_payload(list, value);
3,576✔
448
            instr.prior_size = uint32_t(list.size());
3,576✔
449
            emit(instr);
3,576✔
450
        }
3,576✔
451
    }
3,584✔
452
}
5,318✔
453

454
void SyncReplication::list_insert(const CollectionBase& list, size_t ndx, Mixed value, size_t prior_size)
455
{
343,214✔
456
    // If link is unresolved, it should not be communicated.
171,348✔
457
    if (!value.is_unresolved_link()) {
343,214✔
458
        Replication::list_insert(list, ndx, value, prior_size);
343,176✔
459
    }
343,176✔
460

171,348✔
461
    if (select_collection(list)) {
343,214✔
462
        Instruction::ArrayInsert instr;
177,626✔
463
        populate_path_instr(instr, list, uint32_t(ndx));
177,626✔
464
        instr.value = as_payload(list, value);
177,626✔
465
        instr.prior_size = uint32_t(prior_size);
177,626✔
466
        emit(instr);
177,626✔
467
    }
177,626✔
468
}
343,214✔
469

470
void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value)
471
{
3,944✔
472
    Replication::add_int(table, col, ndx, value);
3,944✔
473

1,962✔
474
    if (select_table(*table)) {
3,944✔
475
        REALM_ASSERT(col != table->get_primary_key_column());
1,364✔
476

680✔
477
        Instruction::AddInteger instr;
1,364✔
478
        populate_path_instr(instr, *table, ndx, {col});
1,364✔
479
        instr.value = value;
1,364✔
480
        emit(instr);
1,364✔
481
    }
1,364✔
482
}
3,944✔
483

484
void SyncReplication::set(const Table* table, ColKey col, ObjKey key, Mixed value, _impl::Instruction variant)
485
{
900,562✔
486
    Replication::set(table, col, key, value, variant);
900,562✔
487

436,966✔
488
    if (key.is_unresolved()) {
900,562✔
489
        return;
452✔
490
    }
452✔
491

436,740✔
492
    if (col == table->get_primary_key_column()) {
900,110✔
493
        return;
1,466✔
494
    }
1,466✔
495

436,004✔
496
    // If link is unresolved, it should not be communicated.
436,004✔
497
    if (value.is_unresolved_link()) {
898,644✔
498
        return;
36✔
499
    }
36✔
500

435,986✔
501
    if (select_table(*table)) {
898,608✔
502
        // Omit of Update(NULL, default=true) for embedded object / dictionary
164,712✔
503
        // columns if the value is already NULL. This is a workaround for the
164,712✔
504
        // fact that erase always wins for nested structures, but we don't want
164,712✔
505
        // default values to win over later embedded object creation.
164,712✔
506
        if (variant == _impl::instr_SetDefault && value.is_null()) {
344,744✔
507
            if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) {
8✔
508
                return;
8✔
509
            }
8✔
510
            if (col.is_dictionary() && table->get_object(key).is_null(col)) {
×
511
                // Dictionary columns cannot currently be NULL, but this is
512
                // likely to change.
513
                return;
×
514
            }
×
515
        }
344,736✔
516

164,708✔
517
        Instruction::Update instr;
344,736✔
518
        populate_path_instr(instr, *table, key, {col});
344,736✔
519
        instr.value = as_payload(*table, col, value);
344,736✔
520
        instr.is_default = (variant == _impl::instr_SetDefault);
344,736✔
521
        emit(instr);
344,736✔
522
    }
344,736✔
523
}
898,608✔
524

525

526
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
527
{
16,694✔
528
    Replication::remove_object(table, row_ndx);
16,694✔
529
    if (table->is_embedded())
16,694✔
530
        return;
6,732✔
531
    if (table->is_asymmetric())
9,962✔
532
        return;
856✔
533
    REALM_ASSERT(!row_ndx.is_unresolved());
9,106✔
534

4,608✔
535
    if (select_table(*table)) {
9,106✔
536
        Instruction::EraseObject instr;
2,918✔
537
        instr.table = m_last_class_name;
2,918✔
538
        instr.object = primary_key_for_object(*table, row_ndx);
2,918✔
539
        emit(instr);
2,918✔
540
    }
2,918✔
541
}
9,106✔
542

543

544
void SyncReplication::list_move(const CollectionBase& view, size_t from_ndx, size_t to_ndx)
545
{
412✔
546
    Replication::list_move(view, from_ndx, to_ndx);
412✔
547
    if (select_collection(view)) {
412✔
548
        Instruction::ArrayMove instr;
384✔
549
        populate_path_instr(instr, view, uint32_t(from_ndx));
384✔
550
        instr.ndx_2 = uint32_t(to_ndx);
384✔
551
        instr.prior_size = uint32_t(view.size());
384✔
552
        emit(instr);
384✔
553
    }
384✔
554
}
412✔
555

556
void SyncReplication::list_erase(const CollectionBase& list, size_t ndx)
557
{
3,946✔
558
    Mixed prior_value = list.get_any(ndx);
3,946✔
559
    // If link is unresolved, it should not be communicated.
1,940✔
560
    if (!prior_value.is_unresolved_link()) {
3,946✔
561
        Replication::list_erase(list, ndx);
3,934✔
562
    }
3,934✔
563

1,940✔
564
    size_t prior_size = list.size();
3,946✔
565
    if (select_collection(list)) {
3,946✔
566
        Instruction::ArrayErase instr;
2,372✔
567
        populate_path_instr(instr, list, uint32_t(ndx));
2,372✔
568
        instr.prior_size = uint32_t(prior_size);
2,372✔
569
        emit(instr);
2,372✔
570
    }
2,372✔
571
}
3,946✔
572

573
void SyncReplication::list_clear(const CollectionBase& view)
574
{
2,030✔
575
    Replication::list_clear(view);
2,030✔
576
    if (select_collection(view)) {
2,030✔
577
        Instruction::Clear instr;
236✔
578
        populate_path_instr(instr, view);
236✔
579
        emit(instr);
236✔
580
    }
236✔
581
}
2,030✔
582

583
void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
584
{
23,052✔
585
    Replication::set_insert(set, set_ndx, value);
23,052✔
586

11,450✔
587
    if (select_collection(set)) {
23,052✔
588
        Instruction::SetInsert instr;
20,720✔
589
        populate_path_instr(instr, set);
20,720✔
590
        instr.value = as_payload(set, value);
20,720✔
591
        emit(instr);
20,720✔
592
    }
20,720✔
593
}
23,052✔
594

595
void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
596
{
4,256✔
597
    Replication::set_erase(set, set_ndx, value);
4,256✔
598

2,128✔
599
    if (select_collection(set)) {
4,256✔
600
        Instruction::SetErase instr;
2,636✔
601
        populate_path_instr(instr, set);
2,636✔
602
        instr.value = as_payload(set, value);
2,636✔
603
        emit(instr);
2,636✔
604
    }
2,636✔
605
}
4,256✔
606

607
void SyncReplication::set_clear(const CollectionBase& set)
608
{
244✔
609
    Replication::set_clear(set);
244✔
610

122✔
611
    if (select_collection(set)) {
244✔
612
        Instruction::Clear instr;
212✔
613
        populate_path_instr(instr, set);
212✔
614
        emit(instr);
212✔
615
    }
212✔
616
}
244✔
617

618
void SyncReplication::dictionary_update(const CollectionBase& dict, const Mixed& key, const Mixed& value)
619
{
28,384✔
620
    // If link is unresolved, it should not be communicated.
14,104✔
621
    if (value.is_unresolved_link()) {
28,384✔
622
        return;
108✔
623
    }
108✔
624

14,050✔
625
    if (select_collection(dict)) {
28,276✔
626
        Instruction::Update instr;
25,172✔
627
        REALM_ASSERT(key.get_type() == type_String);
25,172✔
628
        populate_path_instr(instr, dict);
25,172✔
629
        StringData key_value = key.get_string();
25,172✔
630
        instr.path.push_back(m_encoder.intern_string(key_value));
25,172✔
631
        instr.value = as_payload(dict, value);
25,172✔
632
        instr.is_default = false;
25,172✔
633
        emit(instr);
25,172✔
634
    }
25,172✔
635
}
28,276✔
636

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

643
void SyncReplication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
644
{
3,900✔
645
    Replication::dictionary_set(dict, ndx, key, value);
3,900✔
646
    dictionary_update(dict, key, value);
3,900✔
647
}
3,900✔
648

649
void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
650
{
1,924✔
651
    Replication::dictionary_erase(dict, ndx, key);
1,924✔
652

962✔
653
    if (select_collection(dict)) {
1,924✔
654
        Instruction::Update instr;
1,416✔
655
        REALM_ASSERT(key.get_type() == type_String);
1,416✔
656
        populate_path_instr(instr, dict);
1,416✔
657
        StringData key_value = key.get_string();
1,416✔
658
        instr.path.push_back(m_encoder.intern_string(key_value));
1,416✔
659
        instr.value = Instruction::Payload::Erased{};
1,416✔
660
        instr.is_default = false;
1,416✔
661
        emit(instr);
1,416✔
662
    }
1,416✔
663
}
1,924✔
664

665
void SyncReplication::dictionary_clear(const CollectionBase& dict)
666
{
188✔
667
    Replication::dictionary_clear(dict);
188✔
668

94✔
669
    if (select_collection(dict)) {
188✔
670
        Instruction::Clear instr;
172✔
671
        populate_path_instr(instr, dict);
172✔
672
        emit(instr);
172✔
673
    }
172✔
674
}
188✔
675

676
void SyncReplication::nullify_link(const Table* table, ColKey col_key, ObjKey ndx)
677
{
18✔
678
    Replication::nullify_link(table, col_key, ndx);
18✔
679

8✔
680
    if (select_table(*table)) {
18✔
681
        Instruction::Update instr;
14✔
682
        populate_path_instr(instr, *table, ndx, {col_key});
14✔
683
        REALM_ASSERT(!instr.is_array_update());
14✔
684
        instr.value = Instruction::Payload{realm::util::none};
14✔
685
        instr.is_default = false;
14✔
686
        emit(instr);
14✔
687
    }
14✔
688
}
18✔
689

690
void SyncReplication::link_list_nullify(const Lst<ObjKey>& view, size_t ndx)
691
{
356✔
692
    size_t prior_size = view.size();
356✔
693
    Replication::link_list_nullify(view, ndx);
356✔
694
    if (select_collection(view)) {
356✔
695
        Instruction::ArrayErase instr;
192✔
696
        populate_path_instr(instr, view, uint32_t(ndx));
192✔
697
        instr.prior_size = uint32_t(prior_size);
192✔
698
        emit(instr);
192✔
699
    }
192✔
700
}
356✔
701

702
void SyncReplication::unsupported_instruction() const
703
{
×
704
    throw realm::sync::TransformError{"Unsupported instruction"};
×
705
}
×
706

707
bool SyncReplication::select_table(const Table& table)
708
{
2,610,840✔
709
    if (is_short_circuited()) {
2,610,840✔
710
        return false;
678,548✔
711
    }
678,548✔
712

939,170✔
713
    if (&table == m_last_table) {
1,932,292✔
714
        return true;
1,426,342✔
715
    }
1,426,342✔
716

250,742✔
717
    if (!m_transaction->table_is_public(table.get_key())) {
505,950✔
718
        return false;
201,804✔
719
    }
201,804✔
720

150,542✔
721
    m_last_class_name = emit_class_name(table);
304,146✔
722
    m_last_table = &table;
304,146✔
723
    m_last_field_name = StringData{};
304,146✔
724
    m_last_object = ObjKey{};
304,146✔
725
    m_last_primary_key.reset();
304,146✔
726
    return true;
304,146✔
727
}
304,146✔
728

729
bool SyncReplication::select_collection(const CollectionBase& view)
730
{
413,216✔
731
    if (view.get_owner_key().is_unresolved()) {
413,216✔
732
        return false;
×
733
    }
×
734

206,158✔
735
    return select_table(*view.get_table());
413,216✔
736
}
413,216✔
737

738
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
739
{
387,568✔
740
    bool should_emit = select_table(table);
387,568✔
741
    REALM_ASSERT(should_emit);
387,568✔
742

192,326✔
743
    if (table.get_primary_key_column()) {
387,568✔
744
        return as_primary_key(table.get_primary_key(key));
387,486✔
745
    }
387,486✔
746

72✔
747
    GlobalKey global_key = table.get_object_id(key);
82✔
748
    return global_key;
82✔
749
}
82✔
750

751
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
752
                                          Path path)
753
{
644,766✔
754
    REALM_ASSERT(key);
644,766✔
755
    // The first path entry will be the column key
314,496✔
756
    REALM_ASSERT(path[0].is_col_key());
644,766✔
757

314,496✔
758
    if (table.is_embedded()) {
644,766✔
759
        // For embedded objects, Obj::traverse_path() yields the top object
31,810✔
760
        // first, then objects in the path in order.
31,810✔
761
        auto obj = table.get_object(key);
63,702✔
762
        auto full_path = obj.get_path();
63,702✔
763
        // Populate top object in the normal way.
31,810✔
764
        auto top_table = table.get_parent_group()->get_table(full_path.top_table);
63,702✔
765

31,810✔
766
        full_path.path_from_top.emplace_back(table.get_column_name(path[0].get_col_key()));
63,702✔
767

31,810✔
768
        for (auto it = path.begin() + 1; it != path.end(); ++it) {
63,702✔
769
            full_path.path_from_top.emplace_back(std::move(*it));
×
770
        }
×
771
        populate_path_instr(instr, *top_table, full_path.top_objkey, full_path.path_from_top);
63,702✔
772
        return;
63,702✔
773
    }
63,702✔
774

282,686✔
775
    bool should_emit = select_table(table);
581,064✔
776
    REALM_ASSERT(should_emit);
581,064✔
777

282,686✔
778
    instr.table = m_last_class_name;
581,064✔
779
    if (m_last_object == key) {
581,064✔
780
        instr.object = *m_last_primary_key;
214,506✔
781
    }
214,506✔
782
    else {
366,558✔
783
        instr.object = primary_key_for_object(table, key);
366,558✔
784
        m_last_object = key;
366,558✔
785
        m_last_primary_key = instr.object;
366,558✔
786
    }
366,558✔
787

282,686✔
788
    StringData field_name = table.get_column_name(path[0].get_col_key());
581,064✔
789

282,686✔
790
    if (m_last_field_name == field_name) {
581,064✔
791
        instr.field = m_last_interned_field_name;
368,984✔
792
    }
368,984✔
793
    else {
212,080✔
794
        instr.field = m_encoder.intern_string(field_name);
212,080✔
795
        m_last_field_name = field_name;
212,080✔
796
        m_last_interned_field_name = instr.field;
212,080✔
797
    }
212,080✔
798
    size_t sz = path.size();
581,064✔
799
    instr.path.reserve(sz - 1);
581,064✔
800
    for (size_t i = 1; i < sz; i++) {
732,094✔
801
        auto& path_elem = path[i];
151,030✔
802
        if (path_elem.is_ndx()) {
151,030✔
803
            instr.path.push_back(uint32_t(path_elem.get_ndx()));
32,252✔
804
        }
32,252✔
805
        else {
118,778✔
806
            REALM_ASSERT(path_elem.is_key());
118,778✔
807
            InternString interned_field_name = m_encoder.intern_string(path_elem.get_key().c_str());
118,778✔
808
            instr.path.push_back(interned_field_name);
118,778✔
809
        }
118,778✔
810
    }
151,030✔
811
}
581,064✔
812

813
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list)
814
{
234,730✔
815
    ConstTableRef source_table = list.get_table();
234,730✔
816
    ObjKey source_obj = list.get_owner_key();
234,730✔
817
    populate_path_instr(instr, *source_table, source_obj, list.get_short_path());
234,730✔
818
}
234,730✔
819

820
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
821
                                          uint32_t ndx)
822
{
184,166✔
823
    populate_path_instr(instr, list);
184,166✔
824
    instr.path.push_back(ndx);
184,166✔
825
}
184,166✔
826

827
} // namespace sync
828
} // 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

© 2025 Coveralls, Inc