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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM UTC coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

92.19
/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
{
747,588✔
11
    m_encoder.reset();
747,588✔
12

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

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

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

259,068✔
35
    auto type = value.get_type();
533,438✔
36
    switch (type) {
533,438✔
37
        case type_Int: {
298,902✔
38
            return Instruction::Payload{value.get<int64_t>()};
298,902✔
UNCOV
39
        }
×
40
        case type_Bool: {
2,118✔
41
            return Instruction::Payload{value.get<bool>()};
2,118✔
UNCOV
42
        }
×
43
        case type_Float: {
2,240✔
44
            return Instruction::Payload{value.get<float>()};
2,240✔
UNCOV
45
        }
×
46
        case type_Double: {
3,112✔
47
            return Instruction::Payload{value.get<double>()};
3,112✔
UNCOV
48
        }
×
49
        case type_String: {
189,456✔
50
            auto str = value.get<StringData>();
189,456✔
51
            auto range = m_encoder.add_string_range(str);
189,456✔
52
            return Instruction::Payload{range};
189,456✔
UNCOV
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✔
UNCOV
59
        }
×
60
        case type_Timestamp: {
9,892✔
61
            return Instruction::Payload{value.get<Timestamp>()};
9,892✔
UNCOV
62
        }
×
63
        case type_Decimal: {
2,144✔
64
            return Instruction::Payload{value.get<Decimal128>()};
2,144✔
UNCOV
65
        }
×
66
        case type_ObjectId: {
13,352✔
67
            return Instruction::Payload{value.get<ObjectId>()};
13,352✔
UNCOV
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
            [[fallthrough]];
×
80
        case type_LinkList: {
✔
81
            REALM_TERMINATE("Invalid payload type");
×
82
            break;
×
83
        }
916✔
84
    }
916✔
85
    if (type == type_Dictionary) {
916✔
86
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
272✔
UNCOV
87
            throw IllegalOperation("Cannot sync nested dictionary");
×
88
        return Instruction::Payload(Instruction::Payload::Dictionary());
272✔
89
    }
272✔
90
    else if (type == type_List) {
644✔
91
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
612✔
UNCOV
92
            throw IllegalOperation("Cannot sync nested list");
×
93
        return Instruction::Payload(Instruction::Payload::List());
612✔
94
    }
612✔
95
    else if (type == type_Set) {
32✔
96
        if (!SYNC_SUPPORTS_NESTED_COLLECTIONS)
32✔
UNCOV
97
            throw IllegalOperation("Cannot sync nested set");
×
98
        return Instruction::Payload(Instruction::Payload::Set());
32✔
99
    }
32✔
UNCOV
100
    return Instruction::Payload{};
×
UNCOV
101
}
×
102

103
Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value)
104
{
219,056✔
105
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
219,056✔
106
}
219,056✔
107

108
Instruction::Payload SyncReplication::as_payload(const Table& table, ColKey col_key, Mixed value)
109
{
559,970✔
110
    if (value.is_null()) {
559,970✔
111
        // FIXME: `Mixed::get_type()` asserts on null.
576✔
112
        return Instruction::Payload{};
1,160✔
113
    }
1,160✔
114

271,514✔
115
    if (value.is_type(type_Link)) {
558,810✔
116
        ConstTableRef target_table = table.get_link_target(col_key);
14,938✔
117
        if (target_table->is_embedded()) {
14,938✔
118
            // FIXME: Include target table name to support Mixed of Embedded Objects.
4,668✔
119
            return Instruction::Payload::ObjectValue{};
9,394✔
120
        }
9,394✔
121

2,626✔
122
        Instruction::Payload::Link link;
5,544✔
123
        link.target_table = emit_class_name(*target_table);
5,544✔
124
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
5,544✔
125
        return Instruction::Payload{link};
5,544✔
126
    }
5,544✔
127
    else if (value.is_type(type_TypedLink)) {
543,872✔
128
        auto obj_link = value.get<ObjLink>();
10,400✔
129
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
10,400✔
130
        REALM_ASSERT(target_table);
10,400✔
131

5,128✔
132
        if (target_table->is_embedded()) {
10,400✔
133
            ConstTableRef static_target_table = table.get_link_target(col_key);
2,312✔
134

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

3,984✔
140
        Instruction::Payload::Link link;
8,088✔
141
        link.target_table = emit_class_name(*target_table);
8,088✔
142
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
8,088✔
143
        return Instruction::Payload{link};
8,088✔
144
    }
8,088✔
145
    else {
533,472✔
146
        return as_payload(value);
533,472✔
147
    }
533,472✔
148
}
558,810✔
149

150
InternString SyncReplication::emit_class_name(StringData table_name)
151
{
424,578✔
152
    return m_encoder.intern_string(Group::table_name_to_class_name(table_name));
424,578✔
153
}
424,578✔
154

155
InternString SyncReplication::emit_class_name(const Table& table)
156
{
382,130✔
157
    return emit_class_name(table.get_name());
382,130✔
158
}
382,130✔
159

160
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
161
{
152,396✔
162
    using Type = Instruction::Payload::Type;
152,396✔
163
    switch (type) {
152,396✔
164
        case type_Int:
46,926✔
165
            return Type::Int;
46,926✔
166
        case type_Bool:
4,240✔
167
            return Type::Bool;
4,240✔
168
        case type_String:
35,974✔
169
            return Type::String;
35,974✔
170
        case type_Binary:
4,424✔
171
            return Type::Binary;
4,424✔
172
        case type_Timestamp:
5,264✔
173
            return Type::Timestamp;
5,264✔
174
        case type_Float:
4,436✔
175
            return Type::Float;
4,436✔
176
        case type_Double:
4,416✔
177
            return Type::Double;
4,416✔
178
        case type_Decimal:
4,240✔
179
            return Type::Decimal;
4,240✔
180
        case type_Link:
5,632✔
181
            return Type::Link;
5,632✔
182
        case type_LinkList:
3,108✔
183
            return Type::Link;
3,108✔
UNCOV
184
        case type_TypedLink:
✔
UNCOV
185
            return Type::Link;
×
186
        case type_ObjectId:
22,640✔
187
            return Type::ObjectId;
22,640✔
188
        case type_UUID:
4,384✔
189
            return Type::UUID;
4,384✔
190
        case type_Mixed:
6,712✔
191
            return Type::Null;
6,712✔
UNCOV
192
    }
×
UNCOV
193
    unsupported_instruction();
×
UNCOV
194
    return Type::Int; // Make compiler happy
×
UNCOV
195
}
×
196

197
void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type)
198
{
19,372✔
199
    Replication::add_class(tk, name, table_type);
19,372✔
200

9,494✔
201
    bool is_class = m_transaction->table_is_public(tk);
19,372✔
202

9,494✔
203
    if (is_class && !m_short_circuit) {
19,372✔
204
        Instruction::AddTable instr;
1,708✔
205
        instr.table = emit_class_name(name);
1,708✔
206
        if (table_type == Table::Type::Embedded) {
1,708✔
207
            instr.type = Instruction::AddTable::EmbeddedTable{};
1,652✔
208
        }
1,652✔
209
        else {
56✔
210
            auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"?
56✔
211
            const bool is_nullable = false;
56✔
212
            bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
56✔
213
            instr.type = Instruction::AddTable::TopLevelTable{
56✔
214
                field,
56✔
215
                Instruction::Payload::Type::GlobalKey,
56✔
216
                is_nullable,
56✔
217
                is_asymmetric,
56✔
218
            };
56✔
219
        }
56✔
220
        emit(instr);
1,708✔
221
    }
1,708✔
222
}
19,372✔
223

224
void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field,
225
                                                 bool nullable, Table::Type table_type)
226
{
61,218✔
227
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
61,218✔
228

30,356✔
229
    bool is_class = m_transaction->table_is_public(tk);
61,218✔
230

30,356✔
231
    if (is_class && !m_short_circuit) {
61,218✔
232
        Instruction::AddTable instr;
39,586✔
233
        instr.table = emit_class_name(name);
39,586✔
234
        auto field = m_encoder.intern_string(pk_field);
39,586✔
235
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
39,586✔
236
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
39,586✔
237
        if (!is_valid_key_type(spec.pk_type)) {
39,586✔
238
            unsupported_instruction();
×
239
        }
×
240
        instr.type = std::move(spec);
39,586✔
241
        emit(instr);
39,586✔
242
    }
39,586✔
243
}
61,218✔
244

245
void SyncReplication::create_object(const Table* table, GlobalKey oid)
246
{
15,188✔
247
    if (table->is_embedded()) {
15,188✔
UNCOV
248
        unsupported_instruction(); // FIXME: TODO
×
UNCOV
249
    }
×
250

7,444✔
251
    Replication::create_object(table, oid);
15,188✔
252
    if (select_table(*table)) {
15,188✔
253
        if (table->get_primary_key_column()) {
72✔
254
            // Trying to create object without a primary key in a table that
255
            // has a primary key column.
UNCOV
256
            unsupported_instruction();
×
UNCOV
257
        }
×
258
        Instruction::CreateObject instr;
72✔
259
        instr.table = m_last_class_name;
72✔
260
        instr.object = oid;
72✔
261
        emit(instr);
72✔
262
    }
72✔
263
}
15,188✔
264

265
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
266
{
519,988✔
267
    if (value.is_null()) {
519,988✔
268
        return mpark::monostate{};
300✔
269
    }
300✔
270
    else if (value.get_type() == type_Int) {
519,688✔
271
        return value.get<int64_t>();
397,346✔
272
    }
397,346✔
273
    else if (value.get_type() == type_String) {
122,342✔
274
        return m_encoder.intern_string(value.get<StringData>());
13,792✔
275
    }
13,792✔
276
    else if (value.get_type() == type_ObjectId) {
108,550✔
277
        return value.get<ObjectId>();
108,484✔
278
    }
108,484✔
279
    else if (value.get_type() == type_UUID) {
68✔
280
        return value.get<UUID>();
52✔
281
    }
52✔
282
    else {
2,147,483,663✔
283
        // Unsupported primary key type.
16✔
284
        unsupported_instruction();
2,147,483,663✔
285
    }
2,147,483,663✔
286
}
519,988✔
287

288
void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value)
289
{
237,894✔
290
    if (table->is_embedded()) {
237,894✔
291
        // Trying to create an object with a primary key in an embedded table.
UNCOV
292
        unsupported_instruction();
×
UNCOV
293
    }
×
294

111,910✔
295
    Replication::create_object_with_primary_key(table, oid, value);
237,894✔
296
    if (select_table(*table)) {
237,894✔
297
        if (m_write_validator) {
112,444✔
298
            m_write_validator(*table);
1,594✔
299
        }
1,594✔
300

49,362✔
301
        auto col = table->get_primary_key_column();
112,444✔
302
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
112,444✔
303
            Instruction::CreateObject instr;
112,440✔
304
            instr.table = m_last_class_name;
112,440✔
305
            instr.object = as_primary_key(value);
112,440✔
306
            emit(instr);
112,440✔
307
        }
112,440✔
308
        else {
4✔
309
            // Trying to create object with primary key in table without a
2✔
310
            // primary key column, or with wrong primary key type.
2✔
311
            unsupported_instruction();
4✔
312
        }
4✔
313
    }
112,444✔
314
}
237,894✔
315

316

317
void SyncReplication::erase_class(TableKey table_key, StringData table_name, size_t num_tables)
318
{
4,968✔
319
    Replication::erase_class(table_key, table_name, num_tables);
4,968✔
320

2,384✔
321
    bool is_class = m_transaction->table_is_public(table_key);
4,968✔
322

2,384✔
323
    if (is_class && !m_short_circuit) {
4,968✔
324
        Instruction::EraseTable instr;
1,160✔
325
        instr.table = emit_class_name(table_name);
1,160✔
326
        emit(instr);
1,160✔
327
    }
1,160✔
328

2,384✔
329
    m_last_table = nullptr;
4,968✔
330
}
4,968✔
331

332
void SyncReplication::rename_class(TableKey, StringData)
UNCOV
333
{
×
UNCOV
334
    unsupported_instruction();
×
UNCOV
335
}
×
336

337
void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name,
338
                                    Table* target_table)
339
{
167,028✔
340
    Replication::insert_column(table, col_key, type, name, target_table);
167,028✔
341
    using CollectionType = Instruction::AddColumn::CollectionType;
167,028✔
342

81,268✔
343
    if (select_table(*table)) {
167,028✔
344
        Instruction::AddColumn instr;
90,106✔
345
        instr.table = m_last_class_name;
90,106✔
346
        instr.field = m_encoder.intern_string(name);
90,106✔
347
        instr.nullable = col_key.is_nullable();
90,106✔
348
        instr.type = get_payload_type(type);
90,106✔
349

43,586✔
350
        if (col_key.is_list()) {
90,106✔
351
            instr.collection_type = CollectionType::List;
16,002✔
352
        }
16,002✔
353
        else if (col_key.is_dictionary()) {
74,104✔
354
            instr.collection_type = CollectionType::Dictionary;
11,724✔
355
            auto key_type = table->get_dictionary_key_type(col_key);
11,724✔
356
            REALM_ASSERT(key_type == type_String);
11,724✔
357
            instr.key_type = get_payload_type(key_type);
11,724✔
358
        }
11,724✔
359
        else if (col_key.is_set()) {
62,380✔
360
            instr.collection_type = CollectionType::Set;
10,980✔
361
            auto value_type = table->get_column_type(col_key);
10,980✔
362
            REALM_ASSERT(value_type != type_LinkList);
10,980✔
363
            instr.type = get_payload_type(value_type);
10,980✔
364
            instr.key_type = Instruction::Payload::Type::Null;
10,980✔
365
        }
10,980✔
366
        else {
51,400✔
367
            REALM_ASSERT(!col_key.is_collection());
51,400✔
368
            instr.collection_type = CollectionType::Single;
51,400✔
369
            instr.key_type = Instruction::Payload::Type::Null;
51,400✔
370
        }
51,400✔
371

43,586✔
372
        // Mixed columns are always nullable.
43,586✔
373
        REALM_ASSERT(instr.type != Instruction::Payload::Type::Null || instr.nullable ||
90,106!
374
                     instr.collection_type == CollectionType::Dictionary);
90,106✔
375

43,586✔
376
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
90,106✔
377
            instr.link_target_table = emit_class_name(*target_table);
8,148✔
378
        }
8,148✔
379
        else {
81,958✔
380
            instr.link_target_table = m_encoder.intern_string("");
81,958✔
381
        }
81,958✔
382
        emit(instr);
90,106✔
383
    }
90,106✔
384
}
167,028✔
385

386
void SyncReplication::erase_column(const Table* table, ColKey col_ndx)
387
{
8✔
388
    Replication::erase_column(table, col_ndx);
8✔
389

4✔
390
    if (select_table(*table)) {
8✔
391
        // Not allowed to remove PK/OID columns!
4✔
392
        REALM_ASSERT(col_ndx != table->get_primary_key_column());
8✔
393
        Instruction::EraseColumn instr;
8✔
394
        instr.table = m_last_class_name;
8✔
395
        instr.field = m_encoder.intern_string(table->get_column_name(col_ndx));
8✔
396
        emit(instr);
8✔
397
    }
8✔
398
}
8✔
399

400
void SyncReplication::rename_column(const Table*, ColKey, StringData)
UNCOV
401
{
×
UNCOV
402
    unsupported_instruction();
×
UNCOV
403
}
×
404

405
void SyncReplication::list_set(const CollectionBase& list, size_t ndx, Mixed value)
406
{
13,144✔
407
    Mixed prior_value = list.get_any(ndx);
13,144✔
408
    bool prior_is_unresolved =
13,144✔
409
        prior_value.is_type(type_Link, type_TypedLink) && prior_value.get<ObjKey>().is_unresolved();
13,144✔
410

6,424✔
411
    // If link is unresolved, it should not be communicated.
6,424✔
412
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
13,144✔
413
        // ... but reported internally as a deletion if prior value was not unresolved
78✔
414
        if (!prior_is_unresolved)
156✔
415
            Replication::list_erase(list, ndx);
156✔
416
    }
156✔
417
    else {
12,988✔
418
        if (prior_is_unresolved) {
12,988✔
419
            Replication::list_insert(list, ndx, value, 0 /* prior size not used */);
16✔
420
        }
16✔
421
        else {
12,972✔
422
            Replication::list_set(list, ndx, value);
12,972✔
423
        }
12,972✔
424
    }
12,988✔
425

6,424✔
426
    if (select_collection(list)) {
13,144✔
427
        // If this is an embedded object then we need to emit and erase/insert instruction so that the old
3,080✔
428
        // object gets cleared, otherwise you'll only see the Update ObjectValue instruction, which is idempotent,
3,080✔
429
        // and that will lead to corrupted prior size for array operations inside the embedded object during
3,080✔
430
        // changeset application.
3,080✔
431
        auto needs_insert_erase_sequence = [&] {
6,248✔
432
            if (value.is_type(type_Link)) {
6,248✔
433
                return list.get_target_table()->is_embedded();
140✔
434
            }
140✔
435
            else if (value.is_type(type_TypedLink)) {
6,108✔
436
                return m_transaction->get_table(value.get_link().get_table_key())->is_embedded();
24✔
437
            }
24✔
438
            return false;
6,084✔
439
        };
6,084✔
440
        if (needs_insert_erase_sequence()) {
6,248✔
441
            REALM_ASSERT(!list.is_null(ndx));
8✔
442
            Instruction::ArrayErase erase_instr;
8✔
443
            populate_path_instr(erase_instr, list, static_cast<uint32_t>(ndx));
8✔
444
            erase_instr.prior_size = uint32_t(list.size());
8✔
445
            emit(erase_instr);
8✔
446

4✔
447
            Instruction::ArrayInsert insert_instr;
8✔
448
            populate_path_instr(insert_instr, list, static_cast<uint32_t>(ndx));
8✔
449
            insert_instr.prior_size = erase_instr.prior_size - 1;
8✔
450
            insert_instr.value = as_payload(list, value);
8✔
451
            emit(insert_instr);
8✔
452
        }
8✔
453
        else {
6,240✔
454
            Instruction::Update instr;
6,240✔
455
            populate_path_instr(instr, list, uint32_t(ndx));
6,240✔
456
            REALM_ASSERT(instr.is_array_update());
6,240✔
457
            instr.value = as_payload(list, value);
6,240✔
458
            instr.prior_size = uint32_t(list.size());
6,240✔
459
            emit(instr);
6,240✔
460
        }
6,240✔
461
    }
6,248✔
462
}
13,144✔
463

464
void SyncReplication::list_insert(const CollectionBase& list, size_t ndx, Mixed value, size_t prior_size)
465
{
334,978✔
466
    // If link is unresolved, it should not be communicated.
167,458✔
467
    if (!(value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved())) {
334,978✔
468
        Replication::list_insert(list, ndx, value, prior_size);
334,942✔
469
    }
334,942✔
470

167,458✔
471
    if (select_collection(list)) {
334,978✔
472
        Instruction::ArrayInsert instr;
167,140✔
473
        populate_path_instr(instr, list, uint32_t(ndx));
167,140✔
474
        instr.value = as_payload(list, value);
167,140✔
475
        instr.prior_size = uint32_t(prior_size);
167,140✔
476
        emit(instr);
167,140✔
477
    }
167,140✔
478
}
334,978✔
479

480
void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value)
481
{
5,344✔
482
    Replication::add_int(table, col, ndx, value);
5,344✔
483

2,588✔
484
    if (select_table(*table)) {
5,344✔
485
        REALM_ASSERT(col != table->get_primary_key_column());
1,934✔
486

936✔
487
        Instruction::AddInteger instr;
1,934✔
488
        populate_path_instr(instr, *table, ndx, {col});
1,934✔
489
        instr.value = value;
1,934✔
490
        emit(instr);
1,934✔
491
    }
1,934✔
492
}
5,344✔
493

494
void SyncReplication::set(const Table* table, ColKey col, ObjKey key, Mixed value, _impl::Instruction variant)
495
{
890,508✔
496
    Replication::set(table, col, key, value, variant);
890,508✔
497

431,472✔
498
    if (key.is_unresolved()) {
890,508✔
499
        return;
164✔
500
    }
164✔
501

431,390✔
502
    if (col == table->get_primary_key_column()) {
890,344✔
503
        return;
3,364✔
504
    }
3,364✔
505

429,712✔
506
    // If link is unresolved, it should not be communicated.
429,712✔
507
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
886,980✔
508
        return;
36✔
509
    }
36✔
510

429,694✔
511
    if (select_table(*table)) {
886,944✔
512
        // Omit of Update(NULL, default=true) for embedded object / dictionary
162,918✔
513
        // columns if the value is already NULL. This is a workaround for the
162,918✔
514
        // fact that erase always wins for nested structures, but we don't want
162,918✔
515
        // default values to win over later embedded object creation.
162,918✔
516
        if (variant == _impl::instr_SetDefault && value.is_null()) {
340,914✔
517
            if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) {
52✔
518
                return;
8✔
519
            }
8✔
520
            if (col.is_dictionary() && table->get_object(key).is_null(col)) {
44!
521
                // Dictionary columns cannot currently be NULL, but this is
522
                // likely to change.
UNCOV
523
                return;
×
UNCOV
524
            }
×
525
        }
340,906✔
526

162,914✔
527
        Instruction::Update instr;
340,906✔
528
        populate_path_instr(instr, *table, key, {col});
340,906✔
529
        instr.value = as_payload(*table, col, value);
340,906✔
530
        instr.is_default = (variant == _impl::instr_SetDefault);
340,906✔
531
        emit(instr);
340,906✔
532
    }
340,906✔
533
}
886,944✔
534

535

536
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
537
{
73,620✔
538
    Replication::remove_object(table, row_ndx);
73,620✔
539
    if (table->is_embedded())
73,620✔
540
        return;
6,114✔
541
    if (table->is_asymmetric())
67,506✔
542
        return;
840✔
543
    REALM_ASSERT(!row_ndx.is_unresolved());
66,666✔
544

33,420✔
545
    if (select_table(*table)) {
66,666✔
546
        Instruction::EraseObject instr;
20,886✔
547
        instr.table = m_last_class_name;
20,886✔
548
        instr.object = primary_key_for_object(*table, row_ndx);
20,886✔
549
        emit(instr);
20,886✔
550
    }
20,886✔
551
}
66,666✔
552

553

554
void SyncReplication::list_move(const CollectionBase& view, size_t from_ndx, size_t to_ndx)
555
{
348✔
556
    Replication::list_move(view, from_ndx, to_ndx);
348✔
557
    if (select_collection(view)) {
348✔
558
        Instruction::ArrayMove instr;
328✔
559
        populate_path_instr(instr, view, uint32_t(from_ndx));
328✔
560
        instr.ndx_2 = uint32_t(to_ndx);
328✔
561
        instr.prior_size = uint32_t(view.size());
328✔
562
        emit(instr);
328✔
563
    }
328✔
564
}
348✔
565

566
void SyncReplication::list_erase(const CollectionBase& list, size_t ndx)
567
{
10,414✔
568
    Mixed prior_value = list.get_any(ndx);
10,414✔
569
    // If link is unresolved, it should not be communicated.
5,040✔
570
    if (!(prior_value.is_type(type_Link, type_TypedLink) && prior_value.get<ObjKey>().is_unresolved())) {
10,414✔
571
        Replication::list_erase(list, ndx);
10,402✔
572
    }
10,402✔
573

5,040✔
574
    size_t prior_size = list.size();
10,414✔
575
    if (select_collection(list)) {
10,414✔
576
        Instruction::ArrayErase instr;
4,540✔
577
        populate_path_instr(instr, list, uint32_t(ndx));
4,540✔
578
        instr.prior_size = uint32_t(prior_size);
4,540✔
579
        emit(instr);
4,540✔
580
    }
4,540✔
581
}
10,414✔
582

583
void SyncReplication::list_clear(const CollectionBase& view)
584
{
1,768✔
585
    Replication::list_clear(view);
1,768✔
586
    if (select_collection(view)) {
1,768✔
587
        Instruction::Clear instr;
268✔
588
        populate_path_instr(instr, view);
268✔
589
        emit(instr);
268✔
590
    }
268✔
591
}
1,768✔
592

593
void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
594
{
21,556✔
595
    Replication::set_insert(set, set_ndx, value);
21,556✔
596

10,702✔
597
    if (select_collection(set)) {
21,556✔
598
        Instruction::SetInsert instr;
19,268✔
599
        populate_path_instr(instr, set);
19,268✔
600
        instr.value = as_payload(set, value);
19,268✔
601
        emit(instr);
19,268✔
602
    }
19,268✔
603
}
21,556✔
604

605
void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
606
{
3,628✔
607
    Replication::set_erase(set, set_ndx, value);
3,628✔
608

1,814✔
609
    if (select_collection(set)) {
3,628✔
610
        Instruction::SetErase instr;
2,256✔
611
        populate_path_instr(instr, set);
2,256✔
612
        instr.value = as_payload(set, value);
2,256✔
613
        emit(instr);
2,256✔
614
    }
2,256✔
615
}
3,628✔
616

617
void SyncReplication::set_clear(const CollectionBase& set)
618
{
256✔
619
    Replication::set_clear(set);
256✔
620

128✔
621
    if (select_collection(set)) {
256✔
622
        Instruction::Clear instr;
216✔
623
        populate_path_instr(instr, set);
216✔
624
        emit(instr);
216✔
625
    }
216✔
626
}
256✔
627

628
void SyncReplication::dictionary_update(const CollectionBase& dict, const Mixed& key, const Mixed& value)
629
{
27,372✔
630
    // If link is unresolved, it should not be communicated.
13,598✔
631
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
27,372✔
632
        return;
12✔
633
    }
12✔
634

13,592✔
635
    if (select_collection(dict)) {
27,360✔
636
        Instruction::Update instr;
24,144✔
637
        REALM_ASSERT(key.get_type() == type_String);
24,144✔
638
        populate_path_instr(instr, dict);
24,144✔
639
        StringData key_value = key.get_string();
24,144✔
640
        instr.path.push_back(m_encoder.intern_string(key_value));
24,144✔
641
        instr.value = as_payload(dict, value);
24,144✔
642
        instr.is_default = false;
24,144✔
643
        emit(instr);
24,144✔
644
    }
24,144✔
645
}
27,360✔
646

647
void SyncReplication::dictionary_insert(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
648
{
23,868✔
649
    Replication::dictionary_insert(dict, ndx, key, value);
23,868✔
650
    dictionary_update(dict, key, value);
23,868✔
651
}
23,868✔
652

653
void SyncReplication::dictionary_set(const CollectionBase& dict, size_t ndx, Mixed key, Mixed value)
654
{
3,504✔
655
    Replication::dictionary_set(dict, ndx, key, value);
3,504✔
656
    dictionary_update(dict, key, value);
3,504✔
657
}
3,504✔
658

659
void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
660
{
1,848✔
661
    Replication::dictionary_erase(dict, ndx, key);
1,848✔
662

924✔
663
    if (select_collection(dict)) {
1,848✔
664
        Instruction::Update instr;
1,400✔
665
        REALM_ASSERT(key.get_type() == type_String);
1,400✔
666
        populate_path_instr(instr, dict);
1,400✔
667
        StringData key_value = key.get_string();
1,400✔
668
        instr.path.push_back(m_encoder.intern_string(key_value));
1,400✔
669
        instr.value = Instruction::Payload::Erased{};
1,400✔
670
        instr.is_default = false;
1,400✔
671
        emit(instr);
1,400✔
672
    }
1,400✔
673
}
1,848✔
674

675
void SyncReplication::dictionary_clear(const CollectionBase& dict)
676
{
200✔
677
    Replication::dictionary_clear(dict);
200✔
678

100✔
679
    if (select_collection(dict)) {
200✔
680
        Instruction::Clear instr;
176✔
681
        populate_path_instr(instr, dict);
176✔
682
        emit(instr);
176✔
683
    }
176✔
684
}
200✔
685

686
void SyncReplication::nullify_link(const Table* table, ColKey col_key, ObjKey ndx)
687
{
18✔
688
    Replication::nullify_link(table, col_key, ndx);
18✔
689

8✔
690
    if (select_table(*table)) {
18✔
691
        Instruction::Update instr;
14✔
692
        populate_path_instr(instr, *table, ndx, {col_key});
14✔
693
        REALM_ASSERT(!instr.is_array_update());
14✔
694
        instr.value = Instruction::Payload{realm::util::none};
14✔
695
        instr.is_default = false;
14✔
696
        emit(instr);
14✔
697
    }
14✔
698
}
18✔
699

700
void SyncReplication::link_list_nullify(const Lst<ObjKey>& view, size_t ndx)
701
{
76✔
702
    size_t prior_size = view.size();
76✔
703
    Replication::link_list_nullify(view, ndx);
76✔
704
    if (select_collection(view)) {
76✔
705
        Instruction::ArrayErase instr;
56✔
706
        populate_path_instr(instr, view, uint32_t(ndx));
56✔
707
        instr.prior_size = uint32_t(prior_size);
56✔
708
        emit(instr);
56✔
709
    }
56✔
710
}
76✔
711

712
void SyncReplication::unsupported_instruction() const
UNCOV
713
{
×
UNCOV
714
    throw realm::sync::TransformError{"Unsupported instruction"};
×
UNCOV
715
}
×
716

717
bool SyncReplication::select_table(const Table& table)
718
{
2,769,602✔
719
    if (is_short_circuited()) {
2,769,602✔
720
        return false;
827,182✔
721
    }
827,182✔
722

944,384✔
723
    if (&table == m_last_table) {
1,942,420✔
724
        return true;
1,408,446✔
725
    }
1,408,446✔
726

264,234✔
727
    if (!m_transaction->table_is_public(table.get_key())) {
533,974✔
728
        return false;
175,182✔
729
    }
175,182✔
730

177,298✔
731
    m_last_class_name = emit_class_name(table);
358,792✔
732
    m_last_table = &table;
358,792✔
733
    m_last_field_name = StringData{};
358,792✔
734
    m_last_object = ObjKey{};
358,792✔
735
    m_last_primary_key.reset();
358,792✔
736
    return true;
358,792✔
737
}
358,792✔
738

739
bool SyncReplication::select_collection(const CollectionBase& view)
740
{
415,576✔
741
    if (view.get_owner_key().is_unresolved()) {
415,576✔
UNCOV
742
        return false;
×
UNCOV
743
    }
×
744

207,262✔
745
    return select_table(*view.get_table());
415,576✔
746
}
415,576✔
747

748
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
749
{
407,608✔
750
    bool should_emit = select_table(table);
407,608✔
751
    REALM_ASSERT(should_emit);
407,608✔
752

202,382✔
753
    if (table.get_primary_key_column()) {
407,608✔
754
        return as_primary_key(table.get_primary_key(key));
407,574✔
755
    }
407,574✔
756

22✔
757
    GlobalKey global_key = table.get_object_id(key);
34✔
758
    return global_key;
34✔
759
}
34✔
760

761
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
762
                                          Path path)
763
{
631,152✔
764
    REALM_ASSERT(key);
631,152✔
765
    // The first path entry will be the column key
307,580✔
766
    REALM_ASSERT(path[0].is_col_key());
631,152✔
767

307,580✔
768
    if (table.is_embedded()) {
631,152✔
769
        // For embedded objects, Obj::traverse_path() yields the top object
31,076✔
770
        // first, then objects in the path in order.
31,076✔
771
        auto obj = table.get_object(key);
62,234✔
772
        auto full_path = obj.get_path();
62,234✔
773
        // Populate top object in the normal way.
31,076✔
774
        auto top_table = table.get_parent_group()->get_table(full_path.top_table);
62,234✔
775

31,076✔
776
        full_path.path_from_top.emplace_back(table.get_column_name(path[0].get_col_key()));
62,234✔
777

31,076✔
778
        for (auto it = path.begin() + 1; it != path.end(); ++it) {
62,234✔
UNCOV
779
            full_path.path_from_top.emplace_back(std::move(*it));
×
UNCOV
780
        }
×
781
        populate_path_instr(instr, *top_table, full_path.top_objkey, full_path.path_from_top);
62,234✔
782
        return;
62,234✔
783
    }
62,234✔
784

276,504✔
785
    bool should_emit = select_table(table);
568,918✔
786
    REALM_ASSERT(should_emit);
568,918✔
787

276,504✔
788
    instr.table = m_last_class_name;
568,918✔
789
    if (m_last_object == key) {
568,918✔
790
        instr.object = *m_last_primary_key;
195,804✔
791
    }
195,804✔
792
    else {
373,114✔
793
        instr.object = primary_key_for_object(table, key);
373,114✔
794
        m_last_object = key;
373,114✔
795
        m_last_primary_key = instr.object;
373,114✔
796
    }
373,114✔
797

276,504✔
798
    StringData field_name = table.get_column_name(path[0].get_col_key());
568,918✔
799

276,504✔
800
    if (m_last_field_name == field_name) {
568,918✔
801
        instr.field = m_last_interned_field_name;
347,848✔
802
    }
347,848✔
803
    else {
221,070✔
804
        instr.field = m_encoder.intern_string(field_name);
221,070✔
805
        m_last_field_name = field_name;
221,070✔
806
        m_last_interned_field_name = instr.field;
221,070✔
807
    }
221,070✔
808
    size_t sz = path.size();
568,918✔
809
    instr.path.reserve(sz - 1);
568,918✔
810
    for (size_t i = 1; i < sz; i++) {
717,292✔
811
        auto& path_elem = path[i];
148,374✔
812
        if (path_elem.is_ndx()) {
148,374✔
813
            instr.path.push_back(uint32_t(path_elem.get_ndx()));
32,312✔
814
        }
32,312✔
815
        else {
116,062✔
816
            REALM_ASSERT(path_elem.is_key());
116,062✔
817
            InternString interned_field_name = m_encoder.intern_string(path_elem.get_key().c_str());
116,062✔
818
            instr.path.push_back(interned_field_name);
116,062✔
819
        }
116,062✔
820
    }
148,374✔
821
}
568,918✔
822

823
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list)
824
{
226,048✔
825
    ConstTableRef source_table = list.get_table();
226,048✔
826
    ObjKey source_obj = list.get_owner_key();
226,048✔
827
    populate_path_instr(instr, *source_table, source_obj, list.get_short_path());
226,048✔
828
}
226,048✔
829

830
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
831
                                          uint32_t ndx)
832
{
178,320✔
833
    populate_path_instr(instr, list);
178,320✔
834
    instr.path.push_back(ndx);
178,320✔
835
}
178,320✔
836

837
} // namespace sync
838
} // 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