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

realm / realm-core / 1771

20 Oct 2023 08:58AM UTC coverage: 91.567% (-0.009%) from 91.576%
1771

push

Evergreen

web-flow
Fix blocked DB::open on multiprocess access on exFAT filesystem (#6959)

Fix double file lock and DB::open being blocked with multiple concurrent realm access on fat32/exfat file systems.

When file is truncated on fat32/exfat its uid is available for other files. With multiple processes opening and truncating the same set of files could lead to the situation when within one process get_unique_id will return the same value for different files in timing is right. This breaks proper initialization of static data for interprocess mutexes, so that subsequent locks will hang by trying to lock essentially the same file twice.

94304 of 173552 branches covered (0.0%)

59 of 82 new or added lines in 5 files covered. (71.95%)

53 existing lines in 13 files now uncovered.

230544 of 251776 relevant lines covered (91.57%)

6594884.0 hits per line

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

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

5
#include <realm/transaction.hpp>
6

7
namespace realm::sync {
8
namespace {
9

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

15
} // namespace
16

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

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

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

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

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

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

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

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

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

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

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

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

6,194✔
137
                log("group.get_or_add_table_with_primary_key(group, \"%1\", %2, \"%3\", %4, %5);", table_name,
12,540✔
138
                    pk_type, pk_field, nullable, table_type);
12,540✔
139
                if (!m_transaction.get_or_add_table_with_primary_key(table_name, pk_type, pk_field, nullable,
12,540✔
140
                                                                     table_type)) {
6,194✔
141
                    bad_transaction_log("AddTable: The existing table '%1' has different properties", table_name);
×
142
                }
×
143
            }
12,540✔
144
        },
12,548✔
145
        [&](const Instruction::AddTable::EmbeddedTable&) {
6,418✔
146
            if (TableRef table = m_transaction.get_table(table_name)) {
220✔
147
                if (!table->is_embedded()) {
×
148
                    bad_transaction_log("AddTable: The existing table '%1' is not embedded", table_name);
×
149
                }
×
150
            }
×
151
            else {
220✔
152
                log("group.add_embedded_table(\"%1\");", table_name);
220✔
153
                m_transaction.add_table(table_name, Table::Type::Embedded);
220✔
154
            }
220✔
155
        },
220✔
156
    };
12,768✔
157

6,308✔
158
    mpark::visit(std::move(add_table), instr.type);
12,768✔
159
}
12,768✔
160

161
void InstructionApplier::operator()(const Instruction::EraseTable& instr)
162
{
3,962✔
163
    auto table_name = get_table_name(instr);
3,962✔
164
    // Temporarily swap out the last object key so it doesn't get included in error messages
2,002✔
165
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
3,962✔
166

2,002✔
167
    if (REALM_UNLIKELY(REALM_COVER_NEVER(!m_transaction.has_table(table_name)))) {
3,962✔
168
        // FIXME: Should EraseTable be considered idempotent?
169
        bad_transaction_log("table does not exist");
×
170
    }
×
171

2,002✔
172
    log("sync::erase_table(m_group, \"%1\")", table_name);
3,962✔
173
    m_transaction.remove_table(table_name);
3,962✔
174
}
3,962✔
175

176
void InstructionApplier::operator()(const Instruction::CreateObject& instr)
177
{
136,242✔
178
    auto table = get_table(instr);
136,242✔
179
    ColKey pk_col = table->get_primary_key_column();
136,242✔
180
    m_last_object_key = instr.object;
136,242✔
181

67,166✔
182
    mpark::visit(
136,242✔
183
        util::overload{
136,242✔
184
            [&](mpark::monostate) {
67,182✔
185
                if (!pk_col) {
32✔
186
                    bad_transaction_log("CreateObject(NULL) on table without a primary key");
×
187
                }
×
188
                if (!table->is_nullable(pk_col)) {
32✔
189
                    bad_transaction_log("CreateObject(NULL) on a table with a non-nullable primary key");
×
190
                }
×
191
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), realm::util::none);",
32✔
192
                    table->get_name());
32✔
193
                m_last_object = table->create_object_with_primary_key(util::none);
32✔
194
            },
32✔
195
            [&](int64_t pk) {
123,434✔
196
                if (!pk_col) {
110,364✔
197
                    bad_transaction_log("CreateObject(Int) on table without a primary key");
×
198
                }
×
199
                if (table->get_column_type(pk_col) != type_Int) {
110,364✔
200
                    bad_transaction_log("CreateObject(Int) on a table with primary key type %1",
×
201
                                        table->get_column_type(pk_col));
×
202
                }
×
203
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), pk);
110,364✔
204
                m_last_object = table->create_object_with_primary_key(pk);
110,364✔
205
            },
110,364✔
206
            [&](InternString pk) {
76,046✔
207
                if (!pk_col) {
18,792✔
208
                    bad_transaction_log("CreateObject(String) on table without a primary key");
×
209
                }
×
210
                if (table->get_column_type(pk_col) != type_String) {
18,792✔
211
                    bad_transaction_log("CreateObject(String) on a table with primary key type %1",
×
212
                                        table->get_column_type(pk_col));
×
213
                }
×
214
                StringData str = get_string(pk);
18,792✔
215
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), \"%2\");", table->get_name(),
18,792✔
216
                    str);
18,792✔
217
                m_last_object = table->create_object_with_primary_key(str);
18,792✔
218
            },
18,792✔
219
            [&](const ObjectId& id) {
71,046✔
220
                if (!pk_col) {
6,994✔
221
                    bad_transaction_log("CreateObject(ObjectId) on table without a primary key");
×
222
                }
×
223
                if (table->get_column_type(pk_col) != type_ObjectId) {
6,994✔
224
                    bad_transaction_log("CreateObject(ObjectId) on a table with primary key type %1",
×
225
                                        table->get_column_type(pk_col));
×
226
                }
×
227
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id);
6,994✔
228
                m_last_object = table->create_object_with_primary_key(id);
6,994✔
229
            },
6,994✔
230
            [&](const UUID& id) {
67,190✔
231
                if (!pk_col) {
48✔
232
                    bad_transaction_log("CreateObject(UUID) on table without a primary key");
×
233
                }
×
234
                if (table->get_column_type(pk_col) != type_UUID) {
48✔
235
                    bad_transaction_log("CreateObject(UUID) on a table with primary key type %1",
×
236
                                        table->get_column_type(pk_col));
×
237
                }
×
238
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id);
48✔
239
                m_last_object = table->create_object_with_primary_key(id);
48✔
240
            },
48✔
241
            [&](GlobalKey key) {
67,172✔
242
                if (pk_col) {
12✔
243
                    bad_transaction_log("CreateObject(GlobalKey) on table with a primary key");
×
244
                }
×
245
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), GlobalKey{%2, %3});",
12✔
246
                    table->get_name(), key.hi(), key.lo());
12✔
247
                m_last_object = table->create_object(key);
12✔
248
            },
12✔
249
        },
136,242✔
250
        instr.object);
136,242✔
251
}
136,242✔
252

253
void InstructionApplier::operator()(const Instruction::EraseObject& instr)
254
{
56,442✔
255
    // FIXME: Log actions.
27,360✔
256
    // Note: EraseObject is idempotent.
27,360✔
257
    if (auto obj = get_top_object(instr, "EraseObject")) {
56,442✔
258
        // This call will prevent incoming links to be nullified/deleted
20,702✔
259
        obj->invalidate();
42,206✔
260
    }
42,206✔
261
    m_last_object.reset();
56,442✔
262
}
56,442✔
263

264
template <class F>
265
void InstructionApplier::visit_payload(const Instruction::Payload& payload, F&& visitor)
266
{
644,840✔
267
    using Type = Instruction::Payload::Type;
644,840✔
268

316,878✔
269
    const auto& data = payload.data;
644,840✔
270
    switch (payload.type) {
644,840✔
271
        case Type::ObjectValue:
2,604✔
272
            return visitor(Instruction::Payload::ObjectValue{});
2,604✔
273
        case Type::Dictionary:
✔
274
            return bad_transaction_log("Nested dictionaries not supported yet");
×
275
        case Type::Erased:
252✔
276
            return visitor(Instruction::Payload::Erased{});
252✔
277
        case Type::GlobalKey:
✔
278
            return visitor(realm::util::none); // FIXME: Not sure about this
×
279
        case Type::Null:
1,022✔
280
            return visitor(realm::util::none);
1,022✔
281
        case Type::Int:
448,872✔
282
            return visitor(data.integer);
448,872✔
283
        case Type::Bool:
184✔
284
            return visitor(data.boolean);
184✔
285
        case Type::String: {
172,350✔
286
            StringData value = get_string(data.str);
172,350✔
287
            return visitor(value);
172,350✔
288
        }
×
289
        case Type::Binary: {
11,724✔
290
            BinaryData value = get_binary(data.binary);
11,724✔
291
            return visitor(value);
11,724✔
292
        }
×
293
        case Type::Timestamp:
3,082✔
294
            return visitor(data.timestamp);
3,082✔
295
        case Type::Float:
260✔
296
            return visitor(data.fnum);
260✔
297
        case Type::Double:
1,064✔
298
            return visitor(data.dnum);
1,064✔
299
        case Type::Decimal:
168✔
300
            return visitor(data.decimal);
168✔
301
        case Type::Link: {
1,848✔
302
            StringData class_name = get_string(data.link.target_table);
1,848✔
303
            Group::TableNameBuffer buffer;
1,848✔
304
            StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
1,848✔
305
            TableRef target_table = m_transaction.get_table(target_table_name);
1,848✔
306
            if (!target_table) {
1,848✔
307
                bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
308
            }
×
309
            if (target_table->is_embedded()) {
1,848✔
310
                bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
311
            }
×
312
            ObjKey target = get_object_key(*target_table, data.link.target);
1,848✔
313
            ObjLink link = ObjLink{target_table->get_key(), target};
1,848✔
314
            return visitor(link);
1,848✔
315
        }
×
316
        case Type::ObjectId:
1,120✔
317
            return visitor(data.object_id);
1,120✔
318
        case Type::UUID:
304✔
319
            return visitor(data.uuid);
304✔
320
    }
644,840✔
321
}
644,840✔
322

323
void InstructionApplier::operator()(const Instruction::Update& instr)
324
{
478,970✔
325
    struct UpdateResolver : public PathResolver {
478,970✔
326
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
478,970✔
327
            : PathResolver(applier, instr, "Update")
478,970✔
328
            , m_instr(instr)
478,970✔
329
        {
478,984✔
330
        }
478,974✔
331
        void on_property(Obj& obj, ColKey col) override
478,970✔
332
        {
475,282✔
333
            // Update of object field.
229,740✔
334

229,740✔
335
            auto table = obj.get_table();
471,746✔
336
            auto table_name = table->get_name();
471,746✔
337
            auto field_name = table->get_column_name(col);
471,746✔
338
            auto data_type = DataType(col.get_type());
471,746✔
339

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

229,740✔
389
            m_applier->visit_payload(m_instr.value, visitor);
471,746✔
390
        }
471,746✔
391
        Status on_list_index(LstBase& list, uint32_t index) override
478,970✔
392
        {
235,630✔
393
            // Update of list element.
2,722✔
394

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

2,722✔
401
            auto visitor = util::overload{
5,076✔
402
                [&](const ObjLink& link) {
2,800✔
403
                    if (data_type == type_TypedLink) {
156✔
404
                        REALM_ASSERT(dynamic_cast<Lst<ObjLink>*>(&list));
×
405
                        auto& link_list = static_cast<Lst<ObjLink>&>(list);
×
406
                        link_list.set(index, link);
×
407
                    }
×
408
                    else if (data_type == type_Mixed) {
156✔
409
                        REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
×
410
                        auto& mixed_list = static_cast<Lst<Mixed>&>(list);
×
411
                        mixed_list.set(index, link);
×
412
                    }
×
413
                    else if (data_type == type_LinkList || data_type == type_Link) {
156!
414
                        REALM_ASSERT(dynamic_cast<Lst<ObjKey>*>(&list));
156✔
415
                        auto& link_list = static_cast<Lst<ObjKey>&>(list);
156✔
416
                        // Validate the target.
78✔
417
                        auto target_table = table->get_link_target(col);
156✔
418
                        if (target_table->get_key() != link.get_table_key()) {
156✔
419
                            m_applier->bad_transaction_log(
×
420
                                "Update: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
421
                                m_applier->m_transaction.get_table(link.get_table_key())->get_name());
×
422
                        }
×
423
                        link_list.set(index, link.get_obj_key());
156✔
424
                    }
156✔
425
                    else {
×
426
                        m_applier->bad_transaction_log(
×
427
                            "Update: Type mismatch in list at '%2.%1' (expected link type, was %3)", field_name,
×
428
                            table_name, data_type);
×
429
                    }
×
430
                },
156✔
431
                [&](Mixed value) {
4,998✔
432
                    if (value.is_null()) {
4,920✔
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 {
4,920✔
442
                        if (data_type == type_Mixed || value.get_type() == data_type) {
4,920✔
443
                            list.set_any(index, value);
4,920✔
444
                        }
4,920✔
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
                    }
4,920✔
451
                },
4,920✔
452
                [&](const Instruction::Payload::ObjectValue&) {
2,722✔
453
                    // Embedded object creation is idempotent, and link lists cannot
454
                    // contain nulls, so this is a no-op.
455
                },
×
456
                [&](const Instruction::Payload::Erased&) {
2,722✔
457
                    m_applier->bad_transaction_log("Update: Dictionary erase of list element");
×
458
                },
×
459
            };
5,076✔
460

2,722✔
461
            m_applier->visit_payload(m_instr.value, visitor);
5,076✔
462
            return Status::Pending;
5,076✔
463
        }
5,076✔
464
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
478,970✔
465
        {
234,606✔
466
            // Update (insert) of dictionary element.
1,330✔
467

1,330✔
468
            auto visitor = util::overload{
2,660✔
469
                [&](Mixed value) {
2,354✔
470
                    if (value.is_null()) {
2,048✔
471
                        // FIXME: Separate handling of NULL is needed because
36✔
472
                        // `Mixed::get_type()` asserts on NULL.
36✔
473
                        dict.insert(key, value);
72✔
474
                    }
72✔
475
                    else if (value.get_type() == type_Link) {
1,976✔
476
                        m_applier->bad_transaction_log("Update: Untyped links are not supported in dictionaries.");
×
477
                    }
×
478
                    else {
1,976✔
479
                        dict.insert(key, value);
1,976✔
480
                    }
1,976✔
481
                },
2,048✔
482
                [&](const Instruction::Payload::Erased&) {
1,456✔
483
                    dict.erase(key);
252✔
484
                },
252✔
485
                [&](const Instruction::Payload::ObjectValue&) {
1,510✔
486
                    dict.create_and_insert_linked_object(key);
360✔
487
                },
360✔
488
            };
2,660✔
489

1,330✔
490
            m_applier->visit_payload(m_instr.value, visitor);
2,660✔
491
            return Status::Pending;
2,660✔
492
        }
2,660✔
493

233,276✔
494
    private:
478,970✔
495
        const Instruction::Update& m_instr;
478,970✔
496
    };
478,970✔
497
    UpdateResolver resolver(this, instr);
478,970✔
498
    resolver.resolve();
478,970✔
499
}
478,970✔
500

501
void InstructionApplier::operator()(const Instruction::AddInteger& instr)
502
{
3,492✔
503
    // FIXME: Implement increments of array elements, dictionary values.
1,778✔
504
    struct AddIntegerResolver : public PathResolver {
3,492✔
505
        AddIntegerResolver(InstructionApplier* applier, const Instruction::AddInteger& instr)
3,492✔
506
            : PathResolver(applier, instr, "AddInteger")
3,492✔
507
            , m_instr(instr)
3,492✔
508
        {
3,492✔
509
        }
3,492✔
510
        void on_property(Obj& obj, ColKey col)
3,492✔
511
        {
3,492✔
512
            // Increment of object field.
1,778✔
513
            if (!obj.is_null(col)) {
3,492✔
514
                try {
3,436✔
515
                    obj.add_int(col, m_instr.value);
3,436✔
516
                }
3,436✔
517
                catch (const LogicError&) {
1,750✔
518
                    auto table = obj.get_table();
×
519
                    m_applier->bad_transaction_log("AddInteger: Not an integer field '%2.%1'",
×
520
                                                   table->get_column_name(col), table->get_name());
×
521
                }
×
522
            }
3,436✔
523
        }
3,492✔
524

1,778✔
525
    private:
3,492✔
526
        const Instruction::AddInteger& m_instr;
3,492✔
527
    };
3,492✔
528
    AddIntegerResolver resolver(this, instr);
3,492✔
529
    resolver.resolve();
3,492✔
530
}
3,492✔
531

532
void InstructionApplier::operator()(const Instruction::AddColumn& instr)
533
{
22,174✔
534
    using Type = Instruction::Payload::Type;
22,174✔
535
    using CollectionType = Instruction::AddColumn::CollectionType;
22,174✔
536

10,720✔
537
    // Temporarily swap out the last object key so it doesn't get included in error messages
10,720✔
538
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
22,174✔
539

10,720✔
540
    auto table = get_table(instr, "AddColumn");
22,174✔
541
    auto col_name = get_string(instr.field);
22,174✔
542

10,720✔
543
    if (ColKey existing_key = table->get_column_key(col_name)) {
22,174✔
544
        DataType new_type = get_data_type(instr.type);
304✔
545
        ColumnType existing_type = existing_key.get_type();
304✔
546
        if (existing_type == col_type_LinkList) {
304✔
547
            existing_type = col_type_Link;
×
548
        }
×
549
        if (existing_type != ColumnType(new_type)) {
304✔
550
            bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (expected %3, got %4)",
4✔
551
                                table->get_name(), col_name, existing_type, new_type);
4✔
552
        }
4✔
553
        bool existing_is_list = existing_key.is_list();
304✔
554
        if ((instr.collection_type == CollectionType::List) != existing_is_list) {
304✔
555
            bad_transaction_log(
×
556
                "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a list, the other is%4)",
×
557
                table->get_name(), col_name, existing_is_list ? "" : " not", existing_is_list ? " not" : "");
×
558
        }
×
559
        bool existing_is_set = existing_key.is_set();
304✔
560
        if ((instr.collection_type == CollectionType::Set) != existing_is_set) {
304✔
561
            bad_transaction_log(
×
562
                "AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a set, the other is%4)",
×
563
                table->get_name(), col_name, existing_is_set ? "" : " not", existing_is_set ? " not" : "");
×
564
        }
×
565
        bool existing_is_dict = existing_key.is_dictionary();
304✔
566
        if ((instr.collection_type == CollectionType::Dictionary) != existing_is_dict) {
304✔
567
            bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (existing is%3 a "
×
568
                                "dictionary, the other is%4)",
×
569
                                table->get_name(), col_name, existing_is_dict ? "" : " not",
×
570
                                existing_is_dict ? " not" : "");
×
571
        }
×
572
        if (new_type == type_Link) {
304✔
573
            Group::TableNameBuffer buffer;
16✔
574
            auto target_table_name = Group::class_name_to_table_name(get_string(instr.link_target_table), buffer);
16✔
575
            if (target_table_name != table->get_link_target(existing_key)->get_name()) {
16✔
576
                bad_transaction_log("AddColumn: Schema mismatch for existing column in '%1.%2' (link targets differ)",
×
577
                                    table->get_name(), col_name);
×
578
            }
×
579
        }
16✔
580
        return;
304✔
581
    }
304✔
582

10,568✔
583
    if (instr.collection_type == CollectionType::Dictionary && instr.key_type != Type::String) {
21,870✔
584
        bad_transaction_log("AddColumn '%1.%3' adding dictionary column with non-string keys", table->get_name(),
×
585
                            col_name);
×
586
    }
×
587

10,568✔
588
    if (instr.type != Type::Link) {
21,870✔
589
        DataType type = get_data_type(instr.type);
20,734✔
590
        switch (instr.collection_type) {
20,734✔
591
            case CollectionType::Single: {
14,682✔
592
                table->add_column(type, col_name, instr.nullable);
14,682✔
593
                break;
14,682✔
594
            }
×
595
            case CollectionType::List: {
5,820✔
596
                table->add_column_list(type, col_name, instr.nullable);
5,820✔
597
                break;
5,820✔
598
            }
×
599
            case CollectionType::Dictionary: {
104✔
600
                DataType key_type = get_data_type(instr.key_type);
104✔
601
                table->add_column_dictionary(type, col_name, instr.nullable, key_type);
104✔
602
                break;
104✔
603
            }
×
604
            case CollectionType::Set: {
128✔
605
                table->add_column_set(type, col_name, instr.nullable);
128✔
606
                break;
128✔
607
            }
1,136✔
608
        }
1,136✔
609
    }
1,136✔
610
    else {
1,136✔
611
        Group::TableNameBuffer buffer;
1,136✔
612
        auto target_table_name = get_string(instr.link_target_table);
1,136✔
613
        if (target_table_name.size() != 0) {
1,136✔
614
            TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer));
1,128✔
615
            if (!target) {
1,128✔
616
                bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(),
×
617
                                    col_name, target_table_name);
×
618
            }
×
619
            if (instr.collection_type == CollectionType::List) {
1,128✔
620
                table->add_column_list(*target, col_name);
580✔
621
            }
580✔
622
            else if (instr.collection_type == CollectionType::Set) {
548✔
623
                table->add_column_set(*target, col_name);
8✔
624
            }
8✔
625
            else if (instr.collection_type == CollectionType::Dictionary) {
540✔
626
                table->add_column_dictionary(*target, col_name);
32✔
627
            }
32✔
628
            else {
508✔
629
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
508✔
630
                table->add_column(*target, col_name);
508✔
631
            }
508✔
632
        }
1,128✔
633
        else {
8✔
634
            if (instr.collection_type == CollectionType::List) {
8✔
635
                table->add_column_list(type_TypedLink, col_name);
×
636
            }
×
637
            else {
8✔
638
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
8✔
639
                table->add_column(type_TypedLink, col_name);
8✔
640
            }
8✔
641
        }
8✔
642
    }
1,136✔
643
}
21,870✔
644

645
void InstructionApplier::operator()(const Instruction::EraseColumn& instr)
646
{
×
647
    // Temporarily swap out the last object key so it doesn't get included in error messages
648
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
×
649

650
    auto table = get_table(instr, "EraseColumn");
×
651
    auto col_name = get_string(instr.field);
×
652

653
    ColKey col = table->get_column_key(col_name);
×
654
    if (!col) {
×
655
        bad_transaction_log("EraseColumn '%1.%2' which doesn't exist", table->get_name(), col_name);
×
656
    }
×
657

658
    table->remove_column(col);
×
659
}
×
660

661
void InstructionApplier::operator()(const Instruction::ArrayInsert& instr)
662
{
163,508✔
663
    struct ArrayInsertResolver : public PathResolver {
163,508✔
664
        ArrayInsertResolver(InstructionApplier* applier, const Instruction::ArrayInsert& instr)
163,508✔
665
            : PathResolver(applier, instr, "ArrayInsert")
163,508✔
666
            , m_instr(instr)
163,508✔
667
        {
163,508✔
668
        }
163,508✔
669
        Status on_list_index(LstBase& list, uint32_t index) override
163,508✔
670
        {
163,508✔
671
            auto col = list.get_col_key();
163,508✔
672
            auto data_type = DataType(col.get_type());
163,508✔
673
            auto table = list.get_table();
163,508✔
674
            auto table_name = table->get_name();
163,508✔
675
            auto field_name = table->get_column_name(col);
163,508✔
676

82,310✔
677
            if (index > m_instr.prior_size) {
163,508✔
678
                m_applier->bad_transaction_log("ArrayInsert: Invalid insertion index (index = %1, prior_size = %2)",
×
679
                                               index, m_instr.prior_size);
×
680
            }
×
681

82,310✔
682
            if (index > list.size()) {
163,508✔
683
                m_applier->bad_transaction_log("ArrayInsert: Index out of bounds (%1 > %2)", index, list.size());
×
684
            }
×
685

82,310✔
686
            if (m_instr.prior_size != list.size()) {
163,508✔
687
                m_applier->bad_transaction_log("ArrayInsert: Invalid prior_size (list size = %1, prior_size = %2)",
×
688
                                               list.size(), m_instr.prior_size);
×
689
            }
×
690

82,310✔
691
            auto inserter = util::overload{
163,508✔
692
                [&](const ObjLink& link) {
82,890✔
693
                    if (data_type == type_TypedLink) {
1,160✔
694
                        REALM_ASSERT(dynamic_cast<Lst<ObjLink>*>(&list));
×
695
                        auto& link_list = static_cast<Lst<ObjLink>&>(list);
×
696
                        link_list.insert(index, link);
×
697
                    }
×
698
                    else if (data_type == type_Mixed) {
1,160✔
699
                        REALM_ASSERT(dynamic_cast<Lst<Mixed>*>(&list));
132✔
700
                        auto& mixed_list = static_cast<Lst<Mixed>&>(list);
132✔
701
                        mixed_list.insert(index, link);
132✔
702
                    }
132✔
703
                    else if (data_type == type_LinkList || data_type == type_Link) {
1,028!
704
                        REALM_ASSERT(dynamic_cast<Lst<ObjKey>*>(&list));
1,028✔
705
                        auto& link_list = static_cast<Lst<ObjKey>&>(list);
1,028✔
706
                        // Validate the target.
514✔
707
                        auto target_table = table->get_link_target(col);
1,028✔
708
                        if (target_table->get_key() != link.get_table_key()) {
1,028✔
709
                            m_applier->bad_transaction_log(
×
710
                                "ArrayInsert: Target table mismatch (expected '%1', got '%2')",
×
711
                                target_table->get_name(),
×
712
                                m_applier->m_transaction.get_table(link.get_table_key())->get_name());
×
713
                        }
×
714
                        link_list.insert(index, link.get_obj_key());
1,028✔
715
                    }
1,028✔
716
                    else {
×
717
                        m_applier->bad_transaction_log(
×
718
                            "ArrayInsert: Type mismatch in list at '%2.%1' (expected link type, was %3)", field_name,
×
719
                            table_name, data_type);
×
720
                    }
×
721
                },
1,160✔
722
                [&](Mixed value) {
162,098✔
723
                    if (value.is_null()) {
160,688✔
724
                        if (col.is_nullable()) {
52✔
725
                            list.insert_null(index);
52✔
726
                        }
52✔
727
                        else {
×
728
                            m_applier->bad_transaction_log("ArrayInsert: NULL in non-nullable list '%2.%1'",
×
729
                                                           field_name, table_name);
×
730
                        }
×
731
                    }
52✔
732
                    else {
160,636✔
733
                        if (data_type == type_Mixed || value.get_type() == data_type) {
160,636✔
734
                            list.insert_any(index, value);
160,636✔
735
                        }
160,636✔
UNCOV
736
                        else {
×
UNCOV
737
                            m_applier->bad_transaction_log(
×
UNCOV
738
                                "ArrayInsert: Type mismatch in list at '%2.%1' (expected %3, got %4)", field_name,
×
UNCOV
739
                                table_name, data_type, value.get_type());
×
UNCOV
740
                        }
×
741
                    }
160,636✔
742
                },
160,688✔
743
                [&](const Instruction::Payload::ObjectValue&) {
83,140✔
744
                    if (col.get_type() == col_type_LinkList || col.get_type() == col_type_Link) {
1,660!
745
                        auto target_table = list.get_table()->get_link_target(col);
1,660✔
746
                        if (!target_table->is_embedded()) {
1,660✔
747
                            m_applier->bad_transaction_log(
×
748
                                "ArrayInsert: Creation of embedded object of type '%1', which is not "
×
749
                                "an embedded table",
×
750
                                target_table->get_name());
×
751
                        }
×
752

830✔
753
                        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
1,660✔
754
                        auto& link_list = static_cast<LnkLst&>(list);
1,660✔
755
                        link_list.create_and_insert_linked_object(index);
1,660✔
756
                    }
1,660✔
757
                    else {
×
758
                        m_applier->bad_transaction_log(
×
759
                            "ArrayInsert: Creation of embedded object in non-link list field '%2.%1'", field_name,
×
760
                            table_name);
×
761
                    }
×
762
                },
1,660✔
763
                [&](const Instruction::Payload::Dictionary&) {
163,508✔
764
                    m_applier->bad_transaction_log("Dictionary payload for ArrayInsert");
163,508✔
765
                },
163,508✔
766
                [&](const Instruction::Payload::Erased&) {
82,310✔
767
                    m_applier->bad_transaction_log("Dictionary erase payload for ArrayInsert");
×
768
                },
×
769
            };
163,508✔
770

82,310✔
771
            m_applier->visit_payload(m_instr.value, inserter);
163,508✔
772
            return Status::Pending;
163,508✔
773
        }
163,508✔
774

82,310✔
775
    private:
163,508✔
776
        const Instruction::ArrayInsert& m_instr;
163,508✔
777
    };
163,508✔
778
    ArrayInsertResolver(this, instr).resolve();
163,508✔
779
}
163,508✔
780

781
void InstructionApplier::operator()(const Instruction::ArrayMove& instr)
782
{
112✔
783
    struct ArrayMoveResolver : public PathResolver {
112✔
784
        ArrayMoveResolver(InstructionApplier* applier, const Instruction::ArrayMove& instr)
112✔
785
            : PathResolver(applier, instr, "ArrayMove")
112✔
786
            , m_instr(instr)
112✔
787
        {
112✔
788
        }
112✔
789
        Status on_list_index(LstBase& list, uint32_t index) override
112✔
790
        {
112✔
791
            if (index >= list.size()) {
112✔
792
                m_applier->bad_transaction_log("ArrayMove from out of bounds (%1 >= %2)", m_instr.index(),
×
793
                                               list.size());
×
794
            }
×
795
            if (m_instr.ndx_2 >= list.size()) {
112✔
796
                m_applier->bad_transaction_log("ArrayMove to out of bounds (%1 >= %2)", m_instr.ndx_2, list.size());
×
797
            }
×
798
            if (index == m_instr.ndx_2) {
112✔
799
                // FIXME: Does this really need to be an error?
800
                m_applier->bad_transaction_log("ArrayMove to same location (%1)", m_instr.index());
×
801
            }
×
802
            if (m_instr.prior_size != list.size()) {
112✔
803
                m_applier->bad_transaction_log("ArrayMove: Invalid prior_size (list size = %1, prior_size = %2)",
×
804
                                               list.size(), m_instr.prior_size);
×
805
            }
×
806
            list.move(index, m_instr.ndx_2);
112✔
807
            return Status::Pending;
112✔
808
        }
112✔
809

56✔
810
    private:
112✔
811
        const Instruction::ArrayMove& m_instr;
112✔
812
    };
112✔
813
    ArrayMoveResolver(this, instr).resolve();
112✔
814
}
112✔
815

816
void InstructionApplier::operator()(const Instruction::ArrayErase& instr)
817
{
4,676✔
818
    struct ArrayEraseResolver : public PathResolver {
4,676✔
819
        ArrayEraseResolver(InstructionApplier* applier, const Instruction::ArrayErase& instr)
4,676✔
820
            : PathResolver(applier, instr, "ArrayErase")
4,676✔
821
            , m_instr(instr)
4,676✔
822
        {
4,676✔
823
        }
4,676✔
824
        Status on_list_index(LstBase& list, uint32_t index) override
4,676✔
825
        {
4,676✔
826
            if (index >= m_instr.prior_size) {
4,676✔
827
                m_applier->bad_transaction_log("ArrayErase: Invalid index (index = %1, prior_size = %2)", index,
×
828
                                               m_instr.prior_size);
×
829
            }
×
830
            if (index >= list.size()) {
4,676✔
831
                m_applier->bad_transaction_log("ArrayErase: Index out of bounds (%1 >= %2)", index, list.size());
×
832
            }
×
833
            if (m_instr.prior_size != list.size()) {
4,676✔
834
                m_applier->bad_transaction_log("ArrayErase: Invalid prior_size (list size = %1, prior_size = %2)",
×
835
                                               list.size(), m_instr.prior_size);
×
836
            }
×
837
            list.remove(index, index + 1);
4,676✔
838
            return Status::Pending;
4,676✔
839
        }
4,676✔
840

2,372✔
841
    private:
4,676✔
842
        const Instruction::ArrayErase& m_instr;
4,676✔
843
    };
4,676✔
844
    ArrayEraseResolver(this, instr).resolve();
4,676✔
845
}
4,676✔
846

847
void InstructionApplier::operator()(const Instruction::Clear& instr)
848
{
240✔
849
    struct ClearResolver : public PathResolver {
240✔
850
        ClearResolver(InstructionApplier* applier, const Instruction::Clear& instr)
240✔
851
            : PathResolver(applier, instr, "Clear")
240✔
852
        {
240✔
853
        }
240✔
854
        void on_list(LstBase& list) override
240✔
855
        {
208✔
856
            list.clear();
176✔
857
        }
176✔
858
        void on_dictionary(Dictionary& dict) override
240✔
859
        {
122✔
860
            dict.clear();
×
861
        }
×
862
        void on_set(SetBase& set) override
240✔
863
        {
154✔
864
            set.clear();
64✔
865
        }
64✔
866
    };
240✔
867
    ClearResolver(this, instr).resolve();
240✔
868
}
240✔
869

870
bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr,
871
                                           const std::string_view& instr_name)
872
{
16✔
873
    struct AllowsNullsResolver : public PathResolver {
16✔
874
        AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
16✔
875
                            const std::string_view& instr_name)
16✔
876
            : PathResolver(applier, instr, instr_name)
16✔
877
            , m_allows_nulls(false)
16✔
878
        {
16✔
879
        }
16✔
880
        Status on_list_index(LstBase&, uint32_t) override
16✔
881
        {
8✔
882
            return Status::Pending;
×
883
        }
×
884
        void on_list(LstBase&) override {}
8✔
885
        void on_set(SetBase&) override {}
8✔
886
        void on_dictionary(Dictionary&) override
16✔
887
        {
8✔
888
            m_allows_nulls = true;
×
889
        }
×
890
        Status on_dictionary_key(Dictionary&, Mixed) override
16✔
891
        {
16✔
892
            m_allows_nulls = true;
16✔
893
            return Status::Pending;
16✔
894
        }
16✔
895
        void on_property(Obj&, ColKey) override
16✔
896
        {
8✔
897
            m_allows_nulls = true;
×
898
        }
×
899
        bool allows_nulls()
16✔
900
        {
16✔
901
            resolve();
16✔
902
            return m_allows_nulls;
16✔
903
        }
16✔
904

8✔
905
    private:
16✔
906
        bool m_allows_nulls;
16✔
907
    };
16✔
908
    return AllowsNullsResolver(this, instr, instr_name).allows_nulls();
16✔
909
}
16✔
910

911
std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const
912
{
×
913
    REALM_ASSERT(m_log);
×
914
    std::stringstream ss;
×
915
    m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
916
    return ss.str();
×
917
}
×
918

919
bool InstructionApplier::check_links_exist(const Instruction::Payload& payload)
920
{
21,896✔
921
    bool valid_payload = true;
21,896✔
922
    using Type = Instruction::Payload::Type;
21,896✔
923
    if (payload.type == Type::Link) {
21,896✔
924
        StringData class_name = get_string(payload.data.link.target_table);
500✔
925
        Group::TableNameBuffer buffer;
500✔
926
        StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
500✔
927
        TableRef target_table = m_transaction.get_table(target_table_name);
500✔
928
        if (!target_table) {
500✔
929
            bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
930
        }
×
931
        if (target_table->is_embedded()) {
500✔
932
            bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
933
        }
×
934
        Mixed linked_pk =
500✔
935
            mpark::visit(util::overload{[&](mpark::monostate) {
262✔
936
                                            return Mixed{}; // the link exists and the pk is null
24✔
937
                                        },
24✔
938
                                        [&](int64_t pk) {
486✔
939
                                            return Mixed{pk};
472✔
940
                                        },
472✔
941
                                        [&](InternString interned_pk) {
250✔
942
                                            return Mixed{get_string(interned_pk)};
×
943
                                        },
×
944
                                        [&](GlobalKey) {
250✔
945
                                            bad_transaction_log(
×
946
                                                "Unexpected link to embedded object while validating a primary key");
×
947
                                            return Mixed{}; // appease the compiler; visitors must have a single
×
948
                                                            // return type
949
                                        },
×
950
                                        [&](ObjectId pk) {
252✔
951
                                            return Mixed{pk};
4✔
952
                                        },
4✔
953
                                        [&](UUID pk) {
250✔
954
                                            return Mixed{pk};
×
955
                                        }},
×
956
                         payload.data.link.target);
500✔
957

250✔
958
        if (!target_table->find_primary_key(linked_pk)) {
500✔
959
            valid_payload = false;
48✔
960
        }
48✔
961
    }
500✔
962
    return valid_payload;
21,896✔
963
}
21,896✔
964

965
void InstructionApplier::operator()(const Instruction::SetInsert& instr)
966
{
1,696✔
967
    struct SetInsertResolver : public PathResolver {
1,696✔
968
        SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr)
1,696✔
969
            : PathResolver(applier, instr, "SetInsert")
1,696✔
970
            , m_instr(instr)
1,696✔
971
        {
1,696✔
972
        }
1,696✔
973
        void on_set(SetBase& set) override
1,696✔
974
        {
1,696✔
975
            auto col = set.get_col_key();
1,696✔
976
            auto data_type = DataType(col.get_type());
1,696✔
977
            auto table = set.get_table();
1,696✔
978
            auto table_name = table->get_name();
1,696✔
979
            auto field_name = table->get_column_name(col);
1,696✔
980

848✔
981
            auto inserter = util::overload{
1,696✔
982
                [&](const ObjLink& link) {
920✔
983
                    if (data_type == type_TypedLink) {
144✔
984
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
985
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
986
                        link_set.insert(link);
×
987
                    }
×
988
                    else if (data_type == type_Mixed) {
144✔
989
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
64✔
990
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
64✔
991
                        mixed_set.insert(link);
64✔
992
                    }
64✔
993
                    else if (data_type == type_Link) {
80✔
994
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
80✔
995
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
80✔
996
                        // Validate the target.
40✔
997
                        auto target_table = table->get_link_target(col);
80✔
998
                        if (target_table->get_key() != link.get_table_key()) {
80✔
999
                            m_applier->bad_transaction_log(
×
1000
                                "SetInsert: Target table mismatch (expected '%1', got '%2')",
×
1001
                                target_table->get_name(), table_name);
×
1002
                        }
×
1003
                        link_set.insert(link.get_obj_key());
80✔
1004
                    }
80✔
1005
                    else {
×
1006
                        m_applier->bad_transaction_log(
×
1007
                            "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1008
                            table_name, data_type);
×
1009
                    }
×
1010
                },
144✔
1011
                [&](Mixed value) {
1,624✔
1012
                    if (value.is_null() && !col.is_nullable()) {
1,552✔
1013
                        m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name,
×
1014
                                                       table_name);
×
1015
                    }
×
1016

776✔
1017
                    if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) {
1,552✔
1018
                        set.insert_any(value);
1,552✔
1019
                    }
1,552✔
1020
                    else {
×
1021
                        m_applier->bad_transaction_log(
×
1022
                            "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name,
×
1023
                            table_name, data_type, value.get_type());
×
1024
                    }
×
1025
                },
1,552✔
1026
                [&](const Instruction::Payload::ObjectValue&) {
848✔
1027
                    m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported.");
×
1028
                },
×
1029
                [&](const Instruction::Payload::Dictionary&) {
1,696✔
1030
                    m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported.");
1,696✔
1031
                },
1,696✔
1032
                [&](const Instruction::Payload::Erased&) {
848✔
1033
                    m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert");
×
1034
                },
×
1035
            };
1,696✔
1036

848✔
1037
            m_applier->visit_payload(m_instr.value, inserter);
1,696✔
1038
        }
1,696✔
1039

848✔
1040
    private:
1,696✔
1041
        const Instruction::SetInsert& m_instr;
1,696✔
1042
    };
1,696✔
1043
    SetInsertResolver(this, instr).resolve();
1,696✔
1044
}
1,696✔
1045

1046
void InstructionApplier::operator()(const Instruction::SetErase& instr)
1047
{
456✔
1048
    struct SetEraseResolver : public PathResolver {
456✔
1049
        SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr)
456✔
1050
            : PathResolver(applier, instr, "SetErase")
456✔
1051
            , m_instr(instr)
456✔
1052
        {
456✔
1053
        }
456✔
1054
        void on_set(SetBase& set) override
456✔
1055
        {
456✔
1056
            auto col = set.get_col_key();
456✔
1057
            auto data_type = DataType(col.get_type());
456✔
1058
            auto table = set.get_table();
456✔
1059
            auto table_name = table->get_name();
456✔
1060
            auto field_name = table->get_column_name(col);
456✔
1061

228✔
1062
            auto inserter = util::overload{
456✔
1063
                [&](const ObjLink& link) {
300✔
1064
                    if (data_type == type_TypedLink) {
144✔
1065
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1066
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1067
                        link_set.erase(link);
×
1068
                    }
×
1069
                    else if (data_type == type_Mixed) {
144✔
1070
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
68✔
1071
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
68✔
1072
                        mixed_set.erase(link);
68✔
1073
                    }
68✔
1074
                    else if (data_type == type_Link) {
76✔
1075
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
76✔
1076
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
76✔
1077
                        // Validate the target.
38✔
1078
                        auto target_table = table->get_link_target(col);
76✔
1079
                        if (target_table->get_key() != link.get_table_key()) {
76✔
1080
                            m_applier->bad_transaction_log(
×
1081
                                "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
1082
                                table_name);
×
1083
                        }
×
1084
                        link_set.erase(link.get_obj_key());
76✔
1085
                    }
76✔
1086
                    else {
×
1087
                        m_applier->bad_transaction_log(
×
1088
                            "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1089
                            table_name, data_type);
×
1090
                    }
×
1091
                },
144✔
1092
                [&](Mixed value) {
384✔
1093
                    if (value.is_null() && !col.is_nullable()) {
312!
1094
                        m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name,
×
1095
                                                       table_name);
×
1096
                    }
×
1097

156✔
1098
                    if (data_type == type_Mixed || value.get_type() == data_type) {
312✔
1099
                        set.erase_any(value);
312✔
1100
                    }
312✔
1101
                    else {
×
1102
                        m_applier->bad_transaction_log(
×
1103
                            "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name,
×
1104
                            data_type, value.get_type());
×
1105
                    }
×
1106
                },
312✔
1107
                [&](const Instruction::Payload::ObjectValue&) {
228✔
1108
                    m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported.");
×
1109
                },
×
1110
                [&](const Instruction::Payload::Dictionary&) {
456✔
1111
                    m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported.");
456✔
1112
                },
456✔
1113
                [&](const Instruction::Payload::Erased&) {
228✔
1114
                    m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase");
×
1115
                },
×
1116
            };
456✔
1117

228✔
1118
            m_applier->visit_payload(m_instr.value, inserter);
456✔
1119
        }
456✔
1120

228✔
1121
    private:
456✔
1122
        const Instruction::SetErase& m_instr;
456✔
1123
    };
456✔
1124
    SetEraseResolver(this, instr).resolve();
456✔
1125
}
456✔
1126

1127
StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr,
1128
                                              const std::string_view& name)
1129
{
313,270✔
1130
    if (auto class_name = m_log->try_get_string(instr.table)) {
313,274✔
1131
        return Group::class_name_to_table_name(*class_name, m_table_name_buffer);
313,266✔
1132
    }
313,266✔
1133
    else {
2,147,483,655✔
1134
        bad_transaction_log("Corrupt table name in %1 instruction", name);
2,147,483,655✔
1135
    }
2,147,483,655✔
1136
}
313,270✔
1137

1138
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1139
{
648,416✔
1140
    if (instr.table == m_last_table_name) {
648,416✔
1141
        return m_last_table;
351,896✔
1142
    }
351,896✔
1143
    else {
296,520✔
1144
        auto table_name = get_table_name(instr, name);
296,520✔
1145
        TableRef table = m_transaction.get_table(table_name);
296,520✔
1146
        if (!table) {
296,520✔
1147
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
×
1148
        }
×
1149
        m_last_table = table;
296,520✔
1150
        m_last_table_name = instr.table;
296,520✔
1151
        m_last_object_key.reset();
296,520✔
1152
        m_last_object.reset();
296,520✔
1153
        m_last_field_name = InternString{};
296,520✔
1154
        m_last_field = ColKey{};
296,520✔
1155
        return table;
296,520✔
1156
    }
296,520✔
1157
}
648,416✔
1158

1159
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1160
                                                       const std::string_view& name)
1161
{
733,454✔
1162
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
733,454✔
1163
        *m_last_object_key == instr.object) {
653,978✔
1164
        // We have already found the object, reuse it.
115,288✔
1165
        return *m_last_object;
243,970✔
1166
    }
243,970✔
1167
    else {
489,484✔
1168
        TableRef table = get_table(instr, name);
489,484✔
1169
        ObjKey key = get_object_key(*table, instr.object, name);
489,484✔
1170
        if (!key) {
489,484✔
1171
            return util::none;
×
1172
        }
×
1173
        if (!table->is_valid(key)) {
489,484✔
1174
            // Check if the object is deleted or is a tombstone.
6,786✔
1175
            return util::none;
14,492✔
1176
        }
14,492✔
1177

238,088✔
1178
        Obj obj = table->get_object(key);
474,992✔
1179
        m_last_object_key = instr.object;
474,992✔
1180
        m_last_object = obj;
474,992✔
1181
        return obj;
474,992✔
1182
    }
474,992✔
1183
}
733,454✔
1184

1185
std::unique_ptr<LstBase> InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1186
{
188,136✔
1187
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
94,842✔
1188
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
94,842✔
1189
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
94,842✔
1190
    // links.
94,842✔
1191
    REALM_ASSERT(col.is_list());
188,136✔
1192
    std::unique_ptr<LstBase> list;
188,136✔
1193
    if (col.get_type() == col_type_Link || col.get_type() == col_type_LinkList) {
188,136✔
1194
        auto table = obj.get_table();
13,748✔
1195
        if (!table->get_link_target(col)->is_embedded()) {
13,748✔
1196
            list = obj.get_list_ptr<ObjKey>(col);
1,564✔
1197
        }
1,564✔
1198
        else {
12,184✔
1199
            list = obj.get_listbase_ptr(col);
12,184✔
1200
        }
12,184✔
1201
    }
13,748✔
1202
    else {
174,388✔
1203
        list = obj.get_listbase_ptr(col);
174,388✔
1204
    }
174,388✔
1205
    return list;
188,136✔
1206
}
188,136✔
1207

1208
InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
1209
                                               const std::string_view& instr_name)
1210
    : m_applier(applier)
1211
    , m_path_instr(instr)
1212
    , m_instr_name(instr_name)
1213
{
676,966✔
1214
}
676,966✔
1215

1216
InstructionApplier::PathResolver::~PathResolver()
1217
{
676,878✔
1218
    on_finish();
676,878✔
1219
}
676,878✔
1220

1221
void InstructionApplier::PathResolver::on_property(Obj&, ColKey)
1222
{
×
1223
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name));
×
1224
}
×
1225

1226
void InstructionApplier::PathResolver::on_list(LstBase&)
1227
{
×
1228
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name));
×
1229
}
×
1230

1231
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t)
1232
{
×
1233
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name));
×
1234
    return Status::DidNotResolve;
×
1235
}
×
1236

1237
void InstructionApplier::PathResolver::on_dictionary(Dictionary&)
1238
{
×
1239
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1240
}
×
1241

1242
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed)
1243
{
×
1244
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1245
    return Status::DidNotResolve;
×
1246
}
×
1247

1248
void InstructionApplier::PathResolver::on_set(SetBase&)
1249
{
×
1250
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name));
×
1251
}
×
1252

1253
void InstructionApplier::PathResolver::on_error(const std::string& err_msg)
1254
{
×
1255
    m_applier->bad_transaction_log(err_msg);
×
1256
}
×
1257

1258
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
1259
{
665,600✔
1260
    m_applier->m_last_field = col;
665,600✔
1261
}
665,600✔
1262

1263
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
2,092✔
1264

1265
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
1266
{
7,452✔
1267
    return Status::Pending;
7,452✔
1268
}
7,452✔
1269

1270
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData,
1271
                                                                                                StringData)
1272
{
×
1273
    return Status::Pending;
×
1274
}
×
1275

1276
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
1277
{
653,246✔
1278
    m_applier->m_current_path = m_path_instr.path;
653,246✔
1279
    m_applier->m_last_field_name = m_path_instr.field;
653,246✔
1280
    return Status::Pending;
653,246✔
1281
}
653,246✔
1282

1283
void InstructionApplier::PathResolver::on_finish()
1284
{
676,892✔
1285
    m_applier->m_current_path.reset();
676,892✔
1286
    m_applier->m_last_field_name = InternString{};
676,892✔
1287
    m_applier->m_last_field = ColKey{};
676,892✔
1288
}
676,892✔
1289

1290
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1291
{
701,304✔
1292
    return m_applier->get_string(interned);
701,304✔
1293
}
701,304✔
1294

1295
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve()
1296
{
676,954✔
1297
    util::Optional<Obj> obj = m_applier->get_top_object(m_path_instr, m_instr_name);
676,954✔
1298
    Status begin_status = on_begin(obj);
676,954✔
1299
    if (begin_status != Status::Pending) {
676,954✔
1300
        return begin_status;
232✔
1301
    }
232✔
1302
    if (!obj) {
676,722✔
1303
        m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name,
×
1304
                                       format_pk(m_applier->m_log->get_key(m_path_instr.object)),
×
1305
                                       get_string(m_path_instr.table));
×
1306
    }
×
1307

332,688✔
1308
    m_it_begin = m_path_instr.path.begin();
676,722✔
1309
    m_it_end = m_path_instr.path.end();
676,722✔
1310
    Status status = resolve_field(*obj, m_path_instr.field);
676,722✔
1311
    return status == Status::Pending ? Status::Success : status;
674,450✔
1312
}
676,722✔
1313

1314
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field)
1315
{
692,684✔
1316
    auto field_name = get_string(field);
692,684✔
1317
    ColKey col = obj.get_table()->get_column_key(field_name);
692,684✔
1318
    if (!col) {
692,684✔
1319
        on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name,
×
1320
                              obj.get_table()->get_name()));
×
1321
        return Status::DidNotResolve;
×
1322
    }
×
1323
    on_column_advance(col);
692,684✔
1324

340,812✔
1325
    if (m_it_begin == m_it_end) {
692,684✔
1326
        if (col.is_list()) {
493,266✔
1327
            auto list = obj.get_listbase_ptr(col);
212✔
1328
            on_list(*list);
212✔
1329
        }
212✔
1330
        else if (col.is_dictionary()) {
493,054✔
1331
            auto dict = obj.get_dictionary(col);
×
1332
            on_dictionary(dict);
×
1333
        }
×
1334
        else if (col.is_set()) {
493,054✔
1335
            SetBasePtr set;
3,484✔
1336
            if (col.get_type() == col_type_Link) {
3,484✔
1337
                // We are interested in using non-condensed indexes - as for Lists below
152✔
1338
                set = obj.get_set_ptr<ObjKey>(col);
304✔
1339
            }
304✔
1340
            else {
3,180✔
1341
                set = obj.get_setbase_ptr(col);
3,180✔
1342
            }
3,180✔
1343
            on_set(*set);
3,484✔
1344
        }
3,484✔
1345
        else {
489,570✔
1346
            on_property(obj, col);
489,570✔
1347
        }
489,570✔
1348
        return Status::Pending;
493,266✔
1349
    }
493,266✔
1350

100,462✔
1351
    if (col.is_list()) {
199,418✔
1352
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
186,424✔
1353
            std::unique_ptr<LstBase> list = InstructionApplier::get_list_from_path(obj, col);
186,424✔
1354
            ++m_it_begin;
186,424✔
1355
            return resolve_list_element(*list, *pindex);
186,424✔
1356
        }
186,424✔
1357
        on_error(util::format("%1: List index is not an integer on field '%2' in class '%3'", m_instr_name,
×
1358
                              field_name, obj.get_table()->get_name()));
×
1359
    }
×
1360
    else if (col.is_dictionary()) {
12,994✔
1361
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
8,624✔
1362
            auto dict = obj.get_dictionary(col);
8,624✔
1363
            ++m_it_begin;
8,624✔
1364
            return resolve_dictionary_element(dict, *pkey);
8,624✔
1365
        }
8,624✔
1366
        on_error(util::format("%1: Dictionary key is not a string on field '%2' in class '%3'", m_instr_name,
×
1367
                              field_name, obj.get_table()->get_name()));
×
1368
    }
×
1369
    else if (col.get_type() == col_type_Link) {
4,394✔
1370
        auto target = obj.get_table()->get_link_target(col);
4,376✔
1371
        if (!target->is_embedded()) {
4,376✔
1372
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
×
1373
                                  field_name, obj.get_table()->get_name()));
×
1374
        }
×
1375
        else if (obj.is_null(col)) {
4,376✔
1376
            Status null_status =
132✔
1377
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
132✔
1378
            if (null_status != Status::Pending) {
132✔
1379
                return null_status;
132✔
1380
            }
132✔
1381
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
×
1382
                                  m_instr_name, field_name, obj.get_table()->get_name()));
×
1383
        }
×
1384
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,244✔
1385
            auto embedded_object = obj.get_linked_object(col);
4,244✔
1386
            ++m_it_begin;
4,244✔
1387
            return resolve_field(embedded_object, *pfield);
4,244✔
1388
        }
4,244✔
1389
        else {
×
1390
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1391
        }
×
1392
    }
4,376✔
1393
    else {
2,147,483,665✔
1394
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
2,147,483,665✔
1395
                              field_name, obj.get_table()->get_name(), col.get_type()));
2,147,483,665✔
1396
    }
2,147,483,665✔
1397
    return Status::DidNotResolve;
2,147,483,665✔
1398
}
199,418✔
1399

1400
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
1401
                                                                                                uint32_t index)
1402
{
186,424✔
1403
    if (m_it_begin == m_it_end) {
186,424✔
1404
        return on_list_index(list, index);
176,252✔
1405
    }
176,252✔
1406

5,086✔
1407
    auto col = list.get_col_key();
10,172✔
1408
    auto field_name = list.get_table()->get_column_name(col);
10,172✔
1409

5,086✔
1410
    if (col.get_type() == col_type_LinkList) {
10,172✔
1411
        auto target = list.get_table()->get_link_target(col);
10,172✔
1412
        if (!target->is_embedded()) {
10,172✔
1413
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1414
                                  list.get_table()->get_name(), index));
×
1415
            return Status::DidNotResolve;
×
1416
        }
×
1417

5,086✔
1418
        Status list_status = on_list_index_advance(index);
10,172✔
1419
        if (list_status != Status::Pending) {
10,172✔
1420
            return list_status;
2,720✔
1421
        }
2,720✔
1422

3,726✔
1423
        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
7,452✔
1424
        auto& link_list = static_cast<LnkLst&>(list);
7,452✔
1425
        if (index >= link_list.size()) {
7,452✔
1426
            on_error(util::format("%1: Out-of-bounds index through list at '%3.%2[%4]'", m_instr_name, field_name,
×
1427
                                  list.get_table()->get_name(), index));
×
1428
        }
×
1429
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
7,452✔
1430
            auto embedded_object = link_list.get_object(index);
7,452✔
1431
            ++m_it_begin;
7,452✔
1432
            return resolve_field(embedded_object, *pfield);
7,452✔
1433
        }
7,452✔
1434
        on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1435
    }
×
1436
    else {
×
1437
        on_error(util::format(
×
1438
            "%1: Resolving path through unstructured list element on '%3.%2', which is a list of type '%4'",
×
1439
            m_instr_name, field_name, list.get_table()->get_name(), col.get_type()));
×
1440
    }
×
1441
    return Status::DidNotResolve;
5,086✔
1442
}
10,172✔
1443

1444
InstructionApplier::PathResolver::Status
1445
InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key)
1446
{
8,624✔
1447
    StringData string_key = get_string(key);
8,624✔
1448
    if (m_it_begin == m_it_end) {
8,624✔
1449
        return on_dictionary_key(dict, Mixed{string_key});
4,548✔
1450
    }
4,548✔
1451

2,038✔
1452
    on_dict_key_advance(string_key);
4,076✔
1453

2,038✔
1454
    auto col = dict.get_col_key();
4,076✔
1455
    auto table = dict.get_table();
4,076✔
1456
    auto field_name = table->get_column_name(col);
4,076✔
1457

2,038✔
1458
    if (col.get_type() == col_type_Link) {
4,076✔
1459
        auto target = dict.get_target_table();
4,076✔
1460
        if (!target->is_embedded()) {
4,076✔
1461
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1462
                                  table->get_name(), string_key));
×
1463
            return Status::DidNotResolve;
×
1464
        }
×
1465

2,038✔
1466
        auto embedded_object = dict.get_object(string_key);
4,076✔
1467
        if (!embedded_object) {
4,076✔
1468
            Status null_link_status = on_null_link_advance(table->get_name(), string_key);
80✔
1469
            if (null_link_status != Status::Pending) {
80✔
1470
                return null_link_status;
80✔
1471
            }
80✔
1472
            on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name,
×
1473
                                  table->get_name(), string_key));
×
1474
        }
×
1475
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
3,996✔
1476
            ++m_it_begin;
3,996✔
1477
            return resolve_field(embedded_object, *pfield);
3,996✔
1478
        }
3,996✔
1479
        else {
×
1480
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1481
        }
×
1482
    }
4,076✔
1483
    else {
×
1484
        on_error(
×
1485
            util::format("%1: Resolving path through non link element on '%3.%2', which is a dictionary of type '%4'",
×
1486
                         m_instr_name, field_name, table->get_name(), col.get_type()));
×
1487
    }
×
1488
    return Status::DidNotResolve;
2,038✔
1489
}
4,076✔
1490

1491

1492
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1493
                                          const std::string_view& name) const
1494
{
491,694✔
1495
    StringData table_name = table.get_name();
491,694✔
1496
    ColKey pk_col = table.get_primary_key_column();
491,694✔
1497
    StringData pk_name = "";
491,694✔
1498
    DataType pk_type;
491,694✔
1499
    if (pk_col) {
491,804✔
1500
        pk_name = table.get_column_name(pk_col);
491,804✔
1501
        pk_type = table.get_column_type(pk_col);
491,804✔
1502
    }
491,804✔
1503
    return mpark::visit(
491,694✔
1504
        util::overload{
491,694✔
1505
            [&](mpark::monostate) {
246,166✔
1506
                if (!pk_col) {
24✔
1507
                    bad_transaction_log(
×
1508
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
×
1509
                        name, table_name);
×
1510
                }
×
1511
                if (!table.is_nullable(pk_col)) {
24✔
1512
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
×
1513
                                        name, table_name, pk_name);
×
1514
                }
×
1515

12✔
1516
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
24✔
1517
                return key;
24✔
1518
            },
24✔
1519
            [&](int64_t pk) {
480,970✔
1520
                if (!pk_col) {
467,548✔
1521
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
×
1522
                                        "a primary key column",
×
1523
                                        name, pk, table_name);
×
1524
                }
×
1525
                if (pk_type != type_Int) {
467,548✔
1526
                    bad_transaction_log(
×
1527
                        "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1528
                        name, pk, table_name, pk_name, pk_type);
×
1529
                }
×
1530
                ObjKey key = table.get_objkey_from_primary_key(pk);
467,548✔
1531
                return key;
467,548✔
1532
            },
467,548✔
1533
            [&](InternString interned_pk) {
254,420✔
1534
                auto pk = get_string(interned_pk);
19,248✔
1535
                if (!pk_col) {
19,248✔
1536
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1537
                                        "have a primary key column",
×
1538
                                        name, pk, table_name);
×
1539
                }
×
1540
                if (pk_type != type_String) {
19,248✔
1541
                    bad_transaction_log(
×
1542
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
×
1543
                        name, pk, table_name, pk_name, pk_type);
×
1544
                }
×
1545
                ObjKey key = table.get_objkey_from_primary_key(pk);
19,248✔
1546
                return key;
19,248✔
1547
            },
19,248✔
1548
            [&](GlobalKey id) {
246,156✔
1549
                if (pk_col) {
4✔
1550
                    bad_transaction_log(
×
1551
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
1552
                        name, table_name, pk_type);
×
1553
                }
×
1554
                ObjKey key = table.get_objkey_from_global_key(id);
4✔
1555
                return key;
4✔
1556
            },
4✔
1557
            [&](ObjectId pk) {
248,574✔
1558
                if (!pk_col) {
4,860✔
1559
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1560
                                        "have a primary key column",
×
1561
                                        name, pk, table_name);
×
1562
                }
×
1563
                if (pk_type != type_ObjectId) {
4,860✔
1564
                    bad_transaction_log(
×
1565
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1566
                        name, pk, table_name, pk_name, pk_type);
×
1567
                }
×
1568
                ObjKey key = table.get_objkey_from_primary_key(pk);
4,860✔
1569
                return key;
4,860✔
1570
            },
4,860✔
1571
            [&](UUID pk) {
246,164✔
1572
                if (!pk_col) {
20✔
1573
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
×
1574
                                        "have a primary key column",
×
1575
                                        name, pk, table_name);
×
1576
                }
×
1577
                if (pk_type != type_UUID) {
20✔
1578
                    bad_transaction_log(
×
1579
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
×
1580
                        pk, table_name, pk_name, pk_type);
×
1581
                }
×
1582
                ObjKey key = table.get_objkey_from_primary_key(pk);
20✔
1583
                return key;
20✔
1584
            }},
20✔
1585
        primary_key);
491,694✔
1586
}
491,694✔
1587

1588

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