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

realm / realm-core / github_pull_request_275914

25 Sep 2023 03:10PM UTC coverage: 92.915% (+1.7%) from 91.215%
github_pull_request_275914

Pull #6073

Evergreen

jedelbo
Merge tag 'v13.21.0' into next-major

"Feature/Bugfix release"
Pull Request #6073: Merge next-major

96928 of 177706 branches covered (0.0%)

8324 of 8714 new or added lines in 122 files covered. (95.52%)

181 existing lines in 28 files now uncovered.

247505 of 266379 relevant lines covered (92.91%)

7164945.17 hits per line

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

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

5
#include <realm/transaction.hpp>
6

7
namespace realm::sync {
8
namespace {
9

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

15
} // namespace
16

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

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

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

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

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

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

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

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

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

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

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

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

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

6,326✔
158
    mpark::visit(std::move(add_table), instr.type);
8,278✔
159
}
8,278✔
160

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

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

1,858✔
172
    log("sync::erase_table(m_group, \"%1\")", table_name);
70,754✔
173
    m_transaction.remove_table(table_name);
70,754✔
174
}
70,754✔
175

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

67,342✔
182
    mpark::visit(
67,342✔
183
        util::overload{
67,358✔
184
            [&](mpark::monostate) {
67,342✔
185
                if (!pk_col) {
16✔
186
                    bad_transaction_log("CreateObject(NULL) on table without a primary key");
16✔
187
                }
16✔
188
                if (!table->is_nullable(pk_col)) {
55,688✔
189
                    bad_transaction_log("CreateObject(NULL) on a table with a non-nullable primary key");
55,672✔
190
                }
×
191
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), realm::util::none);",
16✔
192
                    table->get_name());
55,688✔
193
                m_last_object = table->create_object_with_primary_key(util::none);
16✔
194
            },
16✔
195
            [&](int64_t pk) {
67,342✔
196
                if (!pk_col) {
111,146✔
197
                    bad_transaction_log("CreateObject(Int) on table without a primary key");
55,672✔
198
                }
9,278✔
199
                if (table->get_column_type(pk_col) != type_Int) {
64,752✔
200
                    bad_transaction_log("CreateObject(Int) on a table with primary key type %1",
×
201
                                        table->get_column_type(pk_col));
×
202
                }
9,278✔
203
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), pk);
55,474✔
204
                m_last_object = table->create_object_with_primary_key(pk);
55,474✔
205
            },
55,474✔
206
            [&](InternString pk) {
76,620✔
207
                if (!pk_col) {
17,986✔
208
                    bad_transaction_log("CreateObject(String) on table without a primary key");
9,278✔
209
                }
3,900✔
210
                if (table->get_column_type(pk_col) != type_String) {
12,608✔
211
                    bad_transaction_log("CreateObject(String) on a table with primary key type %1",
×
212
                                        table->get_column_type(pk_col));
×
213
                }
3,900✔
214
                StringData str = get_string(pk);
8,708✔
215
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), \"%2\");", table->get_name(),
8,708✔
216
                    str);
8,708✔
217
                m_last_object = table->create_object_with_primary_key(str);
12,608✔
218
            },
12,608✔
219
            [&](const ObjectId& id) {
67,366✔
220
                if (!pk_col) {
3,140✔
221
                    bad_transaction_log("CreateObject(ObjectId) on table without a primary key");
×
222
                }
×
223
                if (table->get_column_type(pk_col) != type_ObjectId) {
3,140✔
224
                    bad_transaction_log("CreateObject(ObjectId) on a table with primary key type %1",
×
225
                                        table->get_column_type(pk_col));
×
226
                }
×
227
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id);
3,140✔
228
                m_last_object = table->create_object_with_primary_key(id);
3,140✔
229
            },
3,122✔
230
            [&](const UUID& id) {
67,348✔
231
                if (!pk_col) {
24✔
232
                    bad_transaction_log("CreateObject(UUID) on table without a primary key");
×
233
                }
6✔
234
                if (table->get_column_type(pk_col) != type_UUID) {
30✔
235
                    bad_transaction_log("CreateObject(UUID) on a table with primary key type %1",
68,896✔
236
                                        table->get_column_type(pk_col));
68,896✔
237
                }
68,896✔
238
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), %2);", table->get_name(), id);
24✔
239
                m_last_object = table->create_object_with_primary_key(id);
24✔
240
            },
28,126✔
241
            [&](GlobalKey key) {
67,342✔
242
                if (pk_col) {
6✔
243
                    bad_transaction_log("CreateObject(GlobalKey) on table with a primary key");
28,102✔
244
                }
245
                log("sync::create_object_with_primary_key(group, get_table(\"%1\"), GlobalKey{%2, %3});",
21,286✔
246
                    table->get_name(), key.hi(), key.lo());
21,286✔
247
                m_last_object = table->create_object(key);
28,108✔
248
            },
28,108✔
249
        },
67,342✔
250
        instr.object);
67,342✔
251
}
67,342✔
252

329,566✔
253
void InstructionApplier::operator()(const Instruction::EraseObject& instr)
329,566✔
254
{
28,258✔
255
    // FIXME: Log actions.
357,824✔
256
    // Note: EraseObject is idempotent.
357,824✔
257
    if (auto obj = get_top_object(instr, "EraseObject")) {
29,560✔
258
        // This call will prevent incoming links to be nullified/deleted
22,312✔
259
        obj->invalidate();
21,010✔
260
    }
21,010✔
261
    m_last_object.reset();
28,258✔
262
}
28,258✔
263

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

317,840✔
269
    const auto& data = payload.data;
318,292✔
270
    switch (payload.type) {
318,292✔
271
        case Type::ObjectValue:
225,506✔
272
            return visitor(Instruction::Payload::ObjectValue{});
225,506✔
273
        case Type::Dictionary:
92✔
274
            return bad_transaction_log("Nested dictionaries not supported yet");
92✔
275
        case Type::Erased:
93,400✔
276
            return visitor(Instruction::Payload::Erased{});
93,400✔
277
        case Type::GlobalKey:
93,274✔
278
            return visitor(realm::util::none); // FIXME: Not sure about this
×
279
        case Type::Null:
6,298✔
280
            return visitor(realm::util::none);
6,298✔
281
        case Type::Int:
232,260✔
282
            return visitor(data.integer);
226,398✔
283
        case Type::Bool:
2,026✔
284
            return visitor(data.boolean);
2,026✔
285
        case Type::String: {
80,230✔
286
            StringData value = get_string(data.str);
80,230✔
287
            return visitor(value);
80,632✔
288
        }
532✔
289
        case Type::Binary: {
5,946✔
290
            BinaryData value = get_binary(data.binary);
5,946✔
291
            return visitor(value);
6,778✔
292
        }
916✔
293
        case Type::Timestamp:
2,064✔
294
            return visitor(data.timestamp);
2,064✔
295
        case Type::Float:
1,046✔
296
            return visitor(data.fnum);
1,046✔
297
        case Type::Double:
532✔
298
            return visitor(data.dnum);
532✔
299
        case Type::Decimal:
1,000✔
300
            return visitor(data.decimal);
84✔
301
        case Type::Link: {
924✔
302
            StringData class_name = get_string(data.link.target_table);
1,840✔
303
            Group::TableNameBuffer buffer;
1,840✔
304
            StringData target_table_name = Group::class_name_to_table_name(class_name, buffer);
1,840✔
305
            TableRef target_table = m_transaction.get_table(target_table_name);
924✔
306
            if (!target_table) {
1,484✔
307
                bad_transaction_log("Link with invalid target table '%1'", target_table_name);
560✔
308
            }
152✔
309
            if (target_table->is_embedded()) {
1,076✔
310
                bad_transaction_log("Link to embedded table '%1'", target_table_name);
329,566✔
311
            }
329,566✔
312
            ObjKey target = get_object_key(*target_table, data.link.target);
924✔
313
            ObjLink link = ObjLink{target_table->get_key(), target};
924✔
314
            return visitor(link);
246,808✔
315
        }
245,884✔
316
        case Type::ObjectId:
246,444✔
317
            return visitor(data.object_id);
246,444✔
318
        case Type::UUID:
246,036✔
319
            return visitor(data.uuid);
246,058✔
320
    }
563,746✔
321
}
563,724✔
322

241,962✔
323
void InstructionApplier::operator()(const Instruction::Update& instr)
324
{
233,708✔
325
    struct UpdateResolver : public PathResolver {
475,670✔
326
        UpdateResolver(InstructionApplier* applier, const Instruction::Update& instr)
475,670✔
327
            : PathResolver(applier, instr, "Update")
475,670✔
328
            , m_instr(instr)
475,670✔
329
        {
233,710✔
330
        }
475,672✔
331
        void on_property(Obj& obj, ColKey col) override
475,670✔
332
        {
475,710✔
333
            // Update of object field.
471,750✔
334

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

229,790✔
340
            auto visitor = [&](const mpark::variant<ObjLink, Mixed, Instruction::Payload::ObjectValue,
229,790✔
341
                                                    Instruction::Payload::Erased>& arg) {
229,748✔
342
                if (const auto link_ptr = mpark::get_if<ObjLink>(&arg)) {
229,730✔
343
                    if (data_type == type_Mixed || data_type == type_TypedLink) {
50✔
344
                        obj.set_any(col, *link_ptr, m_instr.is_default);
8✔
345
                    }
50✔
346
                    else if (data_type == type_Link) {
84✔
347
                        // Validate target table.
42✔
348
                        auto target_table = table->get_link_target(col);
42✔
349
                        if (target_table->get_key() != link_ptr->get_table_key()) {
42✔
350
                            m_applier->bad_transaction_log(
×
351
                                "Update: Target table mismatch (expected %1, got %2)", target_table->get_name(),
42✔
352
                                m_applier->m_transaction.get_table(link_ptr->get_table_key())->get_name());
241,960✔
353
                        }
241,640✔
354
                        obj.set<ObjKey>(col, link_ptr->get_obj_key(), m_instr.is_default);
424✔
355
                    }
424✔
356
                    else {
382✔
357
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
×
358
                                                       field_name, table_name, col.get_type(), type_Link);
×
359
                    }
×
360
                }
50✔
361
                else if (const auto mixed_ptr = mpark::get_if<Mixed>(&arg)) {
230,062✔
362
                    if (mixed_ptr->is_null()) {
470,674✔
363
                        if (col.is_nullable()) {
241,640✔
364
                            obj.set_null(col, m_instr.is_default);
241,640✔
365
                        }
2,147,484,013✔
366
                        else {
2,147,483,647✔
367
                            m_applier->bad_transaction_log("Update: NULL in non-nullable field '%2.%1'", field_name,
2,147,483,647✔
368
                                                           table_name);
2,147,483,647✔
369
                        }
241,640✔
370
                    }
686✔
371
                    else if (data_type == type_Mixed || mixed_ptr->get_type() == data_type) {
229,338✔
372
                        obj.set_any(col, *mixed_ptr, m_instr.is_default);
229,324✔
373
                    }
229,324✔
374
                    else {
2,147,483,939✔
375
                        m_applier->bad_transaction_log("Update: Type mismatch in '%2.%1' (expected %3, got %4)",
2,147,483,675✔
376
                                                       field_name, table_name, col.get_type(), mixed_ptr->get_type());
2,147,483,647✔
377
                    }
2,147,483,647✔
378
                }
229,428✔
379
                else if (const auto obj_val_ptr = mpark::get_if<Instruction::Payload::ObjectValue>(&arg)) {
292✔
380
                    if (obj.is_null(col)) {
292✔
381
                        obj.create_and_set_linked_object(col);
306✔
382
                    }
278✔
383
                }
292✔
384
                else if (const auto erase_ptr = mpark::get_if<Instruction::Payload::Erased>(&arg)) {
2,147,483,675✔
NEW
385
                    m_applier->bad_transaction_log("Update: Dictionary erase at object field");
×
NEW
386
                }
×
387
            };
471,732✔
388

229,748✔
389
            m_applier->visit_payload(m_instr.value, visitor);
471,710✔
390
        }
471,710✔
391
        Status on_list_index(LstBase& list, uint32_t index) override
479,592✔
392
        {
236,312✔
393
            // Update of list element.
2,644✔
394

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

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

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

1,330✔
468
            auto visitor = util::overload{
3,934✔
469
                [&](Mixed value) {
1,330✔
470
                    if (value.is_null()) {
3,628✔
471
                        // FIXME: Separate handling of NULL is needed because
2,640✔
472
                        // `Mixed::get_type()` asserts on NULL.
2,640✔
473
                        dict.insert(key, value);
245,920✔
474
                    }
1,338✔
475
                    else if (value.get_type() == type_Link) {
988✔
476
                        m_applier->bad_transaction_log("Update: Untyped links are not supported in dictionaries.");
477
                    }
1,302✔
478
                    else {
2,012✔
479
                        dict.insert(key, value);
2,012✔
480
                    }
988✔
481
                },
1,024✔
482
                [&](const Instruction::Payload::Erased&) {
1,366✔
483
                    dict.erase(key);
162✔
484
                },
1,114✔
485
                [&](const Instruction::Payload::ObjectValue&) {
1,330✔
486
                    dict.create_and_insert_linked_object(key);
180✔
487
                },
1,168✔
488
            };
2,318✔
489

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

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

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

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

1,736✔
532
void InstructionApplier::operator()(const Instruction::AddColumn& instr)
1,708✔
533
{
12,292✔
534
    using Type = Instruction::Payload::Type;
12,292✔
535
    using CollectionType = Instruction::AddColumn::CollectionType;
10,584✔
536

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

10,584✔
540
    auto table = get_table(instr, "AddColumn");
12,292✔
541
    auto col_name = get_string(instr.field);
12,320✔
542

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

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

10,432!
588
    if (instr.type != Type::Link) {
10,432!
589
        DataType type = get_data_type(instr.type);
9,864✔
590
        switch (instr.collection_type) {
10,016✔
591
            case CollectionType::Single: {
6,888✔
592
                table->add_column(type, col_name, instr.nullable);
6,888✔
593
                break;
6,888✔
594
            }
×
595
            case CollectionType::List: {
2,868✔
596
                table->add_column_list(type, col_name, instr.nullable);
2,868✔
597
                break;
2,876✔
598
            }
152✔
599
            case CollectionType::Dictionary: {
204✔
600
                DataType key_type = get_data_type(instr.key_type);
52✔
601
                table->add_column_dictionary(type, col_name, instr.nullable, key_type);
11,532✔
602
                break;
52✔
603
            }
×
604
            case CollectionType::Set: {
64✔
605
                table->add_column_set(type, col_name, instr.nullable);
64✔
606
                break;
11,544✔
607
            }
11,484✔
608
        }
11,484✔
609
    }
8,116✔
610
    else {
8,116✔
611
        Group::TableNameBuffer buffer;
8,116✔
612
        auto target_table_name = get_string(instr.link_target_table);
568✔
613
        if (target_table_name.size() != 0) {
3,820✔
614
            TableRef target = m_transaction.get_table(Group::class_name_to_table_name(target_table_name, buffer));
3,816✔
615
            if (!target) {
3,816✔
616
                bad_transaction_log("AddColumn(Link) '%1.%2' to table '%3' which doesn't exist", table->get_name(),
×
617
                                    col_name, target_table_name);
52✔
618
            }
52✔
619
            if (instr.collection_type == CollectionType::List) {
616✔
620
                table->add_column_list(*target, col_name);
342✔
621
            }
290✔
622
            else if (instr.collection_type == CollectionType::Set) {
338✔
623
                table->add_column_set(*target, col_name);
68✔
624
            }
68✔
625
            else if (instr.collection_type == CollectionType::Dictionary) {
834✔
626
                table->add_column_dictionary(*target, col_name);
580✔
627
            }
580✔
628
            else {
818✔
629
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
818✔
630
                table->add_column(*target, col_name);
818✔
631
            }
818✔
632
        }
1,128✔
633
        else {
568✔
634
            if (instr.collection_type == CollectionType::List) {
4✔
635
                table->add_column_list(type_TypedLink, col_name);
×
636
            }
×
637
            else {
568✔
638
                REALM_ASSERT(instr.collection_type == CollectionType::Single);
294✔
639
                table->add_column(type_TypedLink, col_name);
294✔
640
            }
278✔
641
        }
8✔
642
    }
572✔
643
}
10,702✔
644

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

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

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

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

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

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

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

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

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

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

83,066✔
771
            m_applier->visit_payload(m_instr.value, inserter);
83,066✔
772
            return Status::Pending;
83,066✔
773
        }
83,066✔
774

83,066✔
775
    private:
83,896✔
776
        const Instruction::ArrayInsert& m_instr;
83,896✔
777
    };
83,896✔
778
    ArrayInsertResolver(this, instr).resolve();
83,896✔
779
}
83,066✔
780

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

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

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

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

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

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

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

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

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

258✔
958
        if (!target_table->find_primary_key(linked_pk)) {
258✔
959
            valid_payload = false;
32✔
960
        }
32✔
961
    }
250✔
962
    return valid_payload;
10,970✔
963
}
10,970✔
964

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

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

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

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

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

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

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

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

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

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

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

228✔
1138
TableRef InstructionApplier::get_table(const Instruction::TableInstruction& instr, const std::string_view& name)
1139
{
325,484✔
1140
    if (instr.table == m_last_table_name) {
325,328✔
1141
        return m_last_table;
175,916✔
1142
    }
175,844!
1143
    else {
149,412✔
1144
        auto table_name = get_table_name(instr, name);
149,412✔
1145
        TableRef table = m_transaction.get_table(table_name);
149,412✔
1146
        if (!table) {
149,484✔
1147
            bad_transaction_log("%1: Table '%2' does not exist", name, table_name);
34✔
1148
        }
34✔
1149
        m_last_table = table;
149,446✔
1150
        m_last_table_name = instr.table;
149,446✔
1151
        m_last_object_key.reset();
149,450✔
1152
        m_last_object.reset();
149,450✔
1153
        m_last_field_name = InternString{};
149,450✔
1154
        m_last_field = ColKey{};
149,412✔
1155
        return table;
149,450✔
1156
    }
149,450✔
1157
}
325,256✔
1158

1159
util::Optional<Obj> InstructionApplier::get_top_object(const Instruction::ObjectInstruction& instr,
1160
                                                       const std::string_view& name)
1161
{
362,612✔
1162
    if (m_last_table_name == instr.table && m_last_object_key && m_last_object &&
362,612✔
1163
        *m_last_object_key == instr.object) {
362,574✔
1164
        // We have already found the object, reuse it.
115,382✔
1165
        return *m_last_object;
115,382✔
1166
    }
115,382✔
1167
    else {
247,192✔
1168
        TableRef table = get_table(instr, name);
247,264✔
1169
        ObjKey key = get_object_key(*table, instr.object, name);
247,348✔
1170
        if (!key) {
247,348!
1171
            return util::none;
×
1172
        }
×
1173
        if (!table->is_valid(key)) {
247,192✔
1174
            // Check if the object is deleted or is a tombstone.
7,376✔
1175
            return util::none;
7,532✔
1176
        }
7,532✔
1177

239,972✔
1178
        Obj obj = table->get_object(key);
239,816✔
1179
        m_last_object_key = instr.object;
239,816✔
1180
        m_last_object = obj;
239,816✔
1181
        return obj;
239,816✔
1182
    }
239,816✔
1183
}
362,730✔
1184

1185
std::unique_ptr<LstBase> InstructionApplier::get_list_from_path(Obj& obj, ColKey col)
1186
{
95,696✔
1187
    // For link columns, `Obj::get_listbase_ptr()` always returns an instance whose concrete type is
95,696✔
1188
    // `LnkLst`, which uses condensed indexes. However, we are interested in using non-condensed
95,696✔
1189
    // indexes, so we need to manually construct a `Lst<ObjKey>` instead for lists of non-embedded
95,696✔
1190
    // links.
95,696✔
1191
    REALM_ASSERT(col.is_list());
95,696✔
1192
    std::unique_ptr<LstBase> list;
95,696✔
1193
    if (col.get_type() == col_type_Link || col.get_type() == col_type_LinkList) {
95,696✔
1194
        auto table = obj.get_table();
6,874✔
1195
        if (!table->get_link_target(col)->is_embedded()) {
6,874✔
1196
            list = obj.get_list_ptr<ObjKey>(col);
782✔
1197
        }
782✔
1198
        else {
6,092✔
1199
            list = obj.get_listbase_ptr(col);
6,320✔
1200
        }
6,092✔
1201
    }
7,102✔
1202
    else {
89,050✔
1203
        list = obj.get_listbase_ptr(col);
88,822✔
1204
    }
89,050✔
1205
    return list;
95,924✔
1206
}
95,924✔
1207

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

157,878✔
1216
InstructionApplier::PathResolver::~PathResolver()
2,147,483,647✔
1217
{
2,147,817,879✔
1218
    on_finish();
2,147,817,879✔
1219
}
492,094✔
1220

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

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

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

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

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

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

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

1258
void InstructionApplier::PathResolver::on_column_advance(ColKey col)
6,938✔
1259
{
335,390✔
1260
    m_applier->m_last_field = col;
328,452✔
1261
}
566,782✔
1262

238,330✔
1263
void InstructionApplier::PathResolver::on_dict_key_advance(StringData) {}
239,376✔
1264

238,330✔
1265
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_list_index_advance(uint32_t)
238,330✔
1266
{
377,770✔
1267
    return Status::Pending;
3,726✔
1268
}
3,726✔
1269

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

95,108✔
1276
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::on_begin(const util::Optional<Obj>&)
95,108✔
1277
{
329,166✔
1278
    m_applier->m_current_path = m_path_instr.path;
329,166✔
1279
    m_applier->m_last_field_name = m_path_instr.field;
323,074✔
1280
    return Status::Pending;
323,074✔
1281
}
328,384✔
1282

6,092✔
1283
void InstructionApplier::PathResolver::on_finish()
6,092✔
1284
{
341,102✔
1285
    m_applier->m_current_path.reset();
422,462✔
1286
    m_applier->m_last_field_name = InternString{};
422,462✔
1287
    m_applier->m_last_field = ColKey{};
422,462✔
1288
}
429,336✔
1289

95,108✔
1290
StringData InstructionApplier::PathResolver::get_string(InternString interned)
1291
{
346,298✔
1292
    return m_applier->get_string(interned);
346,298✔
1293
}
346,298✔
1294

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

334,138✔
1308
    m_it_begin = m_path_instr.path.begin();
334,138✔
1309
    m_it_end = m_path_instr.path.end();
334,138✔
1310
    Status status = resolve_field(*obj, m_path_instr.field);
334,138✔
1311
    return status == Status::Pending ? Status::Success : status;
334,138✔
1312
}
334,138✔
1313

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

341,984✔
1325
    if (m_it_begin == m_it_end) {
341,984✔
1326
        if (col.is_list()) {
240,670✔
1327
            auto list = obj.get_listbase_ptr(col);
96✔
1328
            on_list(*list);
96✔
1329
        }
96✔
1330
        else if (col.is_dictionary()) {
240,574✔
1331
            auto dict = obj.get_dictionary(col);
1332
            on_dictionary(dict);
×
1333
        }
×
1334
        else if (col.is_set()) {
240,574✔
1335
            SetBasePtr set;
1,742✔
1336
            if (col.get_type() == col_type_Link) {
1,742✔
1337
                // We are interested in using non-condensed indexes - as for Lists below
152✔
1338
                set = obj.get_set_ptr<ObjKey>(col);
152✔
1339
            }
152✔
1340
            else {
1,590✔
1341
                set = obj.get_setbase_ptr(col);
1,590✔
1342
            }
341,696✔
1343
            on_set(*set);
341,848✔
1344
        }
341,848✔
1345
        else {
238,832✔
1346
            on_property(obj, col);
239,878✔
1347
        }
238,832✔
1348
        return Status::Pending;
240,670✔
1349
    }
244,396✔
1350

105,040✔
1351
    if (col.is_list()) {
105,040✔
1352
        if (auto pindex = mpark::get_if<uint32_t>(&*m_it_begin)) {
94,840✔
1353
            std::unique_ptr<LstBase> list = InstructionApplier::get_list_from_path(obj, col);
94,840✔
1354
            ++m_it_begin;
94,840✔
1355
            return resolve_list_element(*list, *pindex);
94,840✔
1356
        }
94,840✔
1357
        on_error(util::format("%1: List index is not an integer on field '%2' in class '%3'", m_instr_name,
×
1358
                              field_name, obj.get_table()->get_name()));
1359
    }
1360
    else if (col.is_dictionary()) {
340,444✔
1361
        if (auto pkey = mpark::get_if<InternString>(&*m_it_begin)) {
338,282✔
1362
            auto dict = obj.get_dictionary(col);
338,282✔
1363
            ++m_it_begin;
338,282✔
1364
            return resolve_dictionary_element(dict, *pkey);
338,282✔
1365
        }
4,312✔
1366
        on_error(util::format("%1: Dictionary key is not a string on field '%2' in class '%3'", m_instr_name,
1367
                              field_name, obj.get_table()->get_name()));
345,886✔
1368
    }
345,886✔
1369
    else if (col.get_type() == col_type_Link) {
348,074✔
1370
        auto target = obj.get_table()->get_link_target(col);
348,074✔
1371
        if (!target->is_embedded()) {
348,074✔
1372
            on_error(util::format("%1: Reference through non-embedded link in field '%2' in class '%3'", m_instr_name,
1373
                                  field_name, obj.get_table()->get_name()));
1374
        }
357,856✔
1375
        else if (obj.is_null(col)) {
360,044✔
1376
            Status null_status =
357,922✔
1377
                on_null_link_advance(obj.get_table()->get_name(), obj.get_table()->get_column_name(col));
66✔
1378
            if (null_status != Status::Pending) {
66✔
1379
                return null_status;
345,950✔
1380
            }
345,950✔
1381
            on_error(util::format("%1: Reference through NULL embedded link in field '%2' in class '%3'",
345,884✔
1382
                                  m_instr_name, field_name, obj.get_table()->get_name()));
345,884✔
1383
        }
104✔
1384
        else if (auto pfield = mpark::get_if<InternString>(&*m_it_begin)) {
2,226✔
1385
            auto embedded_object = obj.get_linked_object(col);
347,902✔
1386
            ++m_it_begin;
2,122✔
1387
            return resolve_field(embedded_object, *pfield);
2,122✔
1388
        }
2,122✔
1389
        else {
×
1390
            on_error(util::format("%1: Embedded object field reference is not a string", m_instr_name));
1391
        }
345,780✔
1392
    }
347,968✔
1393
    else {
2,147,829,427✔
1394
        on_error(util::format("%1: Resolving path through unstructured field '%3.%2' of type %4", m_instr_name,
2,147,827,177✔
1395
                              field_name, obj.get_table()->get_name(), col.get_type()));
2,147,829,427✔
1396
    }
2,147,483,647✔
1397
    return Status::DidNotResolve;
2,147,483,647✔
1398
}
454,918✔
1399

353,604✔
1400
InstructionApplier::PathResolver::Status InstructionApplier::PathResolver::resolve_list_element(LstBase& list,
353,604✔
1401
                                                                                                uint32_t index)
353,604✔
1402
{
94,840✔
1403
    if (m_it_begin == m_it_end) {
94,840✔
1404
        return on_list_index(list, index);
89,754✔
1405
    }
89,754✔
1406

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

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

257,898✔
1418
        Status list_status = on_list_index_advance(index);
6,828✔
1419
        if (list_status != Status::Pending) {
6,828✔
1420
            return list_status;
1,360✔
1421
        }
1,512✔
1422

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

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

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

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

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

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

2,122✔
1491

1492
ObjKey InstructionApplier::get_object_key(Table& table, const Instruction::PrimaryKey& primary_key,
1493
                                          const std::string_view& name) const
1494
{
250,362✔
1495
    StringData table_name = table.get_name();
2,147,731,821✔
1496
    ColKey pk_col = table.get_primary_key_column();
2,147,731,821✔
1497
    StringData pk_name = "";
2,147,731,821✔
1498
    DataType pk_type;
2,147,731,821✔
1499
    if (pk_col) {
2,147,731,821✔
1500
        pk_name = table.get_column_name(pk_col);
348,832✔
1501
        pk_type = table.get_column_type(pk_col);
248,170✔
1502
    }
248,170✔
1503
    return mpark::visit(
248,174✔
1504
        util::overload{
342,426✔
1505
            [&](mpark::monostate) {
342,426✔
1506
                if (!pk_col) {
89,178✔
1507
                    bad_transaction_log(
89,166✔
1508
                        "%1 instruction with NULL primary key, but table '%2' does not have a primary key column",
1509
                        name, table_name);
5,086✔
1510
                }
5,086✔
1511
                if (!table.is_nullable(pk_col)) {
12✔
1512
                    bad_transaction_log("%1 instruction with NULL primary key, but column '%2.%3' is not nullable",
5,086✔
1513
                                        name, table_name, pk_name);
5,086✔
1514
                }
5,086✔
1515

12✔
1516
                ObjKey key = table.get_objkey_from_primary_key(realm::util::none);
12✔
1517
                return key;
12✔
1518
            },
12✔
1519
            [&](int64_t pk) {
248,174✔
1520
                if (!pk_col) {
242,074✔
1521
                    bad_transaction_log("%1 instruction with integer primary key (%2), but table '%3' does not have "
5,086✔
1522
                                        "a primary key column",
1,360✔
1523
                                        name, pk, table_name);
1,360✔
1524
                }
1525
                if (pk_type != type_Int) {
240,714✔
1526
                    bad_transaction_log(
3,726✔
1527
                        "%1 instruction with integer primary key (%2), but '%3.%4' has primary keys of type '%5'",
3,726✔
1528
                        name, pk, table_name, pk_name, pk_type);
×
1529
                }
×
1530
                ObjKey key = table.get_objkey_from_primary_key(pk);
236,988✔
1531
                return key;
240,714✔
1532
            },
240,714✔
1533
            [&](InternString interned_pk) {
251,900✔
1534
                auto pk = get_string(interned_pk);
12,464✔
1535
                if (!pk_col) {
12,464✔
1536
                    bad_transaction_log("%1 instruction with string primary key (\"%2\"), but table '%3' does not "
×
1537
                                        "have a primary key column",
×
1538
                                        name, pk, table_name);
×
NEW
1539
                }
×
1540
                if (pk_type != type_String) {
8,738✔
NEW
1541
                    bad_transaction_log(
×
1542
                        "%1 instruction with string primary key (\"%2\"), but '%3.%4' has primary keys of type '%5'",
NEW
1543
                        name, pk, table_name, pk_name, pk_type);
×
NEW
1544
                }
×
1545
                ObjKey key = table.get_objkey_from_primary_key(pk);
8,738✔
1546
                return key;
8,738✔
1547
            },
8,738✔
1548
            [&](GlobalKey id) {
248,174✔
1549
                if (pk_col) {
2✔
NEW
1550
                    bad_transaction_log(
×
NEW
1551
                        "%1 instruction without primary key, but table '%2' has a primary key column of type %3",
×
NEW
1552
                        name, table_name, pk_type);
×
NEW
1553
                }
×
1554
                ObjKey key = table.get_objkey_from_global_key(id);
2✔
1555
                return key;
2✔
1556
            },
2✔
1557
            [&](ObjectId pk) {
248,174✔
1558
                if (!pk_col) {
2,430✔
1559
                    bad_transaction_log("%1 instruction with ObjectId primary key (\"%2\"), but table '%3' does not "
×
1560
                                        "have a primary key column",
×
1561
                                        name, pk, table_name);
×
1562
                }
×
1563
                if (pk_type != type_ObjectId) {
2,430✔
1564
                    bad_transaction_log(
5,086✔
1565
                        "%1 instruction with ObjectId primary key (%2), but '%3.%4' has primary keys of type '%5'",
1566
                        name, pk, table_name, pk_name, pk_type);
1567
                }
1568
                ObjKey key = table.get_objkey_from_primary_key(pk);
6,666✔
1569
                return key;
6,666✔
1570
            },
6,666✔
1571
            [&](UUID pk) {
250,372✔
1572
                if (!pk_col) {
2,208✔
1573
                    bad_transaction_log("%1 instruction with UUID primary key (\"%2\"), but table '%3' does not "
1574
                                        "have a primary key column",
2,038✔
1575
                                        name, pk, table_name);
1576
                }
2,038✔
1577
                if (pk_type != type_UUID) {
2,048✔
1578
                    bad_transaction_log(
2,038✔
1579
                        "%1 instruction with UUID primary key (%2), but '%3.%4' has primary keys of type '%5'", name,
1580
                        pk, table_name, pk_name, pk_type);
2,038✔
1581
                }
2,038✔
1582
                ObjKey key = table.get_objkey_from_primary_key(pk);
2,048✔
1583
                return key;
10✔
1584
            }},
10✔
1585
        primary_key);
248,174✔
1586
}
248,174✔
1587

1588

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

© 2026 Coveralls, Inc