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

realm / realm-core / thomas.goyne_120

20 Nov 2023 07:46PM UTC coverage: 92.066% (+0.4%) from 91.699%
thomas.goyne_120

push

Evergreen

web-flow
Fix client reset cycle detection for PBS recovery errors (#7149)

Tracking that a client reset was in progress was done in the same write
transaction as the recovery operation, so if recovery failed the tracking was
rolled back too. This worked for FLX due to that codepath committing before
beginning recovery.

85328 of 169122 branches covered (0.0%)

30 of 30 new or added lines in 3 files covered. (100.0%)

169 existing lines in 18 files now uncovered.

233147 of 253239 relevant lines covered (92.07%)

5776222.82 hits per line

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

54.74
/test/util/compare_groups.cpp
1
#include "compare_groups.hpp"
2

3
#include <algorithm>
4
#include <vector>
5
#include <set>
6
#include <sstream>
7
#include <iostream>
8

9
#include <realm/table.hpp>
10
#include <realm/sync/object_id.hpp>
11
#include <realm/list.hpp>
12
#include <realm/dictionary.hpp>
13
#include <realm/set.hpp>
14

15
using namespace realm;
16

17
namespace {
18

19

20
class TableCompareLogger : public util::Logger {
21
public:
22
    TableCompareLogger(StringData table_name, util::Logger& base_logger) noexcept
23
        : util::Logger(base_logger.get_level_threshold())
24
        , m_table_name{table_name}
25
        , m_base_logger{base_logger}
26
    {
2,256✔
27
    }
2,256✔
28
    void do_log(Level level, const std::string& message) override final
29
    {
×
30
        ensure_prefix();                                          // Throws
×
31
        Logger::do_log(m_base_logger, level, m_prefix + message); // Throws
×
32
    }
×
33

34
private:
35
    const StringData m_table_name;
36
    util::Logger& m_base_logger;
37
    std::string m_prefix;
38
    void ensure_prefix()
39
    {
×
40
        if (REALM_LIKELY(!m_prefix.empty()))
×
41
            return;
×
42
        std::ostringstream out;
×
43
        out << "Table[" << m_table_name << "]: "; // Throws
×
44
        m_prefix = out.str();                     // Throws
×
45
    }
×
46
};
47

48

49
class ObjectCompareLogger : public util::Logger {
50
public:
51
    ObjectCompareLogger(sync::PrimaryKey oid, util::Logger& base_logger) noexcept
52
        : util::Logger(base_logger.get_level_threshold())
53
        , m_oid{oid}
54
        , m_base_logger{base_logger}
55
    {
9,658✔
56
    }
9,658✔
57
    void do_log(Level level, const std::string& message) override final
58
    {
×
59
        ensure_prefix();                                          // Throws
×
60
        Logger::do_log(m_base_logger, level, m_prefix + message); // Throws
×
61
    }
×
62

63
private:
64
    const sync::PrimaryKey m_oid;
65
    util::Logger& m_base_logger;
66
    std::string m_prefix;
67
    void ensure_prefix()
68
    {
×
69
        if (REALM_LIKELY(!m_prefix.empty()))
×
70
            return;
×
71
        std::ostringstream out;
×
72
        out << sync::format_pk(m_oid) << ": "; // Throws
×
73
        m_prefix = out.str();                  // Throws
×
74
    }
×
75
};
76

77

78
template <class T, class Cmp = std::equal_to<>>
79
bool compare_arrays(T& a, T& b, Cmp equals = Cmp{})
80
{
4,318✔
81
    auto a_it = a.begin();
4,318✔
82
    auto b_it = b.begin();
4,318✔
83
    if (a.size() != b.size()) {
4,318!
84
#if REALM_DEBUG
×
85
        std::cerr << "LEFT size: " << a.size() << std::endl;
×
86
        std::cerr << "RIGHT size: " << b.size() << std::endl;
×
87
#endif // REALM_DEBUG
×
88
        return false;
×
89
    }
×
90

4,318✔
91
    // Compare entries
4,318✔
92
    for (; a_it != a.end(); ++a_it, ++b_it) {
7,557!
93
        if (!equals(*a_it, *b_it))
3,239!
94
            goto different;
×
95
    }
3,239✔
96

4,318✔
97
    return true;
4,318✔
98
different:
×
99
#if REALM_DEBUG
×
100
    std::cerr << "LEFT: " << *a_it << std::endl;
×
101
    std::cerr << "RIGHT: " << *b_it << std::endl;
×
102
#endif // REALM_DEBUG
×
103
    return false;
×
104
}
4,318✔
105

106
template <class T>
107
bool compare_set_values(const Set<T>& a, const Set<T>& b)
108
{
11✔
109
    return compare_arrays(a, b, SetElementEquals<T>{});
11✔
110
}
11✔
111

112
bool compare_dictionaries(const Dictionary& a, const Dictionary& b)
113
{
17✔
114
    auto a_it = a.begin();
17✔
115
    auto b_it = b.begin();
17✔
116
    if (a.size() != b.size()) {
17!
117
#if REALM_DEBUG
×
118
        std::cerr << "LEFT size: " << a.size() << std::endl;
×
119
        std::cerr << "RIGHT size: " << b.size() << std::endl;
×
120
#endif
×
121
        return false;
×
122
    }
×
123

17✔
124
    // Compare entries
17✔
125
    for (; a_it != a.end(); ++a_it, ++b_it) {
93!
126
        if (*a_it != *b_it)
76!
127
            goto different;
×
128
    }
76✔
129

17✔
130
    return true;
17✔
131

132
different:
×
133
#if REALM_DEBUG
×
134
    std::cerr << "LEFT: " << (*a_it).first << " => " << (*a_it).second << std::endl;
×
135
    std::cerr << "RIGHT: " << (*b_it).first << " => " << (*b_it).second << std::endl;
×
136
#endif
×
137
    return false;
×
138
}
17✔
139

140
struct Column {
141
    StringData name;
142
    ColKey key_1, key_2;
143

144
    DataType get_type() const noexcept
145
    {
25,822✔
146
        return DataType(key_1.get_type());
25,822✔
147
    }
25,822✔
148

149
    bool is_list() const noexcept
150
    {
25,809✔
151
        return key_1.is_list();
25,809✔
152
    }
25,809✔
153

154
    bool is_dictionary() const noexcept
155
    {
25,839✔
156
        return key_1.is_dictionary();
25,839✔
157
    }
25,839✔
158

159
    bool is_set() const noexcept
160
    {
25,822✔
161
        return key_1.is_set();
25,822✔
162
    }
25,822✔
163

164
    bool is_nullable() const noexcept
165
    {
31,694✔
166
        return key_1.is_nullable();
31,694✔
167
    }
31,694✔
168
};
169

170
sync::PrimaryKey primary_key_for_row(const Obj& obj)
171
{
19,392✔
172
    auto table = obj.get_table();
19,392✔
173
    ColKey pk_col = table->get_primary_key_column();
19,392✔
174
    if (pk_col) {
19,392!
175
        ColumnType pk_type = pk_col.get_type();
19,392✔
176
        if (obj.is_null(pk_col)) {
19,392!
177
            return mpark::monostate{};
4✔
178
        }
4✔
179

19,388✔
180
        if (pk_type == col_type_Int) {
19,388!
181
            return obj.get<int64_t>(pk_col);
16,218✔
182
        }
16,218✔
183

3,170✔
184
        if (pk_type == col_type_String) {
3,170!
185
            return obj.get<StringData>(pk_col);
3,162✔
186
        }
3,162✔
187

8✔
188
        if (pk_type == col_type_ObjectId) {
8!
189
            return obj.get<ObjectId>(pk_col);
8✔
190
        }
8✔
191

192
        if (pk_type == col_type_UUID) {
×
193
            return obj.get<UUID>(pk_col);
×
194
        }
×
195

196
        REALM_TERMINATE("Missing primary key type support");
197
    }
×
198

19,392✔
199
    GlobalKey global_key = obj.get_object_id();
19,392✔
UNCOV
200
    return global_key;
×
201
}
19,392✔
202

203
sync::PrimaryKey primary_key_for_row(const Table& table, ObjKey key)
204
{
76✔
205
    auto obj = table.get_object(key);
76✔
206
    return primary_key_for_row(obj);
76✔
207
}
76✔
208

209
ObjKey row_for_primary_key(const Table& table, sync::PrimaryKey key)
210
{
19,316✔
211
    ColKey pk_col = table.get_primary_key_column();
19,316✔
212
    if (pk_col) {
19,316!
213
        ColumnType pk_type = pk_col.get_type();
19,316✔
214

19,316✔
215
        if (auto pk = mpark::get_if<mpark::monostate>(&key)) {
19,316!
216
            static_cast<void>(pk);
4✔
217
            if (!pk_col.is_nullable()) {
4!
218
                REALM_TERMINATE("row_for_primary_key with null on non-nullable primary key column");
219
            }
×
220
            return table.find_primary_key({});
4✔
221
        }
4✔
222

19,312✔
223
        if (pk_type == col_type_Int) {
19,312!
224
            if (auto pk = mpark::get_if<int64_t>(&key)) {
16,142!
225
                return table.find_primary_key(*pk);
16,142✔
226
            }
16,142✔
227
            else {
×
228
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected int)");
229
            }
×
230
        }
16,142✔
231

19,312✔
232
        if (pk_type == col_type_String) {
19,312!
233
            if (auto pk = mpark::get_if<StringData>(&key)) {
3,162!
234
                return table.find_primary_key(*pk);
3,162✔
235
            }
3,162✔
236
            else {
×
237
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected string)");
238
            }
×
239
        }
3,162✔
240

3,170✔
241
        if (pk_type == col_type_ObjectId) {
3,170!
242
            if (auto pk = mpark::get_if<ObjectId>(&key)) {
8!
243
                return table.find_primary_key(*pk);
8✔
244
            }
8✔
245
            else {
×
246
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected ObjectId)");
247
            }
×
248
        }
8✔
249

8✔
250
        if (pk_type == col_type_UUID) {
8!
251
            if (auto pk = mpark::get_if<UUID>(&key)) {
×
252
                return table.find_primary_key(*pk);
×
253
            }
×
254
            else {
×
255
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected UUID)");
256
            }
×
257
        }
×
258

259
        REALM_TERMINATE("row_for_primary_key missing primary key type support");
260
    }
×
261

19,316✔
262
    if (auto global_key = mpark::get_if<GlobalKey>(&key)) {
19,316!
263
        return table.get_objkey(*global_key);
×
264
    }
×
UNCOV
265
    else {
×
266
        REALM_TERMINATE("row_for_primary_key() with primary key, expected GlobalKey");
UNCOV
267
    }
×
UNCOV
268
    return {};
×
UNCOV
269
}
×
270

271
bool compare_objects(const Obj& obj_1, const Obj& obj_2, const std::vector<Column>& columns, util::Logger& logger);
272
bool compare_objects(sync::PrimaryKey& oid, const Table& table_1, const Table& table_2,
273
                     const std::vector<Column>& columns, util::Logger& logger);
274

275
bool compare_schemas(const Table& table_1, const Table& table_2, util::Logger& logger,
276
                     std::vector<Column>* out_columns = nullptr)
277
{
2,600✔
278
    bool equal = true;
2,600✔
279

2,600✔
280
    // Compare column names
2,600✔
281
    {
2,600✔
282
        auto col_keys = table_1.get_column_keys();
2,600✔
283
        for (auto key : col_keys) {
6,639!
284
            StringData name = table_1.get_column_name(key);
6,639✔
285
            if (!table_2.get_column_key(name)) {
6,639!
286
                logger.error("Column '%1' not found in right-hand side table", name);
×
287
                equal = false;
×
288
            }
×
289
        }
6,639✔
290
    }
2,600✔
291
    {
2,600✔
292
        auto col_keys = table_2.get_column_keys();
2,600✔
293
        for (auto key : col_keys) {
6,639!
294
            StringData name = table_2.get_column_name(key);
6,639✔
295
            if (!table_1.get_column_key(name)) {
6,639!
296
                logger.error("Column '%1' not found in left-hand side table", name);
×
297
                equal = false;
×
298
            }
×
299
        }
6,639✔
300
    }
2,600✔
301

2,600✔
302
    // Compare column signatures
2,600✔
303
    {
2,600✔
304
        auto keys_1 = table_1.get_column_keys();
2,600✔
305
        for (auto key_1 : keys_1) {
6,639!
306
            StringData name = table_1.get_column_name(key_1);
6,639✔
307
            ColKey key_2 = table_2.get_column_key(name);
6,639✔
308
            if (!key_2)
6,639!
309
                continue;
×
310
            DataType type_1 = table_1.get_column_type(key_1);
6,639✔
311
            DataType type_2 = table_2.get_column_type(key_2);
6,639✔
312
            if (type_1 != type_2) {
6,639!
313
                logger.error("Type mismatch on column '%1'", name);
×
314
                equal = false;
×
315
                continue;
×
316
            }
×
317
            bool nullable_1 = table_1.is_nullable(key_1);
6,639✔
318
            bool nullable_2 = table_2.is_nullable(key_2);
6,639✔
319
            if (nullable_1 != nullable_2) {
6,639!
320
                logger.error("Nullability mismatch on column '%1'", name);
×
321
                equal = false;
×
322
                continue;
×
323
            }
×
324
            bool is_list_1 = table_1.is_list(key_1);
6,639✔
325
            bool is_list_2 = table_2.is_list(key_2);
6,639✔
326
            if (is_list_1 != is_list_2) {
6,639!
327
                logger.error("List type mismatch on column '%1'", name);
×
328
                equal = false;
×
329
                continue;
×
330
            }
×
331
            bool is_dictionary_1 = key_1.is_dictionary();
6,639✔
332
            bool is_dictionary_2 = key_2.is_dictionary();
6,639✔
333
            if (is_dictionary_1 != is_dictionary_2) {
6,639!
334
                logger.error("Dictionary type mismatch on column '%1'", name);
×
335
                equal = false;
×
336
                continue;
×
337
            }
×
338
            bool is_set_1 = key_1.is_set();
6,639✔
339
            bool is_set_2 = key_2.is_set();
6,639✔
340
            if (is_set_1 != is_set_2) {
6,639!
341
                logger.error("Set type mismatch on column '%1'", name);
×
342
                equal = false;
×
343
                continue;
×
344
            }
×
345
            if (type_1 == type_Link || type_1 == type_LinkList) {
6,639!
346
                ConstTableRef target_1 = table_1.get_link_target(key_1);
419✔
347
                ConstTableRef target_2 = table_2.get_link_target(key_2);
419✔
348
                if (target_1->get_name() != target_2->get_name()) {
419!
349
                    logger.error("Link target mismatch on column '%1'", name);
×
350
                    equal = false;
×
351
                    continue;
×
352
                }
×
353
            }
6,639✔
354
            if (out_columns)
6,639!
355
                out_columns->push_back(Column{name, key_1, key_2});
6,639✔
356
        }
6,639✔
357
    }
2,600✔
358

2,600✔
359
    return equal;
2,600✔
360
}
2,600✔
361

362
bool compare_lists(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
363
{
4,653✔
364
    switch (col.get_type()) {
4,653!
365
        case type_Int: {
2,058!
366
            if (col.is_nullable()) {
2,058!
367
                auto a = obj_1.get_list<util::Optional<int64_t>>(col.key_1);
×
368
                auto b = obj_2.get_list<util::Optional<int64_t>>(col.key_2);
×
369
                if (!compare_arrays(a, b)) {
×
370
                    logger.error("List mismatch in column '%1'", col.name);
×
371
                    return false;
×
372
                }
×
373
            }
2,058✔
374
            else {
2,058✔
375
                auto a = obj_1.get_list<int64_t>(col.key_1);
2,058✔
376
                auto b = obj_2.get_list<int64_t>(col.key_2);
2,058✔
377
                if (!compare_arrays(a, b)) {
2,058!
378
                    logger.error("List mismatch in column '%1'", col.name);
×
379
                    return false;
×
380
                }
×
381
            }
2,058✔
382
            break;
2,058✔
383
        }
2,058✔
384
        case type_Bool: {
2,058!
385
            auto a = obj_1.get_list<bool>(col.key_1);
×
386
            auto b = obj_2.get_list<bool>(col.key_2);
×
387
            if (!compare_arrays(a, b)) {
×
388
                logger.error("List mismatch in column '%1'", col.name);
×
389
                return false;
×
390
            }
×
391
            break;
×
392
        }
×
393
        case type_String: {
2,249!
394
            auto a = obj_1.get_list<String>(col.key_1);
2,249✔
395
            auto b = obj_2.get_list<String>(col.key_2);
2,249✔
396
            if (!compare_arrays(a, b)) {
2,249!
397
                logger.error("List mismatch in column '%1'", col.name);
×
398
                return false;
×
399
            }
×
400
            break;
2,249✔
401
        }
2,249✔
402
        case type_Binary: {
2,249!
403
            auto a = obj_1.get_list<Binary>(col.key_1);
×
404
            auto b = obj_2.get_list<Binary>(col.key_2);
×
405
            if (!compare_arrays(a, b)) {
×
406
                logger.error("List mismatch in column '%1'", col.name);
×
407
                return false;
×
408
            }
×
409
            break;
×
410
        }
×
411
        case type_Float: {
×
412
            auto a = obj_1.get_list<float>(col.key_1);
×
413
            auto b = obj_2.get_list<float>(col.key_2);
×
414
            if (!compare_arrays(a, b)) {
×
415
                logger.error("List mismatch in column '%1'", col.name);
×
416
                return false;
×
417
            }
×
418
            break;
×
419
        }
×
420
        case type_Double: {
×
421
            auto a = obj_1.get_list<double>(col.key_1);
×
422
            auto b = obj_2.get_list<double>(col.key_2);
×
423
            if (!compare_arrays(a, b)) {
×
424
                logger.error("List mismatch in column '%1'", col.name);
×
425
                return false;
×
426
            }
×
427
            break;
×
428
        }
×
429
        case type_Timestamp: {
×
430
            auto a = obj_1.get_list<Timestamp>(col.key_1);
×
431
            auto b = obj_2.get_list<Timestamp>(col.key_2);
×
432
            if (!compare_arrays(a, b)) {
×
433
                logger.error("List mismatch in column '%1'", col.name);
×
434
                return false;
×
435
            }
×
436
            break;
×
437
        }
×
438
        case type_ObjectId: {
×
439
            auto a = obj_1.get_list<ObjectId>(col.key_1);
×
440
            auto b = obj_2.get_list<ObjectId>(col.key_2);
×
441
            if (!compare_arrays(a, b)) {
×
442
                logger.error("List mismatch in column '%1'", col.name);
×
443
                return false;
×
444
            }
×
445
            break;
×
446
        }
×
447
        case type_UUID: {
×
448
            auto a = obj_1.get_list<UUID>(col.key_1);
×
449
            auto b = obj_2.get_list<UUID>(col.key_2);
×
450
            if (!compare_arrays(a, b)) {
×
451
                logger.error("List mismatch in column '%1'", col.name);
×
452
                return false;
×
453
            }
×
454
            break;
×
455
        }
×
456
        case type_Decimal: {
×
457
            auto a = obj_1.get_list<Decimal128>(col.key_1);
×
458
            auto b = obj_2.get_list<Decimal128>(col.key_2);
×
459
            if (!compare_arrays(a, b)) {
×
460
                logger.error("List mismatch in column '%1'", col.name);
×
461
                return false;
×
462
            }
×
463
            break;
×
464
        }
×
465
        case type_Mixed: {
×
466
            auto a = obj_1.get_list<Mixed>(col.key_1);
×
467
            auto b = obj_2.get_list<Mixed>(col.key_2);
×
468
            if (!compare_arrays(a, b)) {
×
469
                logger.error("List mismatch in column '%1'", col.name);
×
470
                return false;
×
471
            }
×
472
            break;
×
473
        }
×
474
        case type_TypedLink:
×
475
            // FIXME: Implement
476
            break;
×
477
        case type_LinkList: {
346!
478
            auto a = obj_1.get_list<ObjKey>(col.key_1);
346✔
479
            auto b = obj_2.get_list<ObjKey>(col.key_2);
346✔
480
            if (a.size() != b.size()) {
346!
481
                logger.error("Link list size mismatch in column '%1'", col.name);
×
482
                return false;
×
483
                break;
×
484
            }
346✔
485
            auto table_1 = obj_1.get_table();
346✔
486
            auto table_2 = obj_2.get_table();
346✔
487
            ConstTableRef target_table_1 = table_1->get_link_target(col.key_1);
346✔
488
            ConstTableRef target_table_2 = table_2->get_link_target(col.key_2);
346✔
489

346✔
490
            bool is_embedded = target_table_1->is_embedded();
346✔
491
            std::vector<Column> embedded_columns;
346✔
492
            if (is_embedded) {
346!
493
                // FIXME: This does the schema comparison for
320✔
494
                // embedded tables for every object with embedded
320✔
495
                // objects, just because we want to get the Column
320✔
496
                // info. Instead compare just the objects
320✔
497
                // themselves.
320✔
498
                bool schemas_equal = compare_schemas(*target_table_1, *target_table_2, logger, &embedded_columns);
320✔
499
                REALM_ASSERT(schemas_equal);
320!
500
            }
320✔
501

346✔
502
            std::size_t n = a.size();
346✔
503
            for (std::size_t i = 0; i < n; ++i) {
724!
504
                ObjKey link_1 = a.get(i);
378✔
505
                ObjKey link_2 = b.get(i);
378✔
506

378✔
507
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
378!
508
                    // if one link is unresolved, the other should also be unresolved
11✔
509
                    if (!link_1.is_unresolved() || !link_2.is_unresolved()) {
11!
510
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
511
                                     "list (%3 vs %4)",
×
512
                                     col.name, i, link_1, link_2);
×
513
                        return false;
×
514
                    }
×
515
                }
367✔
516
                else {
367✔
517
                    if (is_embedded) {
367!
518
                        const Obj embedded_1 = target_table_1->get_object(link_1);
337✔
519
                        const Obj embedded_2 = target_table_2->get_object(link_2);
337✔
520
                        // Skip ID comparison for embedded objects, because
337✔
521
                        // they are only identified by their position in the
337✔
522
                        // database.
337✔
523
                        if (!compare_objects(embedded_1, embedded_2, embedded_columns, logger)) {
337!
524
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
525
                            return false;
×
526
                        }
×
527
                    }
30✔
528
                    else {
30✔
529
                        sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
30✔
530
                        sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
30✔
531
                        if (target_oid_1 != target_oid_2) {
30!
532
                            logger.error("Value mismatch in column '%1' at index %2 of the link "
×
533
                                         "list (%3 vs %4)",
×
534
                                         col.name, i, link_1, link_2);
×
535
                            return false;
×
536
                        }
×
537
                    }
30✔
538
                }
367✔
539
            }
378✔
540
            break;
346✔
541
        }
346✔
542
        case type_Link:
346!
543
            REALM_TERMINATE("Unsupported column type.");
544
    }
4,653✔
545

4,653✔
546
    return true;
4,653✔
547
}
4,653✔
548

549
bool compare_sets(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
550
{
13✔
551
    switch (col.get_type()) {
13!
552
        case type_Int: {
2!
553
            if (col.is_nullable()) {
2!
554
                auto a = obj_1.get_set<util::Optional<int64_t>>(col.key_1);
×
555
                auto b = obj_2.get_set<util::Optional<int64_t>>(col.key_2);
×
556
                if (!compare_set_values(a, b)) {
×
557
                    logger.error("Set mismatch in column '%1'", col.name);
×
558
                    return false;
×
559
                }
×
560
            }
2✔
561
            else {
2✔
562
                auto a = obj_1.get_set<int64_t>(col.key_1);
2✔
563
                auto b = obj_2.get_set<int64_t>(col.key_2);
2✔
564
                if (!compare_set_values(a, b)) {
2!
565
                    logger.error("Set mismatch in column '%1'", col.name);
×
566
                    return false;
×
567
                }
×
568
            }
2✔
569
            break;
2✔
570
        }
2✔
571
        case type_Bool: {
2!
572
            auto a = obj_1.get_set<bool>(col.key_1);
×
573
            auto b = obj_2.get_set<bool>(col.key_2);
×
574
            if (!compare_set_values(a, b)) {
×
575
                logger.error("Set mismatch in column '%1'", col.name);
×
576
                return false;
×
577
            }
×
578
            break;
×
579
        }
×
580
        case type_String: {
2!
581
            auto a = obj_1.get_set<String>(col.key_1);
2✔
582
            auto b = obj_2.get_set<String>(col.key_2);
2✔
583
            if (!compare_set_values(a, b)) {
2!
584
                logger.error("Set mismatch in column '%1'", col.name);
×
585
                return false;
×
586
            }
×
587
            break;
2✔
588
        }
2✔
589
        case type_Binary: {
2!
590
            auto a = obj_1.get_set<Binary>(col.key_1);
×
591
            auto b = obj_2.get_set<Binary>(col.key_2);
×
592
            if (!compare_set_values(a, b)) {
×
593
                logger.error("Set mismatch in column '%1'", col.name);
×
594
                return false;
×
595
            }
×
596
            break;
×
597
        }
×
598
        case type_Float: {
×
599
            auto a = obj_1.get_set<float>(col.key_1);
×
600
            auto b = obj_2.get_set<float>(col.key_2);
×
601
            if (!compare_set_values(a, b)) {
×
602
                logger.error("Set mismatch in column '%1'", col.name);
×
603
                return false;
×
604
            }
×
605
            break;
×
606
        }
×
607
        case type_Double: {
×
608
            auto a = obj_1.get_set<double>(col.key_1);
×
609
            auto b = obj_2.get_set<double>(col.key_2);
×
610
            if (!compare_set_values(a, b)) {
×
611
                logger.error("Set mismatch in column '%1'", col.name);
×
612
                return false;
×
613
            }
×
614
            break;
×
615
        }
×
616
        case type_Timestamp: {
×
617
            auto a = obj_1.get_set<Timestamp>(col.key_1);
×
618
            auto b = obj_2.get_set<Timestamp>(col.key_2);
×
619
            if (!compare_set_values(a, b)) {
×
620
                logger.error("Set mismatch in column '%1'", col.name);
×
621
                return false;
×
622
            }
×
623
            break;
×
624
        }
×
625
        case type_ObjectId: {
×
626
            auto a = obj_1.get_set<ObjectId>(col.key_1);
×
627
            auto b = obj_2.get_set<ObjectId>(col.key_2);
×
628
            if (!compare_set_values(a, b)) {
×
629
                logger.error("Set mismatch in column '%1'", col.name);
×
630
                return false;
×
631
            }
×
632
            break;
×
633
        }
×
634
        case type_UUID: {
×
635
            auto a = obj_1.get_set<UUID>(col.key_1);
×
636
            auto b = obj_2.get_set<UUID>(col.key_2);
×
637
            if (!compare_set_values(a, b)) {
×
638
                logger.error("Set mismatch in column '%1'", col.name);
×
639
                return false;
×
640
            }
×
641
            break;
×
642
        }
×
643
        case type_Decimal: {
×
644
            auto a = obj_1.get_set<Decimal128>(col.key_1);
×
645
            auto b = obj_2.get_set<Decimal128>(col.key_2);
×
646
            if (!compare_set_values(a, b)) {
×
647
                logger.error("Set mismatch in column '%1'", col.name);
×
648
                return false;
×
649
            }
×
650
            break;
×
651
        }
×
652
        case type_Mixed: {
7!
653
            auto a = obj_1.get_set<Mixed>(col.key_1);
7✔
654
            auto b = obj_2.get_set<Mixed>(col.key_2);
7✔
655
            if (!compare_set_values(a, b)) {
7!
656
                logger.error("Set mismatch in column '%1'", col.name);
×
657
                return false;
×
658
            }
×
659
            break;
7✔
660
        }
7✔
661
        case type_Link: {
7!
662
            auto a = obj_1.get_linkset(col.key_1);
2✔
663
            auto b = obj_2.get_linkset(col.key_2);
2✔
664
            if (a.size() != b.size()) {
2!
665
                logger.error("Link set size mismatch in column '%1'", col.name);
×
666
                return false;
×
667
                break;
×
668
            }
2✔
669

2✔
670
            auto target_table_1 = a.get_target_table();
2✔
671
            auto target_table_2 = b.get_target_table();
2✔
672

2✔
673
            std::size_t n = a.size();
2✔
674
            for (std::size_t i = 0; i < n; ++i) {
4!
675
                ObjKey link_1 = a.get(i);
2✔
676
                ObjKey link_2 = b.get(i);
2✔
677

2✔
678
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
2!
679
                    // if one link is unresolved, the other should also be unresolved
680
                    if (!link_1.is_unresolved() || !link_2.is_unresolved()) {
×
681
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
682
                                     "set (%3 vs %4)",
×
683
                                     col.name, i, link_1, link_2);
×
684
                        return false;
×
685
                    }
×
686
                }
2✔
687
                else {
2✔
688
                    sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
2✔
689
                    sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
2✔
690
                    if (target_oid_1 != target_oid_2) {
2!
691
                        logger.error("Value mismatch in column '%1' at index %2 of the link "
×
692
                                     "set (%3 vs %4)",
×
693
                                     col.name, i, link_1, link_2);
×
694
                        return false;
×
695
                    }
×
696
                }
2✔
697
            }
2✔
698
            break;
2✔
699
        }
2✔
700
        case type_TypedLink:
2!
701
            // FIXME: Implement
702
            break;
×
703
        case type_LinkList:
2!
704
            REALM_TERMINATE("Unsupported column type.");
705
    }
13✔
706

13✔
707
    return true;
13✔
708
}
13✔
709

710
bool compare_objects(const Obj& obj_1, const Obj& obj_2, const std::vector<Column>& columns, util::Logger& logger)
711
{
10,019✔
712
    bool equal = true;
10,019✔
713
    auto ptable_1 = obj_1.get_table();
10,019✔
714
    auto ptable_2 = obj_2.get_table();
10,019✔
715
    auto& table_1 = *ptable_1;
10,019✔
716
    auto& table_2 = *ptable_2;
10,019✔
717

10,019✔
718
    for (const Column& col : columns) {
29,634!
719
        if (col.is_nullable()) {
29,634!
720
            bool a = obj_1.is_null(col.key_1);
4,331✔
721
            bool b = obj_2.is_null(col.key_2);
4,331✔
722
            if (a && b)
4,331!
723
                continue;
3,795✔
724
            if (a || b) {
536!
725
                logger.error("Null/nonnull disagreement in column '%1' (%2 vs %3)", col.name, a, b);
×
726
                equal = false;
×
727
                continue;
×
728
            }
×
729
        }
25,839✔
730

25,839✔
731
        if (col.is_dictionary()) {
25,839!
732
            auto a = obj_1.get_dictionary(col.key_1);
17✔
733
            auto b = obj_2.get_dictionary(col.key_2);
17✔
734
            if (!compare_dictionaries(a, b)) {
17!
735
                logger.error("Dictionary mismatch in column '%1'", col.name);
×
736
                equal = false;
×
737
            }
×
738
            continue;
17✔
739
        }
17✔
740

25,822✔
741
        if (col.is_set()) {
25,822!
742
            if (!compare_sets(col, obj_1, obj_2, logger)) {
13!
743
                logger.error("Set mismatch in column '%1'", col.name);
×
744
                equal = false;
×
745
            }
×
746
            continue;
13✔
747
        }
13✔
748

25,809✔
749
        if (col.is_list()) {
25,809!
750
            if (!compare_lists(col, obj_1, obj_2, logger)) {
4,653!
751
                equal = false;
×
752
            }
×
753
            continue;
4,653✔
754
        }
4,653✔
755

21,156✔
756
        const bool nullable = table_1.is_nullable(col.key_1);
21,156✔
757
        REALM_ASSERT(table_2.is_nullable(col.key_2) == nullable);
21,156!
758
        switch (col.get_type()) {
21,156!
759
            case type_Int: {
16,828!
760
                if (nullable) {
16,828!
761
                    auto a = obj_1.get<util::Optional<int64_t>>(col.key_1);
285✔
762
                    auto b = obj_2.get<util::Optional<int64_t>>(col.key_2);
285✔
763
                    if (a != b) {
285!
764
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
765
                        equal = false;
×
766
                    }
×
767
                }
285✔
768
                else {
16,543✔
769
                    auto a = obj_1.get<int64_t>(col.key_1);
16,543✔
770
                    auto b = obj_2.get<int64_t>(col.key_2);
16,543✔
771
                    if (a != b) {
16,543!
772
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
773
                        equal = false;
×
774
                    }
×
775
                }
16,543✔
776
                continue;
16,828✔
777
            }
×
778
            case type_Bool: {
6!
779
                if (nullable) {
6!
780
                    auto a = obj_1.get<util::Optional<bool>>(col.key_1);
×
781
                    auto b = obj_2.get<util::Optional<bool>>(col.key_2);
×
782
                    if (a != b) {
×
783
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
784
                        equal = false;
×
785
                    }
×
786
                }
×
787
                else {
6✔
788
                    auto a = obj_1.get<bool>(col.key_1);
6✔
789
                    auto b = obj_2.get<bool>(col.key_2);
6✔
790
                    if (a != b) {
6!
791
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
792
                        equal = false;
×
793
                    }
×
794
                }
6✔
795

6✔
796
                continue;
6✔
797
            }
×
798
            case type_Float: {
6!
799
                auto a = obj_1.get<float>(col.key_1);
6✔
800
                auto b = obj_2.get<float>(col.key_2);
6✔
801
                if (a != b) {
6!
802
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
803
                    equal = false;
×
804
                }
×
805
                continue;
6✔
806
            }
×
807
            case type_Double: {
8!
808
                auto a = obj_1.get<double>(col.key_1);
8✔
809
                auto b = obj_2.get<double>(col.key_2);
8✔
810
                if (a != b) {
8!
811
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
812
                    equal = false;
×
813
                }
×
814
                continue;
8✔
815
            }
×
816
            case type_String: {
3,845!
817
                auto a = obj_1.get<StringData>(col.key_1);
3,845✔
818
                auto b = obj_2.get<StringData>(col.key_2);
3,845✔
819
                if (a != b) {
3,845!
820
                    logger.error("Value mismatch in column '%1'", col.name);
×
821
                    equal = false;
×
822
                }
×
823
                continue;
3,845✔
824
            }
×
825
            case type_Binary: {
416!
826
                // FIXME: This looks like an incorrect way of comparing BLOBs (Table::get_binary_iterator()).
416✔
827
                auto a = obj_1.get<BinaryData>(col.key_1);
416✔
828
                auto b = obj_2.get<BinaryData>(col.key_2);
416✔
829
                if (a != b) {
416!
830
                    logger.error("Value mismatch in column '%1'", col.name);
×
831
                    equal = false;
×
832
                }
×
833
                continue;
416✔
834
            }
×
835
            case type_Timestamp: {
8!
836
                auto a = obj_1.get<Timestamp>(col.key_1);
8✔
837
                auto b = obj_2.get<Timestamp>(col.key_2);
8✔
838
                if (a != b) {
8!
839
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
840
                    equal = false;
×
841
                }
×
842
                continue;
8✔
843
            }
×
844
            case type_ObjectId: {
4!
845
                auto a = obj_1.get<ObjectId>(col.key_1);
4✔
846
                auto b = obj_2.get<ObjectId>(col.key_2);
4✔
847
                if (a != b) {
4!
848
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
849
                    equal = false;
×
850
                }
×
851
                continue;
4✔
852
            }
×
853
            case type_Decimal: {
4!
854
                auto a = obj_1.get<Decimal128>(col.key_1);
4✔
855
                auto b = obj_2.get<Decimal128>(col.key_2);
4✔
856
                if (a != b) {
4!
857
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
858
                    equal = false;
×
859
                }
×
860
                continue;
4✔
861
            }
×
862
            case type_Mixed: {
1!
863
                auto a = obj_1.get<Mixed>(col.key_1);
1✔
864
                auto b = obj_2.get<Mixed>(col.key_2);
1✔
865
                if (a != b) {
1!
866
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
867
                    equal = false;
×
868
                }
×
869
                continue;
1✔
870
            }
×
871
            case type_UUID: {
×
872
                auto a = obj_1.get<UUID>(col.key_1);
×
873
                auto b = obj_2.get<UUID>(col.key_2);
×
874
                if (a != b) {
×
875
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
876
                    equal = false;
×
877
                }
×
878
                continue;
×
879
            }
×
880
            case type_TypedLink:
×
881
                // FIXME: Implement
882
                continue;
×
883
            case type_Link: {
30!
884
                auto link_1 = obj_1.get<ObjKey>(col.key_1);
30✔
885
                auto link_2 = obj_2.get<ObjKey>(col.key_2);
30✔
886
                ConstTableRef target_table_1 = table_1.get_link_target(col.key_1);
30✔
887
                ConstTableRef target_table_2 = table_2.get_link_target(col.key_2);
30✔
888

30✔
889
                if (!link_1 || !link_2) {
30!
890
                    // If one link is null the other should also be null
891
                    if (link_1 != link_2) {
×
892
                        equal = false;
×
893
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, link_1, link_2);
×
894
                    }
×
895
                }
×
896
                else {
30✔
897
                    bool is_embedded = target_table_1->is_embedded();
30✔
898
                    std::vector<Column> embedded_columns;
30✔
899
                    if (is_embedded) {
30!
900
                        // FIXME: This does the schema comparison for
24✔
901
                        // embedded tables for every object with embedded
24✔
902
                        // objects, just because we want to get the Column
24✔
903
                        // info. Instead compare just the objects
24✔
904
                        // themselves.
24✔
905
                        bool schemas_equal =
24✔
906
                            compare_schemas(*target_table_1, *target_table_2, logger, &embedded_columns);
24✔
907
                        REALM_ASSERT(schemas_equal);
24!
908
                    }
24✔
909

30✔
910
                    if (is_embedded) {
30!
911
                        const Obj embedded_1 = target_table_1->get_object(link_1);
24✔
912
                        const Obj embedded_2 = target_table_2->get_object(link_2);
24✔
913
                        // Skip ID comparison for embedded objects, because
24✔
914
                        // they are only identified by their position in the
24✔
915
                        // database.
24✔
916
                        if (!compare_objects(embedded_1, embedded_2, embedded_columns, logger)) {
24!
917
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
918
                            equal = false;
×
919
                        }
×
920
                    }
24✔
921
                    else {
6✔
922
                        sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
6✔
923
                        sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
6✔
924
                        if (target_oid_1 != target_oid_2) {
6!
925
                            logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name,
×
926
                                         sync::format_pk(target_oid_1), sync::format_pk(target_oid_2));
×
927
                            equal = false;
×
928
                        }
×
929
                    }
6✔
930
                }
30✔
931

30✔
932
                continue;
30✔
933
            }
×
934
            case type_LinkList:
×
935
                break;
×
936
        }
×
937
        REALM_TERMINATE("Unsupported column type.");
938
    }
×
939
    return equal;
10,019✔
940
}
10,019✔
941

942
bool compare_objects(sync::PrimaryKey& oid, const Table& table_1, const Table& table_2,
943
                     const std::vector<Column>& columns, util::Logger& logger)
944
{
9,658✔
945
    ObjKey row_1 = row_for_primary_key(table_1, oid);
9,658✔
946
    ObjKey row_2 = row_for_primary_key(table_2, oid);
9,658✔
947

9,658✔
948
    // Note: This is ensured by the inventory handling in compare_tables().
9,658✔
949
    REALM_ASSERT(row_1);
9,658!
950
    REALM_ASSERT(row_2);
9,658!
951
    const Obj obj_1 = table_1.get_object(row_1);
9,658✔
952
    const Obj obj_2 = table_2.get_object(row_2);
9,658✔
953
    return compare_objects(obj_1, obj_2, columns, logger);
9,658✔
954
}
9,658✔
955

956
} // anonymous namespace
957

958
namespace realm::test_util {
959

960
bool compare_tables(const Table& table_1, const Table& table_2)
961
{
×
962
    util::NullLogger logger;
×
963
    return compare_tables(table_1, table_2, logger);
×
964
}
×
965

966
bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& logger)
967
{
2,256✔
968
    bool equal = true;
2,256✔
969

2,256✔
970
    std::vector<Column> columns;
2,256✔
971
    equal = compare_schemas(table_1, table_2, logger, &columns);
2,256✔
972

2,256✔
973
    if (table_1.is_embedded() != table_2.is_embedded()) {
2,256!
974
        logger.error("Table embeddedness mismatch");
×
975
        equal = false;
×
976
    }
×
977

2,256✔
978
    if (table_1.is_embedded() || table_2.is_embedded()) {
2,256!
979
        if (table_1.size() != table_2.size()) {
45!
980
            logger.error("Embedded table size mismatch (%1 vs %2): %3", table_1.size(), table_2.size(),
×
981
                         table_1.get_name());
×
982
            equal = false;
×
983
        }
×
984
        // Do not attempt to compare by row on embedded tables.
45✔
985
        return equal;
45✔
986
    }
45✔
987

2,211✔
988
    // Compare row sets
2,211✔
989
    using Objects = std::set<sync::PrimaryKey>;
2,211✔
990
    auto make_inventory = [](const Table& table, Objects& objects) {
4,422✔
991
        for (const Obj& obj : table) {
19,316!
992
            auto oid = primary_key_for_row(obj);
19,316✔
993
            objects.insert(oid);
19,316✔
994
        }
19,316✔
995
    };
4,422✔
996
    Objects objects_1, objects_2;
2,211✔
997
    make_inventory(table_1, objects_1);
2,211✔
998
    make_inventory(table_2, objects_2);
2,211✔
999
    auto report_missing = [&](const char* hand_2, Objects& objects_1, Objects& objects_2) {
4,422✔
1000
        std::vector<sync::PrimaryKey> missing;
4,422✔
1001
        for (auto oid : objects_1) {
19,316!
1002
            if (objects_2.find(oid) == objects_2.end())
19,316!
1003
                missing.push_back(oid);
×
1004
        }
19,316✔
1005
        if (missing.empty())
4,422!
1006
            return;
4,422✔
1007
        std::size_t n = missing.size();
×
1008
        if (n == 1) {
×
1009
            logger.error("One object missing in %1 side table: %2", hand_2, sync::format_pk(missing[0]));
×
1010
            equal = false;
×
1011
            return;
×
1012
        }
×
1013
        std::ostringstream out;
×
1014
        out << sync::format_pk(missing[0]);
×
1015
        std::size_t m = std::min<std::size_t>(4, n);
×
1016
        for (std::size_t i = 1; i < m; ++i)
×
1017
            out << ", " << sync::format_pk(missing[i]);
×
1018
        if (m < n)
×
1019
            out << ", ...";
×
1020
        logger.error("%1 objects missing in %2 side table: %3", n, hand_2, out.str());
×
1021
        equal = false;
×
1022
    };
×
1023
    report_missing("right-hand", objects_1, objects_2);
2,211✔
1024
    report_missing("left-hand", objects_2, objects_1);
2,211✔
1025

2,211✔
1026
    // Compare individual rows
2,211✔
1027
    for (auto oid : objects_1) {
9,658!
1028
        if (objects_2.find(oid) != objects_2.end()) {
9,658!
1029
            ObjectCompareLogger sublogger{oid, logger};
9,658✔
1030
            if (!compare_objects(oid, table_1, table_2, columns, sublogger)) {
9,658!
1031
                equal = false;
×
1032
            }
×
1033
        }
9,658✔
1034
    }
9,658✔
1035

2,211✔
1036
    return equal;
2,211✔
1037
}
2,211✔
1038

1039

1040
bool compare_groups(const Transaction& group_1, const Transaction& group_2)
1041
{
467✔
1042
    util::NullLogger logger;
467✔
1043
    return compare_groups(group_1, group_2, logger);
467✔
1044
}
467✔
1045

1046

1047
bool compare_groups(const Transaction& group_1, const Transaction& group_2,
1048
                    util::FunctionRef<bool(StringData)> filter_func, util::Logger& logger)
1049
{
671✔
1050
    auto filter = [&](const Group& group, std::vector<StringData>& tables) {
1,342✔
1051
        auto table_keys = group.get_table_keys();
1,342✔
1052
        for (auto i : table_keys) {
4,523!
1053
            ConstTableRef table = group.get_table(i);
4,523✔
1054
            StringData name = table->get_name();
4,523✔
1055
            if (name != "pk" && name != "metadata" && name != "client_reset_metadata" && filter_func(name))
4,523!
1056
                tables.push_back(name);
4,514✔
1057
        }
4,523✔
1058
    };
1,342✔
1059

671✔
1060
    std::vector<StringData> tables_1, tables_2;
671✔
1061
    filter(group_1, tables_1);
671✔
1062
    filter(group_2, tables_2);
671✔
1063

671✔
1064
    bool equal = true;
671✔
1065
    for (StringData table_name : tables_1) {
2,257!
1066
        if (!group_2.has_table(table_name)) {
2,257!
1067
            logger.error("Table '%1' not found in right-hand side group", table_name);
1✔
1068
            equal = false;
1✔
1069
        }
1✔
1070
    }
2,257✔
1071
    for (StringData table_name : tables_2) {
2,257!
1072
        if (!group_1.has_table(table_name)) {
2,257!
1073
            logger.error("Table '%1' not found in left-hand side group", table_name);
1✔
1074
            equal = false;
1✔
1075
        }
1✔
1076
    }
2,257✔
1077

671✔
1078
    for (StringData table_name : tables_1) {
2,257!
1079
        ConstTableRef table_1 = group_1.get_table(table_name);
2,257✔
1080
        ConstTableRef table_2 = group_2.get_table(table_name);
2,257✔
1081
        if (table_2) {
2,257!
1082
            TableCompareLogger sublogger{table_name, logger};
2,256✔
1083
            if (!compare_tables(*table_1, *table_2, sublogger))
2,256!
1084
                equal = false;
×
1085
        }
2,256✔
1086
    }
2,257✔
1087

671✔
1088
    return equal;
671✔
1089
}
671✔
1090

1091
} // namespace realm::test_util
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