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

realm / realm-core / 2088

01 Mar 2024 04:55PM UTC coverage: 90.906% (+0.009%) from 90.897%
2088

push

Evergreen

web-flow
Merge pull request #7407 from realm/release/14.1.0

Release 14.1.0

93916 of 173116 branches covered (54.25%)

238350 of 262194 relevant lines covered (90.91%)

6006140.42 hits per line

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

93.05
/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
#ifdef REALM_DEBUG
54
#include <iostream>
55
#endif
56

57
#include <thread>
58

59
using namespace realm;
60
using namespace realm::_impl;
61

62
namespace {
63
class CountGuard {
64
public:
65
    CountGuard(size_t& count)
66
        : m_count(count)
67
    {
246,040✔
68
        ++m_count;
246,040✔
69
    }
246,040✔
70
    ~CountGuard()
71
    {
246,042✔
72
        --m_count;
246,042✔
73
    }
246,042✔
74

75
private:
76
    size_t& m_count;
77
};
78
} // namespace
79

80
bool RealmConfig::needs_file_format_upgrade() const
81
{
12✔
82
    return DB::needs_file_format_upgrade(path, encryption_key);
12✔
83
}
12✔
84

85
Realm::Realm(Config config, util::Optional<VersionID> version, std::shared_ptr<_impl::RealmCoordinator> coordinator,
86
             Private)
87
    : m_config(std::move(config))
88
    , m_frozen_version(version)
89
    , m_scheduler(m_config.scheduler)
90
{
31,870✔
91
    if (version) {
31,870✔
92
        m_auto_refresh = false;
1,006✔
93
        REALM_ASSERT(*version != VersionID());
1,006✔
94
    }
1,006✔
95
    else if (!coordinator->get_cached_schema(m_schema, m_schema_version, m_schema_transaction_version)) {
30,864✔
96
        m_transaction = coordinator->begin_read();
24,185✔
97
        read_schema_from_group_if_needed();
24,185✔
98
        coordinator->cache_schema(m_schema, m_schema_version, m_schema_transaction_version);
24,185✔
99
        m_transaction = nullptr;
24,185✔
100
    }
24,185✔
101
    m_coordinator = std::move(coordinator);
31,870✔
102
}
31,870✔
103

104
Realm::~Realm()
105
{
31,870✔
106
    if (m_transaction) {
31,870✔
107
        // Wait for potential syncing to finish
12,601✔
108
        m_transaction->prepare_for_close();
25,594✔
109
        call_completion_callbacks();
25,594✔
110
    }
25,594✔
111

15,719✔
112
    if (m_coordinator) {
31,870✔
113
        m_coordinator->unregister_realm(this);
27,573✔
114
    }
27,573✔
115
}
31,870✔
116

117
Group& Realm::read_group()
118
{
670,866✔
119
    return transaction();
670,866✔
120
}
670,866✔
121

122
Transaction& Realm::transaction()
123
{
1,541,263✔
124
    verify_open();
1,541,263✔
125
    if (!m_transaction)
1,541,263✔
126
        begin_read(m_frozen_version.value_or(VersionID{}));
59,045✔
127
    return *m_transaction;
1,541,263✔
128
}
1,541,263✔
129

130
Transaction& Realm::transaction() const
131
{
473,919✔
132
    // one day we should change the way we use constness
231,826✔
133
    Realm* nc_realm = const_cast<Realm*>(this);
473,919✔
134
    return nc_realm->transaction();
473,919✔
135
}
473,919✔
136

137
std::shared_ptr<Transaction> Realm::transaction_ref()
138
{
79,967✔
139
    return m_transaction;
79,967✔
140
}
79,967✔
141

142
std::shared_ptr<Transaction> Realm::duplicate() const
143
{
144✔
144
    auto version = read_transaction_version(); // does the validity check first
144✔
145
    return m_coordinator->begin_read(version, is_frozen());
144✔
146
}
144✔
147

148
std::shared_ptr<DB>& Realm::Internal::get_db(Realm& realm)
149
{
7,012✔
150
    return realm.m_coordinator->m_db;
7,012✔
151
}
7,012✔
152

153
void Realm::Internal::begin_read(Realm& realm, VersionID version_id)
154
{
46✔
155
    realm.begin_read(version_id);
46✔
156
}
46✔
157

158
void Realm::begin_read(VersionID version_id)
159
{
59,091✔
160
    REALM_ASSERT(!m_transaction);
59,091✔
161
    m_transaction = m_coordinator->begin_read(version_id, bool(m_frozen_version));
59,091✔
162
    add_schema_change_handler();
59,091✔
163
    read_schema_from_group_if_needed();
59,091✔
164
}
59,091✔
165

166
SharedRealm Realm::get_shared_realm(Config config)
167
{
29,999✔
168
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
29,999✔
169
    return coordinator->get_realm(std::move(config), util::none);
29,999✔
170
}
29,999✔
171

172
SharedRealm Realm::get_frozen_realm(Config config, VersionID version)
173
{
274✔
174
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
274✔
175
    return coordinator->get_realm(std::move(config), version);
274✔
176
}
274✔
177

178
SharedRealm Realm::get_shared_realm(ThreadSafeReference ref, std::shared_ptr<util::Scheduler> scheduler)
179
{
176✔
180
    if (!scheduler)
176✔
181
        scheduler = util::Scheduler::make_default();
102✔
182
    SharedRealm realm = ref.resolve<std::shared_ptr<Realm>>(nullptr);
176✔
183
    REALM_ASSERT(realm);
176✔
184
    auto& config = realm->config();
176✔
185
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
176✔
186
    if (auto realm = coordinator->get_cached_realm(config, scheduler))
176✔
187
        return realm;
2✔
188
    realm->m_scheduler = scheduler;
174✔
189
    coordinator->bind_to_context(*realm);
174✔
190
    return realm;
174✔
191
}
174✔
192

193
#if REALM_ENABLE_SYNC
194
std::shared_ptr<AsyncOpenTask> Realm::get_synchronized_realm(Config config)
195
{
140✔
196
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
140✔
197
    return coordinator->get_synchronized_realm(std::move(config));
140✔
198
}
140✔
199

200
std::shared_ptr<SyncSession> Realm::sync_session() const
201
{
15,820,476✔
202
    return m_coordinator ? m_coordinator->sync_session() : nullptr;
15,820,475✔
203
}
15,820,476✔
204

205
sync::SubscriptionSet Realm::get_latest_subscription_set()
206
{
655✔
207
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
655✔
208
        throw IllegalOperation("Flexible sync is not enabled");
4✔
209
    }
4✔
210
    // If there is a subscription store, then return the active set
325✔
211
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
651✔
212
    REALM_ASSERT(flx_sub_store);
651✔
213
    return flx_sub_store->get_latest();
651✔
214
}
651✔
215

216
sync::SubscriptionSet Realm::get_active_subscription_set()
217
{
76✔
218
    if (!m_config.sync_config || !m_config.sync_config->flx_sync_requested) {
76✔
219
        throw IllegalOperation("Flexible sync is not enabled");
6✔
220
    }
6✔
221
    // If there is a subscription store, then return the active set
35✔
222
    auto flx_sub_store = m_coordinator->sync_session()->get_flx_subscription_store();
70✔
223
    REALM_ASSERT(flx_sub_store);
70✔
224
    return flx_sub_store->get_active();
70✔
225
}
70✔
226
#endif
227

228
void Realm::set_schema(Schema const& reference, Schema schema)
229
{
10,738✔
230
    m_dynamic_schema = false;
10,738✔
231
    schema.copy_keys_from(reference, m_config.schema_subset_mode);
10,738✔
232
    m_schema = std::move(schema);
10,738✔
233
    notify_schema_changed();
10,738✔
234
}
10,738✔
235

236
void Realm::read_schema_from_group_if_needed()
237
{
83,268✔
238
    if (m_config.immutable()) {
83,268✔
239
        REALM_ASSERT(m_transaction);
118✔
240
        if (m_schema.empty()) {
118✔
241
            m_schema_version = ObjectStore::get_schema_version(*m_transaction);
64✔
242
            m_schema = ObjectStore::schema_from_group(*m_transaction);
64✔
243
            m_schema_transaction_version = m_transaction->get_version_of_current_transaction().version;
64✔
244
        }
64✔
245
        return;
118✔
246
    }
118✔
247

41,009✔
248
    Group& group = read_group();
83,150✔
249
    auto current_version = transaction().get_version_of_current_transaction().version;
83,150✔
250
    if (m_schema_transaction_version == current_version)
83,150✔
251
        return;
57,866✔
252

12,490✔
253
    m_schema_transaction_version = current_version;
25,284✔
254
    m_schema_version = ObjectStore::get_schema_version(group);
25,284✔
255
    auto schema = ObjectStore::schema_from_group(group);
25,284✔
256

12,490✔
257
    if (m_coordinator)
25,284✔
258
        m_coordinator->cache_schema(schema, m_schema_version, m_schema_transaction_version);
1,161✔
259

12,490✔
260
    if (m_dynamic_schema) {
25,284✔
261
        if (m_schema == schema) {
24,743✔
262
            // The structure of the schema hasn't changed. Bring the table column indices up to date.
9,330✔
263
            m_schema.copy_keys_from(schema, SchemaSubsetMode::Strict);
18,842✔
264
        }
18,842✔
265
        else {
5,901✔
266
            // The structure of the schema has changed, so replace our copy of the schema.
2,895✔
267
            m_schema = std::move(schema);
5,901✔
268
        }
5,901✔
269
    }
24,743✔
270
    else {
541✔
271
        ObjectStore::verify_valid_external_changes(m_schema.compare(schema, m_config.schema_mode));
541✔
272
        m_schema.copy_keys_from(schema, m_config.schema_subset_mode);
541✔
273
    }
541✔
274
    notify_schema_changed();
25,284✔
275
}
25,284✔
276

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

10✔
286
    m_schema = ObjectStore::schema_from_group(read_group());
20✔
287
    m_schema_version = ObjectStore::get_schema_version(read_group());
20✔
288
    required_changes = m_schema.compare(schema, m_config.schema_mode);
20✔
289
    m_coordinator->clear_schema_cache_and_set_schema_version(m_schema_version);
20✔
290
    return false;
20✔
291
}
20✔
292

293
bool Realm::schema_change_needs_write_transaction(Schema& schema, std::vector<SchemaChange>& changes,
294
                                                  uint64_t version)
295
{
29,514✔
296
    if (version == m_schema_version && changes.empty())
29,514✔
297
        return false;
10,426✔
298

9,457✔
299
    switch (m_config.schema_mode) {
19,088✔
300
        case SchemaMode::Automatic:
10,382✔
301
            verify_schema_version_not_decreasing(version);
10,382✔
302
            return true;
10,382✔
303

304
        case SchemaMode::Immutable:
12✔
305
            if (version != m_schema_version)
12✔
306
                throw InvalidSchemaVersionException(m_schema_version, version, true);
2✔
307
            REALM_FALLTHROUGH;
10✔
308
        case SchemaMode::ReadOnly:
36✔
309
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
36✔
310
            return m_schema_version == ObjectStore::NotVersioned;
36✔
311

5✔
312
        case SchemaMode::SoftResetFile:
32✔
313
            if (m_schema_version == ObjectStore::NotVersioned)
32✔
314
                return true;
18✔
315
            if (m_schema_version == version && !ObjectStore::needs_migration(changes))
14✔
316
                return true;
8✔
317
            REALM_FALLTHROUGH;
6✔
318
        case SchemaMode::HardResetFile:
20✔
319
            reset_file(schema, changes);
20✔
320
            return true;
20✔
321

3✔
322
        case SchemaMode::AdditiveDiscovered:
4,229✔
323
        case SchemaMode::AdditiveExplicit: {
8,494✔
324
            bool will_apply_index_changes = version > m_schema_version;
8,494✔
325
            if (ObjectStore::verify_valid_additive_changes(changes, will_apply_index_changes))
8,494✔
326
                return true;
8,346✔
327
            return version != m_schema_version;
148✔
328
        }
148✔
329

74✔
330
        case SchemaMode::Manual:
138✔
331
            verify_schema_version_not_decreasing(version);
128✔
332
            if (version == m_schema_version) {
128✔
333
                ObjectStore::verify_no_changes_required(changes);
28✔
334
                REALM_UNREACHABLE(); // changes is non-empty so above line always throws
335
            }
28✔
336
            return true;
128✔
337
    }
×
338
    REALM_COMPILER_HINT_UNREACHABLE();
×
339
}
×
340

341
// Schema version is not allowed to decrease for local and pbs realms.
342
void Realm::verify_schema_version_not_decreasing(uint64_t version)
343
{
10,510✔
344
#if REALM_ENABLE_SYNC
10,510✔
345
    if (m_config.sync_config && m_config.sync_config->flx_sync_requested)
10,510✔
346
        return;
×
347
#endif
10,510✔
348
    if (version < m_schema_version && m_schema_version != ObjectStore::NotVersioned)
10,510✔
349
        throw InvalidSchemaVersionException(m_schema_version, version, false);
4✔
350
}
10,510✔
351

352
Schema Realm::get_full_schema()
353
{
29,757✔
354
    if (!m_config.immutable())
29,757✔
355
        do_refresh();
29,691✔
356

14,669✔
357
    // If the user hasn't specified a schema previously then m_schema is always
14,669✔
358
    // the full schema if it's been read
14,669✔
359
    if (m_dynamic_schema && !m_schema.empty())
29,757✔
360
        return m_schema;
10,528✔
361

9,529✔
362
    // Otherwise we may have a subset of the file's schema, so we need to get
9,529✔
363
    // the complete thing to calculate what changes to make
9,529✔
364
    Schema actual_schema;
19,229✔
365
    uint64_t actual_version;
19,229✔
366
    uint64_t version = -1;
19,229✔
367
    bool got_cached = m_coordinator->get_cached_schema(actual_schema, actual_version, version);
19,229✔
368
    if (!got_cached || version != transaction().get_version_of_current_transaction().version)
19,229✔
369
        return ObjectStore::schema_from_group(read_group());
18,575✔
370
    return actual_schema;
654✔
371
}
654✔
372

373
bool Realm::is_empty()
374
{
4✔
375
    return ObjectStore::is_empty(read_group());
4✔
376
}
4✔
377

378
Class Realm::get_class(StringData object_type)
379
{
20✔
380
    auto it = m_schema.find(object_type);
20✔
381
    if (it == m_schema.end()) {
20✔
382
        throw LogicError(ErrorCodes::NoSuchTable, util::format("No type '%1'", object_type));
×
383
    }
×
384
    return {shared_from_this(), &*it};
20✔
385
}
20✔
386

387
std::vector<Class> Realm::get_classes()
388
{
×
389
    std::vector<Class> ret;
×
390
    ret.reserve(m_schema.size());
×
391
    auto r = shared_from_this();
×
392
    for (auto& os : m_schema) {
×
393
        ret.emplace_back(r, &os);
×
394
    }
×
395
    return ret;
×
396
}
×
397

398
void Realm::set_schema_subset(Schema schema)
399
{
12✔
400
    verify_thread();
12✔
401
    verify_open();
12✔
402
    REALM_ASSERT(m_dynamic_schema);
12✔
403
    REALM_ASSERT(m_schema_version != ObjectStore::NotVersioned);
12✔
404

6✔
405
    std::vector<SchemaChange> changes = m_schema.compare(schema, m_config.schema_mode);
12✔
406
    switch (m_config.schema_mode) {
12✔
407
        case SchemaMode::Automatic:
2✔
408
        case SchemaMode::SoftResetFile:
2✔
409
        case SchemaMode::HardResetFile:
2✔
410
            ObjectStore::verify_no_migration_required(changes);
2✔
411
            break;
2✔
412

1✔
413
        case SchemaMode::Immutable:
1✔
414
        case SchemaMode::ReadOnly:
✔
415
            ObjectStore::verify_compatible_for_immutable_and_readonly(changes);
×
416
            break;
×
417

418
        case SchemaMode::AdditiveDiscovered:
4✔
419
        case SchemaMode::AdditiveExplicit:
8✔
420
            ObjectStore::verify_valid_additive_changes(changes);
8✔
421
            break;
8✔
422

4✔
423
        case SchemaMode::Manual:
4✔
424
            ObjectStore::verify_no_changes_required(changes);
×
425
            break;
×
426
    }
10✔
427

5✔
428
    set_schema(m_schema, std::move(schema));
10✔
429
}
10✔
430

431
void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction migration_function,
432
                          DataInitializationFunction initialization_function, bool in_transaction)
433
{
29,765✔
434
    uint64_t validation_mode = SchemaValidationMode::Basic;
29,765✔
435
#if REALM_ENABLE_SYNC
29,765✔
436
    if (auto sync_config = m_config.sync_config) {
29,765✔
437
        validation_mode |=
1,617✔
438
            sync_config->flx_sync_requested ? SchemaValidationMode::SyncFLX : SchemaValidationMode::SyncPBS;
1,319✔
439
    }
1,617✔
440
#endif
29,765✔
441
    if (m_config.schema_mode == SchemaMode::AdditiveExplicit) {
29,765✔
442
        validation_mode |= SchemaValidationMode::RejectEmbeddedOrphans;
9,185✔
443
    }
9,185✔
444

14,673✔
445
    schema.validate(static_cast<SchemaValidationMode>(validation_mode));
29,765✔
446

14,673✔
447
    bool was_in_read_transaction = is_in_read_transaction();
29,765✔
448
    Schema actual_schema = get_full_schema();
29,765✔
449

14,673✔
450
    // Frozen Realms never modify the schema on disk and we just need to verify
14,673✔
451
    // that the requested schema is compatible with what actually exists on disk
14,673✔
452
    // at that frozen version. Tables are allowed to be missing as those can be
14,673✔
453
    // represented by empty Results, but tables which exist must have all of the
14,673✔
454
    // requested properties with the correct type.
14,673✔
455
    if (m_frozen_version) {
29,765✔
456
        ObjectStore::verify_compatible_for_immutable_and_readonly(
246✔
457
            actual_schema.compare(schema, m_config.schema_mode, true));
246✔
458
        set_schema(actual_schema, std::move(schema));
246✔
459
        return;
246✔
460
    }
246✔
461

14,550✔
462
    std::vector<SchemaChange> required_changes = actual_schema.compare(schema, m_config.schema_mode);
29,519✔
463
    if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
29,519✔
464
        if (!was_in_read_transaction)
10,500✔
465
            m_transaction = nullptr;
10,470✔
466
        set_schema(actual_schema, std::move(schema));
10,500✔
467
        return;
10,500✔
468
    }
10,500✔
469
    // Either the schema version has changed or we need to do non-migration changes
9,424✔
470

9,424✔
471
    // Cancel the write transaction if we exit this function before committing it
9,424✔
472
    auto cleanup = util::make_scope_exit([&]() noexcept {
19,019✔
473
        // When in_transaction is true, caller is responsible to cancel the transaction.
9,377✔
474
        if (!in_transaction && is_in_transaction())
18,925✔
475
            cancel_transaction();
110✔
476
        if (!was_in_read_transaction)
18,925✔
477
            m_transaction = nullptr;
18,575✔
478
    });
18,925✔
479

9,424✔
480
    if (!in_transaction) {
19,019✔
481
        transaction().promote_to_write();
18,899✔
482

9,364✔
483
        // Beginning the write transaction may have advanced the version and left
9,364✔
484
        // us with nothing to do if someone else initialized the schema on disk
9,364✔
485
        if (m_new_schema) {
18,899✔
486
            actual_schema = *m_new_schema;
5✔
487
            required_changes = actual_schema.compare(schema, m_config.schema_mode);
5✔
488
            if (!schema_change_needs_write_transaction(schema, required_changes, version)) {
5✔
489
                cancel_transaction();
2✔
490
                cache_new_schema();
2✔
491
                set_schema(actual_schema, std::move(schema));
2✔
492
                return;
2✔
493
            }
2✔
494
        }
18,897✔
495
        cache_new_schema();
18,897✔
496
    }
18,897✔
497

9,424✔
498
    schema.copy_keys_from(actual_schema, m_config.schema_subset_mode);
19,018✔
499

9,423✔
500
    bool save_schema_version_on_version_decrease = false;
19,017✔
501
#if REALM_ENABLE_SYNC
19,017✔
502
    if (m_config.sync_config && m_config.sync_config->flx_sync_requested)
19,017✔
503
        save_schema_version_on_version_decrease = true;
403✔
504
#endif
19,017✔
505

9,423✔
506
    uint64_t old_schema_version = m_schema_version;
19,017✔
507
    bool additive = m_config.schema_mode == SchemaMode::AdditiveDiscovered ||
19,017✔
508
                    m_config.schema_mode == SchemaMode::AdditiveExplicit ||
18,929✔
509
                    m_config.schema_mode == SchemaMode::ReadOnly;
14,697✔
510
    if (migration_function && !additive) {
19,017✔
511
        auto wrapper = [&] {
543✔
512
            auto config = m_config;
210✔
513
            config.schema_mode = SchemaMode::ReadOnly;
210✔
514
            config.schema = util::none;
210✔
515
            // Don't go through the normal codepath for opening a Realm because
105✔
516
            // we're using a mismatched config
105✔
517
            auto old_realm = std::make_shared<Realm>(std::move(config), none, m_coordinator, Private());
210✔
518
            // block autorefresh for the old realm
105✔
519
            old_realm->m_auto_refresh = false;
210✔
520
            migration_function(old_realm, shared_from_this(), m_schema);
210✔
521
        };
210✔
522

438✔
523
        // migration function needs to see the target schema on the "new" Realm
438✔
524
        std::swap(m_schema, schema);
893✔
525
        std::swap(m_schema_version, version);
893✔
526
        m_in_migration = true;
893✔
527
        auto restore = util::make_scope_exit([&]() noexcept {
893✔
528
            std::swap(m_schema, schema);
893✔
529
            std::swap(m_schema_version, version);
893✔
530
            m_in_migration = false;
893✔
531
        });
893✔
532

438✔
533
        ObjectStore::apply_schema_changes(transaction(), version, m_schema, m_schema_version, m_config.schema_mode,
893✔
534
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
893✔
535
                                          wrapper, save_schema_version_on_version_decrease);
893✔
536
    }
893✔
537
    else {
18,124✔
538
        ObjectStore::apply_schema_changes(transaction(), m_schema_version, schema, version, m_config.schema_mode,
18,124✔
539
                                          required_changes, m_config.automatically_handle_backlinks_in_migrations,
18,124✔
540
                                          nullptr, save_schema_version_on_version_decrease);
18,124✔
541
        REALM_ASSERT_DEBUG(additive ||
18,124✔
542
                           (required_changes = ObjectStore::schema_from_group(read_group()).compare(schema)).empty());
18,124✔
543
    }
18,124✔
544

9,423✔
545
    if (initialization_function && old_schema_version == ObjectStore::NotVersioned) {
19,017✔
546
        // Initialization function needs to see the latest schema
15✔
547
        uint64_t temp_version = ObjectStore::get_schema_version(read_group());
30✔
548
        std::swap(m_schema, schema);
30✔
549
        std::swap(m_schema_version, temp_version);
30✔
550
        auto restore = util::make_scope_exit([&]() noexcept {
30✔
551
            std::swap(m_schema, schema);
30✔
552
            std::swap(m_schema_version, temp_version);
30✔
553
        });
30✔
554
        initialization_function(shared_from_this());
30✔
555
    }
30✔
556

9,423✔
557
    m_schema = std::move(schema);
19,017✔
558
    m_new_schema = ObjectStore::schema_from_group(read_group());
19,017✔
559
    m_schema_version = ObjectStore::get_schema_version(read_group());
19,017✔
560
    m_dynamic_schema = false;
19,017✔
561
    m_coordinator->clear_schema_cache_and_set_schema_version(version);
19,017✔
562

9,423✔
563
    if (!in_transaction) {
19,017✔
564
        m_coordinator->commit_write(*this);
18,787✔
565
        cache_new_schema();
18,787✔
566
    }
18,787✔
567

9,423✔
568
    notify_schema_changed();
19,017✔
569
}
19,017✔
570

571
void Realm::rename_property(Schema schema, StringData object_type, StringData old_name, StringData new_name)
572
{
4✔
573
    ObjectStore::rename_property(read_group(), schema, object_type, old_name, new_name);
4✔
574
}
4✔
575

576
void Realm::add_schema_change_handler()
577
{
59,084✔
578
    if (m_config.immutable())
59,084✔
579
        return;
56✔
580
    m_transaction->set_schema_change_notification_handler([&] {
59,028✔
581
        m_new_schema = ObjectStore::schema_from_group(read_group());
3,742✔
582
        m_schema_version = ObjectStore::get_schema_version(read_group());
3,742✔
583
        if (m_dynamic_schema) {
3,742✔
584
            m_schema = *m_new_schema;
25✔
585
        }
25✔
586
        else {
3,717✔
587
            m_schema.copy_keys_from(*m_new_schema, m_config.schema_subset_mode);
3,717✔
588
        }
3,717✔
589
        notify_schema_changed();
3,742✔
590
    });
3,742✔
591
}
59,028✔
592

593
void Realm::cache_new_schema()
594
{
150,246✔
595
    if (is_closed()) {
150,246✔
596
        return;
×
597
    }
×
598

73,194✔
599
    auto new_version = transaction().get_version_of_current_transaction().version;
150,246✔
600
    if (m_new_schema)
150,246✔
601
        m_coordinator->cache_schema(std::move(*m_new_schema), m_schema_version, new_version);
22,555✔
602
    else
127,691✔
603
        m_coordinator->advance_schema_cache(m_schema_transaction_version, new_version);
127,691✔
604
    m_schema_transaction_version = new_version;
150,246✔
605
    m_new_schema = util::none;
150,246✔
606
}
150,246✔
607

608
void Realm::translate_schema_error()
609
{
4✔
610
    // Read the new (incompatible) schema without changing our read transaction
2✔
611
    auto new_schema = ObjectStore::schema_from_group(*m_coordinator->begin_read());
4✔
612

2✔
613
    // Should always throw
2✔
614
    ObjectStore::verify_valid_external_changes(m_schema.compare(new_schema, m_config.schema_mode, true));
4✔
615

2✔
616
    // Something strange happened so just rethrow the old exception
2✔
617
    throw;
4✔
618
}
4✔
619

620
void Realm::notify_schema_changed()
621
{
58,571✔
622
    if (m_binding_context) {
58,571✔
623
        m_binding_context->schema_did_change(m_schema);
28✔
624
    }
28✔
625
}
58,571✔
626

627
static void check_can_create_write_transaction(const Realm* realm)
628
{
100,848✔
629
    realm->verify_thread();
100,848✔
630
    realm->verify_open();
100,848✔
631
    if (realm->config().immutable() || realm->config().read_only()) {
100,848✔
632
        throw WrongTransactionState("Can't perform transactions on read-only Realms.");
6✔
633
    }
6✔
634
    if (realm->is_frozen()) {
100,842✔
635
        throw WrongTransactionState("Can't perform transactions on a frozen Realm");
2✔
636
    }
2✔
637
    if (!realm->is_closed() && realm->get_number_of_versions() > realm->config().max_number_of_active_versions) {
100,840✔
638
        throw WrongTransactionState(
×
639
            util::format("Number of active versions (%1) in the Realm exceeded the limit of %2",
×
640
                         realm->get_number_of_versions(), realm->config().max_number_of_active_versions));
×
641
    }
×
642
}
100,840✔
643

644
void Realm::verify_thread() const
645
{
513,479✔
646
    if (m_scheduler && !m_scheduler->is_on_thread())
513,479✔
647
        throw LogicError(ErrorCodes::WrongThread, "Realm accessed from incorrect thread.");
8✔
648
}
513,479✔
649

650
void Realm::verify_in_write() const
651
{
14,984✔
652
    if (!is_in_transaction()) {
14,984✔
653
        throw WrongTransactionState("Cannot modify managed objects outside of a write transaction.");
4✔
654
    }
4✔
655
}
14,984✔
656

657
void Realm::verify_open() const
658
{
1,748,445✔
659
    if (is_closed()) {
1,748,445✔
660
        throw LogicError(ErrorCodes::ClosedRealm, "Cannot access realm that has been closed.");
44✔
661
    }
44✔
662
}
1,748,445✔
663

664
bool Realm::verify_notifications_available(bool throw_on_error) const
665
{
33,454✔
666
    if (is_frozen()) {
33,454✔
667
        if (throw_on_error)
56✔
668
            throw WrongTransactionState(
12✔
669
                "Notifications are not available on frozen collections since they do not change.");
12✔
670
        return false;
44✔
671
    }
44✔
672
    if (config().immutable()) {
33,398✔
673
        if (throw_on_error)
2✔
674
            throw WrongTransactionState("Cannot create asynchronous query for immutable Realms");
×
675
        return false;
2✔
676
    }
2✔
677
    if (throw_on_error) {
33,396✔
678
        if (m_transaction && m_transaction->get_commit_size() > 0)
10,490✔
679
            throw WrongTransactionState(
2✔
680
                "Cannot create asynchronous query after making changes in a write transaction.");
2✔
681
    }
22,906✔
682
    else {
22,906✔
683
        // Don't create implicit notifiers inside write transactions even if
11,419✔
684
        // we could as it wouldn't actually be used
11,419✔
685
        if (is_in_transaction())
22,906✔
686
            return false;
17,208✔
687
    }
16,186✔
688

8,072✔
689
    return true;
16,186✔
690
}
16,186✔
691

692
VersionID Realm::read_transaction_version() const
693
{
5,132✔
694
    verify_thread();
5,132✔
695
    verify_open();
5,132✔
696
    REALM_ASSERT(m_transaction);
5,132✔
697
    return m_transaction->get_version_of_current_transaction();
5,132✔
698
}
5,132✔
699

700
uint_fast64_t Realm::get_number_of_versions() const
701
{
100,834✔
702
    verify_open();
100,834✔
703
    return m_coordinator->get_number_of_versions();
100,834✔
704
}
100,834✔
705

706
bool Realm::is_in_transaction() const noexcept
707
{
517,503✔
708
    return !m_config.immutable() && !is_closed() && m_transaction &&
517,503✔
709
           transaction().get_transact_stage() == DB::transact_Writing;
495,333✔
710
}
517,503✔
711

712
bool Realm::is_in_async_transaction() const noexcept
713
{
606✔
714
    return !m_config.immutable() && !is_closed() && m_transaction && m_transaction->is_async();
606✔
715
}
606✔
716

717
util::Optional<VersionID> Realm::current_transaction_version() const
718
{
53,460✔
719
    util::Optional<VersionID> ret;
53,460✔
720
    if (m_transaction) {
53,460✔
721
        ret = m_transaction->get_version_of_current_transaction();
53,158✔
722
    }
53,158✔
723
    else if (m_frozen_version) {
302✔
724
        ret = m_frozen_version;
×
725
    }
×
726
    return ret;
53,460✔
727
}
53,460✔
728

729
// Get the version of the latest snapshot
730
util::Optional<DB::version_type> Realm::latest_snapshot_version() const
731
{
68✔
732
    util::Optional<DB::version_type> ret;
68✔
733
    if (m_transaction) {
68✔
734
        ret = m_transaction->get_version_of_latest_snapshot();
66✔
735
    }
66✔
736
    return ret;
68✔
737
}
68✔
738

739
void Realm::enable_wait_for_change()
740
{
2✔
741
    verify_open();
2✔
742
    m_coordinator->enable_wait_for_change();
2✔
743
}
2✔
744

745
bool Realm::wait_for_change()
746
{
6✔
747
    verify_open();
6✔
748
    if (m_frozen_version || m_config.schema_mode == SchemaMode::Immutable) {
6✔
749
        return false;
4✔
750
    }
4✔
751
    return m_transaction && m_coordinator->wait_for_change(m_transaction);
2!
752
}
2✔
753

754
void Realm::wait_for_change_release()
755
{
2✔
756
    verify_open();
2✔
757
    m_coordinator->wait_for_change_release();
2✔
758
}
2✔
759

760
bool Realm::has_pending_async_work() const
761
{
1,325✔
762
    verify_thread();
1,325✔
763
    return !m_async_commit_q.empty() || !m_async_write_q.empty() || (m_transaction && m_transaction->is_async());
1,325✔
764
}
1,325✔
765

766
void Realm::run_writes_on_proper_thread()
767
{
6✔
768
    m_scheduler->invoke([self = shared_from_this()] {
6✔
769
        self->run_writes();
6✔
770
    });
6✔
771
}
6✔
772

773
void Realm::call_completion_callbacks()
774
{
123,833✔
775
    if (m_is_running_async_commit_completions) {
123,833✔
776
        return;
4✔
777
    }
4✔
778

60,581✔
779
    CountGuard sending_completions(m_is_running_async_commit_completions);
123,829✔
780
    auto error = m_transaction->get_commit_exception();
123,829✔
781
    auto completions = std::move(m_async_commit_q);
123,829✔
782
    m_async_commit_q.clear();
123,829✔
783
    for (auto& cb : completions) {
61,960✔
784
        if (!cb.when_completed)
2,510✔
785
            continue;
16✔
786
        if (m_async_exception_handler) {
2,494✔
787
            try {
2✔
788
                cb.when_completed(error);
2✔
789
            }
2✔
790
            catch (...) {
2✔
791
                m_async_exception_handler(cb.handle, std::current_exception());
2✔
792
            }
2✔
793
        }
2✔
794
        else {
2,492✔
795
            cb.when_completed(error);
2,492✔
796
        }
2,492✔
797
    }
2,494✔
798
}
123,829✔
799

800
void Realm::run_async_completions()
801
{
330✔
802
    call_completion_callbacks();
330✔
803
    check_pending_write_requests();
330✔
804
}
330✔
805

806
void Realm::check_pending_write_requests()
807
{
46,779✔
808
    if (!m_async_write_q.empty()) {
46,779✔
809
        if (m_transaction->is_async()) {
420✔
810
            run_writes_on_proper_thread();
6✔
811
        }
6✔
812
        else {
414✔
813
            m_coordinator->async_request_write_mutex(*this);
414✔
814
        }
414✔
815
    }
420✔
816
}
46,779✔
817

818
void Realm::end_current_write(bool check_pending)
819
{
3,311✔
820
    if (!m_transaction) {
3,311✔
821
        return;
2✔
822
    }
2✔
823
    m_transaction->async_complete_writes([self = shared_from_this(), this]() mutable {
3,309✔
824
        m_scheduler->invoke([self = std::move(self), this]() mutable {
330✔
825
            run_async_completions();
330✔
826
            self.reset();
330✔
827
        });
330✔
828
    });
330✔
829
    if (check_pending && m_async_commit_q.empty()) {
3,309✔
830
        check_pending_write_requests();
2,853✔
831
    }
2,853✔
832
}
3,309✔
833

834
void Realm::run_writes()
835
{
501✔
836
    if (!m_transaction) {
501✔
837
        // Realm might have been closed
838
        return;
×
839
    }
×
840
    if (m_transaction->is_synchronizing()) {
501✔
841
        // Wait for the synchronization complete callback before we run more
842
        // writes as we can't add commits while in that state
843
        return;
×
844
    }
×
845
    if (is_in_transaction()) {
501✔
846
        // This is scheduled asynchronously after acquiring the write lock, so
1✔
847
        // in that time a synchronous transaction may have been started. If so,
1✔
848
        // we'll be re-invoked when that transaction ends.
1✔
849
        return;
2✔
850
    }
2✔
851

125✔
852
    CountGuard running_writes(m_is_running_async_writes);
499✔
853
    int run_limit = 20; // max number of commits without full sync to disk
499✔
854
    // this is tricky
125✔
855
    //  - each pending call may itself add other async writes
125✔
856
    //  - the 'run' will terminate as soon as a commit without grouping is requested
125✔
857
    while (!m_async_write_q.empty() && m_transaction) {
2,709✔
858
        // We might have made a sync commit and thereby given up the write lock
1,150✔
859
        if (!m_transaction->holds_write_mutex()) {
2,660✔
860
            return;
114✔
861
        }
114✔
862

1,149✔
863
        do_begin_transaction();
2,546✔
864

1,149✔
865
        auto write_desc = std::move(m_async_write_q.front());
2,546✔
866
        m_async_write_q.pop_front();
2,546✔
867

1,149✔
868
        // prevent any calls to commit/cancel during a simple notification
1,149✔
869
        m_notify_only = write_desc.notify_only;
2,546✔
870
        m_async_commit_barrier_requested = false;
2,546✔
871
        auto prev_version = m_transaction->get_version();
2,546✔
872
        try {
2,546✔
873
            write_desc.writer();
2,546✔
874
        }
2,546✔
875
        catch (const std::exception&) {
1,152✔
876
            if (m_transaction) {
6✔
877
                transaction::cancel(*m_transaction, m_binding_context.get());
4✔
878
            }
4✔
879
            m_notify_only = false;
6✔
880

3✔
881
            if (m_async_exception_handler) {
6✔
882
                m_async_exception_handler(write_desc.handle, std::current_exception());
2✔
883
                continue;
2✔
884
            }
2✔
885
            end_current_write();
4✔
886
            throw;
4✔
887
        }
4✔
888

1,146✔
889
        // if we've merely delivered a notification, the full transaction will follow later
1,146✔
890
        // and terminate with a call to async commit or async cancel
1,146✔
891
        if (m_notify_only) {
2,540✔
892
            m_notify_only = false;
12✔
893
            return;
12✔
894
        }
12✔
895

1,140✔
896
        // Realm may have been closed in the write function
1,140✔
897
        if (!m_transaction) {
2,528✔
898
            return;
20✔
899
        }
20✔
900

1,130✔
901
        auto new_version = m_transaction->get_version();
2,508✔
902
        // if we've run the full transaction, there is follow up work to do:
1,130✔
903
        if (new_version > prev_version) {
2,508✔
904
            // A commit was done during callback
1,125✔
905
            --run_limit;
2,498✔
906
            if (run_limit <= 0)
2,498✔
907
                break;
102✔
908
        }
10✔
909
        else {
10✔
910
            if (m_transaction->get_transact_stage() == DB::transact_Writing) {
10✔
911
                // Still in writing stage - we make a rollback
4✔
912
                transaction::cancel(transaction(), m_binding_context.get());
8✔
913
            }
8✔
914
        }
10✔
915
        if (m_async_commit_barrier_requested)
2,457✔
916
            break;
198✔
917
    }
2,406✔
918

125✔
919
    end_current_write();
368✔
920
}
349✔
921

922
auto Realm::async_begin_transaction(util::UniqueFunction<void()>&& the_write_block, bool notify_only) -> AsyncHandle
923
{
2,560✔
924
    check_can_create_write_transaction(this);
2,560✔
925
    if (m_is_running_async_commit_completions) {
2,560✔
926
        throw WrongTransactionState("Can't begin a write transaction from inside a commit completion callback.");
×
927
    }
×
928
    if (!m_scheduler->can_invoke()) {
2,560✔
929
        throw WrongTransactionState(
×
930
            "Cannot schedule async transaction. Make sure you are running from inside a run loop.");
×
931
    }
×
932
    REALM_ASSERT(the_write_block);
2,560✔
933

1,156✔
934
    // make sure we have a (at least a) read transaction
1,156✔
935
    transaction();
2,560✔
936
    auto handle = m_async_commit_handle++;
2,560✔
937
    m_async_write_q.push_back({std::move(the_write_block), notify_only, handle});
2,560✔
938

1,156✔
939
    if (!m_is_running_async_writes && !m_transaction->is_async() &&
2,560✔
940
        m_transaction->get_transact_stage() != DB::transact_Writing) {
1,235✔
941
        m_coordinator->async_request_write_mutex(*this);
124✔
942
    }
124✔
943
    return handle;
2,560✔
944
}
2,560✔
945

946
auto Realm::async_commit_transaction(util::UniqueFunction<void(std::exception_ptr)>&& completion, bool allow_grouping)
947
    -> AsyncHandle
948
{
2,514✔
949
    check_can_create_write_transaction(this);
2,514✔
950
    if (m_is_running_async_commit_completions) {
2,514✔
951
        throw WrongTransactionState("Can't commit a write transaction from inside a commit completion callback.");
×
952
    }
×
953
    if (!is_in_transaction()) {
2,514✔
954
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
955
    }
×
956

1,133✔
957
    m_transaction->promote_to_async();
2,514✔
958
    REALM_ASSERT(m_transaction->holds_write_mutex());
2,514✔
959
    REALM_ASSERT(!m_notify_only);
2,514✔
960
    // auditing is not supported
1,133✔
961
    REALM_ASSERT(!audit_context());
2,514✔
962
    // grab a version lock on current version, push it along with the done block
1,133✔
963
    // do in-buffer-cache commit_transaction();
1,133✔
964
    auto handle = m_async_commit_handle++;
2,514✔
965
    m_async_commit_q.push_back({std::move(completion), handle});
2,514✔
966
    try {
2,514✔
967
        m_coordinator->commit_write(*this, /* commit_to_disk: */ false);
2,514✔
968
    }
2,514✔
969
    catch (...) {
1,135✔
970
        // If the exception happened before the commit, we need to roll back the
2✔
971
        // transaction and remove the completion handler from the queue
2✔
972
        if (is_in_transaction()) {
4✔
973
            // Exception happened before the commit, so roll back the transaction
1✔
974
            // and remove the completion handler from the queue
1✔
975
            cancel_transaction();
2✔
976
            auto it = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), [=](auto& e) {
2✔
977
                return e.handle == handle;
2✔
978
            });
2✔
979
            if (it != m_async_commit_q.end()) {
2✔
980
                m_async_commit_q.erase(it);
2✔
981
            }
2✔
982
        }
2✔
983
        else if (m_transaction) {
2✔
984
            end_current_write(false);
2✔
985
        }
2✔
986
        throw;
4✔
987
    }
4✔
988

1,130✔
989
    if (m_is_running_async_writes) {
2,508✔
990
        // we're called from with the callback loop and it will take care of releasing lock
1,122✔
991
        // if applicable, and of triggering followup runs of callbacks
1,122✔
992
        if (!allow_grouping) {
2,368✔
993
            m_async_commit_barrier_requested = true;
206✔
994
        }
206✔
995
    }
2,368✔
996
    else {
140✔
997
        // we're called from outside the callback loop so we have to take care of
8✔
998
        // releasing any lock and of keeping callbacks coming.
8✔
999
        if (allow_grouping) {
140✔
1000
            run_writes();
×
1001
        }
×
1002
        else {
140✔
1003
            end_current_write(false);
140✔
1004
        }
140✔
1005
    }
140✔
1006
    return handle;
2,508✔
1007
}
2,508✔
1008

1009
bool Realm::async_cancel_transaction(AsyncHandle handle)
1010
{
6✔
1011
    verify_thread();
6✔
1012
    verify_open();
6✔
1013
    auto compare = [handle](auto& elem) {
5✔
1014
        return elem.handle == handle;
4✔
1015
    };
4✔
1016

3✔
1017
    auto it1 = std::find_if(m_async_write_q.begin(), m_async_write_q.end(), compare);
6✔
1018
    if (it1 != m_async_write_q.end()) {
6✔
1019
        m_async_write_q.erase(it1);
2✔
1020
        return true;
2✔
1021
    }
2✔
1022
    auto it2 = std::find_if(m_async_commit_q.begin(), m_async_commit_q.end(), compare);
4✔
1023
    if (it2 != m_async_commit_q.end()) {
4✔
1024
        // Just delete the callback. It is important that we know
1✔
1025
        // that there are still commits pending.
1✔
1026
        it2->when_completed = nullptr;
2✔
1027
        return true;
2✔
1028
    }
2✔
1029
    return false;
2✔
1030
}
2✔
1031

1032
void Realm::begin_transaction()
1033
{
49,350✔
1034
    check_can_create_write_transaction(this);
49,350✔
1035

24,212✔
1036
    if (is_in_transaction()) {
49,350✔
1037
        throw WrongTransactionState("The Realm is already in a write transaction");
×
1038
    }
×
1039

24,212✔
1040
    // Any of the callbacks to user code below could drop the last remaining
24,212✔
1041
    // strong reference to `this`
24,212✔
1042
    auto retain_self = shared_from_this();
49,350✔
1043

24,212✔
1044
    // make sure we have a read transaction
24,212✔
1045
    read_group();
49,350✔
1046

24,212✔
1047
    do_begin_transaction();
49,350✔
1048
}
49,350✔
1049

1050
void Realm::do_begin_transaction()
1051
{
51,888✔
1052
    CountGuard sending_notifications(m_is_sending_notifications);
51,888✔
1053
    try {
51,888✔
1054
        m_coordinator->promote_to_write(*this);
51,888✔
1055
    }
51,888✔
1056
    catch (_impl::UnsupportedSchemaChange const&) {
25,357✔
1057
        translate_schema_error();
×
1058
    }
×
1059
    cache_new_schema();
51,888✔
1060

25,357✔
1061
    if (m_transaction && !m_transaction->has_unsynced_commits()) {
51,888✔
1062
        call_completion_callbacks();
49,838✔
1063
    }
49,838✔
1064
}
51,888✔
1065

1066
void Realm::commit_transaction()
1067
{
43,592✔
1068
    check_can_create_write_transaction(this);
43,592✔
1069

21,332✔
1070
    if (!is_in_transaction()) {
43,592✔
1071
        throw WrongTransactionState("Can't commit a non-existing write transaction");
×
1072
    }
×
1073

21,332✔
1074
    DB::VersionID prev_version = transaction().get_version_of_current_transaction();
43,592✔
1075
    if (auto audit = audit_context()) {
43,592✔
1076
        audit->prepare_for_write(prev_version);
91✔
1077
    }
91✔
1078

21,332✔
1079
    m_coordinator->commit_write(*this, /* commit_to_disk */ true);
43,592✔
1080
    cache_new_schema();
43,592✔
1081

21,332✔
1082
    // Realm might have been closed
21,332✔
1083
    if (m_transaction) {
43,592✔
1084
        // Any previous async commits got flushed along with the sync commit
21,331✔
1085
        call_completion_callbacks();
43,590✔
1086
        // If we have pending async writes we need to rerequest the write mutex
21,331✔
1087
        check_pending_write_requests();
43,590✔
1088
    }
43,590✔
1089
    if (auto audit = audit_context()) {
43,592✔
1090
        audit->record_write(prev_version, transaction().get_version_of_current_transaction());
91✔
1091
    }
91✔
1092
}
43,592✔
1093

1094
void Realm::cancel_transaction()
1095
{
2,832✔
1096
    check_can_create_write_transaction(this);
2,832✔
1097

1,417✔
1098
    if (m_is_running_async_commit_completions) {
2,832✔
1099
        throw WrongTransactionState("Can't cancel a write transaction from inside a commit completion callback.");
×
1100
    }
×
1101
    if (!is_in_transaction()) {
2,832✔
1102
        throw WrongTransactionState("Can't cancel a non-existing write transaction");
×
1103
    }
×
1104

1,417✔
1105
    transaction::cancel(transaction(), m_binding_context.get());
2,832✔
1106

1,417✔
1107
    if (m_transaction && !m_is_running_async_writes) {
2,832✔
1108
        if (m_async_write_q.empty()) {
2,824✔
1109
            end_current_write();
2,816✔
1110
        }
2,816✔
1111
        else {
8✔
1112
            check_pending_write_requests();
8✔
1113
        }
8✔
1114
    }
2,824✔
1115
}
2,832✔
1116

1117
void Realm::invalidate()
1118
{
298✔
1119
    verify_thread();
298✔
1120
    verify_open();
298✔
1121

149✔
1122
    if (m_is_sending_notifications) {
298✔
1123
        // This was originally because closing the Realm during notification
3✔
1124
        // sending would break things, but we now support that. However, it's a
3✔
1125
        // breaking change so we keep the old behavior for now.
3✔
1126
        return;
6✔
1127
    }
6✔
1128

146✔
1129
    if (is_in_transaction()) {
292✔
1130
        cancel_transaction();
172✔
1131
    }
172✔
1132

146✔
1133
    do_invalidate();
292✔
1134
}
292✔
1135

1136
void Realm::do_invalidate()
1137
{
4,587✔
1138
    if (!m_config.immutable() && m_transaction) {
4,587✔
1139
        m_transaction->prepare_for_close();
4,481✔
1140
        call_completion_callbacks();
4,481✔
1141
        transaction().close();
4,481✔
1142
    }
4,481✔
1143

2,278✔
1144
    m_transaction = nullptr;
4,587✔
1145
    m_async_write_q.clear();
4,587✔
1146
    m_async_commit_q.clear();
4,587✔
1147
}
4,587✔
1148

1149
bool Realm::compact()
1150
{
8✔
1151
    verify_thread();
8✔
1152
    verify_open();
8✔
1153

4✔
1154
    if (m_config.immutable() || m_config.read_only()) {
8✔
1155
        throw WrongTransactionState("Can't compact a read-only Realm");
4✔
1156
    }
4✔
1157
    if (is_in_transaction()) {
4✔
1158
        throw WrongTransactionState("Can't compact a Realm within a write transaction");
×
1159
    }
×
1160

2✔
1161
    verify_open();
4✔
1162
    m_transaction = nullptr;
4✔
1163
    return m_coordinator->compact();
4✔
1164
}
4✔
1165

1166
void Realm::convert(const Config& config, bool merge_into_existing)
1167
{
60✔
1168
    verify_thread();
60✔
1169
    verify_open();
60✔
1170

30✔
1171
#if REALM_ENABLE_SYNC
60✔
1172
    auto src_is_flx_sync = m_config.sync_config && m_config.sync_config->flx_sync_requested;
60✔
1173
    auto dst_is_flx_sync = config.sync_config && config.sync_config->flx_sync_requested;
60✔
1174
    auto dst_is_pbs_sync = config.sync_config && !config.sync_config->flx_sync_requested;
60✔
1175

30✔
1176
    if (dst_is_flx_sync && !src_is_flx_sync) {
60✔
1177
        throw IllegalOperation(
4✔
1178
            "Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled");
4✔
1179
    }
4✔
1180
    if (dst_is_pbs_sync && src_is_flx_sync) {
56✔
1181
        throw IllegalOperation(
2✔
1182
            "Realm cannot be converted from a flexible sync realm to a partition based sync realm");
2✔
1183
    }
2✔
1184

27✔
1185
#endif
54✔
1186

27✔
1187
    if (merge_into_existing && util::File::exists(config.path)) {
54✔
1188
        auto destination_realm = Realm::get_shared_realm(config);
6✔
1189
        destination_realm->begin_transaction();
6✔
1190
        auto destination = destination_realm->transaction_ref();
6✔
1191
        m_transaction->copy_to(destination);
6✔
1192
        destination_realm->commit_transaction();
6✔
1193
        return;
6✔
1194
    }
6✔
1195

24✔
1196
    if (config.encryption_key.size() && config.encryption_key.size() != 64) {
48!
1197
        throw InvalidEncryptionKey();
×
1198
    }
×
1199

24✔
1200
    auto& tr = transaction();
48✔
1201
    auto repl = tr.get_replication();
48✔
1202
    bool src_is_sync = repl && repl->get_history_type() == Replication::hist_SyncClient;
48✔
1203
    bool dst_is_sync = config.sync_config || config.force_sync_history;
48✔
1204

24✔
1205
    if (dst_is_sync) {
48✔
1206
        m_coordinator->write_copy(config.path, config.encryption_key.data());
20✔
1207
        if (!src_is_sync) {
20✔
1208
#if REALM_ENABLE_SYNC
8✔
1209
            DBOptions options;
8✔
1210
            if (config.encryption_key.size()) {
8✔
1211
                options.encryption_key = config.encryption_key.data();
×
1212
            }
×
1213
            auto db = DB::create(make_in_realm_history(), config.path, options);
8✔
1214
            db->create_new_history(sync::make_client_replication());
8✔
1215
#endif
8✔
1216
        }
8✔
1217
    }
20✔
1218
    else {
28✔
1219
        tr.write(config.path, config.encryption_key.data());
28✔
1220
    }
28✔
1221
}
48✔
1222

1223
OwnedBinaryData Realm::write_copy()
1224
{
6✔
1225
    verify_thread();
6✔
1226
    BinaryData buffer = read_group().write_to_mem();
6✔
1227

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

1233
void Realm::notify()
1234
{
25,291✔
1235
    if (is_closed() || is_in_transaction() || is_frozen()) {
25,291✔
1236
        return;
338✔
1237
    }
338✔
1238

10,946✔
1239
    verify_thread();
24,953✔
1240

10,946✔
1241
    // Any of the callbacks to user code below could drop the last remaining
10,946✔
1242
    // strong reference to `this`
10,946✔
1243
    auto retain_self = shared_from_this();
24,953✔
1244

10,946✔
1245
    if (m_binding_context) {
24,953✔
1246
        m_binding_context->before_notify();
601✔
1247
        if (is_closed() || is_in_transaction()) {
601✔
1248
            return;
×
1249
        }
×
1250
    }
24,953✔
1251

10,946✔
1252
    if (!m_coordinator->can_advance(*this)) {
24,953✔
1253
        CountGuard sending_notifications(m_is_sending_notifications);
17,725✔
1254
        m_coordinator->process_available_async(*this);
17,725✔
1255
        return;
17,725✔
1256
    }
17,725✔
1257

2,941✔
1258
    if (m_binding_context) {
7,228✔
1259
        m_binding_context->changes_available();
80✔
1260

40✔
1261
        // changes_available() may have advanced the read version, and if
40✔
1262
        // so we don't need to do anything further
40✔
1263
        if (!m_coordinator->can_advance(*this))
80✔
1264
            return;
4✔
1265
    }
7,224✔
1266

2,939✔
1267
    CountGuard sending_notifications(m_is_sending_notifications);
7,224✔
1268
    if (m_auto_refresh) {
7,227✔
1269
        if (m_transaction) {
7,224✔
1270
            try {
7,162✔
1271
                m_coordinator->advance_to_ready(*this);
7,162✔
1272
            }
7,162✔
1273
            catch (_impl::UnsupportedSchemaChange const&) {
2,905✔
1274
                translate_schema_error();
×
1275
            }
×
1276
            if (!is_closed())
7,162✔
1277
                cache_new_schema();
7,160✔
1278
        }
7,162✔
1279
        else {
62✔
1280
            if (m_binding_context) {
62✔
1281
                m_binding_context->did_change({}, {});
4✔
1282
            }
4✔
1283
            if (!is_closed()) {
62✔
1284
                m_coordinator->process_available_async(*this);
60✔
1285
            }
60✔
1286
        }
62✔
1287
    }
7,224✔
1288
}
7,224✔
1289

1290
bool Realm::refresh()
1291
{
15,474✔
1292
    verify_thread();
15,474✔
1293
    return do_refresh();
15,474✔
1294
}
15,474✔
1295

1296
bool Realm::do_refresh()
1297
{
45,165✔
1298
    // Frozen Realms never change.
22,282✔
1299
    if (is_frozen()) {
45,165✔
1300
        return false;
250✔
1301
    }
250✔
1302

22,157✔
1303
    if (m_config.immutable()) {
44,915✔
1304
        throw WrongTransactionState("Can't refresh an immutable Realm.");
4✔
1305
    }
4✔
1306

22,155✔
1307
    // can't be any new changes if we're in a write transaction
22,155✔
1308
    if (is_in_transaction()) {
44,911✔
1309
        return false;
30✔
1310
    }
30✔
1311
    // don't advance if we're already in the process of advancing as that just
22,140✔
1312
    // makes things needlessly complicated
22,140✔
1313
    if (m_is_sending_notifications) {
44,881✔
1314
        return false;
8✔
1315
    }
8✔
1316

22,136✔
1317
    // Any of the callbacks to user code below could drop the last remaining
22,136✔
1318
    // strong reference to `this`
22,136✔
1319
    auto retain_self = shared_from_this();
44,873✔
1320

22,136✔
1321
    CountGuard sending_notifications(m_is_sending_notifications);
44,873✔
1322
    if (m_binding_context) {
44,873✔
1323
        m_binding_context->before_notify();
138✔
1324
    }
138✔
1325
    if (m_transaction) {
44,873✔
1326
        try {
9,932✔
1327
            bool version_changed = m_coordinator->advance_to_latest(*this);
9,932✔
1328
            if (is_closed())
9,932✔
1329
                return false;
6✔
1330
            cache_new_schema();
9,926✔
1331
            return version_changed;
9,926✔
1332
        }
9,926✔
1333
        catch (_impl::UnsupportedSchemaChange const&) {
4✔
1334
            translate_schema_error();
4✔
1335
        }
4✔
1336
    }
9,932✔
1337

22,136✔
1338
    // No current read transaction, so just create a new one
22,136✔
1339
    read_group();
39,876✔
1340
    m_coordinator->process_available_async(*this);
34,941✔
1341
    return true;
34,941✔
1342
}
44,873✔
1343

1344
void Realm::set_auto_refresh(bool auto_refresh)
1345
{
16✔
1346
    if (is_frozen() && auto_refresh) {
16✔
1347
        throw WrongTransactionState("Auto-refresh cannot be enabled for frozen Realms.");
2✔
1348
    }
2✔
1349
    m_auto_refresh = auto_refresh;
14✔
1350
}
14✔
1351

1352

1353
bool Realm::can_deliver_notifications() const noexcept
1354
{
5,698✔
1355
    if (m_config.immutable() || !m_config.automatic_change_notifications) {
5,698✔
1356
        return false;
5,642✔
1357
    }
5,642✔
1358

28✔
1359
    if (!m_scheduler || !m_scheduler->can_invoke()) {
56✔
1360
        return false;
×
1361
    }
×
1362

28✔
1363
    return true;
56✔
1364
}
56✔
1365

1366
uint64_t Realm::get_schema_version(const Realm::Config& config)
1367
{
×
1368
    auto coordinator = RealmCoordinator::get_coordinator(config.path);
×
1369
    auto version = coordinator->get_schema_version();
×
1370
    if (version == ObjectStore::NotVersioned)
×
1371
        version = ObjectStore::get_schema_version(coordinator->get_realm(config, util::none)->read_group());
×
1372
    return version;
×
1373
}
×
1374

1375

1376
bool Realm::is_frozen() const
1377
{
251,143✔
1378
    bool result = bool(m_frozen_version);
251,143✔
1379
    REALM_ASSERT_DEBUG(!result || !m_transaction || m_transaction->is_frozen());
251,143✔
1380
    return result;
251,143✔
1381
}
251,143✔
1382

1383
SharedRealm Realm::freeze()
1384
{
638✔
1385
    read_group(); // Freezing requires a read transaction
638✔
1386
    return m_coordinator->freeze_realm(*this);
638✔
1387
}
638✔
1388

1389
void Realm::copy_schema_from(const Realm& source)
1390
{
634✔
1391
    REALM_ASSERT(is_frozen());
634✔
1392
    REALM_ASSERT(m_frozen_version == source.read_transaction_version());
634✔
1393
    m_schema = source.m_schema;
634✔
1394
    m_schema_version = source.m_schema_version;
634✔
1395
    m_schema_transaction_version = m_frozen_version->version;
634✔
1396
    m_dynamic_schema = false;
634✔
1397
}
634✔
1398

1399
void Realm::close()
1400
{
4,317✔
1401
    if (is_closed()) {
4,317✔
1402
        return;
20✔
1403
    }
20✔
1404
    if (m_coordinator) {
4,297✔
1405
        m_coordinator->unregister_realm(this);
4,297✔
1406
    }
4,297✔
1407

2,133✔
1408
    do_invalidate();
4,297✔
1409

2,133✔
1410
    m_binding_context = nullptr;
4,297✔
1411
    m_coordinator = nullptr;
4,297✔
1412
    m_scheduler = nullptr;
4,297✔
1413
    m_config = {};
4,297✔
1414
}
4,297✔
1415

1416
void Realm::delete_files(const std::string& realm_file_path, bool* did_delete_realm)
1417
{
16✔
1418
    bool lock_successful = false;
16✔
1419
    try {
16✔
1420
        lock_successful = DB::call_with_lock(realm_file_path, [=](auto const& path) {
14✔
1421
            DB::delete_files(path, did_delete_realm);
12✔
1422
        });
12✔
1423
    }
16✔
1424
    catch (const FileAccessError& e) {
9✔
1425
        if (e.code() != ErrorCodes::FileNotFound) {
2✔
1426
            throw;
×
1427
        }
×
1428
        // Thrown only if the parent directory of the lock file does not exist,
1✔
1429
        // which obviously indicates that we didn't need to delete anything
1✔
1430
        if (did_delete_realm) {
2✔
1431
            *did_delete_realm = false;
2✔
1432
        }
2✔
1433
        return;
2✔
1434
    }
2✔
1435
    if (!lock_successful) {
14✔
1436
        throw FileAccessError(
2✔
1437
            ErrorCodes::DeleteOnOpenRealm,
2✔
1438
            util::format("Cannot delete files of an open Realm: '%1' is still in use.", realm_file_path),
2✔
1439
            realm_file_path);
2✔
1440
    }
2✔
1441
}
14✔
1442

1443
AuditInterface* Realm::audit_context() const noexcept
1444
{
148,996✔
1445
    return m_coordinator ? m_coordinator->audit_context() : nullptr;
148,996✔
1446
}
148,996✔
1447

1448
namespace {
1449

1450
/*********************************** PropId **********************************/
1451

1452
// The KeyPathResolver will build up a tree of these objects starting with the
1453
// first property. If a wildcard specifier is part of the path, one object can
1454
// have several children.
1455
struct PropId {
1456
    PropId(TableKey tk, ColKey ck, const Property* prop, const ObjectSchema* os, bool b)
1457
        : table_key(tk)
1458
        , col_key(ck)
1459
        , origin_prop(prop)
1460
        , target_schema(os)
1461
        , mandatory(b)
1462
    {
1,058✔
1463
    }
1,058✔
1464
    void expand(KeyPath& key_path, KeyPathArray& key_path_array) const;
1465

1466
    TableKey table_key;
1467
    ColKey col_key;
1468
    const Property* origin_prop;
1469
    const ObjectSchema* target_schema;
1470
    std::vector<PropId> children;
1471
    bool mandatory;
1472
};
1473

1474
// This function will create one KeyPath entry in key_path_array for every
1475
// branch in the tree,
1476
void PropId::expand(KeyPath& key_path, KeyPathArray& key_path_array) const
1477
{
1,020✔
1478
    key_path.emplace_back(table_key, col_key);
1,020✔
1479
    if (children.empty()) {
1,020✔
1480
        key_path_array.push_back(key_path);
676✔
1481
    }
676✔
1482
    else {
344✔
1483
        for (auto& child : children) {
354✔
1484
            child.expand(key_path, key_path_array);
354✔
1485
        }
354✔
1486
    }
344✔
1487
    key_path.pop_back();
1,020✔
1488
}
1,020✔
1489

1490
/****************************** KeyPathResolver ******************************/
1491

1492
class KeyPathResolver {
1493
public:
1494
    KeyPathResolver(Group& g, const Schema& schema)
1495
        : m_group(g)
1496
        , m_schema(schema)
1497
    {
666✔
1498
    }
666✔
1499

1500
    void resolve(const ObjectSchema* object_schema, const char* path)
1501
    {
666✔
1502
        m_full_path = path;
666✔
1503
        if (!_resolve(m_root_props, object_schema, path, true)) {
666✔
1504
            throw InvalidArgument(util::format("'%1' does not resolve in any valid key paths.", m_full_path));
2✔
1505
        }
2✔
1506
    }
666✔
1507

1508
    void expand(KeyPathArray& key_path_array) const
1509
    {
658✔
1510
        for (auto& elem : m_root_props) {
666✔
1511
            KeyPath key_path;
666✔
1512
            key_path.reserve(4);
666✔
1513
            elem.expand(key_path, key_path_array);
666✔
1514
        }
666✔
1515
    }
658✔
1516

1517
private:
1518
    std::pair<ColKey, const ObjectSchema*> get_col_key(const Property* prop);
1519
    bool _resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path, bool mandatory);
1520
    bool _resolve(PropId& current, const char* path);
1521

1522
    Group& m_group;
1523
    const char* m_full_path = nullptr;
1524
    const Schema& m_schema;
1525
    std::vector<PropId> m_root_props;
1526
};
1527

1528
// Get the column key for a specific Property. In case the property is representing a backlink
1529
// we need to look up the backlink column based on the forward link properties.
1530
std::pair<ColKey, const ObjectSchema*> KeyPathResolver::get_col_key(const Property* prop)
1531
{
1,058✔
1532
    ColKey col_key = prop->column_key;
1,058✔
1533
    const ObjectSchema* target_schema = nullptr;
1,058✔
1534
    if (prop->type == PropertyType::Object || prop->type == PropertyType::LinkingObjects) {
1,058✔
1535
        auto found_schema = m_schema.find(prop->object_type);
568✔
1536
        if (found_schema != m_schema.end()) {
568✔
1537
            target_schema = &*found_schema;
568✔
1538
            if (prop->type == PropertyType::LinkingObjects) {
568✔
1539
                auto origin_prop = target_schema->property_for_name(prop->link_origin_property_name);
222✔
1540
                auto origin_table = ObjectStore::table_for_object_type(m_group, target_schema->name);
222✔
1541
                col_key = origin_table->get_opposite_column(origin_prop->column_key);
222✔
1542
            }
222✔
1543
        }
568✔
1544
    }
568✔
1545
    return {col_key, target_schema};
1,058✔
1546
}
1,058✔
1547

1548
// This function will add one or more PropId objects to the props array. This array can either be the root
1549
// array in the KeyPathResolver or it can be the 'children' array in one PropId.
1550
bool KeyPathResolver::_resolve(std::vector<PropId>& props, const ObjectSchema* object_schema, const char* path,
1551
                               bool mandatory)
1552
{
1,022✔
1553
    if (*path == '*') {
1,022✔
1554
        path++;
22✔
1555
        // Add all properties
11✔
1556
        props.reserve(object_schema->persisted_properties.size() + object_schema->computed_properties.size());
22✔
1557
        for (auto& p : object_schema->persisted_properties) {
60✔
1558
            auto [col_key, target_schema] = get_col_key(&p);
60✔
1559
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
60✔
1560
        }
60✔
1561
        for (const auto& p : object_schema->computed_properties) {
16✔
1562
            auto [col_key, target_schema] = get_col_key(&p);
10✔
1563
            props.emplace_back(object_schema->table_key, col_key, &p, target_schema, false);
10✔
1564
        }
10✔
1565
    }
22✔
1566
    else {
1,000✔
1567
        auto p = find_chr(path, '.');
1,000✔
1568
        StringData property(path, p - path);
1,000✔
1569
        path = p;
1,000✔
1570
        auto prop = object_schema->property_for_public_name(property);
1,000✔
1571
        if (prop) {
1,000✔
1572
            auto [col_key, target_schema] = get_col_key(prop);
988✔
1573
            props.emplace_back(object_schema->table_key, col_key, prop, target_schema, true);
988✔
1574
        }
988✔
1575
        else {
12✔
1576
            if (mandatory) {
12✔
1577
                throw InvalidArgument(util::format("Property '%1' in KeyPath '%2' is not a valid property in %3.",
4✔
1578
                                                   property, m_full_path, object_schema->name));
4✔
1579
            }
4✔
1580
            else {
8✔
1581
                return false;
8✔
1582
            }
8✔
1583
        }
1,010✔
1584
    }
1,000✔
1585

505✔
1586
    if (*path++ == '.') {
1,010✔
1587
        auto it = props.begin();
346✔
1588
        while (it != props.end()) {
734✔
1589
            if (_resolve(*it, path)) {
388✔
1590
                ++it;
350✔
1591
            }
350✔
1592
            else {
38✔
1593
                it = props.erase(it);
38✔
1594
            }
38✔
1595
        }
388✔
1596
    }
346✔
1597
    return props.size();
1,010✔
1598
}
1,010✔
1599

1600
bool KeyPathResolver::_resolve(PropId& current, const char* path)
1601
{
388✔
1602
    auto object_schema = current.target_schema;
388✔
1603
    if (!object_schema) {
388✔
1604
        if (current.mandatory) {
32✔
1605
            throw InvalidArgument(
2✔
1606
                util::format("Property '%1' in KeyPath '%2' is not a collection of objects or an object "
2✔
1607
                             "reference, so it cannot be used as an intermediate keypath element.",
2✔
1608
                             current.origin_prop->public_name, m_full_path));
2✔
1609
        }
2✔
1610
        // Check if the rest of the path is stars. If not, we should exclude this property
15✔
1611
        do {
38✔
1612
            auto p = find_chr(path, '.');
38✔
1613
            StringData property(path, p - path);
38✔
1614
            path = p;
38✔
1615
            if (property != "*") {
38✔
1616
                return false;
24✔
1617
            }
24✔
1618
        } while (*path++ == '.');
14✔
1619
        return true;
18✔
1620
    }
356✔
1621
    // Target schema exists - proceed
178✔
1622
    return _resolve(current.children, object_schema, path, current.mandatory);
356✔
1623
}
356✔
1624

1625
} // namespace
1626

1627
KeyPathArray Realm::create_key_path_array(StringData table_name, const std::vector<std::string>& key_paths)
1628
{
652✔
1629
    std::vector<const char*> vec;
652✔
1630
    vec.reserve(key_paths.size());
652✔
1631
    for (auto& kp : key_paths) {
652✔
1632
        vec.push_back(kp.c_str());
652✔
1633
    }
652✔
1634
    return create_key_path_array(m_schema.find(table_name)->table_key, vec.size(), &vec.front());
652✔
1635
}
652✔
1636

1637
KeyPathArray Realm::create_key_path_array(TableKey table_key, size_t num_key_paths, const char** all_key_paths)
1638
{
666✔
1639
    auto object_schema = m_schema.find(table_key);
666✔
1640
    REALM_ASSERT(object_schema != m_schema.end());
666✔
1641
    KeyPathArray resolved_key_path_array;
666✔
1642
    for (size_t n = 0; n < num_key_paths; n++) {
1,332✔
1643
        KeyPathResolver resolver(read_group(), m_schema);
666✔
1644
        // Build property tree
333✔
1645
        resolver.resolve(&*object_schema, all_key_paths[n]);
666✔
1646
        // Expand tree into separate lines
333✔
1647
        resolver.expand(resolved_key_path_array);
666✔
1648
    }
666✔
1649
    return resolved_key_path_array;
666✔
1650
}
666✔
1651

1652
#ifdef REALM_DEBUG
1653
void Realm::print_key_path_array(const KeyPathArray& kpa)
1654
{
×
1655
    auto& g = read_group();
×
1656
    for (auto& kp : kpa) {
×
1657
        for (auto [tk, ck] : kp) {
×
1658
            auto table = g.get_table(tk);
×
1659
            std::cout << '{' << table->get_name() << ':';
×
1660
            if (ck.get_type() == col_type_BackLink) {
×
1661
                auto col_key = table->get_opposite_column(ck);
×
1662
                table = table->get_opposite_table(ck);
×
1663
                std::cout << '{' << table->get_name() << ':' << table->get_column_name(col_key) << "}->";
×
1664
            }
×
1665
            else {
×
1666
                std::cout << table->get_column_name(ck);
×
1667
            }
×
1668
            std::cout << '}';
×
1669
        }
×
1670
        std::cout << std::endl;
×
1671
    }
×
1672
}
×
1673
#endif
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