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

realm / realm-core / 2274

29 Apr 2024 07:20PM UTC coverage: 90.709% (-0.04%) from 90.748%
2274

push

Evergreen

web-flow
Merge pull request #7645 from realm/mwb/fix-warning

Fix warning introduced by PR #7632

101872 of 180246 branches covered (56.52%)

212397 of 234153 relevant lines covered (90.71%)

5624274.76 hits per line

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

68.66
/src/realm/sync/instruction_applier.cpp
1
#include <realm/sync/instruction_applier.hpp>
2
#include <realm/set.hpp>
3
#include <realm/util/scope_exit.hpp>
4

5
#include <realm/transaction.hpp>
6

7
namespace realm::sync {
8
namespace {
9

10
REALM_NORETURN void throw_bad_transaction_log(std::string msg)
11
{
8✔
12
    throw BadChangesetError{std::move(msg)};
8✔
13
}
8✔
14

15
} // namespace
16

17
REALM_NORETURN void InstructionApplier::bad_transaction_log(const std::string& msg) const
18
{
8✔
19
    if (m_last_object_key) {
8✔
20
        // If the last_object_key is valid then we should have a changeset and a current table
21
        REALM_ASSERT(m_log);
×
22
        REALM_ASSERT(m_last_table_name);
×
23
        std::stringstream ss;
×
24
        util::Optional<InternString> field_name;
×
25
        if (m_last_field_name) {
×
26
            field_name = m_last_field_name;
×
27
        }
×
28
        const instr::Path* cur_path = m_current_path ? &(*m_current_path) : nullptr;
×
29
        m_log->print_path(ss, m_last_table_name, *m_last_object_key, field_name, cur_path);
×
30
        throw_bad_transaction_log(
×
31
            util::format("%1 (instruction target: %2, version: %3, last_integrated_remote_version: %4, "
×
32
                         "origin_file_ident: %5, timestamp: %6)",
×
33
                         msg, ss.str(), m_log->version, m_log->last_integrated_remote_version,
×
34
                         m_log->origin_file_ident, m_log->origin_timestamp));
×
35
    }
×
36
    else if (m_last_table_name) {
8✔
37
        // We should have a changeset if we have a table name defined.
38
        REALM_ASSERT(m_log);
8✔
39
        throw_bad_transaction_log(
8✔
40
            util::format("%1 (instruction table: %2, version: %3, last_integrated_remote_version: %4, "
8✔
41
                         "origin_file_ident: %5, timestamp: %6)",
8✔
42
                         msg, m_log->get_string(m_last_table_name), m_log->version,
8✔
43
                         m_log->last_integrated_remote_version, m_log->origin_file_ident, m_log->origin_timestamp));
8✔
44
    }
8✔
45
    else if (m_log) {
×
46
        // If all we have is a changeset, then we should log whatever we can about it.
47
        throw_bad_transaction_log(util::format("%1 (version: %2, last_integrated_remote_version: %3, "
×
48
                                               "origin_file_ident: %4, timestamp: %5)",
×
49
                                               msg, m_log->version, m_log->last_integrated_remote_version,
×
50
                                               m_log->origin_file_ident, m_log->origin_timestamp));
×
51
    }
×
52
    throw_bad_transaction_log(std::move(msg));
8✔
53
}
8✔
54

55
template <class... Params>
56
REALM_NORETURN void InstructionApplier::bad_transaction_log(const char* msg, Params&&... params) const
57
{
8✔
58
    // FIXME: Avoid throwing in normal program flow (since changesets can come
59
    // in over the network, defective changesets are part of normal program
60
    // flow).
61
    bad_transaction_log(util::format(msg, std::forward<Params>(params)...));
8✔
62
}
8✔
63

64
StringData InstructionApplier::get_string(InternString str) const
65
{
710,494✔
66
    auto string = m_log->try_get_intern_string(str);
710,494✔
67
    if (REALM_UNLIKELY(!string))
710,494✔
68
        bad_transaction_log("string read fails");
×
69
    return m_log->get_string(*string);
710,494✔
70
}
710,494✔
71

72
StringData InstructionApplier::get_string(StringBufferRange range) const
73
{
175,842✔
74
    auto string = m_log->try_get_string(range);
175,842✔
75
    if (!string)
175,842✔
76
        bad_transaction_log("string read error");
×
77
    return *string;
175,842✔
78
}
175,842✔
79

80
BinaryData InstructionApplier::get_binary(StringBufferRange range) const
81
{
11,724✔
82
    auto string = m_log->try_get_string(range);
11,724✔
83
    if (!string)
11,724✔
84
        bad_transaction_log("binary read error");
×
85
    return BinaryData{string->data(), string->size()};
11,724✔
86
}
11,724✔
87

88
TableRef InstructionApplier::table_for_class_name(StringData class_name) const
89
{
112✔
90
    if (class_name.size() > Group::max_class_name_length)
112✔
91
        bad_transaction_log("class name too long");
×
92
    Group::TableNameBuffer buffer;
112✔
93
    return m_transaction.get_table(Group::class_name_to_table_name(class_name, buffer));
112✔
94
}
112✔
95

96
template <typename T>
97
struct TemporarySwapOut {
98
    explicit TemporarySwapOut(T& target)
99
        : target(target)
6,056✔
100
        , backup()
6,056✔
101
    {
13,220✔
102
        std::swap(target, backup);
13,220✔
103
    }
13,220✔
104

105
    ~TemporarySwapOut()
106
    {
13,220✔
107
        std::swap(backup, target);
13,220✔
108
    }
13,220✔
109

110
    T& target;
111
    T backup;
112
};
113

114
void InstructionApplier::operator()(const Instruction::AddTable& instr)
115
{
4,488✔
116
    auto table_name = get_table_name(instr);
4,488✔
117

118
    // Temporarily swap out the last object key so it doesn't get included in error messages
119
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
4,488✔
120

121
    auto add_table = util::overload{
4,488✔
122
        [&](const Instruction::AddTable::TopLevelTable& spec) {
4,488✔
123
            auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel);
4,190✔
124
            if (spec.pk_type == Instruction::Payload::Type::GlobalKey) {
4,190✔
125
                m_transaction.get_or_add_table(table_name, table_type);
8✔
126
            }
8✔
127
            else {
4,182✔
128
                if (!is_valid_key_type(spec.pk_type)) {
4,182✔
129
                    bad_transaction_log("Invalid primary key type '%1' while adding table '%2'", int8_t(spec.pk_type),
×
130
                                        table_name);
×
131
                }
×
132
                DataType pk_type = get_data_type(spec.pk_type);
4,182✔
133
                StringData pk_field = get_string(spec.pk_field);
4,182✔
134
                bool nullable = spec.pk_nullable;
4,182✔
135

136
                if (!m_transaction.get_or_add_table_with_primary_key(table_name, pk_type, pk_field, nullable,
4,182✔
137
                                                                     table_type)) {
4,182✔
138
                    bad_transaction_log("AddTable: The existing table '%1' has different properties", table_name);
×
139
                }
×
140
            }
4,182✔
141
        },
4,190✔
142
        [&](const Instruction::AddTable::EmbeddedTable&) {
4,488✔
143
            if (TableRef table = m_transaction.get_table(table_name)) {
296✔
144
                if (!table->is_embedded()) {
×
145
                    bad_transaction_log("AddTable: The existing table '%1' is not embedded", table_name);
×
146
                }
×
147
            }
×
148
            else {
296✔
149
                m_transaction.add_table(table_name, Table::Type::Embedded);
296✔
150
            }
296✔
151
        },
296✔
152
    };
4,488✔
153

154
    mpark::visit(std::move(add_table), instr.type);
4,488✔
155
}
4,488✔
156

157
void InstructionApplier::operator()(const Instruction::EraseTable& instr)
158
{
200✔
159
    auto table_name = get_table_name(instr);
200✔
160
    // Temporarily swap out the last object key so it doesn't get included in error messages
161
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
200✔
162

163
    if (REALM_UNLIKELY(REALM_COVER_NEVER(!m_transaction.has_table(table_name)))) {
200✔
164
        // FIXME: Should EraseTable be considered idempotent?
165
        bad_transaction_log("table does not exist");
×
166
    }
×
167

168
    m_transaction.remove_table(table_name);
200✔
169
}
200✔
170

171
void InstructionApplier::operator()(const Instruction::CreateObject& instr)
172
{
46,290✔
173
    auto table = get_table(instr);
46,290✔
174
    ColKey pk_col = table->get_primary_key_column();
46,290✔
175
    m_last_object_key = instr.object;
46,290✔
176

177
    mpark::visit(util::overload{
46,290✔
178
                     [&](mpark::monostate) {
46,290✔
179
                         if (!pk_col) {
32✔
180
                             bad_transaction_log("CreateObject(NULL) on table without a primary key");
×
181
                         }
×
182
                         if (!table->is_nullable(pk_col)) {
32✔
183
                             bad_transaction_log("CreateObject(NULL) on a table with a non-nullable primary key");
×
184
                         }
×
185
                         m_last_object = table->create_object_with_primary_key(util::none);
32✔
186
                     },
32✔
187
                     [&](int64_t pk) {
46,290✔
188
                         if (!pk_col) {
37,808✔
189
                             bad_transaction_log("CreateObject(Int) on table without a primary key");
×
190
                         }
×
191
                         if (table->get_column_type(pk_col) != type_Int) {
37,808✔
192
                             bad_transaction_log("CreateObject(Int) on a table with primary key type %1",
×
193
                                                 table->get_column_type(pk_col));
×
194
                         }
×
195
                         m_last_object = table->create_object_with_primary_key(pk);
37,808✔
196
                     },
37,808✔
197
                     [&](InternString pk) {
46,290✔
198
                         if (!pk_col) {
650✔
199
                             bad_transaction_log("CreateObject(String) on table without a primary key");
×
200
                         }
×
201
                         if (table->get_column_type(pk_col) != type_String) {
650✔
202
                             bad_transaction_log("CreateObject(String) on a table with primary key type %1",
×
203
                                                 table->get_column_type(pk_col));
×
204
                         }
×
205
                         StringData str = get_string(pk);
650✔
206
                         m_last_object = table->create_object_with_primary_key(str);
650✔
207
                     },
650✔
208
                     [&](const ObjectId& id) {
46,290✔
209
                         if (!pk_col) {
7,744✔
210
                             bad_transaction_log("CreateObject(ObjectId) on table without a primary key");
×
211
                         }
×
212
                         if (table->get_column_type(pk_col) != type_ObjectId) {
7,744✔
213
                             bad_transaction_log("CreateObject(ObjectId) on a table with primary key type %1",
×
214
                                                 table->get_column_type(pk_col));
×
215
                         }
×
216
                         m_last_object = table->create_object_with_primary_key(id);
7,744✔
217
                     },
7,744✔
218
                     [&](const UUID& id) {
46,290✔
219
                         if (!pk_col) {
48✔
220
                             bad_transaction_log("CreateObject(UUID) on table without a primary key");
×
221
                         }
×
222
                         if (table->get_column_type(pk_col) != type_UUID) {
48✔
223
                             bad_transaction_log("CreateObject(UUID) on a table with primary key type %1",
×
224
                                                 table->get_column_type(pk_col));
×
225
                         }
×
226
                         m_last_object = table->create_object_with_primary_key(id);
48✔
227
                     },
48✔
228
                     [&](GlobalKey key) {
46,290✔
229
                         if (pk_col) {
12✔
230
                             bad_transaction_log("CreateObject(GlobalKey) on table with a primary key");
×
231
                         }
×
232
                         m_last_object = table->create_object(key);
12✔
233
                     },
12✔
234
                 },
46,290✔
235
                 instr.object);
46,290✔
236
}
46,290✔
237

238
void InstructionApplier::operator()(const Instruction::EraseObject& instr)
239
{
3,720✔
240
    // FIXME: Log actions.
241
    // Note: EraseObject is idempotent.
242
    if (auto obj = get_top_object(instr, "EraseObject")) {
3,720✔
243
        // This call will prevent incoming links to be nullified/deleted
244
        obj->invalidate();
2,210✔
245
    }
2,210✔
246
    m_last_object.reset();
3,720✔
247
}
3,720✔
248

249
template <class F>
250
void InstructionApplier::visit_payload(const Instruction::Payload& payload, F&& visitor)
251
{
635,948✔
252
    using Type = Instruction::Payload::Type;
635,948✔
253

254
    const auto& data = payload.data;
635,948✔
255
    switch (payload.type) {
635,948✔
256
        case Type::ObjectValue:
2,676✔
257
            return visitor(Instruction::Payload::ObjectValue{});
2,676✔
258
        case Type::Set:
✔
259
            return visitor(Instruction::Payload::Set{});
×
260
        case Type::List:
792✔
261
            return visitor(Instruction::Payload::List{});
792✔
262
        case Type::Dictionary:
696✔
263
            return visitor(Instruction::Payload::Dictionary{});
696✔
264
        case Type::Erased:
236✔
265
            return visitor(Instruction::Payload::Erased{});
236✔
266
        case Type::GlobalKey:
✔
267
            return visitor(realm::util::none); // FIXME: Not sure about this
×
268
        case Type::Null:
380✔
269
            return visitor(realm::util::none);
380✔
270
        case Type::Int:
435,230✔
271
            return visitor(data.integer);
435,230✔
272
        case Type::Bool:
184✔
273
            return visitor(data.boolean);
184✔
274
        case Type::String: {
175,842✔
275
            StringData value = get_string(data.str);
175,842✔
276
            return visitor(value);
175,842✔
277
        }
×
278
        case Type::Binary: {
11,724✔
279
            BinaryData value = get_binary(data.binary);
11,724✔
280
            return visitor(value);
11,724✔
281
        }
×
282
        case Type::Timestamp:
3,102✔
283
            return visitor(data.timestamp);
3,102✔
284
        case Type::Float:
260✔
285
            return visitor(data.fnum);
260✔
286
        case Type::Double:
1,192✔
287
            return visitor(data.dnum);
1,192✔
288
        case Type::Decimal:
168✔
289
            return visitor(data.decimal);
168✔
290
        case Type::Link: {
2,036✔
291
            StringData class_name = get_string(data.link.target_table);
2,036✔
292
            Group::TableNameBuffer buffer;
2,036✔
293
            StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
2,036✔
294
            TableRef target_table = m_transaction.get_table(target_table_name);
2,036✔
295
            if (!target_table) {
2,036✔
296
                bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
297
            }
×
298
            if (target_table->is_embedded()) {
2,036✔
299
                bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
300
            }
×
301
            ObjKey target = get_object_key(*target_table, data.link.target);
2,036✔
302
            ObjLink link = ObjLink{target_table->get_key(), target};
2,036✔
303
            return visitor(link);
2,036✔
304
        }
×
305
        case Type::ObjectId:
1,128✔
306
            return visitor(data.object_id);
1,128✔
307
        case Type::UUID:
304✔
308
            return visitor(data.uuid);
304✔
309
    }
635,948✔
310
}
635,948✔
311

312
void InstructionApplier::operator()(const Instruction::Update& instr)
313
{
471,020✔
314
    struct UpdateResolver : public PathResolver {
471,020✔
315
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
471,020✔
316
            : PathResolver(applier, instr, "Update")
471,022✔
317
            , m_instr(instr)
471,022✔
318
        {
471,024✔
319
        }
471,024✔
320
        void on_property(Obj& obj, ColKey col) override
471,020✔
321
        {
471,020✔
322
            // Update of object field.
323

324
            auto table = obj.get_table();
466,936✔
325
            auto table_name = table->get_name();
466,936✔
326
            auto field_name = table->get_column_name(col);
466,936✔
327
            auto data_type = DataType(col.get_type());
466,936✔
328

329
            auto visitor = [&](const mpark::variant<ObjLink, Mixed, Instruction::Payload::ObjectValue,
466,936✔
330
                                                    Instruction::Payload::Dictionary, Instruction::Payload::List,
466,936✔
331
                                                    Instruction::Payload::Set, Instruction::Payload::Erased>& arg) {
466,942✔
332
                if (const auto link_ptr = mpark::get_if<ObjLink>(&arg)) {
466,936✔
333
                    if (data_type == type_Mixed || data_type == type_TypedLink) {
84✔
334
                        obj.set_any(col, *link_ptr, m_instr.is_default);
×
335
                    }
×
336
                    else if (data_type == type_Link) {
84✔
337
                        // Validate target table.
338
                        auto target_table = table->get_link_target(col);
84✔
339
                        if (target_table->get_key() != link_ptr->get_table_key()) {
84✔
340
                            m_applier->bad_transaction_log(
×
341
                                "Update: Target table mismatch (expected %1, got %2)", target_table->get_name(),
×
342
                                m_applier->m_transaction.get_table(link_ptr->get_table_key())->get_name());
×
343
                        }
×
344
                        obj.set<ObjKey>(col, link_ptr->get_obj_key(), m_instr.is_default);
84✔
345
                    }
84✔
346
                    else {
×
347
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
×
348
                                                       field_name, table_name, col.get_type(), type_Link);
×
349
                    }
×
350
                }
84✔
351
                else if (const auto mixed_ptr = mpark::get_if<Mixed>(&arg)) {
466,852✔
352
                    if (mixed_ptr->is_null()) {
465,374✔
353
                        if (col.is_nullable()) {
200✔
354
                            obj.set_null(col, m_instr.is_default);
200✔
355
                        }
200✔
356
                        else {
×
357
                            m_applier->bad_transaction_log("Update: NULL in non-nullable field '%2.%1'", field_name,
×
358
                                                           table_name);
×
359
                        }
×
360
                    }
200✔
361
                    else if (data_type == type_Mixed || mixed_ptr->get_type() == data_type) {
465,174✔
362
                        obj.set_any(col, *mixed_ptr, m_instr.is_default);
465,158✔
363
                    }
465,158✔
364
                    else {
16✔
365
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
16✔
366
                                                       field_name, table_name, col.get_type(), mixed_ptr->get_type());
16✔
367
                    }
16✔
368
                }
465,374✔
369
                else if (const auto obj_val_ptr = mpark::get_if<Instruction::Payload::ObjectValue>(&arg)) {
1,478✔
370
                    if (obj.is_null(col)) {
652✔
371
                        obj.create_and_set_linked_object(col);
624✔
372
                    }
624✔
373
                }
652✔
374
                else if (const auto erase_ptr = mpark::get_if<Instruction::Payload::Erased>(&arg)) {
826✔
375
                    m_applier->bad_transaction_log("Update: Dictionary erase at object field");
×
376
                }
×
377
                else if (mpark::get_if<Instruction::Payload::Dictionary>(&arg)) {
826✔
378
                    obj.set_collection(col, CollectionType::Dictionary);
360✔
379
                }
360✔
380
                else if (mpark::get_if<Instruction::Payload::List>(&arg)) {
466✔
381
                    obj.set_collection(col, CollectionType::List);
428✔
382
                }
428✔
383
                else if (mpark::get_if<Instruction::Payload::Set>(&arg)) {
38✔
384
                    obj.set_collection(col, CollectionType::Set);
×
385
                }
×
386
            };
466,936✔
387

388
            m_applier->visit_payload(m_instr.value, visitor);
466,936✔
389
        }
466,936✔
390
        Status on_list_index(LstBase& list, uint32_t index) override
471,020✔
391
        {
471,020✔
392
            // Update of list element.
393

394
            auto col = list.get_col_key();
312✔
395
            auto data_type = DataType(col.get_type());
312✔
396
            auto table = list.get_table();
312✔
397
            auto table_name = table->get_name();
312✔
398
            auto field_name = table->get_column_name(col);
312✔
399

400
            auto visitor = util::overload{
312✔
401
                [&](const ObjLink& link) {
312✔
402
                    if (data_type == type_TypedLink) {
156✔
403
                        REALM_ASSERT(dynamic_cast<Lst<ObjLink>*>(&list));
×
404
                        auto& link_list = static_cast<Lst<ObjLink>&>(list);
×
405
                        link_list.set(index, link);
×
406
                    }
×
407
                    else if (data_type == type_Mixed) {
156✔
408
                        REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
×
409
                        auto& mixed_list = static_cast<Lst<Mixed>&>(list);
×
410
                        mixed_list.set(index, link);
×
411
                    }
×
412
                    else if (data_type == type_Link) {
156✔
413
                        REALM_ASSERT(dynamic_cast<Lst<ObjKey>*>(&list));
156✔
414
                        auto& link_list = static_cast<Lst<ObjKey>&>(list);
156✔
415
                        // Validate the target.
416
                        auto target_table = table->get_link_target(col);
156✔
417
                        if (target_table->get_key() != link.get_table_key()) {
156✔
418
                            m_applier->bad_transaction_log(
×
419
                                "Update: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
420
                                m_applier->m_transaction.get_table(link.get_table_key())->get_name());
×
421
                        }
×
422
                        link_list.set(index, link.get_obj_key());
156✔
423
                    }
156✔
424
                    else {
×
425
                        m_applier->bad_transaction_log(
×
426
                            "Update: Type mismatch in list at '%2.%1' (expected link type, was %3)", field_name,
×
427
                            table_name, data_type);
×
428
                    }
×
429
                },
156✔
430
                [&](Mixed value) {
312✔
431
                    if (value.is_null()) {
88✔
432
                        if (col.is_nullable()) {
×
433
                            list.set_null(index);
×
434
                        }
×
435
                        else {
×
436
                            m_applier->bad_transaction_log("Update: NULL in non-nullable list '%2.%1'", field_name,
×
437
                                                           table_name);
×
438
                        }
×
439
                    }
×
440
                    else {
88✔
441
                        if (data_type == type_Mixed || value.get_type() == data_type) {
88✔
442
                            list.set_any(index, value);
88✔
443
                        }
88✔
444
                        else {
×
445
                            m_applier->bad_transaction_log(
×
446
                                "Update: Type mismatch in list at '%2.%1' (expected %3, got %4)", field_name,
×
447
                                table_name, data_type, value.get_type());
×
448
                        }
×
449
                    }
88✔
450
                },
88✔
451
                [&](const Instruction::Payload::ObjectValue&) {
312✔
452
                    // Embedded object creation is idempotent, and link lists cannot
453
                    // contain nulls, so this is a no-op.
454
                },
×
455
                [&](const Instruction::Payload::Dictionary&) {
312✔
456
                    list.set_collection(size_t(index), CollectionType::Dictionary);
16✔
457
                },
16✔
458
                [&](const Instruction::Payload::List&) {
312✔
459
                    list.set_collection(size_t(index), CollectionType::List);
52✔
460
                },
52✔
461
                [&](const Instruction::Payload::Set&) {
312✔
462
                    list.set_collection(size_t(index), CollectionType::Set);
×
463
                },
×
464
                [&](const Instruction::Payload::Erased&) {
312✔
465
                    m_applier->bad_transaction_log("Update: Dictionary erase of list element");
×
466
                },
×
467
            };
312✔
468

469
            m_applier->visit_payload(m_instr.value, visitor);
312✔
470
            return Status::Pending;
312✔
471
        }
312✔
472
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
471,020✔
473
        {
471,020✔
474
            // Update (insert) of dictionary element.
475

476
            auto visitor = util::overload{
3,792✔
477
                [&](Mixed value) {
3,792✔
478
                    if (value.is_null()) {
2,760✔
479
                        // FIXME: Separate handling of NULL is needed because
480
                        // `Mixed::get_type()` asserts on NULL.
481
                        dict.insert(key, value);
112✔
482
                    }
112✔
483
                    else if (value.get_type() == type_Link) {
2,648✔
484
                        m_applier->bad_transaction_log("Update: Untyped links are not supported in dictionaries.");
×
485
                    }
×
486
                    else {
2,648✔
487
                        dict.insert(key, value);
2,648✔
488
                    }
2,648✔
489
                },
2,760✔
490
                [&](const Instruction::Payload::Erased&) {
3,792✔
491
                    dict.try_erase(key);
236✔
492
                },
236✔
493
                [&](const Instruction::Payload::ObjectValue&) {
3,792✔
494
                    dict.create_and_insert_linked_object(key);
364✔
495
                },
364✔
496
                [&](const Instruction::Payload::Dictionary&) {
3,792✔
497
                    dict.insert_collection(key.get_string(), CollectionType::Dictionary);
180✔
498
                },
180✔
499
                [&](const Instruction::Payload::List&) {
3,792✔
500
                    dict.insert_collection(key.get_string(), CollectionType::List);
252✔
501
                },
252✔
502
                [&](const Instruction::Payload::Set&) {
3,792✔
503
                    dict.insert_collection(key.get_string(), CollectionType::Set);
×
504
                },
×
505
            };
3,792✔
506

507
            m_applier->visit_payload(m_instr.value, visitor);
3,792✔
508
            return Status::Pending;
3,792✔
509
        }
3,792✔
510

511
    private:
471,020✔
512
        const Instruction::Update& m_instr;
471,020✔
513
    };
471,020✔
514
    UpdateResolver resolver(this, instr);
471,020✔
515
    resolver.resolve();
471,020✔
516
}
471,020✔
517

518
void InstructionApplier::operator()(const Instruction::AddInteger& instr)
519
{
2,664✔
520
    // FIXME: Implement increments of array elements, dictionary values.
521
    struct AddIntegerResolver : public PathResolver {
2,664✔
522
        AddIntegerResolver(InstructionApplier* applier, const Instruction::AddInteger& instr)
2,664✔
523
            : PathResolver(applier, instr, "AddInteger")
2,664✔
524
            , m_instr(instr)
2,664✔
525
        {
2,664✔
526
        }
2,664✔
527
        void on_property(Obj& obj, ColKey col)
2,664✔
528
        {
2,664✔
529
            // Increment of object field.
530
            if (!obj.is_null(col)) {
2,664✔
531
                try {
2,608✔
532
                    obj.add_int(col, m_instr.value);
2,608✔
533
                }
2,608✔
534
                catch (const LogicError&) {
2,608✔
535
                    auto table = obj.get_table();
×
536
                    m_applier->bad_transaction_log("AddInteger: Not an integer field '%2.%1'",
×
537
                                                   table->get_column_name(col), table->get_name());
×
538
                }
×
539
            }
2,608✔
540
        }
2,664✔
541

542
    private:
2,664✔
543
        const Instruction::AddInteger& m_instr;
2,664✔
544
    };
2,664✔
545
    AddIntegerResolver resolver(this, instr);
2,664✔
546
    resolver.resolve();
2,664✔
547
}
2,664✔
548

549
void InstructionApplier::operator()(const Instruction::AddColumn& instr)
550
{
8,532✔
551
    using Type = Instruction::Payload::Type;
8,532✔
552
    using CollectionType = Instruction::CollectionType;
8,532✔
553

554
    // Temporarily swap out the last object key so it doesn't get included in error messages
555
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
8,532✔
556

557
    auto table = get_table(instr, "AddColumn");
8,532✔
558
    auto col_name = get_string(instr.field);
8,532✔
559

560
    if (ColKey existing_key = table->get_column_key(col_name)) {
8,532✔
561
        DataType new_type = get_data_type(instr.type);
316✔
562
        ColumnType existing_type = existing_key.get_type();
316✔
563
        if (existing_type != ColumnType(new_type)) {
316✔
564
            bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (expected %3, got %4)",
8✔
565
                                table->get_name(), col_name, existing_type, new_type);
8✔
566
        }
8✔
567
        bool existing_is_list = existing_key.is_list();
316✔
568
        if ((instr.collection_type == CollectionType::List) != existing_is_list) {
316✔
569
            bad_transaction_log(
×
570
                "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a list, the other is%4)",
×
571
                table->get_name(), col_name, existing_is_list ? "" : " not", existing_is_list ? " not" : "");
×
572
        }
×
573
        bool existing_is_set = existing_key.is_set();
316✔
574
        if ((instr.collection_type == CollectionType::Set) != existing_is_set) {
316✔
575
            bad_transaction_log(
×
576
                "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a set, the other is%4)",
×
577
                table->get_name(), col_name, existing_is_set ? "" : " not", existing_is_set ? " not" : "");
×
578
        }
×
579
        bool existing_is_dict = existing_key.is_dictionary();
316✔
580
        if ((instr.collection_type == CollectionType::Dictionary) != existing_is_dict) {
316✔
581
            bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a "
×
582
                                "dictionary, the other is%4)",
×
583
                                table->get_name(), col_name, existing_is_dict ? "" : " not",
×
584
                                existing_is_dict ? " not" : "");
×
585
        }
×
586
        if (new_type == type_Link) {
316✔
587
            Group::TableNameBuffer buffer;
16✔
588
            auto target_table_name = Group::class_name_to_table_name(get_string(instr.link_target_table), buffer);
16✔
589
            if (target_table_name != table->get_link_target(existing_key)->get_name()) {
16✔
590
                bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (link targets differ)",
×
591
                                    table->get_name(), col_name);
×
592
            }
×
593
        }
16✔
594
        return;
316✔
595
    }
316✔
596

597
    if (instr.collection_type == CollectionType::Dictionary && instr.key_type != Type::String) {
8,216✔
598
        bad_transaction_log("AddColumn '%1.%3' adding dictionary column with non-string keys", table->get_name(),
×
599
                            col_name);
×
600
    }
×
601

602
    if (instr.type != Type::Link) {
8,216✔
603
        DataType type = get_data_type(instr.type);
6,896✔
604
        switch (instr.collection_type) {
6,896✔
605
            case CollectionType::Single: {
6,066✔
606
                table->add_column(type, col_name, instr.nullable);
6,066✔
607
                break;
6,066✔
608
            }
×
609
            case CollectionType::List: {
590✔
610
                table->add_column_list(type, col_name, instr.nullable);
590✔
611
                break;
590✔
612
            }
×
613
            case CollectionType::Dictionary: {
104✔
614
                DataType key_type = get_data_type(instr.key_type);
104✔
615
                table->add_column_dictionary(type, col_name, instr.nullable, key_type);
104✔
616
                break;
104✔
617
            }
×
618
            case CollectionType::Set: {
136✔
619
                table->add_column_set(type, col_name, instr.nullable);
136✔
620
                break;
136✔
621
            }
×
622
        }
6,896✔
623
    }
6,896✔
624
    else {
1,320✔
625
        Group::TableNameBuffer buffer;
1,320✔
626
        auto target_table_name = get_string(instr.link_target_table);
1,320✔
627
        if (target_table_name.size() != 0) {
1,320✔
628
            TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer));
1,320✔
629
            if (!target) {
1,320✔
630
                bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(),
×
631
                                    col_name, target_table_name);
×
632
            }
×
633
            if (instr.collection_type == CollectionType::List) {
1,320✔
634
                table->add_column_list(*target, col_name);
604✔
635
            }
604✔
636
            else if (instr.collection_type == CollectionType::Set) {
716✔
637
                table->add_column_set(*target, col_name);
8✔
638
            }
8✔
639
            else if (instr.collection_type == CollectionType::Dictionary) {
708✔
640
                table->add_column_dictionary(*target, col_name);
32✔
641
            }
32✔
642
            else {
676✔
643
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
676✔
644
                table->add_column(*target, col_name);
676✔
645
            }
676✔
646
        }
1,320✔
647
        else {
×
648
            if (instr.collection_type == CollectionType::List) {
×
649
                table->add_column_list(type_TypedLink, col_name);
×
650
            }
×
651
            else {
×
652
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
×
653
                table->add_column(type_TypedLink, col_name);
×
654
            }
×
655
        }
×
656
    }
1,320✔
657
}
8,216✔
658

659
void InstructionApplier::operator()(const Instruction::EraseColumn& instr)
660
{
×
661
    // Temporarily swap out the last object key so it doesn't get included in error messages
662
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
×
663

664
    auto table = get_table(instr, "EraseColumn");
×
665
    auto col_name = get_string(instr.field);
×
666

667
    ColKey col = table->get_column_key(col_name);
×
668
    if (!col) {
×
669
        bad_transaction_log("EraseColumn '%1.%2' which doesn't exist", table->get_name(), col_name);
×
670
    }
×
671

672
    table->remove_column(col);
×
673
}
×
674

675
void InstructionApplier::operator()(const Instruction::ArrayInsert& instr)
676
{
162,632✔
677
    struct ArrayInsertResolver : public PathResolver {
162,632✔
678
        ArrayInsertResolver(InstructionApplier* applier, const Instruction::ArrayInsert& instr)
162,632✔
679
            : PathResolver(applier, instr, "ArrayInsert")
162,632✔
680
            , m_instr(instr)
162,632✔
681
        {
162,632✔
682
        }
162,632✔
683
        Status on_list_index(LstBase& list, uint32_t index) override
162,632✔
684
        {
162,634✔
685
            auto data_type = list.get_data_type();
162,634✔
686
            auto table = list.get_table();
162,634✔
687
            auto table_name = table->get_name();
162,634✔
688
            auto field_name = [&] {
162,634✔
689
                return table->get_column_name(list.get_col_key());
×
690
            };
×
691

692
            if (index > m_instr.prior_size) {
162,634✔
693
                m_applier->bad_transaction_log("ArrayInsert: Invalid insertion index (index = %1, prior_size = %2)",
×
694
                                               index, m_instr.prior_size);
×
695
            }
×
696

697
            if (index > list.size()) {
162,634✔
698
                m_applier->bad_transaction_log("ArrayInsert: Index out of bounds (%1 > %2)", index, list.size());
×
699
            }
×
700

701
            if (m_instr.prior_size != list.size()) {
162,634✔
702
                m_applier->bad_transaction_log("ArrayInsert: Invalid prior_size (list size = %1, prior_size = %2)",
×
703
                                               list.size(), m_instr.prior_size);
×
704
            }
×
705

706
            auto inserter = util::overload{
162,634✔
707
                [&](const ObjLink& link) {
162,634✔
708
                    if (data_type == type_TypedLink) {
1,252✔
709
                        REALM_ASSERT(dynamic_cast<Lst<ObjLink>*>(&list));
×
710
                        auto& link_list = static_cast<Lst<ObjLink>&>(list);
×
711
                        link_list.insert(index, link);
×
712
                    }
×
713
                    else if (data_type == type_Mixed) {
1,252✔
714
                        REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
164✔
715
                        auto& mixed_list = static_cast<Lst<Mixed>&>(list);
164✔
716
                        mixed_list.insert(index, link);
164✔
717
                    }
164✔
718
                    else if (data_type == type_Link) {
1,088✔
719
                        REALM_ASSERT(dynamic_cast<Lst<ObjKey>*>(&list));
1,088✔
720
                        auto& link_list = static_cast<Lst<ObjKey>&>(list);
1,088✔
721
                        // Validate the target.
722
                        auto target_table = table->get_link_target(list.get_col_key());
1,088✔
723
                        if (target_table->get_key() != link.get_table_key()) {
1,088✔
724
                            m_applier->bad_transaction_log(
×
725
                                "ArrayInsert: Target table mismatch (expected '%1', got '%2')",
×
726
                                target_table->get_name(),
×
727
                                m_applier->m_transaction.get_table(link.get_table_key())->get_name());
×
728
                        }
×
729
                        link_list.insert(index, link.get_obj_key());
1,088✔
730
                    }
1,088✔
731
                    else {
×
732
                        m_applier->bad_transaction_log(
×
733
                            "ArrayInsert: Type mismatch in list at '%2.%1' (expected link type, was %3)",
×
734
                            field_name(), table_name, data_type);
×
735
                    }
×
736
                },
1,252✔
737
                [&](Mixed value) {
162,634✔
738
                    if (data_type == type_Mixed) {
159,522✔
739
                        list.insert_any(index, value);
596✔
740
                    }
596✔
741
                    else if (value.is_null()) {
158,926✔
742
                        if (list.get_col_key().is_nullable()) {
48✔
743
                            list.insert_null(index);
48✔
744
                        }
48✔
745
                        else {
×
746
                            m_applier->bad_transaction_log("ArrayInsert: NULL in non-nullable list '%2.%1'",
×
747
                                                           field_name(), table_name);
×
748
                        }
×
749
                    }
48✔
750
                    else {
158,878✔
751
                        if (value.get_type() == data_type) {
158,878✔
752
                            list.insert_any(index, value);
158,878✔
753
                        }
158,878✔
754
                        else {
×
755
                            m_applier->bad_transaction_log(
×
756
                                "ArrayInsert: Type mismatch in list at '%2.%1' (expected %3, got %4)", field_name(),
×
757
                                table_name, data_type, value.get_type());
×
758
                        }
×
759
                    }
158,878✔
760
                },
159,522✔
761
                [&](const Instruction::Payload::ObjectValue&) {
162,634✔
762
                    if (data_type == type_Link) {
1,660✔
763
                        auto target_table = list.get_table()->get_link_target(list.get_col_key());
1,660✔
764
                        if (!target_table->is_embedded()) {
1,660✔
765
                            m_applier->bad_transaction_log(
×
766
                                "ArrayInsert: Creation of embedded object of type '%1', which is not "
×
767
                                "an embedded table",
×
768
                                target_table->get_name());
×
769
                        }
×
770

771
                        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
1,660✔
772
                        auto& link_list = static_cast<LnkLst&>(list);
1,660✔
773
                        link_list.create_and_insert_linked_object(index);
1,660✔
774
                    }
1,660✔
775
                    else {
×
776
                        m_applier->bad_transaction_log(
×
777
                            "ArrayInsert: Creation of embedded object in non-link list field '%2.%1'", field_name(),
×
778
                            table_name);
×
779
                    }
×
780
                },
1,660✔
781
                [&](const Instruction::Payload::Dictionary&) {
162,634✔
782
                    REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
140✔
783
                    auto& mixed_list = static_cast<Lst<Mixed>&>(list);
140✔
784
                    mixed_list.insert_collection(size_t(index), CollectionType::Dictionary);
140✔
785
                },
140✔
786
                [&](const Instruction::Payload::List&) {
162,634✔
787
                    REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
60✔
788
                    auto& mixed_list = static_cast<Lst<Mixed>&>(list);
60✔
789
                    mixed_list.insert_collection(size_t(index), CollectionType::List);
60✔
790
                },
60✔
791
                [&](const Instruction::Payload::Set&) {
162,634✔
792
                    REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
×
793
                    auto& mixed_list = static_cast<Lst<Mixed>&>(list);
×
794
                    mixed_list.insert_collection(size_t(index), CollectionType::Set);
×
795
                },
×
796
                [&](const Instruction::Payload::Erased&) {
162,634✔
797
                    m_applier->bad_transaction_log("Dictionary erase payload for ArrayInsert");
×
798
                },
×
799
            };
162,634✔
800

801
            m_applier->visit_payload(m_instr.value, inserter);
162,634✔
802
            return Status::Pending;
162,634✔
803
        }
162,634✔
804

805
    private:
162,632✔
806
        const Instruction::ArrayInsert& m_instr;
162,632✔
807
    };
162,632✔
808
    ArrayInsertResolver(this, instr).resolve();
162,632✔
809
}
162,632✔
810

811
void InstructionApplier::operator()(const Instruction::ArrayMove& instr)
812
{
112✔
813
    struct ArrayMoveResolver : public PathResolver {
112✔
814
        ArrayMoveResolver(InstructionApplier* applier, const Instruction::ArrayMove& instr)
112✔
815
            : PathResolver(applier, instr, "ArrayMove")
112✔
816
            , m_instr(instr)
112✔
817
        {
112✔
818
        }
112✔
819
        Status on_list_index(LstBase& list, uint32_t index) override
112✔
820
        {
112✔
821
            if (index >= list.size()) {
112✔
822
                m_applier->bad_transaction_log("ArrayMove from out of bounds (%1 >= %2)", m_instr.index(),
×
823
                                               list.size());
×
824
            }
×
825
            if (m_instr.ndx_2 >= list.size()) {
112✔
826
                m_applier->bad_transaction_log("ArrayMove to out of bounds (%1 >= %2)", m_instr.ndx_2, list.size());
×
827
            }
×
828
            if (index == m_instr.ndx_2) {
112✔
829
                // FIXME: Does this really need to be an error?
830
                m_applier->bad_transaction_log("ArrayMove to same location (%1)", m_instr.index());
×
831
            }
×
832
            if (m_instr.prior_size != list.size()) {
112✔
833
                m_applier->bad_transaction_log("ArrayMove: Invalid prior_size (list size = %1, prior_size = %2)",
×
834
                                               list.size(), m_instr.prior_size);
×
835
            }
×
836
            list.move(index, m_instr.ndx_2);
112✔
837
            return Status::Pending;
112✔
838
        }
112✔
839

840
    private:
112✔
841
        const Instruction::ArrayMove& m_instr;
112✔
842
    };
112✔
843
    ArrayMoveResolver(this, instr).resolve();
112✔
844
}
112✔
845

846
void InstructionApplier::operator()(const Instruction::ArrayErase& instr)
847
{
248✔
848
    struct ArrayEraseResolver : public PathResolver {
248✔
849
        ArrayEraseResolver(InstructionApplier* applier, const Instruction::ArrayErase& instr)
248✔
850
            : PathResolver(applier, instr, "ArrayErase")
248✔
851
            , m_instr(instr)
248✔
852
        {
248✔
853
        }
248✔
854
        Status on_list_index(LstBase& list, uint32_t index) override
248✔
855
        {
248✔
856
            if (index >= m_instr.prior_size) {
248✔
857
                m_applier->bad_transaction_log("ArrayErase: Invalid index (index = %1, prior_size = %2)", index,
×
858
                                               m_instr.prior_size);
×
859
            }
×
860
            if (index >= list.size()) {
248✔
861
                m_applier->bad_transaction_log("ArrayErase: Index out of bounds (%1 >= %2)", index, list.size());
×
862
            }
×
863
            if (m_instr.prior_size != list.size()) {
248✔
864
                m_applier->bad_transaction_log("ArrayErase: Invalid prior_size (list size = %1, prior_size = %2)",
×
865
                                               list.size(), m_instr.prior_size);
×
866
            }
×
867
            list.remove(index, index + 1);
248✔
868
            return Status::Pending;
248✔
869
        }
248✔
870

871
    private:
248✔
872
        const Instruction::ArrayErase& m_instr;
248✔
873
    };
248✔
874
    ArrayEraseResolver(this, instr).resolve();
248✔
875
}
248✔
876

877
void InstructionApplier::operator()(const Instruction::Clear& instr)
878
{
476✔
879
    // For collections and nested collections in Mixed, applying a Clear instruction
880
    // implicitly sets the collection type (of the clear instruction) too.
881
    struct ClearResolver : public PathResolver {
476✔
882
        ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr)
476✔
883
            : PathResolver(applier, instr, "Clear")
476✔
884
        {
476✔
885
            switch (instr.collection_type) {
476✔
886
                case Instruction::CollectionType::Single:
✔
887
                    break;
×
888
                case Instruction::CollectionType::List:
240✔
889
                    m_collection_type = CollectionType::List;
240✔
890
                    break;
240✔
891
                case Instruction::CollectionType::Dictionary:
172✔
892
                    m_collection_type = CollectionType::Dictionary;
172✔
893
                    break;
172✔
894
                case Instruction::CollectionType::Set:
64✔
895
                    m_collection_type = CollectionType::Set;
64✔
896
                    break;
64✔
897
            }
476✔
898
        }
476✔
899
        void on_list(LstBase& list) override
476✔
900
        {
476✔
901
            // list property
902
            if (m_collection_type && *m_collection_type != CollectionType::List) {
88✔
903
                m_applier->bad_transaction_log("Clear: Not a List");
×
904
            }
×
905
            list.clear();
88✔
906
        }
88✔
907
        Status on_list_index(LstBase& list, uint32_t index) override
476✔
908
        {
476✔
909
            REALM_ASSERT(m_collection_type);
24✔
910
            REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
24✔
911
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
24✔
912
            if (index >= mixed_list.size()) {
24✔
913
                m_applier->bad_transaction_log("Clear: Index out of bounds (%1 > %2)", index,
×
914
                                               mixed_list.size()); // Throws
×
915
            }
×
916
            auto val = mixed_list.get(index);
24✔
917
            if (val.is_type(type_Dictionary)) {
24✔
918
                Dictionary d(mixed_list, mixed_list.get_key(index));
12✔
919
                d.clear();
12✔
920
            }
12✔
921
            else if (val.is_type(type_List)) {
12✔
922
                Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
12✔
923
                l.clear();
12✔
924
            }
12✔
925
            else if (val.is_type(type_Set)) {
×
926
                m_applier->bad_transaction_log("Clear: Item at index %1 is a Set",
×
927
                                               index); // Throws
×
928
            }
×
929
            mixed_list.set_collection(size_t(index), *m_collection_type);
24✔
930
            return Status::Pending;
24✔
931
        }
24✔
932
        void on_dictionary(Dictionary& dict) override
476✔
933
        {
476✔
934
            // dictionary property
935
            if (m_collection_type && *m_collection_type != CollectionType::Dictionary) {
48✔
936
                m_applier->bad_transaction_log("Clear: Not a Dictionary");
×
937
            }
×
938
            dict.clear();
48✔
939
        }
48✔
940
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
476✔
941
        {
476✔
942
            REALM_ASSERT(m_collection_type);
28✔
943
            auto val = dict.get(key);
28✔
944
            if (val.is_type(type_Dictionary)) {
28✔
945
                Dictionary d(dict, dict.build_index(key));
12✔
946
                d.clear();
12✔
947
            }
12✔
948
            else if (val.is_type(type_List)) {
16✔
949
                Lst<Mixed> l(dict, dict.build_index(key));
16✔
950
                l.clear();
16✔
951
            }
16✔
952
            else if (val.is_type(type_Set)) {
×
953
                m_applier->bad_transaction_log("Clear: Item at key '%1' is a Set",
×
954
                                               key); // Throws
×
955
            }
×
956
            dict.insert_collection(key.get_string(), *m_collection_type);
28✔
957
            return Status::Pending;
28✔
958
        }
28✔
959
        void on_set(SetBase& set) override
476✔
960
        {
476✔
961
            // set property
962
            if (m_collection_type && *m_collection_type != CollectionType::Set) {
64✔
963
                m_applier->bad_transaction_log("Clear: Not a Set");
×
964
            }
×
965
            set.clear();
64✔
966
        }
64✔
967
        void on_property(Obj& obj, ColKey col_key) override
476✔
968
        {
476✔
969
            if (col_key.get_type() == col_type_Mixed) {
224✔
970
                REALM_ASSERT(m_collection_type);
224✔
971
                auto val = obj.get<Mixed>(col_key);
224✔
972
                if (val.is_type(type_Dictionary)) {
224✔
973
                    Dictionary dict(obj, col_key);
84✔
974
                    dict.clear();
84✔
975
                }
84✔
976
                else if (val.is_type(type_List)) {
140✔
977
                    Lst<Mixed> list(obj, col_key);
132✔
978
                    list.clear();
132✔
979
                }
132✔
980
                else if (val.is_type(type_Set)) {
8✔
981
                    m_applier->bad_transaction_log("Clear: Mixed property is a Set"); // Throws
×
982
                }
×
983
                obj.set_collection(col_key, *m_collection_type);
224✔
984
                return;
224✔
985
            }
224✔
986

987
            PathResolver::on_property(obj, col_key);
×
988
        }
×
989

990
    private:
476✔
991
        // The server may not send the type for collection properties (non-Mixed)
992
        // since the clients don't send it either before v13.
993
        std::optional<CollectionType> m_collection_type;
476✔
994
    };
476✔
995
    ClearResolver(this, instr).resolve();
476✔
996
}
476✔
997

998
bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr,
999
                                           const std::string_view& instr_name)
1000
{
40✔
1001
    struct AllowsNullsResolver : public PathResolver {
40✔
1002
        AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
40✔
1003
                            const std::string_view& instr_name)
40✔
1004
            : PathResolver(applier, instr, instr_name)
40✔
1005
            , m_allows_nulls(false)
40✔
1006
        {
40✔
1007
        }
40✔
1008
        Status on_list_index(LstBase&, uint32_t) override
40✔
1009
        {
40✔
1010
            return Status::Pending;
×
1011
        }
×
1012
        void on_list(LstBase&) override {}
40✔
1013
        void on_set(SetBase&) override {}
40✔
1014
        void on_dictionary(Dictionary&) override
40✔
1015
        {
40✔
1016
            m_allows_nulls = true;
×
1017
        }
×
1018
        Status on_dictionary_key(Dictionary&, Mixed) override
40✔
1019
        {
40✔
1020
            m_allows_nulls = true;
40✔
1021
            return Status::Pending;
40✔
1022
        }
40✔
1023
        void on_property(Obj&, ColKey) override
40✔
1024
        {
40✔
1025
            m_allows_nulls = true;
×
1026
        }
×
1027
        bool allows_nulls()
40✔
1028
        {
40✔
1029
            resolve();
40✔
1030
            return m_allows_nulls;
40✔
1031
        }
40✔
1032

1033
    private:
40✔
1034
        bool m_allows_nulls;
40✔
1035
    };
40✔
1036
    return AllowsNullsResolver(this, instr, instr_name).allows_nulls();
40✔
1037
}
40✔
1038

1039
std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const
1040
{
×
1041
    REALM_ASSERT(m_log);
×
1042
    std::stringstream ss;
×
1043
    m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
1044
    return ss.str();
×
1045
}
×
1046

1047
bool InstructionApplier::check_links_exist(const Instruction::Payload& payload)
1048
{
23,972✔
1049
    bool valid_payload = true;
23,972✔
1050
    using Type = Instruction::Payload::Type;
23,972✔
1051
    if (payload.type == Type::Link) {
23,972✔
1052
        StringData class_name = get_string(payload.data.link.target_table);
800✔
1053
        Group::TableNameBuffer buffer;
800✔
1054
        StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
800✔
1055
        TableRef target_table = m_transaction.get_table(target_table_name);
800✔
1056
        if (!target_table) {
800✔
1057
            bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
1058
        }
×
1059
        if (target_table->is_embedded()) {
800✔
1060
            bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
1061
        }
×
1062
        Mixed linked_pk =
800✔
1063
            mpark::visit(util::overload{[&](mpark::monostate) {
800✔
1064
                                            return Mixed{}; // the link exists and the pk is null
24✔
1065
                                        },
24✔
1066
                                        [&](int64_t pk) {
800✔
1067
                                            return Mixed{pk};
760✔
1068
                                        },
760✔
1069
                                        [&](InternString interned_pk) {
800✔
1070
                                            return Mixed{get_string(interned_pk)};
×
1071
                                        },
×
1072
                                        [&](GlobalKey) -> Mixed {
800✔
1073
                                            bad_transaction_log(
×
1074
                                                "Unexpected link to embedded object while validating a primary key");
×
1075
                                        },
×
1076
                                        [&](ObjectId pk) {
800✔
1077
                                            return Mixed{pk};
16✔
1078
                                        },
16✔
1079
                                        [&](UUID pk) {
800✔
1080
                                            return Mixed{pk};
×
1081
                                        }},
×
1082
                         payload.data.link.target);
800✔
1083

1084
        if (!target_table->find_primary_key(linked_pk)) {
800✔
1085
            valid_payload = false;
168✔
1086
        }
168✔
1087
    }
800✔
1088
    return valid_payload;
23,972✔
1089
}
23,972✔
1090

1091
void InstructionApplier::operator()(const Instruction::SetInsert& instr)
1092
{
1,760✔
1093
    struct SetInsertResolver : public PathResolver {
1,760✔
1094
        SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr)
1,760✔
1095
            : PathResolver(applier, instr, "SetInsert")
1,760✔
1096
            , m_instr(instr)
1,760✔
1097
        {
1,760✔
1098
        }
1,760✔
1099
        void on_property(Obj& obj, ColKey col) override
1,760✔
1100
        {
1,760✔
1101
            // This better be a mixed column
1102
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1103
            auto set = obj.get_set<Mixed>(col);
×
1104
            on_set(set);
×
1105
        }
×
1106
        void on_set(SetBase& set) override
1,760✔
1107
        {
1,760✔
1108
            auto col = set.get_col_key();
1,760✔
1109
            auto data_type = DataType(col.get_type());
1,760✔
1110
            auto table = set.get_table();
1,760✔
1111
            auto table_name = table->get_name();
1,760✔
1112
            auto field_name = table->get_column_name(col);
1,760✔
1113

1114
            auto inserter = util::overload{
1,760✔
1115
                [&](const ObjLink& link) {
1,760✔
1116
                    if (data_type == type_TypedLink) {
192✔
1117
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1118
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1119
                        link_set.insert(link);
×
1120
                    }
×
1121
                    else if (data_type == type_Mixed) {
192✔
1122
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
88✔
1123
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
88✔
1124
                        mixed_set.insert(link);
88✔
1125
                    }
88✔
1126
                    else if (data_type == type_Link) {
104✔
1127
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
104✔
1128
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
104✔
1129
                        // Validate the target.
1130
                        auto target_table = table->get_link_target(col);
104✔
1131
                        if (target_table->get_key() != link.get_table_key()) {
104✔
1132
                            m_applier->bad_transaction_log(
×
1133
                                "SetInsert: Target table mismatch (expected '%1', got '%2')",
×
1134
                                target_table->get_name(), table_name);
×
1135
                        }
×
1136
                        link_set.insert(link.get_obj_key());
104✔
1137
                    }
104✔
1138
                    else {
×
1139
                        m_applier->bad_transaction_log(
×
1140
                            "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1141
                            table_name, data_type);
×
1142
                    }
×
1143
                },
192✔
1144
                [&](Mixed value) {
1,760✔
1145
                    if (value.is_null() && !col.is_nullable()) {
1,568✔
1146
                        m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name,
×
1147
                                                       table_name);
×
1148
                    }
×
1149

1150
                    if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) {
1,568✔
1151
                        set.insert_any(value);
1,568✔
1152
                    }
1,568✔
1153
                    else {
×
1154
                        m_applier->bad_transaction_log(
×
1155
                            "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name,
×
1156
                            table_name, data_type, value.get_type());
×
1157
                    }
×
1158
                },
1,568✔
1159
                [&](const Instruction::Payload::ObjectValue&) {
1,760✔
1160
                    m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported.");
×
1161
                },
×
1162
                [&](const Instruction::Payload::Dictionary&) {
1,760✔
1163
                    m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported.");
×
1164
                },
×
1165
                [&](const Instruction::Payload::List&) {
1,760✔
1166
                    m_applier->bad_transaction_log("SetInsert: Sets of lists are not supported.");
×
1167
                },
×
1168
                [&](const Instruction::Payload::Set&) {
1,760✔
1169
                    m_applier->bad_transaction_log("SetInsert: Sets of sets are not supported.");
×
1170
                },
×
1171
                [&](const Instruction::Payload::Erased&) {
1,760✔
1172
                    m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert");
×
1173
                },
×
1174
            };
1,760✔
1175

1176
            m_applier->visit_payload(m_instr.value, inserter);
1,760✔
1177
        }
1,760✔
1178

1179
    private:
1,760✔
1180
        const Instruction::SetInsert& m_instr;
1,760✔
1181
    };
1,760✔
1182
    SetInsertResolver(this, instr).resolve();
1,760✔
1183
}
1,760✔
1184

1185
void InstructionApplier::operator()(const Instruction::SetErase& instr)
1186
{
492✔
1187
    struct SetEraseResolver : public PathResolver {
492✔
1188
        SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr)
492✔
1189
            : PathResolver(applier, instr, "SetErase")
492✔
1190
            , m_instr(instr)
492✔
1191
        {
492✔
1192
        }
492✔
1193
        void on_property(Obj& obj, ColKey col) override
492✔
1194
        {
492✔
1195
            // This better be a mixed column
1196
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1197
            auto set = obj.get_set<Mixed>(col);
×
1198
            on_set(set);
×
1199
        }
×
1200
        void on_set(SetBase& set) override
492✔
1201
        {
492✔
1202
            auto col = set.get_col_key();
492✔
1203
            auto data_type = DataType(col.get_type());
492✔
1204
            auto table = set.get_table();
492✔
1205
            auto table_name = table->get_name();
492✔
1206
            auto field_name = table->get_column_name(col);
492✔
1207

1208
            auto inserter = util::overload{
492✔
1209
                [&](const ObjLink& link) {
492✔
1210
                    if (data_type == type_TypedLink) {
168✔
1211
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1212
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1213
                        link_set.erase(link);
×
1214
                    }
×
1215
                    else if (data_type == type_Mixed) {
168✔
1216
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
80✔
1217
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
80✔
1218
                        mixed_set.erase(link);
80✔
1219
                    }
80✔
1220
                    else if (data_type == type_Link) {
88✔
1221
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
88✔
1222
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
88✔
1223
                        // Validate the target.
1224
                        auto target_table = table->get_link_target(col);
88✔
1225
                        if (target_table->get_key() != link.get_table_key()) {
88✔
1226
                            m_applier->bad_transaction_log(
×
1227
                                "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
1228
                                table_name);
×
1229
                        }
×
1230
                        link_set.erase(link.get_obj_key());
88✔
1231
                    }
88✔
1232
                    else {
×
1233
                        m_applier->bad_transaction_log(
×
1234
                            "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1235
                            table_name, data_type);
×
1236
                    }
×
1237
                },
168✔
1238
                [&](Mixed value) {
492✔
1239
                    if (value.is_null() && !col.is_nullable()) {
324!
1240
                        m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name,
×
1241
                                                       table_name);
×
1242
                    }
×
1243

1244
                    if (data_type == type_Mixed || value.get_type() == data_type) {
324✔
1245
                        set.erase_any(value);
324✔
1246
                    }
324✔
1247
                    else {
×
1248
                        m_applier->bad_transaction_log(
×
1249
                            "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name,
×
1250
                            data_type, value.get_type());
×
1251
                    }
×
1252
                },
324✔
1253
                [&](const Instruction::Payload::ObjectValue&) {
492✔
1254
                    m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported.");
×
1255
                },
×
1256
                [&](const Instruction::Payload::List&) {
492✔
1257
                    m_applier->bad_transaction_log("SetErase: Sets of lists are not supported.");
×
1258
                },
×
1259
                [&](const Instruction::Payload::Set&) {
492✔
1260
                    m_applier->bad_transaction_log("SetErase: Sets of sets are not supported.");
×
1261
                },
×
1262
                [&](const Instruction::Payload::Dictionary&) {
492✔
1263
                    m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported.");
×
1264
                },
×
1265
                [&](const Instruction::Payload::Erased&) {
492✔
1266
                    m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase");
×
1267
                },
×
1268
            };
492✔
1269

1270
            m_applier->visit_payload(m_instr.value, inserter);
492✔
1271
        }
492✔
1272

1273
    private:
492✔
1274
        const Instruction::SetErase& m_instr;
492✔
1275
    };
492✔
1276
    SetEraseResolver(this, instr).resolve();
492✔
1277
}
492✔
1278

1279
StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr,
1280
                                              const std::string_view& name)
1281
{
103,998✔
1282
    if (auto class_name = m_log->try_get_string(instr.table)) {
103,998✔
1283
        return Group::class_name_to_table_name(*class_name, m_table_name_buffer);
103,998✔
1284
    }
103,998✔
1285
    else {
×
1286
        bad_transaction_log("Corrupt table name in %1 instruction", name);
×
1287
    }
×
1288
}
103,998✔
1289

1290
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1291
{
453,426✔
1292
    if (instr.table == m_last_table_name) {
453,426✔
1293
        return m_last_table;
354,122✔
1294
    }
354,122✔
1295
    else {
99,304✔
1296
        auto table_name = get_table_name(instr, name);
99,304✔
1297
        TableRef table = m_transaction.get_table(table_name);
99,304✔
1298
        if (!table) {
99,304✔
1299
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
×
1300
        }
×
1301
        m_last_table = table;
99,304✔
1302
        m_last_table_name = instr.table;
99,304✔
1303
        m_last_object_key.reset();
99,304✔
1304
        m_last_object.reset();
99,304✔
1305
        m_last_field_name = InternString{};
99,304✔
1306
        m_last_field = ColKey{};
99,304✔
1307
        return table;
99,304✔
1308
    }
99,304✔
1309
}
453,426✔
1310

1311
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1312
                                                       const std::string_view& name)
1313
{
669,372✔
1314
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
669,372✔
1315
        *m_last_object_key == instr.object) {
669,372✔
1316
        // We have already found the object, reuse it.
1317
        return *m_last_object;
271,052✔
1318
    }
271,052✔
1319
    else {
398,320✔
1320
        TableRef table = get_table(instr, name);
398,320✔
1321
        ObjKey key = get_object_key(*table, instr.object, name);
398,320✔
1322
        if (!key) {
398,320✔
1323
            return util::none;
×
1324
        }
×
1325
        if (!table->is_valid(key)) {
398,320✔
1326
            // Check if the object is deleted or is a tombstone.
1327
            return util::none;
1,762✔
1328
        }
1,762✔
1329

1330
        Obj obj = table->get_object(key);
396,558✔
1331
        m_last_object_key = instr.object;
396,558✔
1332
        m_last_object = obj;
396,558✔
1333
        return obj;
396,558✔
1334
    }
398,320✔
1335
}
669,372✔
1336

1337
LstBasePtr InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1338
{
177,590✔
1339
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
1340
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
1341
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
1342
    // links.
1343
    REALM_ASSERT(col.is_list());
177,590✔
1344
    LstBasePtr list;
177,590✔
1345
    if (col.get_type() == col_type_Link) {
177,590✔
1346
        auto table = obj.get_table();
13,944✔
1347
        if (!table->get_link_target(col)->is_embedded()) {
13,944✔
1348
            list = obj.get_list_ptr<ObjKey>(col);
1,760✔
1349
        }
1,760✔
1350
        else {
12,184✔
1351
            list = obj.get_listbase_ptr(col);
12,184✔
1352
        }
12,184✔
1353
    }
13,944✔
1354
    else {
163,646✔
1355
        list = obj.get_listbase_ptr(col);
163,646✔
1356
    }
163,646✔
1357
    return list;
177,590✔
1358
}
177,590✔
1359

1360
InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
1361
                                               const std::string_view& instr_name)
1362
    : m_applier(applier)
325,880✔
1363
    , m_path_instr(instr)
325,880✔
1364
    , m_instr_name(instr_name)
325,880✔
1365
{
665,444✔
1366
}
665,444✔
1367

1368
InstructionApplier::PathResolver::~PathResolver()
1369
{
665,440✔
1370
    on_finish();
665,440✔
1371
}
665,440✔
1372

1373
void InstructionApplier::PathResolver::on_property(Obj&, ColKey)
1374
{
×
1375
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name));
×
1376
}
×
1377

1378
void InstructionApplier::PathResolver::on_list(LstBase&)
1379
{
×
1380
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name));
×
1381
}
×
1382

1383
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t)
1384
{
×
1385
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name));
×
1386
    return Status::DidNotResolve;
×
1387
}
×
1388

1389
void InstructionApplier::PathResolver::on_dictionary(Dictionary&)
1390
{
×
1391
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1392
}
×
1393

1394
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed)
1395
{
×
1396
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1397
    return Status::DidNotResolve;
×
1398
}
×
1399

1400
void InstructionApplier::PathResolver::on_set(SetBase&)
1401
{
×
1402
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name));
×
1403
}
×
1404

1405
void InstructionApplier::PathResolver::on_error(const std::string& err_msg)
1406
{
×
1407
    m_applier->bad_transaction_log(err_msg);
×
1408
}
×
1409

1410
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
1411
{
652,022✔
1412
    m_applier->m_last_field = col;
652,022✔
1413
}
652,022✔
1414

1415
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
2,928✔
1416

1417
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
1418
{
7,452✔
1419
    return Status::Pending;
7,452✔
1420
}
7,452✔
1421

1422
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData,
1423
                                                                                                StringData)
1424
{
×
1425
    return Status::Pending;
×
1426
}
×
1427

1428
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
1429
{
639,456✔
1430
    m_applier->m_current_path = m_path_instr.path;
639,456✔
1431
    m_applier->m_last_field_name = m_path_instr.field;
639,456✔
1432
    return Status::Pending;
639,456✔
1433
}
639,456✔
1434

1435
void InstructionApplier::PathResolver::on_finish()
1436
{
665,446✔
1437
    m_applier->m_current_path.reset();
665,446✔
1438
    m_applier->m_last_field_name = InternString{};
665,446✔
1439
    m_applier->m_last_field = ColKey{};
665,446✔
1440
}
665,446✔
1441

1442
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1443
{
692,002✔
1444
    return m_applier->get_string(interned);
692,002✔
1445
}
692,002✔
1446

1447
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve()
1448
{
665,460✔
1449
    util::Optional<Obj> obj = m_applier->get_top_object(m_path_instr, m_instr_name);
665,460✔
1450
    Status begin_status = on_begin(obj);
665,460✔
1451
    if (begin_status != Status::Pending) {
665,460✔
1452
        return begin_status;
220✔
1453
    }
220✔
1454
    if (!obj) {
665,240✔
1455
        m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name,
×
1456
                                       format_pk(m_applier->m_log->get_key(m_path_instr.object)),
×
1457
                                       get_string(m_path_instr.table));
×
1458
    }
×
1459

1460
    m_it_begin = m_path_instr.path.begin();
665,240✔
1461
    m_it_end = m_path_instr.path.end();
665,240✔
1462
    Status status = resolve_field(*obj, m_path_instr.field);
665,240✔
1463
    return status == Status::Pending ? Status::Success : status;
665,240✔
1464
}
665,460✔
1465

1466
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field)
1467
{
681,256✔
1468
    auto field_name = get_string(field);
681,256✔
1469
    ColKey col = obj.get_table()->get_column_key(field_name);
681,256✔
1470
    if (!col) {
681,256✔
1471
        on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name,
×
1472
                              obj.get_table()->get_name()));
×
1473
        return Status::DidNotResolve;
×
1474
    }
×
1475
    on_column_advance(col);
681,256✔
1476

1477
    if (m_it_begin == m_it_end) {
681,256✔
1478
        if (col.is_list()) {
489,932✔
1479
            auto list = obj.get_listbase_ptr(col);
124✔
1480
            on_list(*list);
124✔
1481
        }
124✔
1482
        else if (col.is_dictionary()) {
489,808✔
1483
            auto dict = obj.get_dictionary(col);
80✔
1484
            on_dictionary(dict);
80✔
1485
        }
80✔
1486
        else if (col.is_set()) {
489,728✔
1487
            SetBasePtr set;
3,664✔
1488
            if (col.get_type() == col_type_Link) {
3,664✔
1489
                // We are interested in using non-condensed indexes - as for Lists below
1490
                set = obj.get_set_ptr<ObjKey>(col);
376✔
1491
            }
376✔
1492
            else {
3,288✔
1493
                set = obj.get_setbase_ptr(col);
3,288✔
1494
            }
3,288✔
1495
            on_set(*set);
3,664✔
1496
        }
3,664✔
1497
        else {
486,064✔
1498
            on_property(obj, col);
486,064✔
1499
        }
486,064✔
1500
        return Status::Pending;
489,932✔
1501
    }
489,932✔
1502

1503
    if (col.is_list()) {
191,324✔
1504
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
175,774✔
1505
            auto list = InstructionApplier::get_list_from_path(obj, col);
175,774✔
1506
            ++m_it_begin;
175,774✔
1507
            return resolve_list_element(*list, *pindex);
175,774✔
1508
        }
175,774✔
1509
        on_error(util::format("%1: List index is not an integer on field '%2' in class '%3'", m_instr_name,
×
1510
                              field_name, obj.get_table()->get_name()));
×
1511
    }
×
1512
    else if (col.is_dictionary()) {
15,550✔
1513
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
8,804✔
1514
            auto dict = obj.get_dictionary(col);
8,804✔
1515
            ++m_it_begin;
8,804✔
1516
            return resolve_dictionary_element(dict, *pkey);
8,804✔
1517
        }
8,804✔
1518
        on_error(util::format("%1: Dictionary key is not a string on field '%2' in class '%3'", m_instr_name,
×
1519
                              field_name, obj.get_table()->get_name()));
×
1520
    }
×
1521
    else if (col.get_type() == col_type_Mixed) {
6,746✔
1522
        auto val = obj.get<Mixed>(col);
2,136✔
1523
        if (val.is_type(type_Dictionary)) {
2,136✔
1524
            if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
1,260✔
1525
                Dictionary dict(obj, col);
1,260✔
1526
                ++m_it_begin;
1,260✔
1527
                return resolve_dictionary_element(dict, *pkey);
1,260✔
1528
            }
1,260✔
1529
        }
1,260✔
1530
        if (val.is_type(type_List)) {
876✔
1531
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
876✔
1532
                Lst<Mixed> list(obj, col);
876✔
1533
                ++m_it_begin;
876✔
1534
                return resolve_list_element(list, *pindex);
876✔
1535
            }
876✔
1536
        }
876✔
1537
        on_error(util::format("%1: Not a list or dictionary on field '%2' in class '%3'", m_instr_name, field_name,
×
1538
                              obj.get_table()->get_name()));
×
1539
    }
×
1540
    else if (col.get_type() == col_type_Link) {
4,628✔
1541
        auto target = obj.get_table()->get_link_target(col);
4,608✔
1542
        if (!target->is_embedded()) {
4,608✔
1543
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
×
1544
                                  field_name, obj.get_table()->get_name()));
×
1545
        }
×
1546
        else if (obj.is_null(col)) {
4,608✔
1547
            Status null_status =
132✔
1548
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
132✔
1549
            if (null_status != Status::Pending) {
132✔
1550
                return null_status;
132✔
1551
            }
132✔
1552
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
×
1553
                                  m_instr_name, field_name, obj.get_table()->get_name()));
×
1554
        }
×
1555
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,476✔
1556
            auto embedded_object = obj.get_linked_object(col);
4,476✔
1557
            ++m_it_begin;
4,476✔
1558
            return resolve_field(embedded_object, *pfield);
4,476✔
1559
        }
4,476✔
1560
        else {
×
1561
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1562
        }
×
1563
    }
4,608✔
1564
    else {
2,147,483,667✔
1565
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
2,147,483,667✔
1566
                              field_name, obj.get_table()->get_name(), col.get_type()));
2,147,483,667✔
1567
    }
2,147,483,667✔
1568
    return Status::DidNotResolve;
2,147,483,667✔
1569
}
191,324✔
1570

1571
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
1572
                                                                                                uint32_t index)
1573
{
177,440✔
1574
    if (m_it_begin == m_it_end) {
177,440✔
1575
        return on_list_index(list, index);
166,606✔
1576
    }
166,606✔
1577

1578
    auto col = list.get_col_key();
10,834✔
1579
    auto field_name = list.get_table()->get_column_name(col);
10,834✔
1580

1581
    if (col.get_type() == col_type_Link) {
10,834✔
1582
        auto target = list.get_table()->get_link_target(col);
10,172✔
1583
        if (!target->is_embedded()) {
10,172✔
1584
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1585
                                  list.get_table()->get_name(), index));
×
1586
            return Status::DidNotResolve;
×
1587
        }
×
1588

1589
        Status list_status = on_list_index_advance(index);
10,172✔
1590
        if (list_status != Status::Pending) {
10,172✔
1591
            return list_status;
2,720✔
1592
        }
2,720✔
1593

1594
        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
7,452✔
1595
        auto& link_list = static_cast<LnkLst&>(list);
7,452✔
1596
        if (index >= link_list.size()) {
7,452✔
1597
            on_error(util::format("%1: Out-of-bounds index through list at '%3.%2[%4]'", m_instr_name, field_name,
×
1598
                                  list.get_table()->get_name(), index));
×
1599
        }
×
1600
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
7,452✔
1601
            auto embedded_object = link_list.get_object(index);
7,452✔
1602
            ++m_it_begin;
7,452✔
1603
            return resolve_field(embedded_object, *pfield);
7,452✔
1604
        }
7,452✔
1605
        on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1606
    }
×
1607
    else {
662✔
1608
        if (list.get_data_type() == type_Mixed) {
664✔
1609
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
664✔
1610
            if (index < mixed_list.size()) {
664✔
1611
                auto val = mixed_list.get(index);
664✔
1612

1613
                if (val.is_type(type_Dictionary)) {
664✔
1614
                    if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
468✔
1615
                        Dictionary d(mixed_list, mixed_list.get_key(index));
468✔
1616
                        ++m_it_begin;
468✔
1617
                        return resolve_dictionary_element(d, *pfield);
468✔
1618
                    }
468✔
1619
                }
468✔
1620
                if (val.is_type(type_List)) {
196✔
1621
                    if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
196✔
1622
                        Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
196✔
1623
                        ++m_it_begin;
196✔
1624
                        return resolve_list_element(l, *pindex);
196✔
1625
                    }
196✔
1626
                }
196✔
1627
            }
196✔
1628
        }
664✔
1629

1630
        on_error(util::format(
2,147,483,647✔
1631
            "%1: Resolving path through unstructured list element on '%3.%2', which is a list of type '%4'",
2,147,483,647✔
1632
            m_instr_name, field_name, list.get_table()->get_name(), col.get_type()));
2,147,483,647✔
1633
    }
2,147,483,647✔
1634
    return Status::DidNotResolve;
2,147,483,647✔
1635
}
10,834✔
1636

1637
InstructionApplier::PathResolver::Status
1638
InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key)
1639
{
10,736✔
1640
    StringData string_key = get_string(key);
10,736✔
1641
    if (m_it_begin == m_it_end) {
10,736✔
1642
        return on_dictionary_key(dict, Mixed{string_key});
5,772✔
1643
    }
5,772✔
1644

1645
    on_dict_key_advance(string_key);
4,964✔
1646

1647
    auto col = dict.get_col_key();
4,964✔
1648
    auto table = dict.get_table();
4,964✔
1649
    auto field_name = table->get_column_name(col);
4,964✔
1650

1651
    if (col.get_type() == col_type_Link) {
4,964✔
1652
        auto target = dict.get_target_table();
4,164✔
1653
        if (!target->is_embedded()) {
4,164✔
1654
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1655
                                  table->get_name(), string_key));
×
1656
            return Status::DidNotResolve;
×
1657
        }
×
1658

1659
        auto embedded_object = dict.get_object(string_key);
4,164✔
1660
        if (!embedded_object) {
4,164✔
1661
            Status null_link_status = on_null_link_advance(table->get_name(), string_key);
80✔
1662
            if (null_link_status != Status::Pending) {
80✔
1663
                return null_link_status;
80✔
1664
            }
80✔
1665
            on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name,
×
1666
                                  table->get_name(), string_key));
×
1667
        }
×
1668
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,084✔
1669
            ++m_it_begin;
4,084✔
1670
            return resolve_field(embedded_object, *pfield);
4,084✔
1671
        }
4,084✔
1672
        else {
×
1673
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1674
        }
×
1675
    }
4,164✔
1676
    else {
800✔
1677
        auto val = dict.get(string_key);
800✔
1678
        if (val.is_type(type_Dictionary)) {
800✔
1679
            if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
204✔
1680
                Dictionary d(dict, dict.build_index(string_key));
204✔
1681
                ++m_it_begin;
204✔
1682
                return resolve_dictionary_element(d, *pfield);
204✔
1683
            }
204✔
1684
        }
204✔
1685
        if (val.is_type(type_List)) {
596✔
1686
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
596✔
1687
                Lst<Mixed> l(dict, dict.build_index(string_key));
596✔
1688
                ++m_it_begin;
596✔
1689
                return resolve_list_element(l, *pindex);
596✔
1690
            }
596✔
1691
        }
596✔
1692
        on_error(
×
1693
            util::format("%1: Resolving path through non link element on '%3.%2', which is a dictionary of type '%4'",
×
1694
                         m_instr_name, field_name, table->get_name(), col.get_type()));
×
1695
    }
×
1696
    return Status::DidNotResolve;
×
1697
}
4,964✔
1698

1699

1700
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1701
                                          const std::string_view& name) const
1702
{
400,374✔
1703
    StringData table_name = table.get_name();
400,374✔
1704
    ColKey pk_col = table.get_primary_key_column();
400,374✔
1705
    StringData pk_name = "";
400,374✔
1706
    DataType pk_type;
400,374✔
1707
    if (pk_col) {
400,386✔
1708
        pk_name = table.get_column_name(pk_col);
400,386✔
1709
        pk_type = table.get_column_type(pk_col);
400,386✔
1710
    }
400,386✔
1711
    return mpark::visit(
400,374✔
1712
        util::overload{
400,374✔
1713
            [&](mpark::monostate) {
400,374✔
1714
                if (!pk_col) {
24✔
1715
                    bad_transaction_log(
×
1716
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
×
1717
                        name, table_name);
×
1718
                }
×
1719
                if (!table.is_nullable(pk_col)) {
24✔
1720
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
×
1721
                                        name, table_name, pk_name);
×
1722
                }
×
1723

1724
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
24✔
1725
                return key;
24✔
1726
            },
24✔
1727
            [&](int64_t pk) {
400,374✔
1728
                if (!pk_col) {
394,252✔
1729
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
×
1730
                                        "a primary key column",
×
1731
                                        name, pk, table_name);
×
1732
                }
×
1733
                if (pk_type != type_Int) {
394,252✔
1734
                    bad_transaction_log(
×
1735
                        "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1736
                        name, pk, table_name, pk_name, pk_type);
×
1737
                }
×
1738
                ObjKey key = table.get_objkey_from_primary_key(pk);
394,252✔
1739
                return key;
394,252✔
1740
            },
394,252✔
1741
            [&](InternString interned_pk) {
400,374✔
1742
                auto pk = get_string(interned_pk);
598✔
1743
                if (!pk_col) {
598✔
1744
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1745
                                        "have a primary key column",
×
1746
                                        name, pk, table_name);
×
1747
                }
×
1748
                if (pk_type != type_String) {
598✔
1749
                    bad_transaction_log(
×
1750
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
×
1751
                        name, pk, table_name, pk_name, pk_type);
×
1752
                }
×
1753
                ObjKey key = table.get_objkey_from_primary_key(pk);
598✔
1754
                return key;
598✔
1755
            },
598✔
1756
            [&](GlobalKey id) {
400,374✔
1757
                if (pk_col) {
4✔
1758
                    bad_transaction_log(
×
1759
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
1760
                        name, table_name, pk_type);
×
1761
                }
×
1762
                ObjKey key = table.get_objkey_from_global_key(id);
4✔
1763
                return key;
4✔
1764
            },
4✔
1765
            [&](ObjectId pk) {
400,374✔
1766
                if (!pk_col) {
5,426✔
1767
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1768
                                        "have a primary key column",
×
1769
                                        name, pk, table_name);
×
1770
                }
×
1771
                if (pk_type != type_ObjectId) {
5,426✔
1772
                    bad_transaction_log(
×
1773
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1774
                        name, pk, table_name, pk_name, pk_type);
×
1775
                }
×
1776
                ObjKey key = table.get_objkey_from_primary_key(pk);
5,426✔
1777
                return key;
5,426✔
1778
            },
5,426✔
1779
            [&](UUID pk) {
400,374✔
1780
                if (!pk_col) {
20✔
1781
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
×
1782
                                        "have a primary key column",
×
1783
                                        name, pk, table_name);
×
1784
                }
×
1785
                if (pk_type != type_UUID) {
20✔
1786
                    bad_transaction_log(
×
1787
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
×
1788
                        pk, table_name, pk_name, pk_type);
×
1789
                }
×
1790
                ObjKey key = table.get_objkey_from_primary_key(pk);
20✔
1791
                return key;
20✔
1792
            }},
20✔
1793
        primary_key);
400,374✔
1794
}
400,374✔
1795

1796

1797
} // namespace realm::sync
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

© 2026 Coveralls, Inc