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

realm / realm-core / jonathan.reams_2947

01 Dec 2023 08:08PM UTC coverage: 91.739% (+0.04%) from 91.695%
jonathan.reams_2947

Pull #7160

Evergreen

jbreams
allow handle_error to decide resumability
Pull Request #7160: Prevent resuming a session that has not been fully shut down

92428 of 169414 branches covered (0.0%)

315 of 349 new or added lines in 14 files covered. (90.26%)

80 existing lines in 14 files now uncovered.

232137 of 253041 relevant lines covered (91.74%)

6882826.18 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
    {
4,584✔
27
    }
4,584✔
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
    {
19,450✔
56
    }
19,450✔
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
{
8,112✔
81
    auto a_it = a.begin();
8,112✔
82
    auto b_it = b.begin();
8,112✔
83
    if (a.size() != b.size()) {
8,112!
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,302✔
91
    // Compare entries
4,302✔
92
    for (; a_it != a.end(); ++a_it, ++b_it) {
14,890!
93
        if (!equals(*a_it, *b_it))
6,778!
94
            goto different;
×
95
    }
6,778✔
96

4,302✔
97
    return true;
8,112✔
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
}
8,112✔
105

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

112
bool compare_dictionaries(const Dictionary& a, const Dictionary& b)
113
{
34✔
114
    auto a_it = a.begin();
34✔
115
    auto b_it = b.begin();
34✔
116
    if (a.size() != b.size()) {
34✔
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) {
186✔
126
        if (*a_it != *b_it)
152✔
127
            goto different;
×
128
    }
152✔
129

17✔
130
    return true;
34✔
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
}
34✔
139

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

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

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

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

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

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

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

19,932✔
180
        if (pk_type == col_type_Int) {
39,044✔
181
            return obj.get<int64_t>(pk_col);
32,504✔
182
        }
32,504✔
183

3,610✔
184
        if (pk_type == col_type_String) {
6,540✔
185
            return obj.get<StringData>(pk_col);
6,524✔
186
        }
6,524✔
187

8✔
188
        if (pk_type == col_type_ObjectId) {
16✔
189
            return obj.get<ObjectId>(pk_col);
16✔
190
        }
16✔
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,936✔
199
    GlobalKey global_key = obj.get_object_id();
19,936✔
UNCOV
200
    return global_key;
×
201
}
39,052✔
202

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

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

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

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

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

3,610✔
241
        if (pk_type == col_type_ObjectId) {
3,618✔
242
            if (auto pk = mpark::get_if<ObjectId>(&key)) {
16✔
243
                return table.find_primary_key(*pk);
16✔
244
            }
16✔
245
            else {
×
246
                REALM_TERMINATE("row_for_primary_key mismatching primary key type (expected ObjectId)");
247
            }
×
248
        }
16✔
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,860✔
262
    if (auto global_key = mpark::get_if<GlobalKey>(&key)) {
19,860!
263
        return table.get_objkey(*global_key);
×
264
    }
×
265
    else {
×
266
        REALM_TERMINATE("row_for_primary_key() with primary key, expected GlobalKey");
267
    }
×
268
    return {};
×
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
{
5,272✔
278
    bool equal = true;
5,272✔
279

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

2,672✔
302
    // Compare column signatures
2,672✔
303
    {
5,272✔
304
        auto keys_1 = table_1.get_column_keys();
5,272✔
305
        for (auto key_1 : keys_1) {
13,058✔
306
            StringData name = table_1.get_column_name(key_1);
13,058✔
307
            ColKey key_2 = table_2.get_column_key(name);
13,058✔
308
            if (!key_2)
13,058✔
309
                continue;
×
310
            DataType type_1 = table_1.get_column_type(key_1);
13,058✔
311
            DataType type_2 = table_2.get_column_type(key_2);
13,058✔
312
            if (type_1 != type_2) {
13,058✔
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);
13,058✔
318
            bool nullable_2 = table_2.is_nullable(key_2);
13,058✔
319
            if (nullable_1 != nullable_2) {
13,058✔
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);
13,058✔
325
            bool is_list_2 = table_2.is_list(key_2);
13,058✔
326
            if (is_list_1 != is_list_2) {
13,058✔
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();
13,058✔
332
            bool is_dictionary_2 = key_2.is_dictionary();
13,058✔
333
            if (is_dictionary_1 != is_dictionary_2) {
13,058✔
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();
13,058✔
339
            bool is_set_2 = key_2.is_set();
13,058✔
340
            if (is_set_1 != is_set_2) {
13,058✔
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) {
13,058✔
346
                ConstTableRef target_1 = table_1.get_link_target(key_1);
838✔
347
                ConstTableRef target_2 = table_2.get_link_target(key_2);
838✔
348
                if (target_1->get_name() != target_2->get_name()) {
838✔
349
                    logger.error("Link target mismatch on column '%1'", name);
×
350
                    equal = false;
×
351
                    continue;
×
352
                }
×
353
            }
13,058✔
354
            if (out_columns)
13,058✔
355
                out_columns->push_back(Column{name, key_1, key_2});
13,057✔
356
        }
13,058✔
357
    }
5,272✔
358

2,672✔
359
    return equal;
5,272✔
360
}
5,272✔
361

362
bool compare_lists(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
363
{
8,782✔
364
    switch (col.get_type()) {
8,782✔
365
        case type_Int: {
3,976✔
366
            if (col.is_nullable()) {
3,976✔
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
            }
3,976✔
374
            else {
3,976✔
375
                auto a = obj_1.get_list<int64_t>(col.key_1);
3,976✔
376
                auto b = obj_2.get_list<int64_t>(col.key_2);
3,976✔
377
                if (!compare_arrays(a, b)) {
3,976✔
378
                    logger.error("List mismatch in column '%1'", col.name);
×
379
                    return false;
×
380
                }
×
381
            }
3,976✔
382
            break;
3,976✔
383
        }
3,976✔
384
        case type_Bool: {
2,050✔
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: {
4,114✔
394
            auto a = obj_1.get_list<String>(col.key_1);
4,114✔
395
            auto b = obj_2.get_list<String>(col.key_2);
4,114✔
396
            if (!compare_arrays(a, b)) {
4,114✔
397
                logger.error("List mismatch in column '%1'", col.name);
×
398
                return false;
×
399
            }
×
400
            break;
4,114✔
401
        }
4,114✔
402
        case type_Binary: {
2,241✔
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: {
692✔
478
            auto a = obj_1.get_list<ObjKey>(col.key_1);
692✔
479
            auto b = obj_2.get_list<ObjKey>(col.key_2);
692✔
480
            if (a.size() != b.size()) {
692✔
481
                logger.error("Link list size mismatch in column '%1'", col.name);
×
482
                return false;
×
483
                break;
×
484
            }
692✔
485
            auto table_1 = obj_1.get_table();
692✔
486
            auto table_2 = obj_2.get_table();
692✔
487
            ConstTableRef target_table_1 = table_1->get_link_target(col.key_1);
692✔
488
            ConstTableRef target_table_2 = table_2->get_link_target(col.key_2);
692✔
489

346✔
490
            bool is_embedded = target_table_1->is_embedded();
692✔
491
            std::vector<Column> embedded_columns;
692✔
492
            if (is_embedded) {
692✔
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);
640✔
499
                REALM_ASSERT(schemas_equal);
640✔
500
            }
640✔
501

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

378✔
507
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
756✔
508
                    // if one link is unresolved, the other should also be unresolved
11✔
509
                    if (!link_1.is_unresolved() || !link_2.is_unresolved()) {
22✔
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
                }
734✔
516
                else {
734✔
517
                    if (is_embedded) {
734✔
518
                        const Obj embedded_1 = target_table_1->get_object(link_1);
674✔
519
                        const Obj embedded_2 = target_table_2->get_object(link_2);
674✔
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)) {
674✔
524
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
525
                            return false;
×
526
                        }
×
527
                    }
60✔
528
                    else {
60✔
529
                        sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
60✔
530
                        sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
60✔
531
                        if (target_oid_1 != target_oid_2) {
60✔
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
                    }
60✔
538
                }
734✔
539
            }
756✔
540
            break;
692✔
541
        }
692✔
542
        case type_Link:
346✔
543
            REALM_TERMINATE("Unsupported column type.");
544
    }
8,782✔
545

4,637✔
546
    return true;
8,782✔
547
}
8,782✔
548

549
bool compare_sets(const Column& col, const Obj& obj_1, const Obj& obj_2, util::Logger& logger)
550
{
26✔
551
    switch (col.get_type()) {
26✔
552
        case type_Int: {
4✔
553
            if (col.is_nullable()) {
4✔
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
            }
4✔
561
            else {
4✔
562
                auto a = obj_1.get_set<int64_t>(col.key_1);
4✔
563
                auto b = obj_2.get_set<int64_t>(col.key_2);
4✔
564
                if (!compare_set_values(a, b)) {
4✔
565
                    logger.error("Set mismatch in column '%1'", col.name);
×
566
                    return false;
×
567
                }
×
568
            }
4✔
569
            break;
4✔
570
        }
4✔
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: {
4✔
581
            auto a = obj_1.get_set<String>(col.key_1);
4✔
582
            auto b = obj_2.get_set<String>(col.key_2);
4✔
583
            if (!compare_set_values(a, b)) {
4✔
584
                logger.error("Set mismatch in column '%1'", col.name);
×
585
                return false;
×
586
            }
×
587
            break;
4✔
588
        }
4✔
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: {
14✔
653
            auto a = obj_1.get_set<Mixed>(col.key_1);
14✔
654
            auto b = obj_2.get_set<Mixed>(col.key_2);
14✔
655
            if (!compare_set_values(a, b)) {
14✔
656
                logger.error("Set mismatch in column '%1'", col.name);
×
657
                return false;
×
658
            }
×
659
            break;
14✔
660
        }
14✔
661
        case type_Link: {
9✔
662
            auto a = obj_1.get_linkset(col.key_1);
4✔
663
            auto b = obj_2.get_linkset(col.key_2);
4✔
664
            if (a.size() != b.size()) {
4✔
665
                logger.error("Link set size mismatch in column '%1'", col.name);
×
666
                return false;
×
667
                break;
×
668
            }
4✔
669

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

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

2✔
678
                if (link_1.is_unresolved() || link_2.is_unresolved()) {
4✔
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
                }
4✔
687
                else {
4✔
688
                    sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
4✔
689
                    sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
4✔
690
                    if (target_oid_1 != target_oid_2) {
4✔
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
                }
4✔
697
            }
4✔
698
            break;
4✔
699
        }
4✔
700
        case type_TypedLink:
2✔
701
            // FIXME: Implement
702
            break;
×
703
        case type_LinkList:
2✔
704
            REALM_TERMINATE("Unsupported column type.");
705
    }
26✔
706

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

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

10,291✔
718
    for (const Column& col : columns) {
58,140✔
719
        if (col.is_nullable()) {
58,140✔
720
            bool a = obj_1.is_null(col.key_1);
7,570✔
721
            bool b = obj_2.is_null(col.key_2);
7,570✔
722
            if (a && b)
7,570✔
723
                continue;
6,586✔
724
            if (a || b) {
984✔
725
                logger.error("Null/nonnull disagreement in column '%1' (%2 vs %3)", col.name, a, b);
×
726
                equal = false;
×
727
                continue;
×
728
            }
×
729
        }
51,554✔
730

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

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

25,989✔
749
        if (col.is_list()) {
51,494✔
750
            if (!compare_lists(col, obj_1, obj_2, logger)) {
8,782✔
751
                equal = false;
×
752
            }
×
753
            continue;
8,782✔
754
        }
8,782✔
755

21,352✔
756
        const bool nullable = table_1.is_nullable(col.key_1);
42,712✔
757
        REALM_ASSERT(table_2.is_nullable(col.key_2) == nullable);
42,712✔
758
        switch (col.get_type()) {
42,712✔
759
            case type_Int: {
33,424✔
760
                if (nullable) {
33,424✔
761
                    auto a = obj_1.get<util::Optional<int64_t>>(col.key_1);
462✔
762
                    auto b = obj_2.get<util::Optional<int64_t>>(col.key_2);
462✔
763
                    if (a != b) {
462✔
764
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
765
                        equal = false;
×
766
                    }
×
767
                }
462✔
768
                else {
32,962✔
769
                    auto a = obj_1.get<int64_t>(col.key_1);
32,962✔
770
                    auto b = obj_2.get<int64_t>(col.key_2);
32,962✔
771
                    if (a != b) {
32,962✔
772
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
773
                        equal = false;
×
774
                    }
×
775
                }
32,962✔
776
                continue;
33,424✔
777
            }
×
778
            case type_Bool: {
12✔
779
                if (nullable) {
12✔
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 {
12✔
788
                    auto a = obj_1.get<bool>(col.key_1);
12✔
789
                    auto b = obj_2.get<bool>(col.key_2);
12✔
790
                    if (a != b) {
12✔
791
                        logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
792
                        equal = false;
×
793
                    }
×
794
                }
12✔
795

6✔
796
                continue;
12✔
797
            }
×
798
            case type_Float: {
12✔
799
                auto a = obj_1.get<float>(col.key_1);
12✔
800
                auto b = obj_2.get<float>(col.key_2);
12✔
801
                if (a != b) {
12✔
802
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
803
                    equal = false;
×
804
                }
×
805
                continue;
12✔
806
            }
×
807
            case type_Double: {
16✔
808
                auto a = obj_1.get<double>(col.key_1);
16✔
809
                auto b = obj_2.get<double>(col.key_2);
16✔
810
                if (a != b) {
16✔
811
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
812
                    equal = false;
×
813
                }
×
814
                continue;
16✔
815
            }
×
816
            case type_String: {
8,322✔
817
                auto a = obj_1.get<StringData>(col.key_1);
8,322✔
818
                auto b = obj_2.get<StringData>(col.key_2);
8,322✔
819
                if (a != b) {
8,322✔
820
                    logger.error("Value mismatch in column '%1'", col.name);
×
821
                    equal = false;
×
822
                }
×
823
                continue;
8,322✔
824
            }
×
825
            case type_Binary: {
832✔
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);
832✔
828
                auto b = obj_2.get<BinaryData>(col.key_2);
832✔
829
                if (a != b) {
832✔
830
                    logger.error("Value mismatch in column '%1'", col.name);
×
831
                    equal = false;
×
832
                }
×
833
                continue;
832✔
834
            }
×
835
            case type_Timestamp: {
16✔
836
                auto a = obj_1.get<Timestamp>(col.key_1);
16✔
837
                auto b = obj_2.get<Timestamp>(col.key_2);
16✔
838
                if (a != b) {
16✔
839
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
840
                    equal = false;
×
841
                }
×
842
                continue;
16✔
843
            }
×
844
            case type_ObjectId: {
8✔
845
                auto a = obj_1.get<ObjectId>(col.key_1);
8✔
846
                auto b = obj_2.get<ObjectId>(col.key_2);
8✔
847
                if (a != b) {
8✔
848
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
849
                    equal = false;
×
850
                }
×
851
                continue;
8✔
852
            }
×
853
            case type_Decimal: {
8✔
854
                auto a = obj_1.get<Decimal128>(col.key_1);
8✔
855
                auto b = obj_2.get<Decimal128>(col.key_2);
8✔
856
                if (a != b) {
8✔
857
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
858
                    equal = false;
×
859
                }
×
860
                continue;
8✔
861
            }
×
862
            case type_Mixed: {
2✔
863
                auto a = obj_1.get<Mixed>(col.key_1);
2✔
864
                auto b = obj_2.get<Mixed>(col.key_2);
2✔
865
                if (a != b) {
2✔
866
                    logger.error("Value mismatch in column '%1' (%2 vs %3)", col.name, a, b);
×
867
                    equal = false;
×
868
                }
×
869
                continue;
2✔
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: {
60✔
884
                auto link_1 = obj_1.get<ObjKey>(col.key_1);
60✔
885
                auto link_2 = obj_2.get<ObjKey>(col.key_2);
60✔
886
                ConstTableRef target_table_1 = table_1.get_link_target(col.key_1);
60✔
887
                ConstTableRef target_table_2 = table_2.get_link_target(col.key_2);
60✔
888

30✔
889
                if (!link_1 || !link_2) {
60✔
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 {
60✔
897
                    bool is_embedded = target_table_1->is_embedded();
60✔
898
                    std::vector<Column> embedded_columns;
60✔
899
                    if (is_embedded) {
60✔
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 =
48✔
906
                            compare_schemas(*target_table_1, *target_table_2, logger, &embedded_columns);
48✔
907
                        REALM_ASSERT(schemas_equal);
48✔
908
                    }
48✔
909

30✔
910
                    if (is_embedded) {
60✔
911
                        const Obj embedded_1 = target_table_1->get_object(link_1);
48✔
912
                        const Obj embedded_2 = target_table_2->get_object(link_2);
48✔
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)) {
48✔
917
                            logger.error("Embedded object contents mismatch in column '%1'", col.name);
×
918
                            equal = false;
×
919
                        }
×
920
                    }
48✔
921
                    else {
12✔
922
                        sync::PrimaryKey target_oid_1 = primary_key_for_row(*target_table_1, link_1);
12✔
923
                        sync::PrimaryKey target_oid_2 = primary_key_for_row(*target_table_2, link_2);
12✔
924
                        if (target_oid_1 != target_oid_2) {
12✔
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
                    }
12✔
930
                }
60✔
931

30✔
932
                continue;
60✔
933
            }
×
934
            case type_LinkList:
✔
935
                break;
×
936
        }
×
937
        REALM_TERMINATE("Unsupported column type.");
938
    }
×
939
    return equal;
20,172✔
940
}
20,172✔
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
{
19,450✔
945
    ObjKey row_1 = row_for_primary_key(table_1, oid);
19,450✔
946
    ObjKey row_2 = row_for_primary_key(table_2, oid);
19,450✔
947

9,930✔
948
    // Note: This is ensured by the inventory handling in compare_tables().
9,930✔
949
    REALM_ASSERT(row_1);
19,450✔
950
    REALM_ASSERT(row_2);
19,450✔
951
    const Obj obj_1 = table_1.get_object(row_1);
19,450✔
952
    const Obj obj_2 = table_2.get_object(row_2);
19,450✔
953
    return compare_objects(obj_1, obj_2, columns, logger);
19,450✔
954
}
19,450✔
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
{
4,584✔
968
    bool equal = true;
4,584✔
969

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

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

2,328✔
978
    if (table_1.is_embedded() || table_2.is_embedded()) {
4,584✔
979
        if (table_1.size() != table_2.size()) {
90✔
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;
90✔
986
    }
90✔
987

2,283✔
988
    // Compare row sets
2,283✔
989
    using Objects = std::set<sync::PrimaryKey>;
4,494✔
990
    auto make_inventory = [](const Table& table, Objects& objects) {
8,988✔
991
        for (const Obj& obj : table) {
38,900✔
992
            auto oid = primary_key_for_row(obj);
38,900✔
993
            objects.insert(oid);
38,900✔
994
        }
38,900✔
995
    };
8,988✔
996
    Objects objects_1, objects_2;
4,494✔
997
    make_inventory(table_1, objects_1);
4,494✔
998
    make_inventory(table_2, objects_2);
4,494✔
999
    auto report_missing = [&](const char* hand_2, Objects& objects_1, Objects& objects_2) {
8,988✔
1000
        std::vector<sync::PrimaryKey> missing;
8,988✔
1001
        for (auto oid : objects_1) {
38,900✔
1002
            if (objects_2.find(oid) == objects_2.end())
38,900✔
1003
                missing.push_back(oid);
×
1004
        }
38,900✔
1005
        if (missing.empty())
8,988✔
1006
            return;
8,988✔
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);
4,494✔
1024
    report_missing("left-hand", objects_2, objects_1);
4,494✔
1025

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

2,283✔
1036
    return equal;
4,494✔
1037
}
4,494✔
1038

1039

1040
bool compare_groups(const Transaction& group_1, const Transaction& group_2)
1041
{
934✔
1042
    util::NullLogger logger;
934✔
1043
    return compare_groups(group_1, group_2, logger);
934✔
1044
}
934✔
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
{
1,342✔
1050
    auto filter = [&](const Group& group, std::vector<StringData>& tables) {
2,684✔
1051
        auto table_keys = group.get_table_keys();
2,684✔
1052
        for (auto i : table_keys) {
9,192✔
1053
            ConstTableRef table = group.get_table(i);
9,192✔
1054
            StringData name = table->get_name();
9,192✔
1055
            if (name != "pk" && name != "metadata" && name != "client_reset_metadata" && filter_func(name))
9,192✔
1056
                tables.push_back(name);
9,172✔
1057
        }
9,192✔
1058
    };
2,684✔
1059

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

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

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

671✔
1088
    return equal;
1,342✔
1089
}
1,342✔
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