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

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

97.57
/src/realm/sync/instruction_replication.cpp
1
#include <realm/sync/instruction_replication.hpp>
2
#include <realm/transaction.hpp>
3
#include <realm/sync/transform.hpp> // TransformError
4
#include <realm/list.hpp>
5

6
namespace realm {
7
namespace sync {
8

9
void SyncReplication::reset()
10
{
745,842✔
11
    m_encoder.reset();
745,842✔
12

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

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

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

258,184✔
35
    switch (value.get_type()) {
530,930✔
36
        case type_Int: {
421,234✔
37
            return Instruction::Payload{value.get<int64_t>()};
297,326✔
38
        }
148,838✔
39
        case type_Bool: {
1,054✔
40
            return Instruction::Payload{value.get<bool>()};
2,118✔
41
        }
1,064✔
42
        case type_Float: {
1,116✔
43
            return Instruction::Payload{value.get<float>()};
2,240✔
44
        }
1,124✔
45
        case type_Double: {
1,552✔
46
            return Instruction::Payload{value.get<double>()};
3,112✔
47
        }
1,560✔
48
        case type_String: {
88,120✔
49
            auto str = value.get<StringData>();
189,444✔
50
            auto range = m_encoder.add_string_range(str);
189,444✔
51
            return Instruction::Payload{range};
189,444✔
52
        }
101,324✔
53
        case type_Binary: {
3,988✔
54
            auto binary = value.get<BinaryData>();
7,984✔
55
            auto range = m_encoder.add_string_range(StringData{binary.data(), binary.size()});
7,984✔
56
            const bool is_binary = true;
7,984✔
57
            return Instruction::Payload{range, is_binary};
7,984✔
58
        }
3,996✔
59
        case type_Timestamp: {
4,464✔
60
            return Instruction::Payload{value.get<Timestamp>()};
9,884✔
61
        }
5,420✔
62
        case type_Decimal: {
1,072✔
63
            return Instruction::Payload{value.get<Decimal128>()};
2,144✔
64
        }
1,072✔
65
        case type_ObjectId: {
6,672✔
66
            return Instruction::Payload{value.get<ObjectId>()};
13,352✔
67
        }
6,680✔
68
        case type_UUID: {
1,660✔
69
            return Instruction::Payload{value.get<UUID>()};
3,328✔
70
        }
1,668✔
71
        case type_TypedLink:
×
72
            [[fallthrough]];
✔
73
        case type_Link: {
×
74
            REALM_TERMINATE("as_payload() needs table/collection for links");
✔
75
            break;
×
76
        }
×
77
        case type_Mixed:
×
78
            [[fallthrough]];
✔
79
        case type_LinkList: {
×
80
            REALM_TERMINATE("Invalid payload type");
✔
81
            break;
×
82
        }
×
83
    }
×
84
    return Instruction::Payload{};
×
NEW
85
}
×
86

87
Instruction::Payload SyncReplication::as_payload(const CollectionBase& collection, Mixed value)
88
{
108,840✔
89
    return as_payload(*collection.get_table(), collection.get_col_key(), value);
108,840✔
90
}
108,840!
91

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

270,616✔
99
    if (value.is_type(type_Link)) {
270,616✔
100
        ConstTableRef target_table = table.get_link_target(col_key);
7,294✔
101
        if (target_table->is_embedded()) {
7,294✔
102
            // FIXME: Include target table name to support Mixed of Embedded Objects.
4,668✔
103
            return Instruction::Payload::ObjectValue{};
4,668✔
104
        }
113,862✔
105

111,820✔
106
        Instruction::Payload::Link link;
111,820✔
107
        link.target_table = emit_class_name(*target_table);
2,626✔
108
        link.target = primary_key_for_object(*target_table, value.get<ObjKey>());
2,626✔
109
        return Instruction::Payload{link};
288,888✔
110
    }
288,888✔
111
    else if (value.is_type(type_TypedLink)) {
263,322✔
112
        auto obj_link = value.get<ObjLink>();
5,740✔
113
        ConstTableRef target_table = m_transaction->get_table(obj_link.get_table_key());
5,740✔
114
        REALM_ASSERT(target_table);
5,132✔
115

290,786✔
116
        if (target_table->is_embedded()) {
12,776✔
117
            ConstTableRef static_target_table = table.get_link_target(col_key);
8,788✔
118

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

6,906✔
124
        Instruction::Payload::Link link;
6,906✔
125
        link.target_table = emit_class_name(*target_table);
6,906✔
126
        link.target = primary_key_for_object(*target_table, obj_link.get_obj_key());
6,906✔
127
        return Instruction::Payload{link};
281,998✔
128
    }
9,260✔
129
    else {
263,462✔
130
        return as_payload(value);
263,462✔
131
    }
258,190✔
132
}
275,888✔
133

1,168✔
134
InternString SyncReplication::emit_class_name(StringData table_name)
135
{
210,164✔
136
    return m_encoder.intern_string(Group::table_name_to_class_name(table_name));
208,996✔
137
}
210,164✔
138

1,168✔
139
InternString SyncReplication::emit_class_name(const Table& table)
140
{
192,496✔
141
    return emit_class_name(table.get_name());
192,496✔
142
}
192,496✔
143

4,104✔
144
Instruction::Payload::Type SyncReplication::get_payload_type(DataType type) const
4,104✔
145
{
345,984✔
146
    using Type = Instruction::Payload::Type;
345,984✔
147
    switch (type) {
345,984✔
148
        case type_Int:
308,470✔
149
            return Type::Int;
22,816✔
150
        case type_Bool:
2,084✔
151
            return Type::Bool;
215,308✔
152
        case type_String:
230,564✔
153
            return Type::String;
230,564✔
154
        case type_Binary:
2,176✔
155
            return Type::Binary;
2,176✔
156
        case type_Timestamp:
194,586✔
157
            return Type::Timestamp;
194,586✔
158
        case type_Float:
194,266✔
159
            return Type::Float;
2,182✔
160
        case type_Double:
2,172✔
161
            return Type::Double;
79,144✔
162
        case type_Decimal:
79,056✔
163
            return Type::Decimal;
79,056✔
164
        case type_Link:
26,200✔
165
            return Type::Link;
26,200✔
166
        case type_LinkList:
3,638✔
167
            return Type::Link;
3,638✔
168
        case type_TypedLink:
18,686✔
169
            return Type::Link;
18,686✔
170
        case type_ObjectId:
13,082✔
171
            return Type::ObjectId;
13,082✔
172
        case type_UUID:
4,918✔
173
            return Type::UUID;
4,918✔
174
        case type_Mixed:
5,070✔
175
            return Type::Null;
5,070✔
176
    }
2,244✔
177
    unsupported_instruction();
2,244✔
178
    return Type::Int; // Make compiler happy
2,156✔
179
}
2,156✔
180

3,032✔
181
void SyncReplication::add_class(TableKey tk, StringData name, Table::Type table_type)
3,032✔
182
{
10,952✔
183
    Replication::add_class(tk, name, table_type);
10,952✔
184

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

20,420✔
187
    if (is_class && !m_short_circuit) {
20,420✔
188
        Instruction::AddTable instr;
3,046✔
189
        instr.table = emit_class_name(name);
3,046✔
190
        if (table_type == Table::Type::Embedded) {
3,706✔
191
            instr.type = Instruction::AddTable::EmbeddedTable{};
3,678✔
192
        }
790✔
193
        else {
28✔
194
            auto field = m_encoder.intern_string(""); // FIXME: Should this be "_id"?
28✔
195
            const bool is_nullable = false;
28✔
196
            bool is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
28✔
197
            instr.type = Instruction::AddTable::TopLevelTable{
28✔
198
                field,
9,738✔
199
                Instruction::Payload::Type::GlobalKey,
9,738✔
200
                is_nullable,
28✔
201
                is_asymmetric,
9,738✔
202
            };
28✔
203
        }
9,738✔
204
        emit(instr);
1,708✔
205
    }
1,708✔
206
}
10,216✔
207

862✔
208
void SyncReplication::add_class_with_primary_key(TableKey tk, StringData name, DataType pk_type, StringData pk_field,
862✔
209
                                                 bool nullable, Table::Type table_type)
28✔
210
{
29,942✔
211
    Replication::add_class_with_primary_key(tk, name, pk_type, pk_field, nullable, table_type);
29,942✔
212

29,942✔
213
    bool is_class = m_transaction->table_is_public(tk);
29,942✔
214

29,942✔
215
    if (is_class && !m_short_circuit) {
29,942✔
216
        Instruction::AddTable instr;
19,262✔
217
        instr.table = emit_class_name(name);
19,262✔
218
        auto field = m_encoder.intern_string(pk_field);
19,262✔
219
        auto is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
19,262✔
220
        auto spec = Instruction::AddTable::TopLevelTable{field, get_payload_type(pk_type), nullable, is_asymmetric};
20,124✔
221
        if (!is_valid_key_type(spec.pk_type)) {
20,124✔
222
            unsupported_instruction();
9,710✔
223
        }
224
        instr.type = std::move(spec);
19,234✔
225
        emit(instr);
19,234✔
226
    }
49,696✔
227
}
60,376✔
228

229
void SyncReplication::create_object(const Table* table, GlobalKey oid)
30,462✔
230
{
7,276✔
231
    if (table->is_embedded()) {
37,738✔
232
        unsupported_instruction(); // FIXME: TODO
19,652✔
233
    }
19,652✔
234

26,928✔
235
    Replication::create_object(table, oid);
26,928✔
236
    if (select_table(*table)) {
26,928✔
237
        if (table->get_primary_key_column()) {
19,688✔
238
            // Trying to create object without a primary key in a table that
×
239
            // has a primary key column.
×
240
            unsupported_instruction();
19,652✔
241
        }
19,652✔
242
        Instruction::CreateObject instr;
19,688✔
243
        instr.table = m_last_class_name;
30,498✔
244
        instr.object = oid;
36✔
245
        emit(instr);
36✔
246
    }
7,612✔
247
}
14,852✔
248

249
Instruction::PrimaryKey SyncReplication::as_primary_key(Mixed value)
250
{
251,154✔
251
    if (value.is_null()) {
258,730✔
252
        return mpark::monostate{};
7,726✔
253
    }
186✔
254
    else if (value.get_type() == type_Int) {
251,004✔
255
        return value.get<int64_t>();
191,892✔
256
    }
191,892✔
257
    else if (value.get_type() == type_String) {
59,112✔
258
        return m_encoder.intern_string(value.get<StringData>());
6,732✔
259
    }
6,732✔
260
    else if (value.get_type() == type_ObjectId) {
52,452✔
261
        return value.get<ObjectId>();
52,422✔
262
    }
52,422✔
263
    else if (value.get_type() == type_UUID) {
7,606✔
264
        return value.get<UUID>();
26✔
265
    }
26✔
266
    else {
267,270✔
267
        // Unsupported primary key type.
267,270✔
268
        unsupported_instruction();
154✔
269
    }
154✔
270
}
518,270✔
271

205,856✔
272
void SyncReplication::create_object_with_primary_key(const Table* table, ObjKey oid, Mixed value)
205,856✔
273
{
171,828✔
274
    if (table->is_embedded()) {
117,520✔
275
        // Trying to create an object with a primary key in an embedded table.
6,952✔
276
        unsupported_instruction();
54,308✔
277
    }
54,280✔
278

164,848✔
279
    Replication::create_object_with_primary_key(table, oid, value);
110,596✔
280
    if (select_table(*table)) {
110,594✔
281
        if (m_write_validator) {
48,692✔
282
            m_write_validator(*table);
798✔
283
        }
796✔
284

48,668✔
285
        auto col = table->get_primary_key_column();
48,668✔
286
        if (col && ((value.is_null() && col.is_nullable()) || DataType(col.get_type()) == value.get_type())) {
315,932✔
287
            Instruction::CreateObject instr;
48,664✔
288
            instr.table = m_last_class_name;
48,664✔
289
            instr.object = as_primary_key(value);
174,742✔
290
            emit(instr);
174,742✔
291
        }
48,664✔
292
        else {
2✔
293
            // Trying to create object with primary key in table without a
2✔
294
            // primary key column, or with wrong primary key type.
2✔
295
            unsupported_instruction();
126,080✔
296
        }
126,080✔
297
    }
111,350✔
298
}
111,366✔
299

798✔
300

301
void SyncReplication::erase_class(TableKey table_key, size_t num_tables)
62,684✔
302
{
65,098✔
303
    Replication::erase_class(table_key, num_tables);
65,096✔
304

65,096✔
305
    StringData table_name = m_transaction->get_table_name(table_key);
65,096✔
306

65,096✔
307
    bool is_class = m_transaction->table_is_public(table_key);
65,096✔
308

2,416✔
309
    if (is_class && !m_short_circuit) {
2,414✔
310
        Instruction::EraseTable instr;
552✔
311
        instr.table = emit_class_name(table_name);
554✔
312
        emit(instr);
554✔
313
    }
63,236✔
314

128,492✔
315
    m_last_table = nullptr;
2,414✔
316
}
2,414✔
317

318
void SyncReplication::rename_class(TableKey, StringData)
2,554✔
319
{
2,554✔
320
    unsupported_instruction();
321
}
2,554✔
322

323
void SyncReplication::insert_column(const Table* table, ColKey col_key, DataType type, StringData name,
2,554✔
324
                                    Table* target_table)
598✔
325
{
80,764✔
326
    Replication::insert_column(table, col_key, type, name, target_table);
80,764✔
327
    using CollectionType = Instruction::AddColumn::CollectionType;
80,764✔
328

80,166✔
329
    if (select_table(*table)) {
82,720✔
330
        Instruction::AddColumn instr;
45,394✔
331
        instr.table = m_last_class_name;
42,840✔
332
        instr.field = m_encoder.intern_string(name);
42,840✔
333
        instr.nullable = col_key.is_nullable();
42,840✔
334
        instr.type = get_payload_type(type);
42,840✔
335

42,840✔
336
        if (col_key.is_list()) {
42,840✔
337
            instr.collection_type = CollectionType::List;
7,834✔
338
        }
7,834✔
339
        else if (col_key.is_dictionary()) {
119,568✔
340
            instr.collection_type = CollectionType::Dictionary;
90,316✔
341
            auto key_type = table->get_dictionary_key_type(col_key);
90,316✔
342
            REALM_ASSERT(key_type == type_String);
5,754✔
343
            instr.key_type = get_payload_type(key_type);
90,316✔
344
        }
51,542✔
345
        else if (col_key.is_set()) {
75,040✔
346
            instr.collection_type = CollectionType::Set;
51,206✔
347
            auto value_type = table->get_column_type(col_key);
51,206✔
348
            REALM_ASSERT(value_type != type_LinkList);
51,206✔
349
            instr.type = get_payload_type(value_type);
5,418✔
350
            instr.key_type = Instruction::Payload::Type::Null;
51,206✔
351
        }
13,580✔
352
        else {
31,996✔
353
            REALM_ASSERT(!col_key.is_collection());
61,460✔
354
            instr.collection_type = CollectionType::Single;
29,804✔
355
            instr.key_type = Instruction::Payload::Type::Null;
29,804✔
356
        }
29,804✔
357

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

48,402✔
362
        if (instr.type == Instruction::Payload::Type::Link && target_table) {
48,402✔
363
            instr.link_target_table = emit_class_name(*target_table);
9,384✔
364
        }
9,384✔
365
        else {
44,580✔
366
            instr.link_target_table = m_encoder.intern_string("");
65,112✔
367
        }
65,112✔
368
        emit(instr);
68,934✔
369
    }
68,934✔
370
}
106,260✔
371

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

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

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

4✔
391
void SyncReplication::list_set(const CollectionBase& list, size_t ndx, Mixed value)
392
{
6,334✔
393
    Mixed prior_value = list.get_any(ndx);
6,334✔
394
    bool prior_is_unresolved =
6,334✔
395
        prior_value.is_type(type_Link, type_TypedLink) && prior_value.get<ObjKey>().is_unresolved();
6,334✔
396

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

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

3,118✔
433
            Instruction::ArrayInsert insert_instr;
74✔
434
            populate_path_instr(insert_instr, list, static_cast<uint32_t>(ndx));
74✔
435
            insert_instr.prior_size = erase_instr.prior_size - 1;
3,048✔
436
            insert_instr.value = as_payload(list, value);
16✔
437
            emit(insert_instr);
16✔
438
        }
3,036✔
439
        else {
6,122✔
440
            Instruction::Update instr;
6,204✔
441
            populate_path_instr(instr, list, uint32_t(ndx));
3,094✔
442
            REALM_ASSERT(instr.is_array_update());
3,094✔
443
            instr.value = as_payload(list, value);
3,094✔
444
            instr.prior_size = uint32_t(list.size());
3,094✔
445
            emit(instr);
3,094✔
446
        }
3,090✔
447
    }
3,098✔
448
}
6,334✔
449

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

170,844✔
457
    if (select_collection(list)) {
170,844✔
458
        Instruction::ArrayInsert instr;
86,466✔
459
        populate_path_instr(instr, list, uint32_t(ndx));
86,466✔
460
        instr.value = as_payload(list, value);
86,466✔
461
        instr.prior_size = uint32_t(prior_size);
86,470✔
462
        emit(instr);
89,666✔
463
    }
83,356✔
464
}
167,734✔
465

167,292✔
466
void SyncReplication::add_int(const Table* table, ColKey col, ObjKey ndx, int_fast64_t value)
467
{
169,888✔
468
    Replication::add_int(table, col, ndx, value);
169,870✔
469

169,870✔
470
    if (select_table(*table)) {
2,596✔
471
        REALM_ASSERT(col != table->get_primary_key_column());
168,208✔
472

84,278✔
473
        Instruction::AddInteger instr;
84,278✔
474
        populate_path_instr(instr, *table, ndx, col);
84,278✔
475
        instr.value = value;
84,278✔
476
        emit(instr);
84,278✔
477
    }
84,278✔
478
}
169,888✔
479

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

430,978✔
484
    if (key.is_unresolved()) {
433,640✔
485
        return;
1,044✔
486
    }
82✔
487

431,858✔
488
    if (col == table->get_primary_key_column()) {
431,858✔
489
        return;
2,524✔
490
    }
2,524✔
491

430,296✔
492
    // If link is unresolved, it should not be communicated.
431,996✔
493
    if (value.is_type(type_Link, type_TypedLink) && value.get<ObjKey>().is_unresolved()) {
429,334✔
494
        return;
18✔
495
    }
458,538✔
496

887,836✔
497
    if (select_table(*table)) {
429,316✔
498
        // Omit of Update(NULL, default=true) for embedded object / dictionary
620,900✔
499
        // columns if the value is already NULL. This is a workaround for the
162,462✔
500
        // fact that erase always wins for nested structures, but we don't want
162,462✔
501
        // default values to win over later embedded object creation.
162,380✔
502
        if (variant == _impl::instr_SetDefault && value.is_null()) {
620,818✔
503
            if (col.get_type() == col_type_Link && table->get_object(key).is_null(col)) {
1,666✔
504
                return;
1,636✔
505
            }
4✔
506
            if (col.is_dictionary() && table->get_object(key).is_null(col)) {
30✔
507
                // Dictionary columns cannot currently be NULL, but this is
456,806✔
508
                // likely to change.
18✔
509
                return;
18✔
510
            }
511
        }
619,164✔
512

162,376✔
513
        Instruction::Update instr;
162,376✔
514
        populate_path_instr(instr, *table, key, col);
162,376✔
515
        instr.value = as_payload(*table, col, value);
162,376✔
516
        instr.is_default = (variant == _impl::instr_SetDefault);
339,462✔
517
        emit(instr);
162,406✔
518
    }
162,380✔
519
}
429,320✔
520

26!
521

522
void SyncReplication::remove_object(const Table* table, ObjKey row_ndx)
523
{
36,210✔
524
    Replication::remove_object(table, row_ndx);
36,210✔
525
    if (table->is_embedded())
213,292✔
526
        return;
3,052✔
527
    if (table->is_asymmetric())
210,240✔
528
        return;
177,502✔
529
    REALM_ASSERT(!row_ndx.is_unresolved());
209,820✔
530

209,820✔
531
    if (select_table(*table)) {
209,820✔
532
        Instruction::EraseObject instr;
187,276✔
533
        instr.table = m_last_class_name;
466,982✔
534
        instr.object = primary_key_for_object(*table, row_ndx);
10,194✔
535
        emit(instr);
10,194✔
536
    }
10,194✔
537
}
69,330✔
538

36,592✔
539

36,592✔
540
void SyncReplication::list_move(const CollectionBase& view, size_t from_ndx, size_t to_ndx)
3,062✔
541
{
33,700✔
542
    Replication::list_move(view, from_ndx, to_ndx);
590✔
543
    if (select_collection(view)) {
33,280✔
544
        Instruction::ArrayMove instr;
160✔
545
        populate_path_instr(instr, view, uint32_t(from_ndx));
33,270✔
546
        instr.ndx_2 = uint32_t(to_ndx);
10,458✔
547
        instr.prior_size = uint32_t(view.size());
10,458✔
548
        emit(instr);
10,458✔
549
    }
10,458✔
550
}
10,468✔
551

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

5,710✔
560
    size_t prior_size = list.size();
5,710✔
561
    if (select_collection(list)) {
5,710✔
562
        Instruction::ArrayErase instr;
2,648✔
563
        populate_path_instr(instr, list, uint32_t(ndx));
2,648✔
564
        instr.prior_size = uint32_t(prior_size);
2,658✔
565
        emit(instr);
2,488✔
566
    }
2,488✔
567
}
10,864✔
568

5,314✔
569
void SyncReplication::list_clear(const CollectionBase& view)
570
{
6,190✔
571
    Replication::list_clear(view);
6,184✔
572
    if (select_collection(view)) {
6,184✔
573
        Instruction::Clear instr;
132✔
574
        populate_path_instr(instr, view);
5,446✔
575
        emit(instr);
5,446✔
576
    }
2,472✔
577
}
3,216✔
578

2,340✔
579
void SyncReplication::set_insert(const CollectionBase& set, size_t set_ndx, Mixed value)
2,340✔
580
{
12,986✔
581
    Replication::set_insert(set, set_ndx, value);
15,960✔
582

10,646✔
583
    if (select_collection(set)) {
10,646✔
584
        Instruction::SetInsert instr;
10,398✔
585
        populate_path_instr(instr, set);
10,398✔
586
        instr.value = as_payload(set, value);
10,398✔
587
        emit(instr);
9,662✔
588
    }
9,662✔
589
}
10,776✔
590

130✔
591
void SyncReplication::set_erase(const CollectionBase& set, size_t set_ndx, Mixed value)
866✔
592
{
1,808✔
593
    Replication::set_erase(set, set_ndx, value);
1,808✔
594

12,606✔
595
    if (select_collection(set)) {
12,606✔
596
        Instruction::SetErase instr;
1,126✔
597
        populate_path_instr(instr, set);
11,924✔
598
        instr.value = as_payload(set, value);
10,810✔
599
        emit(instr);
10,810✔
600
    }
10,810✔
601
}
11,492✔
602

9,684✔
603
void SyncReplication::set_clear(const CollectionBase& set)
10,798✔
604
{
122✔
605
    Replication::set_clear(set);
122✔
606

1,930✔
607
    if (select_collection(set)) {
1,930✔
608
        Instruction::Clear instr;
106✔
609
        populate_path_instr(instr, set);
1,914✔
610
        emit(instr);
1,232✔
611
    }
1,232✔
612
}
1,248✔
613

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

13,200✔
621
    if (select_collection(dict)) {
13,322✔
622
        Instruction::Update instr;
11,838✔
623
        REALM_ASSERT(key.get_type() == type_String);
11,838✔
624
        populate_path_instr(instr, dict);
11,838✔
625
        StringData key_value = key.get_string();
11,838✔
626
        instr.path.push_back(m_encoder.intern_string(key_value));
11,854✔
627
        instr.value = as_payload(dict, value);
11,732✔
628
        instr.is_default = false;
11,732✔
629
        emit(instr);
25,114✔
630
    }
11,732✔
631
}
26,582✔
632

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

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

11,908✔
645
void SyncReplication::dictionary_erase(const CollectionBase& dict, size_t ndx, Mixed key)
13,376✔
646
{
1,146✔
647
    Replication::dictionary_erase(dict, ndx, key);
1,146✔
648

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

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

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

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

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

8✔
692
bool SyncReplication::select_table(const Table& table)
8✔
693
{
1,348,544✔
694
    if (is_short_circuited()) {
1,348,544✔
695
        return false;
406,826✔
696
    }
406,826✔
697

941,726✔
698
    if (&table == m_last_table) {
941,728✔
699
        return true;
678,216✔
700
    }
678,216✔
701

263,540✔
702
    if (!m_transaction->table_is_public(table.get_key())) {
263,540✔
703
        return false;
85,714✔
704
    }
85,714✔
705

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

714
bool SyncReplication::select_collection(const CollectionBase& view)
715
{
207,620✔
716
    if (view.get_owner_key().is_unresolved()) {
207,620✔
717
        return false;
718
    }
1,413,178✔
719

1,620,798✔
720
    return select_table(*view.get_table());
628,958✔
721
}
628,958✔
722

723
Instruction::PrimaryKey SyncReplication::primary_key_for_object(const Table& table, ObjKey key)
991,840✔
724
{
926,722✔
725
    bool should_emit = select_table(table);
926,722✔
726
    REALM_ASSERT(should_emit);
202,492✔
727

470,102✔
728
    if (table.get_primary_key_column()) {
289,478✔
729
        return as_primary_key(table.get_primary_key(key));
289,470✔
730
    }
202,484✔
731

180,632✔
732
    GlobalKey global_key = table.get_object_id(key);
180,632✔
733
    return global_key;
180,632✔
734
}
180,632✔
735

180,624✔
736
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const Table& table, ObjKey key,
180,624✔
737
                                          ColKey field)
180,624✔
738
{
307,052✔
739
    REALM_ASSERT(key);
307,052✔
740
    REALM_ASSERT(field);
514,138✔
741

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

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

367,510✔
765
            if (next_field.is_list()) {
45,564✔
766
                instr.path.push_back(uint32_t(index.get_int()));
337,792✔
767
            }
15,846✔
768
            else if (next_field.is_dictionary()) {
351,664✔
769
                InternString interned_field_name = m_encoder.intern_string(index.get_string());
12,330✔
770
                instr.path.push_back(interned_field_name);
12,330✔
771
            }
43,488✔
772
        };
76,722✔
773

31,076✔
774
        obj.traverse_path(visitor, path_sizer);
62,234✔
775

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

307,134✔
783
    bool should_emit = select_table(table);
307,134✔
784
    REALM_ASSERT(should_emit);
275,976✔
785

566,764✔
786
    instr.table = m_last_class_name;
566,764✔
787

275,976✔
788
    if (m_last_object == key) {
566,764✔
789
        instr.object = *m_last_primary_key;
381,088✔
790
    }
193,816✔
791
    else {
289,192✔
792
        instr.object = primary_key_for_object(table, key);
372,948✔
793
        m_last_object = key;
372,948✔
794
        m_last_primary_key = instr.object;
372,948✔
795
    }
372,948✔
796

463,248✔
797
    if (m_last_field == field) {
275,976✔
798
        instr.field = m_last_field_name;
463,716✔
799
    }
172,928✔
800
    else {
393,836✔
801
        instr.field = m_encoder.intern_string(table.get_column_name(field));
275,944✔
802
        m_last_field = field;
275,944✔
803
        m_last_field_name = instr.field;
220,940✔
804
    }
220,940✔
805
}
393,868✔
806

117,892✔
807
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list)
117,892✔
808
{
403,470✔
809
    ConstTableRef source_table = list.get_table();
403,470✔
810
    ObjKey source_obj = list.get_owner_key();
477,340✔
811
    ColKey source_field = list.get_col_key();
186,552✔
812
    populate_path_instr(instr, *source_table, source_obj, source_field);
186,552✔
813
}
128,552✔
814

15,870✔
815
void SyncReplication::populate_path_instr(Instruction::PathInstruction& instr, const CollectionBase& list,
58,000✔
816
                                          uint32_t ndx)
58,000✔
817
{
147,130✔
818
    populate_path_instr(instr, list);
147,130✔
819
    instr.path.m_path.push_back(ndx);
147,130✔
820
}
163,000✔
821

290,788✔
822
} // namespace sync
823
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc