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

realm / realm-core / github_pull_request_281922

31 Oct 2023 09:13AM UTC coverage: 90.445% (-0.08%) from 90.528%
github_pull_request_281922

Pull #7039

Evergreen

jedelbo
Merge branch 'next-major' into je/global-key
Pull Request #7039: Remove ability to synchronize objects without primary key

95324 of 175822 branches covered (0.0%)

101 of 105 new or added lines in 13 files covered. (96.19%)

238 existing lines in 19 files now uncovered.

232657 of 257235 relevant lines covered (90.45%)

6351359.67 hits per line

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

54.83
/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,554✔
27
        set_level_threshold(base_logger.get_level_threshold());
4,554✔
28
    }
4,554✔
29
    void do_log(const util::LogCategory& category, Level level, const std::string& message) override final
30
    {
×
31
        ensure_prefix();                                          // Throws
×
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,744✔
57
        set_level_threshold(base_logger.get_level_threshold());
18,744✔
58
    }
18,744✔
59
    void do_log(const util::LogCategory& category, Level level, const std::string& message) override final
60
    {
×
61
        ensure_prefix();                                          // Throws
×
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,792✔
83
    auto a_it = a.begin();
7,792✔
84
    auto b_it = b.begin();
7,792✔
85
    if (a.size() != b.size()) {
7,792!
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,034✔
93
    // Compare entries
4,034✔
94
    for (; a_it != a.end(); ++a_it, ++b_it) {
13,414!
95
        if (!equals(*a_it, *b_it))
5,622!
96
            goto different;
×
97
    }
5,622✔
98

4,034✔
99
    return true;
7,792✔
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,792✔
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,006✔
148
        return DataType(key_1.get_type());
49,006✔
149
    }
49,006✔
150

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

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

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

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

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

18,878✔
182
        if (pk_type == col_type_Int) {
37,632✔
183
            return obj.get<int64_t>(pk_col);
31,732✔
184
        }
31,732✔
185

2,978✔
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

18,882✔
201
    REALM_TERMINATE("Missing primary key");
18,882✔
NEW
202
    return {};
×
203
}
37,640✔
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,488✔
213
    ColKey pk_col = table.get_primary_key_column();
37,488✔
214
    if (pk_col) {
37,488✔
215
        ColumnType pk_type = pk_col.get_type();
37,488✔
216

18,806✔
217
        if (auto pk = mpark::get_if<mpark::monostate>(&key)) {
37,488✔
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

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

18,802✔
234
        if (pk_type == col_type_String) {
21,724✔
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

2,978✔
243
        if (pk_type == col_type_ObjectId) {
2,986✔
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

18,806✔
264
    REALM_TERMINATE("row_for_primary_key() expected primary key");
18,806✔
265
    return {};
×
266
}
37,488✔
267

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

272
bool compare_schemas(const Table& table_1, const Table& table_2, util::Logger& logger,
273
                     std::vector<Column>* out_columns = nullptr)
274
{
5,242✔
275
    bool equal = true;
5,242✔
276

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

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

2,613✔
356
    return equal;
5,242✔
357
}
5,242✔
358

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

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

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

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

4,369✔
543
    return true;
8,462✔
544
}
8,462✔
545

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

2✔
667
            auto target_table_1 = a.get_target_table();
4✔
668
            auto target_table_2 = b.get_target_table();
4✔
669

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

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

13✔
704
    return true;
26✔
705
}
26✔
706

707
bool compare_objects(const Obj& obj_1, const Obj& obj_2, const std::vector<Column>& columns, util::Logger& logger)
708
{
19,466✔
709
    bool equal = true;
19,466✔
710
    auto ptable_1 = obj_1.get_table();
19,466✔
711
    auto ptable_2 = obj_2.get_table();
19,466✔
712
    auto& table_1 = *ptable_1;
19,466✔
713
    auto& table_2 = *ptable_2;
19,466✔
714

9,764✔
715
    for (const Column& col : columns) {
55,890✔
716
        if (col.is_nullable()) {
55,890✔
717
            bool a = obj_1.is_null(col.key_1);
7,738✔
718
            bool b = obj_2.is_null(col.key_2);
7,738✔
719
            if (a && b)
7,738✔
720
                continue;
6,850✔
721
            if (a || b) {
888✔
722
                logger.error("Null/nonnull disagreement in column '%1' (%2 vs %3)", col.name, a, b);
×
723
                equal = false;
×
724
                continue;
×
725
            }
×
726
        }
49,040✔
727

24,810✔
728
        if (col.is_dictionary()) {
49,040✔
729
            auto a = obj_1.get_dictionary(col.key_1);
34✔
730
            auto b = obj_2.get_dictionary(col.key_2);
34✔
731
            if (!compare_dictionaries(a, b)) {
34✔
732
                logger.error("Dictionary mismatch in column '%1'", col.name);
×
733
                equal = false;
×
734
            }
×
735
            continue;
34✔
736
        }
34✔
737

24,793✔
738
        if (col.is_set()) {
49,006✔
739
            if (!compare_sets(col, obj_1, obj_2, logger)) {
26✔
740
                logger.error("Set mismatch in column '%1'", col.name);
×
741
                equal = false;
×
742
            }
×
743
            continue;
26✔
744
        }
26✔
745

24,780✔
746
        if (col.is_list()) {
48,980✔
747
            if (!compare_lists(col, obj_1, obj_2, logger)) {
8,462✔
748
                equal = false;
×
749
            }
×
750
            continue;
8,462✔
751
        }
8,462✔
752

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

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

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

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

30✔
929
                continue;
60✔
930
            }
×
931
            case type_LinkList:
✔
932
                break;
×
933
        }
×
934
        REALM_TERMINATE("Unsupported column type.");
×
935
    }
×
936
    return equal;
19,466✔
937
}
19,466✔
938

939
bool compare_objects(sync::PrimaryKey& oid, const Table& table_1, const Table& table_2,
940
                     const std::vector<Column>& columns, util::Logger& logger)
941
{
18,744✔
942
    ObjKey row_1 = row_for_primary_key(table_1, oid);
18,744✔
943
    ObjKey row_2 = row_for_primary_key(table_2, oid);
18,744✔
944

9,403✔
945
    // Note: This is ensured by the inventory handling in compare_tables().
9,403✔
946
    REALM_ASSERT(row_1);
18,744✔
947
    REALM_ASSERT(row_2);
18,744✔
948
    const Obj obj_1 = table_1.get_object(row_1);
18,744✔
949
    const Obj obj_2 = table_2.get_object(row_2);
18,744✔
950
    return compare_objects(obj_1, obj_2, columns, logger);
18,744✔
951
}
18,744✔
952

953
} // anonymous namespace
954

955
namespace realm::test_util {
956

957
bool compare_tables(const Table& table_1, const Table& table_2, util::Logger& logger)
958
{
4,554✔
959
    bool equal = true;
4,554✔
960

2,269✔
961
    std::vector<Column> columns;
4,554✔
962
    equal = compare_schemas(table_1, table_2, logger, &columns);
4,554✔
963

2,269✔
964
    if (table_1.is_embedded() != table_2.is_embedded()) {
4,554✔
965
        logger.error("Table embeddedness mismatch");
×
966
        equal = false;
×
967
    }
×
968

2,269✔
969
    if (table_1.is_embedded() || table_2.is_embedded()) {
4,554✔
970
        if (table_1.size() != table_2.size()) {
90✔
971
            logger.error("Embedded table size mismatch (%1 vs %2): %3", table_1.size(), table_2.size(),
×
972
                         table_1.get_name());
×
973
            equal = false;
×
974
        }
×
975
        // Do not attempt to compare by row on embedded tables.
45✔
976
        return equal;
90✔
977
    }
90✔
978

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

2,224✔
1017
    // Compare individual rows
2,224✔
1018
    for (auto oid : objects_1) {
18,744✔
1019
        if (objects_2.find(oid) != objects_2.end()) {
18,744✔
1020
            ObjectCompareLogger sublogger{oid, logger};
18,744✔
1021
            if (!compare_objects(oid, table_1, table_2, columns, sublogger)) {
18,744✔
1022
                equal = false;
×
1023
            }
×
1024
        }
18,744✔
1025
    }
18,744✔
1026

2,224✔
1027
    return equal;
4,464✔
1028
}
4,464✔
1029

1030

1031
bool compare_groups(const Transaction& group_1, const Transaction& group_2)
1032
{
928✔
1033
    util::StderrLogger logger;
928✔
1034
    return compare_groups(group_1, group_2, logger);
928✔
1035
}
928✔
1036

1037

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

668✔
1051
    std::vector<StringData> tables_1, tables_2;
1,336✔
1052
    filter(group_1, tables_1);
1,336✔
1053
    filter(group_2, tables_2);
1,336✔
1054

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

668✔
1069
    for (StringData table_name : tables_1) {
4,556✔
1070
        ConstTableRef table_1 = group_1.get_table(table_name);
4,556✔
1071
        ConstTableRef table_2 = group_2.get_table(table_name);
4,556✔
1072
        if (table_2) {
4,556✔
1073
            TableCompareLogger sublogger{table_name, logger};
4,554✔
1074
            if (!compare_tables(*table_1, *table_2, sublogger))
4,554✔
1075
                equal = false;
×
1076
        }
4,554✔
1077
    }
4,556✔
1078

668✔
1079
    return equal;
1,336✔
1080
}
1,336✔
1081

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