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

realm / realm-core / jorgen.edelbo_138

13 Mar 2024 08:41AM UTC coverage: 91.77% (-0.3%) from 92.078%
jorgen.edelbo_138

Pull #7356

Evergreen

jedelbo
Add ability to get path to modified collections in object notifications
Pull Request #7356: Add ability to get path to modified collections in object notifications

94532 of 174642 branches covered (54.13%)

118 of 163 new or added lines in 16 files covered. (72.39%)

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

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

93.32
/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,624✔
64
    if (m_observers.empty())
4,624✔
65
        return;
4,480✔
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()) {
568✔
79
            if (key.is_list()) {
568✔
80
                m_lists.push_back({&observer, {}, {key}});
244✔
81
            }
244✔
82
        }
568✔
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✔
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 tbl = sg.get_table(observer.table_key);
112✔
114
        if (auto path_modifications = table.get_paths_modified(key)) {
112✔
115
            for (const StablePath& path : *path_modifications) {
120✔
116
                observer.changes[tbl->get_column_key(path[0])].kind = BindingContext::ColumnInfo::Kind::Set;
120✔
117
            }
120✔
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);
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);
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:
✔
200
                    changes.kind = BindingContext::ColumnInfo::Kind::Remove;
×
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,624✔
215
    if (!m_context)
4,624✔
216
        return;
4,476✔
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
    {
24,316✔
235
        return m_current_table;
24,316✔
236
    }
24,316✔
237

238
public:
239
    bool select_table(TableKey key) noexcept
240
    {
46,408✔
241
        m_current_table = key;
46,408✔
242
        return true;
46,408✔
243
    }
46,408✔
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
    {
×
252
        schema_error();
×
253
    }
×
254
    bool erase_column(ColKey)
255
    {
2✔
256
        schema_error();
2✔
257
    }
2✔
258
    bool rename_column(ColKey)
259
    {
×
260
        schema_error();
×
261
    }
×
262

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

277
    // Non-schema changes are all allowed
278
    void parse_complete() {}
12,544✔
279
    bool create_object(ObjKey)
280
    {
810,937✔
281
        return true;
810,937✔
282
    }
810,937✔
283
    bool remove_object(ObjKey)
284
    {
803,391✔
285
        return true;
803,391✔
286
    }
803,391✔
287
    bool collection_set(size_t)
288
    {
1,238✔
289
        return true;
1,238✔
290
    }
1,238✔
291
    bool collection_insert(size_t)
292
    {
51,758✔
293
        return true;
51,758✔
294
    }
51,758✔
295
    bool collection_erase(size_t)
296
    {
1,806✔
297
        return true;
1,806✔
298
    }
1,806✔
299
    bool collection_clear(size_t)
300
    {
429✔
301
        return true;
429✔
302
    }
429✔
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
    {
×
309
        return true;
×
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
    {
833,015✔
323
        return true;
833,015✔
324
    }
833,015✔
325
    bool select_collection(ColKey, ObjKey, const StablePath&)
326
    {
4,971✔
327
        return true;
4,971✔
328
    }
4,971✔
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

337
public:
338
    TransactLogObserver(_impl::TransactionChangeInfo& info)
339
        : m_info(info)
340
    {
15,177✔
341
    }
15,177✔
342

343
    void parse_complete()
344
    {
12,701✔
345
        for (auto& collection : m_info.collections)
12,701✔
346
            collection.changes->clean_up_stale_moves();
4,620✔
347
        for (auto it = m_info.tables.begin(); it != m_info.tables.end();) {
24,698✔
348
            if (it->second.empty())
11,997✔
349
                it = m_info.tables.erase(it);
3,632✔
350
            else
8,365✔
351
                ++it;
8,365✔
352
        }
11,997✔
353
    }
12,701✔
354

355
    bool select_table(TableKey key) noexcept
356
    {
20,198✔
357
        TransactLogValidationMixin::select_table(key);
20,198✔
358

10,059✔
359
        TableKey table_key = current_table();
20,198✔
360
        if (auto it = m_info.tables.find(table_key); it != m_info.tables.end())
20,198✔
361
            m_active_table = &it->second;
11,454✔
362
        else
8,744✔
363
            m_active_table = nullptr;
8,744✔
364
        return true;
20,198✔
365
    }
20,198✔
366

367
    bool select_collection(ColKey, ObjKey obj, const StablePath& path)
368
    {
3,788✔
369
        if (m_active_table) {
3,788✔
370
            m_active_table->modifications_add(obj, path);
2,730✔
371
        }
2,730✔
372
        auto table = current_table();
3,788✔
373
        m_active_collection = nullptr;
3,788✔
374
        for (auto& c : m_info.collections) {
3,717✔
375
            if (c.table_key == table && c.obj_key == obj && c.path.is_prefix_of(path)) {
3,646✔
376
                if (c.path.size() != path.size()) {
2,836✔
377
                    c.changes->stable_indexes.insert(path[c.path.size()]);
42✔
378
                }
42✔
379
                // If there are multiple exact matches for this collection we
1,397✔
380
                // use the first and then propagate the data to the others later
1,397✔
381
                else if (!m_active_collection) {
2,794✔
382
                    m_active_collection = c.changes;
1,402✔
383
                }
1,402✔
384
            }
2,836✔
385
        }
3,646✔
386
        return true;
3,788✔
387
    }
3,788✔
388

389
    bool collection_set(size_t index)
390
    {
996✔
391
        if (m_active_collection)
996✔
392
            m_active_collection->modify(index);
326✔
393
        return true;
996✔
394
    }
996✔
395

396
    bool collection_insert(size_t index)
397
    {
2,576✔
398
        if (m_active_collection)
2,576✔
399
            m_active_collection->insert(index);
968✔
400
        return true;
2,576✔
401
    }
2,576✔
402

403
    bool collection_erase(size_t index)
404
    {
2,176✔
405
        if (m_active_collection)
2,176✔
406
            m_active_collection->erase(index);
726✔
407
        return true;
2,176✔
408
    }
2,176✔
409

410
    bool collection_swap(size_t index1, size_t index2)
UNCOV
411
    {
×
UNCOV
412
        if (m_active_collection) {
×
413
            if (index1 > index2)
×
414
                std::swap(index1, index2);
×
415
            m_active_collection->move(index1, index2);
×
416
            if (index1 + 1 != index2)
×
417
                m_active_collection->move(index2 - 1, index1);
×
418
        }
×
419
        return true;
×
420
    }
×
421

422
    bool collection_clear(size_t old_size)
423
    {
304✔
424
        if (m_active_collection)
304✔
425
            m_active_collection->clear(old_size);
290✔
426
        return true;
304✔
427
    }
304✔
428

429
    bool collection_move(size_t from, size_t to)
430
    {
130✔
431
        if (m_active_collection)
130✔
432
            m_active_collection->move(from, to);
98✔
433
        return true;
130✔
434
    }
130✔
435

436
    bool create_object(ObjKey key)
437
    {
4,222✔
438
        if (m_active_table)
4,222✔
439
            m_active_table->insertions_add(key);
876✔
440
        return true;
4,222✔
441
    }
4,222✔
442

443
    bool remove_object(ObjKey key)
444
    {
1,266✔
445
        if (!m_active_table)
1,266✔
446
            return true;
504✔
447
        if (!m_active_table->insertions_remove(key))
762✔
448
            m_active_table->deletions_add(key);
760✔
449
        m_active_table->modifications_remove(key);
762✔
450

381✔
451
        for (size_t i = 0; i < m_info.collections.size(); ++i) {
1,092✔
452
            auto& list = m_info.collections[i];
330✔
453
            if (list.table_key != current_table())
330✔
454
                continue;
302✔
455
            if (list.obj_key == key) {
28✔
456
                if (i + 1 < m_info.collections.size())
26✔
457
                    m_info.collections[i] = std::move(m_info.collections.back());
8✔
458
                m_info.collections.pop_back();
26✔
459
                continue;
26✔
460
            }
26✔
461
        }
28✔
462

381✔
463
        return true;
762✔
464
    }
762✔
465

466
    bool modify_object(ColKey col, ObjKey key)
467
    {
22,849✔
468
        if (m_active_table) {
22,849✔
469
            StablePath path;
8,931✔
470
            path.push_back(StableIndex(col, 0));
8,931✔
471
            m_active_table->modifications_add(key, path);
8,931✔
472
        }
8,931✔
473
        return true;
22,849✔
474
    }
22,849✔
475

476
    bool insert_column(ColKey)
477
    {
10,098✔
478
        m_info.schema_changed = true;
10,098✔
479
        return true;
10,098✔
480
    }
10,098✔
481

482
    bool insert_group_level_table(TableKey)
483
    {
3,360✔
484
        m_info.schema_changed = true;
3,360✔
485
        return true;
3,360✔
486
    }
3,360✔
487

488
    bool typed_link_change(ColKey, TableKey)
489
    {
10✔
490
        m_info.schema_changed = true;
10✔
491
        return true;
10✔
492
    }
10✔
493
};
494

495
class KVOTransactLogObserver : public TransactLogObserver {
496
    KVOAdapter m_adapter;
497
    _impl::NotifierPackage& m_notifiers;
498
    Transaction& m_sg;
499

500
public:
501
    KVOTransactLogObserver(std::vector<BindingContext::ObserverState>& observers, BindingContext* context,
502
                           _impl::NotifierPackage& notifiers, Transaction& sg, bool is_rollback = false)
503
        : TransactLogObserver(m_adapter)
504
        , m_adapter(observers, context, is_rollback)
505
        , m_notifiers(notifiers)
506
        , m_sg(sg)
507
    {
4,624✔
508
    }
4,624✔
509

510
    ~KVOTransactLogObserver()
511
    {
4,624✔
512
        m_adapter.after(m_sg);
4,624✔
513
    }
4,624✔
514

515
    void parse_complete()
516
    {
2,148✔
517
        TransactLogObserver::parse_complete();
2,148✔
518
        m_adapter.before(m_sg);
2,148✔
519

1,074✔
520
        m_notifiers.package_and_wait(m_sg.get_version_of_latest_snapshot());
2,148✔
521
        m_notifiers.before_advance();
2,148✔
522
    }
2,148✔
523
};
524

525
template <typename Func>
526
void advance_with_notifications(BindingContext* context, const std::shared_ptr<Transaction>& sg, Func&& func,
527
                                _impl::NotifierPackage& notifiers)
528
{
71,974✔
529
    auto old_version = sg->get_version_of_current_transaction();
71,974✔
530
    std::vector<BindingContext::ObserverState> observers;
71,974✔
531
    if (context) {
71,974✔
532
        observers = context->get_observed_rows();
262✔
533
    }
262✔
534

34,573✔
535
    // Advancing to the latest version with notifiers requires using the full
34,573✔
536
    // transaction log observer so that we have a point where we know what
34,573✔
537
    // version we're going to before we actually advance to that version
34,573✔
538
    if (observers.empty() && (!notifiers || notifiers.version())) {
71,974✔
539
        notifiers.before_advance();
67,372✔
540
        TransactLogValidator validator;
67,372✔
541
        func(&validator);
67,372✔
542
        auto new_version = sg->get_version_of_current_transaction();
67,372✔
543
        if (context && old_version != new_version)
67,372✔
544
            context->did_change({}, {});
98✔
545
        // Each of these places where we call back to the user could close the
32,282✔
546
        // Realm. Just return early if that happens.
32,282✔
547
        if (sg->get_transact_stage() == DB::transact_Ready)
67,372✔
548
            return;
4✔
549
        if (context)
67,368✔
550
            context->will_send_notifications();
132✔
551
        if (sg->get_transact_stage() == DB::transact_Ready)
67,368✔
552
            return;
2✔
553
        notifiers.after_advance();
67,366✔
554
        if (sg->get_transact_stage() == DB::transact_Ready)
67,366✔
555
            return;
2✔
556
        if (context)
67,364✔
557
            context->did_send_notifications();
128✔
558
        return;
67,364✔
559
    }
67,364✔
560

2,291✔
561
    if (context)
4,602✔
562
        context->will_send_notifications();
126✔
563
    {
4,602✔
564
        KVOTransactLogObserver observer(observers, context, notifiers, *sg);
4,602✔
565
        func(&observer);
4,602✔
566
    }
4,602✔
567
    notifiers.package_and_wait(
4,602✔
568
        sg->get_version_of_current_transaction().version); // is a no-op if parse_complete() was called
4,602✔
569
    notifiers.after_advance();
4,602✔
570
    if (context)
4,602✔
571
        context->did_send_notifications();
126✔
572
}
4,602✔
573

574
} // anonymous namespace
575

576
namespace realm {
577
namespace _impl {
578

579
UnsupportedSchemaChange::UnsupportedSchemaChange()
580
    : std::logic_error(
581
          "Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way")
582
{
4✔
583
}
4✔
584

585
namespace transaction {
586
void advance(Transaction& tr, BindingContext*, VersionID version)
UNCOV
587
{
×
UNCOV
588
    TransactLogValidator validator;
×
589
    tr.advance_read(&validator, version);
×
590
}
×
591

592
void advance(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage&& notifiers)
593
{
19,540✔
594
    advance_with_notifications(
19,540✔
595
        context, tr,
19,540✔
596
        [&](auto&&... args) {
19,540✔
597
            tr->advance_read(std::move(args)..., notifiers.version().value_or(VersionID{}));
19,540✔
598
        },
19,540✔
599
        notifiers);
19,540✔
600
}
19,540✔
601

602
void begin(const std::shared_ptr<Transaction>& tr, BindingContext* context, NotifierPackage&& notifiers)
603
{
52,434✔
604
    advance_with_notifications(
52,434✔
605
        context, tr,
52,434✔
606
        [&](auto&&... args) {
52,434✔
607
            tr->promote_to_write(std::move(args)...);
52,434✔
608
        },
52,434✔
609
        notifiers);
52,434✔
610
}
52,434✔
611

612
void cancel(Transaction& tr, BindingContext* context)
613
{
3,100✔
614
    std::vector<BindingContext::ObserverState> observers;
3,100✔
615
    if (context) {
3,100✔
616
        observers = context->get_observed_rows();
34✔
617
    }
34✔
618
    if (observers.empty()) {
3,100✔
619
        tr.rollback_and_continue_as_read();
3,078✔
620
        return;
3,078✔
621
    }
3,078✔
622

11✔
623
    _impl::NotifierPackage notifiers;
22✔
624
    KVOTransactLogObserver o(observers, context, notifiers, tr, true);
22✔
625
    tr.rollback_and_continue_as_read(o);
22✔
626
}
22✔
627

628
void advance(Transaction& tr, TransactionChangeInfo& info, VersionID version)
629
{
15,503✔
630
    if (info.tables.empty() && info.collections.empty()) {
15,503✔
631
        tr.advance_read(version);
5,286✔
632
    }
5,286✔
633
    else {
10,217✔
634
        TransactLogObserver o(info);
10,217✔
635
        tr.advance_read(&o, version);
10,217✔
636
    }
10,217✔
637
}
15,503✔
638

639
void parse(Transaction& tr, TransactionChangeInfo& info, VersionID::version_type initial_version,
640
           VersionID::version_type end_version)
641
{
360✔
642
    if (!info.tables.empty() || !info.collections.empty()) {
360✔
643
        TransactLogObserver o(info);
336✔
644
        tr.parse_history(o, initial_version, end_version);
336✔
645
    }
336✔
646
}
360✔
647

648
} // namespace transaction
649
} // namespace _impl
650
} // 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