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

realm / realm-core / michael.wilkersonbarker_1147

04 Jun 2024 01:18PM UTC coverage: 90.835% (-0.008%) from 90.843%
michael.wilkersonbarker_1147

Pull #7542

Evergreen

michael-wb
Removed unused SyncClientHookEvent entries
Pull Request #7542: RCORE-2063 Fix some client resets potentially failing with AutoClientResetFailed if a new client reset condition occurred before the first one completed

101700 of 180094 branches covered (56.47%)

61 of 66 new or added lines in 5 files covered. (92.42%)

84 existing lines in 12 files now uncovered.

214628 of 236283 relevant lines covered (90.84%)

5386848.77 hits per line

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

68.51
/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
constexpr static std::string_view
11
    s_dict_key_wrong_type_err("%1: Dictionary key is not a string on field '%2' in class '%3'");
12
constexpr static std::string_view
13
    s_list_index_wrong_type_err("%1: List index is not an integer on field '%2' in class '%3'");
14
constexpr static std::string_view
15
    s_wrong_collection_type_err("%1: Not a list or dictionary on field '%2' in class '%3'");
16

17
REALM_NORETURN void throw_bad_transaction_log(std::string msg)
18
{
8✔
19
    throw BadChangesetError{std::move(msg)};
8✔
20
}
8✔
21

22
} // namespace
23

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

62
template <class... Params>
63
REALM_NORETURN void InstructionApplier::bad_transaction_log(const char* msg, Params&&... params) const
64
{
8✔
65
    // FIXME: Avoid throwing in normal program flow (since changesets can come
66
    // in over the network, defective changesets are part of normal program
67
    // flow).
68
    bad_transaction_log(util::format(msg, std::forward<Params>(params)...));
8✔
69
}
8✔
70

71
StringData InstructionApplier::get_string(InternString str) const
72
{
713,668✔
73
    auto string = m_log->try_get_intern_string(str);
713,668✔
74
    if (REALM_UNLIKELY(!string))
713,668✔
75
        bad_transaction_log("string read fails");
×
76
    return m_log->get_string(*string);
713,668✔
77
}
713,668✔
78

79
StringData InstructionApplier::get_string(StringBufferRange range) const
80
{
175,870✔
81
    auto string = m_log->try_get_string(range);
175,870✔
82
    if (!string)
175,870✔
83
        bad_transaction_log("string read error");
×
84
    return *string;
175,870✔
85
}
175,870✔
86

87
BinaryData InstructionApplier::get_binary(StringBufferRange range) const
88
{
11,724✔
89
    auto string = m_log->try_get_string(range);
11,724✔
90
    if (!string)
11,724✔
91
        bad_transaction_log("binary read error");
×
92
    return BinaryData{string->data(), string->size()};
11,724✔
93
}
11,724✔
94

95
TableRef InstructionApplier::table_for_class_name(StringData class_name) const
96
{
112✔
97
    if (class_name.size() > Group::max_class_name_length)
112✔
98
        bad_transaction_log("class name too long");
×
99
    Group::TableNameBuffer buffer;
112✔
100
    return m_transaction.get_table(Group::class_name_to_table_name(class_name, buffer));
112✔
101
}
112✔
102

103
template <typename T>
104
struct TemporarySwapOut {
105
    explicit TemporarySwapOut(T& target)
106
        : target(target)
6,282✔
107
        , backup()
6,282✔
108
    {
13,630✔
109
        std::swap(target, backup);
13,630✔
110
    }
13,630✔
111

112
    ~TemporarySwapOut()
113
    {
13,630✔
114
        std::swap(backup, target);
13,630✔
115
    }
13,630✔
116

117
    T& target;
118
    T backup;
119
};
120

121
void InstructionApplier::operator()(const Instruction::AddTable& instr)
122
{
4,524✔
123
    auto table_name = get_table_name(instr);
4,524✔
124

125
    // Temporarily swap out the last object key so it doesn't get included in error messages
126
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
4,524✔
127

128
    auto add_table = util::overload{
4,524✔
129
        [&](const Instruction::AddTable::TopLevelTable& spec) {
4,524✔
130
            auto table_type = (spec.is_asymmetric ? Table::Type::TopLevelAsymmetric : Table::Type::TopLevel);
4,228✔
131
            if (spec.pk_type == Instruction::Payload::Type::GlobalKey) {
4,228✔
132
                m_transaction.get_or_add_table(table_name, table_type);
8✔
133
            }
8✔
134
            else {
4,220✔
135
                if (!is_valid_key_type(spec.pk_type)) {
4,220✔
136
                    bad_transaction_log("Invalid primary key type '%1' while adding table '%2'", int8_t(spec.pk_type),
×
137
                                        table_name);
×
138
                }
×
139
                DataType pk_type = get_data_type(spec.pk_type);
4,220✔
140
                StringData pk_field = get_string(spec.pk_field);
4,220✔
141
                bool nullable = spec.pk_nullable;
4,220✔
142

143
                if (!m_transaction.get_or_add_table_with_primary_key(table_name, pk_type, pk_field, nullable,
4,220✔
144
                                                                     table_type)) {
4,220✔
145
                    bad_transaction_log("AddTable: The existing table '%1' has different properties", table_name);
×
146
                }
×
147
            }
4,220✔
148
        },
4,228✔
149
        [&](const Instruction::AddTable::EmbeddedTable&) {
4,524✔
150
            if (TableRef table = m_transaction.get_table(table_name)) {
296✔
151
                if (!table->is_embedded()) {
×
152
                    bad_transaction_log("AddTable: The existing table '%1' is not embedded", table_name);
×
153
                }
×
154
            }
×
155
            else {
296✔
156
                m_transaction.add_table(table_name, Table::Type::Embedded);
296✔
157
            }
296✔
158
        },
296✔
159
    };
4,524✔
160

161
    mpark::visit(std::move(add_table), instr.type);
4,524✔
162
}
4,524✔
163

164
void InstructionApplier::operator()(const Instruction::EraseTable& instr)
165
{
230✔
166
    auto table_name = get_table_name(instr);
230✔
167
    // Temporarily swap out the last object key so it doesn't get included in error messages
168
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
230✔
169

170
    if (REALM_UNLIKELY(REALM_COVER_NEVER(!m_transaction.has_table(table_name)))) {
230✔
171
        // FIXME: Should EraseTable be considered idempotent?
172
        bad_transaction_log("table does not exist");
×
173
    }
×
174

175
    m_transaction.remove_table(table_name);
230✔
176
}
230✔
177

178
void InstructionApplier::operator()(const Instruction::CreateObject& instr)
179
{
45,676✔
180
    auto table = get_table(instr);
45,676✔
181
    ColKey pk_col = table->get_primary_key_column();
45,676✔
182
    m_last_object_key = instr.object;
45,676✔
183

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

245
void InstructionApplier::operator()(const Instruction::EraseObject& instr)
246
{
3,380✔
247
    // FIXME: Log actions.
248
    // Note: EraseObject is idempotent.
249
    if (auto obj = get_top_object(instr, "EraseObject")) {
3,380✔
250
        // This call will prevent incoming links to be nullified/deleted
251
        obj->invalidate();
2,010✔
252
    }
2,010✔
253
    m_last_object.reset();
3,380✔
254
}
3,380✔
255

256
template <class F>
257
void InstructionApplier::visit_payload(const Instruction::Payload& payload, F&& visitor)
258
{
636,390✔
259
    using Type = Instruction::Payload::Type;
636,390✔
260

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

319
void InstructionApplier::operator()(const Instruction::Update& instr)
320
{
470,744✔
321
    struct UpdateResolver : public PathResolver {
470,744✔
322
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
470,744✔
323
            : PathResolver(applier, instr, "Update")
470,744✔
324
            , m_instr(instr)
470,744✔
325
        {
470,744✔
326
        }
470,738✔
327
        Status on_property(Obj& obj, ColKey col) override
470,744✔
328
        {
470,744✔
329
            // Update of object field.
330

331
            auto table = obj.get_table();
466,500✔
332
            auto table_name = table->get_name();
466,500✔
333
            auto field_name = table->get_column_name(col);
466,500✔
334
            auto data_type = DataType(col.get_type());
466,500✔
335

336
            auto visitor = [&](const mpark::variant<ObjLink, Mixed, Instruction::Payload::ObjectValue,
466,500✔
337
                                                    Instruction::Payload::Dictionary, Instruction::Payload::List,
466,500✔
338
                                                    Instruction::Payload::Set, Instruction::Payload::Erased>& arg) {
466,512✔
339
                if (const auto link_ptr = mpark::get_if<ObjLink>(&arg)) {
466,512✔
340
                    if (data_type == type_Mixed || data_type == type_TypedLink) {
84✔
341
                        obj.set_any(col, *link_ptr, m_instr.is_default);
×
342
                    }
×
343
                    else if (data_type == type_Link) {
84✔
344
                        // Validate target table.
345
                        auto target_table = table->get_link_target(col);
84✔
346
                        if (target_table->get_key() != link_ptr->get_table_key()) {
84✔
347
                            m_applier->bad_transaction_log(
×
348
                                "Update: Target table mismatch (expected %1, got %2)", target_table->get_name(),
×
349
                                m_applier->m_transaction.get_table(link_ptr->get_table_key())->get_name());
×
350
                        }
×
351
                        obj.set<ObjKey>(col, link_ptr->get_obj_key(), m_instr.is_default);
84✔
352
                    }
84✔
353
                    else {
×
354
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
×
355
                                                       field_name, table_name, col.get_type(), type_Link);
×
356
                    }
×
357
                }
84✔
358
                else if (const auto mixed_ptr = mpark::get_if<Mixed>(&arg)) {
466,428✔
359
                    if (mixed_ptr->is_null()) {
464,948✔
360
                        if (col.is_nullable()) {
210✔
361
                            obj.set_null(col, m_instr.is_default);
210✔
362
                        }
210✔
363
                        else {
×
364
                            m_applier->bad_transaction_log("Update: NULL in non-nullable field '%2.%1'", field_name,
×
365
                                                           table_name);
×
366
                        }
×
367
                    }
210✔
368
                    else if (data_type == type_Mixed || mixed_ptr->get_type() == data_type) {
464,740✔
369
                        obj.set_any(col, *mixed_ptr, m_instr.is_default);
464,736✔
370
                    }
464,736✔
371
                    else {
2,147,483,651✔
372
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
2,147,483,651✔
373
                                                       field_name, table_name, col.get_type(), mixed_ptr->get_type());
2,147,483,651✔
374
                    }
2,147,483,651✔
375
                }
464,948✔
376
                else if (const auto obj_val_ptr = mpark::get_if<Instruction::Payload::ObjectValue>(&arg)) {
1,480✔
377
                    if (obj.is_null(col)) {
652✔
378
                        obj.create_and_set_linked_object(col);
624✔
379
                    }
624✔
380
                }
652✔
381
                else if (const auto erase_ptr = mpark::get_if<Instruction::Payload::Erased>(&arg)) {
828✔
382
                    m_applier->bad_transaction_log("Update: Dictionary erase at object field");
×
383
                }
×
384
                else if (mpark::get_if<Instruction::Payload::Dictionary>(&arg)) {
828✔
385
                    obj.set_collection(col, CollectionType::Dictionary);
360✔
386
                }
360✔
387
                else if (mpark::get_if<Instruction::Payload::List>(&arg)) {
468✔
388
                    obj.set_collection(col, CollectionType::List);
448✔
389
                }
448✔
390
                else if (mpark::get_if<Instruction::Payload::Set>(&arg)) {
20✔
391
                    obj.set_collection(col, CollectionType::Set);
×
392
                }
×
393
            };
466,512✔
394

395
            m_applier->visit_payload(m_instr.value, visitor);
466,500✔
396
            return Status::Pending;
466,500✔
397
        }
466,500✔
398
        Status on_list_index(LstBase& list, uint32_t index) override
470,744✔
399
        {
470,744✔
400
            // Update of list element.
401

402
            auto col = list.get_col_key();
438✔
403
            auto data_type = DataType(col.get_type());
438✔
404
            auto table = list.get_table();
438✔
405
            auto table_name = table->get_name();
438✔
406
            auto field_name = table->get_column_name(col);
438✔
407

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

477
            m_applier->visit_payload(m_instr.value, visitor);
438✔
478
            return Status::Pending;
438✔
479
        }
438✔
480
        Status on_dictionary_key(Dictionary& dict, Mixed key) override
470,744✔
481
        {
470,744✔
482
            // Update (insert) of dictionary element.
483

484
            auto visitor = util::overload{
3,828✔
485
                [&](Mixed value) {
3,828✔
486
                    if (value.is_null()) {
2,788✔
487
                        // FIXME: Separate handling of NULL is needed because
488
                        // `Mixed::get_type()` asserts on NULL.
489
                        dict.insert(key, value);
112✔
490
                    }
112✔
491
                    else if (value.get_type() == type_Link) {
2,676✔
492
                        m_applier->bad_transaction_log("Update: Untyped links are not supported in dictionaries.");
×
493
                    }
×
494
                    else {
2,676✔
495
                        dict.insert(key, value);
2,676✔
496
                    }
2,676✔
497
                },
2,788✔
498
                [&](const Instruction::Payload::Erased&) {
3,828✔
499
                    dict.try_erase(key);
240✔
500
                },
240✔
501
                [&](const Instruction::Payload::ObjectValue&) {
3,828✔
502
                    dict.create_and_insert_linked_object(key);
364✔
503
                },
364✔
504
                [&](const Instruction::Payload::Dictionary&) {
3,828✔
505
                    dict.insert_collection(key.get_string(), CollectionType::Dictionary);
180✔
506
                },
180✔
507
                [&](const Instruction::Payload::List&) {
3,828✔
508
                    dict.insert_collection(key.get_string(), CollectionType::List);
256✔
509
                },
256✔
510
                [&](const Instruction::Payload::Set&) {
3,828✔
511
                    dict.insert_collection(key.get_string(), CollectionType::Set);
×
512
                },
×
513
            };
3,828✔
514

515
            m_applier->visit_payload(m_instr.value, visitor);
3,828✔
516
            return Status::Pending;
3,828✔
517
        }
3,828✔
518

519
    private:
470,744✔
520
        const Instruction::Update& m_instr;
470,744✔
521
    };
470,744✔
522
    UpdateResolver resolver(this, instr);
470,744✔
523
    resolver.resolve();
470,744✔
524
}
470,744✔
525

526
void InstructionApplier::operator()(const Instruction::AddInteger& instr)
527
{
2,666✔
528
    // FIXME: Implement increments of array elements, dictionary values.
529
    struct AddIntegerResolver : public PathResolver {
2,666✔
530
        AddIntegerResolver(InstructionApplier* applier, const Instruction::AddInteger& instr)
2,666✔
531
            : PathResolver(applier, instr, "AddInteger")
2,666✔
532
            , m_instr(instr)
2,666✔
533
        {
2,666✔
534
        }
2,666✔
535
        Status on_property(Obj& obj, ColKey col)
2,666✔
536
        {
2,666✔
537
            // Increment of object field.
538
            if (!obj.is_null(col)) {
2,666✔
539
                try {
2,610✔
540
                    obj.add_int(col, m_instr.value);
2,610✔
541
                }
2,610✔
542
                catch (const LogicError&) {
2,610✔
543
                    auto table = obj.get_table();
×
544
                    m_applier->bad_transaction_log("AddInteger: Not an integer field '%2.%1'",
×
545
                                                   table->get_column_name(col), table->get_name());
×
546
                }
×
547
            }
2,610✔
548
            return Status::Pending;
2,666✔
549
        }
2,666✔
550

551
    private:
2,666✔
552
        const Instruction::AddInteger& m_instr;
2,666✔
553
    };
2,666✔
554
    AddIntegerResolver resolver(this, instr);
2,666✔
555
    resolver.resolve();
2,666✔
556
}
2,666✔
557

558
void InstructionApplier::operator()(const Instruction::AddColumn& instr)
559
{
8,876✔
560
    using Type = Instruction::Payload::Type;
8,876✔
561
    using CollectionType = Instruction::CollectionType;
8,876✔
562

563
    // Temporarily swap out the last object key so it doesn't get included in error messages
564
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
8,876✔
565

566
    auto table = get_table(instr, "AddColumn");
8,876✔
567
    auto col_name = get_string(instr.field);
8,876✔
568

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

606
    if (instr.collection_type == CollectionType::Dictionary && instr.key_type != Type::String) {
8,520✔
607
        bad_transaction_log("AddColumn '%1.%3' adding dictionary column with non-string keys", table->get_name(),
×
608
                            col_name);
×
609
    }
×
610

611
    if (instr.type != Type::Link) {
8,520✔
612
        DataType type = get_data_type(instr.type);
7,184✔
613
        switch (instr.collection_type) {
7,184✔
614
            case CollectionType::Single: {
6,234✔
615
                table->add_column(type, col_name, instr.nullable);
6,234✔
616
                break;
6,234✔
617
            }
×
618
            case CollectionType::List: {
610✔
619
                table->add_column_list(type, col_name, instr.nullable);
610✔
620
                break;
610✔
621
            }
×
622
            case CollectionType::Dictionary: {
204✔
623
                DataType key_type = get_data_type(instr.key_type);
204✔
624
                table->add_column_dictionary(type, col_name, instr.nullable, key_type);
204✔
625
                break;
204✔
626
            }
×
627
            case CollectionType::Set: {
136✔
628
                table->add_column_set(type, col_name, instr.nullable);
136✔
629
                break;
136✔
630
            }
×
631
        }
7,184✔
632
    }
7,184✔
633
    else {
1,336✔
634
        Group::TableNameBuffer buffer;
1,336✔
635
        auto target_table_name = get_string(instr.link_target_table);
1,336✔
636
        if (target_table_name.size() != 0) {
1,336✔
637
            TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer));
1,336✔
638
            if (!target) {
1,336✔
639
                bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(),
×
640
                                    col_name, target_table_name);
×
641
            }
×
642
            if (instr.collection_type == CollectionType::List) {
1,336✔
643
                table->add_column_list(*target, col_name);
612✔
644
            }
612✔
645
            else if (instr.collection_type == CollectionType::Set) {
724✔
646
                table->add_column_set(*target, col_name);
8✔
647
            }
8✔
648
            else if (instr.collection_type == CollectionType::Dictionary) {
716✔
649
                table->add_column_dictionary(*target, col_name);
32✔
650
            }
32✔
651
            else {
684✔
652
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
684✔
653
                table->add_column(*target, col_name);
684✔
654
            }
684✔
655
        }
1,336✔
UNCOV
656
        else {
×
UNCOV
657
            if (instr.collection_type == CollectionType::List) {
×
658
                table->add_column_list(type_TypedLink, col_name);
×
659
            }
×
UNCOV
660
            else {
×
UNCOV
661
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
×
UNCOV
662
                table->add_column(type_TypedLink, col_name);
×
UNCOV
663
            }
×
UNCOV
664
        }
×
665
    }
1,336✔
666
}
8,520✔
667

668
void InstructionApplier::operator()(const Instruction::EraseColumn& instr)
669
{
×
670
    // Temporarily swap out the last object key so it doesn't get included in error messages
671
    TemporarySwapOut<decltype(m_last_object_key)> last_object_key_guard(m_last_object_key);
×
672

673
    auto table = get_table(instr, "EraseColumn");
×
674
    auto col_name = get_string(instr.field);
×
675

676
    ColKey col = table->get_column_key(col_name);
×
677
    if (!col) {
×
678
        bad_transaction_log("EraseColumn '%1.%2' which doesn't exist", table->get_name(), col_name);
×
679
    }
×
680

681
    table->remove_column(col);
×
682
}
×
683

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

701
            if (index > m_instr.prior_size) {
163,366✔
702
                m_applier->bad_transaction_log("ArrayInsert: Invalid insertion index (index = %1, prior_size = %2)",
×
703
                                               index, m_instr.prior_size);
×
704
            }
×
705

706
            if (index > list.size()) {
163,366✔
707
                m_applier->bad_transaction_log("ArrayInsert: Index out of bounds (%1 > %2)", index, list.size());
×
708
            }
×
709

710
            if (m_instr.prior_size != list.size()) {
163,366✔
711
                m_applier->bad_transaction_log("ArrayInsert: Invalid prior_size (list size = %1, prior_size = %2)",
×
712
                                               list.size(), m_instr.prior_size);
×
713
            }
×
714

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

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

810
            m_applier->visit_payload(m_instr.value, inserter);
163,366✔
811
            return Status::Pending;
163,366✔
812
        }
163,366✔
813

814
    private:
163,368✔
815
        const Instruction::ArrayInsert& m_instr;
163,368✔
816
    };
163,368✔
817
    ArrayInsertResolver(this, instr).resolve();
163,368✔
818
}
163,368✔
819

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

849
    private:
112✔
850
        const Instruction::ArrayMove& m_instr;
112✔
851
    };
112✔
852
    ArrayMoveResolver(this, instr).resolve();
112✔
853
}
112✔
854

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

880
    private:
402✔
881
        const Instruction::ArrayErase& m_instr;
402✔
882
    };
402✔
883
    ArrayEraseResolver(this, instr).resolve();
402✔
884
}
402✔
885

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

999
            return PathResolver::on_property(obj, col_key);
×
1000
        }
232✔
1001

1002
    private:
496✔
1003
        // The server may not send the type for collection properties (non-Mixed)
1004
        // since the clients don't send it either before v13.
1005
        std::optional<CollectionType> m_collection_type;
496✔
1006
    };
496✔
1007
    ClearResolver(this, instr).resolve();
496✔
1008
}
496✔
1009

1010
bool InstructionApplier::allows_null_links(const Instruction::PathInstruction& instr,
1011
                                           const std::string_view& instr_name)
1012
{
40✔
1013
    struct AllowsNullsResolver : public PathResolver {
40✔
1014
        AllowsNullsResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
40✔
1015
                            const std::string_view& instr_name)
40✔
1016
            : PathResolver(applier, instr, instr_name)
40✔
1017
            , m_allows_nulls(false)
40✔
1018
        {
40✔
1019
        }
40✔
1020
        Status on_list_index(LstBase&, uint32_t) override
40✔
1021
        {
40✔
1022
            return Status::Pending;
×
1023
        }
×
1024
        void on_list(LstBase&) override {}
40✔
1025
        void on_set(SetBase&) override {}
40✔
1026
        void on_dictionary(Dictionary&) override
40✔
1027
        {
40✔
1028
            m_allows_nulls = true;
×
1029
        }
×
1030
        Status on_dictionary_key(Dictionary&, Mixed) override
40✔
1031
        {
40✔
1032
            m_allows_nulls = true;
40✔
1033
            return Status::Pending;
40✔
1034
        }
40✔
1035
        Status on_property(Obj&, ColKey) override
40✔
1036
        {
40✔
1037
            m_allows_nulls = true;
×
1038
            return Status::Pending;
×
1039
        }
×
1040
        bool allows_nulls()
40✔
1041
        {
40✔
1042
            resolve();
40✔
1043
            return m_allows_nulls;
40✔
1044
        }
40✔
1045

1046
    private:
40✔
1047
        bool m_allows_nulls;
40✔
1048
    };
40✔
1049
    return AllowsNullsResolver(this, instr, instr_name).allows_nulls();
40✔
1050
}
40✔
1051

1052
std::string InstructionApplier::to_string(const Instruction::PathInstruction& instr) const
1053
{
×
1054
    REALM_ASSERT(m_log);
×
1055
    std::stringstream ss;
×
1056
    m_log->print_path(ss, instr.table, instr.object, instr.field, &instr.path);
×
1057
    return ss.str();
×
1058
}
×
1059

1060
bool InstructionApplier::check_links_exist(const Instruction::Payload& payload)
1061
{
24,598✔
1062
    bool valid_payload = true;
24,598✔
1063
    using Type = Instruction::Payload::Type;
24,598✔
1064
    if (payload.type == Type::Link) {
24,598✔
1065
        StringData class_name = get_string(payload.data.link.target_table);
812✔
1066
        Group::TableNameBuffer buffer;
812✔
1067
        StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
812✔
1068
        TableRef target_table = m_transaction.get_table(target_table_name);
812✔
1069
        if (!target_table) {
812✔
1070
            bad_transaction_log("Link with invalid target table '%1'", target_table_name);
×
1071
        }
×
1072
        if (target_table->is_embedded()) {
812✔
1073
            bad_transaction_log("Link to embedded table '%1'", target_table_name);
×
1074
        }
×
1075
        Mixed linked_pk =
812✔
1076
            mpark::visit(util::overload{[&](mpark::monostate) {
812✔
1077
                                            return Mixed{}; // the link exists and the pk is null
24✔
1078
                                        },
24✔
1079
                                        [&](int64_t pk) {
812✔
1080
                                            return Mixed{pk};
760✔
1081
                                        },
760✔
1082
                                        [&](InternString interned_pk) {
812✔
1083
                                            return Mixed{get_string(interned_pk)};
×
1084
                                        },
×
1085
                                        [&](GlobalKey) -> Mixed {
812✔
1086
                                            bad_transaction_log(
×
1087
                                                "Unexpected link to embedded object while validating a primary key");
×
1088
                                        },
×
1089
                                        [&](ObjectId pk) {
812✔
1090
                                            return Mixed{pk};
28✔
1091
                                        },
28✔
1092
                                        [&](UUID pk) {
812✔
1093
                                            return Mixed{pk};
×
1094
                                        }},
×
1095
                         payload.data.link.target);
812✔
1096

1097
        if (!target_table->find_primary_key(linked_pk)) {
812✔
1098
            valid_payload = false;
168✔
1099
        }
168✔
1100
    }
812✔
1101
    return valid_payload;
24,598✔
1102
}
24,598✔
1103

1104
void InstructionApplier::operator()(const Instruction::SetInsert& instr)
1105
{
1,760✔
1106
    struct SetInsertResolver : public PathResolver {
1,760✔
1107
        SetInsertResolver(InstructionApplier* applier, const Instruction::SetInsert& instr)
1,760✔
1108
            : PathResolver(applier, instr, "SetInsert")
1,760✔
1109
            , m_instr(instr)
1,760✔
1110
        {
1,760✔
1111
        }
1,760✔
1112
        Status on_property(Obj& obj, ColKey col) override
1,760✔
1113
        {
1,760✔
1114
            // This better be a mixed column
1115
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1116
            auto set = obj.get_set<Mixed>(col);
×
1117
            on_set(set);
×
1118
            return Status::Pending;
×
1119
        }
×
1120
        void on_set(SetBase& set) override
1,760✔
1121
        {
1,760✔
1122
            auto col = set.get_col_key();
1,760✔
1123
            auto data_type = DataType(col.get_type());
1,760✔
1124
            auto table = set.get_table();
1,760✔
1125
            auto table_name = table->get_name();
1,760✔
1126
            auto field_name = table->get_column_name(col);
1,760✔
1127

1128
            auto inserter = util::overload{
1,760✔
1129
                [&](const ObjLink& link) {
1,760✔
1130
                    if (data_type == type_TypedLink) {
192✔
1131
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1132
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1133
                        link_set.insert(link);
×
1134
                    }
×
1135
                    else if (data_type == type_Mixed) {
192✔
1136
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
88✔
1137
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
88✔
1138
                        mixed_set.insert(link);
88✔
1139
                    }
88✔
1140
                    else if (data_type == type_Link) {
104✔
1141
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
104✔
1142
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
104✔
1143
                        // Validate the target.
1144
                        auto target_table = table->get_link_target(col);
104✔
1145
                        if (target_table->get_key() != link.get_table_key()) {
104✔
1146
                            m_applier->bad_transaction_log(
×
1147
                                "SetInsert: Target table mismatch (expected '%1', got '%2')",
×
1148
                                target_table->get_name(), table_name);
×
1149
                        }
×
1150
                        link_set.insert(link.get_obj_key());
104✔
1151
                    }
104✔
1152
                    else {
×
1153
                        m_applier->bad_transaction_log(
×
1154
                            "SetInsert: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1155
                            table_name, data_type);
×
1156
                    }
×
1157
                },
192✔
1158
                [&](Mixed value) {
1,760✔
1159
                    if (value.is_null() && !col.is_nullable()) {
1,568✔
1160
                        m_applier->bad_transaction_log("SetInsert: NULL in non-nullable set '%2.%1'", field_name,
×
1161
                                                       table_name);
×
1162
                    }
×
1163

1164
                    if (data_type == type_Mixed || value.is_null() || value.get_type() == data_type) {
1,568✔
1165
                        set.insert_any(value);
1,568✔
1166
                    }
1,568✔
1167
                    else {
×
1168
                        m_applier->bad_transaction_log(
×
1169
                            "SetInsert: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name,
×
1170
                            table_name, data_type, value.get_type());
×
1171
                    }
×
1172
                },
1,568✔
1173
                [&](const Instruction::Payload::ObjectValue&) {
1,760✔
1174
                    m_applier->bad_transaction_log("SetInsert: Sets of embedded objects are not supported.");
×
1175
                },
×
1176
                [&](const Instruction::Payload::Dictionary&) {
1,760✔
1177
                    m_applier->bad_transaction_log("SetInsert: Sets of dictionaries are not supported.");
×
1178
                },
×
1179
                [&](const Instruction::Payload::List&) {
1,760✔
1180
                    m_applier->bad_transaction_log("SetInsert: Sets of lists are not supported.");
×
1181
                },
×
1182
                [&](const Instruction::Payload::Set&) {
1,760✔
1183
                    m_applier->bad_transaction_log("SetInsert: Sets of sets are not supported.");
×
1184
                },
×
1185
                [&](const Instruction::Payload::Erased&) {
1,760✔
1186
                    m_applier->bad_transaction_log("SetInsert: Dictionary erase payload in SetInsert");
×
1187
                },
×
1188
            };
1,760✔
1189

1190
            m_applier->visit_payload(m_instr.value, inserter);
1,760✔
1191
        }
1,760✔
1192

1193
    private:
1,760✔
1194
        const Instruction::SetInsert& m_instr;
1,760✔
1195
    };
1,760✔
1196
    SetInsertResolver(this, instr).resolve();
1,760✔
1197
}
1,760✔
1198

1199
void InstructionApplier::operator()(const Instruction::SetErase& instr)
1200
{
492✔
1201
    struct SetEraseResolver : public PathResolver {
492✔
1202
        SetEraseResolver(InstructionApplier* applier, const Instruction::SetErase& instr)
492✔
1203
            : PathResolver(applier, instr, "SetErase")
492✔
1204
            , m_instr(instr)
492✔
1205
        {
492✔
1206
        }
492✔
1207
        Status on_property(Obj& obj, ColKey col) override
492✔
1208
        {
492✔
1209
            // This better be a mixed column
1210
            REALM_ASSERT(col.get_type() == col_type_Mixed);
×
1211
            auto set = obj.get_set<Mixed>(col);
×
1212
            on_set(set);
×
1213
            return Status::Pending;
×
1214
        }
×
1215
        void on_set(SetBase& set) override
492✔
1216
        {
492✔
1217
            auto col = set.get_col_key();
492✔
1218
            auto data_type = DataType(col.get_type());
492✔
1219
            auto table = set.get_table();
492✔
1220
            auto table_name = table->get_name();
492✔
1221
            auto field_name = table->get_column_name(col);
492✔
1222

1223
            auto inserter = util::overload{
492✔
1224
                [&](const ObjLink& link) {
492✔
1225
                    if (data_type == type_TypedLink) {
168✔
1226
                        REALM_ASSERT(dynamic_cast<Set<ObjLink>*>(&set));
×
1227
                        auto& link_set = static_cast<Set<ObjLink>&>(set);
×
1228
                        link_set.erase(link);
×
1229
                    }
×
1230
                    else if (data_type == type_Mixed) {
168✔
1231
                        REALM_ASSERT(dynamic_cast<Set<Mixed>*>(&set));
80✔
1232
                        auto& mixed_set = static_cast<Set<Mixed>&>(set);
80✔
1233
                        mixed_set.erase(link);
80✔
1234
                    }
80✔
1235
                    else if (data_type == type_Link) {
88✔
1236
                        REALM_ASSERT(dynamic_cast<Set<ObjKey>*>(&set));
88✔
1237
                        auto& link_set = static_cast<Set<ObjKey>&>(set);
88✔
1238
                        // Validate the target.
1239
                        auto target_table = table->get_link_target(col);
88✔
1240
                        if (target_table->get_key() != link.get_table_key()) {
88✔
1241
                            m_applier->bad_transaction_log(
×
1242
                                "SetErase: Target table mismatch (expected '%1', got '%2')", target_table->get_name(),
×
1243
                                table_name);
×
1244
                        }
×
1245
                        link_set.erase(link.get_obj_key());
88✔
1246
                    }
88✔
1247
                    else {
×
1248
                        m_applier->bad_transaction_log(
×
1249
                            "SetErase: Type mismatch in set at '%2.%1' (expected link type, was %3)", field_name,
×
1250
                            table_name, data_type);
×
1251
                    }
×
1252
                },
168✔
1253
                [&](Mixed value) {
492✔
1254
                    if (value.is_null() && !col.is_nullable()) {
324!
1255
                        m_applier->bad_transaction_log("SetErase: NULL in non-nullable set '%2.%1'", field_name,
×
1256
                                                       table_name);
×
1257
                    }
×
1258

1259
                    if (data_type == type_Mixed || value.get_type() == data_type) {
324✔
1260
                        set.erase_any(value);
324✔
1261
                    }
324✔
1262
                    else {
×
1263
                        m_applier->bad_transaction_log(
×
1264
                            "SetErase: Type mismatch in set at '%2.%1' (expected %3, got %4)", field_name, table_name,
×
1265
                            data_type, value.get_type());
×
1266
                    }
×
1267
                },
324✔
1268
                [&](const Instruction::Payload::ObjectValue&) {
492✔
1269
                    m_applier->bad_transaction_log("SetErase: Sets of embedded objects are not supported.");
×
1270
                },
×
1271
                [&](const Instruction::Payload::List&) {
492✔
1272
                    m_applier->bad_transaction_log("SetErase: Sets of lists are not supported.");
×
1273
                },
×
1274
                [&](const Instruction::Payload::Set&) {
492✔
1275
                    m_applier->bad_transaction_log("SetErase: Sets of sets are not supported.");
×
1276
                },
×
1277
                [&](const Instruction::Payload::Dictionary&) {
492✔
1278
                    m_applier->bad_transaction_log("SetErase: Sets of dictionaries are not supported.");
×
1279
                },
×
1280
                [&](const Instruction::Payload::Erased&) {
492✔
1281
                    m_applier->bad_transaction_log("SetErase: Dictionary erase payload in SetErase");
×
1282
                },
×
1283
            };
492✔
1284

1285
            m_applier->visit_payload(m_instr.value, inserter);
492✔
1286
        }
492✔
1287

1288
    private:
492✔
1289
        const Instruction::SetErase& m_instr;
492✔
1290
    };
492✔
1291
    SetEraseResolver(this, instr).resolve();
492✔
1292
}
492✔
1293

1294
StringData InstructionApplier::get_table_name(const Instruction::TableInstruction& instr,
1295
                                              const std::string_view& name)
1296
{
103,890✔
1297
    if (auto class_name = m_log->try_get_string(instr.table)) {
103,892✔
1298
        return Group::class_name_to_table_name(*class_name, m_table_name_buffer);
103,892✔
1299
    }
103,892✔
1300
    else {
2,147,483,647✔
1301
        bad_transaction_log("Corrupt table name in %1 instruction", name);
2,147,483,647✔
1302
    }
2,147,483,647✔
1303
}
103,890✔
1304

1305
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1306
{
453,462✔
1307
    if (instr.table == m_last_table_name) {
453,462✔
1308
        return m_last_table;
354,328✔
1309
    }
354,328✔
1310
    else {
99,134✔
1311
        auto table_name = get_table_name(instr, name);
99,134✔
1312
        TableRef table = m_transaction.get_table(table_name);
99,134✔
1313
        if (!table) {
99,134✔
1314
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
×
1315
        }
×
1316
        m_last_table = table;
99,134✔
1317
        m_last_table_name = instr.table;
99,134✔
1318
        m_last_object_key.reset();
99,134✔
1319
        m_last_object.reset();
99,134✔
1320
        m_last_field_name = InternString{};
99,134✔
1321
        m_last_field = ColKey{};
99,134✔
1322
        return table;
99,134✔
1323
    }
99,134✔
1324
}
453,462✔
1325

1326
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1327
                                                       const std::string_view& name)
1328
{
670,336✔
1329
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
670,336✔
1330
        *m_last_object_key == instr.object) {
670,336✔
1331
        // We have already found the object, reuse it.
1332
        return *m_last_object;
271,762✔
1333
    }
271,762✔
1334
    else {
398,574✔
1335
        TableRef table = get_table(instr, name);
398,574✔
1336
        ObjKey key = get_object_key(*table, instr.object, name);
398,574✔
1337
        if (!key) {
398,574✔
1338
            return util::none;
×
1339
        }
×
1340
        if (!table->is_valid(key)) {
398,574✔
1341
            // Check if the object is deleted or is a tombstone.
1342
            return util::none;
1,622✔
1343
        }
1,622✔
1344

1345
        Obj obj = table->get_object(key);
396,952✔
1346
        m_last_object_key = instr.object;
396,952✔
1347
        m_last_object = obj;
396,952✔
1348
        return obj;
396,952✔
1349
    }
398,574✔
1350
}
670,336✔
1351

1352
LstBasePtr InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1353
{
176,746✔
1354
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
1355
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
1356
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
1357
    // links.
1358
    REALM_ASSERT(col.is_list());
176,746✔
1359
    LstBasePtr list;
176,746✔
1360
    if (col.get_type() == col_type_Link) {
176,746✔
1361
        auto table = obj.get_table();
13,784✔
1362
        if (!table->get_link_target(col)->is_embedded()) {
13,784✔
1363
            list = obj.get_list_ptr<ObjKey>(col);
1,648✔
1364
        }
1,648✔
1365
        else {
12,136✔
1366
            list = obj.get_listbase_ptr(col);
12,136✔
1367
        }
12,136✔
1368
    }
13,784✔
1369
    else {
162,962✔
1370
        list = obj.get_listbase_ptr(col);
162,962✔
1371
    }
162,962✔
1372
    return list;
176,746✔
1373
}
176,746✔
1374

1375
InstructionApplier::PathResolver::PathResolver(InstructionApplier* applier, const Instruction::PathInstruction& instr,
1376
                                               const std::string_view& instr_name)
1377
    : m_applier(applier)
327,486✔
1378
    , m_path_instr(instr)
327,486✔
1379
    , m_instr_name(instr_name)
327,486✔
1380
{
666,746✔
1381
}
666,746✔
1382

1383
InstructionApplier::PathResolver::~PathResolver()
1384
{
666,796✔
1385
    on_finish();
666,796✔
1386
}
666,796✔
1387

1388
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_property(Obj&, ColKey)
1389
{
×
1390
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (object, column)", m_instr_name));
×
1391
    return Status::DidNotResolve;
×
1392
}
×
1393

1394
void InstructionApplier::PathResolver::on_list(LstBase&)
1395
{
×
1396
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list)", m_instr_name));
×
1397
}
×
1398

1399
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index(LstBase&, uint32_t)
1400
{
×
1401
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (list, index)", m_instr_name));
×
1402
    return Status::DidNotResolve;
×
1403
}
×
1404

1405
void InstructionApplier::PathResolver::on_dictionary(Dictionary&)
1406
{
×
1407
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1408
}
×
1409

1410
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_dictionary_key(Dictionary&, Mixed)
1411
{
×
1412
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (dictionary, key)", m_instr_name));
×
1413
    return Status::DidNotResolve;
×
1414
}
×
1415

1416
void InstructionApplier::PathResolver::on_set(SetBase&)
1417
{
×
1418
    m_applier->bad_transaction_log(util::format("Invalid path for %1 (set)", m_instr_name));
×
1419
}
×
1420

1421
void InstructionApplier::PathResolver::on_error(const std::string& err_msg)
1422
{
×
1423
    m_applier->bad_transaction_log(err_msg);
×
1424
}
×
1425

1426
InstructionApplier::PathResolver::Status
1427
InstructionApplier::PathResolver::on_mixed_type_changed(const std::string& err_msg)
1428
{
×
1429
    m_applier->bad_transaction_log(err_msg);
×
1430
    return Status::DidNotResolve;
×
1431
}
×
1432

1433
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
1434
{
652,662✔
1435
    m_applier->m_last_field = col;
652,662✔
1436
}
652,662✔
1437

1438
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
3,012✔
1439

1440
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
1441
{
8,044✔
1442
    return Status::Pending;
8,044✔
1443
}
8,044✔
1444

1445
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_null_link_advance(StringData,
1446
                                                                                                StringData)
1447
{
×
1448
    return Status::Pending;
×
1449
}
×
1450

1451
InstructionApplier::PathResolver::Status
1452
InstructionApplier::PathResolver::on_dict_key_not_found(StringData, StringData, StringData)
1453
{
×
1454
    return Status::Pending;
×
1455
}
×
1456

1457
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
1458
{
640,104✔
1459
    m_applier->m_current_path = m_path_instr.path;
640,104✔
1460
    m_applier->m_last_field_name = m_path_instr.field;
640,104✔
1461
    return Status::Pending;
640,104✔
1462
}
640,104✔
1463

1464
void InstructionApplier::PathResolver::on_finish()
1465
{
666,800✔
1466
    m_applier->m_current_path.reset();
666,800✔
1467
    m_applier->m_last_field_name = InternString{};
666,800✔
1468
    m_applier->m_last_field = ColKey{};
666,800✔
1469
}
666,800✔
1470

1471
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1472
{
693,586✔
1473
    return m_applier->get_string(interned);
693,586✔
1474
}
693,586✔
1475

1476
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve()
1477
{
666,758✔
1478
    util::Optional<Obj> obj = m_applier->get_top_object(m_path_instr, m_instr_name);
666,758✔
1479
    Status begin_status = on_begin(obj);
666,758✔
1480
    if (begin_status != Status::Pending) {
666,758✔
1481
        return begin_status;
220✔
1482
    }
220✔
1483
    if (!obj) {
666,538✔
1484
        m_applier->bad_transaction_log("%1: No such object: '%2' in class '%3'", m_instr_name,
×
1485
                                       format_pk(m_applier->m_log->get_key(m_path_instr.object)),
×
1486
                                       get_string(m_path_instr.table));
×
1487
    }
×
1488

1489
    m_it_begin = m_path_instr.path.begin();
666,538✔
1490
    m_it_end = m_path_instr.path.end();
666,538✔
1491
    Status status = resolve_field(*obj, m_path_instr.field);
666,538✔
1492
    return status == Status::Pending ? Status::Success : status;
666,538✔
1493
}
666,758✔
1494

1495
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_field(Obj& obj, InternString field)
1496
{
682,586✔
1497
    auto field_name = get_string(field);
682,586✔
1498
    ColKey col = obj.get_table()->get_column_key(field_name);
682,586✔
1499
    if (!col) {
682,586✔
1500
        on_error(util::format("%1: No such field: '%2' in class '%3'", m_instr_name, field_name,
×
1501
                              obj.get_table()->get_name()));
×
1502
        return Status::DidNotResolve;
×
1503
    }
×
1504
    on_column_advance(col);
682,586✔
1505

1506
    if (m_it_begin == m_it_end) {
682,586✔
1507
        if (col.is_list()) {
489,944✔
1508
            auto list = obj.get_listbase_ptr(col);
124✔
1509
            on_list(*list);
124✔
1510
        }
124✔
1511
        else if (col.is_dictionary()) {
489,820✔
1512
            auto dict = obj.get_dictionary(col);
80✔
1513
            on_dictionary(dict);
80✔
1514
        }
80✔
1515
        else if (col.is_set()) {
489,740✔
1516
            SetBasePtr set;
3,664✔
1517
            if (col.get_type() == col_type_Link) {
3,664✔
1518
                // We are interested in using non-condensed indexes - as for Lists below
1519
                set = obj.get_set_ptr<ObjKey>(col);
376✔
1520
            }
376✔
1521
            else {
3,288✔
1522
                set = obj.get_setbase_ptr(col);
3,288✔
1523
            }
3,288✔
1524
            on_set(*set);
3,664✔
1525
        }
3,664✔
1526
        else {
486,076✔
1527
            return on_property(obj, col);
486,076✔
1528
        }
486,076✔
1529
        return Status::Pending;
3,868✔
1530
    }
489,944✔
1531

1532
    if (col.is_list()) {
192,642✔
1533
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
176,746✔
1534
            auto list = InstructionApplier::get_list_from_path(obj, col);
176,744✔
1535
            ++m_it_begin;
176,744✔
1536
            return resolve_list_element(*list, *pindex);
176,744✔
1537
        }
176,744✔
1538
        on_error(
2✔
1539
            util::format(s_list_index_wrong_type_err.data(), m_instr_name, field_name, obj.get_table()->get_name()));
2✔
1540
    }
2✔
1541
    else if (col.is_dictionary()) {
15,896✔
1542
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
8,828✔
1543
            auto dict = obj.get_dictionary(col);
8,828✔
1544
            ++m_it_begin;
8,828✔
1545
            return resolve_dictionary_element(dict, *pkey);
8,828✔
1546
        }
8,828✔
1547
        on_error(
×
1548
            util::format(s_dict_key_wrong_type_err.data(), m_instr_name, field_name, obj.get_table()->get_name()));
×
1549
    }
×
1550
    else if (col.get_type() == col_type_Mixed) {
7,068✔
1551
        auto val = obj.get<Mixed>(col);
2,452✔
1552
        std::string_view error_msg;
2,452✔
1553
        if (val.is_type(type_Dictionary)) {
2,452✔
1554
            if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
1,476✔
1555
                Dictionary dict(obj, col);
1,468✔
1556
                ++m_it_begin;
1,468✔
1557
                return resolve_dictionary_element(dict, *pkey);
1,468✔
1558
            }
1,468✔
1559
            error_msg = s_dict_key_wrong_type_err;
8✔
1560
        }
8✔
1561
        else if (val.is_type(type_List)) {
976✔
1562
            if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
968✔
1563
                Lst<Mixed> list(obj, col);
960✔
1564
                ++m_it_begin;
960✔
1565
                return resolve_list_element(list, *pindex);
960✔
1566
            }
960✔
1567
            error_msg = s_list_index_wrong_type_err;
8✔
1568
        }
8✔
1569
        else {
8✔
1570
            error_msg = s_wrong_collection_type_err;
8✔
1571
        }
8✔
1572
        return on_mixed_type_changed(
24✔
1573
            util::format(error_msg.data(), m_instr_name, field_name, obj.get_table()->get_name()));
24✔
1574
    }
2,452✔
1575
    else if (col.get_type() == col_type_Link) {
4,616✔
1576
        auto target = obj.get_table()->get_link_target(col);
4,608✔
1577
        if (!target->is_embedded()) {
4,608✔
1578
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
×
1579
                                  field_name, obj.get_table()->get_name()));
×
1580
        }
×
1581
        else if (obj.is_null(col)) {
4,608✔
1582
            Status null_status =
132✔
1583
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
132✔
1584
            if (null_status != Status::Pending) {
132✔
1585
                return null_status;
132✔
1586
            }
132✔
1587
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
×
1588
                                  m_instr_name, field_name, obj.get_table()->get_name()));
×
1589
        }
×
1590
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,476✔
1591
            auto embedded_object = obj.get_linked_object(col);
4,476✔
1592
            ++m_it_begin;
4,476✔
1593
            return resolve_field(embedded_object, *pfield);
4,476✔
1594
        }
4,476✔
1595
        else {
×
1596
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1597
        }
×
1598
    }
4,608✔
1599
    else {
8✔
1600
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
8✔
1601
                              field_name, obj.get_table()->get_name(), col.get_type()));
8✔
1602
    }
8✔
1603
    return Status::DidNotResolve;
10✔
1604
}
192,642✔
1605

1606
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
1607
                                                                                                uint32_t index)
1608
{
178,580✔
1609
    if (m_it_begin == m_it_end) {
178,580✔
1610
        return on_list_index(list, index);
167,682✔
1611
    }
167,682✔
1612

1613
    auto col = list.get_col_key();
10,898✔
1614
    auto table = list.get_table();
10,898✔
1615
    auto field_name = table->get_column_name(col);
10,898✔
1616

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

1625
        Status list_status = on_list_index_advance(index);
10,172✔
1626
        if (list_status != Status::Pending) {
10,172✔
1627
            return list_status;
2,720✔
1628
        }
2,720✔
1629

1630
        REALM_ASSERT(dynamic_cast<LnkLst*>(&list));
7,452✔
1631
        auto& link_list = static_cast<LnkLst&>(list);
7,452✔
1632
        if (index >= link_list.size()) {
7,452✔
1633
            on_error(util::format("%1: Out-of-bounds index through list at '%2.%3[%4]'", m_instr_name,
×
1634
                                  table->get_name(), field_name, index));
×
1635
        }
×
1636
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
7,452✔
1637
            auto embedded_object = link_list.get_object(index);
7,452✔
1638
            ++m_it_begin;
7,452✔
1639
            return resolve_field(embedded_object, *pfield);
7,452✔
1640
        }
7,452✔
1641
        on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1642
    }
×
1643
    else {
726✔
1644
        if (list.get_data_type() == type_Mixed) {
728✔
1645
            Status list_status = on_list_index_advance(index);
728✔
1646
            if (list_status != Status::Pending) {
728✔
1647
                return list_status;
136✔
1648
            }
136✔
1649
            auto& mixed_list = static_cast<Lst<Mixed>&>(list);
592✔
1650
            if (index >= mixed_list.size()) {
592✔
1651
                on_error(util::format("%1: Out-of-bounds index '%2' through list along path '%2.%3'", m_instr_name,
×
1652
                                      index, table->get_name(), field_name));
×
1653
            }
×
1654
            else {
592✔
1655
                auto val = mixed_list.get(index);
592✔
1656
                std::string_view error_msg;
592✔
1657

1658
                if (val.is_type(type_Dictionary)) {
592✔
1659
                    if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
444✔
1660
                        Dictionary d(mixed_list, mixed_list.get_key(index));
444✔
1661
                        ++m_it_begin;
444✔
1662
                        return resolve_dictionary_element(d, *pfield);
444✔
1663
                    }
444✔
1664
                    error_msg = s_dict_key_wrong_type_err;
×
1665
                }
×
1666
                else if (val.is_type(type_List)) {
148✔
1667
                    if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
148✔
1668
                        Lst<Mixed> l(mixed_list, mixed_list.get_key(index));
148✔
1669
                        ++m_it_begin;
148✔
1670
                        return resolve_list_element(l, *pindex);
148✔
1671
                    }
148✔
1672
                    error_msg = s_list_index_wrong_type_err;
×
1673
                }
×
1674
                else {
×
1675
                    error_msg = s_wrong_collection_type_err;
×
1676
                }
×
1677
                return on_mixed_type_changed(
×
1678
                    util::format(error_msg.data(), m_instr_name, field_name, table->get_name()));
×
1679
            }
592✔
1680
        }
592✔
1681
        on_error(util::format(
2,147,483,647✔
1682
            "%1: Resolving path through unstructured list element on '%2.%3', which is a list of type '%4'",
2,147,483,647✔
1683
            m_instr_name, table->get_name(), field_name, col.get_type()));
2,147,483,647✔
1684
    }
2,147,483,647✔
1685
    return Status::DidNotResolve;
2,147,483,647✔
1686
}
10,898✔
1687

1688
InstructionApplier::PathResolver::Status
1689
InstructionApplier::PathResolver::resolve_dictionary_element(Dictionary& dict, InternString key)
1690
{
11,004✔
1691
    StringData string_key = get_string(key);
11,004✔
1692
    if (m_it_begin == m_it_end) {
11,004✔
1693
        return on_dictionary_key(dict, Mixed{string_key});
5,840✔
1694
    }
5,840✔
1695

1696
    on_dict_key_advance(string_key);
5,164✔
1697

1698
    auto col = dict.get_col_key();
5,164✔
1699
    auto table = dict.get_table();
5,164✔
1700
    auto field_name = table->get_column_name(col);
5,164✔
1701

1702
    if (col.get_type() == col_type_Link) {
5,164✔
1703
        auto target = dict.get_target_table();
4,164✔
1704
        if (!target->is_embedded()) {
4,164✔
1705
            on_error(util::format("%1: Reference through non-embedded link at '%3.%2[%4]'", m_instr_name, field_name,
×
1706
                                  table->get_name(), string_key));
×
1707
            return Status::DidNotResolve;
×
1708
        }
×
1709

1710
        auto embedded_object = dict.get_object(string_key);
4,164✔
1711
        if (!embedded_object) {
4,164✔
1712
            Status null_link_status = on_null_link_advance(table->get_name(), string_key);
80✔
1713
            if (null_link_status != Status::Pending) {
80✔
1714
                return null_link_status;
80✔
1715
            }
80✔
1716
            on_error(util::format("%1: Unmatched key through dictionary at '%3.%2[%4]'", m_instr_name, field_name,
×
1717
                                  table->get_name(), string_key));
×
1718
        }
×
1719
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
4,084✔
1720
            ++m_it_begin;
4,084✔
1721
            return resolve_field(embedded_object, *pfield);
4,084✔
1722
        }
4,084✔
1723
        else {
×
1724
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
×
1725
        }
×
1726
    }
4,164✔
1727
    else {
1,000✔
1728
        if (auto val = dict.try_get(string_key)) {
1,000✔
1729
            std::string_view error_msg;
996✔
1730
            if (val->is_type(type_Dictionary)) {
996✔
1731
                if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
264✔
1732
                    Dictionary d(dict, dict.build_index(string_key));
264✔
1733
                    ++m_it_begin;
264✔
1734
                    return resolve_dictionary_element(d, *pfield);
264✔
1735
                }
264✔
1736
                error_msg = s_dict_key_wrong_type_err;
×
1737
            }
×
1738
            else if (val->is_type(type_List)) {
732✔
1739
                if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
728✔
1740
                    Lst<Mixed> l(dict, dict.build_index(string_key));
728✔
1741
                    ++m_it_begin;
728✔
1742
                    return resolve_list_element(l, *pindex);
728✔
1743
                }
728✔
1744
                error_msg = s_list_index_wrong_type_err;
×
1745
            }
×
1746
            else {
4✔
1747
                error_msg = s_wrong_collection_type_err;
4✔
1748
            }
4✔
1749
            return on_mixed_type_changed(util::format(error_msg.data(), m_instr_name, field_name, table->get_name()));
4✔
1750
        }
996✔
1751
        Status key_not_found_status = on_dict_key_not_found(table->get_name(), field_name, string_key);
4✔
1752
        if (key_not_found_status != Status::Pending) {
4✔
1753
            return key_not_found_status;
4✔
1754
        }
4✔
1755
        on_error(util::format("%1: Unmatched key '%2' through dictionary along path '%3.%4'", m_instr_name,
×
1756
                              string_key, table->get_name(), field_name));
×
1757
    }
×
1758
    return Status::DidNotResolve;
×
1759
}
5,164✔
1760

1761

1762
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1763
                                          const std::string_view& name) const
1764
{
400,670✔
1765
    StringData table_name = table.get_name();
400,670✔
1766
    ColKey pk_col = table.get_primary_key_column();
400,670✔
1767
    StringData pk_name = "";
400,670✔
1768
    DataType pk_type;
400,670✔
1769
    if (pk_col) {
400,670✔
1770
        pk_name = table.get_column_name(pk_col);
400,660✔
1771
        pk_type = table.get_column_type(pk_col);
400,660✔
1772
    }
400,660✔
1773
    return mpark::visit(
400,670✔
1774
        util::overload{
400,670✔
1775
            [&](mpark::monostate) {
400,670✔
1776
                if (!pk_col) {
24✔
1777
                    bad_transaction_log(
×
1778
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
×
1779
                        name, table_name);
×
1780
                }
×
1781
                if (!table.is_nullable(pk_col)) {
24✔
1782
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
×
1783
                                        name, table_name, pk_name);
×
1784
                }
×
1785

1786
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
24✔
1787
                return key;
24✔
1788
            },
24✔
1789
            [&](int64_t pk) {
400,670✔
1790
                if (!pk_col) {
393,558✔
1791
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
×
1792
                                        "a primary key column",
×
1793
                                        name, pk, table_name);
×
1794
                }
×
1795
                if (pk_type != type_Int) {
393,558✔
1796
                    bad_transaction_log(
×
1797
                        "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1798
                        name, pk, table_name, pk_name, pk_type);
×
1799
                }
×
1800
                ObjKey key = table.get_objkey_from_primary_key(pk);
393,558✔
1801
                return key;
393,558✔
1802
            },
393,558✔
1803
            [&](InternString interned_pk) {
400,670✔
1804
                auto pk = get_string(interned_pk);
1,408✔
1805
                if (!pk_col) {
1,408✔
1806
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1807
                                        "have a primary key column",
×
1808
                                        name, pk, table_name);
×
1809
                }
×
1810
                if (pk_type != type_String) {
1,408✔
1811
                    bad_transaction_log(
×
1812
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
×
1813
                        name, pk, table_name, pk_name, pk_type);
×
1814
                }
×
1815
                ObjKey key = table.get_objkey_from_primary_key(pk);
1,408✔
1816
                return key;
1,408✔
1817
            },
1,408✔
1818
            [&](GlobalKey id) {
400,670✔
1819
                if (pk_col) {
4✔
1820
                    bad_transaction_log(
×
1821
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
1822
                        name, table_name, pk_type);
×
1823
                }
×
1824
                ObjKey key = table.get_objkey_from_global_key(id);
4✔
1825
                return key;
4✔
1826
            },
4✔
1827
            [&](ObjectId pk) {
400,670✔
1828
                if (!pk_col) {
5,624✔
1829
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1830
                                        "have a primary key column",
×
1831
                                        name, pk, table_name);
×
1832
                }
×
1833
                if (pk_type != type_ObjectId) {
5,624✔
1834
                    bad_transaction_log(
×
1835
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
×
1836
                        name, pk, table_name, pk_name, pk_type);
×
1837
                }
×
1838
                ObjKey key = table.get_objkey_from_primary_key(pk);
5,624✔
1839
                return key;
5,624✔
1840
            },
5,624✔
1841
            [&](UUID pk) {
400,670✔
1842
                if (!pk_col) {
20✔
1843
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
×
1844
                                        "have a primary key column",
×
1845
                                        name, pk, table_name);
×
1846
                }
×
1847
                if (pk_type != type_UUID) {
20✔
1848
                    bad_transaction_log(
×
1849
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
×
1850
                        pk, table_name, pk_name, pk_type);
×
1851
                }
×
1852
                ObjKey key = table.get_objkey_from_primary_key(pk);
20✔
1853
                return key;
20✔
1854
            }},
20✔
1855
        primary_key);
400,670✔
1856
}
400,670✔
1857

1858

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

© 2025 Coveralls, Inc