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

realm / realm-core / 1701

21 Sep 2023 05:42PM UTC coverage: 91.21% (+0.04%) from 91.172%
1701

push

Evergreen

web-flow
instead of throwing an exception on ill-formatted geospatial data, queries will now ignore those objects (#6989)

95882 of 175714 branches covered (0.0%)

33 of 33 new or added lines in 2 files covered. (100.0%)

39 existing lines in 14 files now uncovered.

232440 of 254841 relevant lines covered (91.21%)

7194113.38 hits per line

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

80.97
/src/realm/sync/noinst/changeset_index.cpp
1
#include <realm/sync/noinst/changeset_index.hpp>
2
#include <realm/sync/object_id.hpp>
3

4
#include <iterator> // std::distance, std::advance
5

6
using namespace realm::sync;
7

8
namespace realm {
9
namespace _impl {
10

11
#if REALM_DEBUG
12
static bool compare_ranges(const Changeset::Range& left, const Changeset::Range& right)
13
{
3,392✔
14
    return left.begin < right.begin;
3,392✔
15
}
3,392✔
16

17
template <class InputIterator>
18
static bool check_ranges(InputIterator begin, InputIterator end)
19
{
249,160✔
20
    if (!std::is_sorted(begin, end, compare_ranges))
249,160✔
21
        return false;
×
22

125,820✔
23
    // Check that there are no overlaps
125,820✔
24
    if (begin != end) {
249,164✔
25
        auto last_end = begin->end;
249,164✔
26
        for (auto i = begin + 1; i != end; ++i) {
252,556✔
27
            if (last_end > i->begin)
3,392✔
28
                return false;
×
29
            last_end = i->end;
3,392✔
30
        }
3,392✔
31
    }
249,164✔
32
    return true;
249,160✔
33
}
249,160✔
34

35
static bool check_ranges(const ChangesetIndex::Ranges& ranges)
36
{
3,463,658✔
37
    for (auto& pair : ranges) {
1,882,580✔
38
        if (!check_ranges(pair.second.begin(), pair.second.end()))
249,166✔
39
            return false;
×
40
    }
249,166✔
41
    return true;
3,463,658✔
42
}
3,463,658✔
43
#endif // REALM_DEBUG
44

45

46
void ChangesetIndex::clear() noexcept
47
{
111,390✔
48
    m_object_instructions.clear();
111,390✔
49
    m_schema_instructions.clear();
111,390✔
50
    m_conflict_groups_owner.clear();
111,390✔
51
    m_num_conflict_groups = 0;
111,390✔
52
}
111,390✔
53

54

55
void ChangesetIndex::scan_changeset(Changeset& changeset)
56
{
10,302,442✔
57
    if (m_contains_destructive_schema_changes)
10,302,442✔
58
        return;
2,110,784✔
59

4,176,208✔
60
#if defined(REALM_DEBUG) // LCOV_EXCL_START
8,191,658✔
61
    for (auto& confict_group : m_conflict_groups_owner) {
72,053,532✔
62
        // Check that add_changeset() has not been called yet.
37,213,682✔
63
        REALM_ASSERT(confict_group.ranges.empty());
72,053,532✔
64
    }
72,053,532✔
65
#endif // REALM_DEBUG LCOV_EXCL_STOP
8,191,658✔
66

4,176,208✔
67
    using Instruction = realm::sync::Instruction;
8,191,658✔
68

4,176,208✔
69
    for (auto it = changeset.begin(); it != changeset.end(); ++it) {
15,023,324✔
70
        if (!*it)
6,943,056✔
71
            continue;
1,248✔
72

3,540,034✔
73
        const auto& instr = **it;
6,941,808✔
74

3,540,034✔
75
        if (auto add_table_instr = instr.get_if<Instruction::AddTable>()) {
6,941,808✔
76
            auto& p = *add_table_instr;
182,996✔
77
            schema_conflict_group(changeset.get_string(p.table));
182,996✔
78
        }
182,996✔
79
        else if (instr.get_if<Instruction::EraseTable>()) {
6,758,812✔
80
            m_contains_destructive_schema_changes = true;
111,390✔
81
            clear();
111,390✔
82
            return;
111,390✔
83
        }
111,390✔
84
        else if (auto add_column_instr = instr.get_if<Instruction::AddColumn>()) {
6,647,422✔
85
            auto& p = *add_column_instr;
437,124✔
86
            StringData table_name = changeset.get_string(p.table);
437,124✔
87
            ConflictGroup& cg = schema_conflict_group(table_name);
437,124✔
88
            if (p.type == Instruction::Payload::Type::Link) {
437,124✔
89
                StringData target_table = changeset.get_string(p.link_target_table);
3,480✔
90
                ConflictGroup& cg2 = schema_conflict_group(target_table);
3,480✔
91
                merge_conflict_groups(cg, cg2);
3,480✔
92
            }
3,480✔
93
        }
437,124✔
94
        else if (instr.get_if<Instruction::EraseColumn>()) {
6,210,298✔
95
            m_contains_destructive_schema_changes = true;
×
96
            clear();
×
97
            return;
×
98
        }
×
99
        else {
6,210,298✔
100
            GlobalID ids[2];
6,210,298✔
101
            size_t num_ids = get_object_ids_in_instruction(changeset, instr, ids, 2);
6,210,298✔
102
            REALM_ASSERT(num_ids >= 1);
6,210,298✔
103
            REALM_ASSERT(num_ids <= 2);
6,210,298✔
104

3,169,462✔
105
            ConflictGroup& cg = object_conflict_group(ids[0]);
6,210,298✔
106
            for (size_t i = 1; i < num_ids; ++i) {
6,210,666✔
107
                ConflictGroup& cg2 = object_conflict_group(ids[1]);
368✔
108
                merge_conflict_groups(cg, cg2);
368✔
109
            }
368✔
110
        }
6,210,298✔
111
    }
6,941,808✔
112
}
8,191,658✔
113

114

115
void ChangesetIndex::merge_conflict_groups(ConflictGroup& into, ConflictGroup& from)
116
{
3,864✔
117
    if (&into == &from)
3,864✔
118
        return;
2,452✔
119

694✔
120
    if (from.size > into.size) {
1,412✔
121
        // This is an optimization. The time it takes to merge two conflict
8✔
122
        // groups is proportional to the size of the incoming group (in number
8✔
123
        // of objects touched). If the incoming group is larger, merge the other
8✔
124
        // way.
8✔
125
        merge_conflict_groups(from, into);
16✔
126
        return;
16✔
127
    }
16✔
128

686✔
129
    REALM_ASSERT(into.ranges.empty());
1,396✔
130
    REALM_ASSERT(from.ranges.empty());
1,396✔
131

686✔
132
    for (auto& class_name : from.schemas) {
1,216✔
133
        m_schema_instructions[class_name] = &into;
1,060✔
134
    }
1,060✔
135
    into.schemas.insert(into.schemas.end(), from.schemas.begin(), from.schemas.end());
1,396✔
136

686✔
137
    for (auto& pair : from.objects) {
866✔
138
        auto& objset = into.objects[pair.first];
336✔
139
        auto& objinstr = m_object_instructions[pair.first];
336✔
140
        for (auto& object : pair.second) {
336✔
141
            objinstr[object] = &into;
336✔
142
        }
336✔
143
        objset.insert(objset.end(), pair.second.begin(), pair.second.end());
336✔
144
    }
336✔
145
    into.size += from.size;
1,396✔
146

686✔
147
    m_conflict_groups_owner.erase(from.self_it);
1,396✔
148
    --m_num_conflict_groups;
1,396✔
149
}
1,396✔
150

151

152
void ChangesetIndex::add_changeset(Changeset& log)
153
{
440,794✔
154
    if (!log.empty())
440,794✔
155
        m_everything[&log] = std::vector<Changeset::Range>(1, Changeset::Range{log.begin(), log.end()});
315,264✔
156

221,120✔
157
    if (m_contains_destructive_schema_changes)
440,794✔
158
        return; // Just add to everything.
111,390✔
159

165,276✔
160
    using Instruction = realm::sync::Instruction;
329,404✔
161

165,276✔
162
    // Iterate over all instructions (skipping tombstones), and add them to the
165,276✔
163
    // index.
165,276✔
164
    for (auto it = log.begin(); it != log.end(); ++it) {
621,470✔
165
        if (!*it)
292,066✔
166
            continue;
×
167

147,154✔
168
        const auto& instr = **it;
292,066✔
169

147,154✔
170
        if (auto add_table_instr = instr.get_if<Instruction::AddTable>()) {
292,066✔
171
            StringData table = log.get_string(add_table_instr->table);
13,924✔
172
            auto& cg = schema_conflict_group(table);
13,924✔
173
            add_instruction_at(cg.ranges, log, it);
13,924✔
174
        }
13,924✔
175
        else if (instr.get_if<Instruction::EraseTable>()) {
278,142✔
176
            REALM_TERMINATE("Call scan_changeset() before add_changeset().");
×
177
        }
×
178
        else if (auto add_column_instr = instr.get_if<Instruction::AddColumn>()) {
278,142✔
179
            auto& p = *add_column_instr;
21,338✔
180
            StringData table = log.get_string(p.table);
21,338✔
181
            auto& cg = schema_conflict_group(table);
21,338✔
182
            if (p.type == Instruction::Payload::Type::Link) {
21,338✔
183
                REALM_ASSERT(&cg == &schema_conflict_group(log.get_string(p.link_target_table)));
1,740✔
184
            }
1,740✔
185
            add_instruction_at(cg.ranges, log, it);
21,338✔
186
        }
21,338✔
187
        else if (instr.get_if<Instruction::EraseColumn>()) {
256,804✔
188
            REALM_TERMINATE("Call scan_changeset() before add_changeset().");
×
189
        }
×
190
        else {
256,804✔
191
            GlobalID ids[2];
256,804✔
192
            size_t num_ids = get_object_ids_in_instruction(log, instr, ids, 2);
256,804✔
193
            REALM_ASSERT(num_ids >= 1);
256,804✔
194
            REALM_ASSERT(num_ids <= 2);
256,804✔
195

129,514✔
196
            auto& cg = object_conflict_group(ids[0]);
256,804✔
197
            for (size_t i = 1; i < num_ids; ++i) {
257,024✔
198
                REALM_ASSERT(&cg == &object_conflict_group(ids[i]));
220✔
199
            }
220✔
200
            add_instruction_at(cg.ranges, log, it);
256,804✔
201
        }
256,804✔
202
    }
292,066✔
203
}
329,404✔
204

205
size_t get_object_ids_in_instruction(const Changeset& changeset, const sync::Instruction& instr,
206
                                     ChangesetIndex::GlobalID* ids, size_t max_num_ids)
207
{
14,289,676✔
208
    REALM_ASSERT_RELEASE(max_num_ids >= 2);
14,289,676✔
209

7,242,574✔
210
    using Instruction = realm::sync::Instruction;
14,289,676✔
211

7,242,574✔
212
    if (auto obj_instr = instr.get_if<Instruction::ObjectInstruction>()) {
14,289,676✔
213
        ids[0] = {changeset.get_string(obj_instr->table), changeset.get_key(obj_instr->object)};
14,288,462✔
214

7,241,840✔
215
        if (auto set_instr = instr.get_if<Instruction::Update>()) {
14,288,462✔
216
            auto& p = *set_instr;
1,785,624✔
217
            if (p.value.type == Instruction::Payload::Type::Link) {
1,785,624✔
218
                ids[1] = {changeset.get_string(p.value.data.link.target_table),
304✔
219
                          changeset.get_key(p.value.data.link.target)};
304✔
220
                return 2;
304✔
221
            }
304✔
222
        }
12,502,838✔
223
        else if (auto insert_instr = instr.get_if<Instruction::ArrayInsert>()) {
12,502,838✔
224
            auto& p = *insert_instr;
1,970,018✔
225
            if (p.value.type == Instruction::Payload::Type::Link) {
1,970,018✔
226
                ids[1] = {changeset.get_string(p.value.data.link.target_table),
636✔
227
                          changeset.get_key(p.value.data.link.target)};
636✔
228
                return 2;
636✔
229
            }
636✔
230
        }
14,287,522✔
231

7,241,406✔
232
        return 1;
14,287,522✔
233
    }
14,287,522✔
234

734✔
235
    return 0;
1,214✔
236
}
1,214✔
237

238
auto ChangesetIndex::get_schema_changes_for_class(StringData class_name) const -> const Ranges*
239
{
19,474✔
240
    return const_cast<ChangesetIndex*>(this)->get_schema_changes_for_class(class_name);
19,474✔
241
}
19,474✔
242

243
auto ChangesetIndex::get_schema_changes_for_class(StringData class_name) -> Ranges*
244
{
19,474✔
245
    if (m_contains_destructive_schema_changes)
19,474✔
246
        return &m_everything;
×
247
    auto it = m_schema_instructions.find(class_name);
19,474✔
248
    if (it == m_schema_instructions.end())
19,474✔
249
        return &m_empty;
×
250
    return &it->second->ranges;
19,474✔
251
}
19,474✔
252

253
auto ChangesetIndex::get_modifications_for_object(GlobalID id) -> Ranges*
254
{
8,059,188✔
255
    if (m_contains_destructive_schema_changes)
8,059,188✔
256
        return &m_everything;
3,013,174✔
257
    auto it = m_object_instructions.find(id.table_name);
5,046,014✔
258
    if (it == m_object_instructions.end())
5,046,014✔
259
        return &m_empty;
×
260

2,568,858✔
261
    auto& object_instructions = it->second;
5,046,014✔
262
    auto it2 = object_instructions.find(id.object_id);
5,046,014✔
263
    if (it2 == object_instructions.end())
5,046,014✔
264
        return &m_empty;
×
265
    return &it2->second->ranges;
5,046,014✔
266
}
5,046,014✔
267

268
auto ChangesetIndex::get_modifications_for_object(GlobalID id) const -> const Ranges*
269
{
471,880✔
270
    return const_cast<ChangesetIndex*>(this)->get_modifications_for_object(id);
471,880✔
271
}
471,880✔
272

273
auto ChangesetIndex::schema_conflict_group(StringData class_name) -> ConflictGroup&
274
{
660,602✔
275
    auto& conflict_group = m_schema_instructions[class_name];
660,602✔
276
    if (conflict_group == nullptr) {
660,602✔
277
        m_conflict_groups_owner.emplace_back();
501,120✔
278
        ++m_num_conflict_groups;
501,120✔
279
        auto new_cg = std::prev(m_conflict_groups_owner.end());
501,120✔
280
        new_cg->schemas.push_back(class_name);
501,120✔
281
        new_cg->size = 1;
501,120✔
282
        new_cg->self_it = new_cg;
501,120✔
283
        conflict_group = &*new_cg;
501,120✔
284
    }
501,120✔
285
    return *conflict_group;
660,602✔
286
}
660,602✔
287

288
auto ChangesetIndex::object_conflict_group(const GlobalID& object_id) -> ConflictGroup&
289
{
6,466,958✔
290
    auto& objects_for_table = m_object_instructions[object_id.table_name];
6,466,958✔
291
    auto& conflict_group = objects_for_table[object_id.object_id];
6,466,958✔
292
    if (conflict_group == nullptr) {
6,466,958✔
293
        m_conflict_groups_owner.emplace_back();
4,031,592✔
294
        ++m_num_conflict_groups;
4,031,592✔
295
        auto new_cg = std::prev(m_conflict_groups_owner.end());
4,031,592✔
296
        new_cg->objects[object_id.table_name].push_back(object_id.object_id);
4,031,592✔
297
        new_cg->size = 1;
4,031,592✔
298
        new_cg->self_it = new_cg;
4,031,592✔
299
        conflict_group = &*new_cg;
4,031,592✔
300
    }
4,031,592✔
301
    return *conflict_group;
6,466,958✔
302
}
6,466,958✔
303

304
auto ChangesetIndex::erase_instruction(RangeIterator pos) -> RangeIterator
305
{
67,700✔
306
    Changeset* changeset = pos.m_outer->first;
67,700✔
307
    pos.check();
67,700✔
308
    auto new_pos = pos;
67,700✔
309
    new_pos.m_pos = changeset->erase_stable(pos.m_pos);
67,700✔
310

33,958✔
311
    if (new_pos.m_pos >= new_pos.m_inner->end) {
67,700✔
312
        // erased the last instruction in the range, move to the next range.
28,980✔
313
        REALM_ASSERT(new_pos.m_inner < new_pos.m_outer->second.end());
57,676✔
314
        ++new_pos.m_inner;
57,676✔
315
        if (new_pos.m_inner == new_pos.m_outer->second.end()) {
57,676✔
316
            REALM_ASSERT(new_pos.m_outer != new_pos.m_ranges->end());
57,676✔
317
            ++new_pos.m_outer;
57,676✔
318
            if (new_pos.m_outer == new_pos.m_ranges->end()) {
57,676✔
319
                return RangeIterator{new_pos.m_ranges, RangeIterator::end_tag{}}; // avoid new_pos.check()
55,318✔
320
            }
55,318✔
321
            else {
2,358✔
322
                new_pos.m_inner = new_pos.m_outer->second.begin();
2,358✔
323
                REALM_ASSERT(new_pos.m_inner != new_pos.m_outer->second.end());
2,358✔
324
                new_pos.m_pos = new_pos.m_inner->begin;
2,358✔
325
            }
2,358✔
326
        }
57,676✔
UNCOV
327
        else {
×
UNCOV
328
            new_pos.m_pos = new_pos.m_inner->begin;
×
UNCOV
329
            REALM_ASSERT(new_pos.m_pos != new_pos.m_inner->end); // empty ranges not allowed
✔
UNCOV
330
        }
×
331
    }
57,676✔
332
    new_pos.check();
40,520✔
333
    return new_pos;
12,382✔
334
}
67,700✔
335

336
#if REALM_DEBUG
337
static std::ostream& operator<<(std::ostream& os, GlobalID gid)
338
{
×
339
    return os << gid.table_name << "/" << format_pk(gid.object_id);
×
340
}
×
341

342
void ChangesetIndex::print(std::ostream& os) const
343
{
×
344
    auto print_ranges = [&](auto& subjects, const Ranges& ranges) {
×
345
        std::sort(subjects.begin(), subjects.end());
×
346
        subjects.erase(std::unique(subjects.begin(), subjects.end()), subjects.end());
×
347

348
        os << "[";
×
349
        for (auto it = subjects.begin(); it != subjects.end();) {
×
350
            os << *it;
×
351
            auto next = it;
×
352
            ++next;
×
353
            if (next != subjects.end())
×
354
                os << ", ";
×
355
            it = next;
×
356
        }
×
357
        os << "]: ";
×
358
        for (auto it = ranges.begin(); it != ranges.end();) {
×
359
            os << "Changeset" << std::dec << it->first->version << "(";
×
360
            for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
×
361
                auto offset = std::distance(it->first->begin(), it2->begin);
×
362
                auto length = std::distance(it2->begin, it2->end);
×
363
                os << "[" << std::dec << offset << "+" << length << "]";
×
364
                if (it2 + 1 != it->second.end())
×
365
                    os << ", ";
×
366
            }
×
367
            os << ")";
×
368
            auto next = it;
×
369
            ++next;
×
370
            if (next != ranges.end())
×
371
                os << ", ";
×
372
            it = next;
×
373
        }
×
374
    };
×
375

376
    std::map<Ranges*, std::vector<StringData>> schema_modifications;
×
377
    std::map<Ranges*, std::vector<GlobalID>> object_modifications;
×
378

379
    for (auto& pair : m_schema_instructions) {
×
380
        schema_modifications[&pair.second->ranges].push_back(pair.first);
×
381
    }
×
382

383
    for (auto& pair : m_object_instructions) {
×
384
        for (auto& pair2 : pair.second) {
×
385
            object_modifications[&pair2.second->ranges].push_back(GlobalID{pair.first, pair2.first});
×
386
        }
×
387
    }
×
388

389
    if (schema_modifications.size()) {
×
390
        os << "SCHEMA MODIFICATIONS:\n";
×
391
        for (auto& pair : schema_modifications) {
×
392
            print_ranges(pair.second, *pair.first);
×
393
            os << "\n";
×
394
        }
×
395
        os << "\n";
×
396
    }
×
397

398
    if (object_modifications.size()) {
×
399
        os << "OBJECT MODIFICATIONS:\n";
×
400
        for (auto& pair : object_modifications) {
×
401
            print_ranges(pair.second, *pair.first);
×
402
            os << "\n";
×
403
        }
×
404
        os << "\n";
×
405
    }
×
406
}
×
407

408
void ChangesetIndex::verify() const
409
{
415,652✔
410
    REALM_ASSERT(m_num_conflict_groups == m_conflict_groups_owner.size());
415,652✔
411

210,000✔
412
    // Verify that there are no stray pointers.
210,000✔
413
    for (auto& pair : m_object_instructions) {
1,117,102✔
414
        for (auto& pair2 : pair.second) {
3,080,496✔
415
            REALM_ASSERT(&*pair2.second->self_it == pair2.second);
3,080,496✔
416
            REALM_ASSERT(std::any_of(m_conflict_groups_owner.begin(), m_conflict_groups_owner.end(), [&](auto& cg) {
3,080,496✔
417
                return &cg == pair2.second;
3,080,496✔
418
            }));
3,080,496✔
419
        }
3,080,496✔
420
    }
1,117,102✔
421

210,000✔
422
    for (auto& pair : m_schema_instructions) {
397,506✔
423
        REALM_ASSERT(&*pair.second->self_it == pair.second);
384,538✔
424
        REALM_ASSERT(std::any_of(m_conflict_groups_owner.begin(), m_conflict_groups_owner.end(), [&](auto& cg) {
384,538✔
425
            return &cg == pair.second;
384,538✔
426
        }));
384,538✔
427
    }
384,538✔
428

210,000✔
429
    // Collect all changesets
210,000✔
430
    std::vector<Changeset*> changesets;
415,652✔
431
    for (auto& cg : m_conflict_groups_owner) {
3,463,672✔
432
        check_ranges(cg.ranges);
3,463,672✔
433

1,759,242✔
434
        for (auto& ranges : cg.ranges) {
1,882,584✔
435
            changesets.push_back(ranges.first);
249,170✔
436
        }
249,170✔
437
    }
3,463,672✔
438
    std::sort(changesets.begin(), changesets.end(), std::less<>());
415,652✔
439
    changesets.erase(std::unique(changesets.begin(), changesets.end()), changesets.end());
415,652✔
440

210,000✔
441
    // Run through all instructions in each changeset and check that
210,000✔
442
    // instructions are correctly covered by the index.
210,000✔
443
    for (auto changeset : changesets) {
325,900✔
444
        auto& log = *changeset;
234,142✔
445

118,242✔
446
        using Instruction = realm::sync::Instruction;
234,142✔
447

118,242✔
448
        // Iterate over all instructions (skipping tombstones), and verify that
118,242✔
449
        // the index covers any objects mentioned in that instruction.
118,242✔
450
        for (auto it = log.begin(); it != log.end(); ++it) {
526,118✔
451
            if (!*it)
291,976✔
452
                continue;
36,664✔
453

128,664✔
454
            const auto& instr = **it;
255,312✔
455

128,664✔
456
            if (auto add_table_instr = instr.get_if<Instruction::AddTable>()) {
255,312✔
457
                StringData table = log.get_string(add_table_instr->table);
7,504✔
458
                auto ranges = *get_schema_changes_for_class(table);
7,504✔
459
                REALM_ASSERT(ranges_cover(ranges, log, it));
7,504✔
460
            }
7,504✔
461
            else if (auto erase_table_instr = instr.get_if<Instruction::EraseTable>()) {
247,808✔
462
                StringData table = log.get_string(erase_table_instr->table);
×
463
                auto ranges = *get_schema_changes_for_class(table);
×
464
                REALM_ASSERT(ranges_cover(ranges, log, it));
×
465
            }
×
466
            else if (auto add_column_instr = instr.get_if<Instruction::AddColumn>()) {
247,808✔
467
                StringData table = log.get_string(add_column_instr->table);
11,970✔
468
                auto ranges = *get_schema_changes_for_class(table);
11,970✔
469
                REALM_ASSERT(ranges_cover(ranges, log, it));
11,970✔
470
            }
11,970✔
471
            else if (auto erase_column_instr = instr.get_if<Instruction::EraseColumn>()) {
235,838✔
472
                StringData table = log.get_string(erase_column_instr->table);
×
473
                auto ranges = *get_schema_changes_for_class(table);
×
474
                REALM_ASSERT(ranges_cover(ranges, log, it));
×
475
            }
×
476
            else {
235,838✔
477
                GlobalID ids[2];
235,838✔
478
                size_t num_ids = get_object_ids_in_instruction(log, instr, ids, 2);
235,838✔
479
                REALM_ASSERT(num_ids >= 1);
235,838✔
480
                REALM_ASSERT(num_ids <= 2);
235,838✔
481
                auto& ranges_first = *get_modifications_for_object(ids[0]);
235,838✔
482

118,934✔
483
                for (size_t i = 0; i < num_ids; ++i) {
471,894✔
484
                    auto& ranges = *get_modifications_for_object(ids[i]);
236,056✔
485
                    REALM_ASSERT(&ranges == &ranges_first);
236,056✔
486
                    REALM_ASSERT(ranges_cover(ranges, log, it));
236,056✔
487
                }
236,056✔
488
            }
235,838✔
489
        }
255,312✔
490
    }
234,142✔
491
}
415,652✔
492

493
bool ChangesetIndex::ranges_cover(const Ranges& ranges, Changeset& log, Changeset::const_iterator it) const
494
{
255,526✔
495
    auto outer = ranges.find(&log);
255,526✔
496
    if (outer == ranges.end())
255,526✔
497
        return false;
×
498
    for (auto& range : outer->second) {
258,938✔
499
        if (it >= range.begin && it < range.end)
258,938✔
500
            return true;
255,530✔
501
    }
258,938✔
502
    return false;
2,147,483,649✔
503
}
255,526✔
504

505
#endif // REALM_DEBUG
506

507
void ChangesetIndex::add_instruction_at(Ranges& ranges, Changeset& changeset, Changeset::iterator pos)
508
{
292,100✔
509
    auto& ranges_for_changeset = ranges[&changeset];
292,100✔
510

147,168✔
511
    REALM_ASSERT(pos != changeset.end());
292,100✔
512
    auto next = pos;
292,100✔
513
    ++next;
292,100✔
514

147,168✔
515
    Changeset::Range incoming{pos, next};
292,100✔
516

147,168✔
517
    auto cmp = [](const Changeset::Range& range_a, const Changeset::Range& range_b) {
169,030✔
518
        return range_a.begin < range_b.begin;
43,492✔
519
    };
43,492✔
520

147,168✔
521
    auto it = std::lower_bound(ranges_for_changeset.begin(), ranges_for_changeset.end(), incoming, cmp);
292,100✔
522

147,168✔
523
    it = ranges_for_changeset.insert(it, incoming);
292,100✔
524
    if (it != ranges_for_changeset.begin())
292,100✔
525
        --it;
42,892✔
526

147,168✔
527
    // Merge adjacent overlapping ranges
147,168✔
528
    while (it + 1 != ranges_for_changeset.end()) {
334,988✔
529
        auto next_it = it + 1;
42,888✔
530
        if (it->end >= next_it->begin) {
42,888✔
531
            it->end = std::max(it->end, next_it->end);
39,502✔
532
            next_it = ranges_for_changeset.erase(next_it);
39,502✔
533
            it = next_it - 1;
39,502✔
534
        }
39,502✔
535
        else {
3,386✔
536
            ++it;
3,386✔
537
        }
3,386✔
538
    }
42,888✔
539
}
292,100✔
540

541

542
} // namespace _impl
543
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc