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

realm / realm-core / 2119

08 Mar 2024 02:33PM UTC coverage: 90.928% (+0.008%) from 90.92%
2119

push

Evergreen

web-flow
Merge pull request #7436 from realm/release/14.2.0

Release core v14.2.0

94008 of 173178 branches covered (54.28%)

238532 of 262330 relevant lines covered (90.93%)

5728859.68 hits per line

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

90.22
/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
{
275,320✔
11
    m_encoder.reset();
275,320✔
12

138,588✔
13
    m_last_table = nullptr;
275,320✔
14
    m_last_object = ObjKey();
275,320✔
15
    m_last_field_name = StringData();
275,320✔
16
    m_last_class_name = InternString::npos;
275,320✔
17
    m_last_primary_key = Instruction::PrimaryKey();
275,320✔
18
    m_last_interned_field_name = InternString::npos;
275,320✔
19
}
275,320✔
20

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

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

264,244✔
35
    auto type = value.get_type();
543,478✔
36
    switch (type) {
543,478✔
37
        case type_Int: {
292,834✔
38
            return Instruction::Payload{value.get<int64_t>()};
292,834✔
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,086✔
50
            auto str = value.get<StringData>();
206,086✔
51
            auto range = m_encoder.add_string_range(str);
206,086✔
52
            return Instruction::Payload{range};
206,086✔
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,652✔
103
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
229,652✔
104
}
229,652✔
105

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

279,134✔
113
    if (value.is_type(type_Link)) {
573,746✔
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,744✔
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,480✔
144
        return as_payload(value);
543,480✔
145
    }
543,480✔
146
}
573,746✔
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,966✔
155
    return emit_class_name(table.get_name());
331,966✔
156
}
331,966✔
157

158
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
159
{
141,148✔
160
    using Type = Instruction::Payload::Type;
141,148✔
161
    switch (type) {
141,148✔
162
        case type_Int:
42,902✔
163
            return Type::Int;
42,902✔
164
        case type_Bool:
3,416✔
165
            return Type::Bool;
3,416✔
166
        case type_String:
35,942✔
167
            return Type::String;
35,942✔
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,754✔
223
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
51,754✔
224

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

25,610✔
227
    if (is_class && !m_short_circuit) {
51,754✔
228
        Instruction::AddTable instr;
37,996✔
229
        instr.table = emit_class_name(name);
37,996✔
230
        auto field = m_encoder.intern_string(pk_field);
37,996✔
231
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
37,996✔
232
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
37,996✔
233
        if (!is_valid_key_type(spec.pk_type)) {
37,996✔
234
            unsupported_instruction();
×
235
        }
×
236
        instr.type = std::move(spec);
37,996✔
237
        emit(instr);
37,996✔
238
    }
37,996✔
239
}
51,754✔
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,556✔
263
    if (value.is_null()) {
476,556✔
264
        return mpark::monostate{};
300✔
265
    }
300✔
266
    else if (value.get_type() == type_Int) {
476,256✔
267
        return value.get<int64_t>();
360,150✔
268
    }
360,150✔
269
    else if (value.get_type() == type_String) {
116,106✔
270
        return m_encoder.intern_string(value.get<StringData>());
1,054✔
271
    }
1,054✔
272
    else if (value.get_type() == type_ObjectId) {
115,052✔
273
        return value.get<ObjectId>();
115,000✔
274
    }
115,000✔
275
    else if (value.get_type() == type_UUID) {
52✔
276
        return value.get<UUID>();
52✔
277
    }
52✔
278
    else {
×
279
        // Unsupported primary key type.
280
        unsupported_instruction();
×
281
    }
×
282
}
476,556✔
283

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

63,894✔
291
    Replication::create_object_with_primary_key(table, oid, value);
142,026✔
292
    if (select_table(*table)) {
142,026✔
293
        if (m_write_validator) {
88,654✔
294
            m_write_validator(*table);
1,606✔
295
        }
1,606✔
296

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

312

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

104✔
317
    bool is_class = m_transaction->table_is_public(table_key);
198✔
318

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

104✔
325
    m_last_table = nullptr;
198✔
326
}
198✔
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,542✔
336
    Replication::insert_column(table, col_key, type, name, target_table);
161,542✔
337
    using CollectionType = Instruction::AddColumn::CollectionType;
161,542✔
338

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

44,070✔
347
        if (col_key.is_list()) {
91,050✔
348
            instr.collection_type = CollectionType::List;
14,864✔
349
        }
14,864✔
350
        else if (col_key.is_dictionary()) {
76,186✔
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,086✔
357
            instr.collection_type = CollectionType::Set;
11,284✔
358
        }
11,284✔
359
        else {
52,802✔
360
            REALM_ASSERT(!col_key.is_collection());
52,802✔
361
            instr.collection_type = CollectionType::Single;
52,802✔
362
        }
52,802✔
363

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

44,070✔
368
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
91,050✔
369
            instr.link_target_table = emit_class_name(*target_table);
9,100✔
370
        }
9,100✔
371
        else {
81,950✔
372
            instr.link_target_table = m_encoder.intern_string("");
81,950✔
373
        }
81,950✔
374
        emit(instr);
91,050✔
375
    }
91,050✔
376
}
161,542✔
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,156✔
399
    bool prior_is_unresolved = list.get_any(ndx).is_unresolved_link();
5,156✔
400

2,520✔
401
    // If link is unresolved, it should not be communicated.
2,520✔
402
    if (value.is_unresolved_link()) {
5,156✔
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 {
4,904✔
408
        if (prior_is_unresolved) {
4,904✔
409
            Replication::list_insert(list, ndx, value, 0 /* prior size not used */);
16✔
410
        }
16✔
411
        else {
4,888✔
412
            Replication::list_set(list, ndx, value);
4,888✔
413
        }
4,888✔
414
    }
4,904✔
415

2,520✔
416
    if (select_collection(list)) {
5,156✔
417
        // If this is an embedded object then we need to emit and erase/insert instruction so that the old
1,754✔
418
        // object gets cleared, otherwise you'll only see the Update ObjectValue instruction, which is idempotent,
1,754✔
419
        // and that will lead to corrupted prior size for array operations inside the embedded object during
1,754✔
420
        // changeset application.
1,754✔
421
        auto needs_insert_erase_sequence = [&] {
3,538✔
422
            if (value.is_type(type_Link)) {
3,538✔
423
                return list.get_target_table()->is_embedded();
208✔
424
            }
208✔
425
            else if (value.is_type(type_TypedLink)) {
3,330✔
426
                return m_transaction->get_table(value.get_link().get_table_key())->is_embedded();
88✔
427
            }
88✔
428
            return false;
3,242✔
429
        };
3,242✔
430
        if (needs_insert_erase_sequence()) {
3,538✔
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,530✔
444
            Instruction::Update instr;
3,530✔
445
            populate_path_instr(instr, list, uint32_t(ndx));
3,530✔
446
            REALM_ASSERT(instr.is_array_update());
3,530✔
447
            instr.value = as_payload(list, value);
3,530✔
448
            instr.prior_size = uint32_t(list.size());
3,530✔
449
            emit(instr);
3,530✔
450
        }
3,530✔
451
    }
3,538✔
452
}
5,156✔
453

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

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

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

1,982✔
474
    if (select_table(*table)) {
3,966✔
475
        REALM_ASSERT(col != table->get_primary_key_column());
1,372✔
476

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

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

437,178✔
488
    if (key.is_unresolved()) {
901,316✔
489
        return;
452✔
490
    }
452✔
491

436,952✔
492
    if (col == table->get_primary_key_column()) {
900,864✔
493
        return;
1,510✔
494
    }
1,510✔
495

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

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

165,150✔
517
        Instruction::Update instr;
345,148✔
518
        populate_path_instr(instr, *table, key, {col});
345,148✔
519
        instr.value = as_payload(*table, col, value);
345,148✔
520
        instr.is_default = (variant == _impl::instr_SetDefault);
345,148✔
521
        emit(instr);
345,148✔
522
    }
345,148✔
523
}
899,318✔
524

525

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

4,662✔
535
    if (select_table(*table)) {
9,202✔
536
        Instruction::EraseObject instr;
2,984✔
537
        instr.table = m_last_class_name;
2,984✔
538
        instr.object = primary_key_for_object(*table, row_ndx);
2,984✔
539
        emit(instr);
2,984✔
540
    }
2,984✔
541
}
9,202✔
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,942✔
558
    Mixed prior_value = list.get_any(ndx);
3,942✔
559
    // If link is unresolved, it should not be communicated.
1,942✔
560
    if (!prior_value.is_unresolved_link()) {
3,942✔
561
        Replication::list_erase(list, ndx);
3,930✔
562
    }
3,930✔
563

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

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

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

11,450✔
587
    if (select_collection(set)) {
23,054✔
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,054✔
594

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

2,128✔
599
    if (select_collection(set)) {
4,252✔
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,252✔
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,613,734✔
709
    if (is_short_circuited()) {
2,613,734✔
710
        return false;
679,202✔
711
    }
679,202✔
712

941,480✔
713
    if (&table == m_last_table) {
1,934,532✔
714
        return true;
1,428,408✔
715
    }
1,428,408✔
716

250,932✔
717
    if (!m_transaction->table_is_public(table.get_key())) {
506,124✔
718
        return false;
201,812✔
719
    }
201,812✔
720

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

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

205,846✔
735
    return select_table(*view.get_table());
412,794✔
736
}
412,794✔
737

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

192,708✔
743
    if (table.get_primary_key_column()) {
387,930✔
744
        return as_primary_key(table.get_primary_key(key));
387,914✔
745
    }
387,914✔
746

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

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

314,620✔
758
    if (table.is_embedded()) {
644,876✔
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,810✔
775
    bool should_emit = select_table(table);
581,174✔
776
    REALM_ASSERT(should_emit);
581,174✔
777

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

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

282,810✔
790
    if (m_last_field_name == field_name) {
581,174✔
791
        instr.field = m_last_interned_field_name;
369,352✔
792
    }
369,352✔
793
    else {
211,822✔
794
        instr.field = m_encoder.intern_string(field_name);
211,822✔
795
        m_last_field_name = field_name;
211,822✔
796
        m_last_interned_field_name = instr.field;
211,822✔
797
    }
211,822✔
798
    size_t sz = path.size();
581,174✔
799
    instr.path.reserve(sz - 1);
581,174✔
800
    for (size_t i = 1; i < sz; i++) {
732,204✔
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,174✔
812

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

820
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
821
                                          uint32_t ndx)
822
{
184,070✔
823
    populate_path_instr(instr, list);
184,070✔
824
    instr.path.push_back(ndx);
184,070✔
825
}
184,070✔
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