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

realm / realm-core / 2617

20 Sep 2024 08:37AM UTC coverage: 91.096% (+0.002%) from 91.094%
2617

push

Evergreen

web-flow
New changelog section to prepare for vNext (#8031)

Co-authored-by: jedelbo <572755+jedelbo@users.noreply.github.com>

102782 of 181476 branches covered (56.64%)

217164 of 238389 relevant lines covered (91.1%)

5727039.47 hits per line

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

91.48
/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
{
289,258✔
11
    m_encoder.reset();
289,258✔
12

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

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

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

35
    auto type = value.get_type();
580,680✔
36
    switch (type) {
580,680✔
37
        case type_Int: {
300,750✔
38
            return Instruction::Payload{value.get<int64_t>()};
300,750✔
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: {
232,720✔
50
            auto str = value.get<StringData>();
232,720✔
51
            auto range = m_encoder.add_string_range(str);
232,720✔
52
            return Instruction::Payload{range};
232,720✔
53
        }
×
54
        case type_Binary: {
8,024✔
55
            auto binary = value.get<BinaryData>();
8,024✔
56
            auto range = m_encoder.add_string_range(StringData{binary.data(), binary.size()});
8,024✔
57
            const bool is_binary = true;
8,024✔
58
            return Instruction::Payload{range, is_binary};
8,024✔
59
        }
×
60
        case type_Timestamp: {
10,024✔
61
            return Instruction::Payload{value.get<Timestamp>()};
10,024✔
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
    }
580,680✔
83
    if (type == type_Dictionary) {
2,600✔
84
        return Instruction::Payload(Instruction::Payload::Dictionary());
1,076✔
85
    }
1,076✔
86
    else if (type == type_List) {
1,524✔
87
        return Instruction::Payload(Instruction::Payload::List());
1,524✔
88
    }
1,524✔
89
    else if (type == type_Set) {
×
90
        return Instruction::Payload(Instruction::Payload::Set());
×
91
    }
×
92
    return Instruction::Payload{};
×
93
}
2,600✔
94

95
Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value)
96
{
255,026✔
97
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
255,026✔
98
}
255,026✔
99

100
Instruction::Payload SyncReplication::as_payload(const Table& table, ColKey col_key, Mixed value)
101
{
612,034✔
102
    if (value.is_null()) {
612,034✔
103
        // FIXME: `Mixed::get_type()` asserts on null.
104
        return Instruction::Payload{};
1,018✔
105
    }
1,018✔
106

107
    if (value.is_type(type_Link)) {
611,016✔
108
        ConstTableRef target_table = table.get_link_target(col_key);
17,002✔
109
        if (target_table->is_embedded()) {
17,002✔
110
            // FIXME: Include target table name to support Mixed of Embedded Objects.
111
            return Instruction::Payload::ObjectValue{};
9,598✔
112
        }
9,598✔
113

114
        Instruction::Payload::Link link;
7,404✔
115
        link.target_table = emit_class_name(*target_table);
7,404✔
116
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
7,404✔
117
        return Instruction::Payload{link};
7,404✔
118
    }
17,002✔
119
    else if (value.is_type(type_TypedLink)) {
594,014✔
120
        auto obj_link = value.get<ObjLink>();
13,340✔
121
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
13,340✔
122
        REALM_ASSERT(target_table);
13,340✔
123

124
        if (target_table->is_embedded()) {
13,340✔
125
            ConstTableRef static_target_table = table.get_link_target(col_key);
2,388✔
126

127
            if (static_target_table != target_table)
2,388✔
128
                REALM_TERMINATE("Dynamically typed embedded objects not supported yet.");
129
            return Instruction::Payload::ObjectValue{};
2,388✔
130
        }
2,388✔
131

132
        Instruction::Payload::Link link;
10,952✔
133
        link.target_table = emit_class_name(*target_table);
10,952✔
134
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
10,952✔
135
        return Instruction::Payload{link};
10,952✔
136
    }
13,340✔
137
    else {
580,674✔
138
        return as_payload(value);
580,674✔
139
    }
580,674✔
140
}
611,016✔
141

142
InternString SyncReplication::emit_class_name(StringData table_name)
143
{
380,432✔
144
    return m_encoder.intern_string(Group::table_name_to_class_name(table_name));
380,432✔
145
}
380,432✔
146

147
InternString SyncReplication::emit_class_name(const Table& table)
148
{
338,546✔
149
    return emit_class_name(table.get_name());
338,546✔
150
}
338,546✔
151

152
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
153
{
148,614✔
154
    using Type = Instruction::Payload::Type;
148,614✔
155
    switch (type) {
148,614✔
156
        case type_Int:
44,178✔
157
            return Type::Int;
44,178✔
158
        case type_Bool:
3,416✔
159
            return Type::Bool;
3,416✔
160
        case type_String:
37,172✔
161
            return Type::String;
37,172✔
162
        case type_Binary:
3,576✔
163
            return Type::Binary;
3,576✔
164
        case type_Timestamp:
4,422✔
165
            return Type::Timestamp;
4,422✔
166
        case type_Float:
3,580✔
167
            return Type::Float;
3,580✔
168
        case type_Double:
3,592✔
169
            return Type::Double;
3,592✔
170
        case type_Decimal:
3,416✔
171
            return Type::Decimal;
3,416✔
172
        case type_Link:
9,284✔
173
            return Type::Link;
9,284✔
174
        case type_TypedLink:
✔
175
            return Type::Link;
×
176
        case type_ObjectId:
23,646✔
177
            return Type::ObjectId;
23,646✔
178
        case type_UUID:
3,560✔
179
            return Type::UUID;
3,560✔
180
        case type_Mixed:
8,772✔
181
            return Type::Null;
8,772✔
182
    }
148,614✔
183
    unsupported_instruction();
×
184
    return Type::Int; // Make compiler happy
×
185
}
148,614✔
186

187
void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type)
188
{
30,934✔
189
    Replication::add_class(tk, name, table_type);
30,934✔
190

191
    bool is_class = m_transaction->table_is_public(tk);
30,934✔
192

193
    if (is_class && !m_short_circuit) {
30,934✔
194
        Instruction::AddTable instr;
1,856✔
195
        instr.table = emit_class_name(name);
1,856✔
196
        if (table_type == Table::Type::Embedded) {
1,856✔
197
            instr.type = Instruction::AddTable::EmbeddedTable{};
1,800✔
198
        }
1,800✔
199
        else {
56✔
200
            auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"?
56✔
201
            const bool is_nullable = false;
56✔
202
            bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
56✔
203
            instr.type = Instruction::AddTable::TopLevelTable{
56✔
204
                field,
56✔
205
                Instruction::Payload::Type::GlobalKey,
56✔
206
                is_nullable,
56✔
207
                is_asymmetric,
56✔
208
            };
56✔
209
        }
56✔
210
        emit(instr);
1,856✔
211
    }
1,856✔
212
}
30,934✔
213

214
void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field,
215
                                                 bool nullable, Table::Type table_type)
216
{
48,154✔
217
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
48,154✔
218

219
    bool is_class = m_transaction->table_is_public(tk);
48,154✔
220

221
    if (is_class && !m_short_circuit) {
48,154✔
222
        Instruction::AddTable instr;
39,986✔
223
        instr.table = emit_class_name(name);
39,986✔
224
        auto field = m_encoder.intern_string(pk_field);
39,986✔
225
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
39,986✔
226
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
39,986✔
227
        if (!is_valid_key_type(spec.pk_type)) {
39,986✔
228
            unsupported_instruction();
×
229
        }
×
230
        instr.type = std::move(spec);
39,986✔
231
        emit(instr);
39,986✔
232
    }
39,986✔
233
}
48,154✔
234

235
void SyncReplication::create_object(const Table* table, GlobalKey oid)
236
{
24,902✔
237
    if (table->is_embedded()) {
24,902✔
238
        unsupported_instruction(); // FIXME: TODO
×
239
    }
×
240

241
    Replication::create_object(table, oid);
24,902✔
242
    if (select_table(*table)) {
24,902✔
243
        if (table->get_primary_key_column()) {
72✔
244
            // Trying to create object without a primary key in a table that
245
            // has a primary key column.
246
            unsupported_instruction();
×
247
        }
×
248
        Instruction::CreateObject instr;
72✔
249
        instr.table = m_last_class_name;
72✔
250
        instr.object = oid;
72✔
251
        emit(instr);
72✔
252
    }
72✔
253
}
24,902✔
254

255
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
256
{
487,754✔
257
    if (value.is_null()) {
487,754✔
258
        return mpark::monostate{};
300✔
259
    }
300✔
260
    else if (value.get_type() == type_Int) {
487,454✔
261
        return value.get<int64_t>();
361,602✔
262
    }
361,602✔
263
    else if (value.get_type() == type_String) {
125,852✔
264
        return m_encoder.intern_string(value.get<StringData>());
1,044✔
265
    }
1,044✔
266
    else if (value.get_type() == type_ObjectId) {
124,808✔
267
        return value.get<ObjectId>();
124,756✔
268
    }
124,756✔
269
    else if (value.get_type() == type_UUID) {
52✔
270
        return value.get<UUID>();
52✔
271
    }
52✔
272
    else {
×
273
        // Unsupported primary key type.
274
        unsupported_instruction();
×
275
    }
×
276
}
487,754✔
277

278
void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value)
279
{
163,866✔
280
    if (table->is_embedded()) {
163,866✔
281
        // Trying to create an object with a primary key in an embedded table.
282
        unsupported_instruction();
×
283
    }
×
284

285
    Replication::create_object_with_primary_key(table, oid, value);
163,866✔
286
    if (select_table(*table)) {
163,866✔
287
        if (m_write_validator) {
92,830✔
288
            m_write_validator(*table);
4,134✔
289
        }
4,134✔
290

291
        auto col = table->get_primary_key_column();
92,830✔
292
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
92,830✔
293
            Instruction::CreateObject instr;
92,826✔
294
            instr.table = m_last_class_name;
92,826✔
295
            instr.object = as_primary_key(value);
92,826✔
296
            emit(instr);
92,826✔
297
        }
92,826✔
298
        else {
4✔
299
            // Trying to create object with primary key in table without a
300
            // primary key column, or with wrong primary key type.
301
            unsupported_instruction();
4✔
302
        }
4✔
303
    }
92,830✔
304
}
163,866✔
305

306

307
void SyncReplication::erase_class(TableKey table_key, StringData table_name, size_t num_tables)
308
{
818✔
309
    Replication::erase_class(table_key, table_name, num_tables);
818✔
310

311
    bool is_class = m_transaction->table_is_public(table_key);
818✔
312

313
    if (is_class && !m_short_circuit) {
818✔
314
        Instruction::EraseTable instr;
46✔
315
        instr.table = emit_class_name(table_name);
46✔
316
        emit(instr);
46✔
317
    }
46✔
318

319
    m_last_table = nullptr;
818✔
320
}
818✔
321

322
void SyncReplication::rename_class(TableKey, StringData)
323
{
×
324
    unsupported_instruction();
×
325
}
×
326

327
void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name,
328
                                    Table* target_table)
329
{
201,494✔
330
    Replication::insert_column(table, col_key, type, name, target_table);
201,494✔
331
    using CollectionType = Instruction::CollectionType;
201,494✔
332

333
    if (select_table(*table)) {
201,494✔
334
        Instruction::AddColumn instr;
95,698✔
335
        instr.table = m_last_class_name;
95,698✔
336
        instr.field = m_encoder.intern_string(name);
95,698✔
337
        instr.nullable = col_key.is_nullable();
95,698✔
338
        instr.type = get_payload_type(type);
95,698✔
339
        instr.key_type = Instruction::Payload::Type::Null;
95,698✔
340

341
        if (col_key.is_list()) {
95,698✔
342
            instr.collection_type = CollectionType::List;
15,598✔
343
        }
15,598✔
344
        else if (col_key.is_dictionary()) {
80,100✔
345
            instr.collection_type = CollectionType::Dictionary;
12,928✔
346
            auto key_type = table->get_dictionary_key_type(col_key);
12,928✔
347
            REALM_ASSERT(key_type == type_String);
12,928✔
348
            instr.key_type = get_payload_type(key_type);
12,928✔
349
        }
12,928✔
350
        else if (col_key.is_set()) {
67,172✔
351
            instr.collection_type = CollectionType::Set;
11,288✔
352
        }
11,288✔
353
        else {
55,884✔
354
            REALM_ASSERT(!col_key.is_collection());
55,884✔
355
            instr.collection_type = CollectionType::Single;
55,884✔
356
        }
55,884✔
357

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

362
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
95,698✔
363
            instr.link_target_table = emit_class_name(*target_table);
9,284✔
364
        }
9,284✔
365
        else {
86,414✔
366
            instr.link_target_table = m_encoder.intern_string("");
86,414✔
367
        }
86,414✔
368
        emit(instr);
95,698✔
369
    }
95,698✔
370
}
201,494✔
371

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

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

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

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

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

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

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

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

455
    if (select_collection(list)) {
391,546✔
456
        Instruction::ArrayInsert instr;
200,914✔
457
        populate_path_instr(instr, list, uint32_t(ndx));
200,914✔
458
        instr.value = as_payload(list, value);
200,914✔
459
        instr.prior_size = uint32_t(prior_size);
200,914✔
460
        emit(instr);
200,914✔
461
    }
200,914✔
462
}
391,546✔
463

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

468
    if (select_table(*table)) {
4,038✔
469
        REALM_ASSERT(col != table->get_primary_key_column());
1,426✔
470

471
        Instruction::AddInteger instr;
1,426✔
472
        populate_path_instr(instr, *table, ndx, {col});
1,426✔
473
        instr.value = value;
1,426✔
474
        emit(instr);
1,426✔
475
    }
1,426✔
476
}
4,038✔
477

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

482
    if (key.is_unresolved()) {
1,056,144✔
483
        return;
452✔
484
    }
452✔
485

486
    if (col == table->get_primary_key_column()) {
1,055,692✔
487
        return;
20,600✔
488
    }
20,600✔
489

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

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

511
        Instruction::Update instr;
357,016✔
512
        populate_path_instr(instr, *table, key, {col});
357,016✔
513
        instr.value = as_payload(*table, col, value);
357,016✔
514
        instr.is_default = (variant == _impl::instr_SetDefault);
357,016✔
515
        emit(instr);
357,016✔
516
    }
357,016✔
517
}
1,035,056✔
518

519

520
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
521
{
25,076✔
522
    Replication::remove_object(table, row_ndx);
25,076✔
523
    if (table->is_embedded())
25,076✔
524
        return;
10,128✔
525
    if (table->is_asymmetric())
14,948✔
526
        return;
856✔
527
    REALM_ASSERT(!row_ndx.is_unresolved());
14,092✔
528

529
    if (select_table(*table)) {
14,092✔
530
        Instruction::EraseObject instr;
2,740✔
531
        instr.table = m_last_class_name;
2,740✔
532
        instr.object = primary_key_for_object(*table, row_ndx);
2,740✔
533
        emit(instr);
2,740✔
534
    }
2,740✔
535
}
14,092✔
536

537

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

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

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

567
void SyncReplication::list_clear(const CollectionBase& view)
568
{
3,034✔
569
    Replication::list_clear(view);
3,034✔
570
    if (select_collection(view)) {
3,034✔
571
        Instruction::Clear instr;
422✔
572
        populate_path_instr(instr, view);
422✔
573
        instr.collection_type = Instruction::CollectionType::List;
422✔
574
        emit(instr);
422✔
575
    }
422✔
576
}
3,034✔
577

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

582
    if (select_collection(set)) {
23,064✔
583
        Instruction::SetInsert instr;
20,724✔
584
        populate_path_instr(instr, set);
20,724✔
585
        instr.value = as_payload(set, value);
20,724✔
586
        emit(instr);
20,724✔
587
    }
20,724✔
588
}
23,064✔
589

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

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

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

606
    if (select_collection(set)) {
244✔
607
        Instruction::Clear instr;
212✔
608
        populate_path_instr(instr, set);
212✔
609
        instr.collection_type = Instruction::CollectionType::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
{
31,552✔
616
    // If link is unresolved, it should not be communicated.
617
    if (value.is_unresolved_link()) {
31,552✔
618
        return;
108✔
619
    }
108✔
620

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

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

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

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

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

661
void SyncReplication::dictionary_clear(const CollectionBase& dict)
662
{
436✔
663
    Replication::dictionary_clear(dict);
436✔
664

665
    if (select_collection(dict)) {
436✔
666
        Instruction::Clear instr;
284✔
667
        populate_path_instr(instr, dict);
284✔
668
        instr.collection_type = Instruction::CollectionType::Dictionary;
284✔
669
        emit(instr);
284✔
670
    }
284✔
671
}
436✔
672

673
void SyncReplication::nullify_link(const Table* table, ColKey col_key, ObjKey ndx)
674
{
18✔
675
    Replication::nullify_link(table, col_key, ndx);
18✔
676

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

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

699
void SyncReplication::unsupported_instruction() const
700
{
×
701
    throw realm::sync::TransformError{"Unsupported instruction"};
×
702
}
×
703

704
bool SyncReplication::select_table(const Table& table)
705
{
2,927,088✔
706
    if (is_short_circuited()) {
2,927,088✔
707
        return false;
806,078✔
708
    }
806,078✔
709

710
    if (&table == m_last_table) {
2,121,010✔
711
        return true;
1,513,446✔
712
    }
1,513,446✔
713

714
    if (!m_transaction->table_is_public(table.get_key())) {
607,564✔
715
        return false;
296,718✔
716
    }
296,718✔
717

718
    m_last_class_name = emit_class_name(table);
310,846✔
719
    m_last_table = &table;
310,846✔
720
    m_last_field_name = StringData{};
310,846✔
721
    m_last_object = ObjKey{};
310,846✔
722
    m_last_primary_key.reset();
310,846✔
723
    return true;
310,846✔
724
}
607,564✔
725

726
bool SyncReplication::select_collection(const CollectionBase& view)
727
{
467,254✔
728
    if (view.get_owner_key().is_unresolved()) {
467,254✔
729
        return false;
×
730
    }
×
731

732
    return select_table(*view.get_table());
467,254✔
733
}
467,254✔
734

735
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
736
{
394,948✔
737
    bool should_emit = select_table(table);
394,948✔
738
    REALM_ASSERT(should_emit);
394,948✔
739

740
    if (table.get_primary_key_column()) {
394,948✔
741
        return as_primary_key(table.get_primary_key(key));
394,928✔
742
    }
394,928✔
743

744
    GlobalKey global_key = table.get_object_id(key);
20✔
745
    return global_key;
20✔
746
}
394,948✔
747

748
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
749
                                          Path path)
750
{
682,728✔
751
    REALM_ASSERT(key);
682,728✔
752
    // The first path entry will be the column key
753
    REALM_ASSERT(path[0].is_col_key());
682,728✔
754

755
    if (table.is_embedded()) {
682,728✔
756
        // For embedded objects, Obj::traverse_path() yields the top object
757
        // first, then objects in the path in order.
758
        auto obj = table.get_object(key);
63,702✔
759
        auto full_path = obj.get_path();
63,702✔
760
        // Populate top object in the normal way.
761
        auto top_table = table.get_parent_group()->get_table(full_path.top_table);
63,702✔
762

763
        full_path.path_from_top.emplace_back(table.get_column_name(path[0].get_col_key()));
63,702✔
764

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

772
    bool should_emit = select_table(table);
619,026✔
773
    REALM_ASSERT(should_emit);
619,026✔
774

775
    instr.table = m_last_class_name;
619,026✔
776
    if (m_last_object == key) {
619,026✔
777
        instr.object = *m_last_primary_key;
245,184✔
778
    }
245,184✔
779
    else {
373,842✔
780
        instr.object = primary_key_for_object(table, key);
373,842✔
781
        m_last_object = key;
373,842✔
782
        m_last_primary_key = instr.object;
373,842✔
783
    }
373,842✔
784

785
    StringData field_name = table.get_column_name(path[0].get_col_key());
619,026✔
786

787
    if (m_last_field_name == field_name) {
619,026✔
788
        instr.field = m_last_interned_field_name;
395,106✔
789
    }
395,106✔
790
    else {
223,920✔
791
        instr.field = m_encoder.intern_string(field_name);
223,920✔
792
        m_last_field_name = field_name;
223,920✔
793
        m_last_interned_field_name = instr.field;
223,920✔
794
    }
223,920✔
795
    size_t sz = path.size();
619,026✔
796
    instr.path.reserve(sz - 1);
619,026✔
797
    for (size_t i = 1; i < sz; i++) {
773,104✔
798
        auto& path_elem = path[i];
154,078✔
799
        if (path_elem.is_ndx()) {
154,078✔
800
            instr.path.push_back(uint32_t(path_elem.get_ndx()));
33,688✔
801
        }
33,688✔
802
        else {
120,390✔
803
            REALM_ASSERT(path_elem.is_key());
120,390✔
804
            InternString interned_field_name = m_encoder.intern_string(path_elem.get_key().c_str());
120,390✔
805
            instr.path.push_back(interned_field_name);
120,390✔
806
        }
120,390✔
807
    }
154,078✔
808
}
619,026✔
809

810
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& collection)
811
{
260,586✔
812
    ConstTableRef source_table = collection.get_table();
260,586✔
813
    ObjKey source_obj = collection.get_owner_key();
260,586✔
814
    populate_path_instr(instr, *source_table, source_obj, collection.get_short_path());
260,586✔
815
}
260,586✔
816

817
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
818
                                          uint32_t ndx)
819
{
207,884✔
820
    populate_path_instr(instr, list);
207,884✔
821
    instr.path.push_back(ndx);
207,884✔
822
}
207,884✔
823

824
} // namespace sync
825
} // 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