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

realm / realm-core / nicola.cabiddu_1042

27 Sep 2023 06:04PM CUT coverage: 91.085% (-1.8%) from 92.915%
nicola.cabiddu_1042

Pull #6766

Evergreen

nicola-cab
Fix logic for dictionaries
Pull Request #6766: Client Reset for collections in mixed / nested collections

97276 of 178892 branches covered (0.0%)

1994 of 2029 new or added lines in 7 files covered. (98.28%)

4556 existing lines in 112 files now uncovered.

237059 of 260260 relevant lines covered (91.09%)

6321099.55 hits per line

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

93.17
/src/realm/object-store/impl/transact_log_handler.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2015 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <realm/object-store/impl/transact_log_handler.hpp>
20

21
#include <realm/object-store/binding_context.hpp>
22
#include <realm/object-store/impl/collection_notifier.hpp>
23
#include <realm/object-store/index_set.hpp>
24
#include <realm/object-store/shared_realm.hpp>
25

26
#include <realm/db.hpp>
27

28
#include <algorithm>
29
#include <numeric>
30

31
using namespace realm;
32

33
namespace {
34

35
class KVOAdapter : public _impl::TransactionChangeInfo {
36
public:
37
    KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context, bool is_rollback);
38

39
    void before(Transaction& sg);
40
    void after(Transaction& sg);
41

42
private:
43
    BindingContext* m_context;
44
    std::vector<BindingContext::ObserverState>& m_observers;
45
    std::vector<void*> m_invalidated;
46

47
    struct ListInfo {
48
        BindingContext::ObserverState* observer;
49
        _impl::CollectionChangeBuilder builder;
50
        ColKey col_key;
51
    };
52
    std::vector<ListInfo> m_lists;
53
    VersionID m_version;
54
    bool m_rollback;
55
};
56

57
KVOAdapter::KVOAdapter(std::vector<BindingContext::ObserverState>& observers, BindingContext* context,
58
                       bool is_rollback)
59
    : _impl::TransactionChangeInfo{}
60
    , m_context(context)
61
    , m_observers(observers)
62
    , m_rollback(is_rollback)
63
{
4,386✔
64
    if (m_observers.empty())
4,386✔
65
        return;
4,242✔
66

72✔
67
    std::vector<TableKey> tables_needed;
144✔
68
    for (auto& observer : observers) {
148✔
69
        tables_needed.push_back(observer.table_key);
148✔
70
    }
148✔
71
    std::sort(begin(tables_needed), end(tables_needed));
144✔
72
    tables_needed.erase(std::unique(begin(tables_needed), end(tables_needed)), end(tables_needed));
144✔
73

72✔
74
    auto realm = context->realm.lock();
144✔
75
    auto& group = realm->read_group();
144✔
76
    for (auto& observer : observers) {
148✔
77
        auto table = group.get_table(TableKey(observer.table_key));
148✔
78
        for (auto key : table->get_column_keys()) {
560✔
79
            if (key.is_collection()) {
560✔
80
                m_lists.push_back({&observer, {}, {key}});
244✔
81
            }
244✔
82
        }
560✔
83
    }
148✔
84

72✔
85
    tables.reserve(tables_needed.size());
144✔
86
    for (auto& tbl : tables_needed)
144✔
87
        tables[tbl] = {};
146✔
88
    for (auto& list : m_lists)
144✔
89
        collections.push_back({list.observer->table_key, list.observer->obj_key,
244✔
90
                               StablePath{{StableIndex(list.col_key, 0)}}, &list.builder});
244✔
91
}
144✔
92

93
void KVOAdapter::before(Transaction& sg)
94
{
2,148✔
95
    if (!m_context)
2,148✔
96
        return;
2,024✔
97

62✔
98
    m_version = sg.get_version_of_current_transaction();
124✔
99
    if (tables.empty())
124✔
100
        return;
6✔
101

59✔
102
    for (auto& observer : m_observers) {
122✔
103
        auto it = tables.find(observer.table_key);
122✔
104
        if (it == tables.end())
122✔
UNCOV
105
            continue;
×
106

61✔
107
        auto const& table = it->second;
122✔
108
        auto key = observer.obj_key;
122✔
109
        if (m_rollback ? table.insertions_contains(key) : table.deletions_contains(key)) {
122✔
110
            m_invalidated.push_back(observer.info);
10✔
111
            continue;
10✔
112
        }
10✔
113
        auto column_modifications = table.get_columns_modified(key);
112✔
114
        if (column_modifications) {
112✔
115
            for (auto col : *column_modifications) {
116✔
116
                observer.changes[col.value].kind = BindingContext::ColumnInfo::Kind::Set;
116✔
117
            }
116✔
118
        }
104✔
119
    }
112✔
120

59✔
121
    for (auto& list : m_lists) {
198✔
122
        if (list.builder.empty()) {
198✔
123
            // We may have pre-emptively marked the column as modified if the
52✔
124
            // LinkList was selected but the actual changes made ended up being
52✔
125
            // a no-op
52✔
126
            list.observer->changes.erase(list.col_key.value);
104✔
127
            continue;
104✔
128
        }
104✔
129
        // If the containing row was deleted then changes will be empty
47✔
130
        if (list.observer->changes.empty()) {
94✔
131
            REALM_ASSERT_DEBUG(tables[list.observer->table_key].deletions_contains(list.observer->obj_key));
4✔
132
            continue;
4✔
133
        }
4✔
134
        // otherwise the column should have been marked as modified
45✔
135
        auto it = list.observer->changes.find(list.col_key.value);
90✔
136
        REALM_ASSERT(it != list.observer->changes.end());
90✔
137
        auto& builder = list.builder;
90✔
138
        auto& changes = it->second;
90✔
139

45✔
140
        builder.modifications.remove(builder.insertions);
90✔
141

45✔
142
        // KVO can't express moves (because NSArray doesn't have them), so
45✔
143
        // transform them into a series of sets on each affected index when possible
45✔
144
        if (!builder.moves.empty() && builder.insertions.count() == builder.moves.size() &&
90✔
145
            builder.deletions.count() == builder.moves.size()) {
57✔
146
            changes.kind = BindingContext::ColumnInfo::Kind::Set;
24✔
147
            changes.indices = builder.modifications;
24✔
148
            changes.indices.add(builder.deletions);
24✔
149

12✔
150
            // Iterate over each of the rows which may have been shifted by
12✔
151
            // the moves and check if it actually has been, or if it's ended
12✔
152
            // up in the same place as it started (either because the moves were
12✔
153
            // actually a swap that doesn't effect the rows in between, or the
12✔
154
            // combination of moves happen to leave some intermediate rows in
12✔
155
            // the same place)
12✔
156
            auto in_range = [](auto& it, auto end, size_t i) {
158✔
157
                if (it != end && i >= it->second)
158✔
158
                    ++it;
32✔
159
                return it != end && i >= it->first && i < it->second;
158✔
160
            };
158✔
161

12✔
162
            auto del_it = builder.deletions.begin(), del_end = builder.deletions.end();
24✔
163
            auto ins_it = builder.insertions.begin(), ins_end = builder.insertions.end();
24✔
164
            size_t start = std::min(ins_it->first, del_it->first);
24✔
165
            size_t end = std::max(std::prev(ins_end)->second, std::prev(del_end)->second);
24✔
166
            ptrdiff_t shift = 0;
24✔
167
            for (size_t i = start; i < end; ++i) {
120✔
168
                if (in_range(del_it, del_end, i))
96✔
169
                    --shift;
34✔
170
                else if (in_range(ins_it, ins_end, i + shift))
62✔
171
                    ++shift;
24✔
172
                if (shift != 0)
96✔
173
                    changes.indices.add(i);
54✔
174
            }
96✔
175
        }
24✔
176
        // KVO can't express multiple types of changes at once
33✔
177
        else if (builder.insertions.empty() + builder.modifications.empty() + builder.deletions.empty() < 2) {
66✔
178
            changes.kind = BindingContext::ColumnInfo::Kind::SetAll;
8✔
179
        }
8✔
180
        else if (!builder.insertions.empty()) {
58✔
181
            changes.kind = BindingContext::ColumnInfo::Kind::Insert;
10✔
182
            changes.indices = builder.insertions;
10✔
183
        }
10✔
184
        else if (!builder.modifications.empty()) {
48✔
185
            changes.kind = BindingContext::ColumnInfo::Kind::Set;
4✔
186
            changes.indices = builder.modifications;
4✔
187
        }
4✔
188
        else {
44✔
189
            REALM_ASSERT(!builder.deletions.empty());
44✔
190
            changes.kind = BindingContext::ColumnInfo::Kind::Remove;
44✔
191
            changes.indices = builder.deletions;
44✔
192
        }
44✔
193

45✔
194
        // If we're rolling back a write transaction, insertions are actually
45✔
195
        // deletions and vice-versa. More complicated scenarios which would
45✔
196
        // require logic beyond this fortunately just aren't supported by KVO.
45✔
197
        if (m_rollback) {
90✔
198
            switch (changes.kind) {
22✔
199
                case BindingContext::ColumnInfo::Kind::Insert:
✔
UNCOV
200
                    changes.kind = BindingContext::ColumnInfo::Kind::Remove;
×
UNCOV
201
                    break;
×
202
                case BindingContext::ColumnInfo::Kind::Remove:
18✔
203
                    changes.kind = BindingContext::ColumnInfo::Kind::Insert;
18✔
204
                    break;
18✔
205
                default:
4✔
206
                    break;
4✔
207
            }
22✔
208
        }
22✔
209
    }
90✔
210
    m_context->will_change(m_observers, m_invalidated);
118✔
211
}
118✔
212

213
void KVOAdapter::after(Transaction& sg)
214
{
4,386✔
215
    if (!m_context)
4,386✔
216
        return;
4,238✔
217
    m_context->did_change(m_observers, m_invalidated,
148✔
218
                          m_version != VersionID{} && m_version != sg.get_version_of_current_transaction());
148✔
219
}
148✔
220

221
class TransactLogValidationMixin {
222
    // The currently selected table
223
    TableKey m_current_table;
224

225
    REALM_NORETURN
226
    REALM_NOINLINE
227
    void schema_error()
228
    {
4✔
229
        throw _impl::UnsupportedSchemaChange();
4✔
230
    }
4✔
231

232
protected:
233
    TableKey current_table() const noexcept
234
    {
22,706✔
235
        return m_current_table;
22,706✔
236
    }
22,706✔
237

238
public:
239
    bool select_table(TableKey key) noexcept
240
    {
43,916✔
241
        m_current_table = key;
43,916✔
242
        return true;
43,916✔
243
    }
43,916✔
244

245
    // Removing or renaming things while a Realm is open is never supported
246
    bool erase_class(TableKey)
247
    {
2✔
248
        schema_error();
2✔
249
    }
2✔
250
    bool rename_class(TableKey)
251
    {
×
UNCOV
252
        schema_error();
×
UNCOV
253
    }
×
254
    bool erase_column(ColKey)
255
    {
2✔
256
        schema_error();
2✔
257
    }
2✔
258
    bool rename_column(ColKey)
259
    {
×
UNCOV
260
        schema_error();
×
UNCOV
261
    }
×
262

263
    // Additive changes and reorderings are supported
264
    bool insert_group_level_table(TableKey)
265
    {
3,808✔
266
        return true;
3,808✔
267
    }
3,808✔
268
    bool insert_column(ColKey)
269
    {
11,968✔
270
        return true;
11,968✔
271
    }
11,968✔
272
    bool set_link_type(ColKey)
273
    {
×
UNCOV
274
        return true;
×
UNCOV
275
    }
×
276

277
    // Non-schema changes are all allowed
278
    void parse_complete() {}
12,785✔
279
    bool create_object(ObjKey)
280
    {
810,572✔
281
        return true;
810,572✔
282
    }
810,572✔
283
    bool remove_object(ObjKey)
284
    {
803,102✔
285
        return true;
803,102✔
286
    }
803,102✔
287
    bool collection_set(size_t)
288
    {
1,096✔
289
        return true;
1,096✔
290
    }
1,096✔
291
    bool collection_insert(size_t)
292
    {
41,470✔
293
        return true;
41,470✔
294
    }
41,470✔
295
    bool collection_erase(size_t)
296
    {
1,448✔
297
        return true;
1,448✔
298
    }
1,448✔
299
    bool collection_clear(size_t)
300
    {
371✔
301
        return true;
371✔
302
    }
371✔
303
    bool collection_move(size_t, size_t)
304
    {
6✔
305
        return true;
6✔
306
    }
6✔
307
    bool collection_swap(size_t, size_t)
308
    {
×
UNCOV
309
        return true;
×
UNCOV
310
    }
×
311
    bool typed_link_change(ColKey, TableKey)
312
    {
6✔
313
        return true;
6✔
314
    }
6✔
315
};
316

317

318
// A transaction log handler that just validates that all operations made are
319
// ones supported by the object store
320
struct TransactLogValidator : public TransactLogValidationMixin {
321
    bool modify_object(ColKey, ObjKey)
322
    {
831,470✔
323
        return true;
831,470✔
324
    }
831,470✔
325
    bool select_collection(ColKey, ObjKey, const StablePath&)
326
    {
4,459✔
327
        return true;
4,459✔
328
    }
4,459✔
329
};
330

331
// Extends TransactLogValidator to track changes made to LinkViews
332
class TransactLogObserver : public TransactLogValidationMixin {
333
    _impl::TransactionChangeInfo& m_info;
334
    _impl::CollectionChangeBuilder* m_active_collection = nullptr;
335
    ObjectChangeSet* m_active_table = nullptr;
336
    Path m_path;
337

338
public:
339
    TransactLogObserver(_impl::TransactionChangeInfo& info)
340
        : m_info(info)
341
    {
16,104✔
342
    }
16,104✔
343

344
    void parse_complete()
345
    {
13,866✔
346
        for (auto& collection : m_info.collections)
13,866✔
347
            collection.changes->clean_up_stale_moves();
4,604✔
348
        for (auto it = m_info.tables.begin(); it != m_info.tables.end();) {
26,728✔
349
            if (it->second.empty())
12,862✔
350
                it = m_info.tables.erase(it);
3,525✔
351
            else
9,337✔
352
                ++it;
9,337✔
353
        }
12,862✔
354
    }
13,866✔
355

356
    bool select_table(TableKey key) noexcept
357
    {
19,014✔
358
        TransactLogValidationMixin::select_table(key);
19,014✔
359

9,557✔
360
        TableKey table_key = current_table();
19,014✔
361
        if (auto it = m_info.tables.find(table_key); it != m_info.tables.end())
19,014✔
362
            m_active_table = &it->second;
10,618✔
363
        else
8,396✔
364
            m_active_table = nullptr;
8,396✔
365
        return true;
19,014✔
366
    }
19,014✔
367

368
    bool select_collection(ColKey col, ObjKey obj, const StablePath& path)
369
    {
3,364✔
370
        modify_object(col, obj);
3,364✔
371
        auto table = current_table();
3,364✔
372
        for (auto& c : m_info.collections) {
2,768✔
373

1,086✔
374
            if (c.table_key == table && c.obj_key == obj && c.path == path) {
2,172✔
375
                m_active_collection = c.changes;
1,400✔
376
                return true;
1,400✔
377
            }
1,400✔
378
        }
2,172✔
379
        m_active_collection = nullptr;
2,664✔
380
        return true;
1,964✔
381
    }
3,364✔
382

383
    bool collection_set(size_t index)
384
    {
866✔
385
        if (m_active_collection)
866✔
386
            m_active_collection->modify(index);
326✔
387
        return true;
866✔
388
    }
866✔
389

390
    bool collection_insert(size_t index)
391
    {
2,486✔
392
        if (m_active_collection)
2,486✔
393
            m_active_collection->insert(index);
978✔
394
        return true;
2,486✔
395
    }
2,486✔
396

397
    bool collection_erase(size_t index)
398
    {
1,830✔
399
        if (m_active_collection)
1,830✔
400
            m_active_collection->erase(index);
726✔
401
        return true;
1,830✔
402
    }
1,830✔
403

404
    bool collection_swap(size_t index1, size_t index2)
405
    {
×
406
        if (m_active_collection) {
×
407
            if (index1 > index2)
×
408
                std::swap(index1, index2);
×
409
            m_active_collection->move(index1, index2);
×
410
            if (index1 + 1 != index2)
×
UNCOV
411
                m_active_collection->move(index2 - 1, index1);
×
UNCOV
412
        }
×
UNCOV
413
        return true;
×
UNCOV
414
    }
×
415

416
    bool collection_clear(size_t old_size)
417
    {
298✔
418
        if (m_active_collection)
298✔
419
            m_active_collection->clear(old_size);
290✔
420
        return true;
298✔
421
    }
298✔
422

423
    bool collection_move(size_t from, size_t to)
424
    {
130✔
425
        if (m_active_collection)
130✔
426
            m_active_collection->move(from, to);
98✔
427
        return true;
130✔
428
    }
130✔
429

430
    bool create_object(ObjKey key)
431
    {
3,704✔
432
        if (m_active_table)
3,704✔
433
            m_active_table->insertions_add(key);
606✔
434
        return true;
3,704✔
435
    }
3,704✔
436

437
    bool remove_object(ObjKey key)
438
    {
1,030✔
439
        if (!m_active_table)
1,030✔
440
            return true;
506✔
441
        if (!m_active_table->insertions_remove(key))
524✔
442
            m_active_table->deletions_add(key);
522✔
443
        m_active_table->modifications_remove(key);
524✔
444

262✔
445
        for (size_t i = 0; i < m_info.collections.size(); ++i) {
852✔
446
            auto& list = m_info.collections[i];
328✔
447
            if (list.table_key != current_table())
328✔
448
                continue;
302✔
449
            if (list.obj_key == key) {
26✔
450
                if (i + 1 < m_info.collections.size())
24✔
451
                    m_info.collections[i] = std::move(m_info.collections.back());
6✔
452
                m_info.collections.pop_back();
24✔
453
                continue;
24✔
454
            }
24✔
455
        }
26✔
456

262✔
457
        return true;
524✔
458
    }
524✔
459

460
    bool modify_object(ColKey col, ObjKey key)
461
    {
25,275✔
462
        if (m_active_table)
25,275✔
463
            m_active_table->modifications_add(key, col);
11,147✔
464
        return true;
25,275✔
465
    }
25,275✔
466

467
    bool insert_column(ColKey)
468
    {
9,354✔
469
        m_info.schema_changed = true;
9,354✔
470
        return true;
9,354✔
471
    }
9,354✔
472

473
    bool insert_group_level_table(TableKey)
474
    {
3,112✔
475
        m_info.schema_changed = true;
3,112✔
476
        return true;
3,112✔
477
    }
3,112✔
478

479
    bool typed_link_change(ColKey, TableKey)
480
    {
10✔
481
        m_info.schema_changed = true;
10✔
482
        return true;
10✔
483
    }
10✔
484
};
485

486
class KVOTransactLogObserver : public TransactLogObserver {
487
    KVOAdapter m_adapter;
488
    _impl::NotifierPackage& m_notifiers;
489
    Transaction& m_sg;
490

491
public:
492
    KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers, BindingContext* context,
493
                           _impl::NotifierPackage& notifiers, Transaction& sg, bool is_rollback = false)
494
        : TransactLogObserver(m_adapter)
495
        , m_adapter(observers, context, is_rollback)
496
        , m_notifiers(notifiers)
497
        , m_sg(sg)
498
    {
4,386✔
499
    }
4,386✔
500

501
    ~KVOTransactLogObserver()
502
    {
4,386✔
503
        m_adapter.after(m_sg);
4,386✔
504
    }
4,386✔
505

506
    void parse_complete()
507
    {
2,148✔
508
        TransactLogObserver::parse_complete();
2,148✔
509
        m_adapter.before(m_sg);
2,148✔
510

1,074✔
511
        m_notifiers.package_and_wait(m_sg.get_version_of_latest_snapshot());
2,148✔
512
        m_notifiers.before_advance();
2,148✔
513
    }
2,148✔
514
};
515

516
template <typename Func>
517
void advance_with_notifications(BindingContext* context, const std::shared_ptr<Transaction>& sg, Func&& func,
518
                                _impl::NotifierPackage& notifiers)
519
{
106,305✔
520
    auto old_version = sg->get_version_of_current_transaction();
106,305✔
521
    std::vector<BindingContext::ObserverState> observers;
106,305✔
522
    if (context) {
106,305✔
523
        observers = context->get_observed_rows();
252✔
524
    }
252✔
525

51,783✔
526
    // Advancing to the latest version with notifiers requires using the full
51,783✔
527
    // transaction log observer so that we have a point where we know what
51,783✔
528
    // version we're going to before we actually advance to that version
51,783✔
529
    if (observers.empty() && (!notifiers || notifiers.version())) {
106,305✔
530
        notifiers.before_advance();
101,941✔
531
        TransactLogValidator validator;
101,941✔
532
        func(&validator);
101,941✔
533
        auto new_version = sg->get_version_of_current_transaction();
101,941✔
534
        if (context && old_version != new_version)
101,941✔
535
            context->did_change({}, {});
88✔
536
        // Each of these places where we call back to the user could close the
49,601✔
537
        // Realm. Just return early if that happens.
49,601✔
538
        if (sg->get_transact_stage() == DB::transact_Ready)
101,941✔
539
            return;
4✔
540
        if (context)
101,937✔
541
            context->will_send_notifications();
122✔
542
        if (sg->get_transact_stage() == DB::transact_Ready)
101,937✔
543
            return;
2✔
544
        notifiers.after_advance();
101,935✔
545
        if (sg->get_transact_stage() == DB::transact_Ready)
101,935✔
546
            return;
2✔
547
        if (context)
101,933✔
548
            context->did_send_notifications();
118✔
549
        return;
101,933✔
550
    }
101,933✔
551

2,182✔
552
    if (context)
4,364✔
553
        context->will_send_notifications();
126✔
554
    {
4,364✔
555
        KVOTransactLogObserver observer(observers, context, notifiers, *sg);
4,364✔
556
        func(&observer);
4,364✔
557
    }
4,364✔
558
    notifiers.package_and_wait(
4,364✔
559
        sg->get_version_of_current_transaction().version); // is a no-op if parse_complete() was called
4,364✔
560
    notifiers.after_advance();
4,364✔
561
    if (context)
4,364✔
562
        context->did_send_notifications();
126✔
563
}
4,364✔
564

565
} // anonymous namespace
566

567
namespace realm {
568
namespace _impl {
569

570
UnsupportedSchemaChange::UnsupportedSchemaChange()
571
    : std::logic_error(
572
          "Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way")
573
{
4✔
574
}
4✔
575

576
namespace transaction {
577
void advance(Transaction& tr, BindingContext*, VersionID version)
UNCOV
578
{
×
UNCOV
579
    TransactLogValidator validator;
×
UNCOV
580
    tr.advance_read(&validator, version);
×
UNCOV
581
}
×
582

583
void advance(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage&& notifiers)
584
{
41,344✔
585
    advance_with_notifications(
41,344✔
586
        context, tr,
41,344✔
587
        [&](auto&&... args) {
41,344✔
588
            tr->advance_read(std::move(args)..., notifiers.version().value_or(VersionID{}));
41,344✔
589
        },
41,344✔
590
        notifiers);
41,344✔
591
}
41,344✔
592

593
void begin(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage&& notifiers)
594
{
64,961✔
595
    advance_with_notifications(
64,961✔
596
        context, tr,
64,961✔
597
        [&](auto&&... args) {
64,961✔
598
            tr->promote_to_write(std::move(args)...);
64,961✔
599
        },
64,961✔
600
        notifiers);
64,961✔
601
}
64,961✔
602

603
void cancel(Transaction& tr, BindingContext* context)
604
{
2,824✔
605
    std::vector<BindingContext::ObserverState> observers;
2,824✔
606
    if (context) {
2,824✔
607
        observers = context->get_observed_rows();
34✔
608
    }
34✔
609
    if (observers.empty()) {
2,824✔
610
        tr.rollback_and_continue_as_read();
2,802✔
611
        return;
2,802✔
612
    }
2,802✔
613

11✔
614
    _impl::NotifierPackage notifiers;
22✔
615
    KVOTransactLogObserver o(observers, context, notifiers, tr, true);
22✔
616
    tr.rollback_and_continue_as_read(o);
22✔
617
}
22✔
618

619
void advance(Transaction& tr, TransactionChangeInfo& info, VersionID version)
620
{
16,399✔
621
    if (info.tables.empty() && info.collections.empty()) {
16,399✔
622
        tr.advance_read(version);
5,017✔
623
    }
5,017✔
624
    else {
11,382✔
625
        TransactLogObserver o(info);
11,382✔
626
        tr.advance_read(&o, version);
11,382✔
627
    }
11,382✔
628
}
16,399✔
629

630
void parse(Transaction& tr, TransactionChangeInfo& info, VersionID::version_type initial_version,
631
           VersionID::version_type end_version)
632
{
360✔
633
    if (!info.tables.empty() || !info.collections.empty()) {
360✔
634
        TransactLogObserver o(info);
336✔
635
        tr.parse_history(o, initial_version, end_version);
336✔
636
    }
336✔
637
}
360✔
638

639
} // namespace transaction
640
} // namespace _impl
641
} // 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

© 2025 Coveralls, Inc