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

realm / realm-core / github_pull_request_281750

30 Oct 2023 03:37PM UTC coverage: 90.528% (-1.0%) from 91.571%
github_pull_request_281750

Pull #6073

Evergreen

jedelbo
Log free space and history sizes when opening file
Pull Request #6073: Merge next-major

95488 of 175952 branches covered (0.0%)

8973 of 12277 new or added lines in 149 files covered. (73.09%)

622 existing lines in 51 files now uncovered.

233503 of 257934 relevant lines covered (90.53%)

6533720.56 hits per line

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

54.44
/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()
24
        , m_table_name{table_name}
25
        , m_base_logger{base_logger}
26
    {
4,498✔
27
        set_level_threshold(base_logger.get_level_threshold());
4,498✔
28
    }
4,498✔
29
    void do_log(const util::LogCategory& category, Level level, const std::string& message) override final
30
    {
×
31
        ensure_prefix();                                          // Throws
×
NEW
32
        Logger::do_log(m_base_logger, category, level, m_prefix + message); // Throws
×
33
    }
×
34

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

49

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

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

79

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

4,054✔
93
    // Compare entries
4,054✔
94
    for (; a_it != a.end(); ++a_it, ++b_it) {
13,847!
95
        if (!equals(*a_it, *b_it))
6,087!
96
            goto different;
×
97
    }
6,087✔
98

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

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

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

17✔
126
    // Compare entries
17✔
127
    for (; a_it != a.end(); ++a_it, ++b_it) {
186✔
128
        if (*a_it != *b_it)
152✔
129
            goto different;
×
130
    }
152✔
131

17✔
132
    return true;
34✔
133

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

142
struct Column {
143
    StringData name;
144
    ColKey key_1, key_2;
145

146
    DataType get_type() const noexcept
147
    {
49,026✔
148
        return DataType(key_1.get_type());
49,026✔
149
    }
49,026✔
150

151
    bool is_list() const noexcept
152
    {
49,000✔
153
        return key_1.is_list();
49,000✔
154
    }
49,000✔
155

156
    bool is_dictionary() const noexcept
157
    {
49,060✔
158
        return key_1.is_dictionary();
49,060✔
159
    }
49,060✔
160

161
    bool is_set() const noexcept
162
    {
49,026✔
163
        return key_1.is_set();
49,026✔
164
    }
49,026✔
165

166
    bool is_nullable() const noexcept
167
    {
59,698✔
168
        return key_1.is_nullable();
59,698✔
169
    }
59,698✔
170
};
171

172
sync::PrimaryKey primary_key_for_row(const Obj& obj)
173
{
37,708✔
174
    auto table = obj.get_table();
37,708✔
175
    ColKey pk_col = table->get_primary_key_column();
37,708✔
176
    if (pk_col) {
37,708✔
177
        ColumnType pk_type = pk_col.get_type();
37,708✔
178
        if (obj.is_null(pk_col)) {
37,708✔
179
            return mpark::monostate{};
8✔
180
        }
8✔
181

19,146✔
182
        if (pk_type == col_type_Int) {
37,700✔
183
            return obj.get<int64_t>(pk_col);
31,800✔
184
        }
31,800✔
185

3,002✔
186
        if (pk_type == col_type_String) {
5,900✔
187
            return obj.get<StringData>(pk_col);
5,884✔
188
        }
5,884✔
189

8✔
190
        if (pk_type == col_type_ObjectId) {
16✔
191
            return obj.get<ObjectId>(pk_col);
16✔
192
        }
16✔
193

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

198
        REALM_TERMINATE("Missing primary key type support");
×
199
    }
×
200

19,150✔
201
    GlobalKey global_key = obj.get_object_id();
19,150✔
202
    return global_key;
×
203
}
37,708✔
204

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

211
ObjKey row_for_primary_key(const Table& table, sync::PrimaryKey key)
212
{
37,556✔
213
    ColKey pk_col = table.get_primary_key_column();
37,556✔
214
    if (pk_col) {
37,556✔
215
        ColumnType pk_type = pk_col.get_type();
37,556✔
216

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

19,070✔
225
        if (pk_type == col_type_Int) {
37,548✔
226
            if (auto pk = mpark::get_if<int64_t>(&key)) {
31,648✔
227
                return table.find_primary_key(*pk);
31,648✔
228
            }
31,648✔
229
            else {
×
230
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected int)");
×
231
            }
×
232
        }
31,648✔
233

19,070✔
234
        if (pk_type == col_type_String) {
21,968✔
235
            if (auto pk = mpark::get_if<StringData>(&key)) {
5,884✔
236
                return table.find_primary_key(*pk);
5,884✔
237
            }
5,884✔
238
            else {
×
239
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected string)");
×
240
            }
×
241
        }
5,884✔
242

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

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

261
        REALM_TERMINATE("row_for_primary_key missing primary key type support");
×
262
    }
×
263

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

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

277
bool compare_schemas(const Table& table_1, const Table& table_2, util::Logger& logger,
278
                     std::vector<Column>* out_columns = nullptr)
279
{
5,186✔
280
    bool equal = true;
5,186✔
281

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

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

2,625✔
361
    return equal;
5,186✔
362
}
5,186✔
363

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

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

346✔
504
            std::size_t n = a.size();
692✔
505
            for (std::size_t i = 0; i < n; ++i) {
1,448✔
506
                ObjKey link_1 = a.get(i);
756✔
507
                ObjKey link_2 = b.get(i);
756✔
508

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

4,389✔
548
    return true;
8,430✔
549
}
8,430✔
550

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

2✔
672
            auto target_table_1 = a.get_target_table();
4✔
673
            auto target_table_2 = b.get_target_table();
4✔
674

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

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

13✔
709
    return true;
26✔
710
}
26✔
711

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

9,898✔
720
    for (const Column& col : columns) {
55,774✔
721
        if (col.is_nullable()) {
55,774✔
722
            bool a = obj_1.is_null(col.key_1);
7,670✔
723
            bool b = obj_2.is_null(col.key_2);
7,670✔
724
            if (a && b)
7,670✔
725
                continue;
6,714✔
726
            if (a || b) {
956✔
727
                logger.error("Null/nonnull disagreement in column '%1' (%2 vs %3)", col.name, a, b);
×
728
                equal = false;
×
729
                continue;
×
730
            }
×
731
        }
49,060✔
732

24,614✔
733
        if (col.is_dictionary()) {
49,060✔
734
            auto a = obj_1.get_dictionary(col.key_1);
34✔
735
            auto b = obj_2.get_dictionary(col.key_2);
34✔
736
            if (!compare_dictionaries(a, b)) {
34✔
737
                logger.error("Dictionary mismatch in column '%1'", col.name);
×
738
                equal = false;
×
739
            }
×
740
            continue;
34✔
741
        }
34✔
742

24,597✔
743
        if (col.is_set()) {
49,026✔
744
            if (!compare_sets(col, obj_1, obj_2, logger)) {
26✔
745
                logger.error("Set mismatch in column '%1'", col.name);
×
746
                equal = false;
×
747
            }
×
748
            continue;
26✔
749
        }
26✔
750

24,584✔
751
        if (col.is_list()) {
49,000✔
752
            if (!compare_lists(col, obj_1, obj_2, logger)) {
8,430✔
753
                equal = false;
×
754
            }
×
755
            continue;
8,430✔
756
        }
8,430✔
757

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

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

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

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

30✔
934
                continue;
60✔
935
            }
×
936
            case type_LinkList:
✔
937
                break;
×
938
        }
×
939
        REALM_TERMINATE("Unsupported column type.");
×
940
    }
×
941
    return equal;
19,500✔
942
}
19,500✔
943

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

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

958
} // anonymous namespace
959

960
namespace realm::test_util {
961

962
bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& logger)
963
{
4,498✔
964
    bool equal = true;
4,498✔
965

2,281✔
966
    std::vector<Column> columns;
4,498✔
967
    equal = compare_schemas(table_1, table_2, logger, &columns);
4,498✔
968

2,281✔
969
    if (table_1.is_embedded() != table_2.is_embedded()) {
4,498✔
970
        logger.error("Table embeddedness mismatch");
×
971
        equal = false;
×
972
    }
×
973

2,281✔
974
    if (table_1.is_embedded() || table_2.is_embedded()) {
4,498✔
975
        if (table_1.size() != table_2.size()) {
90✔
976
            logger.error("Embedded table size mismatch (%1 vs %2): %3", table_1.size(), table_2.size(),
×
977
                         table_1.get_name());
×
978
            equal = false;
×
979
        }
×
980
        // Do not attempt to compare by row on embedded tables.
45✔
981
        return equal;
90✔
982
    }
90✔
983

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

2,236✔
1022
    // Compare individual rows
2,236✔
1023
    for (auto oid : objects_1) {
18,778✔
1024
        if (objects_2.find(oid) != objects_2.end()) {
18,778✔
1025
            ObjectCompareLogger sublogger{oid, logger};
18,778✔
1026
            if (!compare_objects(oid, table_1, table_2, columns, sublogger)) {
18,778✔
1027
                equal = false;
×
1028
            }
×
1029
        }
18,778✔
1030
    }
18,778✔
1031

2,236✔
1032
    return equal;
4,408✔
1033
}
4,408✔
1034

1035

1036
bool compare_groups(const Transaction& group_1, const Transaction& group_2)
1037
{
928✔
1038
    util::StderrLogger logger;
928✔
1039
    return compare_groups(group_1, group_2, logger);
928✔
1040
}
928✔
1041

1042

1043
bool compare_groups(const Transaction& group_1, const Transaction& group_2,
1044
                    util::FunctionRef<bool(StringData)> filter_func, util::Logger& logger)
1045
{
1,336✔
1046
    auto filter = [&](const Group& group, std::vector<StringData>& tables) {
2,672✔
1047
        auto table_keys = group.get_table_keys();
2,672✔
1048
        for (auto i : table_keys) {
9,012✔
1049
            ConstTableRef table = group.get_table(i);
9,012✔
1050
            StringData name = table->get_name();
9,012✔
1051
            if (name != "pk" && name != "metadata" && name != "client_reset_metadata" && filter_func(name))
9,012✔
1052
                tables.push_back(name);
9,000✔
1053
        }
9,012✔
1054
    };
2,672✔
1055

668✔
1056
    std::vector<StringData> tables_1, tables_2;
1,336✔
1057
    filter(group_1, tables_1);
1,336✔
1058
    filter(group_2, tables_2);
1,336✔
1059

668✔
1060
    bool equal = true;
1,336✔
1061
    for (StringData table_name : tables_1) {
4,500✔
1062
        if (!group_2.has_table(table_name)) {
4,500✔
1063
            logger.error("Table '%1' not found in right-hand side group", table_name);
2✔
1064
            equal = false;
2✔
1065
        }
2✔
1066
    }
4,500✔
1067
    for (StringData table_name : tables_2) {
4,500✔
1068
        if (!group_1.has_table(table_name)) {
4,500✔
1069
            logger.error("Table '%1' not found in left-hand side group", table_name);
2✔
1070
            equal = false;
2✔
1071
        }
2✔
1072
    }
4,500✔
1073

668✔
1074
    for (StringData table_name : tables_1) {
4,500✔
1075
        ConstTableRef table_1 = group_1.get_table(table_name);
4,500✔
1076
        ConstTableRef table_2 = group_2.get_table(table_name);
4,500✔
1077
        if (table_2) {
4,500✔
1078
            TableCompareLogger sublogger{table_name, logger};
4,498✔
1079
            if (!compare_tables(*table_1, *table_2, sublogger))
4,498✔
1080
                equal = false;
×
1081
        }
4,498✔
1082
    }
4,500✔
1083

668✔
1084
    return equal;
1,336✔
1085
}
1,336✔
1086

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