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

realm / realm-core / 1714

29 Sep 2023 04:22PM UTC coverage: 91.218% (+0.03%) from 91.188%
1714

push

Evergreen

realm-ci
Updated release notes

95850 of 175738 branches covered (0.0%)

232557 of 254946 relevant lines covered (91.22%)

7251217.66 hits per line

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

94.59
/src/realm/object-store/shared_realm.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/shared_realm.hpp>
20

21
#include <realm/object-store/impl/collection_notifier.hpp>
22
#include <realm/object-store/impl/realm_coordinator.hpp>
23
#include <realm/object-store/impl/transact_log_handler.hpp>
24

25
#include <realm/object-store/audit.hpp>
26
#include <realm/object-store/binding_context.hpp>
27
#include <realm/object-store/list.hpp>
28
#include <realm/object-store/object.hpp>
29
#include <realm/object-store/object_schema.hpp>
30
#include <realm/object-store/object_store.hpp>
31
#include <realm/object-store/results.hpp>
32
#include <realm/object-store/schema.hpp>
33
#include <realm/object-store/thread_safe_reference.hpp>
34

35
#include <realm/object-store/util/scheduler.hpp>
36

37
#include <realm/db.hpp>
38
#include <realm/util/fifo_helper.hpp>
39
#include <realm/util/file.hpp>
40
#include <realm/util/scope_exit.hpp>
41

42
#if REALM_ENABLE_SYNC
43
#include <realm/object-store/sync/impl/sync_file.hpp>
44
#include <realm/object-store/sync/sync_manager.hpp>
45
#include <realm/object-store/sync/sync_user.hpp>
46
#include <realm/object-store/sync/sync_session.hpp>
47

48
#include <realm/sync/config.hpp>
49
#include <realm/sync/history.hpp>
50
#include <realm/sync/noinst/client_history_impl.hpp>
51
#include <realm/history.hpp>
52
#endif
53

54
#include <thread>
55

56
using namespace realm;
57
using namespace realm::_impl;
58

59
namespace {
60
class CountGuard {
61
public:
62
    CountGuard(size_t& count)
63
        : m_count(count)
64
    {
382,198✔
65
        ++m_count;
382,198✔
66
    }
382,198✔
67
    ~CountGuard()
68
    {
382,203✔
69
        --m_count;
382,203✔
70
    }
382,203✔
71

72
private:
73
    size_t& m_count;
74
};
75
} // namespace
76

77
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
78
             MakeSharedTag)
79
    : m_config(std::move(config))
80
    , m_frozen_version(version)
81
    , m_scheduler(m_config.scheduler)
82
{
57,292✔
83
    if (version) {
57,292✔
84
        m_auto_refresh = false;
997✔
85
        REALM_ASSERT(*version != VersionID());
997✔
86
    }
997✔
87
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
56,295✔
88
        m_transaction = coordinator->begin_read();
41,493✔
89
        read_schema_from_group_if_needed();
41,493✔
90
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
41,493✔
91
        m_transaction = nullptr;
41,493✔
92
    }
41,493✔
93
    m_coordinator = std::move(coordinator);
57,292✔
94
}
57,292✔
95

96
Realm::~Realm()
97
{
57,292✔
98
    if (m_transaction) {
57,292✔
99
        // Wait for potential syncing to finish
25,317✔
100
        m_transaction->prepare_for_close();
51,424✔
101
        call_completion_callbacks();
51,424✔
102
    }
51,424✔
103

28,231✔
104
    if (m_coordinator) {
57,292✔
105
        m_coordinator->unregister_realm(this);
53,275✔
106
    }
53,275✔
107
}
57,292✔
108

109
Group& Realm::read_group()
110
{
787,984✔
111
    return transaction();
787,984✔
112
}
787,984✔
113

114
Transaction& Realm::transaction()
115
{
1,935,818✔
116
    verify_open();
1,935,818✔
117
    if (!m_transaction)
1,935,818✔
118
        begin_read(m_frozen_version.value_or(VersionID{}));
110,047✔
119
    return *m_transaction;
1,935,818✔
120
}
1,935,818✔
121

122
Transaction& Realm::transaction() const
123
{
597,645✔
124
    // one day we should change the way we use constness
293,005✔
125
    Realm* nc_realm = const_cast<Realm*>(this);
597,645✔
126
    return nc_realm->transaction();
597,645✔
127
}
597,645✔
128

129
std::shared_ptr<Transaction> Realm::transaction_ref()
130
{
114,775✔
131
    return m_transaction;
114,775✔
132
}
114,775✔
133

134
std::shared_ptr<Transaction> Realm::duplicate() const
135
{
144✔
136
    auto version = read_transaction_version(); // does the validity check first
144✔
137
    return m_coordinator->begin_read(version, is_frozen());
144✔
138
}
144✔
139

140
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
141
{
6,504✔
142
    return realm.m_coordinator->m_db;
6,504✔
143
}
6,504✔
144

145
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
146
{
46✔
147
    realm.begin_read(version_id);
46✔
148
}
46✔
149

150
void Realm::begin_read(VersionID version_id)
151
{
110,093✔
152
    REALM_ASSERT(!m_transaction);
110,093✔
153
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
110,093✔
154
    add_schema_change_handler();
110,093✔
155
    read_schema_from_group_if_needed();
110,093✔
156
}
110,093✔
157

158
SharedRealm Realm::get_shared_realm(Config config)
159
{
55,521✔
160
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
55,521✔
161
    return coordinator->get_realm(std::move(config), util::none);
55,521✔
162
}
55,521✔
163

164
SharedRealm Realm::get_frozen_realm(Config config, VersionID version)
165
{
274✔
166
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
274✔
167
    return coordinator->get_realm(std::move(config), version);
274✔
168
}
274✔
169

170
SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler)
171
{
138✔
172
    if (!scheduler)
138✔
173
        scheduler = util::Scheduler::make_default();
66✔
174
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
138✔
175
    REALM_ASSERT(realm);
138✔
176
    auto& config = realm->config();
138✔
177
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
138✔
178
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
138✔
179
        return realm;
2✔
180
    realm->m_scheduler = scheduler;
136✔
181
    coordinator->bind_to_context(*realm);
136✔
182
    return realm;
136✔
183
}
136✔
184

185
#if REALM_ENABLE_SYNC
186
std::shared_ptr<AsyncOpenTask> Realm::get_synchronized_realm(Config config)
187
{
86✔
188
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
86✔
189
    return coordinator->get_synchronized_realm(std::move(config));
86✔
190
}
86✔
191

192
std::shared_ptr<SyncSession> Realm::sync_session() const
193
{
388✔
194
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
387✔
195
}
388✔
196

197
sync::SubscriptionSet Realm::get_latest_subscription_set()
198
{
495✔
199
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
495✔
200
        throw IllegalOperation("Flexible sync is not enabled");
4✔
201
    }
4✔
202
    // If there is a subscription store, then return the active set
245✔
203
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
491✔
204
    REALM_ASSERT(flx_sub_store);
491✔
205
    return flx_sub_store->get_latest();
491✔
206
}
491✔
207

208
sync::SubscriptionSet Realm::get_active_subscription_set()
209
{
42✔
210
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
42✔
211
        throw IllegalOperation("Flexible sync is not enabled");
6✔
212
    }
6✔
213
    // If there is a subscription store, then return the active set
18✔
214
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
36✔
215
    REALM_ASSERT(flx_sub_store);
36✔
216
    return flx_sub_store->get_active();
36✔
217
}
36✔
218
#endif
219

220
void Realm::set_schema(Schema const& reference, Schema schema)
221
{
33,462✔
222
    m_dynamic_schema = false;
33,462✔
223
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
33,462✔
224
    m_schema = std::move(schema);
33,462✔
225
    notify_schema_changed();
33,462✔
226
}
33,462✔
227

228
void Realm::read_schema_from_group_if_needed()
229
{
151,578✔
230
    if (m_config.immutable()) {
151,578✔
231
        REALM_ASSERT(m_transaction);
118✔
232
        if (m_schema.empty()) {
118✔
233
            m_schema_version = ObjectStore::get_schema_version(*m_transaction);
64✔
234
            m_schema = ObjectStore::schema_from_group(*m_transaction);
64✔
235
            m_schema_transaction_version = m_transaction->get_version_of_current_transaction().version;
64✔
236
        }
64✔
237
        return;
118✔
238
    }
118✔
239

74,634✔
240
    Group& group = read_group();
151,460✔
241
    auto current_version = transaction().get_version_of_current_transaction().version;
151,460✔
242
    if (m_schema_transaction_version == current_version)
151,460✔
243
        return;
108,967✔
244

20,969✔
245
    m_schema_transaction_version = current_version;
42,493✔
246
    m_schema_version = ObjectStore::get_schema_version(group);
42,493✔
247
    auto schema = ObjectStore::schema_from_group(group);
42,493✔
248

20,969✔
249
    if (m_coordinator)
42,493✔
250
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,062✔
251

20,969✔
252
    if (m_dynamic_schema) {
42,493✔
253
        if (m_schema == schema) {
42,021✔
254
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
10,673✔
255
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
21,581✔
256
        }
21,581✔
257
        else {
20,440✔
258
            // The structure of the schema has changed, so replace our copy of the schema.
10,064✔
259
            m_schema = std::move(schema);
20,440✔
260
        }
20,440✔
261
    }
42,021✔
262
    else {
472✔
263
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
472✔
264
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
472✔
265
    }
472✔
266
    notify_schema_changed();
42,493✔
267
}
42,493✔
268

269
bool Realm::reset_file(Schema& schema, std::vector<SchemaChange>& required_changes)
270
{
20✔
271
    // FIXME: this does not work if multiple processes try to open the file at
10✔
272
    // the same time, or even multiple threads if there is not any external
10✔
273
    // synchronization. The latter is probably fixable, but making it
10✔
274
    // multi-process-safe requires some sort of multi-process exclusive lock
10✔
275
    m_transaction = nullptr;
20✔
276
    m_coordinator->delete_and_reopen();
20✔
277

10✔
278
    m_schema = ObjectStore::schema_from_group(read_group());
20✔
279
    m_schema_version = ObjectStore::get_schema_version(read_group());
20✔
280
    required_changes = m_schema.compare(schema, m_config.schema_mode);
20✔
281
    m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
20✔
282
    return false;
20✔
283
}
20✔
284

285
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
286
                                                  uint64_t version)
287
{
54,996✔
288
    if (version == m_schema_version && changes.empty())
54,996✔
289
        return false;
33,158✔
290

10,800✔
291
    switch (m_config.schema_mode) {
21,838✔
292
        case SchemaMode::Automatic:
13,760✔
293
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
13,760✔
294
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
295
            return true;
13,758✔
296

6,841✔
297
        case SchemaMode::Immutable:
6,847✔
298
            if (version != m_schema_version)
12✔
299
                throw InvalidSchemaVersionException(m_schema_version, version, true);
2✔
300
            REALM_FALLTHROUGH;
10✔
301
        case SchemaMode::ReadOnly:
36✔
302
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
36✔
303
            return m_schema_version == ObjectStore::NotVersioned;
36✔
304

5✔
305
        case SchemaMode::SoftResetFile:
32✔
306
            if (m_schema_version == ObjectStore::NotVersioned)
32✔
307
                return true;
18✔
308
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
14✔
309
                return true;
8✔
310
            REALM_FALLTHROUGH;
6✔
311
        case SchemaMode::HardResetFile:
20✔
312
            reset_file(schema, changes);
20✔
313
            return true;
20✔
314

3✔
315
        case SchemaMode::AdditiveDiscovered:
3,910✔
316
        case SchemaMode::AdditiveExplicit: {
7,866✔
317
            bool will_apply_index_changes = version > m_schema_version;
7,866✔
318
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
7,866✔
319
                return true;
7,738✔
320
            return version != m_schema_version;
128✔
321
        }
128✔
322

64✔
323
        case SchemaMode::Manual:
128✔
324
            if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
128✔
325
                throw InvalidSchemaVersionException(m_schema_version, version, false);
2✔
326
            if (version == m_schema_version) {
126✔
327
                ObjectStore::verify_no_changes_required(changes);
28✔
328
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
28✔
329
            }
28✔
330
            return true;
126✔
331
    }
×
332
    REALM_COMPILER_HINT_UNREACHABLE();
×
333
}
×
334

335
Schema Realm::get_full_schema()
336
{
55,224✔
337
    if (!m_config.immutable())
55,224✔
338
        do_refresh();
55,158✔
339

27,200✔
340
    // If the user hasn't specified a schema previously then m_schema is always
27,200✔
341
    // the full schema if it's been read
27,200✔
342
    if (m_dynamic_schema && !m_schema.empty())
55,224✔
343
        return m_schema;
33,238✔
344

10,880✔
345
    // Otherwise we may have a subset of the file's schema, so we need to get
10,880✔
346
    // the complete thing to calculate what changes to make
10,880✔
347
    Schema actual_schema;
21,986✔
348
    uint64_t actual_version;
21,986✔
349
    uint64_t version = -1;
21,986✔
350
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
21,986✔
351
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
21,986✔
352
        return ObjectStore::schema_from_group(read_group());
21,334✔
353
    return actual_schema;
652✔
354
}
652✔
355

356
void Realm::set_schema_subset(Schema schema)
357
{
12✔
358
    verify_thread();
12✔
359
    verify_open();
12✔
360
    REALM_ASSERT(m_dynamic_schema);
12✔
361
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
362

6✔
363
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
364
    switch (m_config.schema_mode) {
12✔
365
        case SchemaMode::Automatic:
2✔
366
        case SchemaMode::SoftResetFile:
2✔
367
        case SchemaMode::HardResetFile:
2✔
368
            ObjectStore::verify_no_migration_required(changes);
2✔
369
            break;
2✔
370

1✔
371
        case SchemaMode::Immutable:
1✔
372
        case SchemaMode::ReadOnly:
✔
373
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
374
            break;
×
375

376
        case SchemaMode::AdditiveDiscovered:
4✔
377
        case SchemaMode::AdditiveExplicit:
8✔
378
            ObjectStore::verify_valid_additive_changes(changes);
8✔
379
            break;
8✔
380

4✔
381
        case SchemaMode::Manual:
4✔
382
            ObjectStore::verify_no_changes_required(changes);
×
383
            break;
×
384
    }
10✔
385

5✔
386
    set_schema(m_schema, std::move(schema));
10✔
387
}
10✔
388

389
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
390
                          DataInitializationFunction initialization_function, bool in_transaction)
391
{
55,232✔
392
    uint64_t validation_mode = SchemaValidationMode::Basic;
55,232✔
393
#if REALM_ENABLE_SYNC
55,232✔
394
    if (auto sync_config = m_config.sync_config) {
55,232✔
395
        validation_mode |=
1,440✔
396
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,214✔
397
    }
1,440✔
398
#endif
55,232✔
399
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
55,232✔
400
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
8,502✔
401
    }
8,502✔
402

27,204✔
403
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
55,232✔
404

27,204✔
405
    bool was_in_read_transaction = is_in_read_transaction();
55,232✔
406
    Schema actual_schema = get_full_schema();
55,232✔
407

27,204✔
408
    // Frozen Realms never modify the schema on disk and we just need to verify
27,204✔
409
    // that the requested schema is compatible with what actually exists on disk
27,204✔
410
    // at that frozen version. Tables are allowed to be missing as those can be
27,204✔
411
    // represented by empty Results, but tables which exist must have all of the
27,204✔
412
    // requested properties with the correct type.
27,204✔
413
    if (m_frozen_version) {
55,232✔
414
        ObjectStore::verify_compatible_for_immutable_and_readonly(
240✔
415
            actual_schema.compare(schema, m_config.schema_mode, true));
240✔
416
        set_schema(actual_schema, std::move(schema));
240✔
417
        return;
240✔
418
    }
240✔
419

27,084✔
420
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
54,992✔
421
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
54,992✔
422
        if (!was_in_read_transaction)
33,230✔
423
            m_transaction = nullptr;
33,200✔
424
        set_schema(actual_schema, std::move(schema));
33,230✔
425
        return;
33,230✔
426
    }
33,230✔
427
    // Either the schema version has changed or we need to do non-migration changes
10,768✔
428

10,768✔
429
    // Cancel the write transaction if we exit this function before committing it
10,768✔
430
    auto cleanup = util::make_scope_exit([&]() noexcept {
21,762✔
431
        // When in_transaction is true, caller is responsible to cancel the transaction.
10,722✔
432
        if (!in_transaction && is_in_transaction())
21,670✔
433
            cancel_transaction();
110✔
434
        if (!was_in_read_transaction)
21,670✔
435
            m_transaction = nullptr;
21,318✔
436
    });
21,670✔
437

10,768✔
438
    if (!in_transaction) {
21,762✔
439
        transaction().promote_to_write();
21,644✔
440

10,709✔
441
        // Beginning the write transaction may have advanced the version and left
10,709✔
442
        // us with nothing to do if someone else initialized the schema on disk
10,709✔
443
        if (m_new_schema) {
21,644✔
444
            actual_schema = *m_new_schema;
14✔
445
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
14✔
446
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
14✔
447
                cancel_transaction();
2✔
448
                cache_new_schema();
2✔
449
                set_schema(actual_schema, std::move(schema));
2✔
450
                return;
2✔
451
            }
2✔
452
        }
21,642✔
453
        cache_new_schema();
21,642✔
454
    }
21,642✔
455

10,768✔
456
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
21,761✔
457

10,767✔
458
    uint64_t old_schema_version = m_schema_version;
21,760✔
459
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
21,760✔
460
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
21,673✔
461
                    m_config.schema_mode == SchemaMode::ReadOnly;
17,757✔
462
    if (migration_function && !additive) {
21,760✔
463
        auto wrapper = [&] {
2,247✔
464
            auto config = m_config;
210✔
465
            config.schema_mode = SchemaMode::ReadOnly;
210✔
466
            config.schema = util::none;
210✔
467
            // Don't go through the normal codepath for opening a Realm because
105✔
468
            // we're using a mismatched config
105✔
469
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, MakeSharedTag{});
210✔
470
            // block autorefresh for the old realm
105✔
471
            old_realm->m_auto_refresh = false;
210✔
472
            migration_function(old_realm, shared_from_this(), m_schema);
210✔
473
        };
210✔
474

2,142✔
475
        // migration function needs to see the target schema on the "new" Realm
2,142✔
476
        std::swap(m_schema, schema);
4,355✔
477
        std::swap(m_schema_version, version);
4,355✔
478
        m_in_migration = true;
4,355✔
479
        auto restore = util::make_scope_exit([&]() noexcept {
4,355✔
480
            std::swap(m_schema, schema);
4,355✔
481
            std::swap(m_schema_version, version);
4,355✔
482
            m_in_migration = false;
4,355✔
483
        });
4,355✔
484

2,142✔
485
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
4,355✔
486
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
4,355✔
487
                                          wrapper);
4,355✔
488
    }
4,355✔
489
    else {
17,405✔
490
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
17,405✔
491
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations);
17,405✔
492
        REALM_ASSERT_DEBUG(additive ||
17,405✔
493
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
17,405✔
494
    }
17,405✔
495

10,767✔
496
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
21,760✔
497
        // Initialization function needs to see the latest schema
15✔
498
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
499
        std::swap(m_schema, schema);
30✔
500
        std::swap(m_schema_version, temp_version);
30✔
501
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
502
            std::swap(m_schema, schema);
30✔
503
            std::swap(m_schema_version, temp_version);
30✔
504
        });
30✔
505
        initialization_function(shared_from_this());
30✔
506
    }
30✔
507

10,767✔
508
    m_schema = std::move(schema);
21,760✔
509
    m_new_schema = ObjectStore::schema_from_group(read_group());
21,760✔
510
    m_schema_version = ObjectStore::get_schema_version(read_group());
21,760✔
511
    m_dynamic_schema = false;
21,760✔
512
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
21,760✔
513

10,767✔
514
    if (!in_transaction) {
21,760✔
515
        m_coordinator->commit_write(*this);
21,532✔
516
        cache_new_schema();
21,532✔
517
    }
21,532✔
518

10,767✔
519
    notify_schema_changed();
21,760✔
520
}
21,760✔
521

522
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
523
{
4✔
524
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
525
}
4✔
526

527
void Realm::add_schema_change_handler()
528
{
110,085✔
529
    if (m_config.immutable())
110,085✔
530
        return;
56✔
531
    m_transaction->set_schema_change_notification_handler([&] {
110,029✔
532
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,455✔
533
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,455✔
534
        if (m_dynamic_schema) {
3,455✔
535
            m_schema = *m_new_schema;
34✔
536
        }
34✔
537
        else {
3,421✔
538
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,421✔
539
        }
3,421✔
540
        notify_schema_changed();
3,455✔
541
    });
3,455✔
542
}
110,029✔
543

544
void Realm::cache_new_schema()
545
{
203,496✔
546
    if (is_closed()) {
203,496✔
547
        return;
×
548
    }
×
549

99,751✔
550
    auto new_version = transaction().get_version_of_current_transaction().version;
203,496✔
551
    if (m_new_schema)
203,496✔
552
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
25,013✔
553
    else
178,483✔
554
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
178,483✔
555
    m_schema_transaction_version = new_version;
203,496✔
556
    m_new_schema = util::none;
203,496✔
557
}
203,496✔
558

559
void Realm::translate_schema_error()
560
{
4✔
561
    // Read the new (incompatible) schema without changing our read transaction
2✔
562
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
563

2✔
564
    // Should always throw
2✔
565
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
566

2✔
567
    // Something strange happened so just rethrow the old exception
2✔
568
    throw;
4✔
569
}
4✔
570

571
void Realm::notify_schema_changed()
572
{
100,962✔
573
    if (m_binding_context) {
100,962✔
574
        m_binding_context->schema_did_change(m_schema);
26✔
575
    }
26✔
576
}
100,962✔
577

578
static void check_can_create_write_transaction(const Realm* realm)
579
{
125,548✔
580
    realm->verify_thread();
125,548✔
581
    realm->verify_open();
125,548✔
582
    if (realm->config().immutable() || realm->config().read_only()) {
125,548✔
583
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
584
    }
6✔
585
    if (realm->is_frozen()) {
125,542✔
586
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
587
    }
2✔
588
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
125,540✔
589
        throw WrongTransactionState(
×
590
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
591
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
592
    }
×
593
}
125,540✔
594

595
void Realm::verify_thread() const
596
{
595,918✔
597
    if (m_scheduler && !m_scheduler->is_on_thread())
595,918✔
598
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
599
}
595,918✔
600

601
void Realm::verify_in_write() const
602
{
11,948✔
603
    if (!is_in_transaction()) {
11,948✔
604
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
605
    }
4✔
606
}
11,948✔
607

608
void Realm::verify_open() const
609
{
2,192,074✔
610
    if (is_closed()) {
2,192,074✔
611
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
612
    }
44✔
613
}
2,192,074✔
614

615
bool Realm::verify_notifications_available(bool throw_on_error) const
616
{
42,698✔
617
    if (is_frozen()) {
42,698✔
618
        if (throw_on_error)
56✔
619
            throw WrongTransactionState(
12✔
620
                "Notifications are not available on frozen collections since they do not change.");
12✔
621
        return false;
44✔
622
    }
44✔
623
    if (config().immutable()) {
42,642✔
624
        if (throw_on_error)
2✔
625
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
626
        return false;
2✔
627
    }
2✔
628
    if (throw_on_error) {
42,640✔
629
        if (m_transaction && m_transaction->get_commit_size() > 0)
9,865✔
630
            throw WrongTransactionState(
2✔
631
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
632
    }
32,775✔
633
    else {
32,775✔
634
        // Don't create implicit notifiers inside write transactions even if
16,297✔
635
        // we could as it wouldn't actually be used
16,297✔
636
        if (is_in_transaction())
32,775✔
637
            return false;
20,612✔
638
    }
22,026✔
639

10,959✔
640
    return true;
22,026✔
641
}
22,026✔
642

643
VersionID Realm::read_transaction_version() const
644
{
4,867✔
645
    verify_thread();
4,867✔
646
    verify_open();
4,867✔
647
    REALM_ASSERT(m_transaction);
4,867✔
648
    return m_transaction->get_version_of_current_transaction();
4,867✔
649
}
4,867✔
650

651
uint_fast64_t Realm::get_number_of_versions() const
652
{
125,534✔
653
    verify_open();
125,534✔
654
    return m_coordinator->get_number_of_versions();
125,534✔
655
}
125,534✔
656

657
bool Realm::is_in_transaction() const noexcept
658
{
692,290✔
659
    return !m_config.immutable() && !is_closed() && m_transaction &&
692,290✔
660
           transaction().get_transact_stage() == DB::transact_Writing;
644,190✔
661
}
692,290✔
662

663
bool Realm::is_in_async_transaction() const noexcept
664
{
969✔
665
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
969✔
666
}
969✔
667

668
util::Optional<VersionID> Realm::current_transaction_version() const
669
{
103,929✔
670
    util::Optional<VersionID> ret;
103,929✔
671
    if (m_transaction) {
103,929✔
672
        ret = m_transaction->get_version_of_current_transaction();
103,701✔
673
    }
103,701✔
674
    else if (m_frozen_version) {
228✔
675
        ret = m_frozen_version;
×
676
    }
×
677
    return ret;
103,929✔
678
}
103,929✔
679

680
// Get the version of the latest snapshot
681
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
682
{
62✔
683
    util::Optional<DB::version_type> ret;
62✔
684
    if (m_transaction) {
62✔
685
        ret = m_transaction->get_version_of_latest_snapshot();
60✔
686
    }
60✔
687
    return ret;
62✔
688
}
62✔
689

690
void Realm::enable_wait_for_change()
691
{
2✔
692
    verify_open();
2✔
693
    m_coordinator->enable_wait_for_change();
2✔
694
}
2✔
695

696
bool Realm::wait_for_change()
697
{
6✔
698
    verify_open();
6✔
699
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
700
        return false;
4✔
701
    }
4✔
702
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
703
}
2✔
704

705
void Realm::wait_for_change_release()
706
{
2✔
707
    verify_open();
2✔
708
    m_coordinator->wait_for_change_release();
2✔
709
}
2✔
710

711
bool Realm::has_pending_async_work() const
712
{
937✔
713
    verify_thread();
937✔
714
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
937✔
715
}
937✔
716

717
void Realm::run_writes_on_proper_thread()
718
{
8✔
719
    m_scheduler->invoke([self = shared_from_this()] {
8✔
720
        self->run_writes();
8✔
721
    });
8✔
722
}
8✔
723

724
void Realm::call_completion_callbacks()
725
{
174,076✔
726
    if (m_is_running_async_commit_completions) {
174,076✔
727
        return;
4✔
728
    }
4✔
729

85,339✔
730
    CountGuard sending_completions(m_is_running_async_commit_completions);
174,072✔
731
    auto error = m_transaction->get_commit_exception();
174,072✔
732
    auto completions = std::move(m_async_commit_q);
174,072✔
733
    m_async_commit_q.clear();
174,072✔
734
    for (auto& cb : completions) {
86,718✔
735
        if (!cb.when_completed)
2,510✔
736
            continue;
16✔
737
        if (m_async_exception_handler) {
2,494✔
738
            try {
2✔
739
                cb.when_completed(error);
2✔
740
            }
2✔
741
            catch (...) {
2✔
742
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
743
            }
2✔
744
        }
2✔
745
        else {
2,492✔
746
            cb.when_completed(error);
2,492✔
747
        }
2,492✔
748
    }
2,494✔
749
}
174,072✔
750

751
void Realm::run_async_completions()
752
{
331✔
753
    call_completion_callbacks();
331✔
754
    check_pending_write_requests();
331✔
755
}
331✔
756

757
void Realm::check_pending_write_requests()
758
{
59,135✔
759
    if (!m_async_write_q.empty()) {
59,135✔
760
        if (m_transaction->is_async()) {
421✔
761
            run_writes_on_proper_thread();
8✔
762
        }
8✔
763
        else {
413✔
764
            m_coordinator->async_request_write_mutex(*this);
413✔
765
        }
413✔
766
    }
421✔
767
}
59,135✔
768

769
void Realm::end_current_write(bool check_pending)
770
{
3,295✔
771
    if (!m_transaction) {
3,295✔
772
        return;
2✔
773
    }
2✔
774
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,293✔
775
        m_scheduler->invoke([self = std::move(self), this]() mutable {
331✔
776
            run_async_completions();
331✔
777
            self.reset();
331✔
778
        });
331✔
779
    });
331✔
780
    if (check_pending && m_async_commit_q.empty()) {
3,293✔
781
        check_pending_write_requests();
2,837✔
782
    }
2,837✔
783
}
3,293✔
784

785
void Realm::run_writes()
786
{
502✔
787
    if (!m_transaction) {
502✔
788
        // Realm might have been closed
789
        return;
×
790
    }
×
791
    if (m_transaction->is_synchronizing()) {
502✔
792
        // Wait for the synchronization complete callback before we run more
793
        // writes as we can't add commits while in that state
794
        return;
×
795
    }
×
796
    if (is_in_transaction()) {
502✔
797
        // This is scheduled asynchronously after acquiring the write lock, so
1✔
798
        // in that time a synchronous transaction may have been started. If so,
1✔
799
        // we'll be re-invoked when that transaction ends.
1✔
800
        return;
2✔
801
    }
2✔
802

125✔
803
    CountGuard running_writes(m_is_running_async_writes);
500✔
804
    int run_limit = 20; // max number of commits without full sync to disk
500✔
805
    // this is tricky
125✔
806
    //  - each pending call may itself add other async writes
125✔
807
    //  - the 'run' will terminate as soon as a commit without grouping is requested
125✔
808
    while (!m_async_write_q.empty() && m_transaction) {
2,710✔
809
        // We might have made a sync commit and thereby given up the write lock
1,150✔
810
        if (!m_transaction->holds_write_mutex()) {
2,661✔
811
            return;
115✔
812
        }
115✔
813

1,149✔
814
        do_begin_transaction();
2,546✔
815

1,149✔
816
        auto write_desc = std::move(m_async_write_q.front());
2,546✔
817
        m_async_write_q.pop_front();
2,546✔
818

1,149✔
819
        // prevent any calls to commit/cancel during a simple notification
1,149✔
820
        m_notify_only = write_desc.notify_only;
2,546✔
821
        m_async_commit_barrier_requested = false;
2,546✔
822
        auto prev_version = m_transaction->get_version();
2,546✔
823
        try {
2,546✔
824
            write_desc.writer();
2,546✔
825
        }
2,546✔
826
        catch (const std::exception&) {
1,152✔
827
            if (m_transaction) {
6✔
828
                transaction::cancel(*m_transaction, m_binding_context.get());
4✔
829
            }
4✔
830
            m_notify_only = false;
6✔
831

3✔
832
            if (m_async_exception_handler) {
6✔
833
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
834
                continue;
2✔
835
            }
2✔
836
            end_current_write();
4✔
837
            throw;
4✔
838
        }
4✔
839

1,146✔
840
        // if we've merely delivered a notification, the full transaction will follow later
1,146✔
841
        // and terminate with a call to async commit or async cancel
1,146✔
842
        if (m_notify_only) {
2,540✔
843
            m_notify_only = false;
12✔
844
            return;
12✔
845
        }
12✔
846

1,140✔
847
        // Realm may have been closed in the write function
1,140✔
848
        if (!m_transaction) {
2,528✔
849
            return;
20✔
850
        }
20✔
851

1,130✔
852
        auto new_version = m_transaction->get_version();
2,508✔
853
        // if we've run the full transaction, there is follow up work to do:
1,130✔
854
        if (new_version > prev_version) {
2,508✔
855
            // A commit was done during callback
1,125✔
856
            --run_limit;
2,498✔
857
            if (run_limit <= 0)
2,498✔
858
                break;
102✔
859
        }
10✔
860
        else {
10✔
861
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
10✔
862
                // Still in writing stage - we make a rollback
4✔
863
                transaction::cancel(transaction(), m_binding_context.get());
8✔
864
            }
8✔
865
        }
10✔
866
        if (m_async_commit_barrier_requested)
2,457✔
867
            break;
198✔
868
    }
2,406✔
869

125✔
870
    end_current_write();
368✔
871
}
349✔
872

873
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
874
{
2,560✔
875
    check_can_create_write_transaction(this);
2,560✔
876
    if (m_is_running_async_commit_completions) {
2,560✔
877
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
878
    }
×
879
    if (!m_scheduler->can_invoke()) {
2,560✔
880
        throw WrongTransactionState(
×
881
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
882
    }
×
883
    REALM_ASSERT(the_write_block);
2,560✔
884

1,156✔
885
    // make sure we have a (at least a) read transaction
1,156✔
886
    transaction();
2,560✔
887
    auto handle = m_async_commit_handle++;
2,560✔
888
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,560✔
889

1,156✔
890
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,560✔
891
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,236✔
892
        m_coordinator->async_request_write_mutex(*this);
125✔
893
    }
125✔
894
    return handle;
2,560✔
895
}
2,560✔
896

897
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
898
    -> AsyncHandle
899
{
2,514✔
900
    check_can_create_write_transaction(this);
2,514✔
901
    if (m_is_running_async_commit_completions) {
2,514✔
902
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
903
    }
×
904
    if (!is_in_transaction()) {
2,514✔
905
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
906
    }
×
907

1,133✔
908
    m_transaction->promote_to_async();
2,514✔
909
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,514✔
910
    REALM_ASSERT(!m_notify_only);
2,514✔
911
    // auditing is not supported
1,133✔
912
    REALM_ASSERT(!audit_context());
2,514✔
913
    // grab a version lock on current version, push it along with the done block
1,133✔
914
    // do in-buffer-cache commit_transaction();
1,133✔
915
    auto handle = m_async_commit_handle++;
2,514✔
916
    m_async_commit_q.push_back({std::move(completion), handle});
2,514✔
917
    try {
2,514✔
918
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,514✔
919
    }
2,514✔
920
    catch (...) {
1,135✔
921
        // If the exception happened before the commit, we need to roll back the
2✔
922
        // transaction and remove the completion handler from the queue
2✔
923
        if (is_in_transaction()) {
4✔
924
            // Exception happened before the commit, so roll back the transaction
1✔
925
            // and remove the completion handler from the queue
1✔
926
            cancel_transaction();
2✔
927
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
928
                return e.handle == handle;
2✔
929
            });
2✔
930
            if (it != m_async_commit_q.end()) {
2✔
931
                m_async_commit_q.erase(it);
2✔
932
            }
2✔
933
        }
2✔
934
        else if (m_transaction) {
2✔
935
            end_current_write(false);
2✔
936
        }
2✔
937
        throw;
4✔
938
    }
4✔
939

1,130✔
940
    if (m_is_running_async_writes) {
2,508✔
941
        // we're called from with the callback loop and it will take care of releasing lock
1,122✔
942
        // if applicable, and of triggering followup runs of callbacks
1,122✔
943
        if (!allow_grouping) {
2,368✔
944
            m_async_commit_barrier_requested = true;
206✔
945
        }
206✔
946
    }
2,368✔
947
    else {
140✔
948
        // we're called from outside the callback loop so we have to take care of
8✔
949
        // releasing any lock and of keeping callbacks coming.
8✔
950
        if (allow_grouping) {
140✔
951
            run_writes();
×
952
        }
×
953
        else {
140✔
954
            end_current_write(false);
140✔
955
        }
140✔
956
    }
140✔
957
    return handle;
2,508✔
958
}
2,508✔
959

960
bool Realm::async_cancel_transaction(AsyncHandle handle)
961
{
6✔
962
    verify_thread();
6✔
963
    verify_open();
6✔
964
    auto compare = [handle](auto& elem) {
5✔
965
        return elem.handle == handle;
4✔
966
    };
4✔
967

3✔
968
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
6✔
969
    if (it1 != m_async_write_q.end()) {
6✔
970
        m_async_write_q.erase(it1);
2✔
971
        return true;
2✔
972
    }
2✔
973
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
4✔
974
    if (it2 != m_async_commit_q.end()) {
4✔
975
        // Just delete the callback. It is important that we know
1✔
976
        // that there are still commits pending.
1✔
977
        it2->when_completed = nullptr;
2✔
978
        return true;
2✔
979
    }
2✔
980
    return false;
2✔
981
}
2✔
982

983
void Realm::begin_transaction()
984
{
61,695✔
985
    check_can_create_write_transaction(this);
61,695✔
986

30,304✔
987
    if (is_in_transaction()) {
61,695✔
988
        throw WrongTransactionState("The Realm is already in a write transaction");
×
989
    }
×
990

30,304✔
991
    // Any of the callbacks to user code below could drop the last remaining
30,304✔
992
    // strong reference to `this`
30,304✔
993
    auto retain_self = shared_from_this();
61,695✔
994

30,304✔
995
    // make sure we have a read transaction
30,304✔
996
    read_group();
61,695✔
997

30,304✔
998
    do_begin_transaction();
61,695✔
999
}
61,695✔
1000

1001
void Realm::do_begin_transaction()
1002
{
64,233✔
1003
    CountGuard sending_notifications(m_is_sending_notifications);
64,233✔
1004
    try {
64,233✔
1005
        m_coordinator->promote_to_write(*this);
64,233✔
1006
    }
64,233✔
1007
    catch (_impl::UnsupportedSchemaChange const&) {
31,449✔
1008
        translate_schema_error();
×
1009
    }
×
1010
    cache_new_schema();
64,233✔
1011

31,449✔
1012
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
64,233✔
1013
        call_completion_callbacks();
62,183✔
1014
    }
62,183✔
1015
}
64,233✔
1016

1017
void Realm::commit_transaction()
1018
{
55,963✔
1019
    check_can_create_write_transaction(this);
55,963✔
1020

27,434✔
1021
    if (!is_in_transaction()) {
55,963✔
1022
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1023
    }
×
1024

27,434✔
1025
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
55,963✔
1026
    if (auto audit = audit_context()) {
55,963✔
1027
        audit->prepare_for_write(prev_version);
91✔
1028
    }
91✔
1029

27,434✔
1030
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
55,963✔
1031
    cache_new_schema();
55,963✔
1032

27,434✔
1033
    // Realm might have been closed
27,434✔
1034
    if (m_transaction) {
55,963✔
1035
        // Any previous async commits got flushed along with the sync commit
27,433✔
1036
        call_completion_callbacks();
55,961✔
1037
        // If we have pending async writes we need to rerequest the write mutex
27,433✔
1038
        check_pending_write_requests();
55,961✔
1039
    }
55,961✔
1040
    if (auto audit = audit_context()) {
55,963✔
1041
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1042
    }
91✔
1043
}
55,963✔
1044

1045
void Realm::cancel_transaction()
1046
{
2,816✔
1047
    check_can_create_write_transaction(this);
2,816✔
1048

1,412✔
1049
    if (m_is_running_async_commit_completions) {
2,816✔
1050
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1051
    }
×
1052
    if (!is_in_transaction()) {
2,816✔
1053
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1054
    }
×
1055

1,412✔
1056
    transaction::cancel(transaction(), m_binding_context.get());
2,816✔
1057

1,412✔
1058
    if (m_transaction && !m_is_running_async_writes) {
2,816✔
1059
        if (m_async_write_q.empty()) {
2,808✔
1060
            end_current_write();
2,800✔
1061
        }
2,800✔
1062
        else {
8✔
1063
            check_pending_write_requests();
8✔
1064
        }
8✔
1065
    }
2,808✔
1066
}
2,816✔
1067

1068
void Realm::invalidate()
1069
{
246✔
1070
    verify_thread();
246✔
1071
    verify_open();
246✔
1072

123✔
1073
    if (m_is_sending_notifications) {
246✔
1074
        // This was originally because closing the Realm during notification
3✔
1075
        // sending would break things, but we now support that. However, it's a
3✔
1076
        // breaking change so we keep the old behavior for now.
3✔
1077
        return;
6✔
1078
    }
6✔
1079

120✔
1080
    if (is_in_transaction()) {
240✔
1081
        cancel_transaction();
172✔
1082
    }
172✔
1083

120✔
1084
    do_invalidate();
240✔
1085
}
240✔
1086

1087
void Realm::do_invalidate()
1088
{
4,255✔
1089
    if (!m_config.immutable() && m_transaction) {
4,255✔
1090
        m_transaction->prepare_for_close();
4,177✔
1091
        call_completion_callbacks();
4,177✔
1092
        transaction().close();
4,177✔
1093
    }
4,177✔
1094

2,112✔
1095
    m_transaction = nullptr;
4,255✔
1096
    m_async_write_q.clear();
4,255✔
1097
    m_async_commit_q.clear();
4,255✔
1098
}
4,255✔
1099

1100
bool Realm::compact()
1101
{
8✔
1102
    verify_thread();
8✔
1103
    verify_open();
8✔
1104

4✔
1105
    if (m_config.immutable() || m_config.read_only()) {
8✔
1106
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1107
    }
4✔
1108
    if (is_in_transaction()) {
4✔
1109
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1110
    }
×
1111

2✔
1112
    verify_open();
4✔
1113
    m_transaction = nullptr;
4✔
1114
    return m_coordinator->compact();
4✔
1115
}
4✔
1116

1117
void Realm::convert(const Config& config, bool merge_into_existing)
1118
{
60✔
1119
    verify_thread();
60✔
1120
    verify_open();
60✔
1121

30✔
1122
#if REALM_ENABLE_SYNC
60✔
1123
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1124
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1125
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1126

30✔
1127
    if (dst_is_flx_sync && !src_is_flx_sync) {
60✔
1128
        throw IllegalOperation(
4✔
1129
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
4✔
1130
    }
4✔
1131
    if (dst_is_pbs_sync && src_is_flx_sync) {
56✔
1132
        throw IllegalOperation(
2✔
1133
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
2✔
1134
    }
2✔
1135

27✔
1136
#endif
54✔
1137

27✔
1138
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1139
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1140
        destination_realm->begin_transaction();
6✔
1141
        auto destination = destination_realm->transaction_ref();
6✔
1142
        m_transaction->copy_to(destination);
6✔
1143
        destination_realm->commit_transaction();
6✔
1144
        return;
6✔
1145
    }
6✔
1146

24✔
1147
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1148
        throw InvalidEncryptionKey();
×
1149
    }
×
1150

24✔
1151
    auto& tr = transaction();
48✔
1152
    auto repl = tr.get_replication();
48✔
1153
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1154
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1155

24✔
1156
    if (dst_is_sync) {
48✔
1157
        m_coordinator->write_copy(config.path, config.encryption_key.data());
20✔
1158
        if (!src_is_sync) {
20✔
1159
#if REALM_ENABLE_SYNC
8✔
1160
            DBOptions options;
8✔
1161
            if (config.encryption_key.size()) {
8✔
1162
                options.encryption_key = config.encryption_key.data();
×
1163
            }
×
1164
            auto db = DB::create(make_in_realm_history(), config.path, options);
8✔
1165
            db->create_new_history(sync::make_client_replication());
8✔
1166
#endif
8✔
1167
        }
8✔
1168
    }
20✔
1169
    else {
28✔
1170
        tr.write(config.path, config.encryption_key.data());
28✔
1171
    }
28✔
1172
}
48✔
1173

1174
OwnedBinaryData Realm::write_copy()
1175
{
6✔
1176
    verify_thread();
6✔
1177
    BinaryData buffer = read_group().write_to_mem();
6✔
1178

3✔
1179
    // Since OwnedBinaryData does not have a constructor directly taking
3✔
1180
    // ownership of BinaryData, we have to do this to avoid copying the buffer
3✔
1181
    return OwnedBinaryData(std::unique_ptr<char[]>((char*)buffer.data()), buffer.size());
6✔
1182
}
6✔
1183

1184
void Realm::notify()
1185
{
23,940✔
1186
    if (is_closed() || is_in_transaction() || is_frozen()) {
23,940✔
1187
        return;
330✔
1188
    }
330✔
1189

10,353✔
1190
    verify_thread();
23,610✔
1191

10,353✔
1192
    // Any of the callbacks to user code below could drop the last remaining
10,353✔
1193
    // strong reference to `this`
10,353✔
1194
    auto retain_self = shared_from_this();
23,610✔
1195

10,353✔
1196
    if (m_binding_context) {
23,610✔
1197
        m_binding_context->before_notify();
198✔
1198
        if (is_closed() || is_in_transaction()) {
198✔
1199
            return;
×
1200
        }
×
1201
    }
23,610✔
1202

10,353✔
1203
    if (!m_coordinator->can_advance(*this)) {
23,610✔
1204
        CountGuard sending_notifications(m_is_sending_notifications);
16,578✔
1205
        m_coordinator->process_available_async(*this);
16,578✔
1206
        return;
16,578✔
1207
    }
16,578✔
1208

2,917✔
1209
    if (m_binding_context) {
7,032✔
1210
        m_binding_context->changes_available();
70✔
1211

35✔
1212
        // changes_available() may have advanced the read version, and if
35✔
1213
        // so we don't need to do anything further
35✔
1214
        if (!m_coordinator->can_advance(*this))
70✔
1215
            return;
4✔
1216
    }
7,028✔
1217

2,915✔
1218
    CountGuard sending_notifications(m_is_sending_notifications);
7,028✔
1219
    if (m_auto_refresh) {
7,031✔
1220
        if (m_transaction) {
7,028✔
1221
            try {
6,998✔
1222
                m_coordinator->advance_to_ready(*this);
6,998✔
1223
            }
6,998✔
1224
            catch (_impl::UnsupportedSchemaChange const&) {
2,897✔
1225
                translate_schema_error();
×
1226
            }
×
1227
            if (!is_closed())
6,998✔
1228
                cache_new_schema();
6,996✔
1229
        }
6,998✔
1230
        else {
30✔
1231
            if (m_binding_context) {
30✔
1232
                m_binding_context->did_change({}, {});
4✔
1233
            }
4✔
1234
            if (!is_closed()) {
30✔
1235
                m_coordinator->process_available_async(*this);
28✔
1236
            }
28✔
1237
        }
30✔
1238
    }
7,028✔
1239
}
7,028✔
1240

1241
bool Realm::refresh()
1242
{
64,917✔
1243
    verify_thread();
64,917✔
1244
    return do_refresh();
64,917✔
1245
}
64,917✔
1246

1247
bool Realm::do_refresh()
1248
{
120,075✔
1249
    // Frozen Realms never change.
59,414✔
1250
    if (is_frozen()) {
120,075✔
1251
        return false;
244✔
1252
    }
244✔
1253

59,292✔
1254
    if (m_config.immutable()) {
119,831✔
1255
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1256
    }
4✔
1257

59,290✔
1258
    // can't be any new changes if we're in a write transaction
59,290✔
1259
    if (is_in_transaction()) {
119,827✔
1260
        return false;
30✔
1261
    }
30✔
1262
    // don't advance if we're already in the process of advancing as that just
59,275✔
1263
    // makes things needlessly complicated
59,275✔
1264
    if (m_is_sending_notifications) {
119,797✔
1265
        return false;
8✔
1266
    }
8✔
1267

59,271✔
1268
    // Any of the callbacks to user code below could drop the last remaining
59,271✔
1269
    // strong reference to `this`
59,271✔
1270
    auto retain_self = shared_from_this();
119,789✔
1271

59,271✔
1272
    CountGuard sending_notifications(m_is_sending_notifications);
119,789✔
1273
    if (m_binding_context) {
119,789✔
1274
        m_binding_context->before_notify();
138✔
1275
    }
138✔
1276
    if (m_transaction) {
119,789✔
1277
        try {
33,142✔
1278
            bool version_changed = m_coordinator->advance_to_latest(*this);
33,142✔
1279
            if (is_closed())
33,142✔
1280
                return false;
6✔
1281
            cache_new_schema();
33,136✔
1282
            return version_changed;
33,136✔
1283
        }
33,136✔
1284
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1285
            translate_schema_error();
4✔
1286
        }
4✔
1287
    }
33,142✔
1288

59,271✔
1289
    // No current read transaction, so just create a new one
59,271✔
1290
    read_group();
103,265✔
1291
    m_coordinator->process_available_async(*this);
86,647✔
1292
    return true;
86,647✔
1293
}
119,789✔
1294

1295
void Realm::set_auto_refresh(bool auto_refresh)
1296
{
16✔
1297
    if (is_frozen() && auto_refresh) {
16✔
1298
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1299
    }
2✔
1300
    m_auto_refresh = auto_refresh;
14✔
1301
}
14✔
1302

1303

1304
bool Realm::can_deliver_notifications() const noexcept
1305
{
12,163✔
1306
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
12,163✔
1307
        return false;
12,107✔
1308
    }
12,107✔
1309

28✔
1310
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1311
        return false;
×
1312
    }
×
1313

28✔
1314
    return true;
56✔
1315
}
56✔
1316

1317
uint64_t Realm::get_schema_version(const Realm::Config& config)
1318
{
×
1319
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1320
    auto version = coordinator->get_schema_version();
×
1321
    if (version == ObjectStore::NotVersioned)
×
1322
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1323
    return version;
×
1324
}
×
1325

1326

1327
bool Realm::is_frozen() const
1328
{
376,864✔
1329
    bool result = bool(m_frozen_version);
376,864✔
1330
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
376,864✔
1331
    return result;
376,864✔
1332
}
376,864✔
1333

1334
SharedRealm Realm::freeze()
1335
{
635✔
1336
    read_group(); // Freezing requires a read transaction
635✔
1337
    return m_coordinator->freeze_realm(*this);
635✔
1338
}
635✔
1339

1340
void Realm::copy_schema_from(const Realm& source)
1341
{
631✔
1342
    REALM_ASSERT(is_frozen());
631✔
1343
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
631✔
1344
    m_schema = source.m_schema;
631✔
1345
    m_schema_version = source.m_schema_version;
631✔
1346
    m_schema_transaction_version = m_frozen_version->version;
631✔
1347
    m_dynamic_schema = false;
631✔
1348
}
631✔
1349

1350
void Realm::close()
1351
{
4,037✔
1352
    if (is_closed()) {
4,037✔
1353
        return;
20✔
1354
    }
20✔
1355
    if (m_coordinator) {
4,017✔
1356
        m_coordinator->unregister_realm(this);
4,017✔
1357
    }
4,017✔
1358

1,993✔
1359
    do_invalidate();
4,017✔
1360

1,993✔
1361
    m_binding_context = nullptr;
4,017✔
1362
    m_coordinator = nullptr;
4,017✔
1363
    m_scheduler = nullptr;
4,017✔
1364
    m_config = {};
4,017✔
1365
}
4,017✔
1366

1367
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1368
{
16✔
1369
    bool lock_successful = false;
16✔
1370
    try {
16✔
1371
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
14✔
1372
            DB::delete_files(path, did_delete_realm);
12✔
1373
        });
12✔
1374
    }
16✔
1375
    catch (const FileAccessError& e) {
9✔
1376
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
1377
            throw;
×
1378
        }
×
1379
        // Thrown only if the parent directory of the lock file does not exist,
1✔
1380
        // which obviously indicates that we didn't need to delete anything
1✔
1381
        if (did_delete_realm) {
2✔
1382
            *did_delete_realm = false;
2✔
1383
        }
2✔
1384
        return;
2✔
1385
    }
2✔
1386
    if (!lock_successful) {
14✔
1387
        throw FileAccessError(
2✔
1388
            ErrorCodes::DeleteOnOpenRealm,
2✔
1389
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1390
            realm_file_path);
2✔
1391
    }
2✔
1392
}
14✔
1393

1394
AuditInterface* Realm::audit_context() const noexcept
1395
{
179,891✔
1396
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
179,891✔
1397
}
179,891✔
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