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

realm / realm-core / james.stone_385

26 Sep 2023 08:24PM UTC coverage: 90.917% (+0.03%) from 90.892%
james.stone_385

Pull #6670

Evergreen

ironage
fix lint
Pull Request #6670: Sorting stage 3

97066 of 177964 branches covered (0.0%)

901 of 927 new or added lines in 13 files covered. (97.2%)

122 existing lines in 17 files now uncovered.

236115 of 259704 relevant lines covered (90.92%)

6670991.87 hits per line

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

64.5
/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
{
4✔
12
    throw BadChangesetError{std::move(msg)};
4✔
13
}
4✔
14

15
} // namespace
16

17
REALM_NORETURN void InstructionApplier::bad_transaction_log(const std::string& msg) const
18
{
4✔
19
    if (m_last_object_key) {
4✔
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) {
4✔
37
        // We should have a changeset if we have a table name defined.
2✔
38
        REALM_ASSERT(m_log);
4✔
39
        throw_bad_transaction_log(
4✔
40
            util::format("%1 (instruction table: %2, version: %3, last_integrated_remote_version: %4, "
4✔
41
                         "origin_file_ident: %5, timestamp: %6)",
4✔
42
                         msg, m_log->get_string(m_last_table_name), m_log->version,
4✔
43
                         m_log->last_integrated_remote_version, m_log->origin_file_ident, m_log->origin_timestamp));
4✔
44
    }
4✔
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));
4✔
53
}
4✔
54

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

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

72
StringData InstructionApplier::get_string(StringBufferRange range) const
73
{
173,458✔
74
    auto string = m_log->try_get_string(range);
173,458✔
75
    if (!string)
173,458✔
76
        bad_transaction_log("string read error");
×
77
    return *string;
173,458✔
78
}
173,458✔
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
{
56✔
90
    if (class_name.size() >= Group::max_table_name_length - 6)
56✔
91
        bad_transaction_log("class name too long");
×
92
    Group::TableNameBuffer buffer;
56✔
93
    return m_transaction.get_table(Group::class_name_to_table_name(class_name, buffer));
56✔
94
}
56✔
95

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

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

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

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

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

6,370✔
121
    auto add_table = util::overload{
12,896✔
122
        [&](const Instruction::AddTable::TopLevelTable& spec) {
12,780✔
123
            auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel);
12,652✔
124
            if (spec.pk_type == Instruction::Payload::Type::GlobalKey) {
12,670✔
125
                m_transaction.get_or_add_table(table_name, table_type);
8✔
126
            }
8✔
127
            else {
12,662✔
128
                if (!is_valid_key_type(spec.pk_type)) {
12,662✔
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);
12,662✔
133
                StringData pk_field = get_string(spec.pk_field);
12,662✔
134
                bool nullable = spec.pk_nullable;
12,662✔
135

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

6,370✔
154
    mpark::visit(std::move(add_table), instr.type);
12,896✔
155
}
12,896✔
156

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

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

1,942✔
168
    m_transaction.remove_table(table_name);
3,774✔
169
}
3,774✔
170

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

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

239
void InstructionApplier::operator()(const Instruction::EraseObject& instr)
240
{
56,502✔
241
    // FIXME: Log actions.
27,382✔
242
    // Note: EraseObject is idempotent.
27,382✔
243
    if (auto obj = get_top_object(instr, "EraseObject")) {
56,502✔
244
        // This call will prevent incoming links to be nullified/deleted
20,804✔
245
        obj->invalidate();
42,100✔
246
    }
42,100✔
247
    m_last_object.reset();
56,502✔
248
}
56,502✔
249

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

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

313
void InstructionApplier::operator()(const Instruction::Update& instr)
314
{
479,174✔
315
    struct UpdateResolver : public PathResolver {
479,174✔
316
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
479,174✔
317
            : PathResolver(applier, instr, "Update")
479,174✔
318
            , m_instr(instr)
479,174✔
319
        {
479,192✔
320
        }
479,190✔
321
        void on_property(Obj& obj, ColKey col) override
479,174✔
322
        {
475,438✔
323
            // Update of object field.
229,212✔
324

229,212✔
325
            auto table = obj.get_table();
471,082✔
326
            auto table_name = table->get_name();
471,082✔
327
            auto field_name = table->get_column_name(col);
471,082✔
328
            auto data_type = DataType(col.get_type());
471,082✔
329

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

229,212✔
389
            m_applier->visit_payload(m_instr.value, visitor);
471,082✔
390
        }
471,082✔
391
        Status on_list_index(LstBase& list, uint32_t index) override
479,174✔
392
        {
235,976✔
393
            // Update of list element.
3,046✔
394

3,046✔
395
            auto col = list.get_col_key();
5,454✔
396
            auto data_type = DataType(col.get_type());
5,454✔
397
            auto table = list.get_table();
5,454✔
398
            auto table_name = table->get_name();
5,454✔
399
            auto field_name = table->get_column_name(col);
5,454✔
400

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

3,046✔
470
            m_applier->visit_payload(m_instr.value, visitor);
5,454✔
471
            return Status::Pending;
5,454✔
472
        }
5,454✔
473
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
479,174✔
474
        {
234,870✔
475
            // Update (insert) of dictionary element.
1,302✔
476

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

1,302✔
508
            m_applier->visit_payload(m_instr.value, visitor);
2,604✔
509
            return Status::Pending;
2,604✔
510
        }
2,604✔
511

233,568✔
512
    private:
479,174✔
513
        const Instruction::Update& m_instr;
479,174✔
514
    };
479,174✔
515
    UpdateResolver resolver(this, instr);
479,174✔
516
    resolver.resolve();
479,174✔
517
}
479,174✔
518

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

1,780✔
543
    private:
3,634✔
544
        const Instruction::AddInteger& m_instr;
3,634✔
545
    };
3,634✔
546
    AddIntegerResolver resolver(this, instr);
3,634✔
547
    resolver.resolve();
3,634✔
548
}
3,634✔
549

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

10,524✔
555
    // Temporarily swap out the last object key so it doesn't get included in error messages
10,524✔
556
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
22,008✔
557

10,524✔
558
    auto table = get_table(instr, "AddColumn");
22,008✔
559
    auto col_name = get_string(instr.field);
22,008✔
560

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

10,372✔
601
    if (instr.collection_type == CollectionType::Dictionary && instr.key_type != Type::String) {
21,686✔
602
        bad_transaction_log("AddColumn '%1.%3' adding dictionary column with non-string keys", table->get_name(),
×
603
                            col_name);
×
604
    }
×
605

10,372✔
606
    if (instr.type != Type::Link) {
21,686✔
607
        DataType type = get_data_type(instr.type);
20,558✔
608
        switch (instr.collection_type) {
20,558✔
609
            case CollectionType::Single: {
14,506✔
610
                table->add_column(type, col_name, instr.nullable);
14,506✔
611
                break;
14,506✔
612
            }
×
613
            case CollectionType::List: {
5,820✔
614
                table->add_column_list(type, col_name, instr.nullable);
5,820✔
615
                break;
5,820✔
616
            }
×
617
            case CollectionType::Dictionary: {
104✔
618
                DataType key_type = get_data_type(instr.key_type);
104✔
619
                table->add_column_dictionary(type, col_name, instr.nullable, key_type);
104✔
620
                break;
104✔
621
            }
×
622
            case CollectionType::Set: {
128✔
623
                table->add_column_set(type, col_name, instr.nullable);
128✔
624
                break;
128✔
625
            }
1,128✔
626
        }
1,128✔
627
    }
1,128✔
628
    else {
1,128✔
629
        Group::TableNameBuffer buffer;
1,128✔
630
        auto target_table_name = get_string(instr.link_target_table);
1,128✔
631
        if (target_table_name.size() != 0) {
1,128✔
632
            TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer));
1,128✔
633
            if (!target) {
1,128✔
634
                bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(),
×
635
                                    col_name, target_table_name);
×
636
            }
×
637
            if (instr.collection_type == CollectionType::List) {
1,128✔
638
                table->add_column_list(*target, col_name);
580✔
639
            }
580✔
640
            else if (instr.collection_type == CollectionType::Set) {
548✔
641
                table->add_column_set(*target, col_name);
8✔
642
            }
8✔
643
            else if (instr.collection_type == CollectionType::Dictionary) {
540✔
644
                table->add_column_dictionary(*target, col_name);
32✔
645
            }
32✔
646
            else {
508✔
647
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
508✔
648
                table->add_column(*target, col_name);
508✔
649
            }
508✔
650
        }
1,128✔
UNCOV
651
        else {
×
UNCOV
652
            if (instr.collection_type == CollectionType::List) {
×
653
                table->add_column_list(type_TypedLink, col_name);
×
654
            }
×
UNCOV
655
            else {
×
UNCOV
656
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
×
UNCOV
657
                table->add_column(type_TypedLink, col_name);
×
UNCOV
658
            }
×
UNCOV
659
        }
×
660
    }
1,128✔
661
}
21,686✔
662

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

668
    auto table = get_table(instr, "EraseColumn");
×
669
    auto col_name = get_string(instr.field);
×
670

671
    ColKey col = table->get_column_key(col_name);
×
672
    if (!col) {
×
673
        bad_transaction_log("EraseColumn '%1.%2' which doesn't exist", table->get_name(), col_name);
×
674
    }
×
675

676
    table->remove_column(col);
×
677
}
×
678

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

83,696✔
696
            if (index > m_instr.prior_size) {
165,256✔
697
                m_applier->bad_transaction_log("ArrayInsert: Invalid insertion index (index = %1, prior_size = %2)",
×
698
                                               index, m_instr.prior_size);
×
699
            }
×
700

83,696✔
701
            if (index > list.size()) {
165,256✔
702
                m_applier->bad_transaction_log("ArrayInsert: Index out of bounds (%1 > %2)", index, list.size());
×
703
            }
×
704

83,696✔
705
            if (m_instr.prior_size != list.size()) {
165,256✔
706
                m_applier->bad_transaction_log("ArrayInsert: Invalid prior_size (list size = %1, prior_size = %2)",
×
707
                                               list.size(), m_instr.prior_size);
×
708
            }
×
709

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

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

83,696✔
805
            m_applier->visit_payload(m_instr.value, inserter);
165,256✔
806
            return Status::Pending;
165,256✔
807
        }
165,256✔
808

83,696✔
809
    private:
165,256✔
810
        const Instruction::ArrayInsert& m_instr;
165,256✔
811
    };
165,256✔
812
    ArrayInsertResolver(this, instr).resolve();
165,256✔
813
}
165,256✔
814

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

52✔
844
    private:
104✔
845
        const Instruction::ArrayMove& m_instr;
104✔
846
    };
104✔
847
    ArrayMoveResolver(this, instr).resolve();
104✔
848
}
104✔
849

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

2,674✔
875
    private:
4,968✔
876
        const Instruction::ArrayErase& m_instr;
4,968✔
877
    };
4,968✔
878
    ArrayEraseResolver(this, instr).resolve();
4,968✔
879
}
4,968✔
880

881
void InstructionApplier::operator()(const Instruction::Clear& instr)
882
{
290✔
883
    struct ClearResolver : public PathResolver {
290✔
884
        ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr)
290✔
885
            : PathResolver(applier, instr, "Clear")
290✔
886
        {
290✔
887
        }
290✔
888
        void on_list(LstBase& list) override
290✔
889
        {
234✔
890
            list.clear();
178✔
891
        }
178✔
892
        void on_dictionary(Dictionary& dict) override
290✔
893
        {
186✔
894
            dict.clear();
48✔
895
        }
48✔
896
        void on_set(SetBase& set) override
290✔
897
        {
194✔
898
            set.clear();
64✔
899
        }
64✔
900
        void on_property(Obj& obj, ColKey col_key) override
290✔
901
        {
162✔
902
            if (col_key.get_type() == col_type_Mixed) {
×
903
                auto val = obj.get<Mixed>(col_key);
×
904
                if (val.is_type(type_Dictionary)) {
×
905
                    Dictionary dict(obj, col_key);
×
906
                    dict.clear();
×
907
                    return;
×
908
                }
×
909
                else if (val.is_type(type_List)) {
×
910
                    Lst<Mixed> list(obj, col_key);
×
911
                    list.clear();
×
912
                    return;
×
913
                }
×
914
                else if (val.is_type(type_Set)) {
×
915
                    Set<Mixed> set(obj, col_key);
×
916
                    set.clear();
×
917
                    return;
×
918
                }
×
919
            }
×
920

921
            PathResolver::on_property(obj, col_key);
×
922
        }
×
923
    };
290✔
924
    ClearResolver(this, instr).resolve();
290✔
925
}
290✔
926

927
bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr,
928
                                           const std::string_view& instr_name)
929
{
16✔
930
    struct AllowsNullsResolver : public PathResolver {
16✔
931
        AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
16✔
932
                            const std::string_view& instr_name)
16✔
933
            : PathResolver(applier, instr, instr_name)
16✔
934
            , m_allows_nulls(false)
16✔
935
        {
16✔
936
        }
16✔
937
        Status on_list_index(LstBase&, uint32_t) override
16✔
938
        {
8✔
939
            return Status::Pending;
×
940
        }
×
941
        void on_list(LstBase&) override {}
8✔
942
        void on_set(SetBase&) override {}
8✔
943
        void on_dictionary(Dictionary&) override
16✔
944
        {
8✔
945
            m_allows_nulls = true;
×
946
        }
×
947
        Status on_dictionary_key(Dictionary&, Mixed) override
16✔
948
        {
16✔
949
            m_allows_nulls = true;
16✔
950
            return Status::Pending;
16✔
951
        }
16✔
952
        void on_property(Obj&, ColKey) override
16✔
953
        {
8✔
954
            m_allows_nulls = true;
×
955
        }
×
956
        bool allows_nulls()
16✔
957
        {
16✔
958
            resolve();
16✔
959
            return m_allows_nulls;
16✔
960
        }
16✔
961

8✔
962
    private:
16✔
963
        bool m_allows_nulls;
16✔
964
    };
16✔
965
    return AllowsNullsResolver(this, instr, instr_name).allows_nulls();
16✔
966
}
16✔
967

968
std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const
969
{
×
970
    REALM_ASSERT(m_log);
×
971
    std::stringstream ss;
×
972
    m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
973
    return ss.str();
×
974
}
×
975

976
bool InstructionApplier::check_links_exist(const Instruction::Payload& payload)
977
{
21,872✔
978
    bool valid_payload = true;
21,872✔
979
    using Type = Instruction::Payload::Type;
21,872✔
980
    if (payload.type == Type::Link) {
21,872✔
981
        StringData class_name = get_string(payload.data.link.target_table);
500✔
982
        Group::TableNameBuffer buffer;
500✔
983
        StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
500✔
984
        TableRef target_table = m_transaction.get_table(target_table_name);
500✔
985
        if (!target_table) {
500✔
986
            bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
987
        }
×
988
        if (target_table->is_embedded()) {
500✔
989
            bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
990
        }
×
991
        Mixed linked_pk =
500✔
992
            mpark::visit(util::overload{[&](mpark::monostate) {
262✔
993
                                            return Mixed{}; // the link exists and the pk is null
24✔
994
                                        },
24✔
995
                                        [&](int64_t pk) {
486✔
996
                                            return Mixed{pk};
472✔
997
                                        },
472✔
998
                                        [&](InternString interned_pk) {
250✔
999
                                            return Mixed{get_string(interned_pk)};
×
1000
                                        },
×
1001
                                        [&](GlobalKey) {
250✔
1002
                                            bad_transaction_log(
×
1003
                                                "Unexpected link to embedded object while validating a primary key");
×
1004
                                            return Mixed{}; // appease the compiler; visitors must have a single
×
1005
                                                            // return type
1006
                                        },
×
1007
                                        [&](ObjectId pk) {
252✔
1008
                                            return Mixed{pk};
4✔
1009
                                        },
4✔
1010
                                        [&](UUID pk) {
250✔
1011
                                            return Mixed{pk};
×
1012
                                        }},
×
1013
                         payload.data.link.target);
500✔
1014

250✔
1015
        if (!target_table->find_primary_key(linked_pk)) {
500✔
1016
            valid_payload = false;
48✔
1017
        }
48✔
1018
    }
500✔
1019
    return valid_payload;
21,872✔
1020
}
21,872✔
1021

1022
void InstructionApplier::operator()(const Instruction::SetInsert& instr)
1023
{
1,696✔
1024
    struct SetInsertResolver : public PathResolver {
1,696✔
1025
        SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr)
1,696✔
1026
            : PathResolver(applier, instr, "SetInsert")
1,696✔
1027
            , m_instr(instr)
1,696✔
1028
        {
1,696✔
1029
        }
1,696✔
1030
        void on_property(Obj& obj, ColKey col) override
1,696✔
1031
        {
848✔
1032
            // This better be a mixed column
1033
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1034
            auto set = obj.get_set<Mixed>(col);
×
1035
            on_set(set);
×
1036
        }
×
1037
        void on_set(SetBase& set) override
1,696✔
1038
        {
1,696✔
1039
            auto col = set.get_col_key();
1,696✔
1040
            auto data_type = DataType(col.get_type());
1,696✔
1041
            auto table = set.get_table();
1,696✔
1042
            auto table_name = table->get_name();
1,696✔
1043
            auto field_name = table->get_column_name(col);
1,696✔
1044

848✔
1045
            auto inserter = util::overload{
1,696✔
1046
                [&](const ObjLink& link) {
920✔
1047
                    if (data_type == type_TypedLink) {
144✔
1048
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1049
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1050
                        link_set.insert(link);
×
1051
                    }
×
1052
                    else if (data_type == type_Mixed) {
144✔
1053
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
64✔
1054
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
64✔
1055
                        mixed_set.insert(link);
64✔
1056
                    }
64✔
1057
                    else if (data_type == type_Link) {
80✔
1058
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
80✔
1059
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
80✔
1060
                        // Validate the target.
40✔
1061
                        auto target_table = table->get_link_target(col);
80✔
1062
                        if (target_table->get_key() != link.get_table_key()) {
80✔
1063
                            m_applier->bad_transaction_log(
×
1064
                                "SetInsert: Target table mismatch (expected '%1', got '%2')",
×
1065
                                target_table->get_name(), table_name);
×
1066
                        }
×
1067
                        link_set.insert(link.get_obj_key());
80✔
1068
                    }
80✔
1069
                    else {
×
1070
                        m_applier->bad_transaction_log(
×
1071
                            "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1072
                            table_name, data_type);
×
1073
                    }
×
1074
                },
144✔
1075
                [&](Mixed value) {
1,624✔
1076
                    if (value.is_null() && !col.is_nullable()) {
1,552✔
1077
                        m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name,
×
1078
                                                       table_name);
×
1079
                    }
×
1080

776✔
1081
                    if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) {
1,552✔
1082
                        set.insert_any(value);
1,552✔
1083
                    }
1,552✔
1084
                    else {
×
1085
                        m_applier->bad_transaction_log(
×
1086
                            "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name,
×
1087
                            table_name, data_type, value.get_type());
×
1088
                    }
×
1089
                },
1,552✔
1090
                [&](const Instruction::Payload::ObjectValue&) {
848✔
1091
                    m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported.");
×
1092
                },
×
1093
                [&](const Instruction::Payload::Dictionary&) {
848✔
1094
                    m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported.");
×
1095
                },
×
1096
                [&](const Instruction::Payload::List&) {
848✔
1097
                    m_applier->bad_transaction_log("SetInsert: Sets of lists are not supported.");
×
1098
                },
×
1099
                [&](const Instruction::Payload::Set&) {
848✔
1100
                    m_applier->bad_transaction_log("SetInsert: Sets of sets are not supported.");
×
1101
                },
×
1102
                [&](const Instruction::Payload::Erased&) {
848✔
1103
                    m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert");
×
1104
                },
×
1105
            };
1,696✔
1106

848✔
1107
            m_applier->visit_payload(m_instr.value, inserter);
1,696✔
1108
        }
1,696✔
1109

848✔
1110
    private:
1,696✔
1111
        const Instruction::SetInsert& m_instr;
1,696✔
1112
    };
1,696✔
1113
    SetInsertResolver(this, instr).resolve();
1,696✔
1114
}
1,696✔
1115

1116
void InstructionApplier::operator()(const Instruction::SetErase& instr)
1117
{
456✔
1118
    struct SetEraseResolver : public PathResolver {
456✔
1119
        SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr)
456✔
1120
            : PathResolver(applier, instr, "SetErase")
456✔
1121
            , m_instr(instr)
456✔
1122
        {
456✔
1123
        }
456✔
1124
        void on_property(Obj& obj, ColKey col) override
456✔
1125
        {
228✔
1126
            // This better be a mixed column
1127
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1128
            auto set = obj.get_set<Mixed>(col);
×
1129
            on_set(set);
×
1130
        }
×
1131
        void on_set(SetBase& set) override
456✔
1132
        {
456✔
1133
            auto col = set.get_col_key();
456✔
1134
            auto data_type = DataType(col.get_type());
456✔
1135
            auto table = set.get_table();
456✔
1136
            auto table_name = table->get_name();
456✔
1137
            auto field_name = table->get_column_name(col);
456✔
1138

228✔
1139
            auto inserter = util::overload{
456✔
1140
                [&](const ObjLink& link) {
300✔
1141
                    if (data_type == type_TypedLink) {
144✔
1142
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1143
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1144
                        link_set.erase(link);
×
1145
                    }
×
1146
                    else if (data_type == type_Mixed) {
144✔
1147
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
68✔
1148
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
68✔
1149
                        mixed_set.erase(link);
68✔
1150
                    }
68✔
1151
                    else if (data_type == type_Link) {
76✔
1152
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
76✔
1153
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
76✔
1154
                        // Validate the target.
38✔
1155
                        auto target_table = table->get_link_target(col);
76✔
1156
                        if (target_table->get_key() != link.get_table_key()) {
76✔
1157
                            m_applier->bad_transaction_log(
×
1158
                                "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
1159
                                table_name);
×
1160
                        }
×
1161
                        link_set.erase(link.get_obj_key());
76✔
1162
                    }
76✔
1163
                    else {
×
1164
                        m_applier->bad_transaction_log(
×
1165
                            "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1166
                            table_name, data_type);
×
1167
                    }
×
1168
                },
144✔
1169
                [&](Mixed value) {
384✔
1170
                    if (value.is_null() && !col.is_nullable()) {
312!
1171
                        m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name,
×
1172
                                                       table_name);
×
1173
                    }
×
1174

156✔
1175
                    if (data_type == type_Mixed || value.get_type() == data_type) {
312✔
1176
                        set.erase_any(value);
312✔
1177
                    }
312✔
1178
                    else {
×
1179
                        m_applier->bad_transaction_log(
×
1180
                            "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name,
×
1181
                            data_type, value.get_type());
×
1182
                    }
×
1183
                },
312✔
1184
                [&](const Instruction::Payload::ObjectValue&) {
228✔
1185
                    m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported.");
×
1186
                },
×
1187
                [&](const Instruction::Payload::List&) {
228✔
1188
                    m_applier->bad_transaction_log("SetErase: Sets of lists are not supported.");
×
1189
                },
×
1190
                [&](const Instruction::Payload::Set&) {
228✔
1191
                    m_applier->bad_transaction_log("SetErase: Sets of sets are not supported.");
×
1192
                },
×
1193
                [&](const Instruction::Payload::Dictionary&) {
228✔
1194
                    m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported.");
×
1195
                },
×
1196
                [&](const Instruction::Payload::Erased&) {
228✔
1197
                    m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase");
×
1198
                },
×
1199
            };
456✔
1200

228✔
1201
            m_applier->visit_payload(m_instr.value, inserter);
456✔
1202
        }
456✔
1203

228✔
1204
    private:
456✔
1205
        const Instruction::SetErase& m_instr;
456✔
1206
    };
456✔
1207
    SetEraseResolver(this, instr).resolve();
456✔
1208
}
456✔
1209

1210
StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr,
1211
                                              const std::string_view& name)
1212
{
314,350✔
1213
    if (auto class_name = m_log->try_get_string(instr.table)) {
314,350✔
1214
        return Group::class_name_to_table_name(*class_name, m_table_name_buffer);
314,346✔
1215
    }
314,346✔
1216
    else {
4✔
1217
        bad_transaction_log("Corrupt table name in %1 instruction", name);
4✔
1218
    }
4✔
1219
}
314,350✔
1220

1221
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1222
{
650,264✔
1223
    if (instr.table == m_last_table_name) {
650,264✔
1224
        return m_last_table;
352,606✔
1225
    }
352,606✔
1226
    else {
297,658✔
1227
        auto table_name = get_table_name(instr, name);
297,658✔
1228
        TableRef table = m_transaction.get_table(table_name);
297,658✔
1229
        if (!table) {
297,658✔
1230
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
×
1231
        }
×
1232
        m_last_table = table;
297,658✔
1233
        m_last_table_name = instr.table;
297,658✔
1234
        m_last_object_key.reset();
297,658✔
1235
        m_last_object.reset();
297,658✔
1236
        m_last_field_name = InternString{};
297,658✔
1237
        m_last_field = ColKey{};
297,658✔
1238
        return table;
297,658✔
1239
    }
297,658✔
1240
}
650,264✔
1241

1242
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1243
                                                       const std::string_view& name)
1244
{
736,092✔
1245
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
736,092✔
1246
        *m_last_object_key == instr.object) {
656,438✔
1247
        // We have already found the object, reuse it.
115,328✔
1248
        return *m_last_object;
244,078✔
1249
    }
244,078✔
1250
    else {
492,014✔
1251
        TableRef table = get_table(instr, name);
492,014✔
1252
        ObjKey key = get_object_key(*table, instr.object, name);
492,014✔
1253
        if (!key) {
492,014✔
1254
            return util::none;
×
1255
        }
×
1256
        if (!table->is_valid(key)) {
492,014✔
1257
            // Check if the object is deleted or is a tombstone.
6,694✔
1258
            return util::none;
14,634✔
1259
        }
14,634✔
1260

240,368✔
1261
        Obj obj = table->get_object(key);
477,380✔
1262
        m_last_object_key = instr.object;
477,380✔
1263
        m_last_object = obj;
477,380✔
1264
        return obj;
477,380✔
1265
    }
477,380✔
1266
}
736,092✔
1267

1268
LstBasePtr InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1269
{
190,546✔
1270
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
96,850✔
1271
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
96,850✔
1272
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
96,850✔
1273
    // links.
96,850✔
1274
    REALM_ASSERT(col.is_list());
190,546✔
1275
    LstBasePtr list;
190,546✔
1276
    if (col.get_type() == col_type_Link || col.get_type() == col_type_LinkList) {
190,546✔
1277
        auto table = obj.get_table();
13,748✔
1278
        if (!table->get_link_target(col)->is_embedded()) {
13,748✔
1279
            list = obj.get_list_ptr<ObjKey>(col);
1,564✔
1280
        }
1,564✔
1281
        else {
12,184✔
1282
            list = obj.get_listbase_ptr(col);
12,184✔
1283
        }
12,184✔
1284
    }
13,748✔
1285
    else {
176,798✔
1286
        list = obj.get_listbase_ptr(col);
176,798✔
1287
    }
176,798✔
1288
    return list;
190,546✔
1289
}
190,546✔
1290

1291
InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
1292
                                               const std::string_view& instr_name)
1293
    : m_applier(applier)
1294
    , m_path_instr(instr)
1295
    , m_instr_name(instr_name)
1296
{
679,458✔
1297
}
679,458✔
1298

1299
InstructionApplier::PathResolver::~PathResolver()
1300
{
679,478✔
1301
    on_finish();
679,478✔
1302
}
679,478✔
1303

1304
void InstructionApplier::PathResolver::on_property(Obj&, ColKey)
1305
{
×
1306
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name));
×
1307
}
×
1308

1309
void InstructionApplier::PathResolver::on_list(LstBase&)
1310
{
×
1311
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name));
×
1312
}
×
1313

1314
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t)
1315
{
×
1316
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name));
×
1317
    return Status::DidNotResolve;
×
1318
}
×
1319

1320
void InstructionApplier::PathResolver::on_dictionary(Dictionary&)
1321
{
×
1322
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1323
}
×
1324

1325
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed)
1326
{
×
1327
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1328
    return Status::DidNotResolve;
×
1329
}
×
1330

1331
void InstructionApplier::PathResolver::on_set(SetBase&)
1332
{
×
1333
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name));
×
1334
}
×
1335

1336
void InstructionApplier::PathResolver::on_error(const std::string& err_msg)
1337
{
×
1338
    m_applier->bad_transaction_log(err_msg);
×
1339
}
×
1340

1341
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
1342
{
667,902✔
1343
    m_applier->m_last_field = col;
667,902✔
1344
}
667,902✔
1345

1346
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
2,092✔
1347

1348
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
1349
{
7,452✔
1350
    return Status::Pending;
7,452✔
1351
}
7,452✔
1352

1353
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData,
1354
                                                                                                StringData)
1355
{
×
1356
    return Status::Pending;
×
1357
}
×
1358

1359
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
1360
{
655,610✔
1361
    m_applier->m_current_path = m_path_instr.path;
655,610✔
1362
    m_applier->m_last_field_name = m_path_instr.field;
655,610✔
1363
    return Status::Pending;
655,610✔
1364
}
655,610✔
1365

1366
void InstructionApplier::PathResolver::on_finish()
1367
{
679,456✔
1368
    m_applier->m_current_path.reset();
679,456✔
1369
    m_applier->m_last_field_name = InternString{};
679,456✔
1370
    m_applier->m_last_field = ColKey{};
679,456✔
1371
}
679,456✔
1372

1373
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1374
{
703,444✔
1375
    return m_applier->get_string(interned);
703,444✔
1376
}
703,444✔
1377

1378
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve()
1379
{
679,470✔
1380
    util::Optional<Obj> obj = m_applier->get_top_object(m_path_instr, m_instr_name);
679,470✔
1381
    Status begin_status = on_begin(obj);
679,470✔
1382
    if (begin_status != Status::Pending) {
679,470✔
1383
        return begin_status;
208✔
1384
    }
208✔
1385
    if (!obj) {
679,262✔
1386
        m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name,
×
1387
                                       format_pk(m_applier->m_log->get_key(m_path_instr.object)),
×
1388
                                       get_string(m_path_instr.table));
×
1389
    }
×
1390

334,842✔
1391
    m_it_begin = m_path_instr.path.begin();
679,262✔
1392
    m_it_end = m_path_instr.path.end();
679,262✔
1393
    Status status = resolve_field(*obj, m_path_instr.field);
679,262✔
1394
    return status == Status::Pending ? Status::Success : status;
677,006✔
1395
}
679,262✔
1396

1397
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field)
1398
{
694,950✔
1399
    auto field_name = get_string(field);
694,950✔
1400
    ColKey col = obj.get_table()->get_column_key(field_name);
694,950✔
1401
    if (!col) {
694,950✔
1402
        on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name,
×
1403
                              obj.get_table()->get_name()));
×
1404
        return Status::DidNotResolve;
×
1405
    }
×
1406
    on_column_advance(col);
694,950✔
1407

342,698✔
1408
    if (m_it_begin == m_it_end) {
694,950✔
1409
        if (col.is_list()) {
493,248✔
1410
            auto list = obj.get_listbase_ptr(col);
214✔
1411
            on_list(*list);
214✔
1412
        }
214✔
1413
        else if (col.is_dictionary()) {
493,034✔
1414
            auto dict = obj.get_dictionary(col);
80✔
1415
            on_dictionary(dict);
80✔
1416
        }
80✔
1417
        else if (col.is_set()) {
492,954✔
1418
            SetBasePtr set;
3,484✔
1419
            if (col.get_type() == col_type_Link) {
3,484✔
1420
                // We are interested in using non-condensed indexes - as for Lists below
152✔
1421
                set = obj.get_set_ptr<ObjKey>(col);
304✔
1422
            }
304✔
1423
            else {
3,180✔
1424
                set = obj.get_setbase_ptr(col);
3,180✔
1425
            }
3,180✔
1426
            on_set(*set);
3,484✔
1427
        }
3,484✔
1428
        else {
489,470✔
1429
            on_property(obj, col);
489,470✔
1430
        }
489,470✔
1431
        return Status::Pending;
493,248✔
1432
    }
493,248✔
1433

102,430✔
1434
    if (col.is_list()) {
201,702✔
1435
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
188,834✔
1436
            auto list = InstructionApplier::get_list_from_path(obj, col);
188,834✔
1437
            ++m_it_begin;
188,834✔
1438
            return resolve_list_element(*list, *pindex);
188,834✔
1439
        }
188,834✔
UNCOV
1440
        on_error(util::format("%1: List index is not an integer on field '%2' in class '%3'", m_instr_name,
×
UNCOV
1441
                              field_name, obj.get_table()->get_name()));
×
UNCOV
1442
    }
×
1443
    else if (col.is_dictionary()) {
12,868✔
1444
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
8,472✔
1445
            auto dict = obj.get_dictionary(col);
8,472✔
1446
            ++m_it_begin;
8,472✔
1447
            return resolve_dictionary_element(dict, *pkey);
8,472✔
1448
        }
8,472✔
1449
        on_error(util::format("%1: Dictionary key is not a string on field '%2' in class '%3'", m_instr_name,
×
1450
                              field_name, obj.get_table()->get_name()));
×
1451
    }
×
1452
    else if (col.get_type() == col_type_Mixed) {
4,396✔
1453
        auto val = obj.get<Mixed>(col);
×
1454
        if (val.is_type(type_Dictionary)) {
×
1455
            if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
×
1456
                Dictionary dict(obj, col);
×
1457
                ++m_it_begin;
×
1458
                return resolve_dictionary_element(dict, *pkey);
×
1459
            }
×
1460
        }
×
1461
        if (val.is_type(type_List)) {
×
1462
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
×
1463
                Lst<Mixed> list(obj, col);
×
1464
                ++m_it_begin;
×
1465
                return resolve_list_element(list, *pindex);
×
1466
            }
×
1467
        }
×
1468
        on_error(util::format("%1: Not a list or dictionary on field '%2' in class '%3'", m_instr_name, field_name,
×
1469
                              obj.get_table()->get_name()));
×
1470
    }
×
1471
    else if (col.get_type() == col_type_Link) {
4,396✔
1472
        auto target = obj.get_table()->get_link_target(col);
4,376✔
1473
        if (!target->is_embedded()) {
4,376✔
1474
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
×
1475
                                  field_name, obj.get_table()->get_name()));
×
1476
        }
×
1477
        else if (obj.is_null(col)) {
4,376✔
1478
            Status null_status =
132✔
1479
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
132✔
1480
            if (null_status != Status::Pending) {
132✔
1481
                return null_status;
132✔
1482
            }
132✔
1483
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
×
1484
                                  m_instr_name, field_name, obj.get_table()->get_name()));
×
1485
        }
×
1486
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,244✔
1487
            auto embedded_object = obj.get_linked_object(col);
4,244✔
1488
            ++m_it_begin;
4,244✔
1489
            return resolve_field(embedded_object, *pfield);
4,244✔
1490
        }
4,244✔
1491
        else {
×
1492
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1493
        }
×
1494
    }
4,376✔
1495
    else {
20✔
1496
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
20✔
1497
                              field_name, obj.get_table()->get_name(), col.get_type()));
20✔
1498
    }
20✔
1499
    return Status::DidNotResolve;
102,438✔
1500
}
201,702✔
1501

1502
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
1503
                                                                                                uint32_t index)
1504
{
188,834✔
1505
    if (m_it_begin == m_it_end) {
188,834✔
1506
        return on_list_index(list, index);
178,662✔
1507
    }
178,662✔
1508

5,086✔
1509
    auto col = list.get_col_key();
10,172✔
1510
    auto field_name = list.get_table()->get_column_name(col);
10,172✔
1511

5,086✔
1512
    if (col.get_type() == col_type_LinkList) {
10,172✔
1513
        auto target = list.get_table()->get_link_target(col);
10,172✔
1514
        if (!target->is_embedded()) {
10,172✔
1515
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1516
                                  list.get_table()->get_name(), index));
×
1517
            return Status::DidNotResolve;
×
1518
        }
×
1519

5,086✔
1520
        Status list_status = on_list_index_advance(index);
10,172✔
1521
        if (list_status != Status::Pending) {
10,172✔
1522
            return list_status;
2,720✔
1523
        }
2,720✔
1524

3,726✔
1525
        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
7,452✔
1526
        auto& link_list = static_cast<LnkLst&>(list);
7,452✔
1527
        if (index >= link_list.size()) {
7,452✔
1528
            on_error(util::format("%1: Out-of-bounds index through list at '%3.%2[%4]'", m_instr_name, field_name,
×
1529
                                  list.get_table()->get_name(), index));
×
1530
        }
×
1531
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
7,452✔
1532
            auto embedded_object = link_list.get_object(index);
7,452✔
1533
            ++m_it_begin;
7,452✔
1534
            return resolve_field(embedded_object, *pfield);
7,452✔
1535
        }
7,452✔
1536
        on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1537
    }
×
UNCOV
1538
    else {
×
UNCOV
1539
        if (list.get_data_type() == type_Mixed) {
×
1540
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
×
1541
            auto val = mixed_list.get(index);
×
1542

1543
            if (val.is_type(type_Dictionary)) {
×
1544
                if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
×
1545
                    Dictionary d(mixed_list, mixed_list.get_key(index));
×
1546
                    ++m_it_begin;
×
1547
                    return resolve_dictionary_element(d, *pfield);
×
1548
                }
×
1549
            }
×
1550
            if (val.is_type(type_List)) {
×
1551
                if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
×
1552
                    Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
×
1553
                    ++m_it_begin;
×
1554
                    return resolve_list_element(l, *pindex);
×
1555
                }
×
UNCOV
1556
            }
×
1557
        }
×
1558

UNCOV
1559
        on_error(util::format(
×
UNCOV
1560
            "%1: Resolving path through unstructured list element on '%3.%2', which is a list of type '%4'",
×
UNCOV
1561
            m_instr_name, field_name, list.get_table()->get_name(), col.get_type()));
×
UNCOV
1562
    }
×
1563
    return Status::DidNotResolve;
5,086✔
1564
}
10,172✔
1565

1566
InstructionApplier::PathResolver::Status
1567
InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key)
1568
{
8,472✔
1569
    StringData string_key = get_string(key);
8,472✔
1570
    if (m_it_begin == m_it_end) {
8,472✔
1571
        return on_dictionary_key(dict, Mixed{string_key});
4,396✔
1572
    }
4,396✔
1573

2,038✔
1574
    on_dict_key_advance(string_key);
4,076✔
1575

2,038✔
1576
    auto col = dict.get_col_key();
4,076✔
1577
    auto table = dict.get_table();
4,076✔
1578
    auto field_name = table->get_column_name(col);
4,076✔
1579

2,038✔
1580
    if (col.get_type() == col_type_Link) {
4,076✔
1581
        auto target = dict.get_target_table();
4,076✔
1582
        if (!target->is_embedded()) {
4,076✔
1583
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1584
                                  table->get_name(), string_key));
×
1585
            return Status::DidNotResolve;
×
1586
        }
×
1587

2,038✔
1588
        auto embedded_object = dict.get_object(string_key);
4,076✔
1589
        if (!embedded_object) {
4,076✔
1590
            Status null_link_status = on_null_link_advance(table->get_name(), string_key);
80✔
1591
            if (null_link_status != Status::Pending) {
80✔
1592
                return null_link_status;
80✔
1593
            }
80✔
1594
            on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name,
×
1595
                                  table->get_name(), string_key));
×
1596
        }
×
1597
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
3,996✔
1598
            ++m_it_begin;
3,996✔
1599
            return resolve_field(embedded_object, *pfield);
3,996✔
1600
        }
3,996✔
1601
        else {
×
1602
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1603
        }
×
1604
    }
4,076✔
1605
    else {
×
1606
        auto val = dict.get(string_key);
×
1607
        if (val.is_type(type_Dictionary)) {
×
1608
            if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
×
1609
                Dictionary d(dict, dict.build_index(string_key));
×
1610
                ++m_it_begin;
×
1611
                return resolve_dictionary_element(d, *pfield);
×
1612
            }
×
1613
        }
×
1614
        if (val.is_type(type_List)) {
×
1615
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
×
1616
                Lst<Mixed> l(dict, dict.build_index(string_key));
×
1617
                ++m_it_begin;
×
1618
                return resolve_list_element(l, *pindex);
×
1619
            }
×
1620
        }
×
1621
        on_error(
×
1622
            util::format("%1: Resolving path through non link element on '%3.%2', which is a dictionary of type '%4'",
×
1623
                         m_instr_name, field_name, table->get_name(), col.get_type()));
×
1624
    }
×
1625
    return Status::DidNotResolve;
2,038✔
1626
}
4,076✔
1627

1628

1629
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1630
                                          const std::string_view& name) const
1631
{
493,828✔
1632
    StringData table_name = table.get_name();
493,828✔
1633
    ColKey pk_col = table.get_primary_key_column();
493,828✔
1634
    StringData pk_name = "";
493,828✔
1635
    DataType pk_type;
493,828✔
1636
    if (pk_col) {
493,848✔
1637
        pk_name = table.get_column_name(pk_col);
493,848✔
1638
        pk_type = table.get_column_type(pk_col);
493,848✔
1639
    }
493,848✔
1640
    return mpark::visit(
493,828✔
1641
        util::overload{
493,828✔
1642
            [&](mpark::monostate) {
247,986✔
1643
                if (!pk_col) {
24✔
1644
                    bad_transaction_log(
×
1645
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
×
1646
                        name, table_name);
×
1647
                }
×
1648
                if (!table.is_nullable(pk_col)) {
24✔
1649
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
×
1650
                                        name, table_name, pk_name);
×
1651
                }
×
1652

12✔
1653
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
24✔
1654
                return key;
24✔
1655
            },
24✔
1656
            [&](int64_t pk) {
482,376✔
1657
                if (!pk_col) {
471,064✔
1658
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
×
1659
                                        "a primary key column",
×
1660
                                        name, pk, table_name);
×
1661
                }
×
1662
                if (pk_type != type_Int) {
471,064✔
1663
                    bad_transaction_log(
×
1664
                        "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1665
                        name, pk, table_name, pk_name, pk_type);
×
1666
                }
×
1667
                ObjKey key = table.get_objkey_from_primary_key(pk);
471,064✔
1668
                return key;
471,064✔
1669
            },
471,064✔
1670
            [&](InternString interned_pk) {
257,008✔
1671
                auto pk = get_string(interned_pk);
17,894✔
1672
                if (!pk_col) {
17,894✔
1673
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1674
                                        "have a primary key column",
×
1675
                                        name, pk, table_name);
×
1676
                }
×
1677
                if (pk_type != type_String) {
17,894✔
1678
                    bad_transaction_log(
×
1679
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
×
1680
                        name, pk, table_name, pk_name, pk_type);
×
1681
                }
×
1682
                ObjKey key = table.get_objkey_from_primary_key(pk);
17,894✔
1683
                return key;
17,894✔
1684
            },
17,894✔
1685
            [&](GlobalKey id) {
247,976✔
1686
                if (pk_col) {
4✔
1687
                    bad_transaction_log(
×
1688
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
1689
                        name, table_name, pk_type);
×
1690
                }
×
1691
                ObjKey key = table.get_objkey_from_global_key(id);
4✔
1692
                return key;
4✔
1693
            },
4✔
1694
            [&](ObjectId pk) {
250,400✔
1695
                if (!pk_col) {
4,852✔
1696
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1697
                                        "have a primary key column",
×
1698
                                        name, pk, table_name);
×
1699
                }
×
1700
                if (pk_type != type_ObjectId) {
4,852✔
1701
                    bad_transaction_log(
×
1702
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1703
                        name, pk, table_name, pk_name, pk_type);
×
1704
                }
×
1705
                ObjKey key = table.get_objkey_from_primary_key(pk);
4,852✔
1706
                return key;
4,852✔
1707
            },
4,852✔
1708
            [&](UUID pk) {
247,984✔
1709
                if (!pk_col) {
20✔
1710
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
×
1711
                                        "have a primary key column",
×
1712
                                        name, pk, table_name);
×
1713
                }
×
1714
                if (pk_type != type_UUID) {
20✔
1715
                    bad_transaction_log(
×
1716
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
×
1717
                        pk, table_name, pk_name, pk_type);
×
1718
                }
×
1719
                ObjKey key = table.get_objkey_from_primary_key(pk);
20✔
1720
                return key;
20✔
1721
            }},
20✔
1722
        primary_key);
493,828✔
1723
}
493,828✔
1724

1725

1726
} // 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

© 2025 Coveralls, Inc