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

realm / realm-core / jorgen.edelbo_138

13 Mar 2024 08:41AM UTC coverage: 91.77% (-0.3%) from 92.078%
jorgen.edelbo_138

Pull #7356

Evergreen

jedelbo
Add ability to get path to modified collections in object notifications
Pull Request #7356: Add ability to get path to modified collections in object notifications

94532 of 174642 branches covered (54.13%)

118 of 163 new or added lines in 16 files covered. (72.39%)

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

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

70.29
/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.
4✔
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
4✔
59
    // in over the network, defective changesets are part of normal program
4✔
60
    // flow).
4✔
61
    bad_transaction_log(util::format(msg, std::forward<Params>(params)...));
8✔
62
}
8✔
63

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

72
StringData InstructionApplier::get_string(StringBufferRange range) const
73
{
175,820✔
74
    auto string = m_log->try_get_string(range);
175,820✔
75
    if (!string)
175,820✔
76
        bad_transaction_log("string read error");
×
77
    return *string;
175,820✔
78
}
175,820✔
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)
100
        , backup()
101
    {
12,042✔
102
        std::swap(target, backup);
12,042✔
103
    }
12,042✔
104

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

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

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

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

1,982✔
121
    auto add_table = util::overload{
4,094✔
122
        [&](const Instruction::AddTable::TopLevelTable& spec) {
3,984✔
123
            auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel);
3,862✔
124
            if (spec.pk_type == Instruction::Payload::Type::GlobalKey) {
3,874✔
125
                m_transaction.get_or_add_table(table_name, table_type);
8✔
126
            }
8✔
127
            else {
3,866✔
128
                if (!is_valid_key_type(spec.pk_type)) {
3,866✔
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);
3,866✔
133
                StringData pk_field = get_string(spec.pk_field);
3,866✔
134
                bool nullable = spec.pk_nullable;
3,866✔
135

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

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

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

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

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

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

22,490✔
177
    mpark::visit(
46,040✔
178
        util::overload{
46,040✔
179
            [&](mpark::monostate) {
22,506✔
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) {
41,348✔
189
                if (!pk_col) {
37,600✔
190
                    bad_transaction_log("CreateObject(Int) on table without a primary key");
×
191
                }
×
192
                if (table->get_column_type(pk_col) != type_Int) {
37,600✔
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);
37,600✔
197
            },
37,600✔
198
            [&](InternString pk) {
23,002✔
199
                if (!pk_col) {
870✔
200
                    bad_transaction_log("CreateObject(String) on table without a primary key");
×
201
                }
×
202
                if (table->get_column_type(pk_col) != type_String) {
870✔
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);
870✔
207
                m_last_object = table->create_object_with_primary_key(str);
870✔
208
            },
870✔
209
            [&](const ObjectId& id) {
26,624✔
210
                if (!pk_col) {
7,480✔
211
                    bad_transaction_log("CreateObject(ObjectId) on table without a primary key");
×
212
                }
×
213
                if (table->get_column_type(pk_col) != type_ObjectId) {
7,480✔
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,480✔
218
            },
7,480✔
219
            [&](const UUID& id) {
22,514✔
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) {
22,496✔
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
        },
46,040✔
236
        instr.object);
46,040✔
237
}
46,040✔
238

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

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

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

313
void InstructionApplier::operator()(const Instruction::Update& instr)
314
{
468,374✔
315
    struct UpdateResolver : public PathResolver {
468,374✔
316
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
468,374✔
317
            : PathResolver(applier, instr, "Update")
468,374✔
318
            , m_instr(instr)
468,374✔
319
        {
468,380✔
320
        }
468,380✔
321
        void on_property(Obj& obj, ColKey col) override
468,374✔
322
        {
466,378✔
323
            // Update of object field.
226,546✔
324

226,546✔
325
            auto table = obj.get_table();
464,376✔
326
            auto table_name = table->get_name();
464,376✔
327
            auto field_name = table->get_column_name(col);
464,376✔
328
            auto data_type = DataType(col.get_type());
464,376✔
329

226,546✔
330
            auto visitor = [&](const mpark::variant<ObjLink, Mixed, Instruction::Payload::ObjectValue,
464,376✔
331
                                                    Instruction::Payload::Dictionary, Instruction::Payload::List,
464,376✔
332
                                                    Instruction::Payload::Set, Instruction::Payload::Erased>& arg) {
464,426✔
333
                if (const auto link_ptr = mpark::get_if<ObjLink>(&arg)) {
464,426✔
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)) {
464,342✔
353
                    if (mixed_ptr->is_null()) {
463,168✔
354
                        if (col.is_nullable()) {
228✔
355
                            obj.set_null(col, m_instr.is_default);
228✔
356
                        }
228✔
357
                        else {
×
358
                            m_applier->bad_transaction_log("Update: NULL in non-nullable field '%2.%1'", field_name,
×
359
                                                           table_name);
×
360
                        }
×
361
                    }
228✔
362
                    else if (data_type == type_Mixed || mixed_ptr->get_type() == data_type) {
462,940✔
363
                        obj.set_any(col, *mixed_ptr, m_instr.is_default);
462,930✔
364
                    }
462,930✔
365
                    else {
10✔
366
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
10✔
367
                                                       field_name, table_name, col.get_type(), mixed_ptr->get_type());
10✔
368
                    }
10✔
369
                }
463,168✔
370
                else if (const auto obj_val_ptr = mpark::get_if<Instruction::Payload::ObjectValue>(&arg)) {
1,174✔
371
                    if (obj.is_null(col)) {
652✔
372
                        obj.create_and_set_linked_object(col);
624✔
373
                    }
624✔
374
                }
652✔
375
                else if (const auto erase_ptr = mpark::get_if<Instruction::Payload::Erased>(&arg)) {
522✔
376
                    m_applier->bad_transaction_log("Update: Dictionary erase at object field");
×
377
                }
×
378
                else if (mpark::get_if<Instruction::Payload::Dictionary>(&arg)) {
522✔
379
                    obj.set_collection(col, CollectionType::Dictionary);
300✔
380
                }
300✔
381
                else if (mpark::get_if<Instruction::Payload::List>(&arg)) {
236✔
382
                    obj.set_collection(col, CollectionType::List);
232✔
383
                }
232✔
384
                else if (mpark::get_if<Instruction::Payload::Set>(&arg)) {
2,147,483,651✔
385
                    obj.set_collection(col, CollectionType::Set);
×
386
                }
×
387
            };
464,426✔
388

226,546✔
389
            m_applier->visit_payload(m_instr.value, visitor);
464,376✔
390
        }
464,376✔
391
        Status on_list_index(LstBase& list, uint32_t index) override
468,374✔
392
        {
228,746✔
393
            // Update of list element.
210✔
394

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

210✔
401
            auto visitor = util::overload{
408✔
402
                [&](const ObjLink& link) {
288✔
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_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) {
296✔
432
                    if (value.is_null()) {
184✔
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 {
184✔
442
                        if (data_type == type_Mixed || value.get_type() == data_type) {
184✔
443
                            list.set_any(index, value);
184✔
444
                        }
184✔
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
                    }
184✔
451
                },
184✔
452
                [&](const Instruction::Payload::ObjectValue&) {
210✔
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&) {
218✔
457
                    list.set_collection(size_t(index), CollectionType::Dictionary);
16✔
458
                },
16✔
459
                [&](const Instruction::Payload::List&) {
236✔
460
                    list.set_collection(size_t(index), CollectionType::List);
52✔
461
                },
52✔
462
                [&](const Instruction::Payload::Set&) {
210✔
463
                    list.set_collection(size_t(index), CollectionType::Set);
×
464
                },
×
465
                [&](const Instruction::Payload::Erased&) {
210✔
466
                    m_applier->bad_transaction_log("Update: Dictionary erase of list element");
×
467
                },
×
468
            };
408✔
469

210✔
470
            m_applier->visit_payload(m_instr.value, visitor);
408✔
471
            return Status::Pending;
408✔
472
        }
408✔
473
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
468,374✔
474
        {
230,334✔
475
            // Update (insert) of dictionary element.
1,786✔
476

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

1,786✔
508
            m_applier->visit_payload(m_instr.value, visitor);
3,572✔
509
            return Status::Pending;
3,572✔
510
        }
3,572✔
511

228,548✔
512
    private:
468,374✔
513
        const Instruction::Update& m_instr;
468,374✔
514
    };
468,374✔
515
    UpdateResolver resolver(this, instr);
468,374✔
516
    resolver.resolve();
468,374✔
517
}
468,374✔
518

519
void InstructionApplier::operator()(const Instruction::AddInteger& instr)
520
{
2,686✔
521
    // FIXME: Implement increments of array elements, dictionary values.
1,356✔
522
    struct AddIntegerResolver : public PathResolver {
2,686✔
523
        AddIntegerResolver(InstructionApplier* applier, const Instruction::AddInteger& instr)
2,686✔
524
            : PathResolver(applier, instr, "AddInteger")
2,686✔
525
            , m_instr(instr)
2,686✔
526
        {
2,686✔
527
        }
2,686✔
528
        void on_property(Obj& obj, ColKey col)
2,686✔
529
        {
2,686✔
530
            // Increment of object field.
1,356✔
531
            if (!obj.is_null(col)) {
2,686✔
532
                try {
2,630✔
533
                    obj.add_int(col, m_instr.value);
2,630✔
534
                }
2,630✔
535
                catch (const LogicError&) {
1,328✔
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
            }
2,630✔
541
        }
2,686✔
542

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

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

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

3,392✔
558
    auto table = get_table(instr, "AddColumn");
7,806✔
559
    auto col_name = get_string(instr.field);
7,806✔
560

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

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

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

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

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

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

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

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

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

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

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

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

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

81,552✔
802
            m_applier->visit_payload(m_instr.value, inserter);
162,920✔
803
            return Status::Pending;
162,920✔
804
        }
162,920✔
805

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

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

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

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

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

878
void InstructionApplier::operator()(const Instruction::Clear& instr)
879
{
336✔
880
    struct ClearResolver : public PathResolver {
336✔
881
        ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr)
336✔
882
            : PathResolver(applier, instr, "Clear")
336✔
883
        {
336✔
884
        }
336✔
885
        void on_list(LstBase& list) override
336✔
886
        {
216✔
887
            list.clear();
96✔
888
        }
96✔
889
        Status on_list_index(LstBase& list, uint32_t index) override
336✔
890
        {
180✔
891
            REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
32✔
892
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
32✔
893
            if (index >= mixed_list.size()) {
32✔
894
                m_applier->bad_transaction_log("Clear: Index out of bounds (%1 > %2)", index,
×
895
                                               mixed_list.size()); // Throws
×
896
                return Status::DidNotResolve;
×
897
            }
×
898
            auto val = mixed_list.get(index);
32✔
899
            if (val.is_type(type_Dictionary)) {
32✔
900
                Dictionary d(mixed_list, mixed_list.get_key(index));
16✔
901
                d.clear();
16✔
902
                return Status::Pending;
16✔
903
            }
16✔
904
            if (val.is_type(type_List)) {
16✔
905
                Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
16✔
906
                l.clear();
16✔
907
                return Status::Pending;
16✔
908
            }
16✔
909
            m_applier->bad_transaction_log("Clear: Item (%1) at index %2 is not a collection", val.get_type(),
×
910
                                           index); // Throws
×
911
            return Status::DidNotResolve;
×
912
        }
×
913
        void on_dictionary(Dictionary& dict) override
336✔
914
        {
188✔
915
            dict.clear();
48✔
916
        }
48✔
917
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
336✔
918
        {
182✔
919
            auto val = dict.get(key);
36✔
920
            if (val.is_type(type_Dictionary)) {
36✔
921
                Dictionary d(dict, dict.build_index(key));
16✔
922
                d.clear();
16✔
923
                return Status::Pending;
16✔
924
            }
16✔
925
            if (val.is_type(type_List)) {
20✔
926
                Lst<Mixed> l(dict, dict.build_index(key));
20✔
927
                l.clear();
20✔
928
                return Status::Pending;
20✔
929
            }
20✔
930
            m_applier->bad_transaction_log("Clear: Item (%1) at key '%2' is not a collection", val.get_type(),
×
931
                                           key); // Throws
×
932
            return Status::DidNotResolve;
×
933
        }
×
934
        void on_set(SetBase& set) override
336✔
935
        {
196✔
936
            set.clear();
64✔
937
        }
64✔
938
        void on_property(Obj& obj, ColKey col_key) override
336✔
939
        {
194✔
940
            if (col_key.get_type() == col_type_Mixed) {
60✔
941
                auto val = obj.get<Mixed>(col_key);
60✔
942
                if (val.is_type(type_Dictionary)) {
60✔
943
                    Dictionary dict(obj, col_key);
28✔
944
                    dict.clear();
28✔
945
                    return;
28✔
946
                }
28✔
947
                else if (val.is_type(type_List)) {
32✔
948
                    Lst<Mixed> list(obj, col_key);
32✔
949
                    list.clear();
32✔
950
                    return;
32✔
951
                }
32✔
952
                else if (val.is_type(type_Set)) {
×
953
                    Set<Mixed> set(obj, col_key);
×
954
                    set.clear();
×
955
                    return;
×
956
                }
×
957
            }
×
958

959
            PathResolver::on_property(obj, col_key);
×
960
        }
×
961
    };
336✔
962
    ClearResolver(this, instr).resolve();
336✔
963
}
336✔
964

965
bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr,
966
                                           const std::string_view& instr_name)
967
{
40✔
968
    struct AllowsNullsResolver : public PathResolver {
40✔
969
        AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
40✔
970
                            const std::string_view& instr_name)
40✔
971
            : PathResolver(applier, instr, instr_name)
40✔
972
            , m_allows_nulls(false)
40✔
973
        {
40✔
974
        }
40✔
975
        Status on_list_index(LstBase&, uint32_t) override
40✔
976
        {
20✔
977
            return Status::Pending;
×
978
        }
×
979
        void on_list(LstBase&) override {}
20✔
980
        void on_set(SetBase&) override {}
20✔
981
        void on_dictionary(Dictionary&) override
40✔
982
        {
20✔
983
            m_allows_nulls = true;
×
984
        }
×
985
        Status on_dictionary_key(Dictionary&, Mixed) override
40✔
986
        {
40✔
987
            m_allows_nulls = true;
40✔
988
            return Status::Pending;
40✔
989
        }
40✔
990
        void on_property(Obj&, ColKey) override
40✔
991
        {
20✔
992
            m_allows_nulls = true;
×
993
        }
×
994
        bool allows_nulls()
40✔
995
        {
40✔
996
            resolve();
40✔
997
            return m_allows_nulls;
40✔
998
        }
40✔
999

20✔
1000
    private:
40✔
1001
        bool m_allows_nulls;
40✔
1002
    };
40✔
1003
    return AllowsNullsResolver(this, instr, instr_name).allows_nulls();
40✔
1004
}
40✔
1005

1006
std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const
1007
{
×
1008
    REALM_ASSERT(m_log);
×
1009
    std::stringstream ss;
×
1010
    m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
1011
    return ss.str();
×
1012
}
×
1013

1014
bool InstructionApplier::check_links_exist(const Instruction::Payload& payload)
1015
{
23,960✔
1016
    bool valid_payload = true;
23,960✔
1017
    using Type = Instruction::Payload::Type;
23,960✔
1018
    if (payload.type == Type::Link) {
23,960✔
1019
        StringData class_name = get_string(payload.data.link.target_table);
800✔
1020
        Group::TableNameBuffer buffer;
800✔
1021
        StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
800✔
1022
        TableRef target_table = m_transaction.get_table(target_table_name);
800✔
1023
        if (!target_table) {
800✔
1024
            bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
1025
        }
×
1026
        if (target_table->is_embedded()) {
800✔
1027
            bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
1028
        }
×
1029
        Mixed linked_pk =
800✔
1030
            mpark::visit(util::overload{[&](mpark::monostate) {
412✔
1031
                                            return Mixed{}; // the link exists and the pk is null
24✔
1032
                                        },
24✔
1033
                                        [&](int64_t pk) {
780✔
1034
                                            return Mixed{pk};
760✔
1035
                                        },
760✔
1036
                                        [&](InternString interned_pk) {
400✔
1037
                                            return Mixed{get_string(interned_pk)};
×
1038
                                        },
×
1039
                                        [&](GlobalKey) -> Mixed {
400✔
1040
                                            bad_transaction_log(
×
1041
                                                "Unexpected link to embedded object while validating a primary key");
×
1042
                                        },
×
1043
                                        [&](ObjectId pk) {
408✔
1044
                                            return Mixed{pk};
16✔
1045
                                        },
16✔
1046
                                        [&](UUID pk) {
400✔
1047
                                            return Mixed{pk};
×
1048
                                        }},
×
1049
                         payload.data.link.target);
800✔
1050

400✔
1051
        if (!target_table->find_primary_key(linked_pk)) {
800✔
1052
            valid_payload = false;
168✔
1053
        }
168✔
1054
    }
800✔
1055
    return valid_payload;
23,960✔
1056
}
23,960✔
1057

1058
void InstructionApplier::operator()(const Instruction::SetInsert& instr)
1059
{
1,752✔
1060
    struct SetInsertResolver : public PathResolver {
1,752✔
1061
        SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr)
1,752✔
1062
            : PathResolver(applier, instr, "SetInsert")
1,752✔
1063
            , m_instr(instr)
1,752✔
1064
        {
1,752✔
1065
        }
1,752✔
1066
        void on_property(Obj& obj, ColKey col) override
1,752✔
1067
        {
876✔
1068
            // This better be a mixed column
1069
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1070
            auto set = obj.get_set<Mixed>(col);
×
1071
            on_set(set);
×
1072
        }
×
1073
        void on_set(SetBase& set) override
1,752✔
1074
        {
1,752✔
1075
            auto col = set.get_col_key();
1,752✔
1076
            auto data_type = DataType(col.get_type());
1,752✔
1077
            auto table = set.get_table();
1,752✔
1078
            auto table_name = table->get_name();
1,752✔
1079
            auto field_name = table->get_column_name(col);
1,752✔
1080

876✔
1081
            auto inserter = util::overload{
1,752✔
1082
                [&](const ObjLink& link) {
972✔
1083
                    if (data_type == type_TypedLink) {
192✔
1084
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1085
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1086
                        link_set.insert(link);
×
1087
                    }
×
1088
                    else if (data_type == type_Mixed) {
192✔
1089
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
88✔
1090
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
88✔
1091
                        mixed_set.insert(link);
88✔
1092
                    }
88✔
1093
                    else if (data_type == type_Link) {
104✔
1094
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
104✔
1095
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
104✔
1096
                        // Validate the target.
52✔
1097
                        auto target_table = table->get_link_target(col);
104✔
1098
                        if (target_table->get_key() != link.get_table_key()) {
104✔
1099
                            m_applier->bad_transaction_log(
×
1100
                                "SetInsert: Target table mismatch (expected '%1', got '%2')",
×
1101
                                target_table->get_name(), table_name);
×
1102
                        }
×
1103
                        link_set.insert(link.get_obj_key());
104✔
1104
                    }
104✔
1105
                    else {
×
1106
                        m_applier->bad_transaction_log(
×
1107
                            "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1108
                            table_name, data_type);
×
1109
                    }
×
1110
                },
192✔
1111
                [&](Mixed value) {
1,656✔
1112
                    if (value.is_null() && !col.is_nullable()) {
1,560✔
1113
                        m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name,
×
1114
                                                       table_name);
×
1115
                    }
×
1116

780✔
1117
                    if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) {
1,560✔
1118
                        set.insert_any(value);
1,560✔
1119
                    }
1,560✔
1120
                    else {
×
1121
                        m_applier->bad_transaction_log(
×
1122
                            "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name,
×
1123
                            table_name, data_type, value.get_type());
×
1124
                    }
×
1125
                },
1,560✔
1126
                [&](const Instruction::Payload::ObjectValue&) {
876✔
1127
                    m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported.");
×
1128
                },
×
1129
                [&](const Instruction::Payload::Dictionary&) {
876✔
1130
                    m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported.");
×
1131
                },
×
1132
                [&](const Instruction::Payload::List&) {
876✔
1133
                    m_applier->bad_transaction_log("SetInsert: Sets of lists are not supported.");
×
1134
                },
×
1135
                [&](const Instruction::Payload::Set&) {
876✔
1136
                    m_applier->bad_transaction_log("SetInsert: Sets of sets are not supported.");
×
1137
                },
×
1138
                [&](const Instruction::Payload::Erased&) {
876✔
1139
                    m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert");
×
1140
                },
×
1141
            };
1,752✔
1142

876✔
1143
            m_applier->visit_payload(m_instr.value, inserter);
1,752✔
1144
        }
1,752✔
1145

876✔
1146
    private:
1,752✔
1147
        const Instruction::SetInsert& m_instr;
1,752✔
1148
    };
1,752✔
1149
    SetInsertResolver(this, instr).resolve();
1,752✔
1150
}
1,752✔
1151

1152
void InstructionApplier::operator()(const Instruction::SetErase& instr)
1153
{
480✔
1154
    struct SetEraseResolver : public PathResolver {
480✔
1155
        SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr)
480✔
1156
            : PathResolver(applier, instr, "SetErase")
480✔
1157
            , m_instr(instr)
480✔
1158
        {
480✔
1159
        }
480✔
1160
        void on_property(Obj& obj, ColKey col) override
480✔
1161
        {
240✔
1162
            // This better be a mixed column
1163
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1164
            auto set = obj.get_set<Mixed>(col);
×
1165
            on_set(set);
×
1166
        }
×
1167
        void on_set(SetBase& set) override
480✔
1168
        {
480✔
1169
            auto col = set.get_col_key();
480✔
1170
            auto data_type = DataType(col.get_type());
480✔
1171
            auto table = set.get_table();
480✔
1172
            auto table_name = table->get_name();
480✔
1173
            auto field_name = table->get_column_name(col);
480✔
1174

240✔
1175
            auto inserter = util::overload{
480✔
1176
                [&](const ObjLink& link) {
324✔
1177
                    if (data_type == type_TypedLink) {
168✔
1178
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1179
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1180
                        link_set.erase(link);
×
1181
                    }
×
1182
                    else if (data_type == type_Mixed) {
168✔
1183
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
80✔
1184
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
80✔
1185
                        mixed_set.erase(link);
80✔
1186
                    }
80✔
1187
                    else if (data_type == type_Link) {
88✔
1188
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
88✔
1189
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
88✔
1190
                        // Validate the target.
44✔
1191
                        auto target_table = table->get_link_target(col);
88✔
1192
                        if (target_table->get_key() != link.get_table_key()) {
88✔
1193
                            m_applier->bad_transaction_log(
×
1194
                                "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
1195
                                table_name);
×
1196
                        }
×
1197
                        link_set.erase(link.get_obj_key());
88✔
1198
                    }
88✔
1199
                    else {
×
1200
                        m_applier->bad_transaction_log(
×
1201
                            "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1202
                            table_name, data_type);
×
1203
                    }
×
1204
                },
168✔
1205
                [&](Mixed value) {
396✔
1206
                    if (value.is_null() && !col.is_nullable()) {
312!
1207
                        m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name,
×
1208
                                                       table_name);
×
1209
                    }
×
1210

156✔
1211
                    if (data_type == type_Mixed || value.get_type() == data_type) {
312✔
1212
                        set.erase_any(value);
312✔
1213
                    }
312✔
1214
                    else {
×
1215
                        m_applier->bad_transaction_log(
×
1216
                            "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name,
×
1217
                            data_type, value.get_type());
×
1218
                    }
×
1219
                },
312✔
1220
                [&](const Instruction::Payload::ObjectValue&) {
240✔
1221
                    m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported.");
×
1222
                },
×
1223
                [&](const Instruction::Payload::List&) {
240✔
1224
                    m_applier->bad_transaction_log("SetErase: Sets of lists are not supported.");
×
1225
                },
×
1226
                [&](const Instruction::Payload::Set&) {
240✔
1227
                    m_applier->bad_transaction_log("SetErase: Sets of sets are not supported.");
×
1228
                },
×
1229
                [&](const Instruction::Payload::Dictionary&) {
240✔
1230
                    m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported.");
×
1231
                },
×
1232
                [&](const Instruction::Payload::Erased&) {
240✔
1233
                    m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase");
×
1234
                },
×
1235
            };
480✔
1236

240✔
1237
            m_applier->visit_payload(m_instr.value, inserter);
480✔
1238
        }
480✔
1239

240✔
1240
    private:
480✔
1241
        const Instruction::SetErase& m_instr;
480✔
1242
    };
480✔
1243
    SetEraseResolver(this, instr).resolve();
480✔
1244
}
480✔
1245

1246
StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr,
1247
                                              const std::string_view& name)
1248
{
101,778✔
1249
    if (auto class_name = m_log->try_get_string(instr.table)) {
101,780✔
1250
        return Group::class_name_to_table_name(*class_name, m_table_name_buffer);
101,780✔
1251
    }
101,780✔
1252
    else {
2,147,483,647✔
1253
        bad_transaction_log("Corrupt table name in %1 instruction", name);
2,147,483,647✔
1254
    }
2,147,483,647✔
1255
}
101,778✔
1256

1257
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1258
{
450,812✔
1259
    if (instr.table == m_last_table_name) {
450,812✔
1260
        return m_last_table;
353,286✔
1261
    }
353,286✔
1262
    else {
97,526✔
1263
        auto table_name = get_table_name(instr, name);
97,526✔
1264
        TableRef table = m_transaction.get_table(table_name);
97,526✔
1265
        if (!table) {
97,526✔
1266
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
×
1267
        }
×
1268
        m_last_table = table;
97,526✔
1269
        m_last_table_name = instr.table;
97,526✔
1270
        m_last_object_key.reset();
97,526✔
1271
        m_last_object.reset();
97,526✔
1272
        m_last_field_name = InternString{};
97,526✔
1273
        m_last_field = ColKey{};
97,526✔
1274
        return table;
97,526✔
1275
    }
97,526✔
1276
}
450,812✔
1277

1278
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1279
                                                       const std::string_view& name)
1280
{
666,774✔
1281
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
666,774✔
1282
        *m_last_object_key == instr.object) {
634,640✔
1283
        // We have already found the object, reuse it.
128,286✔
1284
        return *m_last_object;
270,116✔
1285
    }
270,116✔
1286
    else {
396,658✔
1287
        TableRef table = get_table(instr, name);
396,658✔
1288
        ObjKey key = get_object_key(*table, instr.object, name);
396,658✔
1289
        if (!key) {
396,658✔
1290
            return util::none;
×
1291
        }
×
1292
        if (!table->is_valid(key)) {
396,658✔
1293
            // Check if the object is deleted or is a tombstone.
832✔
1294
            return util::none;
1,702✔
1295
        }
1,702✔
1296

198,650✔
1297
        Obj obj = table->get_object(key);
394,956✔
1298
        m_last_object_key = instr.object;
394,956✔
1299
        m_last_object = obj;
394,956✔
1300
        return obj;
394,956✔
1301
    }
394,956✔
1302
}
666,774✔
1303

1304
LstBasePtr InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1305
{
178,208✔
1306
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
89,198✔
1307
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
89,198✔
1308
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
89,198✔
1309
    // links.
89,198✔
1310
    REALM_ASSERT(col.is_list());
178,208✔
1311
    LstBasePtr list;
178,208✔
1312
    if (col.get_type() == col_type_Link) {
178,208✔
1313
        auto table = obj.get_table();
13,944✔
1314
        if (!table->get_link_target(col)->is_embedded()) {
13,944✔
1315
            list = obj.get_list_ptr<ObjKey>(col);
1,760✔
1316
        }
1,760✔
1317
        else {
12,184✔
1318
            list = obj.get_listbase_ptr(col);
12,184✔
1319
        }
12,184✔
1320
    }
13,944✔
1321
    else {
164,264✔
1322
        list = obj.get_listbase_ptr(col);
164,264✔
1323
    }
164,264✔
1324
    return list;
178,208✔
1325
}
178,208✔
1326

1327
InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
1328
                                               const std::string_view& instr_name)
1329
    : m_applier(applier)
1330
    , m_path_instr(instr)
1331
    , m_instr_name(instr_name)
1332
{
663,024✔
1333
}
663,024✔
1334

1335
InstructionApplier::PathResolver::~PathResolver()
1336
{
663,042✔
1337
    on_finish();
663,042✔
1338
}
663,042✔
1339

1340
void InstructionApplier::PathResolver::on_property(Obj&, ColKey)
1341
{
×
1342
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name));
×
1343
}
×
1344

1345
void InstructionApplier::PathResolver::on_list(LstBase&)
1346
{
×
1347
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name));
×
1348
}
×
1349

1350
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t)
1351
{
×
1352
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name));
×
1353
    return Status::DidNotResolve;
×
1354
}
×
1355

1356
void InstructionApplier::PathResolver::on_dictionary(Dictionary&)
1357
{
×
1358
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1359
}
×
1360

1361
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed)
1362
{
×
1363
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1364
    return Status::DidNotResolve;
×
1365
}
×
1366

1367
void InstructionApplier::PathResolver::on_set(SetBase&)
1368
{
×
1369
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name));
×
1370
}
×
1371

1372
void InstructionApplier::PathResolver::on_error(const std::string& err_msg)
1373
{
×
1374
    m_applier->bad_transaction_log(err_msg);
×
1375
}
×
1376

1377
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
1378
{
649,636✔
1379
    m_applier->m_last_field = col;
649,636✔
1380
}
649,636✔
1381

1382
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
2,896✔
1383

1384
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
1385
{
7,452✔
1386
    return Status::Pending;
7,452✔
1387
}
7,452✔
1388

1389
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData,
1390
                                                                                                StringData)
1391
{
×
1392
    return Status::Pending;
×
1393
}
×
1394

1395
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
1396
{
637,078✔
1397
    m_applier->m_current_path = m_path_instr.path;
637,078✔
1398
    m_applier->m_last_field_name = m_path_instr.field;
637,078✔
1399
    return Status::Pending;
637,078✔
1400
}
637,078✔
1401

1402
void InstructionApplier::PathResolver::on_finish()
1403
{
663,032✔
1404
    m_applier->m_current_path.reset();
663,032✔
1405
    m_applier->m_last_field_name = InternString{};
663,032✔
1406
    m_applier->m_last_field = ColKey{};
663,032✔
1407
}
663,032✔
1408

1409
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1410
{
689,336✔
1411
    return m_applier->get_string(interned);
689,336✔
1412
}
689,336✔
1413

1414
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve()
1415
{
663,036✔
1416
    util::Optional<Obj> obj = m_applier->get_top_object(m_path_instr, m_instr_name);
663,036✔
1417
    Status begin_status = on_begin(obj);
663,036✔
1418
    if (begin_status != Status::Pending) {
663,036✔
1419
        return begin_status;
220✔
1420
    }
220✔
1421
    if (!obj) {
662,816✔
1422
        m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name,
×
1423
                                       format_pk(m_applier->m_log->get_key(m_path_instr.object)),
×
1424
                                       get_string(m_path_instr.table));
×
1425
    }
×
1426

325,860✔
1427
    m_it_begin = m_path_instr.path.begin();
662,816✔
1428
    m_it_end = m_path_instr.path.end();
662,816✔
1429
    Status status = resolve_field(*obj, m_path_instr.field);
662,816✔
1430
    return status == Status::Pending ? Status::Success : status;
660,524✔
1431
}
662,816✔
1432

1433
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field)
1434
{
678,840✔
1435
    auto field_name = get_string(field);
678,840✔
1436
    ColKey col = obj.get_table()->get_column_key(field_name);
678,840✔
1437
    if (!col) {
678,840✔
1438
        on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name,
×
1439
                              obj.get_table()->get_name()));
×
1440
        return Status::DidNotResolve;
×
1441
    }
×
1442
    on_column_advance(col);
678,840✔
1443

333,872✔
1444
    if (m_it_begin == m_it_end) {
678,840✔
1445
        if (col.is_list()) {
487,232✔
1446
            auto list = obj.get_listbase_ptr(col);
132✔
1447
            on_list(*list);
132✔
1448
        }
132✔
1449
        else if (col.is_dictionary()) {
487,100✔
1450
            auto dict = obj.get_dictionary(col);
80✔
1451
            on_dictionary(dict);
80✔
1452
        }
80✔
1453
        else if (col.is_set()) {
487,020✔
1454
            SetBasePtr set;
3,644✔
1455
            if (col.get_type() == col_type_Link) {
3,644✔
1456
                // We are interested in using non-condensed indexes - as for Lists below
188✔
1457
                set = obj.get_set_ptr<ObjKey>(col);
376✔
1458
            }
376✔
1459
            else {
3,268✔
1460
                set = obj.get_setbase_ptr(col);
3,268✔
1461
            }
3,268✔
1462
            on_set(*set);
3,644✔
1463
        }
3,644✔
1464
        else {
483,376✔
1465
            on_property(obj, col);
483,376✔
1466
        }
483,376✔
1467
        return Status::Pending;
487,232✔
1468
    }
487,232✔
1469

95,896✔
1470
    if (col.is_list()) {
191,608✔
1471
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
176,392✔
1472
            auto list = InstructionApplier::get_list_from_path(obj, col);
176,392✔
1473
            ++m_it_begin;
176,392✔
1474
            return resolve_list_element(*list, *pindex);
176,392✔
1475
        }
176,392✔
UNCOV
1476
        on_error(util::format("%1: List index is not an integer on field '%2' in class '%3'", m_instr_name,
×
UNCOV
1477
                              field_name, obj.get_table()->get_name()));
×
UNCOV
1478
    }
×
1479
    else if (col.is_dictionary()) {
15,216✔
1480
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
8,768✔
1481
            auto dict = obj.get_dictionary(col);
8,768✔
1482
            ++m_it_begin;
8,768✔
1483
            return resolve_dictionary_element(dict, *pkey);
8,768✔
1484
        }
8,768✔
1485
        on_error(util::format("%1: Dictionary key is not a string on field '%2' in class '%3'", m_instr_name,
×
1486
                              field_name, obj.get_table()->get_name()));
×
1487
    }
×
1488
    else if (col.get_type() == col_type_Mixed) {
6,448✔
1489
        auto val = obj.get<Mixed>(col);
1,840✔
1490
        if (val.is_type(type_Dictionary)) {
1,840✔
1491
            if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
1,120✔
1492
                Dictionary dict(obj, col);
1,120✔
1493
                ++m_it_begin;
1,120✔
1494
                return resolve_dictionary_element(dict, *pkey);
1,120✔
1495
            }
1,120✔
1496
        }
720✔
1497
        if (val.is_type(type_List)) {
720✔
1498
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
720✔
1499
                Lst<Mixed> list(obj, col);
720✔
1500
                ++m_it_begin;
720✔
1501
                return resolve_list_element(list, *pindex);
720✔
1502
            }
720✔
1503
        }
×
1504
        on_error(util::format("%1: Not a list or dictionary on field '%2' in class '%3'", m_instr_name, field_name,
×
1505
                              obj.get_table()->get_name()));
×
1506
    }
×
1507
    else if (col.get_type() == col_type_Link) {
4,610✔
1508
        auto target = obj.get_table()->get_link_target(col);
4,608✔
1509
        if (!target->is_embedded()) {
4,608✔
1510
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
×
1511
                                  field_name, obj.get_table()->get_name()));
×
1512
        }
×
1513
        else if (obj.is_null(col)) {
4,608✔
1514
            Status null_status =
132✔
1515
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
132✔
1516
            if (null_status != Status::Pending) {
132✔
1517
                return null_status;
132✔
1518
            }
132✔
1519
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
×
1520
                                  m_instr_name, field_name, obj.get_table()->get_name()));
×
1521
        }
×
1522
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,476✔
1523
            auto embedded_object = obj.get_linked_object(col);
4,476✔
1524
            ++m_it_begin;
4,476✔
1525
            return resolve_field(embedded_object, *pfield);
4,476✔
1526
        }
4,476✔
1527
        else {
×
1528
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1529
        }
×
1530
    }
4,608✔
1531
    else {
2,147,483,649✔
1532
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
2,147,483,649✔
1533
                              field_name, obj.get_table()->get_name(), col.get_type()));
2,147,483,649✔
1534
    }
2,147,483,649✔
1535
    return Status::DidNotResolve;
2,147,483,649✔
1536
}
191,608✔
1537

1538
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
1539
                                                                                                uint32_t index)
1540
{
177,872✔
1541
    if (m_it_begin == m_it_end) {
177,872✔
1542
        return on_list_index(list, index);
167,104✔
1543
    }
167,104✔
1544

5,384✔
1545
    auto col = list.get_col_key();
10,768✔
1546
    auto field_name = list.get_table()->get_column_name(col);
10,768✔
1547

5,384✔
1548
    if (col.get_type() == col_type_Link) {
10,768✔
1549
        auto target = list.get_table()->get_link_target(col);
10,172✔
1550
        if (!target->is_embedded()) {
10,172✔
1551
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1552
                                  list.get_table()->get_name(), index));
×
1553
            return Status::DidNotResolve;
×
1554
        }
×
1555

5,086✔
1556
        Status list_status = on_list_index_advance(index);
10,172✔
1557
        if (list_status != Status::Pending) {
10,172✔
1558
            return list_status;
2,720✔
1559
        }
2,720✔
1560

3,726✔
1561
        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
7,452✔
1562
        auto& link_list = static_cast<LnkLst&>(list);
7,452✔
1563
        if (index >= link_list.size()) {
7,452✔
1564
            on_error(util::format("%1: Out-of-bounds index through list at '%3.%2[%4]'", m_instr_name, field_name,
×
1565
                                  list.get_table()->get_name(), index));
×
1566
        }
×
1567
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
7,452✔
1568
            auto embedded_object = link_list.get_object(index);
7,452✔
1569
            ++m_it_begin;
7,452✔
1570
            return resolve_field(embedded_object, *pfield);
7,452✔
1571
        }
7,452✔
1572
        on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1573
    }
×
1574
    else {
596✔
1575
        if (list.get_data_type() == type_Mixed) {
596✔
1576
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
596✔
1577
            if (index < mixed_list.size()) {
596✔
1578
                auto val = mixed_list.get(index);
596✔
1579

298✔
1580
                if (val.is_type(type_Dictionary)) {
596✔
1581
                    if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
416✔
1582
                        Dictionary d(mixed_list, mixed_list.get_key(index));
416✔
1583
                        ++m_it_begin;
416✔
1584
                        return resolve_dictionary_element(d, *pfield);
416✔
1585
                    }
416✔
1586
                }
180✔
1587
                if (val.is_type(type_List)) {
180✔
1588
                    if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
180✔
1589
                        Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
180✔
1590
                        ++m_it_begin;
180✔
1591
                        return resolve_list_element(l, *pindex);
180✔
1592
                    }
180✔
1593
                }
×
1594
            }
180✔
1595
        }
596✔
1596

1597
        on_error(util::format(
×
1598
            "%1: Resolving path through unstructured list element on '%3.%2', which is a list of type '%4'",
×
1599
            m_instr_name, field_name, list.get_table()->get_name(), col.get_type()));
×
1600
    }
×
1601
    return Status::DidNotResolve;
5,384✔
1602
}
10,768✔
1603

1604
InstructionApplier::PathResolver::Status
1605
InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key)
1606
{
10,492✔
1607
    StringData string_key = get_string(key);
10,492✔
1608
    if (m_it_begin == m_it_end) {
10,492✔
1609
        return on_dictionary_key(dict, Mixed{string_key});
5,560✔
1610
    }
5,560✔
1611

2,466✔
1612
    on_dict_key_advance(string_key);
4,932✔
1613

2,466✔
1614
    auto col = dict.get_col_key();
4,932✔
1615
    auto table = dict.get_table();
4,932✔
1616
    auto field_name = table->get_column_name(col);
4,932✔
1617

2,466✔
1618
    if (col.get_type() == col_type_Link) {
4,932✔
1619
        auto target = dict.get_target_table();
4,164✔
1620
        if (!target->is_embedded()) {
4,164✔
1621
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1622
                                  table->get_name(), string_key));
×
1623
            return Status::DidNotResolve;
×
1624
        }
×
1625

2,082✔
1626
        auto embedded_object = dict.get_object(string_key);
4,164✔
1627
        if (!embedded_object) {
4,164✔
1628
            Status null_link_status = on_null_link_advance(table->get_name(), string_key);
80✔
1629
            if (null_link_status != Status::Pending) {
80✔
1630
                return null_link_status;
80✔
1631
            }
80✔
1632
            on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name,
×
1633
                                  table->get_name(), string_key));
×
1634
        }
×
1635
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,084✔
1636
            ++m_it_begin;
4,084✔
1637
            return resolve_field(embedded_object, *pfield);
4,084✔
1638
        }
4,084✔
1639
        else {
×
1640
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1641
        }
×
1642
    }
4,164✔
1643
    else {
768✔
1644
        auto val = dict.get(string_key);
768✔
1645
        if (val.is_type(type_Dictionary)) {
768✔
1646
            if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
188✔
1647
                Dictionary d(dict, dict.build_index(string_key));
188✔
1648
                ++m_it_begin;
188✔
1649
                return resolve_dictionary_element(d, *pfield);
188✔
1650
            }
188✔
1651
        }
580✔
1652
        if (val.is_type(type_List)) {
580✔
1653
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
580✔
1654
                Lst<Mixed> l(dict, dict.build_index(string_key));
580✔
1655
                ++m_it_begin;
580✔
1656
                return resolve_list_element(l, *pindex);
580✔
1657
            }
580✔
1658
        }
×
1659
        on_error(
×
1660
            util::format("%1: Resolving path through non link element on '%3.%2', which is a dictionary of type '%4'",
×
1661
                         m_instr_name, field_name, table->get_name(), col.get_type()));
×
1662
    }
×
1663
    return Status::DidNotResolve;
2,466✔
1664
}
4,932✔
1665

1666

1667
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1668
                                          const std::string_view& name) const
1669
{
398,768✔
1670
    StringData table_name = table.get_name();
398,768✔
1671
    ColKey pk_col = table.get_primary_key_column();
398,768✔
1672
    StringData pk_name = "";
398,768✔
1673
    DataType pk_type;
398,768✔
1674
    if (pk_col) {
398,768✔
1675
        pk_name = table.get_column_name(pk_col);
398,756✔
1676
        pk_type = table.get_column_type(pk_col);
398,756✔
1677
    }
398,756✔
1678
    return mpark::visit(
398,768✔
1679
        util::overload{
398,768✔
1680
            [&](mpark::monostate) {
200,558✔
1681
                if (!pk_col) {
24✔
1682
                    bad_transaction_log(
×
1683
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
×
1684
                        name, table_name);
×
1685
                }
×
1686
                if (!table.is_nullable(pk_col)) {
24✔
1687
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
×
1688
                                        name, table_name, pk_name);
×
1689
                }
×
1690

12✔
1691
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
24✔
1692
                return key;
24✔
1693
            },
24✔
1694
            [&](int64_t pk) {
395,610✔
1695
                if (!pk_col) {
392,330✔
1696
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
×
1697
                                        "a primary key column",
×
1698
                                        name, pk, table_name);
×
1699
                }
×
1700
                if (pk_type != type_Int) {
392,330✔
1701
                    bad_transaction_log(
×
1702
                        "%1 instruction with integer 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);
392,330✔
1706
                return key;
392,330✔
1707
            },
392,330✔
1708
            [&](InternString interned_pk) {
201,000✔
1709
                auto pk = get_string(interned_pk);
1,044✔
1710
                if (!pk_col) {
1,044✔
1711
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1712
                                        "have a primary key column",
×
1713
                                        name, pk, table_name);
×
1714
                }
×
1715
                if (pk_type != type_String) {
1,044✔
1716
                    bad_transaction_log(
×
1717
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
×
1718
                        name, pk, table_name, pk_name, pk_type);
×
1719
                }
×
1720
                ObjKey key = table.get_objkey_from_primary_key(pk);
1,044✔
1721
                return key;
1,044✔
1722
            },
1,044✔
1723
            [&](GlobalKey id) {
200,548✔
1724
                if (pk_col) {
4✔
1725
                    bad_transaction_log(
×
1726
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
1727
                        name, table_name, pk_type);
×
1728
                }
×
1729
                ObjKey key = table.get_objkey_from_global_key(id);
4✔
1730
                return key;
4✔
1731
            },
4✔
1732
            [&](ObjectId pk) {
203,220✔
1733
                if (!pk_col) {
5,346✔
1734
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1735
                                        "have a primary key column",
×
1736
                                        name, pk, table_name);
×
1737
                }
×
1738
                if (pk_type != type_ObjectId) {
5,346✔
1739
                    bad_transaction_log(
×
1740
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1741
                        name, pk, table_name, pk_name, pk_type);
×
1742
                }
×
1743
                ObjKey key = table.get_objkey_from_primary_key(pk);
5,346✔
1744
                return key;
5,346✔
1745
            },
5,346✔
1746
            [&](UUID pk) {
200,556✔
1747
                if (!pk_col) {
20✔
1748
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
×
1749
                                        "have a primary key column",
×
1750
                                        name, pk, table_name);
×
1751
                }
×
1752
                if (pk_type != type_UUID) {
20✔
1753
                    bad_transaction_log(
×
1754
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
×
1755
                        pk, table_name, pk_name, pk_type);
×
1756
                }
×
1757
                ObjKey key = table.get_objkey_from_primary_key(pk);
20✔
1758
                return key;
20✔
1759
            }},
20✔
1760
        primary_key);
398,768✔
1761
}
398,768✔
1762

1763

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